Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2. ------------------------------------------------------------
  3.         Fixed Rate Pig - a fixed logic frame rate demo
  4. ------------------------------------------------------------
  5.  * Copyright (C) 2004 David Olofson <david@olofson.net>
  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 <stdio.h>
  14. #include <signal.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #include <ctype.h>
  18. #include <math.h>
  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. }
  1333.