Skip to content

Instantly share code, notes, and snippets.

@chebert
Last active April 26, 2018 09:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chebert/7e1255304c2fb79c6636 to your computer and use it in GitHub Desktop.
Save chebert/7e1255304c2fb79c6636 to your computer and use it in GitHub Desktop.
pacman ncurses
/*
map ,g :!gcc % -o %:r -lncurses && ./%:r <CR>
*/
#include <assert.h>
#include <stdint.h>
#include <ncurses.h>
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
void* memset(void* s, int c, size_t n);
char* strchr(const char* s, int c);
#define internal static
#define global_variable static
#define local_persist static
#define UNUSED(x) (void)(x);
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) < (y) ? (y) : (x))
#define ARRAY_LENGTH(arr)\
(sizeof(arr) / sizeof(arr[0]))
#define for_array(index, arr)\
for (index = 0; index < ARRAY_LENGTH(arr); ++index)
#define MS(x) (1000*(x))
#define SECONDS(x) (MS(1000*(x)))
typedef int8_t int8;
typedef uint8_t uint8;
typedef int16_t int16;
typedef uint16_t uint16;
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
typedef float real32;
typedef double real64;
typedef int32_t bool32;
typedef char* c_str;
struct v2 {
int32 x, y;
};
inline internal struct v2
add(struct v2 a, struct v2 b) {
a.x += b.x;
a.y += b.y;
return a;
}
inline internal struct v2
sub(struct v2 a, struct v2 b) {
a.x -= b.x;
a.y -= b.y;
return a;
}
inline internal bool32
eql(struct v2* a, struct v2* b) {
return a->x == b->x && a->y == b->y;
}
inline internal struct v2
scale(struct v2 a, int32 amt) {
a.x *= amt;
a.y *= amt;
return a;
}
inline internal int32
floor_div(int32 num, int32 den) {
double fresult = (double)num / (double)den;
int32 result = (int32)fresult;
if (fresult < (double)result) {
--result;
}
return result;
}
inline internal struct v2
div_scale(struct v2 a, int32 amt) {
a.x = floor_div(a.x, amt);
a.y = floor_div(a.y, amt);
return a;
}
uint32
time_in_us() {
struct timeval t;
gettimeofday(&t, NULL);
return t.tv_sec * 1000000 + t.tv_usec;
}
#define SQUARE(x) ((x)*(x))
#define DIST_SQUARE(x, y) (SQUARE(x) + SQUARE(y))
#define PIXEL_SIZE 20
#define TILE_SIZE_IN_PIXELS 8
#define TILE_SIZE (PIXEL_SIZE * TILE_SIZE_IN_PIXELS)
#define LIFE_LOST_PAUSE_TIME 30
#define LIFE_LOST_TURN_TIME 12
struct v2 TILE_CENTER_IN_PIXELS = { 3, 4 };
struct v2 TILE_CENTER = { 3*PIXEL_SIZE, 4*PIXEL_SIZE };
#define NUM_GHOSTS 4
#define NUM_DIRS 4
struct view {
int top, left;
struct v2 camera_target_tile;
bool32 zoom_view;
};
#define ARENA_WIDTH_IN_TILES 28
#define ARENA_HEIGHT_IN_TILES 31
#define HOUSE_CENTER (ARENA_WIDTH_IN_TILES / 2)
#define HOUSE_LEFT (HOUSE_CENTER - 3)
#define HOUSE_RIGHT (HOUSE_CENTER + 2)
#define HOUSE_TOP 13
#define HOUSE_BOTTOM 15
#define ARENA_HEIGHT (ARENA_HEIGHT_IN_TILES*TILE_SIZE)
#define ARENA_WIDTH (ARENA_WIDTH_IN_TILES*TILE_SIZE)
global_variable
struct v2 top_house_targets[] = {
{ 0 },
{ HOUSE_CENTER, HOUSE_TOP },
{ HOUSE_LEFT, HOUSE_TOP },
{ HOUSE_RIGHT, HOUSE_TOP },
};
global_variable
struct v2 bottom_house_targets[] = {
{ 0 },
{ HOUSE_CENTER, HOUSE_BOTTOM },
{ HOUSE_LEFT, HOUSE_BOTTOM },
{ HOUSE_RIGHT, HOUSE_BOTTOM },
};
global_variable
struct v2 forbidden_upward_tiles[] = {
{ 12, 10 },
{ 15, 10 },
{ 12, 22 },
{ 15, 22 },
};
global_variable
struct v2 fruit_tile = { HOUSE_CENTER, HOUSE_BOTTOM + 2 };
/* Corners:
* top-left: /
* top-right: `
* bottom-left: [
* bottom-right: ]
*/
const
char arena[ARENA_HEIGHT_IN_TILES*ARENA_WIDTH_IN_TILES] =
"/------------`/------------`"
"| || |"
"| /--` /---` || /---` /--` |"
"| |xx| |xxx| || |xxx| |xx| |"
"| [--] [---] [] [---] [--] |"
"| |"
"| /--` /` /------` /` /--` |"
"| [--] || [--`/--] || [--] |"
"| || || || |"
"[----` |[--` || /--]| /----]"
"xxxxx| |/--] [] [--`| |xxxxx"
"xxxxx| || || |xxxxx"
"xxxxx| || +--__--+ || |xxxxx"
"-----] [] |xxxxxx| [] [-----"
" |xxxxxx| "
"-----` /` |xxxxxx| /` /-----"
"xxxxx| || +------+ || |xxxxx"
"xxxxx| || || |xxxxx"
"xxxxx| || /------` || |xxxxx"
"/----] [] [--`/--] [] [----`"
"| || |"
"| /--` /---` || /---` /--` |"
"| [-`| [---] [] [---] |/-] |"
"| || || |"
"[-` || /` /------` /` || /-]"
"/-] [] || [--`/--] || [] [-`"
"| || || || |"
"| /----][--` || /--][----` |"
"| [--------] [] [--------] |"
"| |"
"[--------------------------]"
;
const
char dot_placement_map[ARENA_HEIGHT_IN_TILES*ARENA_WIDTH_IN_TILES] =
"+------------++------------+"
"|............||............|"
"|.+--+.+---+.||.+---+.+--+.|"
"|*| |.| |.||.| |.| |*|"
"|.+--+.+---+.++.+---+.+--+.|"
"|..........................|"
"|.+--+.++.+------+.++.+--+.|"
"|.+--+.||.+--++--+.||.+--+.|"
"|......||....||....||......|"
"+----+.|+--+ || +--+|.+----+"
" |.|+--+ ++ +--+|.| "
" |.|| ||.| "
" |.|| +--__--+ ||.| "
"-----+.++ | | ++.+-----"
" . | | . "
"-----+.++ | | ++.+-----"
" |.|| +------+ ||.| "
" |.|| ||.| "
" |.|| +------+ ||.| "
"+----+.++ +--++--+ ++.+----+"
"|............||............|"
"|.+--+.+---+.||.+---+.+--+.|"
"|.+-+|.+---+.++.+---+.|+-+.|"
"|*..||....... .......||..*|"
"+-+.||.++.+------+.++.||.+-+"
"+-+.++.||.+--++--+.||.++.+-+"
"|......||....||....||......|"
"|.+----++--+.||.+--++----+.|"
"|.+--------+.++.+--------+.|"
"|..........................|"
"+--------------------------+"
;
char dot_map[ARENA_HEIGHT_IN_TILES][ARENA_WIDTH_IN_TILES];
enum {
BG_PAIR = 1,
PACMAN_PAIR,
DOT_PAIR,
GAME_OVER_TEXT_PAIR,
BLINKY_PAIR,
INKY_PAIR,
PINKY_PAIR,
CLYDE_PAIR,
FRIGHT_PAIR,
FRIGHT_FLASH_PAIR,
EYES_PAIR,
EMPTY_PAIR,
WALL_PAIR,
DOOR_PAIR,
CHERRIES_PAIR,
STRAWBERRY_PAIR,
PEACH_PAIR,
APPLE_PAIR,
GRAPES_PAIR,
GALAXIAN_PAIR,
BELL_PAIR,
KEY_PAIR,
};
internal char
arena_get(int row, int col) {
assert(row >= 0 && row < ARENA_HEIGHT_IN_TILES);
if (col < 0) {
col += ARENA_WIDTH_IN_TILES;
} else if (col >= ARENA_WIDTH_IN_TILES) {
col -= ARENA_WIDTH_IN_TILES;
}
return arena[row*ARENA_WIDTH_IN_TILES + col];
}
enum dir {
UP,
LEFT,
DOWN,
RIGHT
};
#define DRAW_SIZE 8
internal struct v2
draw_pos(int row, int col, struct view* view) {
struct v2 ret = {
3 * (col - view->camera_target_tile.x) + view->left,
(row - view->camera_target_tile.y) + view->top,
};
return ret;
}
internal struct v2
draw_pos_v2(struct v2 p, struct view* view) {
return draw_pos(p.y, p.x, view);
}
internal void
draw_tile(int row, int col, char ch, struct view* view, char fill_ch) {
if (view->zoom_view) {
int tile_size = DRAW_SIZE;
int i;
for (i = 0; i < tile_size; ++i) {
int j;
for (j = 0; j < tile_size; ++j) {
mvaddch(tile_size * (row - view->camera_target_tile.y) + view->top + j, tile_size * (col - view->camera_target_tile.x) + view->left + i, fill_ch);
}
}
} else {
struct v2 pos = draw_pos(row, col, view);
mvaddch(pos.y, pos.x, fill_ch);
mvaddch(pos.y, pos.x + 1, ch);
mvaddch(pos.y, pos.x + 2, fill_ch);
}
}
internal void
draw_tile_v2(struct v2 tile_pos, char ch, struct view* view, char fill_ch) {
draw_tile(tile_pos.y, tile_pos.x, ch, view, fill_ch);
}
enum {
COLOR_LIGHT_BLUE=51,
COLOR_PINK=197,
COLOR_ORANGE=172,
COLOR_DARK_RED=88,
COLOR_BRIGHT_RED=196,
COLOR_PURPLE=91,
COLOR_DOOR_RED=124,
COLOR_PACMAN_YELLOW=226,
COLOR_WALL_BLUE=21,
COLOR_DARK_BACKGROUND=232,
COLOR_GREY=233,
};
const char* DIR_NAMES[] = {
"Up", "Down", "Left", "Right"
};
internal bool32
is_h_dir(enum dir dir) {
return dir == LEFT || dir == RIGHT;
}
internal void
move_in_dir(enum dir dir, struct v2* pos, int amt) {
switch (dir) {
case RIGHT:
pos->x += amt;
break;
case LEFT:
pos->x -= amt;
break;
case UP:
pos->y -= amt;
break;
case DOWN:
pos->y += amt;
break;
}
}
internal struct v2
pos_to_tile(struct v2* p) {
return div_scale(*p, TILE_SIZE);
}
int num_blocked_tiles = 0;
struct v2 blocked_tiles[20];
enum ghost_mode {
NORMAL,
FRIGHTENED,
EYES,
};
internal bool32
test_pacman_dir_blocked(enum dir dir, struct v2* next_pos) {
int mv_amt = TILE_SIZE / 2;
if (dir == RIGHT || dir == UP) {
mv_amt += PIXEL_SIZE;
}
move_in_dir(dir, next_pos, mv_amt);
struct v2 next_tile = pos_to_tile(next_pos);
return !!strchr("/`[]-|+_", arena_get(next_tile.y, next_tile.x));
}
inline internal uint32
get_speed(int percentage) {
return percentage / (100 / PIXEL_SIZE);
}
/* TODO
*
* BUG: Ghosts get stuck!
* Try:
* start from every position, and make sure inky can get out.
*
* onomatopoeic sound effect?
* intro screen?
*
*/
enum bonus_symbol {
CHERRIES,
STRAWBERRY,
PEACH,
APPLE,
GRAPES,
GALAXIAN,
BELL,
KEY
};
struct level_constants {
enum bonus_symbol bonus_symbol;
uint32 pacman_speed, pacman_powerup_speed;
uint32 ghost_speed, ghost_tunnel_speed, ghost_frightened_speed;
uint32 num_ghost_flashes;
uint32 pacman_powerup_time;
uint32 elroy_v1_dots_left, elroy_v1_speed;
uint32 scatter_times[4];
uint32 chase_times[3];
uint32 ghost_dot_limits[NUM_GHOSTS];
};
inline internal uint32
seconds_to_frames(uint32 seconds) {
return SECONDS(seconds) / MS(16);
}
enum ghost_name {
BLINKY,
PINKY,
INKY,
CLYDE
};
uint32 symbol_points[] = {
100, 300, 500, 700, 1000, 2000, 3000, 5000
};
internal enum bonus_symbol
get_symbol_for_level(uint32 level) {
enum bonus_symbol level_symbols[] = {
CHERRIES, STRAWBERRY, PEACH, PEACH, APPLE, APPLE, GRAPES, GRAPES,
GALAXIAN, GALAXIAN, BELL, BELL
};
if (level < ARRAY_LENGTH(level_symbols)) {
return level_symbols[level];
} else {
return KEY;
}
}
internal void
set_level_constants(struct level_constants* level_constants, uint32 level) {
level_constants->bonus_symbol = get_symbol_for_level(level);
if (level == 0) {
level_constants->pacman_speed = get_speed(80);
level_constants->pacman_powerup_speed = get_speed(90);
} else if (level < 4 || level >= 20) {
level_constants->pacman_speed = get_speed(90);
level_constants->pacman_powerup_speed = get_speed(95);
} else {
level_constants->pacman_powerup_speed = level_constants->pacman_speed = get_speed(100);
}
if (level == 0) {
level_constants->ghost_speed = get_speed(75);
level_constants->ghost_tunnel_speed = get_speed(40);
level_constants->ghost_frightened_speed = get_speed(50);
} else if (level < 4) {
level_constants->ghost_speed = get_speed(85);
level_constants->ghost_tunnel_speed = get_speed(45);
level_constants->ghost_frightened_speed = get_speed(55);
} else {
level_constants->ghost_speed = get_speed(95);
level_constants->ghost_tunnel_speed = get_speed(50);
level_constants->ghost_frightened_speed = get_speed(60);
}
if ((level != 8 && level <= 10) || level == 14) {
level_constants->num_ghost_flashes = 5;
} else {
level_constants->num_ghost_flashes = 3;
}
uint32 pacman_powerup_times[] = {
6, 5, 4, 3, 2, 5, 2, 2, 1, 5, 2, 1, 1, 3, 1, 1, 0, 1
};
if (level == 16 || level >= ARRAY_LENGTH(pacman_powerup_times)) {
level_constants->pacman_powerup_time = 1;
} else {
level_constants->pacman_powerup_time = seconds_to_frames(pacman_powerup_times[level]);
}
level_constants->elroy_v1_speed = get_speed(100);
if (level == 0) {
level_constants->elroy_v1_dots_left = 20;
level_constants->elroy_v1_speed = get_speed(80);
} else if (level == 1) {
level_constants->elroy_v1_dots_left = 30;
level_constants->elroy_v1_speed = get_speed(90);
} else if (level < 5) {
level_constants->elroy_v1_dots_left = 40;
} else if (level < 8) {
level_constants->elroy_v1_dots_left = 50;
} else if (level < 11) {
level_constants->elroy_v1_dots_left = 60;
} else if (level < 14) {
level_constants->elroy_v1_dots_left = 80;
} else if (level < 18) {
level_constants->elroy_v1_dots_left = 100;
} else {
level_constants->elroy_v1_dots_left = 120;
}
level_constants->chase_times[0] = level_constants->chase_times[1] = seconds_to_frames(20);
if (level == 0) {
level_constants->scatter_times[0] = level_constants->scatter_times[1] = seconds_to_frames(7);
level_constants->scatter_times[2] = level_constants->scatter_times[3] = seconds_to_frames(5);
level_constants->chase_times[2] = seconds_to_frames(20);
} else if (level < 4) {
level_constants->scatter_times[0] = level_constants->scatter_times[1] = seconds_to_frames(7);
level_constants->scatter_times[2] = seconds_to_frames(5);
level_constants->scatter_times[3] = 1;
level_constants->chase_times[2] = seconds_to_frames(1033);
} else {
level_constants->scatter_times[0] = level_constants->scatter_times[1] = level_constants->scatter_times[2] = seconds_to_frames(5);
level_constants->scatter_times[3] = 1;
level_constants->chase_times[2] = seconds_to_frames(1037);
}
level_constants->ghost_dot_limits[BLINKY] = 0;
level_constants->ghost_dot_limits[PINKY] = 0;
if (level == 0) {
level_constants->ghost_dot_limits[INKY] = 30;
level_constants->ghost_dot_limits[CLYDE] = 60;
} else if (level == 1) {
level_constants->ghost_dot_limits[INKY] = 0;
level_constants->ghost_dot_limits[CLYDE] = 50;
} else {
level_constants->ghost_dot_limits[INKY] = 0;
level_constants->ghost_dot_limits[CLYDE] = 0;
}
}
enum ghost_house_state {
EXITING_GHOST_HOUSE,
OUTSIDE_GHOST_HOUSE,
ENTERING_GHOST_HOUSE,
IN_GHOST_HOUSE,
};
struct ghost {
struct v2 pos;
enum dir dir;
struct v2 last_tile;
bool32 is_path_chosen;
enum dir chosen_dir;
struct v2 target_tile;
enum ghost_mode mode;
enum ghost_name name;
char nickname;
int curses_color_pair;
enum ghost_house_state ghost_house_state;
uint32 dot_counter;
bool32 bounce_target_bottom;
};
enum game_mode {
PAUSED_BEFORE_PLAYING,
PLAYING,
LOSING_A_LIFE,
GAME_OVER,
LEVEL_TRANSITION,
};
#define TUNNEL_WIDTH (4*TILE_SIZE)
#define GHOST_FLASH_TIME 10
internal void
init_ghost(struct ghost* ghost_in, char nickname, enum ghost_name name, int curses_color_pair) {
struct ghost ghost;
memset(&ghost, 0, sizeof(ghost));
ghost.nickname = nickname;
ghost.name = name;
ghost.curses_color_pair = curses_color_pair;
*ghost_in = ghost;
}
internal void
reverse_ghosts(struct ghost* ghosts) {
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
ghosts[i].is_path_chosen = TRUE;
if (ghosts[i].mode == NORMAL && ghosts[i].ghost_house_state == OUTSIDE_GHOST_HOUSE) {
switch (ghosts[i].dir) {
case UP:
ghosts[i].chosen_dir = DOWN;
break;
case DOWN:
ghosts[i].chosen_dir = UP;
break;
case LEFT:
ghosts[i].chosen_dir = RIGHT;
break;
case RIGHT:
ghosts[i].chosen_dir = LEFT;
break;
}
}
}
}
const
enum dir ghost_start_dirs[NUM_GHOSTS] = {
LEFT, UP, UP, UP
};
#define NUM_DOTS 244
struct level_data {
struct ghost* ghost_to_be_eaten;
uint32 pacman_chomp_timer, pacman_eat_timer,
ghost_eat_timer, pacman_powerup_timer,
ghost_mode_timer, fruit_timer,
ghost_flash_timer, num_ghost_mode_cycles,
fruit_score_timer, consecutive_ghosts_eaten,
extra_life_timer;
uint32 fright_ghost_seed;
uint32 global_ghost_house_dot_counter;
bool32 fruit_is_visible, pacman_blocked, pacman_turning, is_chasing, use_global_ghost_house_dot_counter;
uint32 dots_eaten;
struct v2 pacman_pos;
enum dir pacman_dir, next_dir;
uint32 dot_timer;
};
internal void
get_normal_target_tile(
struct level_data* level_data,
struct v2 scatter_targets[NUM_GHOSTS],
struct v2* blinky_pos, struct ghost* ghost, struct v2 ghost_tile) {
struct v2 pacman_tile = pos_to_tile(&level_data->pacman_pos);
/* Chase after a target */
if (level_data->is_chasing) {
switch (ghost->name) {
case BLINKY: {
/* Follow pacman */
ghost->target_tile = pacman_tile;
} break;
case PINKY: {
/* Follow pacmans direction of motion. */
struct v2 target = pacman_tile;
move_in_dir(level_data->pacman_dir, &target, 4);
ghost->target_tile = target;
} break;
case INKY: {
/* This gets close sometimes, but usually runs away. */
struct v2 blinky_tile = pos_to_tile(blinky_pos);
struct v2 target = pacman_tile;
move_in_dir(level_data->pacman_dir, &target, 2);
struct v2 delta = sub(target, blinky_tile);
target = add(target, delta);
ghost->target_tile = target;
} break;
case CLYDE: {
struct v2 target = pacman_tile;
struct v2 delta = sub(ghost_tile, target);
int dist_square = DIST_SQUARE(delta.x, delta.y);
/* Follow pacman from a distance */
if (dist_square >= SQUARE(8)) {
ghost->target_tile = target;
} else {
/* Scatter if too close. */
ghost->target_tile = scatter_targets[CLYDE];
}
} break;
}
} else {
ghost->target_tile = scatter_targets[ghost->name];
}
}
internal void
return_ghosts_and_pacman_to_start_position(struct ghost ghosts[NUM_GHOSTS], struct level_data* level_data) {
struct v2 pacman_start_pos = { ARENA_WIDTH / 2, (ARENA_HEIGHT_IN_TILES - 8)*TILE_SIZE + TILE_CENTER.y };
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
struct v2 start_position;
switch (i) {
case BLINKY: {
struct v2 blinky_start_pos_s = { ARENA_WIDTH / 2 - 2*PIXEL_SIZE, 11*TILE_SIZE + TILE_CENTER.y };
start_position = blinky_start_pos_s;
} break;
case INKY:
start_position = add(TILE_CENTER, scale(bottom_house_targets[INKY], TILE_SIZE));
break;
case PINKY:
start_position = add(TILE_CENTER, scale(bottom_house_targets[PINKY], TILE_SIZE));
break;
case CLYDE:
start_position = add(TILE_CENTER, scale(bottom_house_targets[CLYDE], TILE_SIZE));
break;
}
ghosts[i].pos = start_position;
ghosts[i].last_tile = pos_to_tile(&ghosts[i].pos);
ghosts[i].dir = ghost_start_dirs[i];
ghosts[i].is_path_chosen = FALSE;
ghosts[i].mode = NORMAL;
ghosts[i].bounce_target_bottom = FALSE;
if (i == BLINKY) {
ghosts[i].ghost_house_state = OUTSIDE_GHOST_HOUSE;
} else {
ghosts[i].ghost_house_state = IN_GHOST_HOUSE;
}
}
level_data->pacman_pos = pacman_start_pos;
level_data->pacman_dir = LEFT;
level_data->pacman_blocked = FALSE;
level_data->pacman_turning = FALSE;
level_data->next_dir = level_data->pacman_dir;
level_data->dot_timer = 0;
}
internal void
start_new_level(struct ghost ghosts[NUM_GHOSTS], struct level_data* level_data, uint32 original_fright_ghost_seed) {
int ghost;
for (ghost = 0; ghost < NUM_GHOSTS; ++ghost) {
ghosts[ghost].dot_counter = 0;
}
int row, col;
for (row = 0; row < ARENA_HEIGHT_IN_TILES; ++ row) {
for (col = 0; col < ARENA_WIDTH_IN_TILES; ++ col) {
char ch = dot_placement_map[row*ARENA_WIDTH_IN_TILES + col];
if (ch == '.' || ch == '*') {
dot_map[row][col] = ch;
} else {
dot_map[row][col] = '\0';
}
}
}
memset(level_data, 0, sizeof(struct level_data));
level_data->fright_ghost_seed = original_fright_ghost_seed;
level_data->ghost_mode_timer = 0;
return_ghosts_and_pacman_to_start_position(ghosts, level_data);
}
struct game_data {
uint32 num_extra_lives;
uint32 current_level;
uint32 score;
uint32 original_fright_ghost_seed;
};
internal void
start_new_game(struct game_data* game_data, struct ghost ghosts[NUM_GHOSTS], struct level_data* level_data, struct level_constants* level_constants) {
game_data->num_extra_lives = 2;
game_data->current_level = 0;
game_data->score = 0;
game_data->original_fright_ghost_seed = time(NULL);
set_level_constants(level_constants, game_data->current_level);
init_ghost(&ghosts[BLINKY], 'B', BLINKY, BLINKY_PAIR);
init_ghost(&ghosts[INKY], 'I', INKY, INKY_PAIR);
init_ghost(&ghosts[PINKY], 'P', PINKY, PINKY_PAIR);
init_ghost(&ghosts[CLYDE], 'C', CLYDE, CLYDE_PAIR);
start_new_level(ghosts, level_data, game_data->original_fright_ghost_seed);
}
internal void
enable_bonus_symbol_color(enum bonus_symbol bonus_symbol) {
attron(COLOR_PAIR(bonus_symbol + CHERRIES_PAIR));
}
internal void
draw_fruit(struct v2 left_tile, enum bonus_symbol bonus_symbol, struct view* view) {
const char* bonus_strs[] = {
"^00",
"<0'",
"(0)",
"`0)",
"`88",
"|^|",
"3>-",
"3-^",
};
enable_bonus_symbol_color(bonus_symbol);
struct v2 pos = draw_pos_v2(left_tile, view);
mvprintw(pos.y, pos.x, bonus_strs[bonus_symbol]);
}
internal uint32
get_ghost_score(uint32 ghosts_eaten) {
return (2 << ghosts_eaten) * 100;
}
internal void
add_extra_life(struct game_data* game_data, struct level_data* level_data) {
level_data->extra_life_timer = seconds_to_frames(1);
if (game_data->num_extra_lives < 5) {
++game_data->num_extra_lives;
}
}
int main(int argc, char** argv) {
UNUSED(argc);
UNUSED(argv);
/* Initialize curses */
initscr();
cbreak();
keypad(stdscr, TRUE);
noecho();
curs_set(FALSE);
timeout(0);
enum game_mode game_mode = GAME_OVER;
uint32 transition_timer = 0;
bool32 running = TRUE;
uint32 last_update = time_in_us();
uint32 frame_timer = 0;
uint32 high_score = 0;
bool32 has_color_terminal = has_colors() && can_change_color();
start_color();
if (!has_color_terminal || COLORS != 256) {
mvprintw(0, 0, "Warning: Colors not enabled for this terminal.\nAuthors suggestion: try running in the unicode-rxvt terminal.\nPress any key to continue.");
mvprintw(0, 0, "%u colors available in this terminal. This game currently supports 256.", COLORS);
timeout(-1);
getch();
timeout(0);
} else {
init_pair(BG_PAIR, COLOR_WHITE, COLOR_DARK_BACKGROUND);
int bkgd_color = COLOR_GREY;
init_pair(EMPTY_PAIR, COLOR_WHITE, bkgd_color);
init_pair(WALL_PAIR, COLOR_WALL_BLUE, COLOR_DARK_BACKGROUND);
init_pair(DOOR_PAIR, COLOR_DOOR_RED, COLOR_DARK_BACKGROUND);
init_pair(PACMAN_PAIR, COLOR_PACMAN_YELLOW, bkgd_color);
init_pair(DOT_PAIR, COLOR_WHITE, bkgd_color);
init_pair(GAME_OVER_TEXT_PAIR, COLOR_DOOR_RED, bkgd_color);
init_pair(BLINKY_PAIR, COLOR_WHITE, COLOR_DOOR_RED);
init_pair(INKY_PAIR, COLOR_WHITE, COLOR_LIGHT_BLUE);
init_pair(PINKY_PAIR, COLOR_WHITE, COLOR_PINK);
init_pair(CLYDE_PAIR, COLOR_WHITE, COLOR_ORANGE);
init_pair(FRIGHT_PAIR, COLOR_WHITE, COLOR_WALL_BLUE);
init_pair(FRIGHT_FLASH_PAIR, COLOR_WALL_BLUE, COLOR_WHITE);
init_pair(EYES_PAIR, COLOR_WHITE, bkgd_color);
init_pair(CHERRIES_PAIR, COLOR_DARK_RED, bkgd_color);
init_pair(STRAWBERRY_PAIR, COLOR_BRIGHT_RED, bkgd_color);
init_pair(PEACH_PAIR, COLOR_ORANGE, bkgd_color);
init_pair(APPLE_PAIR, COLOR_DOOR_RED, bkgd_color);
init_pair(GRAPES_PAIR, COLOR_PURPLE, bkgd_color);
init_pair(GALAXIAN_PAIR, COLOR_ORANGE, bkgd_color);
init_pair(BELL_PAIR, COLOR_PACMAN_YELLOW, bkgd_color);
init_pair(KEY_PAIR, COLOR_LIGHT_BLUE, bkgd_color);
bkgd(COLOR_PAIR(BG_PAIR));
}
bool32 debug_ghost_mode_overridden = FALSE, debug_no_death_mode = FALSE;
struct view view = { 0 };
struct ghost ghosts[NUM_GHOSTS];
struct level_data level_data;
struct level_constants level_constants;
struct game_data game_data;
start_new_game(&game_data, ghosts, &level_data, &level_constants);
struct v2 pacman_turn_tile;
uint32 frame_times[] = { 8, 16, 50, 100, 150 };
int frame_time_index = 1;
while (running) {
/* Get Input */
int ch;
while ((ch = getch()) != -1) {
switch (ch) {
case 'q':
case 'Q':
running = FALSE;
continue;
#ifdef DEBUG
case 'j':
case 'J':
--frame_time_index;
if (frame_time_index < 0) {
frame_time_index = ARRAY_LENGTH(frame_times) - 1;
}
break;
case 'k':
case 'K':
++frame_time_index;
if (frame_time_index == ARRAY_LENGTH(frame_times)) {
frame_time_index = 0;
}
break;
case 'z':
case 'Z':
view.zoom_view = !view.zoom_view;
break;
case 'e':
case 'E': {
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
if (ghosts[i].ghost_house_state == OUTSIDE_GHOST_HOUSE) {
ghosts[i].mode = EYES;
}
}
} break;
case 'f':
case 'F':
game_mode = LEVEL_TRANSITION;
transition_timer = 0;
break;
case 'l':
case 'L':
game_mode = LOSING_A_LIFE;
transition_timer = 0;
break;
case '+':
add_extra_life(&game_data, &level_data);
break;
case '-':
if (game_data.num_extra_lives > 0) {
--game_data.num_extra_lives;
}
break;
case 'c':
case 'C':
debug_ghost_mode_overridden = TRUE;
level_data.is_chasing = !level_data.is_chasing;
break;
case 'd':
case 'D':
debug_no_death_mode = !debug_no_death_mode;
break;
case 'r':
case 'R':
reverse_ghosts(ghosts);
break;
case 'p':
case 'P':
start_new_game(&game_data, ghosts, &level_data, &level_constants);
transition_timer = 0;
game_mode = PAUSED_BEFORE_PLAYING;
break;
#endif
}
if (game_mode == PLAYING && !level_data.pacman_turning) {
switch (ch) {
case KEY_LEFT:
level_data.next_dir = LEFT;
break;
case KEY_RIGHT:
level_data.next_dir = RIGHT;
break;
case KEY_UP:
level_data.next_dir = UP;
break;
case KEY_DOWN:
level_data.next_dir = DOWN;
break;
}
} else if (game_mode == GAME_OVER) {
start_new_game(&game_data, ghosts, &level_data, &level_constants);
transition_timer = 0;
game_mode = PAUSED_BEFORE_PLAYING;
}
}
frame_timer += time_in_us() - last_update;
last_update = time_in_us();
if (frame_timer > MS(frame_times[frame_time_index])) {
if (!level_data.ghost_eat_timer) {
++level_data.pacman_chomp_timer;
if (level_data.fruit_is_visible) {
++level_data.fruit_timer;
if (level_data.fruit_timer > seconds_to_frames(9)) {
level_data.fruit_is_visible = FALSE;
}
}
}
switch (game_mode) {
case PAUSED_BEFORE_PLAYING: {
transition_timer++;
if (transition_timer >= seconds_to_frames(2)) {
transition_timer = 0;
game_mode = PLAYING;
}
} break;
case PLAYING: {
uint32 pacman_speed;
uint32 old_score = game_data.score;
num_blocked_tiles = 0;
if (level_data.ghost_eat_timer) {
--level_data.ghost_eat_timer;
if (!level_data.ghost_eat_timer) {
level_data.ghost_to_be_eaten->mode = EYES;
level_data.ghost_to_be_eaten = NULL;
}
} else if (level_data.pacman_powerup_timer) {
--level_data.pacman_powerup_timer;
pacman_speed = level_constants.pacman_powerup_speed;
if (!level_data.pacman_powerup_timer) {
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
if (ghosts[i].mode != EYES) {
ghosts[i].mode = NORMAL;
}
}
} else if (level_data.pacman_powerup_timer <= level_constants.num_ghost_flashes * 2 * GHOST_FLASH_TIME) {
++level_data.ghost_flash_timer;
}
} else {
pacman_speed = level_constants.pacman_speed;
}
if (level_data.pacman_eat_timer) {
--level_data.pacman_eat_timer;
} else if (!level_data.ghost_eat_timer) {
if (level_data.pacman_dir != level_data.next_dir) {
if (is_h_dir(level_data.pacman_dir) == is_h_dir(level_data.next_dir)) {
level_data.pacman_dir = level_data.next_dir;
} else {
struct v2 blocked_pos_s = level_data.pacman_pos;
bool32 blocked = test_pacman_dir_blocked(level_data.next_dir, &blocked_pos_s);
if (blocked) {
blocked_tiles[num_blocked_tiles++] = pos_to_tile(&blocked_pos_s);
} else {
level_data.pacman_turning = TRUE;
pacman_turn_tile = pos_to_tile(&level_data.pacman_pos);
}
}
}
if (level_data.pacman_turning) {
move_in_dir(level_data.next_dir, &level_data.pacman_pos, pacman_speed);
/* Move toward the center of the turn_tile */
struct v2 tile_center = add(scale(pacman_turn_tile, TILE_SIZE), TILE_CENTER);
bool32 reached_centerline = FALSE;
if (is_h_dir(level_data.pacman_dir)) {
int mv_amt = tile_center.x - level_data.pacman_pos.x;
if (mv_amt < 0) {
level_data.pacman_pos.x -= MIN(-mv_amt, pacman_speed);
reached_centerline = -mv_amt < pacman_speed;
} else {
level_data.pacman_pos.x += MIN(mv_amt, pacman_speed);
reached_centerline = mv_amt < pacman_speed;
}
} else {
int mv_amt = tile_center.y - level_data.pacman_pos.y;
if (mv_amt < 0) {
level_data.pacman_pos.y -= MIN(-mv_amt, pacman_speed);
reached_centerline = -mv_amt < pacman_speed;
} else {
level_data.pacman_pos.y += MIN(mv_amt, pacman_speed);
reached_centerline = mv_amt < pacman_speed;
}
}
if (reached_centerline) {
level_data.pacman_turning = FALSE;
level_data.pacman_dir = level_data.next_dir;
}
} else {
struct v2 blocked_pos_s = level_data.pacman_pos;
level_data.pacman_blocked = test_pacman_dir_blocked(level_data.pacman_dir, &blocked_pos_s);
if (level_data.pacman_blocked) {
blocked_tiles[num_blocked_tiles++] = pos_to_tile(&blocked_pos_s);
} else {
move_in_dir(level_data.pacman_dir, &level_data.pacman_pos, pacman_speed);
if (level_data.pacman_pos.x < -TUNNEL_WIDTH / 2) {
level_data.pacman_pos.x += ARENA_WIDTH + TUNNEL_WIDTH;
} else if (level_data.pacman_pos.x >= ARENA_WIDTH + TUNNEL_WIDTH / 2) {
level_data.pacman_pos.x -= ARENA_WIDTH + TUNNEL_WIDTH;
}
}
}
}
{
struct v2 pacman_tile = pos_to_tile(&level_data.pacman_pos);
char ch = dot_map[pacman_tile.y][pacman_tile.x];
if (ch == '.') {
level_data.pacman_eat_timer = 1;
game_data.score += 10;
} else if (ch == '*') {
level_data.pacman_eat_timer = 3;
level_data.pacman_powerup_timer = level_constants.pacman_powerup_time;
level_data.consecutive_ghosts_eaten = 0;
level_data.ghost_flash_timer = 0;
reverse_ghosts(ghosts);
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
if (ghosts[i].mode != EYES) {
ghosts[i].mode = FRIGHTENED;
}
}
game_data.score += 50;
}
if (ch == '.' || ch == '*') {
uint32 fruit_dot_counter = 70;
level_data.dot_timer = 0;
++level_data.dots_eaten;
if (level_data.dots_eaten == fruit_dot_counter) {
level_data.fruit_is_visible = TRUE;
} else if (level_data.dots_eaten == NUM_DOTS) {
game_mode = LEVEL_TRANSITION;
transition_timer = 0;
}
if (level_data.use_global_ghost_house_dot_counter) {
++level_data.global_ghost_house_dot_counter;
} else {
int i;
for (i = PINKY; i < NUM_GHOSTS; ++i) {
if (ghosts[i].ghost_house_state == IN_GHOST_HOUSE) {
++ghosts[i].dot_counter;
}
}
}
} else {
++level_data.dot_timer;
}
dot_map[pacman_tile.y][pacman_tile.x] = 0;
}
if (level_data.num_ghost_mode_cycles < 4 && !level_data.pacman_powerup_timer && !debug_ghost_mode_overridden) {
++level_data.ghost_mode_timer;
if (!level_data.is_chasing && level_data.ghost_mode_timer > level_constants.scatter_times[level_data.num_ghost_mode_cycles]) {
/* Switch to chase mode */
level_data.is_chasing = TRUE;
level_data.ghost_mode_timer = 0;
++level_data.num_ghost_mode_cycles;
reverse_ghosts(ghosts);
} else if (level_data.is_chasing && level_data.ghost_mode_timer > level_constants.chase_times[level_data.num_ghost_mode_cycles]) {
/* Switch to scatter mode */
level_data.is_chasing = FALSE;
level_data.ghost_mode_timer = 0;
reverse_ghosts(ghosts);
}
}
{ /* Update all ghost positions */
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
struct ghost* ghost = &ghosts[i];
if (ghost->mode != EYES && level_data.ghost_eat_timer) {
continue;
}
uint32 speed;
struct v2 eyes_target_tile = { ARENA_WIDTH_IN_TILES / 2 - 1, 11 };
struct v2 eyes_target_pos = add(scale(eyes_target_tile, TILE_SIZE), TILE_CENTER);
if (ghost->ghost_house_state == EXITING_GHOST_HOUSE) {
if (ghost->pos.x != eyes_target_pos.x) {
ghost->dir = ghost->pos.x < eyes_target_pos.x ? RIGHT : LEFT;
} else {
ghost->dir = UP;
}
}
{
uint32 ghost_eyes_speed = get_speed(175);
uint32 elroy_v2_speed = level_constants.elroy_v1_speed + get_speed(5);
uint32 elroy_v2_dots_left = level_constants.elroy_v1_dots_left / 2;
struct v2 ghost_tile = pos_to_tile(&ghost->pos);
/* If the ghost is inside the tunnel, slow down. */
bool32 is_in_tunnel = ghost_tile.y == 14 && (ghost_tile.x <= 5 || ghost_tile.x >= ARENA_WIDTH_IN_TILES - 5);
if (ghost->mode == EYES) {
speed = ghost_eyes_speed;
} else if (ghost->ghost_house_state != OUTSIDE_GHOST_HOUSE) {
speed = level_constants.ghost_tunnel_speed;
} else if (is_in_tunnel) {
speed = level_constants.ghost_tunnel_speed;
} else if (ghost->mode == FRIGHTENED) {
speed = level_constants.ghost_frightened_speed;
} else if (i == BLINKY && NUM_DOTS - level_data.dots_eaten <= level_constants.elroy_v1_dots_left) {
/* Oh he mad now beotch. */
speed = level_constants.elroy_v1_speed;
} else if (i == BLINKY && NUM_DOTS - level_data.dots_eaten <= elroy_v2_dots_left) {
/* Cruise Elroy */
speed = elroy_v2_speed;
} else {
speed = level_constants.ghost_speed;
}
if (ghost->ghost_house_state == EXITING_GHOST_HOUSE) {
int offset = is_h_dir(ghost->dir) ?
eyes_target_pos.x - ghost->pos.x :
eyes_target_pos.y - ghost->pos.y
;
if (offset < 0) {
offset = -offset;
}
speed = MIN(offset, speed);
} else if (!eql(&ghost_tile, &ghost->last_tile)) {
struct v2 tile_center = add(scale(ghost_tile, TILE_SIZE), TILE_CENTER);
bool32 making_a_90_degree_turn = ghost->is_path_chosen && is_h_dir(ghost->chosen_dir) != is_h_dir(ghost->dir);
if (making_a_90_degree_turn) {
int offset = is_h_dir(ghost->dir) ?
tile_center.x - ghost->pos.x :
tile_center.y - ghost->pos.y
;
if (offset < 0) {
offset = -offset;
}
speed = MIN(offset, speed);
}
}
}
move_in_dir(ghost->dir, &ghost->pos, speed);
if (ghost->pos.x < -TUNNEL_WIDTH / 2) {
ghost->pos.x += ARENA_WIDTH + TUNNEL_WIDTH;
} else if (ghost->pos.x >= ARENA_WIDTH + TUNNEL_WIDTH / 2) {
ghost->pos.x -= ARENA_WIDTH + TUNNEL_WIDTH;
}
struct v2 ghost_tile = pos_to_tile(&ghost->pos);
if (ghost->ghost_house_state == EXITING_GHOST_HOUSE) {
struct v2 just_below_eyes_tile = eyes_target_tile;
--just_below_eyes_tile.y;
if (eql(&eyes_target_pos, &ghost->pos)) {
ghost->ghost_house_state = OUTSIDE_GHOST_HOUSE;
} else if (!eql(&ghost_tile, &just_below_eyes_tile)) {
continue;
}
}
if (!eql(&ghost_tile, &ghost->last_tile)) {
/* CLEANUP: merge with other movement to center */
struct v2 tile_center = add(scale(ghost_tile, TILE_SIZE), TILE_CENTER);
bool32 made_it_to_center;
switch (ghost->dir) {
case UP:
made_it_to_center = ghost->pos.y <= tile_center.y;
break;
case DOWN:
made_it_to_center = ghost->pos.y >= tile_center.y;
break;
case LEFT:
made_it_to_center = ghost->pos.x <= tile_center.x;
break;
case RIGHT:
made_it_to_center = ghost->pos.x >= tile_center.x;
break;
}
if (!made_it_to_center) {
continue;
}
ghost->last_tile = ghost_tile;
if (ghost->is_path_chosen) {
ghost->dir = ghost->chosen_dir;
ghost->is_path_chosen = FALSE;
}
move_in_dir(ghost->dir, &ghost_tile, 1);
enum dir paths[NUM_DIRS];
int num_paths = 0;
/* Get all paths from the next tile. */
{
int dir;
for (dir = 0; dir < NUM_DIRS; ++dir) {
/* Prevent reversal */
if ((is_h_dir(dir) == is_h_dir(ghost->dir) && dir != ghost->dir)) {
continue;
}
/* Only horizontal dirs when leaving the ghost house */
if (ghost->ghost_house_state == EXITING_GHOST_HOUSE && !is_h_dir(dir)) {
continue;
}
struct v2 tile = ghost_tile;
move_in_dir(dir, &tile, 1);
bool32 ghost_is_blocked = !!strchr(
ghost->mode == EYES ?
"/`[]-|+" : "/`[]-|+_",
arena_get(tile.y, tile.x));
if (ghost_is_blocked) {
blocked_tiles[num_blocked_tiles++] = tile;
} else {
/* Don't go through any of the 'forbidden tiles'. */
if (dir == UP && ghost->mode == NORMAL) {
int i;
bool32 found = FALSE;
for_array(i, forbidden_upward_tiles) {
if (eql(&forbidden_upward_tiles[i], &tile)) {
found = TRUE;
break;
}
}
if (found) {
continue;
}
}
paths[num_paths++] = dir;
}
}
}
assert(num_paths >= 1);
int best_choice;
struct v2 scatter_targets[NUM_GHOSTS] = {
{ ARENA_WIDTH_IN_TILES - 3, -3 },
{ 2, -3 },
{ ARENA_WIDTH_IN_TILES, ARENA_HEIGHT_IN_TILES },
{ 0, ARENA_HEIGHT_IN_TILES },
};
if (ghost->mode == FRIGHTENED && ghost->ghost_house_state == OUTSIDE_GHOST_HOUSE) {
/* Take pseudo-random turns. */
int choice = rand_r(&level_data.fright_ghost_seed) % NUM_DIRS;
best_choice = -1;
while (best_choice == -1) {
int i;
for (i = 0; i < num_paths; ++i) {
if (paths[i] == choice) {
best_choice = choice;
break;
}
}
if (best_choice == -1) {
--choice;
if (choice < 0) {
choice = NUM_DIRS - 1;
}
}
}
} else {
/* Chase after a target */
if (ghost->ghost_house_state == IN_GHOST_HOUSE) {
/* BOUNCING, alternate targets. */
uint32 ghost_global_dot_limits[] = { 0, 7, 17, 32 };
if (ghost_tile.y >= 13 && ghost->mode == EYES) {
ghost->mode = NORMAL;
}
enum ghost_name next_ghost_released;
for (next_ghost_released = PINKY; next_ghost_released < NUM_GHOSTS; ++next_ghost_released) {
if (ghosts[next_ghost_released].ghost_house_state == IN_GHOST_HOUSE) {
break;
}
}
if (ghost->name == BLINKY ||
(ghost->name == next_ghost_released && (
level_data.dot_timer >= seconds_to_frames(4) ||
(level_data.use_global_ghost_house_dot_counter && level_data.global_ghost_house_dot_counter >= ghost_global_dot_limits[i]) ||
(!level_data.use_global_ghost_house_dot_counter && ghost->dot_counter >= level_constants.ghost_dot_limits[i])))) {
if (ghost->name != BLINKY) {
level_data.dot_timer = 0;
}
ghost->target_tile = top_house_targets[PINKY];
ghost->ghost_house_state = EXITING_GHOST_HOUSE;
} else if (ghost->bounce_target_bottom) {
if (eql(&ghost_tile, &bottom_house_targets[i])) {
ghost->bounce_target_bottom = !ghost->bounce_target_bottom;
ghost->target_tile = top_house_targets[i];
} else {
ghost->target_tile = bottom_house_targets[i];
}
} else {
if (eql(&ghost_tile, &top_house_targets[i])) {
ghost->bounce_target_bottom = !ghost->bounce_target_bottom;
ghost->target_tile = bottom_house_targets[i];
} else {
ghost->target_tile = top_house_targets[i];
}
}
} else if (ghost->mode == NORMAL) {
get_normal_target_tile(&level_data, scatter_targets, &ghosts[BLINKY].pos, ghost, ghost_tile);
} else if (ghost->mode == EYES) {
/* Target the door. */
struct v2 home_target = { ARENA_WIDTH_IN_TILES / 2 - 1, 13 };
if (eql(&ghost_tile, &eyes_target_tile)) {
ghost->ghost_house_state = ENTERING_GHOST_HOUSE;
ghost->target_tile = home_target;
} else if (ghost->ghost_house_state == ENTERING_GHOST_HOUSE) {
if (!eql(&ghost_tile, &home_target)) {
ghost->target_tile = home_target;
} else {
ghost->ghost_house_state = IN_GHOST_HOUSE;
ghost->target_tile = home_target;
++ghost->target_tile.y;
}
} else {
ghost->target_tile = eyes_target_tile;
}
}
int i, best_path = 0, best_distance = -1;
for (i = 0; i < num_paths; ++i) {
struct v2 tile = ghost_tile;
move_in_dir(paths[i], &tile, 1);
int distance = DIST_SQUARE(tile.x - ghost->target_tile.x, tile.y - ghost->target_tile.y);
if (best_distance == -1 || distance < best_distance) {
best_distance = distance;
best_path = i;
}
}
best_choice = paths[best_path];
}
ghost->is_path_chosen = TRUE;
ghost->chosen_dir = best_choice;
}
}
}
{
struct v2 pacman_tile = pos_to_tile(&level_data.pacman_pos);
if (level_data.fruit_is_visible && (eql(&pacman_tile, &fruit_tile))) {
level_data.fruit_is_visible = FALSE;
level_data.fruit_score_timer = seconds_to_frames(2);
game_data.score += symbol_points[level_constants.bonus_symbol];
}
/* Check for pacman/ghost collisions */
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
struct v2 ghost_tile = pos_to_tile(&ghosts[i].pos);
if (eql(&ghost_tile, &pacman_tile)) {
if (ghosts[i].mode == FRIGHTENED && !level_data.ghost_to_be_eaten) {
uint32 ghost_eat_time = seconds_to_frames(1);
level_data.ghost_eat_timer = ghost_eat_time;
game_data.score += get_ghost_score(level_data.consecutive_ghosts_eaten++);
level_data.ghost_to_be_eaten = &ghosts[i];
} else if (ghosts[i].mode == NORMAL && !debug_no_death_mode) {
game_mode = LOSING_A_LIFE;
level_data.extra_life_timer = 0;
level_data.ghost_eat_timer = 0;
transition_timer = 0;
}
}
}
}
high_score = MAX(game_data.score, high_score);
if (old_score / 10000 != game_data.score / 10000) {
add_extra_life(&game_data, &level_data);
}
if (level_data.extra_life_timer) {
--level_data.extra_life_timer;
}
} break;
case GAME_OVER: {
} break;
case LEVEL_TRANSITION: {
++transition_timer;
if (transition_timer > 30) {
set_level_constants(&level_constants, ++game_data.current_level);
start_new_level(ghosts, &level_data, game_data.original_fright_ghost_seed);
transition_timer = 0;
game_mode = PAUSED_BEFORE_PLAYING;
}
} break;
case LOSING_A_LIFE: {
++transition_timer;
switch (transition_timer) {
/* Spin in a circle */
case LIFE_LOST_PAUSE_TIME + LIFE_LOST_TURN_TIME*1:
case LIFE_LOST_PAUSE_TIME + LIFE_LOST_TURN_TIME*2:
case LIFE_LOST_PAUSE_TIME + LIFE_LOST_TURN_TIME*3:
case LIFE_LOST_PAUSE_TIME + LIFE_LOST_TURN_TIME*4:
--level_data.pacman_dir;
if (level_data.pacman_dir == -1) {
level_data.pacman_dir = NUM_DIRS - 1;
}
break;
/* Restart the level */
case LIFE_LOST_PAUSE_TIME + LIFE_LOST_TURN_TIME*8:
if (game_data.num_extra_lives) {
--game_data.num_extra_lives;
transition_timer = 0;
game_mode = PAUSED_BEFORE_PLAYING;
return_ghosts_and_pacman_to_start_position(ghosts, &level_data);
level_data.use_global_ghost_house_dot_counter = TRUE;
level_data.global_ghost_house_dot_counter = 0;
level_data.ghost_mode_timer = 0;
level_data.num_ghost_mode_cycles = 0;
level_data.is_chasing = FALSE;
} else {
game_mode = GAME_OVER;
}
break;
}
} break;
}
erase();
view.left = 2;
view.top = 3;
if (view.zoom_view) {
struct v2 offset = { -8, -2 };
view.camera_target_tile = add(offset, pos_to_tile(&ghosts[INKY].pos));
} else {
struct v2 zero = { 0 };
view.camera_target_tile = zero;
}
if (level_data.extra_life_timer && level_data.extra_life_timer / 10 % 2 == 0) {
init_pair(WALL_PAIR, COLOR_LIGHT_BLUE, COLOR_DARK_BACKGROUND);
} else {
init_pair(WALL_PAIR, COLOR_WALL_BLUE, COLOR_DARK_BACKGROUND);
}
{ /* Draw Arena */
int row, col;
for (row = 0; row < ARENA_HEIGHT_IN_TILES; ++row) {
for (col = 0; col < ARENA_WIDTH_IN_TILES; ++col) {
char ch = arena_get(row, col), draw_ch = ch, fill_ch = ' ';
switch (ch) {
case '`':
case '[':
case ']':
case '/':
draw_ch = '+';
attron(COLOR_PAIR(WALL_PAIR));
break;
case 'x':
draw_ch = ' ';
attron(COLOR_PAIR(WALL_PAIR));
break;
case ' ':
attron(COLOR_PAIR(EMPTY_PAIR));
break;
case '_':
attron(COLOR_PAIR(DOOR_PAIR));
fill_ch = ch;
break;
case '-':
case '|':
case '+':
attron(COLOR_PAIR(WALL_PAIR));
break;
}
if (ch == '-') {
fill_ch = ch;
}
draw_tile(row, col, draw_ch, &view, fill_ch);
}
}
}
if (level_data.fruit_is_visible) {
draw_fruit(fruit_tile, level_constants.bonus_symbol, &view);
} else if (level_data.fruit_score_timer) {
enable_bonus_symbol_color(level_constants.bonus_symbol);
--level_data.fruit_score_timer;
struct v2 pos = draw_pos_v2(fruit_tile, &view);
mvprintw(pos.y, pos.x, "%u", symbol_points[level_constants.bonus_symbol]);
}
{
int i;
int start_level = game_data.current_level - 6;
start_level = MAX(0, start_level);
for (i = start_level; i < game_data.current_level + 1; ++i) {
struct v2 tile = { ARENA_WIDTH_IN_TILES - 2*(i+1 - start_level), ARENA_HEIGHT_IN_TILES };
draw_fruit(tile, get_symbol_for_level(i), &view);
}
}
{
attron(COLOR_PAIR(DOT_PAIR));
int row, col;
for (row = 0; row < ARENA_HEIGHT_IN_TILES; ++row) {
for (col = 0; col < ARENA_WIDTH_IN_TILES; ++col) {
char ch = dot_map[row][col];
if (ch == '.' || ch == '*') {
draw_tile(row, col, ch, &view, ' ');
}
}
}
}
attron(COLOR_PAIR(PACMAN_PAIR));
char pacman_char = 'O';
if (game_mode != PLAYING || level_data.pacman_blocked || level_data.pacman_chomp_timer / 3 % 3 != 0) {
switch (level_data.pacman_dir) {
case RIGHT:
pacman_char = '<';
break;
case LEFT:
pacman_char = '>';
break;
case UP:
pacman_char = 'Y';
break;
case DOWN:
pacman_char = '^';
break;
}
}
{
int i;
for (i = 0; i < game_data.num_extra_lives; ++i) {
draw_tile(ARENA_HEIGHT_IN_TILES, i + 1, '<', &view, ' ');
}
}
struct v2 pacman_tile = pos_to_tile(&level_data.pacman_pos);
if (!level_data.ghost_eat_timer) {
if (pacman_tile.x >= 0 && pacman_tile.x < ARENA_WIDTH_IN_TILES) {
draw_tile_v2(pacman_tile, pacman_char, &view, ' ');
}
}
#if 0
{
attron(COLOR_PAIR(DOOR_PAIR));
int i;
for (i = 0; i < num_blocked_tiles; ++i) {
draw_tile_v2(blocked_tiles[i], 'X', &view);
}
for_array(i, forbidden_upward_tiles) {
draw_tile_v2(forbidden_upward_tiles[i], 'X', &view);
}
}
#endif
if (game_mode != LOSING_A_LIFE && game_mode != GAME_OVER) {
int i;
for (i = 0; i < NUM_GHOSTS; ++i) {
if (level_data.ghost_eat_timer && &ghosts[i] == level_data.ghost_to_be_eaten) {
continue;
}
if (ghosts[i].mode == FRIGHTENED) {
if (level_data.ghost_flash_timer / GHOST_FLASH_TIME % 2 == 0) {
attron(COLOR_PAIR(FRIGHT_PAIR));
} else {
attron(COLOR_PAIR(FRIGHT_FLASH_PAIR));
}
} else if (ghosts[i].mode == EYES) {
attron(COLOR_PAIR(EYES_PAIR));
} else {
attron(COLOR_PAIR(ghosts[i].curses_color_pair));
}
struct v2 ghost_tile = pos_to_tile(&ghosts[i].pos);
if (ghost_tile.x >= 0 && ghost_tile.x < ARENA_WIDTH_IN_TILES) {
draw_tile_v2(ghost_tile, 'm', &view, ' ');
}
#if 0
/* Draw Target Tile */
attron(COLOR_PAIR(DOT_PAIR));
draw_tile_v2(ghosts[i].target_tile, ghosts[i].nickname, &view);
#endif
}
}
if (level_data.ghost_eat_timer) {
attron(COLOR_PAIR(KEY_PAIR));
struct v2 pos = draw_pos_v2(pacman_tile, &view);
mvprintw(pos.y, pos.x, "%d", get_ghost_score(level_data.consecutive_ghosts_eaten - 1));
}
if (game_mode == GAME_OVER) {
attron(COLOR_PAIR(GAME_OVER_TEXT_PAIR));
struct v2 pos = draw_pos(HOUSE_BOTTOM + 2, HOUSE_LEFT, &view);
mvprintw(pos.y, pos.x, "G A M E O V E R");
}
if (view.zoom_view) {
attron(COLOR_PAIR(PACMAN_PAIR));
draw_tile_v2(pacman_tile, 'X', &view, ' ');
if (level_data.pacman_turning) {
attron(COLOR_PAIR(WALL_PAIR));
draw_tile_v2(pacman_turn_tile, 'O', &view, ' ');
}
attron(COLOR_PAIR(BG_PAIR));
mvaddch(
ghosts[INKY].pos.y / PIXEL_SIZE + view.top - view.camera_target_tile.y * TILE_SIZE_IN_PIXELS,
ghosts[INKY].pos.x / PIXEL_SIZE + view.left - view.camera_target_tile.x * TILE_SIZE_IN_PIXELS, 'O');
}
attron(COLOR_PAIR(BG_PAIR));
mvprintw(view.top - 2, view.left + 2*3, "1 U P");
mvprintw(view.top - 1, view.left + 2*3, "%7d", game_data.score);
mvprintw(view.top - 2, (HOUSE_CENTER - 2)*3, "H I G H S C O R E");
mvprintw(view.top - 1, (HOUSE_CENTER - 1)*3, "%7d", game_data.score);
#if 0
attron(COLOR_PAIR(BG_PAIR));
int diag_row = 0;
mvprintw(diag_row++, 0, "Score: %06d, High Score: %06d", game_data.score, high_score);
mvprintw(diag_row++, 0, "Dots Eaten: %d", level_data.dots_eaten);
mvprintw(diag_row++, 0, "Current Level %u%s", game_data.current_level, debug_no_death_mode ? ", GOD-MODE" : "");
#endif
refresh();
frame_timer = 0;
}
usleep(1);
}
endwin();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment