I've made a quiz and would like for it to shuffle the answers to make it more challenging. This means that when there should be a method that can check which button is the answer. If answer is button 1 then button 1 contains the right answer. If answer is button 4 then button 4 contains the right answer etc.
Right now the code shuffles the answer each time I play a new game, but it sets the titles of all 4 buttons with the same title and the correct answer is always button 1.
Any ideas?
Philip
This is what I have.
- (IBAction)PlayHistory_Easy:(id)sender {
// The questions
history_Easy = [[NSMutableArray alloc] initWithObjects:kHisQ_E1, kHisQ_E2, kHisQ_E3, kHisQ_E4,nil];
numberOfQuestions = [history_Easy count];
index = arc4random() % numberOfQuestions--;
NSString *dd = [history_Easy objectAtIndex:index];
questionLabel.text = dd;
// Setup code to shuffle answers
NSMutableArray *randomizeAnswer = [[NSMutableArray alloc] initWithObjects:btnOne, btnTwo, btnThree, btnFour, nil];
NSMutableArray *ranAns = [[NSMutableArray alloc] initWithObjects:#"Answer 1", #"Answer 2", #"Answer 3", #"Answer 4", nil];
if ([questionLabel.text isEqualToString:kHisQ_E1]) { // Answer 1st button
for (UIButton *btn in randomizeAnswer) {
int i = arc4random() % ranAns.count;
NSString *str = [ranAns objectAtIndex:i];
[btnOne setTitle:[ranAns objectAtIndex:i] forState:UIControlStateNormal];
[btnTwo setTitle:[ranAns objectAtIndex:i] forState:UIControlStateNormal];
[btnThree setTitle:[ranAns objectAtIndex:i] forState:UIControlStateNormal];
[btnFour setTitle:[ranAns objectAtIndex:i] forState:UIControlStateNormal];
}
}
Check the answer. Here checking the answer is done manually, but in order to shuffle the answer, the code be different obviously.
- (IBAction)button1:(id)sender {
if ([questionLabel.text isEqualToString:kHisQ_E1]) {
[self playRIGHTAnswer_EASY];
}
}
- (IBAction)button2:(id)sender {
if ([questionLabel.text isEqualToString:kHisQ_E1]) {
[self playWRONGAnswer_EASY];
}
}
- (IBAction)button3:(id)sender {
if ([questionLabel.text isEqualToString:kHisQ_E1]) {
[self playWRONGAnswer_EASY];
}
}
- (IBAction)button4:(id)sender {
if ([questionLabel.text isEqualToString:kHisQ_E1]) {
[self playWRONGAnswer_EASY];
}
}
To all who is interested in this I provide an answer to my own question.
In trying to figure this out I thought that it would be easier to simply randomly reposition the buttons with each question. So I googled that and found this link:
how to random placement of UIButton and value
I only used part of the code since I already had created my buttons in IB (with outlets and actions properly connected) and so only needed the code below. Whenever you call this code it replaces the buttons according to integer values you put in the mutable array.
- (void)randomlyPositionAnswerButtons {
//Create an array with the rectangles used for the frames
NSMutableArray *indexArray = [NSMutableArray arrayWithObjects:
[NSValue valueWithCGRect:CGRectMake(28, 230, 260, 42)],
[NSValue valueWithCGRect:CGRectMake(28, 296, 260, 42)],
[NSValue valueWithCGRect:CGRectMake(28, 359, 260, 42)],
[NSValue valueWithCGRect:CGRectMake(28, 422, 260, 42)], nil];
//Randomize the array
NSUInteger count = [indexArray count];
for (NSUInteger i = 0; i < count; ++i) {
int nElements = count - i;
int n = (arc4random() % nElements) + i;
[indexArray exchangeObjectAtIndex:i withObjectAtIndex:n];
}
//Assign the frames
btnOne.frame = [((NSValue *)[indexArray objectAtIndex:0]) CGRectValue];
btnTwo.frame = [((NSValue *)[indexArray objectAtIndex:1]) CGRectValue];
btnThree.frame = [((NSValue *)[indexArray objectAtIndex:2]) CGRectValue];
btnFour.frame = [((NSValue *)[indexArray objectAtIndex:3]) CGRectValue];
}
You seem to have both logical errors and copy/paste errors.
Take a step back and separate your data store (the questions and answers) from the logic (the shuffling) and from the UI (the buttons). At the moment you're mixing all 3 of these things together and it's really messy.
Your data should be stored so that you have a list of items containing:
Question
Possible answers
Correct answer
When you shuffle, just change the orders of the possible answers (maybe in a mutable copy of the source list). There are many algorithms for shuffling, but when you iterate over the list you should generally be swapping two items each time.
Now, taking your shuffled answers you can set the value of each button. This should be trivial. Don't think about shuffling buttons...
When a button is pressed, get the answer associated with it and compare it to the correct answer. Done.
If you want to shuffle the questions as a whole, that should be done as a single stage before any of the above is started.
Related
I am trying to make grid with touch capabilities using a NSMutableDictionary. I created a coordinate grid (_coordinateRecords being my dictionary) using this in the viewDidLoad function of the view controller:
[self _loopOnCellWithIterator: (^(int iLooper, int jLooper)
{
int cellWidth = 10;
int space = 1;
[_coordinateRecords setObject: [NSIndexPath indexPathForRow: iLooper inSection: jLooper] forKey: [NSValue valueWithPointer: redCell]];
redCell = [[[UIView alloc] initWithFrame: CGRectMake(iLooper * (cellWidth + space) + 24.25,
jLooper * (cellWidth + space) + 110,
cellWidth, cellWidth)] init];
_cells[iLooper][jLooper] = redCell;
[redCell setBackgroundColor: [UIColor blueColor]];
[[self view] addSubview: redCell];
})];
using this block:
- (void)_loopOnCellWithIterator: (void(^)(int iLooper, int jLooper))iterator
{
for (int iLooper = 0; iLooper < kCellSize; iLooper++)
{
for (int jLooper = 0; jLooper < kCellSize; jLooper++)
{
iterator(iLooper, jLooper);
}
}
}
So, I was wondering how I can call upon the objects in the dictionary that I set.
I'm not exactly sure what you're asking, but two things may help:
You access the values in a dictionary using keys. The method -objectForKey: takes a key and returns the associated object. You're expected to know what the keys are, but there's also a method that provides an array containing all the keys.
If you're having a hard time mapping from grid coordinates to dictionary keys, perhaps you're using the wrong data structure. An array may be more suitable since its easy to convert from coordinates to indices.
I am using a UICollectionView with a custom layout that lays out cells in a grid format. There can be well over 50 rows and 50 columns. Scrolling occurs both vertically and horizontally. Currently, I am doing all of the layout setup in prepareLayout and storing it in arrays:
- (void)prepareLayout {
NSMutableArray *newLayoutInfo = [[NSMutableArray alloc] init];
NSMutableArray *newLinearLayoutInfor = [[NSMutableArray alloc] init];
NSInteger sectionCount = [self.collectionView numberOfSections];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
self.heightForRows = [delegate collectionViewHeightForAllRows];
self.totalWidthsForRows = [[NSMutableArray alloc] init];
for (int i = 0; i < sectionCount; i++) {
[self.totalWidthsForRows addObject:[NSNumber numberWithInt:0]];
}
for (NSInteger section = 0; section < sectionCount; section++) {
NSMutableArray *cellLayoutInfo = [[NSMutableArray alloc] init];
NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];
for (NSInteger item = 0; item < itemCount; item++) {
indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
itemAttributes.frame = [self frameForCellAtIndexPath:indexPath];
[cellLayoutInfo addObject:itemAttributes];
[newLinearLayoutInfor addObject:itemAttributes];
}
[newLayoutInfo addObject:cellLayoutInfo];
}
self.layoutInfo = newLayoutInfo;
self.linearLayoutInfo = newLinearLayoutInfor;
}
Then in layoutAttributesForElementsInRect I have:
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *rows = [self.linearLayoutInfo filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *evaluatedObject, NSDictionary *bindings) {
return CGRectIntersectsRect(rect, [evaluatedObject frame]);
}]];
This works okay, but it is laggy and jumpy when I have over 50 columns and 50 rows. The problem I now have is that I must set
-(BOOL)shouldInvalidateLayoutForBoundsChange {
return YES;
}
This makes it prepare the entire layout every time the bounds change, which, needless to say, has a huge impact on performance and you can barely scroll. The cells consist of just text with an opaque background, so there is no issue there.
I am sure I am not doing this right and that there must be a better way. Thanks for the help in advance.
In custom flow layout I do this and it seems to help:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return !(CGSizeEqualToSize(newBounds.size, self.collectionView.frame.size));
}
-(BOOL)shouldInvalidateLayoutForBoundsChange {
return YES;
}
Causes the layout to do prepareLayout() every time it scrolls, which means anything of heavy computing in prepare will lead to a laggy practice, so one possible direction to solve this is to check what's really taking much time. One possibility is what's inside
for (NSInteger section = 0; section < sectionCount; section++)
{
// generate attributes ...
}
in order to generate attributes for the layout. Every time it scrolls, every time this generalization reruns, so that it impacts on the scroll appear to be jumpy and clumsy. So in order to solve this issue, or at least sort out that this is not the really trouble, I suggest setting a flag in this layout algorithm, say, isScrolling, standing for the situation where the layout needs to prepare. Every time in prepareLayout() check the flag, if it is YES, then we'll know there's no need to do for loop to regenerate all the attributes, which alreay exsit ever since the first time the layout is initialised.
ok--I understand now. Here's what I recommend: create 3 collection views... one for the column headers (where each cell is column header), one for the row leaders (each cell = 1 row leader) and one collection view for your cells. Then when the scroll position of any collection view is changed by the user, update the scroll positions for the other 2 collection views as appropriate.
I have already posted another question about this, but no one seemed to know how to do this.
I want my app to pick a random XIB file for me, but dont use the ones that have already been randomly picked.
So heres what i have set up as of right now, it seems to work, but i have to keep pressing the button over and over until it finds one that hasnt be used.
-(IBAction)continueAction:(id)sender{
random = arc4random() % 2;
if (random == 0 && usedQ2 == 0) {
Question_2 *Q2 = [[Question_2 alloc] initWithNibName:#"Question 2" bundle:nil];
Q2.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:Q2 animated:YES];
[Q2 release];
}
else if (random == 1 && usedQ3 == 0) {
Question_3 *Q3 = [[Question_3 alloc] initWithNibName:#"Question 3" bundle:nil];
Q3.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:Q3 animated:YES];
[Q3 release];
}
}
So as you can see i have it pick from a random number, and from their find the one that it matches.
Then you can see i have another part of my if statement that is checking to make sure it hasn't been used before.
each NIB file has its own usedQ(whatever Q it is), and when that Nib file is loaded it puts that usedQ as 1.
I think i could get by doing this, but in order to get rid of the constant button pushing, i will have to put loads of else statements with more else statements in them.
I have also tried running the
random = arc4random() % 2;
in a while statement and a for statement, i hoped that it would keep looking for a number until one that hasn't be used was found with no luck.
Any help? thanks!
Why don't you make a mutable array
and populate it with the names of all
your nibs.
Then read the count of the array and
generate a random number in that
range.
Extract the nib name at that index
and remove it from the array.
repeat steps 2-3.
//Setup your list at an appropriate place
NSMutableArray *nibs
= [[NSMutableArray alloc] initWithObjects: #"One Nib", #"Another
Nib", #"Last Nib", nil];
self.unusedNibs = nibs; //This should be a property you declare in
your header.
[nibs release];
-(IBAction)continueAction:(id)sender{
int random = arc4random() % [self.unusedNibs count];
NSString
*nibName = [self.unusedNibs objectAtIndex: random];
[self.unusedNibs removeObjectAtIndex:
random];
//Load nib here.
}
EDIT: I think i asked something really wrong over here, and i am sorry. What i am doing, is a mock of a slot machine, but i don't think that it will be wise(just don't want to insult myself) of me to put 250(50*5) images in the UIPICKERVIEW, There is no reason doing that, My idea now is to use only the 6 images for each column in deferent order and then when i spin just go again and again over the 30(6*5) images and spin them, that will give the same effect while using less resources, right? I can't see what i was thinking, and thank you all for the answers
Implementing a complex UIPickerView (5 components) in iOS, I am stuck on this problem...
I have 5 NSArrays (for each component in the picker...not mutable) and I have 6 images.
I need to init every array with the set of images, that will populate the array for 50 images, set after set (I'm guessing some kind of for loop that will run 8 times and populate the array with 2 more images at the end?)
In every array the images need to be in a different order, but again, for the same array the set has to be the same all the way.
When I say a set I mean that I need to put the images in the same order again and again, not that they are in another array or anything like that....
Can any one help with this?
10x,
Erez
Not sure I understood the question, but, if I did, you may do:
NSArray *set = [NSArray arrayWithObjects:[UIImage imageNamed:#"img1.png"],/* all the images */ , nil];
NSMutableArray *tmp = [[NSMutableArray alloc] init];
for (int i = 0; i<50; i++) {
[tmp addObject:[set objectAtIndex:(i%6)]];
}
then you may use tmp as common NSArray, or do something like
NSArray *result = [NSArray arrayWithArray:tmp];
ps. don't forget to release tmp when you finished.
UIImage *image;
int randomIndex;
NSCountedSet *set = [[NSCountedSet alloc] initWithCapacity:50];
NSMutableArray *arrays = [[NSMutableArray alloc] initWithCapacity:5];
for ( int i = 0; i < 5; i++ ) {
while ( [set count] < 50 ) {
randomIndex = arc4random() % 6;
image = [images objectAtIndex:randomIndex];
switch ( [set count] ) {
case 48:
case 49:
if ( [set countForObject:image] < 9 ) {
[set addObject:image];
}
break;
default:
if ( [set countForObject:image] < 8 ) {
[set addObject:image];
}
}
}
[arrays addObject:[set allObjects]];
[set removeAllObjects];
}
[set release];
The idea would be to use a counted set and get the count of elements added to it. This way we can keep track of objects being added.
I am developing an application for the iPhone. The question I have is how to display a new label with a different text every .5 seconds. For example, it would display Blue, Red, Green, Orange and Purple; one right after one another. Right now I am doing this:
results = aDictionary;
NSArray *myKeys = [results allKeys];
NSArray *sortedKeys = [myKey sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
int keyCount = [sortedKeys count];
while (flag == NO) {
NSTimeInterval timeMS = [startDate timeIntervalSinceNow] * -10000.0;
if (timeMS >= i) {
ii++;
i += 1000;
NSLog(#"endDate = %f", timeMS);
int randomNumber = rand() % keyCount + 1;
lblResult.text = [results valueForKey:[sortedKeys objectAtIndex:(randomNumber - 1)]];
result = [results valueForKey:[sortedKeys objectAtIndex:(randomNumber - 1)]];
lblResult.text = result;
}
if (ii > 25) {
flag = YES;
}
}
lblResult.text = [results valueForKey:[sortedKeys objectAtIndex:(sortedKeys.count - 1)]];
this function is called at the viewDidAppear Function and currently isn't displaying the new labels. It only displays the one at the end. Am I doing anything wrong? What would be the best method to approach this?
The problem is that you're not giving the run loop a chance to run (and therefore, drawing to happen). You'll want to use an NSTimer that fires periodically and sets the next text (you could remember in an instance variable where you currently are).
Or use something like this (assuming that items is an NSArray holding your strings):
- (void)updateText:(NSNumber *)num
{
NSUInteger index = [num unsignedInteger];
[label setText:[items objectAtIndex:index]];
index++;
// to loop, add
// if (index == [items count]) { index = 0; }
if (index < [items count]) {
[self performSelector:#selector(updateText:) withObject:[NSNumber numberWithUnsignedInteger:index] afterDelay:0.5];
}
}
At the beginning (e.g. in viewDidAppear:), you could then call
[self updateText:[NSNumber numberWithUnsignedInteger:0]];
to trigger the initial update.
You'd of course need to ensure that the performs are not continuing when your view disappears, you could do this by canceling the performSelector, or if you're using a timer, by simply invalidating it, or using a boolean, or ...
And if you want to get really fancy, use GCD :)