Friday, September 16, 2011

Introducing the start and ending quiz number functionality

Ok, so, yesterday we finally managed to do a validation check on low and high start and end quiz numbers. The next step is to save these to preferences, and than consult them during the multiple choice quiz game to figure out which numbers to quiz upon.

The funny thing about getting into c-based languages is that something as simple as a boolean can be complicated, unlike the nice easy "boolean" primitive in Java. I played around with it earlier and I think there was something defined in the system to make a BOOL a zero or greater zero, and NO was zero and YES was 1. But, let's take another look.

This posting in SO:

http://stackoverflow.com/questions/541289/objective-c-bool-vs-bool

says that BOOL is the more or less default one to use for objective C, since it's a typedef defined in objc.h.

So, let's use that:


BOOL valid = YES;

if (endNumInt > [appState.questionDict count]) {

valid = NO;
...
}

if (valid) {

// save date

}



So, here's the current iteration of the validation routine:


if (valid) {
prefs = [NSUserDefaults standardUserDefaults];

// save level
[prefs setInteger:jlptLevel forKey:@"jlptLevel"];

[prefs setInteger:startNumInt forKey:@"startNum"];

[prefs setInteger:endNumInt forKey:@"endNum"];

// saving it all
[prefs synchronize];

}

Let's pull the prefs fields and print them to the console to make sure they make sense, though.

And, after validating the error validation work2, let's test the save. Good, we got this program output:

2011-09-16 14:41:26.417 JlptQuizApp[2975:207] SettingsController, saved jlptLevel: 4:
2011-09-16 14:41:26.418 JlptQuizApp[2975:207] SettingsController, saved startNum : 4:
2011-09-16 14:41:26.419 JlptQuizApp[2975:207] SettingsController, saved endNum : 6:

Sweet. Now we're ready to temporarily venture beyond the editing routine to utilize these settings in the quiz portions of the app.

So, we're back into a little bit of the tricky startup logic. What we learned from the JLPT Quiz App for Android is that it's best just to start from scratch each time going into a quiz. Don't try to save the order of the randomized array, just reorder it. Sorry, but if the app gets taken out while your doing it, you're starting over.

So, one question I have, is if you have a property, do you have to have backing variable declaration? Apparently not, because I have several such properties in AppState.

For example, these two:

@property (nonatomic, retain) NSMutableDictionary *questionDict;
@property (nonatomic, retain) NSArray *shuffledQuestionDictKeyList;


So, it appears to me that there's not a question shuffled array, it looks like I'm just extracting the keys or creating a list of keys, and then shuffling, and then sequentially accessing that. So the trick is going to be to only extract from the the list between the start and end number from prefs.

So, here's the code that does it:

appState.currentShuffledArrayIndex = 1;

NSArray *keys = [appState.questionDict allKeys];
appState.shuffledQuestionDictKeyList = [keys shuffledArray];

So probably, all I have to do is loop from the start num to the end num into keys.

It looks like this:

NSInteger startNum = [Utils getStartNum];
NSInteger endNum = [Utils getEndNum];

for (int i = startStart; i <= endNum; i++){

}


Now, the question is, how to alloc and init the NSArray. Here we go.

NSMutableArray* abc = [NSMutableArray array];

Here's the loop.

for (int i = startStart; i <= endNum; i++){

[keys addObject:i]
}


But i isn't an object. What was the keys returning?

Ok, here's how I'm trying to to convert it to an object:


NSNumber myInteger = [NSNumber intValue:i];


Shoot - I forgot to look under the class methods in the doc. Here it is:

numberWithInt


So,


NSInteger startNum = [Utils getStartNum];
NSInteger endNum = [Utils getEndNum];

NSMutableArray* keys = [NSMutableArray array];

for (int i = startNum; i <= endNum; i++){

NSNumber *myKey = [NSNumber numberWithInt:i];

[keys addObject:myKey];
}



Ok, it's crashing after one access, it brought up the value for sky, sora. Let's look at the DB and see if it's in the 4-6 range.

Hmm. It's hitting the current question twice before displaying, after a enter settings and then click on quiz.

Then, it's always hitting the same value, sora, so it's not really shuffling anything,

So, the values it's creating are ok; if I print a the values like this:

for (int i = startNum; i <= endNum; i++){

NSNumber *myKey = [NSNumber numberWithInt:i];
NSLog(@"QuizAppDelegate, myKey: %d", [myKey intValue]);

[keys addObject:myKey];
}



I get this:

2011-09-16 16:54:09.166 JlptQuizApp[3585:207] QuizAppDelegate, myKey: 4
2011-09-16 16:54:09.167 JlptQuizApp[3585:207] QuizAppDelegate, myKey: 5
2011-09-16 16:54:09.167 JlptQuizApp[3585:207] QuizAppDelegate, myKey: 6


Ok, so, that's fine.

So, 4, 5 and 6 when I load the word dictionary are these:

011-09-16 23:00:37.520 JlptQuizApp[3868:207] AppState, question number 4, kanji is 赤ん坊
2011-09-16 23:00:37.521 JlptQuizApp[3868:207] AppState, question number 5, kanji is 空く
2011-09-16 23:00:37.523 JlptQuizApp[3868:207] AppState, question number 6, kanji is 浅い
2

Ah, ok, the problem is I haven't order the table by frequency.

So, all we need to do is change this:


NSString *querySQL = [NSString stringWithFormat: @"SELECT kanji, hiragana, english FROM all_words WHERE level = %d;", level];


to this:


NSString *querySQL = [NSString stringWithFormat: @"SELECT kanji, hiragana, english FROM all_words WHERE level = %d;", level];

And that gives is these three:


2011-09-16 23:17:30.686 JlptQuizApp[3965:207] AppState, question number 4, kanji is 彼
2011-09-16 23:17:30.687 JlptQuizApp[3965:207] AppState, question number 5, kanji is そんな
2011-09-16 23:17:30.688 JlptQuizApp[3965:207] AppState, question number 6, kanji is くれる

which, meaning "he", "that" and "give to me", humble form, fit the mold of frequently used words.

Ok. but we're still back at the original problem. We're getting a SIGABRT on this statement:

NSNumber *key = [shuffledQuestionDictKeyList objectAtIndex:currentShuffledArrayIndex];


So, what does this mean? Well, the crash show the index is 3; and if there are 3 objects in the shuffledQuestionDictKeyList, then that's the problem, it's a zero based array. So, why 3?

The stack trace shows it's coming from here:

4 JlptQuizApp 0x000032d3 -[QuestionController advance_question:] + 218

See, the problem is I have this cntr_question in the database loader, and that starts at 1, and is assigned as a variable in question. That's not a problem, but then, I use that as a key to the word dictionary. That's also not a problem. But, when then I get the key set, and assign it to a (probably unordered) array of keys. This is fine, because, I just shuffle the key anyway. Then the program starts going through the (randomized) array of keys in a sequential manner. This is what that "currentShuffledArrayIndex" is all about.

Hmmm - I'm really wondering about this logic in the AppState init:

currentShuffledArrayIndex = [prefs integerForKey:@"currentQuestionNumber"];

// question number starts at 1
if (0 == currentShuffledArrayIndex) {
currentShuffledArrayIndex = 1;
}

I don't like this logic - especially pulling the index from prefs. That should just get reset every time. It doesn't belong in prefs. That's throwback logic from the template program.

We'll change it to this.


currentShuffledArrayIndex = 0;


Ok, what's next? Ok, will if I start it at zero, and look at two questions, the index is still getting to 3 and crashing, only after 2 question instead of one. Why is it getting to 3?

This is probably the problem,

(void) advance_question: (BOOL) animated {
// won't be animated on the first call
if (animated) {
appState.currentShuffledArrayIndex++;
[appState saveState];
}


That first comment is probably no longer valid. So, I either should advance the question at the end somehow, or just set it to -1 at the beginning. How about advancing it on "next question?"

Ok - good. It got through all 3 questions. However, I have a couple of new questions myself. First, why isn't the order ever different? And second (and more fun) is how to handle the completion of the quiz (this is where I get to display some original art by one of my customers and say "Congratulation - you've gone to the next level".

We'll work on those next.

No comments:

Post a Comment