Per a couple requests, I've gone ahead and put together a very simple quiz game. Here's what the game does:
- Loads a welcome screen, with a button to start the game.
- Loads questions with 4 potential answers.
- Provides a simple scoring mechanism; 50 points for right answers, 50 points for wrong ones.
- Allows players a few seconds to answer questions, and gives a brief break between questions.
So, that's the basics of it. The source files are attached below. Please note, this was coded in an afternoon, so there are some error messages that pop up, and some really bad code in the actual quiz itself! I'm going to update this thread once I rewrite the code...
Now I'm going to walk through some parts of the code. This is not the complete program, so to get the complete program please download the source code and run it. I'm just going to show off some of the highlights...
One thing worth noting is that I created an IBOutlet for the buttons; this provides the ability to change the text on them so the answers will be "clickable". I figure that's a pretty straightforward way to program it. As you can see in the header I've initialized some variables like booleans and integers, and most importantly an array that we'll use to actually load the quiz itself.
In IB, there are 3 main labels: The question area, the "score" and the timer counter box (which, I labeled "lives" inadvertently thinking I was going to have a lives counter... just run with it).
So, now in the .m file, here are some of the notable functions... (Please note, these are not in order as they appear in the code, so if you're looking at the source, you may have to scroll a little bit to find these things)
This is just the initialization of the app, but it has some notable activities which might be worth noticing. First off, we've established some values for some of the variables that we'll be using in the game, including questionLive (a boolean), the questionNumber, myScore, etc. myLives will likely be killed in the next iteration of the game, since I'm not using that feature. You'll see how a lot of these get used later on. One of the handy pieces of code that I've used is the [... setTitle:] and [... setHidden:] features for the buttons. The buttons are UIButtons with IBOutlets attached to them in the Interface Builder, so we can manipulate them to be visible or hidden, or change the content of their text label. There are several states that buttons can be in when you set the title, but for convenience sake I only really care about UIControlStateNormal. Press the escape key while you're typing the statement, and a popup will appear with all the options...
The loadQuiz function is pretty straightforward, but it's important to note that this is a TERRIBLE way to program this. It SHOULD be nested arrays based on a dictionary, but for the sake of just getting this done I put it all in a flat NSArray. This is the thing that I'll be updating when I update this program, probably to access an independent file from resources like a plist or a txt file. Stay tuned for that update.
The "game" itself is in the function askQuestion:
Code:
-(void)askQuestion
{
// Unhide all the answer buttons.
[answerOne setHidden:NO];
[answerTwo setHidden:NO];
[answerThree setHidden:NO];
[answerFour setHidden:NO];
// Set the game to a "live" question (for timer purposes)
questionLive = YES;
// Set the time for the timer
time = 8.0;
// Go to the next question
questionNumber = questionNumber + 1;
Here you can see some initialization features, such as un-hiding the buttons, setting the questionLive variable to "YES" so that the timer knows it's in a question (I'll explain that in a moment), setting the time for the timer (how long the player gets to answer the question, in this case 8 seconds), and updating the questionNumber to move to the next row.
Code:
// THIS IS REALLY TERRIBLE CODE!!!
// We get the question from the questionNumber * the row that we look up in the array.
// This is absolutely horrible, just a placeholder until the right way.
// I cannot even begin to describe how wrong this solution is.
NSInteger row = 0;
if(questionNumber == 1)
{
row = questionNumber - 1;
}
else
{
row = ((questionNumber - 1) * 6);
}
// Set the question string, and set the buttons the the answers
NSString *selected = [theQuiz objectAtIndex:row];
NSString *activeQuestion = [[NSString alloc] initWithFormat:@"Question: %@", selected];
[answerOne setTitle:[theQuiz objectAtIndex:row+1] forState:UIControlStateNormal];
[answerTwo setTitle:[theQuiz objectAtIndex:row+2] forState:UIControlStateNormal];
[answerThree setTitle:[theQuiz objectAtIndex:row+3] forState:UIControlStateNormal];
[answerFour setTitle:[theQuiz objectAtIndex:row+4] forState:UIControlStateNormal];
rightAnswer = [[theQuiz objectAtIndex:row+5] intValue];
// Set theQuestion label to the active question
theQuestion.text = activeQuestion;
// Start the timer for the countdown
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(countDown) userInfo:nil repeats:YES];
[selected release];
[activeQuestion release];
So all that stuff should be relatively self-explanatory if you just take a little while to look at it. Again, this is shameful programming (by skimming through a flat array to pull the question, answers, and right answer variables) but in short order it was what I came up with. Also, note the activation of the timer object, which uses the "countDown" function to warn the user of how much longer they have to answer the question.
Here's the code for -(IBAction)buttonOne function just so you see what's going on in there:
Code:
- (IBAction)buttonOne
{
if(questionNumber == 0){
// This means that we are at the startup-state
// We need to make the other buttons visible, and start the game.
[answerTwo setHidden:NO];
[answerThree setHidden:NO];
[answerFour setHidden:NO];
[self askQuestion];
}
else
{
NSInteger theAnswerValue = 1;
[self checkAnswer:(int)theAnswerValue];
if(restartGame==YES)
{
// Create a restart game function.
}
}
}
Note that since button 1 is being used both as an answer button and as the "Let's play" game starter, we have to have a conditional statement in there to change it from the start button to a normal answer statement, which we accomplish by finding out if we're on question #0. If it's a normal question (in the 'else' portion of the conditional, covering all other situations), we set up a variable called theAnswerValue, which is the button number that they pressed, and then send that value to the function checkAnswer. This is creating a warning message in the process, so I need to check my syntax to code it right (it will run, but with warning errors - nothing severe though).
-(void)checkAnswer:(int)theAnswerValue
{
if(rightAnswer == theAnswerValue)
{
theQuestion.text = @"You're so money baby! Good work.";
myScore = myScore + 50;
}
else
{
theQuestion.text = @"Sorry weaksauce, you came up empty on that one!";
myScore = myScore - 50;
}
[self updateScore];
}
Notice rightAnswer is set in the askQuestion function, and is being compared to the theAnswerValue that was passed from the button action function (depending on what button was pressed, 1-4). If it is a match, they got it right, and they get a plithy response and 50 points. If it's not a match, they got it wrong, get shamed, and lose 50 points.
Then we call updateScore function:
Code:
-(void)updateScore
{
// If the score is being updated, the question is not live
questionLive = NO;
[timer invalidate];
// Hide the answers from the previous question
[answerOne setHidden:YES];
[answerTwo setHidden:YES];
[answerThree setHidden:YES];
[answerFour setHidden:YES];
NSString *scoreUpdate = [[NSString alloc] initWithFormat:@"Score: %d", myScore];
theScore.text = scoreUpdate;
[scoreUpdate release];
// END THE GAME.
NSInteger endOfQuiz = [theQuiz count];
if(((questionNumber) * 6) == endOfQuiz)
{
// Game is over.
if(myScore > 0)
{
NSString *finishingStatement = [[NSString alloc] initWithFormat:@"That's game!\nNice going \nYou scored %i!", myScore];
theQuestion.text = finishingStatement;
[finishingStatement release];
}
else
{
NSString *finishingStatement = [[NSString alloc] initWithFormat:@"That's game!\nWow. You're terrible! \nYou scored %i.", myScore];
theQuestion.text = finishingStatement;
[finishingStatement release];
}
theLives.text = @"";
// Make button 1 appear as a reset game button
restartGame = YES;
[answerOne setHidden:NO];
[answerOne setTitle:@"Restart the game" forState:UIControlStateNormal];
}
else
{
// Give a short rest between questions
time = 3.0;
// Initialize the timer
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(countDown) userInfo:nil repeats:YES];
}
}
So, that's essentially the rest of the heavy lifting the the app. The countDown function is worth a look, since that's where the timer runs through, but I think if you take a look at the source you should get what I'm doing in there.
I welcome your questions about what I did, and I eagerly encourage any and all suggestions for improvements, enhancements, and coding short-cuts that I could have taken. Like I said, the main thing that I'm going to work on next is to change the method that the array is assembled (so we're using a series of multidimensional arrays to control questions, answers, etc); provide a mechanism to have variable question difficulty (and award variable points), and set it up so questions & answers will appear randomly. Also, I'd like to add a function to show the user the right answer when they get it wrong (or have that a setting that they can turn on or off).
Anyways, thanks for reading! Hope this was helpful.
Yeah, that's the next piece I want to do. Like I said, the method I'm using isn't good at all! Haha. So yes, I'm going to update this file and provide some detail on how to use the plist and file-derived arrays.
All this did was provide a quick way to create the array using a txt file called quizgame.txt moved in the 'Resources' group under "Groups & Files". Everything else in the code stays the same for now...
To explain what's going on in this function now:
1 - Create an instance of an NSBundle called bundle that we can use to access the file system. This way we don't need to actually write the exact path all the way to the root, this'll do it for us.
2 - Create a string called textFilePath which uses the bundle to create a string with the location of the file we're trying to access. Note that you don't need to include the extension when describing the file, but you do need to set it in the 'ofType'.
3 - Create a string called fileContents which contains the entire contents of the file. Worth noting, each line of the file will designate a new line in the array.
4 - Populate the array quizArray with the contents of the fileContents, using the method componentsSeparatedByString of @"\n" which means every line break means it's a new element in the array.
5 - Set theQuiz (our standing array of questions & answers) to quizArray and then...
... release quizArray
So that's about it. The only real hiccup that I had in testing this is that the text file cannot have an ending blank line in it, as if you were going to write out more content. It'll record that as a new line in the array, and the game-ending code will fry (because it'll think there are more questions & answers). The formatting is still the same: 1st line is the question, lines 2-5 are answers, and line 6 is the right answer. So the text file might look like this...
quizgame.txt:
Code:
Who played the president on the TV show 'The West Wing'?
Alan Alda
Rob Lowe
Martin Sheen
Jack Bauer
3
What year did The Daily Show premier?
1994
1996
1998
2001
2
Who replaced Conan O'Brien on the Late Night show?
Jimmy Kimmel
Jimmy Tudeski
Jimmy Dean
Jimmy Fallon
4
So that's a quick upgrade to the code. Next, I'm trying to decide if I want to try to use a SQLite db or a plist file to actually break out these arrays in to something a little more meaningful (and add some new functionality in the process).
I'm sure he can, once he's polished up his silver platter.
Right?
Dru, you should download the zip from the second post on this thread, change the function (void)loadQuiz to what I showed in the follow-up. Then create your own txt file of questions and answers, copy it to your project, and run the game. Should be literally that simple, since the main processes of the game haven't changed.
I studied a bit your quiz tutorial and now trying to make something similar but I desperately need your teaching!
I uploaded my files herehttp://www.jinweb.net/ferdi/iQQQ.zip, if u have time plz have a look and give me some directions how to move forward from here
Your game has 4 answers as buttons, but what i tried to do is basically to have a simple interface where the answers are all embedded in the question, and there are 4 fixed answer buttons A,B,C,and D.
Each time an answer is clicked (or Next button is clicked) the answer variable (ABCD) will be stored, and the next question will be displayed, and so on.
At the end, a table should show the list of questions (1.2.3...), selected answer (user selected) and correct answer (red if wrong), with a total percentage grade.
FIRST THING TO DO:
I have created three string variables and each contains a question.
now what i want to do is to have a loop function attached to button A,B,C, and D to store selected answer and move on to next question.
There's a few things that come to mind. First off, using the UIToolbar is different from using buttons... I haven't really played around with them much, but I think with a little searching you can find out how to trigger the action based upon what button the user presses (it's different from how buttons are treated, but not by much).
It covers NSMutableArray which is a modifiable array you can use to dynamically store your answers. I'm assuming that what you're trying to do is get a user to the end of the quiz before telling them how many they got right or wrong. That just means you'll need to do something simple at the end like:
Code:
i = 0
rightanswers = 0
for i++{
if ((questionArray objectAtIndex:i) == (answerArray objectAtIndex:i)){
rightanswers = rightanswers++;
}
}
I figured out how to use the mutablearray and now restarted developing the quiz with intro view, main view and result view (all following switchview tutorial).
My problem is very banal, when I switch from intro view (clicking Start button), the main view is shown, but the toolbar's position is not where I see from Interface builder.
Intro View:
Main View:
What am I doing wrong? I also played with the size, position and autosizing, but no luck... also tried with other tools such as simple buttons (instead of toolbar), but the problem resists...
[SOLVED]: The size of the first view was : 480x300, and the second view was 460x320... don't know why, but I resized second view to 480x300 and resolved the problem
Last edited by jin8k; 03-30-2009 at 07:33 AM.
Reason: SOLVED
Okay I've edited this to a little more of my likings. I'm no developer at all, I've never coded in my life. But when ever at the end of the game taping the reset only freezes there. Any way to create a control to reset to the splash?
I took a look at this tutorial thread and it has been very helpful indeed! Kudos to you kind sir!
I am trying to do a similar quiz app and wanted to put images to questions. For instance if its a sports question I would put a sports picture, music question pic of guitar, etc.
Could you perhaps give any guidance on this? I am thinking it would of course be another array with images loaded into it, however I am not sure how to tie the images to transition when the question was answered.
Hello, This tutoril is very good! thx!
I hace a question, how can I make Random Question Apear? I know how to create a random generator, but not to make it work with questions.
Thx
Hello, This tutoril is very good! thx!
I hace a question, how can I make Random Question Apear? I know how to create a random generator, but not to make it work with questions.
Thx
Once you have 'random() % numberOfQuestions', just multiply by 6 (in this case of a flat array) and index into the array. You should also randomize the position of each answer to mix things up.
I started out with random in 'That Ain't It! Trivia Game', but eventually moved to keeping track of which questions were displayed because using random was causing repeat questions more often than we liked.
Once you have 'random() % numberOfQuestions', just multiply by 6 (in this case of a flat array) and index into the array. You should also randomize the position of each answer to mix things up.
I started out with random in 'That Ain't It! Trivia Game', but eventually moved to keeping track of which questions were displayed because using random was causing repeat questions more often than we liked.
Thx Dude! Finnaly ive got an answer.
Can you upload a Sample Code? using the Quiz Game tutorial?
Thx Dude! Finnaly ive got an answer.
Can you upload a Sample Code? using the Quiz Game tutorial?
How do I create random number of questions?
Thx!
Sorry, I don't have any sample code to upload. What do you mean by 'create a random number of questions?' I would probably be more helpful in answering questions about the That Ain't It! trivia app I wrote. You can check out the YouTube video (search IMAKcreations) and I'll be happy to answer any questions about it. It took quite a bit longer to write than 1 day, but I learned a lot.