Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1419914
  • 博文数量: 430
  • 博客积分: 9995
  • 博客等级: 中将
  • 技术积分: 4388
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-24 18:04
文章存档

2013年(1)

2008年(2)

2007年(14)

2006年(413)

分类: LINUX

2006-05-26 10:47:05

             pig.c
 
/*
------------------------------------------------------------
 Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
 * Copyright (C) 2004 David Olofson <>
 *
 * This software is released under the terms of the GPL.
 *
 * Contact author for permission if you want to use this
 * software, or work derived from it, under other terms.
 */
#include
#include
#include
#include
#include
#include
#include "engine.h"

/* Graphics defines */
#define SCREEN_W 800
#define SCREEN_H 600
#define TILE_W  32
#define TILE_H  32
#define MAP_W  25
#define MAP_H  17
#define FONT_SPACING 45
#define PIG_FRAMES 12
/* World/physics constants */
#define GRAV_ACC 4
#define JUMP_SPEED 28
/* Sprite collision groups */
#define GROUP_ENEMY 0x0001
#define GROUP_POWERUP 0x0002
typedef enum
{
 POWER_LIFE,
 POWER_BONUS1,
 POWER_BONUS2
} POWERUPS;

typedef struct GAMESTATE
{
 /* I/O */
 PIG_engine *pe;
 Uint8  *keys;
 int  nice;
 int  refresh_screen;
 int  jump;
 /* Sprites */
 int  lifepig;
 int  scorefont;
 int  glassfont;
 int  icons;
 int  stars;
 int  pigframes;
 int  evil;
 int  slime;
 /* Global game state */
 int  running;
 int  level;
 int  lives;
 float  lives_wobble;
 float  lives_wobble_time;
 int  score;
 float  score_wobble;
 float  score_wobble_time;
 float  dashboard_time;
 int  fun_count;
 int  enemycount;
 int  messages;
 /* Objects */
 PIG_object *player;
 /* Statistics */
 int  logic_frames;
 int  rendered_frames;
} GAMESTATE;

static void add_life(GAMESTATE *gs);
static void remove_life(GAMESTATE *gs);
static void inc_score(GAMESTATE *gs, int v);
static void inc_score_nobonus(GAMESTATE *gs, int v);
static PIG_object *new_player(GAMESTATE *gs);
static void message(GAMESTATE *gs, const char *s);
static PIG_object *new_powerup(GAMESTATE *gs,
  int x, int y, int speed, POWERUPS type);
static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy);
static PIG_object *new_evil(GAMESTATE *gs,
  int x, int y, int speed);
static PIG_object *new_slime(GAMESTATE *gs,
  int x, int y, int speed);

/*----------------------------------------------------------
 Init, load stuff etc
----------------------------------------------------------*/
static int load_level(GAMESTATE *gs, int map)
{
 const char *m;
 const char *k;
 if(map > 10)
  map = 3;
 gs->level = map;
 pig_object_close_all(gs->pe);
 gs->enemycount = 0;
 gs->messages = 0;
 switch(map)
 {
   case 1:
   case 2:
          case 3:
   case 5:
          case 6:
          case 7:
          case 9:
          case 10:
    k = "abcd" "efgh" "ijkl" /* Red, green, yellov */
   "0123456789ABCDEFG" /* Sky */
   "xyz";   /* Single R, G, Y */
  break;
   case 0:
          case 4:
   case 8:
    k = "abcd" "efgh" "ijkl" /* Red, green, yellov */
   "................."
   "xyz"   /* Single R, G, Y */
   "-+012345..ABCDEF"; /* Night sky */
  break;
 }
 switch(map)
 {
   case 0: m = "-------------ad----------"
   "-abcd-x-ad--ad-abcd-acd--"
   "-x----x--abcd--x----x--x-"
   "-abd--x---ad---abd--x--x-"
   "-x----x--abcd--x----x--x-"
   "-x----x-ad--ad-abcd-abd--"
   "----efhad-eh--egh-efh----"
   "----y--y-y--y--y--y------"
   "++++efh++efgh++y++eh+++++"
   "0123y50y2y45y12y45y123450"
   "ABCDyFAyCyEFyBCyEFeghDEFA"
   "----ijkjl-ijkl--ijkjl----"
   "----il--il-il--il--------"
   "----ijkjl--il--il-ikl----"
   "----il-----il--il--il----"
   "----il----ijkl--ijkjl----"
   "-------------------------";
   break;
          case 1: m =   "0000000000000000000000000"
                        "1111111111111111111111111"
                        "2222222222222222222222222"
                        "3333333333333333333333333"
                        "4444444444444444444444444"
                        "5555555555555555555555555"
                        "6666666666666666666666666"
                        "7777777ijkjkjjkjkl7777777"
                        "8888888888888888888888888"
                        "9999999999999999999999999"
                        "abcdAAAAAAAAAAAAAAAAAabcd"
                        "BBBBBBBBBBBBBBBBBBBBBBBBB"
                        "CCCCCCCCCCCCCCCCCCCCCCCCC"
                        "efgfgffgfgfgfgfggffgfgfgh"
                        "EEEEEEEEEEEEEEEEEEEEEEEEE"
                        "FFFFFFFFFFFFFFFFFFFFFFFFF"
                        "GGGGGGGGGGGGGGGGGGGGGGGGG";
                        new_evil(gs, 2, 0, 5);
                        new_evil(gs,22, 0, 5);
                        new_evil(gs, 5, 0, 7);
                        new_evil(gs,19, 0,-7);
                        break;
          case 2: m =   "0000000000000000000000000"
                        "1111111111111111111111111"
                        "2222222222222222222222222"
                        "3333333333333333333333333"
                        "4444444444444444444444444"
                        "5555555555555555555555555"
                        "6666666666666666666666666"
                        "7777jjjjjjj777kkkkkkk7777"
                        "8888888888888888888888888"
                        "9999999999999999999999999"
                        "AAAbAAAAAAAAAAAAAAAAAbAAA"
                        "BBBBBBBBBBBBBBBBBBBBBBBBB"
                        "CCCCCCCCCCcccccCCCCCCCCCC"
                        "DDDDDDDDDDDDDDDDDDDDDDDDD"
                        "EEEEEEEEEEEEEEEEEEEEEEEEE"
                        "ggggggggggggggggggggggggg"
                        "GGGGGGGGGGGGGGGGGGGGGGGGG";
                        new_evil(gs, 8, 0, 5);
                        new_evil(gs,17, 0,-5);
                        new_evil(gs, 3, 0, 7);
                        new_evil(gs,22, 0,-7);
                        break;
          case 3: m =   "0000000000000000000000000"
                        "1111111111111111111111111"
                        "2222222222222222222222222"
                        "3333333333333333333333333"
                        "4444444444444444444444444"
                        "5555555555555555555555555"
                        "6666666666666666666666666"
                        "7777777777777777777777777"
                        "8888888888888888888888888"
                        "9999kkkkk9999999kkkkk9999"
                        "AAAAAAAAAAAAAAAAAAAAAAAAA"
                        "BBBBBBBBBBBBBBBBBBBBBBBBB"
                        "CffCCCCCCCCCCCCCCCCCCCffC"
                        "DDDDDDDDDDDDDDDDDDDDDDDDD"
                        "EEEEEEEEEEEEEEEEEEEEEEEEE"
                        "cccccFFFbbbbbbbbbFFFccccc"
                        "GGGcGGGGGbGGGGGbGGGGGcGGG";
                        new_evil(gs,  7, 0, 5);
                        new_evil(gs, 18, 0,-5);
                        new_evil(gs,  1, 0, 7);
                        new_evil(gs, 24, 0,-7);
                        break;
          case 4: m =   "-------------------------"
                        "-------------------------"
                        "-------------------------"
                        "-------------------------"
                        "-------------------------"
                        "-------------------------"
                        "---------------bbbbbb----"
                        "-------------------------"
                        "bbbbbb+++++++++++++++++++"
                        "01234501234501234501234bb"
                        "ABCDEFABCDEFABCDEFABCDEFA"
                        "--------jjjjj--------j---"
                        "-------------------------"
                        "-------------------f-----"
                        "-------------------------"
                        "-------------------------"
                        "fffffffffffffffffffffffff";
                        new_slime(gs,13, 0, 5);
                        new_evil(gs, 3, 0, 5);
                        new_evil(gs, 7, 0, 7);
                        new_evil(gs,20, 0, 5);
                        break;
          case 5: m =   "0000000000000000000000000"
                        "1111111111111111111111111"
                        "2222222222222222222222222"
                        "3333333333333333333333333"
                        "4444444444444444444444444"
                        "5555555555555555555555555"
                        "6666666666666666666666666"
                        "77777777777jjj77777777777"
                        "8888888888888888888888888"
                        "9999999999999999999999999"
                        "AbbbbbbbbAAAAAAAbbbbbbbbA"
                        "BBBBBBBBBBBBBBBBBBBBBBBBB"
                        "CCCCCCCCCCCCCCCCCCCCCCCCC"
                        "DDbDDDDDDDDDDDDDDDDDDDbDD"
                        "EEEEEEEEEEEEEEEEEEEEEEEEE"
                        "FFFFFFFFFFFFFFFFFFFFFFFFF"
                        "fffffffffffffffffffffffff";
                        new_evil(gs, 8, 0, 5);
                        new_evil(gs,17, 0, 5);
                        new_slime(gs, 1, 0, 5);
                        new_slime(gs,24, 0,-5);
                        break;
          case  6: m =  "0000000000000000000000000"
                        "1111111111111111111111111"
                        "2222222222222222222222222"
                        "3333333333333333333333333"
                        "4444444444444444444444444"
                        "5555555555555555555555555"
                        "6666666666666666666666666"
                        "7777777777777777777777777"
                        "8888888888888888888888888"
                        "99jjjjjjj9999999bbbbbbb99"
                        "AAAAAAAAAAAAAAAAAAAAAAAAA"
                        "BBBBBBBBBBBBBBBBBBBBBBBBB"
                        "CCCCCCCbbbbbCjjjjjCCCCCCC"
                        "DDDDDDDDDDDDDDDDDDDDDDDDD"
                        "EEEEEEEEEEEEEEEEEEEEEEEEE"
                        "jjjjjjjjjFFfffFFbbbbbbbbb"
                        "GzGGGGGzGGGGyGGGGxGGGGGxG";
                        new_evil(gs, 3, 0, 7);
                        new_evil(gs, 22, 0, 7);
                        new_slime(gs,  6, 0,-7);
                        new_slime(gs, 19, 0, 7);
                        break;
          case 7: m =   "0000000000000000000000000"
                        "1111111111111111111111111"
                        "2222222222222222222222222"
                        "3333333333333333333333333"
                        "4444444444xxxxx4444444444"
                        "5555555555x555x5555555555"
                        "6666666666x666x6666666666"
                        "7777777xxxx777xxxx7777777"
                        "8888888x888888888x8888888"
                        "9999999x999999999x9999999"
                        "AAAAAAAxxxxAAAxxxxAAAAAAA"
                        "BBBBBBBBBBxBBBxBBBBBBBBBB"
                        "CCCCCCCCCCxCCCxCCCCCCCCCC"
                        "DDDDDDDDDDxxxxxDDDDDDDDDD"
                        "EEEEEEEEEEEEEEEEEEEEEEEEE"
                        "ijklFFFFFFFFFFFFFFFFFijkl"
                        "GGGijlGilGilGilGilGiklGGG";
                        new_slime(gs, 2, 0, -5);
                        new_slime(gs, 22, 0, 5);
                        new_evil(gs, 8, 0, 7);
                        new_evil(gs, 16, 0, -7);
                        break;
          case  8: m =  "-------------------------"
                        "-------------------------"
                        "-------------------------"
                        "-------------------------"
                        "ijkl----------efgh-------"
                        "-------------------------"
                        "-------------------------"
                        "z----------------abcbcbbd"
                        "+++++++++++++++++++++++++"
                        "01z3450123450123450123450"
                        "ABCDEFABCefgfgfghFABCDEFA"
                        "----z--------------------"
                        "-------------------------"
                        "------z--------------ijkl"
                        "-------------------------"
                        "-------------------------"
                        "abdefghijkl---efghijklabd";
                        new_slime(gs, 5, 0, -5);
                        new_slime(gs, 20, 15, -5);
                        new_evil(gs, 1, 0, 7);
                        new_evil(gs, 20, 0, 10);
                        new_evil(gs, 15, 0, 7);
                        break;
   case 9: m = "0000000000000000000000000"
   "1111111111111111111111111"
   "2222222222222222222222222"
   "3333333333333333333333333"
   "4444444444444444444444444"
   "555555555555z555555555555"
   "66666666666ijl66666666666"
   "7777777777ijlil7777777777"
   "888888888ijlikkl888888888"
   "99999999ijkjklikl99999999"
   "AAAAAAAikjlijkjkjlAAAAAAA"
   "BBBBBBiklijkjlijkjlBBBBBB"
   "CCCCCijkjlikkjklikklCCCCC"
   "DDDDijklijjklikjkjkklDDDD"
   "EEEijkkjkjlikjkjlijjklEEE"
   "FFijkjlilijkjklikjlikklFF"
   "efggfggfgfgfggfgfgfgfgfgh";
   new_evil(gs, 11, 0, 5);
   new_evil(gs, 10, 0, 6);
   new_evil(gs, 9, 0, 7);
   new_evil(gs, 8, 0, 8);
   new_evil(gs, 7, 0, 9);
   new_evil(gs, 6, 0, 10);
   new_evil(gs, 5, 0, 11);
   new_evil(gs, 4, 0, 12);
   new_evil(gs, 3, 0, 13);
   new_slime(gs, 1, 0, 16);
   new_slime(gs, 24, 0, -14);
   break;
          case 10: m =  "0000000000000000000000000"
                        "1111111111111111111111111"
                        "2222222222222222222222222"
                        "3333333333333333333333333"
                        "44444444444444444444bbbbb"
                        "5555555555555555555555555"
                        "6666666666666666666666666"
                        "777777777777777jjjjj77777"
                        "8888888888888888888888888"
                        "9999999999999999999999999"
                        "AAAAAAAAAAyyyyyAAAAAAAAAA"
                        "BBBBBBBBBBBBBBBBBBBBBBBBB"
                        "CCCCCCCCCCCCCCCCCCCCCCCCC"
                        "DDDDDbbbbbDDDDDDDDDDDDDDD"
                        "EEEEEEEEEEEEEEEEEEEEEEEEE"
                        "FFFFFFFFFFFFFFFFFFFFFFFFF"
                        "jjjjjGGGGGGGGGGGGGGGGGGGG";
                        new_evil(gs, 1, 0, 4);
                        new_evil(gs, 3, 0,-4);
                        new_evil(gs, 6, 0, 4);
                        new_evil(gs, 8, 0,-4);
                        new_evil(gs,16, 0, 4);
                        new_evil(gs,18, 0,-4);
                        new_evil(gs,21, 0, 4);
                        new_evil(gs,23, 0,-4);
                        break;
   default:
  return -1;
 }
 pig_map_from_string(gs->pe->map, k, m);
 gs->refresh_screen = gs->pe->pages;
 return 0;
}

static GAMESTATE *init_all(SDL_Surface *screen)
{
 int i;
 PIG_map *pm;
 GAMESTATE *gs = (GAMESTATE *)calloc(1, sizeof(GAMESTATE));
 if(!gs)
  return NULL;
 gs->running = 1;
 gs->pe = pig_open(screen);
 if(!gs->pe)
 {
  fprintf(stderr, "Could not open the Pig Engine!\n");
  free(gs);
  return NULL;
 }
 gs->pe->userdata = gs;
 pig_viewport(gs->pe, 0, 0, SCREEN_W, MAP_H * TILE_H);
 i = gs->lifepig = pig_sprites(gs->pe, "lifepig.png", 0, 0);
 i |= gs->scorefont = pig_sprites(gs->pe, "font.png", 44, 56);
 i |= gs->glassfont = pig_sprites(gs->pe, "glassfont.png", 60, 60);
 i |= gs->icons = pig_sprites(gs->pe, "icons.png", 48, 48);
 i |= gs->stars = pig_sprites(gs->pe, "stars.png", 32, 32);
 i |= gs->pigframes = pig_sprites(gs->pe, "pigframes.png", 64, 48);
 i |= gs->evil = pig_sprites(gs->pe, "evil.png", 48, 48);
 i |= gs->slime = pig_sprites(gs->pe, "slime.png", 48, 48);
 if(i < 0)
 {
  fprintf(stderr, "Could not load graphics!\n");
  pig_close(gs->pe);
  free(gs);
  return NULL;
 }
 for(i = gs->icons; i < gs->icons + 3*8; ++i)
  pig_hotspot(gs->pe, i, PIG_CENTER, 45);
 for(i = gs->pigframes; i < gs->pigframes + 12; ++i)
  pig_hotspot(gs->pe, i, PIG_CENTER, 43);
 for(i = gs->evil; i < gs->evil + 16; ++i)
  pig_hotspot(gs->pe, i, PIG_CENTER, 46);
 for(i = gs->slime; i < gs->slime + 16; ++i)
  pig_hotspot(gs->pe, i, PIG_CENTER, 46);
 pm = pig_map_open(gs->pe, MAP_W, MAP_H);
 if(!pm)
 {
  fprintf(stderr, "Could not create map!\n");
  pig_close(gs->pe);
  free(gs);
  return NULL;
 }
 if(pig_map_tiles(pm, "tiles.png", TILE_W, TILE_H) < 0)
 {
  fprintf(stderr, "Could not load background graphics!\n");
  pig_close(gs->pe);
  free(gs);
  return NULL;
 }
 /* Mark tiles for collision detection */
 pig_map_collisions(pm, 0, 12, PIG_ALL); /* Red, green, yellov */
 pig_map_collisions(pm, 12, 17, PIG_NONE);/* Sky */
 pig_map_collisions(pm, 29, 3, PIG_ALL); /* Single R, G, Y */
 load_level(gs, 0);
 return gs;
}

/*----------------------------------------------------------
 Render the dashboard
----------------------------------------------------------*/
static void dashboard(GAMESTATE *gs)
{
 SDL_Rect r;
 int i, v;
 float x;
 float t = SDL_GetTicks() * 0.001;
 r.x = 0;
 r.y = SCREEN_H - 56;
 r.w = SCREEN_W;
 r.h = 56;
 SDL_SetClipRect(gs->pe->surface, &r);
 /* Render "plasma bar" */
 for(i = 0; i < 56; ++i)
 {
  float f1, f2, m;
  SDL_Rect cr;
  cr.x = 0;
  cr.w = SCREEN_W;
  cr.y = SCREEN_H - 56 + i;
  cr.h = 1;
  f1 = .25 + .25 * sin(t * 1.7 + (float)i / SCREEN_H * 42);
  f1 += .25 + .25 * sin(-t * 2.1 + (float)i / SCREEN_H * 66);
  f2 = .25 + .25 * sin(t * 3.31 + (float)i / SCREEN_H * 90);
  f2 += .25 + .25 * sin(-t * 1.1 + (float)i / SCREEN_H * 154);
  m = sin((float)i * M_PI / 56.0);
  m = sin(m * M_PI * 0.5);
  m = sin(m * M_PI * 0.5);
  SDL_FillRect(gs->pe->surface,
    &cr, SDL_MapRGB(gs->pe->surface->format,
    ((int)128.0 * f1 + 64) * m,
    ((int)64.0 * f1 * f2 + 64) * m,
    ((int)128.0 * f2 + 32) * m
    ));
 }
 /* Draw pigs... uh, lives! */
 x = -10;
 for(i = 0; i < gs->lives; ++i)
 {
  x += 48 + gs->lives_wobble *
    sin(gs->lives_wobble_time * 12) * .2;
  pig_draw_sprite(gs->pe, gs->lifepig,
    (int)x + gs->lives_wobble *
    sin(gs->lives_wobble_time * 20 + i * 1.7),
    SCREEN_H - 56/2);
 }
 /* Print score */
 x = SCREEN_W + 5;
 v = gs->score;
 for(i = 9; i >= 0; --i)
 {
  int n = v % 10;
  x -= 39 - gs->score_wobble *
    sin(gs->score_wobble_time * 15 + i * .5);
  pig_draw_sprite(gs->pe, gs->scorefont + n, (int)x,
    SCREEN_H - 56/2);
  v /= 10;
  if(!v)
   break;
 }
 pig_dirty(gs->pe, &r);
}

/*----------------------------------------------------------
 Game logic event handlers
----------------------------------------------------------*/
static void before_objects(PIG_engine *pe)
{
 GAMESTATE *gs = (GAMESTATE *)pe->userdata;
 if(gs->lives_wobble > 0)
 {
  gs->lives_wobble *= 0.95;
  gs->lives_wobble -= 0.3;
  if(gs->lives_wobble < 0)
   gs->lives_wobble = 0;
 }
 if(gs->score_wobble > 0)
 {
  gs->score_wobble *= 0.95;
  gs->score_wobble -= 0.3;
  if(gs->score_wobble < 0)
   gs->score_wobble = 0;
 }
 ++gs->logic_frames;
 if(0 == gs->level)
 {
  switch(gs->fun_count % 60)
  {
    case 17:
   new_powerup(gs, 250, -20, -10, POWER_LIFE);
   break;
    case 29:
   new_powerup(gs, 550, -20, 10, POWER_LIFE);
   break;
    case 37:
   new_powerup(gs, 250, -20, 10, POWER_BONUS2);
   break;
    case 51:
   new_powerup(gs, 550, -20, -10, POWER_BONUS1);
   break;
  }
  if(150 == gs->fun_count % 300)
   message(gs, "Press Space!");
  ++gs->fun_count;
 }
}

typedef enum
{
 WAITING,
 WALKING,
 FALLING,
 KNOCKED,
 NEXT_LEVEL,
 DEAD
} OBJECT_states;

static void player_handler(PIG_object *po, const PIG_event *ev)
{
 GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
 switch(ev->type)
 {
   case PIG_PREFRAME:
  switch(po->state)
  {
    case WAITING:
   if(1 == po->age)
    message(gs, "Get ready!");
   else if(po->age > 50)
    po->state = FALLING;
   break;
    case WALKING:
   if(gs->keys[SDLK_LEFT])
   {
    po->ax = -(20 + po->vx) * .4;
    po->target = 3 + po->age % 4 - 1;
    if(5 == po->target)
     po->target = 3;
   }
   else if(gs->keys[SDLK_RIGHT])
   {
    po->ax = (20 - po->vx) * .4;
    po->target = 9 + po->age % 4 - 1;
    if(11 == po->target)
     po->target = 9;
   }
   else
   {
    po->ax = -po->vx * .8;
    if(po->target >= 6)
     po->target = (po->target + 1) %
       PIG_FRAMES;
    else if(po->target)
     --po->target;
   }
   break;
    case FALLING:
   if(gs->keys[SDLK_LEFT])
    po->ax = -(20 + po->vx) * .2;
   else if(gs->keys[SDLK_RIGHT])
    po->ax = (20 - po->vx) * .2;
   else
    po->ax = -po->vx * .2;
   po->target = (po->target + 1) % PIG_FRAMES;
   break;
  }
  po->timer[0] = 1;
  break;
   case PIG_TIMER0:
  if(po->x < 0)
   po->x = 0;
  else if(po->x > po->owner->view.w - 1)
   po->x = po->owner->view.w - 1;
  switch(po->state)
  {
    case WALKING:
   if(po->power)
    --po->power;
   po->image = po->target % PIG_FRAMES;
   if(!pig_test_map(gs->pe, po->x, po->y + 1))
   {
    po->state = FALLING;
    po->ay = GRAV_ACC;
   }
   if(gs->jump || gs->keys[SDLK_UP])
   {
    po->ay = 0;
    po->vy = -JUMP_SPEED;
    po->state = FALLING;
    gs->jump = 0;
   }
   break;
    case FALLING:
   if(po->vy > 2)
    po->power = 3;
   po->ay = GRAV_ACC;
   po->image = po->target;
   break;
    case KNOCKED:
   po->power = 0;
   po->ay = GRAV_ACC;
   po->target = (po->target + 2) % PIG_FRAMES;
   po->image = po->target;
   po->ax = -po->vx * .2;
   break;
    case NEXT_LEVEL:
   po->vx = (SCREEN_W / 2 - po->x) * .1;
   po->target = (po->target + 1) % PIG_FRAMES;
   po->image = po->target;
   break;
    case DEAD:
   po->ax = po->ay = 0;
   po->vx = po->vy = 0;
   break;
  }
  if(gs->jump)
   --gs->jump;
  if(NEXT_LEVEL != po->state)
  {
   if(gs->enemycount <= 0)
   {
    message(gs, "Well Done!");
    po->state = NEXT_LEVEL;
    po->vy = 0;
    po->ay = -1;
    po->tilemask = 0;
    po->hitgroup = 0;
    po->timer[2] = 50;
   }
  }
  break;
   case PIG_TIMER1:
  /* Snap out of KNOCKED mode */
  po->state = FALLING;
  break;
   case PIG_TIMER2:
  switch(po->state)
  {
    case NEXT_LEVEL:
   add_life(gs);
   pig_object_close(po);
   load_level(gs, gs->level + 1);
   new_player(gs);
   break;
    default:
   pig_object_close(po);
   if(!new_player(gs))
    load_level(gs, 0);
   break;
  }
  break;
   case PIG_HIT_TILE:
  if(KNOCKED == po->state)
   break;
  if(ev->cinfo.sides & PIG_TOP)
  {
   po->y = ev->cinfo.y;
   po->vy = 0;
   po->ay = 0;
  }
  po->state = WALKING;
  break;
   case PIG_HIT_OBJECT:
  if(KNOCKED == po->state)
   break;
  switch(ev->obj->hitgroup)
  {
    case GROUP_ENEMY:
   if((po->power && ev->cinfo.sides & PIG_TOP) ||
     (po->vy - ev->obj->vy) >= 15)
   {
    /* Win: Stomp! */
    inc_score(gs, ev->obj->score);
    ev->obj->y = ev->cinfo.y + 10;
    if(po->vy > 0)
     ev->obj->vy = po->vy;
    else
     ev->obj->vy = 10;
    ev->obj->ay = GRAV_ACC;
    ev->obj->tilemask = 0;
    ev->obj->hitgroup = 0;
    if(gs->jump || gs->keys[SDLK_UP])
    {
     /* Mega jump! */
     po->vy = -(JUMP_SPEED + 7);
     gs->jump = 0;
    }
    else
    {
     /* Bounce a little */
     po->vy = -15;
    }
    po->y = ev->cinfo.y;
    po->ay = 0;
    po->state = FALLING;
   }
   else
   {
    /* Lose: Knocked! */
    po->vy = -15;
    po->ay = GRAV_ACC;
    po->state = KNOCKED;
    po->timer[1] = 11;
    new_star(gs, po->x, po->y - 20, -5, 3);
    new_star(gs, po->x, po->y - 20, 2, -6);
    new_star(gs, po->x, po->y - 20, 4, 4);
   }
   break;
    case GROUP_POWERUP:
   switch(ev->obj->score)
   {
     case POWER_LIFE:
    add_life(gs);
    message(gs, "Extra Life!");
    break;
     case POWER_BONUS1:
    /* Double or 100k bonus! */
    if(gs->score < 100000)
    {
     inc_score_nobonus(gs, gs->score);
     message(gs, "Double Score!");
    }
    else
    {
     inc_score_nobonus(gs, 100000);
     message(gs, "100 000!");
    }
    break;
     case POWER_BONUS2:
    inc_score_nobonus(gs, 1000);
    message(gs, "1000!");
    break;
   }
   ev->obj->state = DEAD;
   ev->obj->tilemask = 0;
   ev->obj->hitgroup = 0;
   ev->obj->vy = -20;
   ev->obj->ay = -2;
   break;
  }
  break;
   case PIG_OFFSCREEN:
  /*
   * Dead pigs don't care about being off-screen.
   * A timer is used to remove them, and to continue
   * the game with a new life.
   */
  if(DEAD == po->state)
   break;
  if(po->y < 0) /* Above the playfield is ok. */
   break;
  if(gs->lives)
   message(gs, "Oiiiiiiink!!!");
  else
   message(gs, "Game Over!");
  po->state = DEAD;
  po->timer[2] = 50;
   default:
  break;
 }
}

static void powerup_handler(PIG_object *po, const PIG_event *ev)
{
 GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
 switch(ev->type)
 {
   case PIG_PREFRAME:
  if(DEAD == po->state)
   break;
  po->ax = (po->target - po->vx) * .3;
  po->ay = GRAV_ACC;
  po->image = po->age % 8;
  ++po->power;
  break;
   case PIG_HIT_TILE:
  if(DEAD == po->state)
   break;
  if(po->power > 2)
   po->target = -po->target;
  po->power = 0;
  po->vy = 0;
  po->ay = 0;
  po->x = ev->cinfo.x + po->vx;
  po->y = ev->cinfo.y;
  break;
   case PIG_OFFSCREEN:
  if(po->y > SCREEN_H || (po->y < -100))
  {
   pig_object_close(po);
   --gs->enemycount;
  }
   default:
  break;
 }
}

static void star_handler(PIG_object *po, const PIG_event *ev)
{
 switch(ev->type)
 {
   case PIG_PREFRAME:
  if(po->age >= 8)
   pig_object_close(po);
  else
   po->image = po->age;
   default:
  break;
 }
}

static void evil_handler(PIG_object *po, const PIG_event *ev)
{
 GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
 int look_x;
 switch(ev->type)
 {
   case PIG_PREFRAME:
  if(DEAD == po->state)
   break;
  po->ax = (po->target - po->vx) * .5;
  po->ay = GRAV_ACC;
  po->image = po->age % 16;
  break;
   case PIG_HIT_TILE:
  if(DEAD == po->state)
   break;
  po->vy = 0;
  po->ay = 0;
  po->x = ev->cinfo.x + po->vx;
  po->y = ev->cinfo.y;
  break;
   case PIG_OFFSCREEN:
  if(po->y > SCREEN_H)
  {
   pig_object_close(po);
   --gs->enemycount;
  }
  break;
   case PIG_POSTFRAME:
  if(DEAD == po->state)
   break;
  look_x = 10 + fabs(po->vx * 2);
  if(po->target < 0)
   look_x = -look_x;
  if(!pig_test_map(po->owner, po->x + look_x, po->y + 1))
   po->target = -po->target;
   default:
  break;
 }
}

static void slime_handler(PIG_object *po, const PIG_event *ev)
{
 GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
 int look_x;
 switch(ev->type)
 {
   case PIG_PREFRAME:
  if(DEAD == po->state)
   break;
  po->ax = (po->target - po->vx) * .2;
  po->ay = GRAV_ACC;
  po->image = po->age % 16;
  break;
   case PIG_HIT_TILE:
  po->vy = -(JUMP_SPEED + GRAV_ACC);
  po->ay = 0;
  po->y = ev->cinfo.y;
  break;
   case PIG_OFFSCREEN:
  if(po->y > SCREEN_H)
  {
   pig_object_close(po);
   --gs->enemycount;
  }
  break;
   case PIG_POSTFRAME:
  if(DEAD == po->state)
   break;
  /* Don't bother looking if we're close to a floor. */
  if(pig_test_map_vector(po->owner,
    po->x, po->y,
    po->x, po->y + 48,
    PIG_TOP, NULL))
   break;
  /* Turn around if there's no floor! */
  look_x = 10 + fabs(po->vx * 4);
  if(po->target < 0)
   look_x = -look_x;
  if(!pig_test_map_vector(po->owner,
    po->x + look_x, po->y,
    po->x + look_x, SCREEN_H,
    PIG_TOP, NULL))
   po->target = -po->target;
   default:
  break;
 }
}

static void chain_head_handler(PIG_object *po, const PIG_event *ev)
{
 GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
 switch(ev->type)
 {
   case PIG_PREFRAME:
  po->vx = (po->target - po->x) * .3;
  po->vy = 15 * cos(po->age * .3) - 9;
  if((gs->messages > 1) && (1 == po->state))
   po->timer[1] = 0;
  if(po->timer[1])
   break;
   case PIG_TIMER1:
  switch(po->state)
  {
    case 0:
   po->timer[1] = 35;
   ++po->state;
   break;
    case 1:
   po->target = -SCREEN_W;
   po->timer[1] = 50;
   ++po->state;
   if(gs->messages > 0)
    --gs->messages;
   break;
    case 2:
   pig_object_close(po);
   break;
  }
   default:
  break;
 }
}
static void chain_link_handler(PIG_object *po, const PIG_event *ev)
{
 PIG_object *target = pig_object_find(po, po->target);
 switch(ev->type)
 {
   case PIG_PREFRAME:
  if(target)
  {
   po->vx = ((target->x + FONT_SPACING) - po->x) * .6;
   po->vy = (target->y - po->y) * .6 - 9;
  }
  else
   pig_object_close(po);
   default:
  break;
 }
}

/*----------------------------------------------------------
 Accounting (score, lives etc)
----------------------------------------------------------*/
static void add_life(GAMESTATE *gs)
{
 ++gs->lives;
 gs->lives_wobble += 10;
 if(gs->lives_wobble > 15)
  gs->lives_wobble = 15;
 gs->lives_wobble_time = 0;
}

static void remove_life(GAMESTATE *gs)
{
 --gs->lives;
 gs->lives_wobble += 10;
 if(gs->lives_wobble > 15)
  gs->lives_wobble = 15;
 gs->lives_wobble_time = 0;
}

static void inc_score_nobonus(GAMESTATE *gs, int v)
{
 int os = gs->score;
 gs->score += v;
 while(v)
 {
  gs->score_wobble += 1;
  v /= 10;
 }
 if(gs->score_wobble > 15)
  gs->score_wobble = 15;
 gs->score_wobble_time = 0;
 if(os / 10000 != gs->score / 10000)
  new_powerup(gs, SCREEN_W / 2, -20, -4, POWER_LIFE);
}
static void inc_score(GAMESTATE *gs, int v)
{
 int os = gs->score;
 inc_score_nobonus(gs, v);
 if(os / 5000 != gs->score / 5000)
  new_powerup(gs, SCREEN_W / 2, -20, 8, POWER_BONUS1);
 else if(os / 1000 != gs->score / 1000)
  new_powerup(gs, SCREEN_W / 2, -20, -6, POWER_BONUS2);
}

static PIG_object *new_player(GAMESTATE *gs)
{
 PIG_object *po;
 if(!gs->lives)
  return NULL;
 po = pig_object_open(gs->pe, SCREEN_W / 2, -50, 1);
 if(!po)
  return NULL;
 remove_life(gs);
 po->ibase = gs->pigframes;
 po->handler = player_handler;
 po->hitmask = GROUP_POWERUP | GROUP_ENEMY;
 return po;
}

static PIG_object *new_powerup(GAMESTATE *gs,
  int x, int y, int speed, POWERUPS type)
{
 PIG_object *po = pig_object_open(gs->pe, x, y, 1);
 if(!po)
  return NULL;
 ++gs->enemycount;
 po->score = type;
 po->ibase = gs->icons + 8 * po->score;
 po->target = speed;
 po->handler = powerup_handler;
 po->tilemask = PIG_TOP;
 po->hitgroup = GROUP_POWERUP;
 return po;
}

static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy)
{
 PIG_object *po = pig_object_open(gs->pe, x + vx, y + vy, 1);
 if(!po)
  return NULL;
 po->ibase = gs->stars;
 po->ax = -vx * 0.3;
 po->vx = vx * 3;
 po->ay = -vy * 0.3;
 po->vy = vy * 3;
 po->handler = star_handler;
 return po;
}

static PIG_object *new_evil(GAMESTATE *gs,
  int x, int y, int speed)
{
 PIG_object *po = pig_object_open(gs->pe,
   x * TILE_W, y * TILE_H, 1);
 if(!po)
  return NULL;
 ++gs->enemycount;
 po->ibase = gs->evil;
 po->target = speed;
 po->handler = evil_handler;
 po->score = 200;
 po->tilemask = PIG_TOP;
 po->hitgroup = GROUP_ENEMY;
 return po;
}

static PIG_object *new_slime(GAMESTATE *gs,
  int x, int y, int speed)
{
 PIG_object *po = pig_object_open(gs->pe,
   x * TILE_W, y * TILE_H, 1);
 if(!po)
  return NULL;
 ++gs->enemycount;
 po->ibase = gs->slime;
 po->target = speed;
 po->handler = slime_handler;
 po->score = 300;
 po->tilemask = PIG_TOP;
 po->hitgroup = GROUP_ENEMY;
 return po;
}

static PIG_object *new_chain_head(GAMESTATE *gs,
  int x, int y, int image, int target_x)
{
 PIG_object *po = pig_object_open(gs->pe, x, y, 1);
 if(!po)
  return NULL;
 po->ibase = image;
 po->handler = chain_head_handler;
 po->target = target_x;
 return po;
}

static PIG_object *new_chain_link(GAMESTATE *gs,
  int x, int y, int image, int target)
{
 PIG_object *po = pig_object_open(gs->pe, x, y, 1);
 if(!po)
  return NULL;
 po->ibase = image;
 po->handler = chain_link_handler;
 po->target = target;
 return po;
}

static void message(GAMESTATE *gs, const char *s)
{
 int i = 0;
 const int x = SCREEN_W + FONT_SPACING;
 const int y = MAP_H * TILE_H - 30;
 int tx = (SCREEN_W - ((signed)strlen(s) - 1) * FONT_SPACING) / 2;
 PIG_object *po = NULL;
 while(s[i])
 {
  int c = toupper(s[i]) - 32 + gs->glassfont;
  if(0 == i)
   po = new_chain_head(gs, x, y, c, tx);
  else
   po = new_chain_link(gs, x, y, c, po->id);
  if(!po)
   return;
  ++i;
 }
 ++gs->messages;
}

static int start_game(GAMESTATE *gs)
{
 if(0 != gs->level)
  return 0; /* Already playing! --> */
 gs->score = 0;
 gs->lives = 5;
 if(load_level(gs, 1) < 0)
  return -1;
 gs->player = new_player(gs);
 if(!gs->player)
  return -1;
 return 0;
}

/*----------------------------------------------------------
 Input; events and game control keys
----------------------------------------------------------*/
static void handle_input(GAMESTATE *gs, SDL_Event *ev)
{
 switch(ev->type)
 {
   case SDL_MOUSEBUTTONUP:
  break;
   case SDL_KEYDOWN:
  switch(ev->key.keysym.sym)
  {
    case SDLK_UP:
   gs->jump = 3;
   break;
    case SDLK_F1:
   gs->pe->interpolation = !gs->pe->interpolation;
   if(gs->pe->interpolation)
    message(gs, "Interpolation: ON");
   else
    message(gs, "Interpolation: OFF");
   break;
    case SDLK_F2:
   gs->pe->direct = !gs->pe->direct;
   if(gs->pe->direct)
    message(gs, "Rendering: Direct");
   else
    message(gs, "Rendering: Buffered");
   break;
    case SDLK_F3:
   gs->pe->show_dirtyrects = !gs->pe->show_dirtyrects;
   if(gs->pe->show_dirtyrects)
    message(gs, "Dirtyrects: ON");
   else
    message(gs, "Dirtyrects: OFF");
   break;
    case SDLK_F4:
   gs->nice = !gs->nice;
   if(gs->nice)
    message(gs, "Be Nice: ON");
   else
    message(gs, "Be Nice: OFF");
   break;
    case SDLK_SPACE:
   start_game(gs);
    default:
   break;
  }
  break;
   case SDL_KEYUP:
  switch(ev->key.keysym.sym)
  {
    case SDLK_ESCAPE:
   gs->running = 0;
    default:
   break;
  }
  break;
   case SDL_QUIT:
  gs->running = 0;
  break;
 }
}

static void handle_keys(GAMESTATE *gs)
{
}

static int break_received = 0;
#ifndef RETSIGTYPE
#define RETSIGTYPE void
#endif
static RETSIGTYPE breakhandler(int sig)
{
 /* For platforms that drop the handlers on the first signal... */
 signal(SIGTERM, breakhandler);
 signal(SIGINT, breakhandler);
 break_received = 1;
#if (RETSIGTYPE != void)
 return 0;
#endif
}

/*----------------------------------------------------------
 main()
----------------------------------------------------------*/
int main(int argc, char* argv[])
{
 SDL_Surface *screen;
 GAMESTATE *gs;
 int i;
 int bpp = 0;
 int last_tick, start_time, end_time;
 int dashframe;
 float logic_fps = 20.0;
 int flags = SDL_DOUBLEBUF | SDL_HWSURFACE;
 SDL_Init(SDL_INIT_VIDEO);
 atexit(SDL_Quit);
 signal(SIGTERM, breakhandler);
 signal(SIGINT, breakhandler);
 for(i = 1; i < argc; ++i)
 {
  if(strncmp(argv[i], "-s", 2) == 0)
   flags &= ~SDL_DOUBLEBUF;
  else if(strncmp(argv[i], "-f", 2) == 0)
   flags |= SDL_FULLSCREEN;
  else
   bpp = atoi(&argv[i][1]);
 }
 screen = SDL_SetVideoMode(SCREEN_W, SCREEN_H, bpp, flags);
 if(!screen)
 {
  fprintf(stderr, "Failed to open screen!\n");
  return 1;
 }
 SDL_WM_SetCaption("Fixed Rate Pig", "Pig");
 SDL_ShowCursor(0);
 gs = init_all(screen);
 if(!gs)
  return 1;
 gs->keys = SDL_GetKeyState(&i);
 gs->logic_frames = 0;
 gs->rendered_frames = 0;
 gs->pe->before_objects = before_objects;
 pig_start(gs->pe, 0);
 gs->refresh_screen = gs->pe->pages;
 start_time = last_tick = SDL_GetTicks();
 while(gs->running)
 {
  int tick;
  float frames, dt;
  SDL_Event ev;
  /* Handle input */
  while(SDL_PollEvent(&ev) > 0)
   handle_input(gs, &ev);
  handle_keys(gs);
  if(break_received)
   gs->running = 0;
  /* Calculate time since last update */
  tick = SDL_GetTicks();
  dt = (tick - last_tick) * 0.001;
  frames = dt * logic_fps;
  /* Run the game logic */
  pig_animate(gs->pe, frames);
  /*
   * Limit the dashboard frame rate to 15 fps
   * when there's no wobbling going on.
   *
   * The 'dashframe' deal is about keeping the
   * pages in sync on a double buffered display.
   */
  if(gs->lives_wobble || gs->score_wobble ||
    (gs->dashboard_time > 1.0/15.0))
  {
   dashframe = gs->pe->pages;
   gs->dashboard_time = 0;
  }
  if(dashframe)
  {
   --dashframe;
   dashboard(gs);
  }
  /* Update sprites */
  if(gs->refresh_screen)
  {
   --gs->refresh_screen;
   pig_refresh_all(gs->pe);
  }
  else
   pig_refresh(gs->pe);
  /* Make the new frame visible */
  pig_flip(gs->pe);
  /* Update statistics, timers and stuff */
  ++gs->rendered_frames;
  gs->lives_wobble_time += dt;
  gs->score_wobble_time += dt;
  gs->dashboard_time += dt;
  last_tick = tick;
  if(gs->nice)
   SDL_Delay(10);
 }
 /* Print some statistics */
 end_time = SDL_GetTicks();
 i = end_time - start_time;
 printf("          Total time running: %d ms\n", i);
 if(!i)
  i = 1;
 printf("Average rendering frame rate: %.2f fps\n",
   gs->rendered_frames * 1000.0 / i);
 printf("    Average logic frame rate: %.2f fps\n",
   gs->logic_frames * 1000.0 / i);
 pig_close(gs->pe);
 return 0;
}
 
 
 
 
==================dirty.c==============

/*
------------------------------------------------------------
 Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
 * Copyright (C) 2004 David Olofson <>
 *
 * This software is released under the terms of the GPL.
 *
 * Contact author for permission if you want to use this
 * software, or work derived from it, under other terms.
 */

#include
#include "engine.h"

/* Approximate worth of one dirtyrect in pixels. */
#define PIG_WORST_MERGE  300

/*
 * If the merged result gets at most this many percent
 * bigger than the larger of the two input rects,
 * accept it as Perfect.
 */
#define PIG_INSTANT_MERGE 10


PIG_dirtytable *pig_dirty_open(int size)
{
 PIG_dirtytable *pdt = (PIG_dirtytable *)malloc(sizeof(PIG_dirtytable));
 if(!pdt)
  return NULL;

 pdt->size = size;
 pdt->rects = (SDL_Rect *)calloc(size, sizeof(SDL_Rect));
 if(!pdt->rects)
 {
  free(pdt);
  return NULL;
 }

 pdt->count = 0;
 pdt->best = 0;
 return pdt;
}


void pig_dirty_close(PIG_dirtytable *pdt)
{
 free(pdt->rects);
 free(pdt);
}


void pig_mergerect(SDL_Rect *from, SDL_Rect *to)
{
 int x1 = from->x;
 int y1 = from->y;
 int x2 = from->x + from->w;
 int y2 = from->y + from->h;
 if(to->x < x1)
  x1 = to->x;
 if(to->y < y1)
  y1 = to->y;
 if(to->x + to->w > x2)
  x2 = to->x + to->w;
 if(to->y + to->h > y2)
  y2 = to->y + to->h;
 to->x = x1;
 to->y = y1;
 to->w = x2 - x1;
 to->h = y2 - y1;
}


void pig_intersectrect(SDL_Rect *from, SDL_Rect *to)
{
 int Amin, Amax, Bmin, Bmax;
 Amin = to->x;
 Amax = Amin + to->w;
 Bmin = from->x;
 Bmax = Bmin + from->w;
 if(Bmin > Amin)
  Amin = Bmin;
 to->x = Amin;
 if(Bmax < Amax)
  Amax = Bmax;
 to->w = Amax - Amin > 0 ? Amax - Amin : 0;

 Amin = to->y;
 Amax = Amin + to->h;
 Bmin = from->y;
 Bmax = Bmin + from->h;
 if(Bmin > Amin)
  Amin = Bmin;
 to->y = Amin;
 if(Bmax < Amax)
  Amax = Bmax;
 to->h = Amax - Amin > 0 ? Amax - Amin : 0;
}


void pig_dirty_add(PIG_dirtytable *pdt, SDL_Rect *dr)
{
 int i, j, best_i, best_loss;
 /*
  * Look for merger candidates.
  *
  * We start right before the best match we
  * had the last time around. This can give
  * us large numbers of direct or quick hits
  * when dealing with old/new rects for moving
  * objects and the like.
  */
 best_i = -1;
 best_loss = 100000000;
 if(pdt->count)
  i = (pdt->best + pdt->count - 1) % pdt->count;
 for(j = 0; j < pdt->count; ++j)
 {
  int a1, a2, am, ratio, loss;
  SDL_Rect testr;

  a1 = dr->w * dr->h;

  testr = pdt->rects[i];
  a2 = testr.w * testr.h;

  pig_mergerect(dr, &testr);
  am = testr.w * testr.h;

  /* Perfect or Instant Pick? */
  ratio = 100 * am / (a1 > a2 ? a1 : a2);
  if(ratio < PIG_INSTANT_MERGE)
  {
   /* Ok, this is good enough! Stop searching. */
   pig_mergerect(dr, &pdt->rects[i]);
   pdt->best = i;
   return;
  }

  loss = am - a1 - a2;
  if(loss < best_loss)
  {
   best_i = i;
   best_loss = loss;
   pdt->best = i;
  }

  ++i;
  i %= pdt->count;
 }
 /* ...and if the best result is good enough, merge! */
 if((best_i >= 0) && (best_loss < PIG_WORST_MERGE))
 {
  pig_mergerect(dr, &pdt->rects[best_i]);
  return;
 }

 /* Try to add to table... */
 if(pdt->count < pdt->size)
 {
  pdt->rects[pdt->count++] = *dr;
  return;
 }

 /* Emergency: Table full! Grab best candidate... */
 pig_mergerect(dr, &pdt->rects[best_i]);
}


void pig_dirty_merge(PIG_dirtytable *pdt, PIG_dirtytable *from)
{
 int i;
 for(i = 0; i < from->count; ++i)
  pig_dirty_add(pdt, from->rects + i);
}

 

===================dirty.h==============

/*
------------------------------------------------------------
 Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
 * Copyright (C) 2004 David Olofson <>
 *
 * This software is released under the terms of the GPL.
 *
 * Contact author for permission if you want to use this
 * software, or work derived from it, under other terms.
 */

#ifndef PIG_DIRTY_H
#define PIG_DIRTY_H

/* A table of dirtyrects for one display page */
typedef struct PIG_dirtytable
{
 int  size; /* Table size */
 SDL_Rect *rects; /* Table of rects */
 int  count; /* # of rects currently used */
 int  best; /* Merge testing starts here! */
} PIG_dirtytable;


PIG_dirtytable *pig_dirty_open(int size);
void pig_dirty_close(PIG_dirtytable *pdt);

/* Add rectangle 'dr' to table 'pdt' */
void pig_dirty_add(PIG_dirtytable *pdt, SDL_Rect *dr);

/* Merge table 'from' into 'pdt' */
void pig_dirty_merge(PIG_dirtytable *pdt, PIG_dirtytable *from);

/* Extend 'to' to a new rect that includes both 'from' and 'to' */
void pig_mergerect(SDL_Rect *from, SDL_Rect *to);

/* Clip 'to' into a rect that is the intersection of 'from' and 'to' */
void pig_intersectrect(SDL_Rect *from, SDL_Rect *to);

#endif /* PIG_DIRTY_H */

 

==================engine.c================

/*
------------------------------------------------------------
 Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
 * Copyright (C) 2004 David Olofson <>
 *
 * This software is released under the terms of the GPL.
 *
 * Contact author for permission if you want to use this
 * software, or work derived from it, under other terms.
 */

#include
#include
#include "engine.h"
#include "SDL_image.h"

/* Size of sprite frame table */
#define PIG_MAX_SPRITES  1024


/*
 * Actually remove an objects. Used internally,
 * to remove objects that have been marked for
 * destruction.
 */
static void close_object(PIG_object *po);


/*----------------------------------------------------------
 Engine
----------------------------------------------------------*/
PIG_engine *pig_open(SDL_Surface *screen)
{
 PIG_engine *pe = (PIG_engine *)calloc(1, sizeof(PIG_engine));
 if(!pe)
  return NULL;

 pe->screen = screen;
 if(!pe->screen)
 {
  pig_close(pe);
  return NULL;
 }
 if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE)
 {
  pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE,
    screen->w, screen->h,
    screen->format->BitsPerPixel,
    screen->format->Rmask,
    screen->format->Gmask,
    screen->format->Bmask,
    screen->format->Amask);
  if(!pe->buffer)
  {
   pig_close(pe);
   return NULL;
  }
  pe->surface = pe->buffer;
 }
 else
  pe->surface = screen;

 pe->pages = 1 + ((screen->flags & SDL_DOUBLEBUF) == SDL_DOUBLEBUF);

 pe->interpolation = 1;
 pe->time = 0.0;
 pe->view.w = pe->surface->w;
 pe->view.h = pe->surface->h;

 pe->sprites = (PIG_sprite **)calloc(PIG_MAX_SPRITES,
   sizeof(PIG_sprite *));
 if(!pe->sprites)
 {
  pig_close(pe);
  return NULL;
 }

 pe->pagedirty[0] = pig_dirty_open(128);
 pe->workdirty = pig_dirty_open(256);
 if(!pe->pagedirty[0] || !pe->workdirty)
 {
  pig_close(pe);
  return NULL;
 }
 if(pe->pages > 1)
 {
  pe->pagedirty[1] = pig_dirty_open(128);
  if(!pe->pagedirty[1])
  {
   pig_close(pe);
   return NULL;
  }
 }

 return pe;
}


void pig_close(PIG_engine *pe)
{
 if(pe->sprites)
 {
  int i;
  for(i = 0; i < pe->nsprites; ++i)
   if(pe->sprites[i])
   {
    if(pe->sprites[i]->surface)
     SDL_FreeSurface(pe->sprites[i]->surface);
    free(pe->sprites[i]);
   }
  free(pe->sprites);
 }
 while(pe->objects)
  close_object(pe->objects);
 if(pe->map)
  pig_map_close(pe->map);
 if(pe->buffer)
  SDL_FreeSurface(pe->buffer);
 if(pe->pagedirty[0])
  pig_dirty_close(pe->pagedirty[0]);
 if(pe->pagedirty[1])
  pig_dirty_close(pe->pagedirty[1]);
 if(pe->workdirty)
  pig_dirty_close(pe->workdirty);
 free(pe);
}


void pig_viewport(PIG_engine *pe, int x, int y, int w, int h)
{
 pe->view.x = x;
 pe->view.y = y;
 pe->view.w = w;
 pe->view.h = h;
}


int pig_sprites(PIG_engine *pe, const char *filename, int sw, int sh)
{
 int x, y, count, handle;
 SDL_Surface *tmp = IMG_Load(filename);
 if(!tmp)
 {
  fprintf(stderr, "Could not load '%s'!\n", filename);
  return -1;
 }

 handle = pe->nsprites;

 if(!sw)
  sw = tmp->w;
 if(!sh)
  sh = tmp->h;

 /* Disable blending, so we get the alpha channel COPIED! */
 SDL_SetAlpha(tmp, 0, 0);

 count = 0;
 for(y = 0; y <= tmp->h - sh; y += sh)
  for(x = 0; x <= tmp->w - sw; x += sw)
  {
   SDL_Rect r;
   SDL_Surface *tmp2;
   PIG_sprite *s;
   if(pe->nsprites >= PIG_MAX_SPRITES)
   {
    fprintf(stderr, "Sprite bank full!\n");
    return -1;
   }
   s = (PIG_sprite *)calloc(1, sizeof(PIG_sprite));
   if(!s)
    return -1;
   s->w = sw;
   s->h = sh;
   s->hotx = sw / 2;
   s->hoty = sh / 2;
   s->radius = (sw + sh) / 5;
   tmp2 = SDL_CreateRGBSurface(SDL_SWSURFACE,
     sw, sh, 32,
     0xff000000, 0x00ff0000,
     0x0000ff00, 0x000000ff);
   SDL_SetAlpha(tmp2, 0, 0);
   r.x = x;
   r.y = y;
   r.w = sw;
   r.h = sh;
   SDL_BlitSurface(tmp, &r, tmp2, NULL);
   SDL_SetAlpha(tmp2, SDL_SRCALPHA | SDL_RLEACCEL,
     SDL_ALPHA_OPAQUE);
   s->surface = SDL_DisplayFormatAlpha(tmp2);
   if(!s->surface)
   {
    fprintf(stderr, "Could not convert sprite %d"
      " of '%s'!\n",
      count, filename);
    return -1;
   }
   SDL_FreeSurface(tmp2);
   pe->sprites[pe->nsprites] = s;
   ++pe->nsprites;
   ++count;
  }

 SDL_FreeSurface(tmp);
 return handle;
}


int pig_hotspot(PIG_engine *pe, int frame, int hotx, int hoty)
{
 if((frame < 0 ) || (frame >= pe->nsprites))
  return -1;

 switch(hotx)
 {
   case PIG_UNCHANGED:
  break;
   case PIG_MIN:
  pe->sprites[frame]->hotx = 0;
  break;
   case PIG_CENTER:
  pe->sprites[frame]->hotx = pe->sprites[frame]->w / 2;
  break;
   case PIG_MAX:
  pe->sprites[frame]->hotx = pe->sprites[frame]->w;
  break;
   default:
  pe->sprites[frame]->hotx = hotx;
  break;
 }
 switch(hoty)
 {
   case PIG_UNCHANGED:
  break;
   case PIG_MIN:
  pe->sprites[frame]->hoty = 0;
  break;
   case PIG_CENTER:
  pe->sprites[frame]->hoty = pe->sprites[frame]->h / 2;
  break;
   case PIG_MAX:
  pe->sprites[frame]->hoty = pe->sprites[frame]->h;
  break;
   default:
  pe->sprites[frame]->hoty = hoty;
  break;
 }
 return 0;
}


int pig_radius(PIG_engine *pe, int frame, int radius)
{
 if((frame < 0 ) || (frame >= pe->nsprites))
  return -1;

 pe->sprites[frame]->radius = radius;
 return 0;
}


void pig_start(PIG_engine *pe, int frame)
{
 PIG_object *po = pe->objects;
 pe->time = (double)frame;
 pe->frame = frame;
 while(po)
 {
  po->ip.gx = po->ip.ox = po->x;
  po->ip.gy = po->ip.oy = po->y;
  po->ip.gimage = po->ibase + po->image;
  po = po->next;
 }
}


static void run_timers(PIG_engine *pe, PIG_object *po)
{
 int i;
 for(i = 0; i < PIG_TIMERS; ++i)
  if(po->timer[i])
  {
   --po->timer[i];
   if(!po->timer[i])
   {
    PIG_event ev;
    ev.type = PIG_TIMER0 + i;
    po->handler(po, &ev);
    if(!po->id)
     return;
   }
  }
}


static void test_offscreen(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
{
 PIG_event ev;
 int hx, hy, w, h;
 if(s)
 {
  hx = s->hotx;
  hy = s->hoty;
  w = s->w;
  h = s->h;
 }
 else
  hx = hy = w = h = 0;
 ev.cinfo.sides = (po->y - hy < -h) << PIG_TOP_B;
 ev.cinfo.sides |= (po->y - hy >= pe->view.h) << PIG_BOTTOM_B;
 ev.cinfo.sides |= (po->x - hx < -w) << PIG_LEFT_B;
 ev.cinfo.sides |= (po->x - hx >= pe->view.w) << PIG_RIGHT_B;
 if(ev.cinfo.sides)
 {
  float dx = po->x - po->ip.ox;
  float dy = po->y - po->ip.oy;
  if(ev.cinfo.sides & PIG_TOP)
  {
   ev.cinfo.y = 0;
   if(dy)
    ev.cinfo.x = po->ip.ox - dx * po->ip.oy / dy;
  }
  else if(ev.cinfo.sides & PIG_BOTTOM)
  {
   ev.cinfo.y = pe->view.h - 1;
   if(dy)
    ev.cinfo.x = po->ip.ox + dx *
      (ev.cinfo.y - po->ip.oy) / dy;
  }
  if(ev.cinfo.sides & PIG_LEFT)
  {
   ev.cinfo.x = 0;
   if(dx)
    ev.cinfo.y = po->ip.oy - dy * po->ip.ox / dx;
  }
  else if(ev.cinfo.sides & PIG_RIGHT)
  {
   ev.cinfo.x = pe->view.w - 1;
   if(dx)
    ev.cinfo.y = po->ip.oy + dy *
      (ev.cinfo.x - po->ip.ox) / dx;
  }
  ev.type = PIG_OFFSCREEN;
  po->handler(po, &ev);
 }
}


/* Test for stationary sprite/sprite collision */
static void sprite_sprite_one(PIG_object *po, PIG_object *po2, float t, float hitdist)
{
 float dx, dy, dsquare;
 PIG_event ev;
 int sides;
 float ix = po->ip.ox * (1 - t) + po->x * t;
 float iy = po->ip.oy * (1 - t) + po->y * t;
 float ix2 = po2->ip.ox * (1 - t) + po2->x * t;
 float iy2 = po2->ip.oy * (1 - t) + po2->y * t;
 dx = ix - ix2;
 dy = iy - iy2;
 dsquare = dx*dx + dy*dy;
 if(dsquare >= hitdist*hitdist)
  return;  /* Nothing... --> */

 if(fabs(dsquare) < 1)
  sides = PIG_ALL;
 else
 {
  float d = sqrt(dsquare);
  dx /= d;
  dy /= d;
  if(dx < -0.707)
   sides = PIG_LEFT;
  else if((dx > 0.707))
   sides = PIG_RIGHT;
  else
   sides = 0;
  if(dy < -0.707)
   sides |= PIG_TOP;
  else if((dy > 0.707))
   sides |= PIG_BOTTOM;
 }
 ev.type = PIG_HIT_OBJECT;
 ev.cinfo.ff = 0.0;

 ev.cinfo.x = ix;
 ev.cinfo.y = iy;
 ev.cinfo.sides = sides;
 if(po->hitmask & po2->hitgroup)
 {
  ev.obj = po2;
  po->handler(po, &ev);
 }

 if(po2->id && (po2->hitmask & po->hitgroup))
 {
  int s;
  ev.cinfo.x = ix2;
  ev.cinfo.y = iy2;
  s = ((sides >> PIG_LEFT_B) & 1) << PIG_RIGHT_B;
  s |= ((sides >> PIG_RIGHT_B) & 1) << PIG_LEFT_B;
  s |= ((sides >> PIG_TOP_B) & 1) << PIG_BOTTOM_B;
  s |= ((sides >> PIG_BOTTOM_B) & 1) << PIG_TOP_B;
  ev.cinfo.sides = s;
  ev.obj = po;
  po2->handler(po2, &ev);
 }
}


/*
 * Check 'po' against all subsequent objects in the list.
 * The testing is step size limited so that neither object
 * moves more than 25% of the collision distance between tests.
 * (25% should be sufficient for correct direction flags.)
 */
static void test_sprite_sprite(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
{
 int image;
 PIG_object *po2, *next2;
 for(po2 = po->next; po2; po2 = next2)
 {
  float hitdist, d, dmax, t, dt;
  next2 = po2->next;
  if(!po->id || !po2->id)
   break;

  /* Check collision groups and masks */
  if(!(po->hitmask & po2->hitgroup) &&
    !(po2->hitmask & po->hitgroup))
   continue;

  /* Calculate minimum distance */
  hitdist = s ? s->radius : 0;
  image = po2->ibase + po2->image;
  if((image >= 0) && (image < pe->nsprites))
   hitdist += pe->sprites[image]->radius;
  if(hitdist < 1)
   hitdist = 1;

  /* Calculate number of testing steps */
  dmax = fabs(po->ip.ox - po->x);
  d = fabs(po->ip.oy - po->y);
  dmax = d > dmax ? d : dmax;
  d = fabs(po2->ip.ox - po2->x);
  dmax = d > dmax ? d : dmax;
  d = fabs(po2->ip.oy - po2->y);
  dmax = d > dmax ? d : dmax;
  if(dmax > 1)
   dt = hitdist / (dmax * 4);
  else
   dt = 1;

  /* Sweep test! */
  for(t = 0; t < 1; t += dt)
   sprite_sprite_one(po, po2, t, hitdist);
 }
}


/*
 * Returns a non-zero value if the tile at (x, y) is marked for
 * collisions on the side indicated by 'mask'.
 */
static __inline__ int check_tile(PIG_map *m, int x, int y, int mask)
{
 int mx, my;
 /*
  * Must check < 0 first! (Division rounds
  * towards zero - not downwards.)
  */
 if(x < 0 || y < 0)
  return PIG_NONE;

 mx = x / m->tw;
 my = y / m->th;
 if(mx >= m->w || my >= m->h)
  return PIG_NONE;

 return m->hit[my * m->w + mx] & mask;
}


int pig_test_map(PIG_engine *pe, int x, int y)
{
 int mx, my;
 if(x < 0 || y < 0)
  return PIG_NONE;

 mx = x / pe->map->tw;
 my = y / pe->map->th;
 if(mx >= pe->map->w || my >= pe->map->h)
  return PIG_NONE;

 return pe->map->hit[my * pe->map->w + mx];
}


/*
 * Simple implementation that checks only for top edge collisions.
 * (Full top/bottom/left/right checks with proper handling of
 * corners and rows of tiles is a lot more complicated, so I'll
 * leave that out for now, rather than hacking something simple
 * but incorrect.)
 */
int pig_test_map_vector(PIG_engine *pe, int x1, int y1, int x2, int y2,
  int mask, PIG_cinfo *ci)
{
 PIG_cinfo lci;
 PIG_map *m = pe->map;
 int x, y;
 int dist = 2000000000L;
 if(!ci)
  ci = &lci;
 ci->sides = 0;
 if((mask & PIG_TOP) && (y1 < y2))
 {
  /* Test for tiles that can be hit from the top */
  for(y = y1 + m->th - y1 % m->th; y <= y2; y += m->th)
  {
   x = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
   if(check_tile(m, x, y + 1, PIG_TOP))
   {
    dist = (x-x1) * (x-x1) + (y-y1) * (y-y1);
    ci->x = x;
    ci->y = y - 1;
    ci->sides |= PIG_TOP;
    break;
   }
  }
 }
 if(ci->sides)
  ci->ff = sqrt((x2 - x1) * (x2 - x1) +
    (y2 - y1) * (y2 - y1) / dist);
 return ci->sides;
}


static void test_sprite_map(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
{
 PIG_event ev;
 if(pig_test_map_vector(pe, po->ip.ox, po->ip.oy, po->x, po->y,
   po->tilemask, &ev.cinfo))
 {
  ev.type = PIG_HIT_TILE;
  po->handler(po, &ev);
 }
}


static void run_logic(PIG_engine *pe)
{
 PIG_object *po, *next;
 int image;

 /* Shift logic coordinates */
 for(po = pe->objects; po; po = po->next)
 {
  po->ip.ox = po->x;
  po->ip.oy = po->y;
 }

 if(pe->before_objects)
  pe->before_objects(pe);

 for(po = pe->objects; po; po = next)
 {
  PIG_event ev;
  /*
   * We must grab the next pointer before
   * we call any event handlers, as they
   * may cause objects to remove themselves!
   */
  next = po->next;
  ev.type = PIG_PREFRAME;
  po->handler(po, &ev);
 }

 for(po = pe->objects; po; po = next)
 {
  PIG_sprite *s;
  next = po->next;
  image = po->ibase + po->image;
  if((image >= 0) && (image < pe->nsprites))
   s = pe->sprites[image];
  else
   s = NULL;

  /* Move! */
  po->vx += po->ax;
  po->vy += po->ay;
  po->x += po->vx;
  po->y += po->vy;

  /* Check and handle events */
  if(po->handler)
  {
   run_timers(pe, po);
   if(po->id)
    test_offscreen(pe, po, s);
   if(po->id && (po->hitmask || po->hitgroup))
    test_sprite_sprite(pe, po, s);
   if(po->id && po->tilemask)
    test_sprite_map(pe, po, s);

  }
 }

 for(po = pe->objects; po; po = next)
 {
  next = po->next;
  if(po->id)
  {
   PIG_event ev;
   ev.type = PIG_POSTFRAME;
   po->handler(po, &ev);
   ++po->age;
  }
 }

 if(pe->after_objects)
  pe->after_objects(pe);
}


void pig_animate(PIG_engine *pe, float frames)
{
 /* Advance logic time */
 int i = floor(pe->time + frames) - floor(pe->time);
 while(i--)
 {
  run_logic(pe);
  ++pe->frame;
 }
 pe->time += frames;
}


void pig_dirty(PIG_engine *pe, SDL_Rect *dr)
{
 SDL_Rect r;
 r.x = 0;
 r.y = 0;
 r.w = pe->surface->w;
 r.h = pe->surface->h;
 if(dr)
  pig_intersectrect(dr, &r);
 if(r.w && r.h)
  pig_dirty_add(pe->pagedirty[pe->page], &r);
}


static void tile_area(PIG_engine *pe, SDL_Rect *r)
{
 SDL_Rect cr;
 int x, y, startx, starty, maxx, maxy, tilesperrow;
 cr = *r;
 cr.x += pe->view.x;
 cr.y += pe->view.y;
 SDL_SetClipRect(pe->surface, &cr);

 startx = r->x / pe->map->tw;
 starty = r->y / pe->map->th;
 maxx = (r->x + r->w + pe->map->tw - 1) / pe->map->tw;
 maxy = (r->y + r->h + pe->map->th - 1) / pe->map->th;
 if(maxx > pe->map->w - 1)
  maxx = pe->map->w - 1;
 if(maxy > pe->map->h - 1)
  maxy = pe->map->h - 1;
 tilesperrow = pe->map->tiles->w / pe->map->tw;

 for(y = starty; y <= maxy; ++y)
  for(x = startx; x <= maxx; ++x)
  {
   SDL_Rect from, to;
   int c = pe->map->map[y * pe->map->w + x];
   from.x = c % tilesperrow * pe->map->tw;
   from.y = c / tilesperrow * pe->map->th;
   from.w = pe->map->tw;
   from.h = pe->map->th;
   to.x = pe->view.x + x * pe->map->tw;
   to.y = pe->view.y + y * pe->map->th;
   SDL_BlitSurface(pe->map->tiles, &from,
     pe->surface, &to);
  }
}


void remove_sprites(PIG_engine *pe)
{
 SDL_Rect r;
 PIG_sprite *s;
 PIG_object *po, *next;

 /*
  * Remove all objects, using the information that
  * remains from the last frame. The actual removal
  * is done by drawing over the sprites with tiles
  * from the map.
  *
  * We assume that most objects don't overlap. If
  * they do that a lot, we could use a "dirty map"
  * to avoid rendering the same tiles multiple times
  * in the overlapping areas.
  */
 for(po = pe->objects; po; po = next)
 {
  next = po->next;
  if((po->ip.gimage < 0) || (po->ip.gimage >= pe->nsprites))
   continue;
  s = pe->sprites[po->ip.gimage];
  r.x = po->ip.gx - s->hotx;
  r.y = po->ip.gy - s->hoty;
  r.w = s->w;
  r.h = s->h;
  pig_intersectrect(&pe->view, &r);
  if(r.w && r.h)
   tile_area(pe, &r);

  /*
   * Delete dead objects *after* they've
   * been removed from the rendering buffer!
   */
  if(!po->id)
   close_object(po);
 }
}


void draw_sprites(PIG_engine *pe)
{
 PIG_dirtytable *pdt;
 PIG_sprite *s;
 PIG_object *po;
 float fframe = pe->time - floor(pe->time);
 SDL_SetClipRect(pe->surface, &pe->view);

 /* Swap the work and display/back page dirtytables */
 pdt = pe->workdirty;
 pe->workdirty = pe->pagedirty[pe->page];
 pe->pagedirty[pe->page] = pdt;

 /* Clear the display/back page dirtytable */
 pdt->count = 0;

 /* Update positions and render all objects */
 po = pe->objects;
 while(po)
 {
  /* Calculate graphic coordinates */
  if(pe->interpolation)
  {
   po->ip.gx = po->ip.ox * (1 - fframe) + po->x * fframe;
   po->ip.gy = po->ip.oy * (1 - fframe) + po->y * fframe;
  }
  else
  {
   po->ip.gx = po->x;
   po->ip.gy = po->y;
  }
  po->ip.gimage = po->ibase + po->image;

  /* Render the sprite! */
  if((po->ip.gimage >= 0) && (po->ip.gimage < pe->nsprites))
  {
   SDL_Rect dr;
   s = pe->sprites[po->ip.gimage];
   dr.x = po->ip.gx - s->hotx + pe->view.x;
   dr.y = po->ip.gy - s->hoty + pe->view.y;
   SDL_BlitSurface(pe->sprites[po->ip.gimage]->surface,
    NULL, pe->surface, &dr);
   /*
    * We use the clipped rect for the dirtyrect!
    */
   if(dr.w && dr.h)
    pig_dirty_add(pdt, &dr);
  }
  po = po->next;
 }

 /* Merge the display/back page table into the work table */
 pig_dirty_merge(pe->workdirty, pdt);
}


void pig_refresh(PIG_engine *pe)
{
 remove_sprites(pe);
 draw_sprites(pe);
}


void pig_refresh_all(PIG_engine *pe)
{
 tile_area(pe, &pe->view);
 pig_dirty(pe, NULL);
 draw_sprites(pe);
}


static void show_rects(PIG_engine *pe, PIG_dirtytable *pdt)
{
 int i;
 Uint32 color;
 if(!pe->buffer)
 {
  pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE,
    pe->screen->w, pe->screen->h,
    pe->screen->format->BitsPerPixel,
    pe->screen->format->Rmask,
    pe->screen->format->Gmask,
    pe->screen->format->Bmask,
    pe->screen->format->Amask);
  if(!pe->buffer)
   return;
  pe->surface = pe->buffer;
  tile_area(pe, &pe->view);
 }
 if(!pe->buffer)
  return;

 pe->direct = 0;

 for(i = 0; i < pdt->count; ++i)
 {
  SDL_Rect r;
  r = pdt->rects[i];
  r.x -= 32;
  r.y -= 32;
  r.w += 64;
  r.h += 64;
  SDL_BlitSurface(pe->buffer, &r, pe->screen, &r);
 }

 color = SDL_MapRGB(pe->screen->format, 255, 0, 255);
 for(i = 0; i < pdt->count; ++i)
 {
  SDL_Rect r;
  r = pdt->rects[i];
  r.h = 1;
  SDL_FillRect(pe->screen, &r, color);
  r.y += pdt->rects[i].h - 1;
  SDL_FillRect(pe->screen, &r, color);
  r = pdt->rects[i];
  r.w = 1;
  SDL_FillRect(pe->screen, &r, color);
  r.x += pdt->rects[i].w - 1;
  SDL_FillRect(pe->screen, &r, color);
 }
}


void pig_flip(PIG_engine *pe)
{
 PIG_dirtytable *pdt = pe->workdirty;
 int i;
 SDL_SetClipRect(pe->surface, NULL);

 if(pe->show_dirtyrects)
 {
  show_rects(pe, pdt);
  for(i = 0; i < pdt->count; ++i)
  {
   pdt->rects[i].x -= 32;
   pdt->rects[i].y -= 32;
   pdt->rects[i].w += 64;
   pdt->rects[i].h += 64;
   pig_intersectrect(&pe->buffer->clip_rect, &pdt->rects[i]);
  }
 }
 else if(pe->surface == pe->buffer)
  for(i = 0; i < pdt->count; ++i)
   SDL_BlitSurface(pe->buffer, pdt->rects + i,
     pe->screen, pdt->rects + i);

 if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE)
 {
  SDL_Flip(pe->screen);
  if(pe->pages > 1)
   pe->page = 1 - pe->page;
 }
 else
  SDL_UpdateRects(pe->screen, pdt->count, pdt->rects);

 if(pe->direct)
  pe->surface = pe->screen;
 else
  pe->surface = pe->buffer ? pe->buffer : pe->screen;
}


void pig_draw_sprite(PIG_engine *pe, int frame, int x, int y)
{
 SDL_Rect dr;
 if(frame >= pe->nsprites)
  return;
 dr.x = x - pe->sprites[frame]->hotx + pe->view.x;
 dr.y = y - pe->sprites[frame]->hoty + pe->view.y;
 SDL_BlitSurface(pe->sprites[frame]->surface, NULL,
   pe->surface, &dr);
}


/*----------------------------------------------------------
 Map
----------------------------------------------------------*/
PIG_map *pig_map_open(PIG_engine *pe, int w, int h)
{
 if(pe->map)
  pig_map_close(pe->map);

 pe->map = (PIG_map *)calloc(1, sizeof(PIG_map));
 if(!pe->map)
  return NULL;

 pe->map->owner = pe;
 pe->map->w = w;
 pe->map->h = h;
 pe->map->hit = (unsigned char *)calloc(w, h);
 if(!pe->map->hit)
 {
  pig_map_close(pe->map);
  return NULL;
 }
 pe->map->map = (unsigned char *)calloc(w, h);
 if(!pe->map->map)
 {
  pig_map_close(pe->map);
  return NULL;
 }
 return pe->map;
}


void pig_map_close(PIG_map *pm)
{
 PIG_engine *pe = pm->owner;
 if(pm->tiles)
  SDL_FreeSurface(pm->tiles);
 free(pm->hit);
 free(pm->map);
 free(pe->map);
 pe->map = NULL;
}


int pig_map_tiles(PIG_map *pm, const char *filename, int tw, int th)
{
 SDL_Surface *tmp;
 pm->tw = tw;
 pm->th = th;
 tmp = IMG_Load(filename);
 if(!tmp)
 {
  fprintf(stderr, "Could not load '%s'!\n", filename);
  return -1;
 }
 pm->tiles = SDL_DisplayFormat(tmp);
 if(!pm->tiles)
 {
  fprintf(stderr, "Could not convert '%s'!\n", filename);
  return -1;
 }
 SDL_FreeSurface(tmp);
 return 0;
}


void pig_map_collisions(PIG_map *pm, unsigned first, unsigned count, PIG_sides sides)
{
 int i;
 if(first > 255)
  return;
 if(first + count > 255)
  count = 255 - first;
 for(i = first; i < first + count; ++i)
  pm->hitinfo[i] = sides;
}


/*
 * Load a map from a string (one byte/tile). 'trans'
 * is a string used for translating 'data' into integer
 * tile indices. Each position in 'trans' corresponds
 * to one tile in the tile palette.
 */
int pig_map_from_string(PIG_map *pm, const char *trans, const char *data)
{
 int x, y, z;

 /* Load the map */
 z = 0;
 for(y = 0; y < pm->h; ++y)
  for(x = 0; x < pm->w; ++x)
  {
   const char *f;
   int c = data[z];
   if(!c)
   {
    fprintf(stderr, "Map string too short!\n");
    return -1;
   }
   f = strchr(trans, c);
   if(!f)
   {
    fprintf(stderr, "Character '%c' not in"
      " the translation string!\n",
      c);
    return -1;
   }
   pm->map[z] = f - trans;
   ++z;
  }
 /* Generate collision map */
 for(y = 0; y < pm->h; ++y)
  for(x = 0; x < pm->w; ++x)
   pm->hit[y * pm->w + x] =
     pm->hitinfo[pm->map[y * pm->w + x]];
 return 0;
}


/*----------------------------------------------------------
 Object
----------------------------------------------------------*/


static PIG_object *get_object(PIG_engine *pe)
{
 PIG_object *po;
 if(pe->object_pool)
 {
  po = pe->object_pool;
  pe->object_pool = po->next;
  memset(po, 0, sizeof(PIG_object));
 }
 else
 {
  po = (PIG_object *)calloc(1, sizeof(PIG_object));
  if(!po)
   return NULL;
 }
 po->id = ++pe->object_id_counter;
 return po;
}


static void free_object(PIG_object *po)
{
 po->prev = NULL;
 po->next = po->owner->object_pool;
 po->owner->object_pool = po;
 po->id = 0;
}


PIG_object *pig_object_open(PIG_engine *pe, int x, int y, int last)
{
 PIG_object *po = get_object(pe);
 if(!po)
  return NULL;

 po->owner = pe;
 po->tilemask = PIG_ALL;
 po->hitmask = 0;
 po->hitgroup = 0;

 if(last && pe->objects)
 {
  PIG_object *lo = pe->objects;
  while(lo->next)
   lo = lo->next;
  po->prev = lo;
  po->next = NULL;
  lo->next = po;
 }
 else
 {
  po->prev = NULL;
  po->next = pe->objects;
  if(po->next)
   po->next->prev = po;
  pe->objects = po;
 }

 po->x = x;
 po->y = y;
 po->ip.ox = x;
 po->ip.oy = y;
 return po;
}


static void close_object(PIG_object *po)
{
 if(po == po->owner->objects)
  po->owner->objects = po->next;
 else if(po->prev)
  po->prev->next = po->next;
 if(po->next)
  po->next->prev = po->prev;
 free_object(po);
}


void pig_object_close(PIG_object *po)
{
 if(!po->id)
  fprintf(stderr, "Object %p closed more than once!\n", po);
 po->id = 0; /* Mark for eventual removal and destruction */
}


void pig_object_close_all(PIG_engine *pe)
{
 while(pe->objects)
  close_object(pe->objects);
}


PIG_object *pig_object_find(PIG_object *start, int id)
{
 PIG_object *pob, *pof;
 if(start)
  pob = pof = start;
 else
 {
  pof = start->owner->objects;
  while(pof)
  {
   if(pof->id == id)
    return pof;
   pof = pof->next;
  }
  return NULL;
 }
 while(1)
 {
  if(pob)
  {
   if(pob->id == id)
    return pob;
   pob = pob->prev;
  }
  if(pof)
  {
   if(pof->id == id)
    return pof;
   pof = pof->next;
  }
  else
   if(!pob)
    return NULL;
 }
}

 

==================engine.h===================

/*
------------------------------------------------------------
 Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
 * Copyright (C) 2004 David Olofson <>
 *
 * This software is released under the terms of the GPL.
 *
 * Contact author for permission if you want to use this
 * software, or work derived from it, under other terms.
 */

#ifndef PIG_ENGINE_H
#define PIG_ENGINE_H

#include "SDL.h"
#include
#ifndef M_PI
# define M_PI 3.14159265358979323846 /* pi */
#endif
#include "dirty.h"


/*----------------------------------------------------------
 Game Engine
----------------------------------------------------------*/

typedef struct PIG_object PIG_object;
typedef struct PIG_engine PIG_engine;


/* Interpolated point */
typedef struct PIG_ipoint
{
 /* From the last logic frame: */
 float ox, oy;  /* Position */

 /* From the last/current rendered frame: */
 int gimage;  /* Sprite frame index */
 float gx, gy;  /* Interpolated position */
} PIG_ipoint;


/*
 * Game logic events
 *
 * PREFRAME:
 * Occurs once per logic frame, before collision and
 * off-screen detection, and before timer handlers.
 *
 * TIMERx:
 * Occurs whenever timer x expires. Timers are one-
 * shot, but can be reloaded by the handler for
 * periodic action. Timer events are handled before
 * before collision and off-screen detection.
 *
 * HIT_TILE:
 * Occurs when the hot-spot of an object hits a
 * marked side of a tile, and the corresponding bit
 * in 'tilemask' is set.
 *
 * HIT_OBJECT:
 * Occurs when the collision circle of an object
 * intersects the collision circle of another object,
 * provided one or more bits in 'hitgroup' of the
 * other object matches bits in 'hitmask'.
 *
 * OFFSCREEN:
 * Occurs when an object is off-screen. This takes
 * in account the hot-spot and bounding rectangle of
 * the current sprite frame.
 *
 * POSTFRAME:
 * Occurs once per logic frame, after collision
 * detection, off-screen detection and all other
 * events.
 *
 */
#define PIG_TIMERS 3
typedef enum
{
 PIG_PREFRAME,
 PIG_TIMER0,
 PIG_TIMER1,
 PIG_TIMER2,
 PIG_HIT_TILE,
 PIG_HIT_OBJECT,
 PIG_OFFSCREEN,
 PIG_POSTFRAME
} PIG_events;


typedef enum
{
 PIG_NONE = 0,

 /* Bit positions */
 PIG_TOP_B = 0,
 PIG_BOTTOM_B = 1,
 PIG_LEFT_B = 2,
 PIG_RIGHT_B = 3,

 /* Masks */
 PIG_TOP = 1 << PIG_TOP_B,
 PIG_BOTTOM = 1 << PIG_BOTTOM_B,
 PIG_LEFT = 1 << PIG_LEFT_B,
 PIG_RIGHT = 1 << PIG_RIGHT_B,

 /* Combined masks */
 PIG_TL = PIG_TOP | PIG_LEFT,
 PIG_TR = PIG_TOP | PIG_RIGHT,
 PIG_BL = PIG_BOTTOM | PIG_LEFT,
 PIG_BR = PIG_BOTTOM | PIG_RIGHT,
 PIG_ALL = 0xf,
} PIG_sides;


typedef enum
{
 PIG_UNCHANGED = -10000000,
 PIG_MIN = -10000001,
 PIG_CENTER = -10000002,
 PIG_MAX = -10000003
} PIG_values;


/* Collision info */
typedef struct
{
 float  ff; /* Fractional frame */
 int  x, y; /* Exact position */
 PIG_sides sides; /* Side of tile hit */
} PIG_cinfo;


typedef struct PIG_event
{
 PIG_events type;

 /* For HIT_TILE, HIT_OBJECT and OFFSCREEN: */
 PIG_cinfo cinfo; /* Detailed collision info */

 /* For HIT_OBJECT: */
 PIG_object *obj; /* Which object? */
}  PIG_event;


/* Logic object */
struct PIG_object
{
 PIG_engine *owner;
 PIG_object *next, *prev;

 int  id;  /* Unique ID. 0 means "free". */

 int  ibase;  /* Sprite frame base index */
 int  image;  /* Sprite frame offset */
 float  x, y;  /* Position */
 float  vx, vy;  /* Speed */
 float  ax, ay;  /* Acceleration */
 PIG_ipoint ip;
 int  tilemask; /* Sprite/tile mask [PIG_ALL] */

 int  hitmask; /* Sprite/sprite mask [0] */
 int  hitgroup; /* Sprite/sprite group [0] */

 int  timer[PIG_TIMERS]; /* Down-counting timers */
 int  age;  /* Age timer (logic frames) */

 int  score;
 int  power;
 int  target;
 int  state;

 void (*handler)(PIG_object *po, const PIG_event *ev);

 void  *userdata;
};


/* Level map */
typedef struct PIG_map
{
 PIG_engine *owner;

 int  w, h;  /* Size of map (tiles) */
 unsigned char *map;  /* 2D aray of tile indices */
 unsigned char *hit;  /* 2D aray of collision flags */

 int  tw, th;  /* Size of one tile (pixels) */
 SDL_Surface *tiles;  /* Tile palette image */
 unsigned char hitinfo[256]; /* Collision info for the tiles */
} PIG_map;


/* Sprite frame */
typedef struct PIG_sprite
{
 int  w, h;  /* Size of sprite (pixels) */
 int  hotx, hoty; /* Hot-spot offset (pixels) */
 int  radius;  /* Collision zone radius (pixels) */
 SDL_Surface *surface;
} PIG_sprite;

/* Engine */
struct PIG_engine
{
 /* Video stuff */
 SDL_Surface *screen;
 SDL_Surface *buffer; /* For h/w surface displays */
 SDL_Surface *surface; /* Where to render to */
 int  pages;  /* # of display VRAM buffers */
 SDL_Rect view;  /* Viewport pos & size (pixels) */
 int  page;  /* Current page (double buffer) */
 PIG_dirtytable *pagedirty[2]; /* One table for each page */
 PIG_dirtytable *workdirty; /* The work dirtytable */

 /* "Live" switches */
 int  interpolation;
 int  direct;  /* 1 ==> render directly to screen */
 int  show_dirtyrects;

 /* Time */
 double  time;  /* Logic time (frames) */
 int  frame;  /* Logic time; integer part */

 /* Background graphics */
 PIG_map  *map;

 /* Sprites and stuff */
 PIG_object *objects;
 PIG_object *object_pool;
 int  object_id_counter;
 int  nsprites;
 PIG_sprite **sprites;

 /* Logic frame global handlers */
 void (*before_objects)(PIG_engine *pe);
 void (*after_objects)(PIG_engine *pe);

 /* Space for user data */
 void  *userdata;
};


/*
 * Engine
 */
PIG_engine *pig_open(SDL_Surface *screen);
void pig_close(PIG_engine *pe);

/* Set viewport size and position */
void pig_viewport(PIG_engine *pe, int x, int y, int w, int h);

/* Start engine at logic time 'frame' */
void pig_start(PIG_engine *pe, int frame);

/*
 * Load a sprite palette image. The image is chopped up into
 * sprites, based on 'sw' and 'sh', and added as new frames
 * in the sprite bank. Default values:
 * Hot-spot:  (sw/2, sh/2)
 * Collision radius: 0.2 * (sw + sh)
 *
 * Passing 0 for 'sw' and/or 'sh' makes pig_sprites() take
 * the respective value from the image width and/or height.
 *
 * Returns the index of the first frame loaded.
 */
int pig_sprites(PIG_engine *pe, const char *filename, int sw, int sh);

/* Set hot-spot of sprite 'frame' to (hotx, hoty) */
int pig_hotspot(PIG_engine *pe, int frame, int hotx, int hoty);

/* Set sprite/sprite collision zone radius of 'frame' */
int pig_radius(PIG_engine *pe, int frame, int radius);

/* Advance logic time by 'frames' logic frames */
void pig_animate(PIG_engine *pe, float frames);

/*
 * Manually add a dirtyrect for pig_refresh().
 * 'dr' can be outside the engine viewport.
 */
void pig_dirty(PIG_engine *pe, SDL_Rect *dr);

/*
 * Do what's needed to deal with the dirtyrects
 * and then make the new frame visible.
 */
void pig_flip(PIG_engine *pe);

/*
 * Refresh the viewport and any additional dirtyrects.
 *
 * Note that this does not refresh the entire viewport;
 * only the areas that have actually changed!
 */
void pig_refresh(PIG_engine *pe);

/*
 * Refresh the whole viewport, including sprites.
 */
void pig_refresh_all(PIG_engine *pe);

/* Render a sprite "manually", bypassing the engine */
void pig_draw_sprite(PIG_engine *pe, int frame, int x, int y);

/*
 * Get the collision flags for the tile at (x, y),
 * where the unit of x and y is pixels. The return
 * is the PIG_sides flags for the tile, or PIG_NONE
 * if (x, y) is outside the map.
 */
int pig_test_map(PIG_engine *pe, int x, int y);

/*
 * Find the first "collidable" tile side when going from
 * (x1, y1) to (x2, y2). 'mask' determines which tile sides
 * are considered for collisions.
 *
 * Returns the side(s) hit, if any tile was hit. If the return
 * is non-zero, the PIG_cinfo struct at 'ci' contains detailed
 * information about the collision.
 */
int pig_test_map_vector(PIG_engine *pe, int x1, int y1, int x2, int y2,
  int mask, PIG_cinfo *ci);


/*
 * Map
 */
PIG_map *pig_map_open(PIG_engine *pe, int w, int h);
void pig_map_close(PIG_map *pm);

/* Load a tile palette image */
int pig_map_tiles(PIG_map *pm, const char *filename, int tw, int th);

/*
 * Set tile collision info for 'count' tiles, starting at
 * 'first'. Each tile in the tile palette has a set of
 * PIG_sides flags that determine which sides the tile are
 * considered for sprite/map collisions.
 */
void pig_map_collisions(PIG_map *pm, unsigned first, unsigned count,
  PIG_sides sides);

/*
 * Load a map from a string (one byte/tile). 'trans'
 * is a string used for translating 'data' into integer
 * tile indices. Each position in 'trans' corresponds
 * to one tile in the tile palette.
 */
int pig_map_from_string(PIG_map *pm, const char *trans, const char *data);


/*
 * Object
 */

/*
 * Create an object with the initial position (x, y). If
 * 'last' is 1, the object will end up last in the
 * processing and rendering order, otherwise, first.
 *
 * Note that relative processing order is very important
 * to objects that chase each other and stuff like that!
 * If they're placed in the "wrong" order, the tracking
 * objects get an extra frame of reaction time, which is
 * annoying if it's not what you intend.
 */
PIG_object *pig_object_open(PIG_engine *pe, int x, int y, int last);

/*
 * Delete an object.
 *
 * Note that objects are never actually deleted. Instead,
 * they are placed in a free pool, where pig_object_open()
 * looks for objects to recycle.
 *
 * In fact, they are not even freed when you ask for it,
 * but rather kept around until the next rendered frame,
 * so they can be removed from the screen correctly.
 */
void pig_object_close(PIG_object *po);

/*
 * Close all objects.
 */
void pig_object_close_all(PIG_engine *pe);

/*
 * Find object by 'id', starting at object 'start'.
 *
 * The search starts at 'start' and is done in both
 * directions in parallel, assuming that the matching
 * object is near 'start' in the list. (It usually is
 * when dealing with linked objects.)
 *
 * Returns NULL if the object was not found.
 */
PIG_object *pig_object_find(PIG_object *start, int id);

#endif /* PIG_ENGINE_H */

 

 
 
 
 
 
 
 
 
 

 
阅读(5348) | 评论(0) | 转发(0) |
0

上一篇:俄罗斯方块

下一篇:Surface Loading and Blitting

给主人留下些什么吧!~~