Subversion Repositories Kolibri OS

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1806 yogev_ezra 1
/*
2
------------------------------------------------------------
3
	Fixed Rate Pig - a fixed logic frame rate demo
4
------------------------------------------------------------
5
 * Copyright (C) 2004 David Olofson 
6
 *
7
 * This software is released under the terms of the GPL.
8
 *
9
 * Contact author for permission if you want to use this
10
 * software, or work derived from it, under other terms.
11
 */
12
 
13
#include 
14
#include 
15
#include 
16
#include 
17
#include 
18
#include 
19
#include "engine.h"
20
 
21
 
22
/* Graphics defines */
23
#define	SCREEN_W	800
24
#define	SCREEN_H	600
25
#define	TILE_W		32
26
#define	TILE_H		32
27
#define	MAP_W		25
28
#define	MAP_H		17
29
#define	FONT_SPACING	45
30
#define	PIG_FRAMES	12
31
 
32
/* World/physics constants */
33
#define	GRAV_ACC	4
34
#define	JUMP_SPEED	28
35
 
36
/* Sprite collision groups */
37
#define	GROUP_ENEMY	0x0001
38
#define	GROUP_POWERUP	0x0002
39
 
40
typedef enum
41
{
42
	POWER_LIFE,
43
	POWER_BONUS1,
44
	POWER_BONUS2
45
} POWERUPS;
46
 
47
 
48
typedef struct GAMESTATE
49
{
50
	/* I/O */
51
	PIG_engine	*pe;
52
	Uint8		*keys;
53
	int		nice;
54
	int		refresh_screen;
55
	int		jump;
56
 
57
	/* Sprites */
58
	int		lifepig;
59
	int		scorefont;
60
	int		glassfont;
61
	int		icons;
62
	int		stars;
63
	int		pigframes;
64
	int		evil;
65
	int		slime;
66
 
67
	/* Global game state */
68
	int		running;
69
	int		level;
70
	int		lives;
71
	float		lives_wobble;
72
	float		lives_wobble_time;
73
	int		score;
74
	float		score_wobble;
75
	float		score_wobble_time;
76
	float		dashboard_time;
77
	int		fun_count;
78
	int		enemycount;
79
	int		messages;
80
 
81
	/* Objects */
82
	PIG_object	*player;
83
 
84
	/* Statistics */
85
	int		logic_frames;
86
	int		rendered_frames;
87
} GAMESTATE;
88
 
89
 
90
static void add_life(GAMESTATE *gs);
91
static void remove_life(GAMESTATE *gs);
92
static void inc_score(GAMESTATE *gs, int v);
93
static void inc_score_nobonus(GAMESTATE *gs, int v);
94
static PIG_object *new_player(GAMESTATE *gs);
95
static void message(GAMESTATE *gs, const char *s);
96
static PIG_object *new_powerup(GAMESTATE *gs,
97
		int x, int y, int speed, POWERUPS type);
98
static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy);
99
static PIG_object *new_evil(GAMESTATE *gs,
100
		int x, int y, int speed);
101
static PIG_object *new_slime(GAMESTATE *gs,
102
		int x, int y, int speed);
103
 
104
 
105
/*----------------------------------------------------------
106
	Init, load stuff etc
107
----------------------------------------------------------*/
108
 
109
static int load_level(GAMESTATE *gs, int map)
110
{
111
	const char *m;
112
	const char *k;
113
	if(map > 4)
114
		map = 1;
115
	gs->level = map;
116
	pig_object_close_all(gs->pe);
117
	gs->enemycount = 0;
118
	gs->messages = 0;
119
	switch(map)
120
	{
121
	  case 1:
122
	  case 2:
123
	  case 4:
124
	  	k =	"abcd" "efgh" "ijkl"	/* Red, green, yellov */
125
			"0123456789ABCDEFG"	/* Sky */
126
			"xyz";			/* Single R, G, Y */
127
		break;
128
	  case 0:
129
	  case 3:
130
	  	k =	"abcd" "efgh" "ijkl"	/* Red, green, yellov */
131
			"................."
132
			"xyz"			/* Single R, G, Y */
133
			"-+012345..ABCDEF";	/* Night sky */
134
		break;
135
	}
136
	switch(map)
137
	{
138
	  case 0: m =	"-------------ad----------"
139
			"-abcd-x-ad--ad-abcd-acd--"
140
			"-x----x--abcd--x----x--x-"
141
			"-abd--x---ad---abd--x--x-"
142
			"-x----x--abcd--x----x--x-"
143
			"-x----x-ad--ad-abcd-abd--"
144
			"----efhad-eh--egh-efh----"
145
			"----y--y-y--y--y--y------"
146
			"++++efh++efgh++y++eh+++++"
147
			"0123y50y2y45y12y45y123450"
148
			"ABCDyFAyCyEFyBCyEFeghDEFA"
149
			"----ijkjl-ijkl--ijkjl----"
150
			"----il--il-il--il--------"
151
			"----ijkjl--il--il-ikl----"
152
			"----il-----il--il--il----"
153
			"----il----ijkl--ijkjl----"
154
			"-------------------------";
155
			break;
156
	  case 1: m =	"0000000000000000000000000"
157
			"1111111111111111111111111"
158
			"2222222222222222222222222"
159
			"3333333333333333333333333"
160
			"4444444444444444444444444"
161
			"5555555555555555555555555"
162
			"6666666666666666666666666"
163
			"7777777ijkjkjjkjkl7777777"
164
			"8888888888888888888888888"
165
			"9999999999999999999999999"
166
			"abcdAAAAAAAAAAAAAAAAAabcd"
167
			"BBBBBBBBBBBBBBBBBBBBBBBBB"
168
			"CCCCCCCCCCCCCCCCCCCCCCCCC"
169
			"efgfgffgfgfgfgfggffgfgfgh"
170
			"EEEEEEEEEEEEEEEEEEEEEEEEE"
171
			"FFFFFFFFFFFFFFFFFFFFFFFFF"
172
			"GGGGGGGGGGGGGGGGGGGGGGGGG";
173
			new_evil(gs, 2, 0, 5);
174
			new_evil(gs, 22, 0, 5);
175
			new_evil(gs, 5, 0, 7);
176
			new_evil(gs, 19, 0, 7);
177
			break;
178
	  case 2: m =	"0000000000000000000000000"
179
			"1111111111111111111111111"
180
			"2222222222222222222222222"
181
			"3333333333333333333333333"
182
			"4444444444xxxxx4444444444"
183
			"5555555555x555x5555555555"
184
			"6666666666x666x6666666666"
185
			"7777777xxxx777xxxx7777777"
186
			"8888888x888888888x8888888"
187
			"9999999x999999999x9999999"
188
			"AAAAAAAxxxxAAAxxxxAAAAAAA"
189
			"BBBBBBBBBBxBBBxBBBBBBBBBB"
190
			"CCCCCCCCCCxCCCxCCCCCCCCCC"
191
			"DDDDDDDDDDxxxxxDDDDDDDDDD"
192
			"EEEEEEEEEEEEEEEEEEEEEEEEE"
193
			"ijklFFFFFFFFFFFFFFFFFijkl"
194
			"GGGijlGilGilGilGilGiklGGG";
195
			new_slime(gs, 2, 0, -5);
196
			new_slime(gs, 22, 0, 5);
197
			new_evil(gs, 8, 0, 7);
198
			new_evil(gs, 16, 0, -7);
199
			break;
200
	  case 3: m =	"-------------------------"
201
			"-------------------------"
202
			"-------------------------"
203
			"-------------------------"
204
			"ijkl----------efgh-------"
205
			"-------------------------"
206
			"-------------------------"
207
			"z----------------abcbcbbd"
208
			"+++++++++++++++++++++++++"
209
			"01z3450123450123450123450"
210
			"ABCDEFABCefgfgfghFABCDEFA"
211
			"----z--------------------"
212
			"-------------------------"
213
			"------z--------------ijkl"
214
			"-------------------------"
215
			"-------------------------"
216
			"abdefghijkl---efghijklabd";
217
			new_slime(gs, 5, 0, -5);
218
			new_slime(gs, 20, 15, -5);
219
			new_evil(gs, 1, 0, 7);
220
			new_evil(gs, 20, 0, 10);
221
			new_evil(gs, 15, 0, 7);
222
			break;
223
	  case 4: m =	"0000000000000000000000000"
224
			"1111111111111111111111111"
225
			"2222222222222222222222222"
226
			"3333333333333333333333333"
227
			"4444444444444444444444444"
228
			"555555555555z555555555555"
229
			"66666666666ijl66666666666"
230
			"7777777777ijlil7777777777"
231
			"888888888ijlikkl888888888"
232
			"99999999ijkjklikl99999999"
233
			"AAAAAAAikjlijkjkjlAAAAAAA"
234
			"BBBBBBiklijkjlijkjlBBBBBB"
235
			"CCCCCijkjlikkjklikklCCCCC"
236
			"DDDDijklijjklikjkjkklDDDD"
237
			"EEEijkkjkjlikjkjlijjklEEE"
238
			"FFijkjlilijkjklikjlikklFF"
239
			"efggfggfgfgfggfgfgfgfgfgh";
240
			new_evil(gs, 11, 0, 5);
241
			new_evil(gs, 10, 0, 6);
242
			new_evil(gs, 9, 0, 7);
243
			new_evil(gs, 8, 0, 8);
244
			new_evil(gs, 7, 0, 9);
245
			new_evil(gs, 6, 0, 10);
246
			new_evil(gs, 5, 0, 11);
247
			new_evil(gs, 4, 0, 12);
248
			new_evil(gs, 3, 0, 13);
249
			new_slime(gs, 1, 0, 16);
250
			new_slime(gs, 24, 0, -14);
251
			break;
252
	  default:
253
		return -1;
254
	}
255
	pig_map_from_string(gs->pe->map, k, m);
256
	gs->refresh_screen = gs->pe->pages;
257
	return 0;
258
}
259
 
260
 
261
static GAMESTATE *init_all(SDL_Surface *screen)
262
{
263
	int i;
264
	PIG_map *pm;
265
	GAMESTATE *gs = (GAMESTATE *)calloc(1, sizeof(GAMESTATE));
266
	if(!gs)
267
		return NULL;
268
 
269
	gs->running = 1;
270
 
271
	gs->pe = pig_open(screen);
272
	if(!gs->pe)
273
	{
274
		fprintf(stderr, "Could not open the Pig Engine!\n");
275
		free(gs);
276
		return NULL;
277
	}
278
	gs->pe->userdata = gs;
279
 
280
	pig_viewport(gs->pe, 0, 0, SCREEN_W, MAP_H * TILE_H);
281
 
282
	i = gs->lifepig = pig_sprites(gs->pe, "lifepig.png", 0, 0);
283
	i |= gs->scorefont = pig_sprites(gs->pe, "font.png", 44, 56);
284
	i |= gs->glassfont = pig_sprites(gs->pe, "glassfont.png", 60, 60);
285
	i |= gs->icons = pig_sprites(gs->pe, "icons.png", 48, 48);
286
	i |= gs->stars = pig_sprites(gs->pe, "stars.png", 32, 32);
287
	i |= gs->pigframes = pig_sprites(gs->pe, "pigframes.png", 64, 48);
288
	i |= gs->evil = pig_sprites(gs->pe, "evil.png", 48, 48);
289
	i |= gs->slime = pig_sprites(gs->pe, "slime.png", 48, 48);
290
	if(i < 0)
291
	{
292
		fprintf(stderr, "Could not load graphics!\n");
293
		pig_close(gs->pe);
294
		free(gs);
295
		return NULL;
296
	}
297
	for(i = gs->icons; i < gs->icons + 3*8; ++i)
298
		pig_hotspot(gs->pe, i, PIG_CENTER, 45);
299
	for(i = gs->pigframes; i < gs->pigframes + 12; ++i)
300
		pig_hotspot(gs->pe, i, PIG_CENTER, 43);
301
	for(i = gs->evil; i < gs->evil + 16; ++i)
302
		pig_hotspot(gs->pe, i, PIG_CENTER, 46);
303
	for(i = gs->slime; i < gs->slime + 16; ++i)
304
		pig_hotspot(gs->pe, i, PIG_CENTER, 46);
305
 
306
	pm = pig_map_open(gs->pe, MAP_W, MAP_H);
307
	if(!pm)
308
	{
309
		fprintf(stderr, "Could not create map!\n");
310
		pig_close(gs->pe);
311
		free(gs);
312
		return NULL;
313
	}
314
	if(pig_map_tiles(pm, "tiles.png", TILE_W, TILE_H) < 0)
315
	{
316
		fprintf(stderr, "Could not load background graphics!\n");
317
		pig_close(gs->pe);
318
		free(gs);
319
		return NULL;
320
	}
321
 
322
	/* Mark tiles for collision detection */
323
	pig_map_collisions(pm, 0, 12, PIG_ALL);	/* Red, green, yellov */
324
	pig_map_collisions(pm, 12, 17, PIG_NONE);/* Sky */
325
	pig_map_collisions(pm, 29, 3, PIG_ALL);	/* Single R, G, Y */
326
 
327
	load_level(gs, 0);
328
	return gs;
329
}
330
 
331
 
332
/*----------------------------------------------------------
333
	Render the dashboard
334
----------------------------------------------------------*/
335
static void dashboard(GAMESTATE *gs)
336
{
337
	SDL_Rect r;
338
	int i, v;
339
	float x;
340
	float t = SDL_GetTicks() * 0.001;
341
	r.x = 0;
342
	r.y = SCREEN_H - 56;
343
	r.w = SCREEN_W;
344
	r.h = 56;
345
	SDL_SetClipRect(gs->pe->surface, &r);
346
 
347
	/* Render "plasma bar" */
348
	for(i = 0; i < 56; ++i)
349
	{
350
		float f1, f2, m;
351
		SDL_Rect cr;
352
		cr.x = 0;
353
		cr.w = SCREEN_W;
354
		cr.y = SCREEN_H - 56 + i;
355
		cr.h = 1;
356
		f1 = .25 + .25 * sin(t * 1.7 + (float)i / SCREEN_H * 42);
357
		f1 += .25 + .25 * sin(-t * 2.1 + (float)i / SCREEN_H * 66);
358
		f2 = .25 + .25 * sin(t * 3.31 + (float)i / SCREEN_H * 90);
359
		f2 += .25 + .25 * sin(-t * 1.1 + (float)i / SCREEN_H * 154);
360
		m = sin((float)i * M_PI / 56.0);
361
		m = sin(m * M_PI * 0.5);
362
		m = sin(m * M_PI * 0.5);
363
		SDL_FillRect(gs->pe->surface,
364
				&cr, SDL_MapRGB(gs->pe->surface->format,
365
				((int)128.0 * f1 + 64) * m,
366
				((int)64.0 * f1 * f2 + 64) * m,
367
				((int)128.0 * f2 + 32) * m
368
				));
369
	}
370
 
371
	/* Draw pigs... uh, lives! */
372
	x = -10;
373
	for(i = 0; i < gs->lives; ++i)
374
	{
375
		x += 48 + gs->lives_wobble *
376
				sin(gs->lives_wobble_time * 12) * .2;
377
		pig_draw_sprite(gs->pe, gs->lifepig,
378
				(int)x + gs->lives_wobble *
379
				sin(gs->lives_wobble_time * 20 + i * 1.7),
380
				SCREEN_H - 56/2);
381
	}
382
 
383
	/* Print score */
384
	x = SCREEN_W + 5;
385
	v = gs->score;
386
	for(i = 9; i >= 0; --i)
387
	{
388
		int n = v % 10;
389
		x -= 39 - gs->score_wobble *
390
				sin(gs->score_wobble_time * 15 + i * .5);
391
		pig_draw_sprite(gs->pe, gs->scorefont + n, (int)x,
392
				SCREEN_H - 56/2);
393
		v /= 10;
394
		if(!v)
395
			break;
396
	}
397
 
398
	pig_dirty(gs->pe, &r);
399
}
400
 
401
 
402
/*----------------------------------------------------------
403
	Game logic event handlers
404
----------------------------------------------------------*/
405
static void before_objects(PIG_engine *pe)
406
{
407
	GAMESTATE *gs = (GAMESTATE *)pe->userdata;
408
	if(gs->lives_wobble > 0)
409
	{
410
		gs->lives_wobble *= 0.95;
411
		gs->lives_wobble -= 0.3;
412
		if(gs->lives_wobble < 0)
413
			gs->lives_wobble = 0;
414
	}
415
	if(gs->score_wobble > 0)
416
	{
417
		gs->score_wobble *= 0.95;
418
		gs->score_wobble -= 0.3;
419
		if(gs->score_wobble < 0)
420
			gs->score_wobble = 0;
421
	}
422
	++gs->logic_frames;
423
 
424
	if(0 == gs->level)
425
	{
426
		switch(gs->fun_count % 60)
427
		{
428
		  case 17:
429
			new_powerup(gs, 250, -20, -10, POWER_LIFE);
430
			break;
431
		  case 29:
432
			new_powerup(gs, 550, -20, 10, POWER_LIFE);
433
			break;
434
		  case 37:
435
			new_powerup(gs, 250, -20, 10, POWER_BONUS2);
436
			break;
437
		  case 51:
438
			new_powerup(gs, 550, -20, -10, POWER_BONUS1);
439
			break;
440
		}
441
		if(150 == gs->fun_count % 300)
442
			message(gs, "Press Space!");
443
		++gs->fun_count;
444
	}
445
}
446
 
447
 
448
typedef enum
449
{
450
	WAITING,
451
	WALKING,
452
	FALLING,
453
	KNOCKED,
454
	NEXT_LEVEL,
455
	DEAD
456
} OBJECT_states;
457
 
458
 
459
static void player_handler(PIG_object *po, const PIG_event *ev)
460
{
461
	GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
462
	switch(ev->type)
463
	{
464
	  case PIG_PREFRAME:
465
		switch(po->state)
466
		{
467
		  case WAITING:
468
			if(1 == po->age)
469
				message(gs, "Get ready!");
470
			else if(po->age > 50)
471
				po->state = FALLING;
472
			break;
473
		  case WALKING:
474
			if(gs->keys[SDLK_LEFT])
475
			{
476
				po->ax = -(20 + po->vx) * .4;
477
				po->target = 3 + po->age % 4 - 1;
478
				if(5 == po->target)
479
					po->target = 3;
480
			}
481
			else if(gs->keys[SDLK_RIGHT])
482
			{
483
				po->ax = (20 - po->vx) * .4;
484
				po->target = 9 + po->age % 4 - 1;
485
				if(11 == po->target)
486
					po->target = 9;
487
			}
488
			else
489
			{
490
				po->ax = -po->vx * .8;
491
				if(po->target >= 6)
492
					po->target = (po->target + 1) %
493
							PIG_FRAMES;
494
				else if(po->target)
495
					--po->target;
496
			}
497
			break;
498
		  case FALLING:
499
			if(gs->keys[SDLK_LEFT])
500
				po->ax = -(20 + po->vx) * .2;
501
			else if(gs->keys[SDLK_RIGHT])
502
				po->ax = (20 - po->vx) * .2;
503
			else
504
				po->ax = -po->vx * .2;
505
			po->target = (po->target + 1) % PIG_FRAMES;
506
			break;
507
		}
508
		po->timer[0] = 1;
509
		break;
510
	  case PIG_TIMER0:
511
		if(po->x < 0)
512
			po->x = 0;
513
		else if(po->x > po->owner->view.w - 1)
514
			po->x = po->owner->view.w - 1;
515
		switch(po->state)
516
		{
517
		  case WALKING:
518
			if(po->power)
519
				--po->power;
520
			po->image = po->target % PIG_FRAMES;
521
			if(!pig_test_map(gs->pe, po->x, po->y + 1))
522
			{
523
				po->state = FALLING;
524
				po->ay = GRAV_ACC;
525
			}
526
			if(gs->jump || gs->keys[SDLK_UP])
527
			{
528
				po->ay = 0;
529
				po->vy = -JUMP_SPEED;
530
				po->state = FALLING;
531
				gs->jump = 0;
532
			}
533
			break;
534
		  case FALLING:
535
			if(po->vy > 2)
536
				po->power = 3;
537
			po->ay = GRAV_ACC;
538
			po->image = po->target;
539
			break;
540
		  case KNOCKED:
541
			po->power = 0;
542
			po->ay = GRAV_ACC;
543
			po->target = (po->target + 2) % PIG_FRAMES;
544
			po->image = po->target;
545
			po->ax = -po->vx * .2;
546
			break;
547
		  case NEXT_LEVEL:
548
			po->vx = (SCREEN_W / 2 - po->x) * .1;
549
			po->target = (po->target + 1) % PIG_FRAMES;
550
			po->image = po->target;
551
			break;
552
		  case DEAD:
553
			po->ax = po->ay = 0;
554
			po->vx = po->vy = 0;
555
			break;
556
		}
557
		if(gs->jump)
558
			--gs->jump;
559
		if(NEXT_LEVEL != po->state)
560
		{
561
			if(gs->enemycount <= 0)
562
			{
563
				message(gs, "Well Done!");
564
				po->state = NEXT_LEVEL;
565
				po->vy = 0;
566
				po->ay = -1;
567
				po->tilemask = 0;
568
				po->hitgroup = 0;
569
				po->timer[2] = 50;
570
			}
571
		}
572
		break;
573
 
574
	  case PIG_TIMER1:
575
		/* Snap out of KNOCKED mode */
576
		po->state = FALLING;
577
		break;
578
 
579
	  case PIG_TIMER2:
580
		switch(po->state)
581
		{
582
		  case NEXT_LEVEL:
583
			add_life(gs);
584
			pig_object_close(po);
585
			load_level(gs, gs->level + 1);
586
			new_player(gs);
587
			break;
588
		  default:
589
			pig_object_close(po);
590
			if(!new_player(gs))
591
				load_level(gs, 0);
592
			break;
593
		}
594
		break;
595
 
596
	  case PIG_HIT_TILE:
597
		if(KNOCKED == po->state)
598
			break;
599
 
600
		if(ev->cinfo.sides & PIG_TOP)
601
		{
602
			po->y = ev->cinfo.y;
603
			po->vy = 0;
604
			po->ay = 0;
605
		}
606
		po->state = WALKING;
607
		break;
608
 
609
	  case PIG_HIT_OBJECT:
610
		if(KNOCKED == po->state)
611
			break;
612
 
613
		switch(ev->obj->hitgroup)
614
		{
615
		  case GROUP_ENEMY:
616
			if((po->power && ev->cinfo.sides & PIG_TOP) ||
617
					(po->vy - ev->obj->vy) >= 15)
618
			{
619
				/* Win: Stomp! */
620
				inc_score(gs, ev->obj->score);
621
				ev->obj->y = ev->cinfo.y + 10;
622
				if(po->vy > 0)
623
					ev->obj->vy = po->vy;
624
				else
625
					ev->obj->vy = 10;
626
				ev->obj->ay = GRAV_ACC;
627
				ev->obj->tilemask = 0;
628
				ev->obj->hitgroup = 0;
629
				if(gs->jump || gs->keys[SDLK_UP])
630
				{
631
					/* Mega jump! */
632
					po->vy = -(JUMP_SPEED + 7);
633
					gs->jump = 0;
634
				}
635
				else
636
				{
637
					/* Bounce a little */
638
					po->vy = -15;
639
				}
640
				po->y = ev->cinfo.y;
641
				po->ay = 0;
642
				po->state = FALLING;
643
			}
644
			else
645
			{
646
				/* Lose: Knocked! */
647
				po->vy = -15;
648
				po->ay = GRAV_ACC;
649
				po->state = KNOCKED;
650
				po->timer[1] = 11;
651
				new_star(gs, po->x, po->y - 20, -5, 3);
652
				new_star(gs, po->x, po->y - 20, 2, -6);
653
				new_star(gs, po->x, po->y - 20, 4, 4);
654
			}
655
			break;
656
		  case GROUP_POWERUP:
657
			switch(ev->obj->score)
658
			{
659
			  case POWER_LIFE:
660
				add_life(gs);
661
				message(gs, "Extra Life!");
662
				break;
663
			  case POWER_BONUS1:
664
				/* Double or 100k bonus! */
665
				if(gs->score < 100000)
666
				{
667
					inc_score_nobonus(gs, gs->score);
668
					message(gs, "Double Score!");
669
				}
670
				else
671
				{
672
					inc_score_nobonus(gs, 100000);
673
					message(gs, "100 000!");
674
				}
675
				break;
676
			  case POWER_BONUS2:
677
				inc_score_nobonus(gs, 1000);
678
				message(gs, "1000!");
679
				break;
680
			}
681
			ev->obj->state = DEAD;
682
			ev->obj->tilemask = 0;
683
			ev->obj->hitgroup = 0;
684
			ev->obj->vy = -20;
685
			ev->obj->ay = -2;
686
			break;
687
		}
688
		break;
689
	  case PIG_OFFSCREEN:
690
		/*
691
		 * Dead pigs don't care about being off-screen.
692
		 * A timer is used to remove them, and to continue
693
		 * the game with a new life.
694
		 */
695
		if(DEAD == po->state)
696
			break;
697
		if(po->y < 0)	/* Above the playfield is ok. */
698
			break;
699
		if(gs->lives)
700
			message(gs, "Oiiiiiiink!!!");
701
		else
702
			message(gs, "Game Over!");
703
		po->state = DEAD;
704
		po->timer[2] = 50;
705
	  default:
706
		break;
707
	}
708
}
709
 
710
 
711
static void powerup_handler(PIG_object *po, const PIG_event *ev)
712
{
713
	GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
714
	switch(ev->type)
715
	{
716
	  case PIG_PREFRAME:
717
		if(DEAD == po->state)
718
			break;
719
		po->ax = (po->target - po->vx) * .3;
720
		po->ay = GRAV_ACC;
721
		po->image = po->age % 8;
722
		++po->power;
723
		break;
724
	  case PIG_HIT_TILE:
725
		if(DEAD == po->state)
726
			break;
727
		if(po->power > 2)
728
			po->target = -po->target;
729
		po->power = 0;
730
		po->vy = 0;
731
		po->ay = 0;
732
		po->x = ev->cinfo.x + po->vx;
733
		po->y = ev->cinfo.y;
734
		break;
735
	  case PIG_OFFSCREEN:
736
		if(po->y > SCREEN_H || (po->y < -100))
737
		{
738
			pig_object_close(po);
739
			--gs->enemycount;
740
		}
741
	  default:
742
		break;
743
	}
744
}
745
 
746
 
747
static void star_handler(PIG_object *po, const PIG_event *ev)
748
{
749
	switch(ev->type)
750
	{
751
	  case PIG_PREFRAME:
752
		if(po->age >= 8)
753
			pig_object_close(po);
754
		else
755
			po->image = po->age;
756
	  default:
757
		break;
758
	}
759
}
760
 
761
 
762
static void evil_handler(PIG_object *po, const PIG_event *ev)
763
{
764
	GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
765
	int look_x;
766
	switch(ev->type)
767
	{
768
	  case PIG_PREFRAME:
769
		if(DEAD == po->state)
770
			break;
771
		po->ax = (po->target - po->vx) * .5;
772
		po->ay = GRAV_ACC;
773
		po->image = po->age % 16;
774
		break;
775
	  case PIG_HIT_TILE:
776
		if(DEAD == po->state)
777
			break;
778
		po->vy = 0;
779
		po->ay = 0;
780
		po->x = ev->cinfo.x + po->vx;
781
		po->y = ev->cinfo.y;
782
		break;
783
	  case PIG_OFFSCREEN:
784
		if(po->y > SCREEN_H)
785
		{
786
			pig_object_close(po);
787
			--gs->enemycount;
788
		}
789
		break;
790
	  case PIG_POSTFRAME:
791
		if(DEAD == po->state)
792
			break;
793
		look_x = 10 + fabs(po->vx * 2);
794
		if(po->target < 0)
795
			look_x = -look_x;
796
		if(!pig_test_map(po->owner, po->x + look_x, po->y + 1))
797
			po->target = -po->target;
798
	  default:
799
		break;
800
	}
801
}
802
 
803
 
804
static void slime_handler(PIG_object *po, const PIG_event *ev)
805
{
806
	GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
807
	int look_x;
808
	switch(ev->type)
809
	{
810
	  case PIG_PREFRAME:
811
		if(DEAD == po->state)
812
			break;
813
		po->ax = (po->target - po->vx) * .2;
814
		po->ay = GRAV_ACC;
815
		po->image = po->age % 16;
816
		break;
817
	  case PIG_HIT_TILE:
818
		po->vy = -(JUMP_SPEED + GRAV_ACC);
819
		po->ay = 0;
820
		po->y = ev->cinfo.y;
821
		break;
822
	  case PIG_OFFSCREEN:
823
		if(po->y > SCREEN_H)
824
		{
825
			pig_object_close(po);
826
			--gs->enemycount;
827
		}
828
		break;
829
	  case PIG_POSTFRAME:
830
		if(DEAD == po->state)
831
			break;
832
		/* Don't bother looking if we're close to a floor. */
833
		if(pig_test_map_vector(po->owner,
834
				po->x, po->y,
835
				po->x, po->y + 48,
836
				PIG_TOP, NULL))
837
			break;
838
		/* Turn around if there's no floor! */
839
		look_x = 10 + fabs(po->vx * 4);
840
		if(po->target < 0)
841
			look_x = -look_x;
842
		if(!pig_test_map_vector(po->owner,
843
				po->x + look_x, po->y,
844
				po->x + look_x, SCREEN_H,
845
				PIG_TOP, NULL))
846
			po->target = -po->target;
847
	  default:
848
		break;
849
	}
850
}
851
 
852
 
853
static void chain_head_handler(PIG_object *po, const PIG_event *ev)
854
{
855
	GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
856
	switch(ev->type)
857
	{
858
	  case PIG_PREFRAME:
859
		po->vx = (po->target - po->x) * .3;
860
		po->vy = 15 * cos(po->age * .3) - 9;
861
		if((gs->messages > 1) && (1 == po->state))
862
			po->timer[1] = 0;
863
		if(po->timer[1])
864
			break;
865
	  case PIG_TIMER1:
866
		switch(po->state)
867
		{
868
		  case 0:
869
			po->timer[1] = 35;
870
			++po->state;
871
			break;
872
		  case 1:
873
			po->target = -SCREEN_W;
874
			po->timer[1] = 50;
875
			++po->state;
876
			if(gs->messages > 0)
877
				--gs->messages;
878
			break;
879
		  case 2:
880
			pig_object_close(po);
881
			break;
882
		}
883
	  default:
884
		break;
885
	}
886
}
887
 
888
static void chain_link_handler(PIG_object *po, const PIG_event *ev)
889
{
890
	PIG_object *target = pig_object_find(po, po->target);
891
	switch(ev->type)
892
	{
893
	  case PIG_PREFRAME:
894
		if(target)
895
		{
896
			po->vx = ((target->x + FONT_SPACING) - po->x) * .6;
897
			po->vy = (target->y - po->y) * .6 - 9;
898
		}
899
		else
900
			pig_object_close(po);
901
	  default:
902
		break;
903
	}
904
}
905
 
906
 
907
/*----------------------------------------------------------
908
	Accounting (score, lives etc)
909
----------------------------------------------------------*/
910
 
911
static void add_life(GAMESTATE *gs)
912
{
913
	++gs->lives;
914
	gs->lives_wobble += 10;
915
	if(gs->lives_wobble > 15)
916
		gs->lives_wobble = 15;
917
	gs->lives_wobble_time = 0;
918
}
919
 
920
 
921
static void remove_life(GAMESTATE *gs)
922
{
923
	--gs->lives;
924
	gs->lives_wobble += 10;
925
	if(gs->lives_wobble > 15)
926
		gs->lives_wobble = 15;
927
	gs->lives_wobble_time = 0;
928
}
929
 
930
 
931
static void inc_score_nobonus(GAMESTATE *gs, int v)
932
{
933
	int os = gs->score;
934
	gs->score += v;
935
	while(v)
936
	{
937
		gs->score_wobble += 1;
938
		v /= 10;
939
	}
940
	if(gs->score_wobble > 15)
941
		gs->score_wobble = 15;
942
	gs->score_wobble_time = 0;
943
	if(os / 10000 != gs->score / 10000)
944
		new_powerup(gs, SCREEN_W / 2, -20, -4, POWER_LIFE);
945
}
946
 
947
static void inc_score(GAMESTATE *gs, int v)
948
{
949
	int os = gs->score;
950
	inc_score_nobonus(gs, v);
951
	if(os / 5000 != gs->score / 5000)
952
		new_powerup(gs, SCREEN_W / 2, -20, 8, POWER_BONUS1);
953
	else if(os / 1000 != gs->score / 1000)
954
		new_powerup(gs, SCREEN_W / 2, -20, -6, POWER_BONUS2);
955
}
956
 
957
 
958
static PIG_object *new_player(GAMESTATE *gs)
959
{
960
	PIG_object *po;
961
	if(!gs->lives)
962
		return NULL;
963
 
964
	po = pig_object_open(gs->pe, SCREEN_W / 2, -50, 1);
965
	if(!po)
966
		return NULL;
967
 
968
	remove_life(gs);
969
	po->ibase = gs->pigframes;
970
	po->handler = player_handler;
971
	po->hitmask = GROUP_POWERUP | GROUP_ENEMY;
972
	return po;
973
}
974
 
975
 
976
static PIG_object *new_powerup(GAMESTATE *gs,
977
		int x, int y, int speed, POWERUPS type)
978
{
979
	PIG_object *po = pig_object_open(gs->pe, x, y, 1);
980
	if(!po)
981
		return NULL;
982
 
983
	++gs->enemycount;
984
	po->score = type;
985
	po->ibase = gs->icons + 8 * po->score;
986
	po->target = speed;
987
	po->handler = powerup_handler;
988
	po->tilemask = PIG_TOP;
989
	po->hitgroup = GROUP_POWERUP;
990
	return po;
991
}
992
 
993
 
994
static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy)
995
{
996
	PIG_object *po = pig_object_open(gs->pe, x + vx, y + vy, 1);
997
	if(!po)
998
		return NULL;
999
 
1000
	po->ibase = gs->stars;
1001
	po->ax = -vx * 0.3;
1002
	po->vx = vx * 3;
1003
	po->ay = -vy * 0.3;
1004
	po->vy = vy * 3;
1005
	po->handler = star_handler;
1006
	return po;
1007
}
1008
 
1009
 
1010
static PIG_object *new_evil(GAMESTATE *gs,
1011
		int x, int y, int speed)
1012
{
1013
	PIG_object *po = pig_object_open(gs->pe,
1014
			x * TILE_W, y * TILE_H, 1);
1015
	if(!po)
1016
		return NULL;
1017
 
1018
	++gs->enemycount;
1019
	po->ibase = gs->evil;
1020
	po->target = speed;
1021
	po->handler = evil_handler;
1022
	po->score = 200;
1023
	po->tilemask = PIG_TOP;
1024
	po->hitgroup = GROUP_ENEMY;
1025
	return po;
1026
}
1027
 
1028
 
1029
static PIG_object *new_slime(GAMESTATE *gs,
1030
		int x, int y, int speed)
1031
{
1032
	PIG_object *po = pig_object_open(gs->pe,
1033
			x * TILE_W, y * TILE_H, 1);
1034
	if(!po)
1035
		return NULL;
1036
 
1037
	++gs->enemycount;
1038
	po->ibase = gs->slime;
1039
	po->target = speed;
1040
	po->handler = slime_handler;
1041
	po->score = 300;
1042
	po->tilemask = PIG_TOP;
1043
	po->hitgroup = GROUP_ENEMY;
1044
	return po;
1045
}
1046
 
1047
 
1048
static PIG_object *new_chain_head(GAMESTATE *gs,
1049
		int x, int y, int image, int target_x)
1050
{
1051
	PIG_object *po = pig_object_open(gs->pe, x, y, 1);
1052
	if(!po)
1053
		return NULL;
1054
 
1055
	po->ibase = image;
1056
	po->handler = chain_head_handler;
1057
	po->target = target_x;
1058
	return po;
1059
}
1060
 
1061
 
1062
static PIG_object *new_chain_link(GAMESTATE *gs,
1063
		int x, int y, int image, int target)
1064
{
1065
	PIG_object *po = pig_object_open(gs->pe, x, y, 1);
1066
	if(!po)
1067
		return NULL;
1068
 
1069
	po->ibase = image;
1070
	po->handler = chain_link_handler;
1071
	po->target = target;
1072
	return po;
1073
}
1074
 
1075
 
1076
static void message(GAMESTATE *gs, const char *s)
1077
{
1078
	int i = 0;
1079
	const int x = SCREEN_W + FONT_SPACING;
1080
	const int y = MAP_H * TILE_H - 30;
1081
	int tx = (SCREEN_W - ((signed)strlen(s) - 1) * FONT_SPACING) / 2;
1082
	PIG_object *po = NULL;
1083
	while(s[i])
1084
	{
1085
		int c = toupper(s[i]) - 32 + gs->glassfont;
1086
		if(0 == i)
1087
			po = new_chain_head(gs, x, y, c, tx);
1088
		else
1089
			po = new_chain_link(gs, x, y, c, po->id);
1090
		if(!po)
1091
			return;
1092
		++i;
1093
	}
1094
	++gs->messages;
1095
}
1096
 
1097
 
1098
static int start_game(GAMESTATE *gs)
1099
{
1100
	if(0 != gs->level)
1101
		return 0;	/* Already playing! --> */
1102
 
1103
	gs->score = 0;
1104
	gs->lives = 5;
1105
 
1106
	if(load_level(gs, 1) < 0)
1107
		return -1;
1108
 
1109
	gs->player = new_player(gs);
1110
	if(!gs->player)
1111
		return -1;
1112
 
1113
	return 0;
1114
}
1115
 
1116
 
1117
/*----------------------------------------------------------
1118
	Input; events and game control keys
1119
----------------------------------------------------------*/
1120
static void handle_input(GAMESTATE *gs, SDL_Event *ev)
1121
{
1122
	switch(ev->type)
1123
	{
1124
	  case SDL_MOUSEBUTTONUP:
1125
		break;
1126
	  case SDL_KEYDOWN:
1127
		switch(ev->key.keysym.sym)
1128
		{
1129
		  case SDLK_UP:
1130
			gs->jump = 3;
1131
			break;
1132
		  case SDLK_F1:
1133
			gs->pe->interpolation = !gs->pe->interpolation;
1134
			if(gs->pe->interpolation)
1135
				message(gs, "Interpolation: ON");
1136
			else
1137
				message(gs, "Interpolation: OFF");
1138
			break;
1139
		  case SDLK_F2:
1140
			gs->pe->direct = !gs->pe->direct;
1141
			if(gs->pe->direct)
1142
				message(gs, "Rendering: Direct");
1143
			else
1144
				message(gs, "Rendering: Buffered");
1145
			break;
1146
		  case SDLK_F3:
1147
			gs->pe->show_dirtyrects = !gs->pe->show_dirtyrects;
1148
			if(gs->pe->show_dirtyrects)
1149
				message(gs, "Dirtyrects: ON");
1150
			else
1151
				message(gs, "Dirtyrects: OFF");
1152
			break;
1153
		  case SDLK_F4:
1154
			gs->nice = !gs->nice;
1155
			if(gs->nice)
1156
				message(gs, "Be Nice: ON");
1157
			else
1158
				message(gs, "Be Nice: OFF");
1159
			break;
1160
		  case SDLK_SPACE:
1161
			start_game(gs);
1162
		  default:
1163
			break;
1164
		}
1165
		break;
1166
	  case SDL_KEYUP:
1167
		switch(ev->key.keysym.sym)
1168
		{
1169
		  case SDLK_ESCAPE:
1170
			gs->running = 0;
1171
		  default:
1172
			break;
1173
		}
1174
		break;
1175
	  case SDL_QUIT:
1176
		gs->running = 0;
1177
		break;
1178
	}
1179
}
1180
 
1181
 
1182
static void handle_keys(GAMESTATE *gs)
1183
{
1184
}
1185
 
1186
 
1187
static int break_received = 0;
1188
 
1189
#ifndef	RETSIGTYPE
1190
#define	RETSIGTYPE void
1191
#endif
1192
static RETSIGTYPE breakhandler(int sig)
1193
{
1194
	/* For platforms that drop the handlers on the first signal... */
1195
	signal(SIGTERM, breakhandler);
1196
	signal(SIGINT, breakhandler);
1197
	break_received = 1;
1198
#if (RETSIGTYPE != void)
1199
	return 0;
1200
#endif
1201
}
1202
 
1203
 
1204
/*----------------------------------------------------------
1205
	main()
1206
----------------------------------------------------------*/
1207
int main(int argc, char* argv[])
1208
{
1209
	SDL_Surface *screen;
1210
	GAMESTATE *gs;
1211
	int i;
1212
	int bpp = 0;
1213
	int last_tick, start_time, end_time;
1214
	int dashframe;
1215
	float logic_fps = 20.0;
1216
	int flags = SDL_DOUBLEBUF | SDL_HWSURFACE;
1217
 
1218
	SDL_Init(SDL_INIT_VIDEO);
1219
	atexit(SDL_Quit);
1220
	signal(SIGTERM, breakhandler);
1221
	signal(SIGINT, breakhandler);
1222
 
1223
	for(i = 1; i < argc; ++i)
1224
	{
1225
		if(strncmp(argv[i], "-s", 2) == 0)
1226
			flags &= ~SDL_DOUBLEBUF;
1227
		else if(strncmp(argv[i], "-f", 2) == 0)
1228
			flags |= SDL_FULLSCREEN;
1229
		else
1230
			bpp = atoi(&argv[i][1]);
1231
	}
1232
 
1233
	screen = SDL_SetVideoMode(SCREEN_W, SCREEN_H, bpp, flags);
1234
	if(!screen)
1235
	{
1236
		fprintf(stderr, "Failed to open screen!\n");
1237
		return 1;
1238
	}
1239
 
1240
	SDL_WM_SetCaption("Fixed Rate Pig", "Pig");
1241
	SDL_ShowCursor(0);
1242
 
1243
	gs = init_all(screen);
1244
	if(!gs)
1245
		return 1;
1246
 
1247
	gs->keys = SDL_GetKeyState(&i);
1248
 
1249
	gs->logic_frames = 0;
1250
	gs->rendered_frames = 0;
1251
	gs->pe->before_objects = before_objects;
1252
 
1253
	pig_start(gs->pe, 0);
1254
	gs->refresh_screen = gs->pe->pages;
1255
	start_time = last_tick = SDL_GetTicks();
1256
	while(gs->running)
1257
	{
1258
		int tick;
1259
		float frames, dt;
1260
		SDL_Event ev;
1261
 
1262
		/* Handle input */
1263
		while(SDL_PollEvent(&ev) > 0)
1264
			handle_input(gs, &ev);
1265
		handle_keys(gs);
1266
		if(break_received)
1267
			gs->running = 0;
1268
 
1269
		/* Calculate time since last update */
1270
		tick = SDL_GetTicks();
1271
		dt = (tick - last_tick) * 0.001;
1272
		frames = dt * logic_fps;
1273
 
1274
		/* Run the game logic */
1275
		pig_animate(gs->pe, frames);
1276
 
1277
		/*
1278
		 * Limit the dashboard frame rate to 15 fps
1279
		 * when there's no wobbling going on.
1280
		 *
1281
		 * The 'dashframe' deal is about keeping the
1282
		 * pages in sync on a double buffered display.
1283
		 */
1284
		if(gs->lives_wobble || gs->score_wobble ||
1285
				(gs->dashboard_time > 1.0/15.0))
1286
		{
1287
			dashframe = gs->pe->pages;
1288
			gs->dashboard_time = 0;
1289
		}
1290
		if(dashframe)
1291
		{
1292
			--dashframe;
1293
			dashboard(gs);
1294
		}
1295
 
1296
		/* Update sprites */
1297
		if(gs->refresh_screen)
1298
		{
1299
			--gs->refresh_screen;
1300
			pig_refresh_all(gs->pe);
1301
		}
1302
		else
1303
			pig_refresh(gs->pe);
1304
 
1305
		/* Make the new frame visible */
1306
		pig_flip(gs->pe);
1307
 
1308
		/* Update statistics, timers and stuff */
1309
		++gs->rendered_frames;
1310
		gs->lives_wobble_time += dt;
1311
		gs->score_wobble_time += dt;
1312
		gs->dashboard_time += dt;
1313
 
1314
		last_tick = tick;
1315
		if(gs->nice)
1316
			SDL_Delay(10);
1317
	}
1318
 
1319
	/* Print some statistics */
1320
	end_time = SDL_GetTicks();
1321
	i = end_time - start_time;
1322
	printf("          Total time running: %d ms\n", i);
1323
	if(!i)
1324
		i = 1;
1325
	printf("Average rendering frame rate: %.2f fps\n",
1326
			gs->rendered_frames * 1000.0 / i);
1327
	printf("    Average logic frame rate: %.2f fps\n",
1328
			gs->logic_frames * 1000.0 / i);
1329
 
1330
	pig_close(gs->pe);
1331
	return 0;
1332
}