Assigning random image with arc4random? - iphone

Ok, I'm going to try to make this more clear because my last question was extremely confusing. I've included a picture this time. Each one of these circles is a UIImageView and they are each assigned a random image which is one of 7 colors. So each circle could be one of 7 colors. I want to make it so that the user has to hit the circles in order of a pre-determined order according to color. For example, blue, green, yellow, pink, purple, orange, red. My HUGE problem is that I can't seem to figure out how to determine whether or not a color that isn't supposed to be hit got hit. Is there a way to assign multiple image views the same value and then somehow have an if statement that says....
if(a blue circle is hit && ANY ORANGE CIRCLE IS STILL IN VIEW){
do something
}
The only way I know how to code this would be an insane amount of code, because of all the random images being assigned.
bluebubble = [UIImage imageNamed:#"bluebubble.png"];
greenbubble = [UIImage imageNamed:#"greenbubble.png"];
redbubble = [UIImage imageNamed:#"redbubble.png"];
yellowbubble = [UIImage imageNamed:#"yellowbubble.png"];
orangebubble = [UIImage imageNamed:#"orangebubble.png"];
pinkbubble = [UIImage imageNamed:#"pinkbubble.png"];
purplebubble = [UIImage imageNamed:#"purplebubble.png"];
image1 = arc4random()%7;
if(image1 == 0){
[test setImage:bluebubble];
}
else if(image1 == 1){
[test setImage:greenbubble];
}
else if(image1 == 2){
[test setImage:redbubble];
}
else if(image1 == 3){
[test setImage:yellowbubble];
}
else if(image1 == 4){
[test setImage:orangebubble];
}
else if(image1 == 5){
[test setImage:pinkbubble];
}
else if(image1 == 6){
[test setImage:purplebubble];
}
image2 = arc4random()%7;
if(image2 == 0){
[test2 setImage:bluebubble];
}
else if(image2 == 1){
[test2 setImage:greenbubble];
}
else if(image2 == 2){
[test2 setImage:redbubble];
}
else if(image2 == 3){
[test2 setImage:yellowbubble];
}
else if(image2 == 4){
[test2 setImage:orangebubble];
}
else if(image2 == 5){
[test2 setImage:pinkbubble];
}
else if(image2 == 6){
[test2 setImage:purplebubble];
}

I think it's also wise to create a more reasonable way to assign the different color images to your image views, so this solution does both.
These Should be declared in the header of the class
NSArray *allCircleImagesViews; // These are suppose to be the onscreen UIImagesViews
NSArray *circlesByColor;
NSMutableArray *correctCircles; // The current circles the user is allowed to click
NSArray *colorOrder; // The order of the colors to click
int currentColorIndex; // The current index in the order of colors
Now for the functions:
The first one creates assign the different color images, sets the correct order of the colors, and sets the mechanism to determine if the correct color was clicked
- (void) populateImages
{
NSArray *images = [NSArray arrayWithObjects:
[UIImage imageNamed:#"bluebubble.png"],
[UIImage imageNamed:#"redbubble.png"],
[UIImage imageNamed:#"yellowbubble.png"],
[UIImage imageNamed:#"greenbubble.png"],
[UIImage imageNamed:#"orangebubble.png"],
[UIImage imageNamed:#"pinkbubble.png"],
[UIImage imageNamed:#"purplebubble.png"],
nil];
NSArray *circlesByColor=[NSArray arrayWithObjects:
[NSMutableArray array],
[NSMutableArray array],
[NSMutableArray array],
[NSMutableArray array],
[NSMutableArray array],
[NSMutableArray array],
[NSMutableArray array],
nil];
[circlesByColor retain];
// Assign images to circles and set the
for (UIImageView *currCircle in allCircleImagesViews)
{
int nextIndex = arc4random()%7;
currCircle.image = [images objectAtIndex:0];
[(NSMutableArray *)[circlesByColor objectAtIndex:nextIndex] addObject:currCircle];
}
// Set the correct order
NSArray *colorOrder = [NSArray arrayWithObjects:
[NSNumber numberWithInt:5], // Pink
[NSNumber numberWithInt:0], // Blue
[NSNumber numberWithInt:1], // etc.
[NSNumber numberWithInt:4],
[NSNumber numberWithInt:2],
[NSNumber numberWithInt:3],
[NSNumber numberWithInt:6],nil];
[colorOrder retain];
currentColorIndex = 0;
correctCircles = [circlesByColor objectAtIndex:[[colorOrder objectAtIndex:currentColorIndex] intValue]];
}
The following function takes care of checking if the correct circle was clicked
- (void) checkCircle:(UIImageView *)clickedImageView
{
BOOL result;
if ([correctCircles containsObject:clickedImageView])
{
[correctCircles removeObject:clickedImageView];
result = YES;
}
else {
result = NO;
}
if ([correctCircles count] == 0)
{
currentColorIndex++;
if (currentColorIndex < 7)
correctCircles = [circlesByColor objectAtIndex:[[colorOrder objectAtIndex:currentColorIndex] intValue]];
else
correctCircles = nil;
}
if (!result)
{
// Wrong circle clicked logic
}
else {
if (!correctCircles)
{
// Game won logic
}
else {
// Correct circle clicked logic
}
}
}

Yes, you can set the image property of UIImageView to the same UIImage. In fact, you are already doing this!
The imageNamed: class method will return a reference to the same object when called again with the same image file name.
However, you should get the four UIImage references once and then use those. You should also use an array and loop or at least a function to make this code shorter and simpler.
Actually, it sounds like you will want your four UIImage references to be instance variables so that you can compare the image property of tapped UIImageView objects against them later.

If your purpose is simply the if clause, then what I would do is to define two dictionaries with the key as the color and the values as the arrays of burbles with the value be the location of the burble.
NSDictionary *hitDictionaryOfBurbles;
NSDictionary *notCurrentlyBeingHitDictionaryOfBurbles;
With this dictionary defined, you have with you all the items of all colors currently not being hit. You can then easily check this dictionary the value of the array of Orange color to see how many items there are.
Offcourse, once the burble is being hit, you should change the value of these two dictionaries to reflect the change.
Es.
hitDictioanryOfBurbles:
"blue", [0,7,13]
"green", [2,6,16]
etc...
notCurrentlyBeingHitDictioanryOfBurbles:
"blue", [25,33,37]
"green", [19,27,29]
etc...
If you have other logic to support, then you should define more data structures.

Are you always going to be hitting these in a certain order, one after the other, where only one color is valid at a time? If so, you can create a stack of UIImages (I don't think there's a built-in stack data type, but you can simulate it easily with NSMutableArray) consisting of the colors in order.
You also keep track of all the color-circle subviews on the screen in a separate NSMutableArray. Then, whenever a user hits a circle, execute the following code:
if(hitImageView.image == [imageStack peek]) {
//"hit the right one" logic
//you hit it, so remove it from the screen
[allImageViews removeObject:hitImageView];
[hitImageView removeFromSuperView];
//if it was the last imageview of the "correct" color...
BOOL wasLast = YES;
for(UIImageView *imageView in allImageViews) {
if(imageView.image == [imageStack peek]) {
wasLast = NO;
break;
}
}
//...then set the correct color to whatever is next
if(wasLast)
[imageStack pop];
if(![imageStack hasItems])
;//"won the game" logic
}
else {
//"hit the wrong one" logic
}
(This is O(N) for determining remaining circles, but if you're dealing with such a small set then it really doesn't matter. If you needed to optimize it, you could of course keep track of the remaining counts of each color.)
On another note, you'll be a much happier person if, instead of using that gigantic if-else block to identify the correct color, you just name the files color1.png, color2.png, etc. and say [UIImage imageNamed:[NSString stringWithFormat:#"color%i.png", randNum]];

Related

Tapping button to go through each frame of animation

I am trying to create an app where when you tap a button it will go to the next frame in an animation.
I have 8 image files, and when I press the button I want the 1st image to display, and when i press the button again, I want the 2nd image to replace the 1st image and so on.
I was thinking something like:
-(IBAction)buttonPressDoStuff:(id)sender {
imageThing.image = [UIImage imageNamed:#"image1.png"];
imageThing.image = [UIImage imageNamed:#"image2.png"];
imageThing.image = [UIImage imageNamed:#"image3.png"];
imageThing.image = [UIImage imageNamed:#"image4.png"];
}
and somehow making this all work consecutively with each press.
I am fairly new at objective c, so any help would be much appreciated.
Can anyone throw up some sample code to do this?
Let's think about this. If you want to do something sequentially, that sounds like the job of an array. So what do you think about this:
In your .h file, add these instance variables:
NSMutableArray* picturesArray;
NSInteger counter;
And now in your .m file, in your class' init method:
//this loop will fill your array with the pictures
for(int idx = 0; idx < NUMBER_OF_PICTURES; idx++) {
//IMPORTANT: this assumes that your pictures' names start with
//'image0.png` for the first image, then 'image1.png`, and so on
//if your images' names start with 'image1.png' and then go up, then you
//should change the 'int idx = 0' declaration in the for loop to 'int idx = 1'
//so the loop will start at 0. You will then need to change the condition
//to 'idx < (NUMBER_OF_PICTURES + 1)' to accomodate the last image
NSString* temp = [NSString stringWithFormat:#"image%i.png", idx];
UIImage* tempImage = [UIImage imageNamed:temp];
[picturesArray addObject:tempImage];
}
and in your buttonPressDoStuff: method:
//this method will move to the next picture in the array each time it is pressed
-(IBAction)buttonPressDoStuff:(id)sender {
if(counter < [pictureArray count]) {
imageThing.image = [picturesArray objectAtIndex:counter];
counter++;
}
}
Your init method should look something like this:
- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self) {
//do setup here
for(int idx = 0; idx < NUMBER_OF_PICTURES; idx++) {
NSString* temp = [NSString stringWithFormat:#"image%i.png", idx];
UIImage* tempImage = [UIImage imageNamed:temp];
[picturesArray addObject:tempImage];
}
}
//it is important that you return 'self' no matter what- if you don't,
//you will get the 'control reached end of non-void method' warning
return self;
}

Animating a button to look like a spinner on click

So basically I have a button. When it is clicked the first time I want it to display a spinner animation (kind of like the game twister, the spinner thing). I want this animation on the button itself. Preferably as the background. I have the other part of the button working correctly where when it is pressed again it stops pointing in a direction at random. Any ideas on how to get this working?
-(IBAction)spinnerButton:(id)sender{
NSString *display;
if (pressCount%2 == 0) {
NSString *spinning;
spinning = #"Press again to stop spinner.";
display = [[NSString alloc] initWithFormat:#"%#",spinning];
sender.animationImages = [NSArray arrayWithArray: imageArray];
sender.animationDuration = 1.0;
sender.animationRepeatCount = 0;
sender.startAnimating;
}
The imageArray is put together here.
- (void)viewDidLoad
{
[super viewDidLoad];
imageArray = [[NSMutableArray alloc] initWithCapacity:8];
for (int i = 0; i < 8; i++) {
[imageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"piece%d.png", i]]];
}
}
My thought is that it needs to look something like this
[sender setBackgroundImage:[UIImage imageNamed:#"piece1.png"] forState:UIControlStateNormal];
but I'm not exactly sure how to place the array in it and make set the duration and all that stuffs. Thanks any help is welcomed and appreciated.
I got it to work fine. I placed an Image View over the top of the button. It still allowed me to click the button and it allowed me to tie the images and animation in correctly.

Slow down when dealing with MKAnnotations on MKMapView

I'm stepping thru the annotations on my map, in order to find one which has a certain tag. I then remove this annotation, and add a new one (a diff color)
The problem is that this takes a very, very long time. Not sure why. It's going thru 300 annotations, and it takes up to 20 seconds! How can I speed this up? It there a way to, for instance, only get the annotations which are currently visible? Or, if I have the annotation, can I tell what is its index in mapView.annotations array is, so I don't have to step thru?
for (int i =0; i < [self.mapView.annotations count]; i++)
{
if ([[self.mapView.annotations objectAtIndex:i] isKindOfClass:[BasicMapAnnotation class]] )
{
BasicMapAnnotation *bmaTemp =(BasicMapAnnotation*)[self.mapView.annotations objectAtIndex:i];
if (bmaTemp.mapTag == pinTag) {
[self.mapView removeAnnotation:[self.mapView.annotations objectAtIndex:i]];
self.customAnnotation = [[[BasicMapAnnotation alloc] initWithLatitude:[shop.latitude doubleValue] andLongitude:[shop.longitude doubleValue]] autorelease];
self.customAnnotation.mapTag = pinTag;
[self.mapView addAnnotation:self.customAnnotation];
}
}
}
If you are sure that the Annotation you want to modify is actually visible, you could use something like
for (id annotation in [self.mapView annotationsInMapRect:self.mapView.visibleMapRect]){
if([annotation isKindOfClass:[BasicMapAnnotation class]]){
BasicMapAnnotation *annotationToModify = annotation;
if (bmaTemp.mapTag == pinTag) {
[self.mapView removeAnnotation:annotationToModify];
self.customAnnotation = [[[BasicMapAnnotation alloc] initWithLatitude:[shop.latitude doubleValue] andLongitude:[shop.longitude doubleValue]] autorelease];
self.customAnnotation.mapTag = pinTag;
[self.mapView addAnnotation:self.customAnnotation];
}
}
}
If you want to know the index of a single given annotation:
int annotationIndex = [self.mapView.annotations indexOfObject:givenAnnotation];
Untested, I wrote this here on SO so please let me know, whether it works or not.

Hiding or moving SegmentContoller

Hello I've tried for 3 weeks to solve this issue and it stumps me. What i am trying to do is create a 3 part segment from an array, display it in a view in a certain position, then remove it from view when the "OFF" flag is set. Every thing works except the removal of the segment. It will even commuticate with (pickOne) and display the segment letters in a label. What i can't get to work is either of the two: setHidden:YES, or removeAllSegments. Any help would be appreciated. Here is my code.
- (void) showSegment {
int x = 192;
int y = 212;
int w = 125;
int h = 25;
SegUnit1 = #"A";
SegUnit2 = #"B";
SegUnit3 = #"C";
threeSegs = [NSArray arrayWithObjects: SegUnit1, SegUnit2, SegUnit3, nil];
segSize = [NSArray arrayWithArray:threeSegs];
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
if ([segmentState_height isEqualToString:#"ON"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
} else if ([segmentState_height isEqualToString:#"OFF"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
[heightSC setHidden:YES]; // NSLog showing "OFF" but segment will not hide.
[heightSC removeAllSegments]; // NSLog showing "OFF" and segment is suppose to dismantle and does not.
}
}
I know now that i have to "not" create and remove in the same function, and was given a tip on correcting this but I don't know how to use the tip.
here is what was suggested.
Well, your method is a little confused, since you are trying to both create and hide at the same time. So you might consider splitting that up into separate methods.
In general, it will be along these lines:
Code:
if ([self theControlProperty] == nil)
{
UISeg... *theControl = [[UISeg alloc] ....];
[self setTheControlProperty:theControl];
...
}
if (shouldHideTheControl)
{
[[self theControlProperty] setHidden:YES];
}
Any help would be appreciated.
The problem you have is that you're creating a new UISegmentedControl instance every time that method is called. The first time through, you create an instance and add it as a subview to your view. This apparently works fine, as it should. Then the method returns, and you no longer have any easy way to refer to that instance that you created. When you re-enter -showSegment, you create a different instance, and then hide and/or destroy it. This different instance has no effect whatsoever on the instance that you gave to the view.
What you need to do is make heightSC an instance variable. Add it to the interface declaration in the header file, then initialize it only once, and hide or modify it as needed subsequently. The key point is that you need to have a reference to the instance of the UISegmentedControl which is being drawn on the screen, a reference that lives outside the method itself that you can use the second, third, fourth, etc time you call that method.
Try using the remove segments in your button choice method pickOne. This takes it outside the showSegment method and matches the users desired action to make the change and clear off the buttons.
- (void) pickOne:(id)sender {
UISegmentedControl* userChose = sender;
if( [userChose selectedSegmentIndex] == 0 ){
your first button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 1 ){
your second button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 2 ){
your third button operation;
[heightSC removeAllSegments];
}
}
I tried this and got the results I was looking for. Thanks goes to Mythogen and BrianSlick I just need to check and make sure there are no leaks. Now that will be a task.
Does anyone know if I need the second [heightSC release]; ?
// .h
# interface ------ {
UISegmentedControl *segmentPicked;
}
|
#property (nonatomic, retain) UISegmentedControl *segmentPicked;
// .m
|
#synthesize segmentPicked;
|
if ([self segmentPicked] == nil) {
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
[self setSegmentPicked:heightSC];
[heightSC release];
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
}
if ([segmentState_height isEqualToString:#"OFF"])
{
[[self segmentPicked] setHidden:YES];
} else {
[[self segmentPicked] setHidden:NO];
}
[yourSegment removeFromSuperview];
?

How to get UIButtons title in an array to be shown each one by one in a timed sequence

In my program, I have a series of UIButtons in an array that I would like its title to be shown each one at a time one by one in a timed sequence after executing a function that has a while loop.
I have an IBAction attached to the buttons and when touched will call another function that will do some operation and in the end, will change the UIButtons title in that array.
The problem is that it changed the whole buttons title simultaneously after executing that method but I want the title to be shown one by one in a timed sequence.
To illustrate clearly, here are my codes:
-(IBAction)touchedButton1:(id)sender
{
[self calculateSteps:0];
}
-(void)calculateSteps:(NSUInteger)hole_index2
{
index = hole_index2;
NSNumber *tempNumber = [marblesArray objectAtIndex:index];
stones = [tempNumber intValue];
[marblesArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInt:0]];
[[buttonsArray objectAtIndex:index] setTitle:[NSString stringWithFormat:#"%d", [NSNumber numberWithInt:0].intValue] forState:UIControlStateNormal];
while(stones > 0) {
if (player == PLAYER_A && stones >= 1 && index == 6) {
NSNumber *tempNumber3 = [storeArray objectAtIndex:0];
NSUInteger tempInt3 = [tempNumber3 intValue];
tempInt3++;
[storeArray replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:tempInt3]];
[buttonPlayerA setTitle:[NSString stringWithFormat:#"%d", [NSNumber numberWithInt:tempInt3].intValue] forState:UIControlStateNormal];
stones--;
if (stones == 0) { // end in PLAYER A's store
NSLog(#"end in big hole player A");
return;
}
}
if (player == PLAYER_B && stones >= 1 && index == 12) {
NSNumber *tempNumber4 = [storeArray objectAtIndex:1];
NSUInteger tempInt4 = [tempNumber4 intValue];
tempInt4++;
[storeArray replaceObjectAtIndex:1 withObject:[NSNumber numberWithInt:tempInt4]];
[buttonPlayerB setTitle:[NSString stringWithFormat:#"%d", [NSNumber numberWithInt:tempInt4].intValue] forState:UIControlStateNormal];
stones--;
if (stones == 0) { // end in PLAYER B's store
NSLog(#"end in big hole player B");
return;
}
}
index++;
if (index >= NUM_HOLES) index = 0;
NSNumber *tempNumber2 = [marblesArray objectAtIndex:index];
tempInt2 = [tempNumber2 intValue];
tempInt2++;
[marblesArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInt:tempInt2]];
NSLog(#"Number in marblesArray index: %d", [NSNumber numberWithInt:tempInt2].intValue);
[[buttonsArray objectAtIndex:index] setTitle:[NSString stringWithFormat:#"%d", [NSNumber numberWithInt:tempInt2].intValue] forState:UIControlStateNormal];
stones--;
}
}
So, I have tried to put NSTimer in the calculateSteps method and also in that while loop but couldn't get to work. I guess maybe the while loop function fast enough that it didn't get the chance to get NSTimer to work in time.
I know it could work if I use if timer == 1, or else if timer == 2, etc as the timer increase, each button associated with it will change after that interval. However, when I tried to use for (timer == 1; timer < stones; timer++) , it doesn't show the buttons title each one by one but simultaneously after it is done with the loop. Am I wrong with my logic?
Also, I've tried to put sleep(2) in the while loop also, it works for the NSLog(#"Number in marblesArray index:...") appearing each 2 seconds but still the buttons array title still shown simultaneously after while loop completed.
I'm not sure how can I get the buttons title in that array to be shown each one by one in a timed sequence. Have been trying for 2 days but couldn't get it to work.
Appreciate if anyone out there who can help me. I don't know if it's my logic or if there's other function that can be used to solve this issue.
Thank you very much in advance.
Yes, you could used :
NSTimer
or just performSelector:withObject:afterDelay: of any NSObject subclass.
You can not use sleep or loop in main thread. It will block the HMI.
But I don't really understand your code and your explanation...
In the target selector of the timer, you could have a static int to count the number of ticks
example (very simple):
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(onTimer) userInfo:nil repeats:NO];
-(void)onTimer{
static int ticks = 0;
ticks++;
if (ticks==1) {
//TODO
} else if (ticks==2) {
//TODO
} else if (ticks==3) {
//TODO
} else {
//TODO
ticks=0;
}
}