EAGLView.m
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
#import <ApplicationServices/ApplicationServices.h>
#import "EAGLView.h"
#import "peanutsmodel.h"
#import "sprite.h"
#define USE_DEPTH_BUFFER 0
@interface EAGLView ()
// boilerplate omitted
- (void)drawRectangleX1:(double)x1 y1:(double)y1 x2:(double)x2 y2:(double)y2 color:(UIColor*)c;
@end
@implementation EAGLView
// synthesized properties and initWithCoder omitted
- (void)drawView {
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(0, 0, backingWidth, backingHeight);
// set up ortho projection to show game area of (0, 0) to (1, 1) [should use model getWidth...]
// note that the view is 50% taller than wide in portrait mode so we have a little extra space there
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(0.0, 1.0f, -0.25f, 1.25f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);
// clear view
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw borders
[self drawRectangleX1:0.0 y1:-0.25 x2:1.0 y2:0.0 color:[UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0]];
[self drawRectangleX1:0.0 y1:1.0 x2:1.0 y2:1.25 color:[UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0]];
NSArray* sprites = [model getSprites];
for (int i = 0; i < [sprites count]; i++)
{
Sprite* s = (Sprite*)[sprites objectAtIndex:i];
[self drawRectangleX1:[s getX] - [s getBoundingRadius] / 2
y1:[s getY] - [s getBoundingRadius] / 2
x2:[s getX] + [s getBoundingRadius] / 2
y2:[s getY] + [s getBoundingRadius] / 2
color:[s getColor]];
}
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
- (void)drawRectangleX1:(double)x1 y1:(double)y1 x2:(double)x2 y2:(double)y2 color:(UIColor*)c
{
GLfloat* verts = malloc(sizeof(GLfloat) * 8);
GLubyte* colors = malloc(sizeof(GLubyte) * 16);
verts[0] = x1;
verts[1] = y1;
verts[2] = x2;
verts[3] = y1;
verts[4] = x1;
verts[5] = y2;
verts[6] = x2;
verts[7] = y2;
const CGFloat* colorComponents = CGColorGetComponents(c.CGColor);
int numComponents = CGColorGetNumberOfComponents(c.CGColor);
for (int v = 0; v < 4; v++)
{
for (int c = 0; c < 4; c++)
{
if (c < numComponents - 1)
{
colors[v * 4 + c] = (int)(colorComponents[c] * 255);
}
else if (c == 3)
{
// alpha is last component
colors[v * 4 + c] = (int)(colorComponents[numComponents - 1] * 255);
}
else
{
// assume grayscale; use 1st component
colors[v * 4 + c] = (int)(colorComponents[0] * 255);
}
}
}
glVertexPointer(2, GL_FLOAT, 0, verts);
glEnableClientState(GL_VERTEX_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
free(verts);
free(colors);
}
// layoutSubviews, createFramebufer, destroyFramebuffer omitted
- (void)update
{
if (model != nil)
{
[model update];
}
[self drawView];
}
- (id) init
{
model = nil;
return [super init];
}
- (void)associate:(PeanutsModel*) d
{
model = [d retain];
}
- (void)startAnimation {
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(update) userInfo:nil repeats:YES];
}
// stopAnimation, setAnimationTimer, setAnimationInterval omitted
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
[model movePlayerXDir:1 yDir:0];
// haven't enabled multi-touch, so anyObject gets _the_ touch
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInView:self];
// compute touch location in world space (hard-coded (0,-.25)-(1,1.25) viewport)
double touchX = (location.x - self.bounds.origin.x) / self.bounds.size.width;
double touchY = 1.25 - (location.y - self.bounds.origin.y) / self.bounds.size.height * 1.5;
double playerX = [[model getPlayer] getX];
double playerY = [[model getPlayer] getY];
// figure out direction from player to touch
int dx = 0;
if (touchX + 0.1 < playerX)
{
dx = -1;
}
else if (touchX - 0.1 > playerX)
{
dx = 1;
}
int dy = 0;
if (touchY + 0.1 < playerY)
{
dy = -1;
}
else if (touchY - 0.1 > playerY)
{
dy = 1;
}
[model movePlayerXDir:dx yDir:dy];
}
- (void)dealloc {
[self stopAnimation];
if ([EAGLContext currentContext] == context) {
[EAGLContext setCurrentContext:nil];
}
if (model != nil)
{
[model release];
}
[context release];
[super dealloc];
}
@end
elephant.m
#import "elephant.h"
#import "peanutsmodel.h"
#import "hole.h"
@implementation Elephant
-(id) initAtX:(double)initX y:(double)initY
{
[super initWithX:initX y:initY vx:0.0 vy:0.0 radius:ELEPHANT_DEFAULT_SIZE color:[UIColor grayColor]];
peanutsEaten = 0;
return self;
}
-(double) getMaximumSpeed
{
return 0.2;
}
-(void) updateForTime:(double)t model:(GameModel*)m
{
PeanutsModel* pm = (PeanutsModel*)m;
if ([self getState] != STATE_FULL)
{
// get direction to peanut
double peanutX = [[pm getPeanut] getX];
double peanutY = [[pm getPeanut] getY];
double dx = peanutX - [self getX];
double dy = peanutY - [self getY];
// change to one of 8 compass directions
if (fabs(dx) < [self getBoundingRadius] / 4)
{
dx = 0;
}
else
{
dx = (dx == 0 ? 0 : dx / fabs(dx)); // signum(dx)
}
if (fabs(dy) < [self getBoundingRadius] / 4)
{
dy = 0;
}
else
{
dy = (dy == 0 ? 0 : dy / fabs(dy)); // signum(dy)
}
[self setVelocityX:dx * [self getMaximumSpeed] y:dy * [self getMaximumSpeed]];
if (peanutsEaten > MAX_PEANUTS && [self getStateTime] > LAST_PEANUT_TIME)
{
[self setState:STATE_FULL];
}
}
else
{
[self setVelocityX:0.0 y:0.0];
if ([self getStateTime] > RECOVERY_TIME)
{
[self setState:STATE_NORMAL];
[m addSprite:[[[Hole alloc] initAtX:[self getX] y:[self getY]] autorelease]];
peanutsEaten = 0;
}
}
[super updateForTime:t model:m];
}
-(void) eatPeanut
{
peanutsEaten++;
[self setState:STATE_NORMAL];
}
-(void) dealloc
{
[super dealloc];
}
@end
gamemodel.m
#import "gamemodel.h"
#import "sprite.h"
@implementation GameModel
-(id) init
{
[super init];
sprites = [[NSMutableArray alloc] init];
toAdd = [[NSMutableArray alloc] init];
toRemove = [[NSMutableArray alloc] init];
lastUpdate = 0.0;
return self;
}
-(NSArray*) getSprites
{
NSMutableArray* result = [[[NSMutableArray alloc] init] autorelease];
[result addObjectsFromArray:sprites];
[result removeObjectsInArray:toRemove];
return result;
}
-(void) addSprite:(Sprite*)s
{
if (![sprites containsObject:s] && ![toAdd containsObject:s])
{
[toAdd addObject:s];
}
}
-(void) removeSprite:(Sprite*)s
{
if (![toRemove containsObject:s] && [sprites containsObject:s])
{
[toRemove addObject:s];
}
}
-(double) getWidth
{
return 1.0;
}
-(double) getHeight
{
return 1.0;
}
-(void) processRemovedSprites
{
[sprites removeObjectsInArray:toRemove];
[toRemove removeObjectsInRange:NSMakeRange(0, [toRemove count])];
}
-(void) processAddedSprites
{
[sprites addObjectsFromArray:toAdd];
[toAdd removeObjectsInRange:NSMakeRange(0, [toAdd count])];
}
-(void) update
{
// get current time since 1970
NSDate* currentDate = [NSDate date];
NSTimeInterval currentTime = [currentDate timeIntervalSince1970];
if (lastUpdate != 0.0)
{
for (int i = 0; i < [sprites count]; i++)
{
[(Sprite*)[sprites objectAtIndex:i] updateForTime:currentTime - lastUpdate model:self];
}
}
[self checkCollisions];
[self processRemovedSprites];
[self processAddedSprites];
lastUpdate = currentTime;
}
-(void) checkCollisions
{
for (int i = 0; i < [sprites count]; i++)
{
Sprite* s1 = [sprites objectAtIndex:i];
for (int j = i + 1; j < [sprites count]; j++)
{
Sprite* s2 = [sprites objectAtIndex:j];
if ([s1 collidesWith:s2])
{
[self handleCollisionSprite:s1 with:s2];
}
}
}
}
-(void) handleCollisionSprite:(Sprite*)s1 with:(Sprite*)s2
{
}
-(void) dealloc
{
[sprites release];
[toAdd release];
[toRemove release];
[super dealloc];
}
@end
hole.m
#import "hole.h"
@implementation Hole
-(id) initAtX:(double)initX y:(double)initY
{
[super initWithX:initX y:initY vx:0.0 vy:0.0 radius:HOLE_DEFAULT_SIZE color:[UIColor blackColor]];
return self;
}
-(void) dealloc
{
[super dealloc];
}
@end
peanut.m
#import "peanut.h"
@implementation Peanut
-(id) initAtX:(double)initX y:(double)initY
{
[super initWithX:initX y:initY vx:0.0 vy:0.0 radius:PEANUT_DEFAULT_SIZE color:[UIColor brownColor]];
return self;
}
-(int) getValue
{
double t = [self getStateTime];
if (t < 2.0)
{
return 50;
}
else if (t < 4.0)
{
return 20;
}
else
{
return 10;
}
}
-(void) dealloc
{
[super dealloc];
}
@end
PeanutsAppDelegate.m
#import "PeanutsAppDelegate.h"
#import "EAGLView.h"
#import "peanutsmodel.h"
@implementation PeanutsAppDelegate
// synthesis of properties omitted
- (id)init
{
[super init];
model = [[PeanutsModel alloc] init];
return self;
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
glView.animationInterval = 1.0 / 60.0;
[glView startAnimation];
[glView associate:model];
}
// applicationWillResignActive, applicationDidBecomeActive omitted
- (void)dealloc {
[window release];
[glView release];
[model release];
[super dealloc];
}
@end
peanutsmodel.m
#import <stdlib.h>
#import "peanutsmodel.h"
#import "elephant.h"
#import "player.h"
#import "peanut.h"
#import "hole.h"
@implementation PeanutsModel
-(id) init
{
[super init];
elephant = [[[Elephant alloc] initAtX:0.9 y:0.5] autorelease];
[self addSprite:elephant];
player = [[[Player alloc] initAtX:0.1 y:0.5] autorelease];
[self addSprite:player];
[self makePeanut];
score = 0;
gameLength = DEFAULT_GAME_LENGTH;
NSDate* currentDate = [NSDate date];
startTime = [currentDate timeIntervalSince1970];
return self;
}
-(int) getPlayerScore
{
return score;
}
-(Peanut*) getPeanut
{
return peanut;
}
-(Player*) getPlayer
{
return player;
}
-(void) movePlayerXDir:(int)dx yDir:(int)dy
{
[player moveDirX:dx y:dy];
}
-(void) makePeanut
{
if (peanut != nil)
{
[self removeSprite:peanut];
}
double px = (double)random() / RAND_MAX;
double py = (double)random() / RAND_MAX;
peanut = [[[Peanut alloc] initAtX:px y:py] autorelease];
[self addSprite:peanut];
}
-(int) getTimeLeft
{
NSDate* currentDate = [NSDate date];
NSTimeInterval currentTime = [currentDate timeIntervalSince1970];
NSTimeInterval timeElapsed = currentTime - startTime;
if (timeElapsed > gameLength)
{
return 0;
}
else
{
return (int)(gameLength - timeElapsed);
}
}
-(void) update
{
if ([self getTimeLeft] > 0)
{
[super update];
}
}
-(void) handleCollisionSprite:(Sprite*)s1 with:(Sprite*) s2
{
if ([s1 isMemberOfClass:[Peanut class]] || [s2 isMemberOfClass:[Peanut class]])
{
// swap to make s1 the one that ate the peanut
if ([s1 isMemberOfClass:[Peanut class]])
{
Sprite* temp = s1;
s1 = s2;
s2 = temp;
}
if ([s1 isMemberOfClass:[Player class]])
{
// player ate peanut
int value = [(Peanut*)s2 getValue];
score += value;
[self makePeanut];
// and add a value display when you make them
}
else if ([s1 isMemberOfClass:[Elephant class]])
{
// elephant ate peanut
[(Elephant*)s1 eatPeanut];
[self makePeanut];
}
else
{
// something else ran into the peanut
}
}
else if ([s1 isMemberOfClass:[Elephant class]] && [s2 isMemberOfClass:[Player class]])
{
// player runs into elephant
[(Player*)s2 trample];
}
else if([s2 isMemberOfClass:[Elephant class]] && [s1 isMemberOfClass:[Player class]])
{
// elephant tramples player
[(Player*)s1 trample];
}
else if ([s1 isMemberOfClass:[Player class]] && [s2 isMemberOfClass:[Hole class]])
{
// player falls in hole
[(Player*)s1 fallInHole];
[self removeSprite:s2];
}
else if ([s2 isMemberOfClass:[Player class]] && [s1 isMemberOfClass:[Hole class]])
{
// hole attacks player
[(Player*)s2 fallInHole];
[self removeSprite:s1];
}
}
-(void) dealloc
{
[super dealloc];
}
@end
player.m
#import "player.h"
#import "gamemodel.h"
@implementation Player
-(id) initAtX:(double)initX y:(double)initY
{
[super initWithX:initX y:initY vx:0.0 vy:0.0 radius:PLAYER_DEFAULT_SIZE color:[UIColor redColor]];
return self;
}
-(double) getMaximumSpeed
{
return 0.2;
}
-(void) moveDirX:(int)dx y:(int)dy
{
if ([self getState] != STATE_TRAMPLED)
{
[self setVelocityX:[self getMaximumSpeed] * dx y:[self getMaximumSpeed] * dy];
}
else
{
recoverDx = dx;
recoverDy = dy;
}
}
-(void) trample
{
if ([self getState] != STATE_TRAMPLED)
{
[self setState:STATE_TRAMPLED];
recoverDx = 0;
recoverDy = 0;
recoveryTime = PLAYER_RECOVERY_TIME;
}
}
-(void) fallInHole
{
if ([self getState] != STATE_TRAMPLED)
{
[self setState:STATE_TRAMPLED];
recoverDx = 0;
recoverDy = 0;
recoveryTime = PLAYER_CLIMBING_TIME;
}
}
-(void) updateForTime:(double)t model:(GameModel*)m
{
if ([self getState] == STATE_TRAMPLED)
{
if ([self getStateTime] > recoveryTime)
{
// recovered from trampling or falling
[self setState:STATE_NORMAL];
[self moveDirX:recoverDx y:recoverDy];
}
else
{
[self setVelocityX:0.0 y:0.0];
}
}
[super updateForTime:t model:m];
// do wraparound
if ([self getX] < [self getBoundingRadius] / 2)
{
x = [m getWidth] - [self getBoundingRadius] / 2;
}
else if ([self getX] + [self getBoundingRadius] / 2 > [m getWidth])
{
x = [self getBoundingRadius] / 2;
}
if ([self getY] < [self getBoundingRadius] / 2)
{
y = [m getHeight] - [self getBoundingRadius] / 2;
}
else if ([self getY] + [self getBoundingRadius] / 2 > [m getHeight])
{
y = [self getBoundingRadius] / 2;
}
}
-(void) dealloc
{
[super dealloc];
}
@end
sprite.m
#import <math.h>
#import "sprite.h"
@implementation Sprite
-(id) initWithX:(double)initX y:(double)initY radius:(double)r
{
[super init];
x = initX;
y = initY;
vx = 0.0;
vy = 0.0;
radius = r;
color = [[UIColor blackColor] retain];
state = 0;
stateTime = 0.0;
return self;
}
-(id) initWithX:(double)initX y:(double)initY vx:(double)initVx vy:(double)initVy radius:(double)r
{
[super init];
x = initX;
y = initY;
vx = initVx;
vy = initVy;
radius = r;
color = [[UIColor blackColor] retain];
state = STATE_NORMAL;
stateTime = 0.0;
return self;
}
-(id) initWithX:(double)initX y:(double)initY vx:(double)initVx vy:(double)initVy radius:(double)r color:(UIColor *)c
{
[super init];
x = initX;
y = initY;
vx = initVx;
vy = initVy;
radius = r;
color = [c retain];
state = 0;
stateTime = 0.0;
return self;
}
-(double) getX
{
return x;
}
-(double) getY
{
return y;
}
-(UIColor*) getColor
{
return color;
}
-(double) getMaximumSpeed
{
return 1e+10;
}
-(void) setVelocityX:(double)newVx y:(double)newVy
{
vx = newVx;
vy = newVy;
// limit speed to max velocity
double v = sqrt(vx * vx + vy * vy);
double maxV = [self getMaximumSpeed];
if (v > maxV)
{
vx = vx / (v / maxV);
vy = vy / (v / maxV);
}
}
-(double) getBoundingRadius
{
return radius;
}
-(BOOL) collidesWith:(Sprite*)s
{
double dx = x - [s getX];
double dy = y - [s getY];
double dist = sqrt(dx * dx + dy * dy);
return (dist < radius + [s getBoundingRadius]);
}
-(void) updateForTime:(double)t model:(GameModel*)m
{
stateTime += t;
x += vx * t;
y += vy * t;
}
-(int) getState
{
return state;
}
-(void) setState:(int)s
{
state = s;
stateTime = 0.0;
}
-(double) getStateTime
{
return stateTime;
}
-(void) dealloc
{
[color release];
[super dealloc];
}
@end
EAGLView.h
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
@class PeanutsModel;
@interface EAGLView : UIView {
@private
// boilerplate from OpenGL ES template omitted
NSTimer *animationTimer;
NSTimeInterval animationInterval;
PeanutsModel *model;
}
@property NSTimeInterval animationInterval;
// more boilerplayer omitted
- (void)update;
- (void)associate:(PeanutsModel*) model;
@end
elephant.h
#import "sprite.h"
@interface Elephant : Sprite
{
int peanutsEaten;
}
#define ELEPHANT_DEFAULT_SIZE 0.025
#define MAX_PEANUTS 4
#define STATE_FULL 1
#define RECOVERY_TIME 2.0
#define LAST_PEANUT_TIME 1.0
-(id) initAtX:(double)initX y:(double)initY;
-(double) getMaximumSpeed;
-(void) updateForTime:(double)t model:(GameModel*)m;
-(void) eatPeanut;
-(void) dealloc;
@end
gamemodel.h
#import <Foundation/Foundation.h>
@class Sprite;
@interface GameModel : NSObject
{
NSMutableArray* sprites;
NSMutableArray* toRemove;
NSMutableArray* toAdd;
NSTimeInterval lastUpdate;
}
-(id) init;
-(NSArray*) getSprites;
-(void) addSprite:(Sprite*)s;
-(void) removeSprite:(Sprite*)s;
-(double) getWidth;
-(double) getHeight;
-(void) processRemovedSprites;
-(void) processAddedSprites;
-(void) update;
-(void) checkCollisions;
-(void) handleCollisionSprite:(Sprite*)s1 with:(Sprite*) s2;
-(void) dealloc;
@end
hole.h
#import "sprite.h"
@interface Hole : Sprite
#define HOLE_DEFAULT_SIZE 0.01
-(id) initAtX:(double)x y:(double)y;
-(void) dealloc;
@end
peanut.h
#import "sprite.h"
@interface Peanut : Sprite
#define PEANUT_DEFAULT_SIZE 0.01
-(id) initAtX:(double)initX y:(double)initY;
-(int) getValue;
-(void) dealloc;
@end
PeanutsAppDelegate.h
#import <UIKit/UIKit.h>
@class EAGLView;
@class PeanutsModel;
@interface PeanutsAppDelegate : NSObject <UIApplicationDelegate> {
// default members omitted
PeanutsModel* model;
}
// property declarations omitted
- (id)init;
- (void)dealloc;
@end
peanutsmodel.h
#import <Foundation/Foundation.h>
#import "gamemodel.h"
@class Player;
@class Elephant;
@class Peanut;
@interface PeanutsModel : GameModel
{
Player* player;
Elephant* elephant;
Peanut* peanut;
NSTimeInterval startTime;
NSTimeInterval gameLength;
int score;
}
#define DEFAULT_GAME_LENGTH 180;
-(id) init;
-(int) getPlayerScore;
-(Peanut*) getPeanut;
-(Player*) getPlayer;
-(void) movePlayerXDir:(int)dx yDir:(int)dy;
-(void) makePeanut;
-(int) getTimeLeft;
-(void) handleCollisionSprite:(Sprite*)s1 with:(Sprite*) s2;
@end
player.h
#import "sprite.h"
@interface Player : Sprite
{
int recoverDx;
int recoverDy;
double recoveryTime;
}
#define PLAYER_DEFAULT_SIZE 0.025
#define PLAYER_RECOVERY_TIME 0.5
#define PLAYER_CLIMBING_TIME 0.2
#define STATE_TRAMPLED 1
-(id) initAtX:(double)initX y:(double)initY;
-(double) getMaximumSpeed;
-(void) moveDirX:(int)dx y:(int)dy;
-(void) trample;
-(void) fallInHole;
-(void) updateForTime:(double)t model:(GameModel*)m;
-(void) dealloc;
@end
sprite.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class GameModel;
@interface Sprite : NSObject
{
double x;
double y;
double vx;
double vy;
double radius;
UIColor *color;
int state;
double stateTime;
}
#define STATE_NORMAL 0
-(id) initWithX:(double)initX y:(double)initY radius:(double)r;
-(id) initWithX:(double)initX y:(double)initY vx:(double)initVx vy:(double)initVy radius:(double)r;
-(id) initWithX:(double)initX y:(double)initY vx:(double)initVx vy:(double)initVy radius:(double)r color:(UIColor *)c;
-(double) getX;
-(double) getY;
-(UIColor*) getColor;
-(double) getMaximumSpeed;
-(void) setVelocityX:(double)newVx y:(double)newVy;
-(double) getBoundingRadius;
-(BOOL) collidesWith:(Sprite*)s;
-(void) updateForTime:(double)t model:(GameModel*)m;
-(int) getState;
-(void) setState:(int)s;
-(double) getStateTime;
-(void) dealloc;
@end
This code can also be downloaded from the files
EAGLView.m,
elephant.m,
gamemodel.m,
hole.m,
peanut.m,
PeanutsAppDelegate.m,
peanutsmodel.m,
player.m,
sprite.m,
EAGLView.h,
elephant.h,
gamemodel.h,
hole.h,
peanut.h,
PeanutsAppDelegate.h,
peanutsmodel.h,
player.h,
and sprite.h.