[Userscript] Self-Study Quiz

I didn’t have the link at the time, but this is the one I meant:

And this:

2 Likes

I’ve known about this script for quite a while now and about a week ago I finally decided to try it out. It’s been a blast - works smooth and fast.

One thing I’ve been trying to do with my studies is to learn all readings when I study a kanji (not only the main reading) which has helped me a ton with vocab. So to help with that I have a kanji-only preset that gives me a level (or ten) worth of kanji and I work through them. I tweaked a bit my local installation of the script to display all readings in the Help popup so I can review them after I try to guess them for myself.

But I’ve been wanting to type in all of the readings. I know I can type in the secondary reading, get an input shake, delete it and type the primary one. I could also make it so that it deletes it for me so that I don’t have to lose time on that. But is there an easy way to split the Kanji Reading question into multiple ones? I still want to have one question for a meaning, but also one question per each each reading (be that onyomi or kunyomi). Is there an array with data where I can split some of the objects to produce more questions in the quiz? I’m happy to work it out myself if you can point me to a piece of state or a function. I’ve looked through the code, but am unsure of where to make a tweak.

It would be a lot simpler to make a single Reading question accept multiple comma-separated answers (or space-separated).

You can use the following function to split your answer into an array of answers:

function split_list(str) {return str.replace(/、/g,',').replace(/[\s ]+/g,' ').trim().replace(/ *, */g, ',').split(',').filter(function(name) {return (name.length > 0);});}

In the script, search for the submit_answer() function, and do something like this:

case 'reading':
  answer = wanakana.toHiragana(answer);
  if (itype === 'kanji') {
    let answers = split_list(answer);
    let correct_count = answers.filter((ans) => (qinfo.answer.good.indexof(ans) >= 0)).length;
    let incorrect_count = answers.length - correct_count;
    if (correct_count === answers.length) {
      action = 'correct';
    } else if (correct_count > 0 && incorrect_count == 0) {
      action = 'shake';
      message = 'You entered '+correct_count+' out of '+answers.length+' readings.';
    } else {
      // Leave the "else" the same for wrong answers
    }
  } else {
   // Leave everything the same for vocab readings
  }

You’ll also need to change the question generator to not filter out Wanikani’s non-preferred readings. In the populate_qinfo() function, change:

if (qinfo.item.type === 'vocabulary' || reading.accepted_answer) {

to

if (true) {

Note: I’m just typing the above code from my tablet, so it’s not tested. It might need some debugging.

Also, you could add a setting to make this behavior selectable.

1 Like

I think that should be incorrect_count !== 0, but it also might be an implicitly covered case. But yeah, I’ll do some testing. You’re right, this approach might be easiest and it should fit my purpose. Sorry to have made you code on a tablet ;D I’ll try it out after work today. Thank you.

I’m not sure which line you mean, but if you’re referring to this one:

} else if (correct_count > 0 && incorrect_count == 0) {

I think it’s correct for what I intended, which is that it warns if all of the readings you gave were correct, but you didn’t give all of the ones that WK knows. If it contains any incorrect answers, it should go to the fail condition in the final ‘else’.

This is what I ended up with:

case 'reading':
    answer = wanakana.toHiragana(answer);
    if (itype === 'kanji') {
        let answers = split_list(answer);
        let correct_count = answers.filter((ans) => (qinfo.answer.good.indexOf(ans) >= 0)).length;
        let incorrect_count = answers.length - correct_count;
        if (correct_count === qinfo.answer.good.length) {
            action = 'correct';
        } else if (correct_count > 0 && incorrect_count == 0) {
            action = 'shake';
            message = 'You entered '+correct_count+' out of '+qinfo.answer.good.length+' readings.';
        } else {
            var bad = qinfo.answer.bad.map((english) => wanakana.toHiragana(english.toLowerCase()));
            if (answers.some(ans => bad.indexOf(ans) >= 0)) {
                action = 'shake';
                message = 'We\'re looking for the reading, not the meaning';
            } else if (answers.some(ans => !wanakana.isKana(ans))) {
                action = 'shake';
                message = 'Your answer contains invalid characters';
            } else {
                action = 'incorrect';
            }
        }
    } else {
        // Same for vocab readings
    }

The only difference being if (correct_count === answers.length) became if (correct_count === qinfo.answer.good.length) since otherwise it would mark your answer as correct if all your provided answers were correct, even if there were missing some.

The else for bad answers needed just a slight tweak to make it work with arrays, but otherwise is unchanged.

Also, you were right about incorrect_count == 0 (and yes, that’s what I was referring to). I guess I was thinking of a shake on providing some correct and some incorrect answers. But marking it as invalid makes more sense.

In all that code you wrote indexof was your only typo, so I’d say your coding skills on a tablet are top notch. I’ll probably look to add that setting to switch the behavior later this week since it can get taxing to have to recall everything. I’m pretty happy with the result. Thank you very much for the help!

1 Like