Making Pointed Flags In CSS Only

The other day at work, I was presented with a project to add pointed flags to the TripAdvisor tablet site. Here is the finished version which consists of the green pointed flags and some icons/text that are placed on top.

The finished pointed flags
The finished pointed flags

A location at TripAdvisor can have 3 different awards. The first is a “Certificate of Excellence”, the second is a “Traveller’s Choice Award”, and the third is a “Green Award”. The “Green Award” has 4 levels (Platinum, Gold, Silver, and Bronze).

These awards already existed on the site and were available in all the languages that TripAdvisor supported. Our product team really wanted them moved and put on the green pointed flags above. The two solutions were create new images that need to be localized or reuse the icons. Since TripAdvisor’s moto is “Speed Wins”, I thought “Why not make the flags using CSS” and avoid having a designer create the flags by hand.

This whole project took me about an 1.5 hours to get right. I really like how they came out.

Here is a jsfiddle, that shows both the css and html needed to complete the project:
http://jsfiddle.net/WNEem/7/

The magic lives in css borders. If you give a div with no height and width equal borders you get a box. (http://jsfiddle.net/KE8RR/). So if you make everything but the top transparent, you get a triangle. Adjusting border sizes can give you different elongations.

Using and Centering UITextFields In Cocos2d

While working on Empous, I needed to include UITextFields so I could allow my users to create accounts and login to my application. This post will explain the difficulties I encountered along with my solutions.

The first part of this post explains how to add and remove UITextFields from a Cocos2d scene. This is provided as a background for those who have never used UITextFields in a cocos2d application before. The second half of this post will highlight the code I use in Empous to center a UITextField above the keyboard whenever a user enters text.

Adding and Removing UITextFields

You cannot add UITextFields directly to a Cocos2d object, but you can add them to the to the openGLView held by the CCDirector object. To get a basic UITextField onto the scene you could do the following (Please note that the code has only been tested in a landscape orientation. I am quite confident this will not work without modification in a portrait application):

CGSize winSize = [[CCDirector sharedDirector] winSize];
CGPoint center = ccp(winSize.width/2,winSize.height/2);
UITextField* textField = [[UITextField alloc] initWithFrame:CGRectMake((center.x - 150), 170, 300, 35)];

//The UITextField will not be visible unless you set the background color and border style. This sets the background color to a dark green. You can choose what you like. 
textField.backgroundColor = [UIColor colorWithRed:.27 green:.18 blue:.05 alpha:1.0];
textField.borderStyle = UITextBorderStyleRoundedRect;

//Add the text field to the openGlView
[[[CCDirector sharedDirector] openGLView] addSubview: textField];

Removal is handled using iOS semantics since we are using a UITextField. You will need to keep a pointer to the text field in order to remove it from the view. The code is one line:

[textField removeFromSuperView];

For more information on UITextViews and how to access the text from them see the UITextField documentation.

Center UITextFields Above the iOS Keyboard

When a user activates a UITextField, iOS will automatically bring up a keyboard. If the text field is too low on the screen, the keyboard may cover it preventing the user from seeing what they are typing. Even if the text field is not covered by the keyboard, it may look odd if the text field is not centered in the remaining screen space. The code below will move the scene to center any active text field. It will also move the screen back when the user is done entering text.

You must declare that your class obeys the UITextFieldDelegate in the .h file.

@interface TextFieldScene : CCScene <UITextFieldDelegate>

Implementing the protocol is not enough. You must also have every text field set your class as its delegate. Using the code from the first part of this post, you would need to add the following line:

textField.delegate = self;

Without the above line, none of the delegate methods will be called when a user interacts with a text field. To actually slide the screen, enter the following code into your class.

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    
    int idealViewingLocation = 3*320/4;
    
    //Need to slide the layer to center the text field at the desiredViewingLocation
    double centerYOfField = textField.frame.origin.y - (textField.frame.size.height/2);

    //Translate to cocos2d frame (0,0 is the top left of Apple coordinates).
    double centerYOfFieldCocos = 320 - centerYOfField;
    
    double moveDistance = idealViewingLocation - centerYOfFieldCocos;
    if(moveDistance > 0)
    {
        UIView* cocosView = [[CCDirector sharedDirector] openGLView];
        [UIView beginAnimations: @"anim" context: nil];
        [UIView setAnimationBeginsFromCurrentState: YES];
        [UIView setAnimationDuration: .2f];
        cocosView.frame = CGRectOffset(cocosView.frame, moveDistance, 0);
        [UIView commitAnimations];
    }
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    UIView* cocosView = [[CCDirector sharedDirector] openGLView];
    
    //Get the current location of the UIView and see how far off it is from its proper location i.e. 0
    double moveDistance = 0 - cocosView.frame.origin.x;
    
    //Need to slide the layer back down
    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: .2f];
    cocosView.frame = CGRectOffset(cocosView.frame, moveDistance, 0);
    [UIView commitAnimations];    
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}

The above code should handle all the terrible sliding for you. I’ve only tested this with landscape oriented apps, but works well for me.

CCButton – My Button Subclass of CCSprite

For those who don’t know, I’ve been working on a game for iOS known as Empous. Empous, which resembles RISK, is being implemented using Cocos2d.

Check out Cocos2d if you want to make a 2D game for iOS. Cocos2d is very intuitive and highly recommended by me. There are some drawbacks. The biggest one being the lack of seemingly obvious classes. Cocos2d does not assume anything about anyone’s development style. It gives you all the basic tools but you’re going to have to write the bigger ones yourself. Over the development of Empous, I have made a few classes I’m proud of. The most recent of these is a subclass of CCSprite.

To display an image using Cocos2d, a developer would rely on the CCSprite class. It’s a very simple class and handles all the terrible openGL code needed to display the image. It even caches the textures in case they’re needed again.

CCSprite only handles images. It doesn’t even respond to touches. A programmer can check if a touch and a sprite coincide, but that has to be done manually. A perfect extension of CCSprite would be some sort of button class. Cocos2d has no such class because they don’t want to force programmers to follow patterns unless absolutely necessary.

I however use buttons everywhere, so I created the CCButton class. The rest of this post will explain how it works. If you want the code now feel free to check out the HurleyProg cocos2dUtilities repo. The repository will always have the most up-to-date code. It is likely that this post will not be updated as that code changes.

How CCButton Works

The ideal button should know when it’s been touched and should be able to fire any callback when touched. CCbutton can do this. In addition, CCButton allows a programmer to specify both pressed and unpressed images for the button. CCButton will automatically change the image as necessary. Finally, CCButton allows a programmer to optionally scale the touch area of the button. I used this to improve the user experience when the button images were quite small.

The header for CCButton is as follows (comments have been removed for brevity):

#import "CCSprite.h"
#import "cocos2d.h"

@interface CCButton : CCSprite 
{
    SEL _selector;
    id _target;
    NSString* _spriteFile;
    NSString* _pressedSpriteFile;
    float _touchRectScale;
}

@property (nonatomic, assign) id target;
@property (nonatomic) SEL selector;
@property (nonatomic, copy) NSString* spriteFile;
@property (nonatomic, copy) NSString* pressedSpriteFile;
@property (nonatomic, assign) float touchRectScale;

+(id)spriteWithFile:(NSString *)filename withPressedFile:(NSString*)pressedFilename target:(id)object function:(SEL)callback;
+(id)spriteWithFile:(NSString *)filename withPressedFile:(NSString*)pressedFilename touchAreaScale:(float)scale target:(id)object function:(SEL)callback;

@end
}

The class is not terribly complicated. The main thing to point out is the two methods that return id. The first creates a button where the touch area is equal to the bounding box of the sprite. The second allows a programmer to specify how much much the touch area should be scaled. In mathematical terms, the touch area = boundingbox * scale. Using a scale of “1.0” would be equivalent to the first spriteWithFile method. The two methods both return autoreleased objects which is the custom of Cocos2d.

CCbutton.m will be covered in chunks. This first chunk shows how the two id methods in the header return autoreleased objects. It also shows how the init method calls constructor for CCSprite and then stores the arguments using the properties defined in the header. Finally, the dealloc method cleans up the NSString references.

#import "CCButton.h"

@implementation CCButton

@synthesize target = _target;
@synthesize selector = _selector;
@synthesize spriteFile = _spriteFile;
@synthesize pressedSpriteFile = _pressedSpriteFile;
@synthesize touchRectScale = _touchRectScale;

+(id)spriteWithFile:(NSString *)filename withPressedFile:(NSString*)pressedFilename target:(id)object function:(SEL)callback
{
    return [[[self alloc]initWithWithFile:filename withPressedFile:pressedFilename touchAreaScale:1.0 target:object method:callback] autorelease];
}

+(id)spriteWithFile:(NSString *)filename withPressedFile:(NSString*)pressedFilename touchAreaScale:(float)scale target:(id)object function:(SEL)callback;
{
    return [[[self alloc]initWithWithFile:filename withPressedFile:pressedFilename touchAreaScale:scale target:object method:callback] autorelease];
}

-(id)initWithWithFile:(NSString *)filename withPressedFile:(NSString*)pressedFilename touchAreaScale:(float)scale target:(id)object method:(SEL)callback
{
    self = [super initWithFile:filename];
    if(self)
    {
        self.target = object;
        self.selector = callback;
        self.spriteFile = filename;
        self.pressedSpriteFile = pressedFilename;
        self.touchRectScale = scale;
    }
    return self;
}

-(void)dealloc
{
    [_spriteFile release];
    [_pressedSpriteFile release];
    [super dealloc];
}

The following code registers the CCButton instance with the touch dispatcher. It will also remove itself from the touch dispatcher when it is either removed from a layer or the scene changes.

- (void) onEnter
{
    [super onEnter];
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}

- (void) onExit
{
    [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
    [super onExit];
}

The next method is the most important method in the class. It will check to see if a touch occurred within the sprite. It does this by first getting the bounding box from the button. If a scale other than 1.0 has been set, it will create a new rectangle by scaling the bounding box and then readjusting the origin to center the new touch area around the button. It finally does the check using CGRectContainsPoint. This code assumes that the button’s parent is the size of the iPhone screen. Nested sprites probably won’t work.

- (BOOL)containsTouchLocation:(UITouch *)touch
{
    CGPoint touchLocation = [self.parent convertTouchToNodeSpace:touch];

    CGRect spriteArea = self.boundingBox;
    if (self.touchRectScale != 1.0)
    {
        float newWidth = spriteArea.size.width * self.touchRectScale;
        float newHeight = spriteArea.size.height * self.touchRectScale;
        
        float differenceInWidth = newWidth - spriteArea.size.width;
        float differenceInHeight = newHeight - spriteArea.size.height;
        
        spriteArea = CGRectMake(spriteArea.origin.x - (0.5 * differenceInWidth),
                              spriteArea.origin.y - (0.5 * differenceInHeight),
                              spriteArea.size.width * self.touchRectScale,
                              spriteArea.size.height * self.touchRectScale);
    }
    return CGRectContainsPoint(spriteArea, touchLocation);
}

Finally, the button needs to respond to touches. Besides calling the above method, these two methods will swap out the button images as necessary.

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    BOOL isSpriteTouched = [self containsTouchLocation:touch];
    if(isSpriteTouched)
    {
        [self setTexture:[[CCTextureCache sharedTextureCache] addImage:self.pressedSpriteFile]];
    }
    return isSpriteTouched;
}

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
    [self setTexture:[[CCTextureCache sharedTextureCache] addImage:self.spriteFile]];
    
    if(![self containsTouchLocation:touch])
        return;
    [self.target performSelector:self.selector withObject:self];
}

@end

That’s it! Works great for me. I remind you that you should get the code from the repository. If I have made any bug fixes, they will be in the repo and may not be in this post.

Empous Update – 3/24/13

 “my turn, not my turn”

Empous has been in testing for about 3 weeks. So far the biggest concern has been a weird race condition named by the testers as the “my turn, not my turn” bug.

The problem occurred when the Empous server and the uploaded file (which held the game state) did not agree on the current player. This problem occurred when updates where handled out of order.

In Empous, the game is continuously uploaded to prevent cheating. Without this constant uploading, a player could simply restart his/her turn if it did not go so well. In addition to all these intermediate updates, there is one final update where the current player is changed to the next person both in the game file and in the server’s database.

I never prepared for out of order requests (silly me), so it was possible for one of the “anti-cheating” updates to happen after the end-of-turn update. When this transpired, the current player was changed to the new player according to the Empous server but the game file was overwritten with data which pointed to the original player as the current player. To the tester the game would say that is was their turn, but once loaded, the game would deny them any actions hence “my turn, not my turn”.

This problem was resolved in the most recent update. Now the server verifies that the person updating the game is the current player. If the update does not come from the current player, it is thrown out. This keeps the server and game file in sync and fixes the bug.

Upcoming Work

 The testers have provided valuable feedback. This is a list of the upcoming items that I will be working on.

  1. Make push messages more grammatically correct. I was a little rushed when implementing push notifications and need to clean up some English issues.
  2. Make the application return to the “Current Games” screen after a turn has been played. After every turn, Empous returns to the main menu. There have been requests for the application to return to the current games screen to save on button presses, and I agree.
  3. Add colors for each player. In games with more than one opponent there is no way to tell who is who. I will soon be adding a key which will map colors to players.
  4. Increase touch area of buttons. The buttons are pretty small currently. I like how they look, but I understand they are hard to touch. I will be increasing the touch area to compensate.

  There are other miscellaneous issues that I will be working on too, but these will be transparent to any end user. 

Empous Update – 3/11/13

Testing has begun!

I am happy enough with Empous to have some alpha testers play against me. The dev server is quite slow, but this is forcing me to overhaul the app for speed.

There’s a lot of things you notice when actually using the app. For example, I added the ability for the app to refresh itself when it becomes active. I also found an issue where a user could cheat! That has been fixed. Still many improvements to come.

Programming Portfolio of Ryan Hurley