Created
June 30, 2018 18:19
-
-
Save chebert/f71a8694f2815061d705f610329e2a5b to your computer and use it in GitHub Desktop.
Pacman in C using NCURSES
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
map ,g :!clang -DDEBUG % -o %:r -lncurses && ./%:r <CR> | |
gcc pacman.c -o pacman -lncurses | |
./pacman | |
*/ | |
/* | |
* TODO | |
* minor: ghosts should go all the way to their bottom positions (TILE_CENTER) | |
* | |
* ghosts bounce around after being eaten *sometimes*. don't know what | |
* conditions. | |
* | |
* Start screen for 1 or two player | |
* show ghosts being eaten. | |
* | |
* TODO: determine 88 color | |
* | |
*/ | |
#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); | |
void* memcpy(void* d, void* s, 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 40 | |
#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[] = { | |
{ HOUSE_CENTER, HOUSE_BOTTOM }, | |
{ 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 }; | |
global_variable | |
struct v2 eyes_target_tile = { ARENA_WIDTH_IN_TILES / 2 - 1, 11 }; | |
typedef char dot_map_t[ARENA_HEIGHT_IN_TILES][ARENA_WIDTH_IN_TILES]; | |
global_variable | |
dot_map_t new_dot_map; | |
/* 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] = | |
"+------------++------------+" | |
"|............||............|" | |
"|.+--+.+---+.||.+---+.+--+.|" | |
"|*| |.| |.||.| |.| |*|" | |
"|.+--+.+---+.++.+---+.+--+.|" | |
"|..........................|" | |
"|.+--+.++.+------+.++.+--+.|" | |
"|.+--+.||.+--++--+.||.+--+.|" | |
"|......||....||....||......|" | |
"+----+.|+--+ || +--+|.+----+" | |
" |.|+--+ ++ +--+|.| " | |
" |.|| ||.| " | |
" |.|| +--__--+ ||.| " | |
"-----+.++ | | ++.+-----" | |
" . | | . " | |
"-----+.++ | | ++.+-----" | |
" |.|| +------+ ||.| " | |
" |.|| ||.| " | |
" |.|| +------+ ||.| " | |
"+----+.++ +--++--+ ++.+----+" | |
"|............||............|" | |
"|.+--+.+---+.||.+---+.+--+.|" | |
"|.+-+|.+---+.++.+---+.|+-+.|" | |
"|*..||....... .......||..*|" | |
"+-+.||.++.+------+.++.||.+-+" | |
"+-+.++.||.+--++--+.||.++.+-+" | |
"|......||....||....||......|" | |
"|.+----++--+.||.+--++----+.|" | |
"|.+--------+.++.+--------+.|" | |
"|..........................|" | |
"+--------------------------+" | |
; | |
const | |
struct level new_level; | |
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, | |
DARK_WALL_PAIR, | |
LIGHT_WALL_PAIR, | |
DOOR_PAIR, | |
CHERRIES_PAIR, | |
STRAWBERRY_PAIR, | |
PEACH_PAIR, | |
APPLE_PAIR, | |
GRAPES_PAIR, | |
GALAXIAN_PAIR, | |
BELL_PAIR, | |
KEY_PAIR, | |
CHERRIES_STEM_PAIR, | |
METAL_PAIR, | |
GALAXIAN_WING_PAIR, | |
PLAYER_TEXT_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 | |
}; | |
char* dir_names[] = { | |
"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, | |
COLOR_PINK, | |
COLOR_ORANGE, | |
COLOR_DARK_RED, | |
COLOR_BRIGHT_RED, | |
COLOR_GRAPE_GREEN, | |
COLOR_DOOR_RED, | |
COLOR_PACMAN_YELLOW, | |
COLOR_WALL_BLUE, | |
COLOR_DARK_BACKGROUND, | |
COLOR_GREY, | |
COLOR_LIGHT_GREY, | |
COLOR_STEM, | |
NUM_COLORS | |
}; | |
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 * PIXEL_SIZE / 100) * 6 / 5; | |
} | |
enum bonus_symbol { | |
CHERRIES, | |
STRAWBERRY, | |
PEACH, | |
APPLE, | |
GRAPES, | |
GALAXIAN, | |
BELL, | |
KEY | |
}; | |
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; | |
} | |
} | |
struct game_data { | |
/* Data that persists between levels, but not between games. */ | |
uint32 num_extra_lives; | |
uint32 current_level; | |
uint32 score; | |
uint32 original_fright_ghost_seed; | |
enum { | |
PAUSED_BEFORE_PLAYING, | |
GAME_OVER, | |
PLAYING, | |
LOSING_A_LIFE, | |
LEVEL_TRANSITION, | |
} mode; | |
/* Data that describes the current state of a level */ | |
struct level { | |
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, should_flash_extra_life; | |
uint32 dots_eaten; | |
struct v2 pacman_pos; | |
enum dir pacman_dir, next_dir; | |
uint32 dot_timer; | |
dot_map_t dot_map; | |
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; | |
uint32 dot_counter; | |
bool32 bounce_target_bottom; | |
enum { | |
EXITING_GHOST_HOUSE, | |
OUTSIDE_GHOST_HOUSE, | |
ENTERING_GHOST_HOUSE, | |
IN_GHOST_HOUSE, | |
} ghost_house_state; | |
} ghosts[NUM_GHOSTS]; | |
} level; | |
/* Data that describes the constants for the current level */ | |
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, ghost_flash_time; | |
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]; | |
} level_constants; | |
}; | |
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]); | |
} | |
uint32 min_ghost_flash_time = 10, large_ghost_flash_time=2*min_ghost_flash_time; | |
if (level_constants->pacman_powerup_time >= (2 * large_ghost_flash_time * level_constants->num_ghost_flashes)) { | |
level_constants->ghost_flash_time = large_ghost_flash_time; | |
} else { | |
level_constants->ghost_flash_time = min_ghost_flash_time; | |
} | |
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; | |
} | |
} | |
#define TUNNEL_WIDTH (4*TILE_SIZE) | |
internal void | |
reverse_ghosts(struct ghost* ghosts) { | |
int i; | |
for (i = 0; i < NUM_GHOSTS; ++i) { | |
ghosts[i].is_path_chosen = TRUE; | |
struct v2 ghost_tile = pos_to_tile(&ghosts[i].pos); | |
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 | |
internal void | |
get_normal_target_tile( | |
enum ghost_name ghost_name, | |
struct level* level, | |
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->pacman_pos); | |
/* Chase after a target */ | |
if (level->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->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->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 level* level) { | |
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: | |
case PINKY: | |
case CLYDE: | |
start_position = add(TILE_CENTER, scale(bottom_house_targets[i], TILE_SIZE)); | |
break; | |
} | |
level->ghosts[i].pos = start_position; | |
level->ghosts[i].last_tile = pos_to_tile(&level->ghosts[i].pos); | |
level->ghosts[i].dir = ghost_start_dirs[i]; | |
level->ghosts[i].is_path_chosen = FALSE; | |
level->ghosts[i].mode = NORMAL; | |
level->ghosts[i].bounce_target_bottom = FALSE; | |
if (i == BLINKY) { | |
level->ghosts[i].ghost_house_state = OUTSIDE_GHOST_HOUSE; | |
} else { | |
level->ghosts[i].ghost_house_state = IN_GHOST_HOUSE; | |
} | |
} | |
level->pacman_pos = pacman_start_pos; | |
level->pacman_dir = LEFT; | |
level->pacman_blocked = FALSE; | |
level->pacman_turning = FALSE; | |
level->next_dir = level->pacman_dir; | |
level->dot_timer = 0; | |
} | |
internal void | |
start_new_level(struct game_data* game_data) { | |
game_data->level = new_level; | |
memcpy(game_data->level.dot_map, new_dot_map, sizeof(new_dot_map)); | |
game_data->level.fright_ghost_seed = game_data->original_fright_ghost_seed; | |
return_ghosts_and_pacman_to_start_position(&game_data->level); | |
} | |
#ifdef MOM | |
#define NUM_LIVES 5 | |
#else | |
#define NUM_LIVES 2 | |
#endif | |
const | |
struct game_data new_game = { | |
.num_extra_lives = NUM_LIVES | |
}; | |
internal struct game_data | |
create_new_game() { | |
struct game_data game_data = new_game; | |
game_data.original_fright_ghost_seed = time(NULL); | |
set_level_constants(&game_data.level_constants, 0); | |
start_new_level(&game_data); | |
game_data.level.should_flash_extra_life = TRUE; | |
return game_data; | |
} | |
global_variable | |
bool32 has_color_terminal; | |
internal void | |
safe_attron(int color_pair) { | |
if (has_color_terminal) { | |
attron(color_pair); | |
} | |
} | |
internal void | |
draw_fruit(struct v2 left_tile, enum bonus_symbol bonus_symbol, struct view* view) { | |
#define FRUIT_LEN 3 | |
struct bonus_symbol_draw_data { | |
char str[FRUIT_LEN]; | |
int colors[FRUIT_LEN]; | |
} bonus_symbols_draw_data[] = { | |
{ "o^o", | |
{ CHERRIES_PAIR, CHERRIES_STEM_PAIR, CHERRIES_PAIR } }, | |
{ ":C>", | |
{ GRAPES_PAIR, STRAWBERRY_PAIR, STRAWBERRY_PAIR } }, | |
{ "(')", | |
{ PEACH_PAIR, GRAPES_PAIR, PEACH_PAIR } }, | |
{ "(`)", | |
{ APPLE_PAIR, GRAPES_PAIR, APPLE_PAIR } }, | |
{ "`88", | |
{ CHERRIES_STEM_PAIR, GRAPES_PAIR, GRAPES_PAIR } }, | |
{ "|Y|", | |
{ GALAXIAN_WING_PAIR, PACMAN_PAIR, GALAXIAN_WING_PAIR } }, | |
{ "3>-", | |
{ KEY_PAIR, PACMAN_PAIR, PACMAN_PAIR } }, | |
{ "w=D", | |
{ METAL_PAIR, METAL_PAIR, KEY_PAIR } }, | |
}; | |
struct v2 pos = draw_pos_v2(left_tile, view); | |
int i; | |
for (i = 0; i < FRUIT_LEN; ++i) { | |
safe_attron(COLOR_PAIR(bonus_symbols_draw_data[bonus_symbol].colors[i])); | |
mvaddch(pos.y, pos.x++, bonus_symbols_draw_data[bonus_symbol].str[i]); | |
} | |
} | |
internal uint32 | |
get_ghost_score(uint32 ghosts_eaten) { | |
return (2 << ghosts_eaten) * 100; | |
} | |
internal void | |
add_extra_life(struct game_data* game_data) { | |
game_data->level.extra_life_timer = seconds_to_frames(1) / 3; | |
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); | |
{ /*Init new_dot_map*/ | |
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 == '*') { | |
new_dot_map[row][col] = ch; | |
} | |
} | |
} | |
} | |
uint32 transition_timer = 0; | |
bool32 running = TRUE; | |
uint32 last_update = time_in_us(); | |
uint32 frame_timer = 0; | |
uint32 high_score = 0; | |
int32 next_input = -1; | |
bool32 is_two_player = FALSE; | |
int current_player = 0; | |
has_color_terminal = has_colors(); | |
uint32 fruit_time = 0; | |
start_color(); | |
int term_colors[NUM_COLORS] = { 0 }; | |
if (!has_color_terminal) { | |
mvprintw(0, 0, "Warning: Colors not enabled for this terminal.\nProgrammer's suggestion: try running in the unicode-rxvt terminal with 256 color support.\nPress any key to continue."); | |
timeout(-1); | |
getch(); | |
timeout(0); | |
} else if (COLORS == 256) { | |
term_colors[COLOR_LIGHT_BLUE] = 51; | |
term_colors[COLOR_PINK] = 197; | |
term_colors[COLOR_ORANGE] = 172; | |
term_colors[COLOR_DARK_RED] = 88; | |
term_colors[COLOR_BRIGHT_RED] = 196; | |
term_colors[COLOR_GRAPE_GREEN] = 76; | |
term_colors[COLOR_DOOR_RED] = 124; | |
term_colors[COLOR_PACMAN_YELLOW] = 226; | |
term_colors[COLOR_WALL_BLUE] = 21; | |
term_colors[COLOR_DARK_BACKGROUND] = 232; | |
term_colors[COLOR_GREY] = 233; | |
term_colors[COLOR_LIGHT_GREY] = 252; | |
term_colors[COLOR_STEM] = 94; | |
} else { | |
mvprintw(0, 0, "WARNING: %u colors available in this terminal. This game currently looks best in 256 colors.", COLORS); | |
timeout(-1); | |
getch(); | |
timeout(0); | |
term_colors[COLOR_LIGHT_BLUE] = COLOR_WHITE; | |
term_colors[COLOR_PINK] = COLOR_MAGENTA; | |
term_colors[COLOR_ORANGE] = COLOR_YELLOW; | |
term_colors[COLOR_DARK_RED] = COLOR_RED; | |
term_colors[COLOR_BRIGHT_RED] = COLOR_RED; | |
term_colors[COLOR_GRAPE_GREEN] = COLOR_GREEN; | |
term_colors[COLOR_DOOR_RED] = COLOR_RED; | |
term_colors[COLOR_PACMAN_YELLOW] = COLOR_YELLOW; | |
term_colors[COLOR_WALL_BLUE] = COLOR_BLUE; | |
term_colors[COLOR_DARK_BACKGROUND] = COLOR_BLACK; | |
term_colors[COLOR_GREY] = COLOR_BLACK; | |
term_colors[COLOR_LIGHT_GREY] = COLOR_WHITE; | |
term_colors[COLOR_STEM] = COLOR_RED; | |
} | |
if (has_color_terminal) { | |
init_pair(BG_PAIR, COLOR_WHITE, term_colors[COLOR_DARK_BACKGROUND]); | |
int bkgd_color = term_colors[COLOR_GREY]; | |
init_pair(EMPTY_PAIR, COLOR_WHITE, bkgd_color); | |
init_pair(DARK_WALL_PAIR, term_colors[COLOR_WALL_BLUE], term_colors[COLOR_DARK_BACKGROUND]); | |
init_pair(LIGHT_WALL_PAIR, term_colors[COLOR_LIGHT_BLUE], term_colors[COLOR_DARK_BACKGROUND]); | |
init_pair(DOOR_PAIR, term_colors[COLOR_DOOR_RED], term_colors[COLOR_DARK_BACKGROUND]); | |
init_pair(PACMAN_PAIR, term_colors[COLOR_PACMAN_YELLOW], bkgd_color); | |
init_pair(DOT_PAIR, COLOR_WHITE, bkgd_color); | |
init_pair(GAME_OVER_TEXT_PAIR, term_colors[COLOR_DOOR_RED], bkgd_color); | |
init_pair(BLINKY_PAIR, COLOR_WHITE, term_colors[COLOR_DOOR_RED]); | |
init_pair(INKY_PAIR, COLOR_WHITE, term_colors[COLOR_LIGHT_BLUE]); | |
init_pair(PINKY_PAIR, COLOR_WHITE, term_colors[COLOR_PINK]); | |
init_pair(CLYDE_PAIR, COLOR_WHITE, term_colors[COLOR_ORANGE]); | |
init_pair(FRIGHT_PAIR, COLOR_WHITE, term_colors[COLOR_WALL_BLUE]); | |
init_pair(FRIGHT_FLASH_PAIR, term_colors[COLOR_WALL_BLUE], COLOR_WHITE); | |
init_pair(EYES_PAIR, COLOR_WHITE, bkgd_color); | |
init_pair(CHERRIES_PAIR, term_colors[COLOR_DARK_RED], bkgd_color); | |
init_pair(STRAWBERRY_PAIR, term_colors[COLOR_BRIGHT_RED], bkgd_color); | |
init_pair(PEACH_PAIR, term_colors[COLOR_ORANGE], bkgd_color); | |
init_pair(APPLE_PAIR, term_colors[COLOR_DOOR_RED], bkgd_color); | |
init_pair(GRAPES_PAIR, term_colors[COLOR_GRAPE_GREEN], bkgd_color); | |
init_pair(GALAXIAN_PAIR, term_colors[COLOR_ORANGE], bkgd_color); | |
init_pair(BELL_PAIR, term_colors[COLOR_PACMAN_YELLOW], bkgd_color); | |
init_pair(KEY_PAIR, term_colors[COLOR_LIGHT_BLUE], bkgd_color); | |
init_pair(CHERRIES_STEM_PAIR, term_colors[COLOR_STEM], bkgd_color); | |
init_pair(METAL_PAIR, term_colors[COLOR_LIGHT_GREY], bkgd_color); | |
init_pair(GALAXIAN_WING_PAIR, term_colors[COLOR_WALL_BLUE], bkgd_color); | |
init_pair(PLAYER_TEXT_PAIR, term_colors[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 game_data games[2] = { create_new_game(), create_new_game() }; | |
struct v2 pacman_turn_tile; | |
games[current_player].mode = GAME_OVER; | |
uint32 frame_times[] = { 8000, 16667, 50000, 100000, 150000 }; | |
int frame_time_index = 1; | |
while (running) { | |
/* Get Input */ | |
int ch; | |
while ((ch = getch()) != -1) { | |
if (games[current_player].mode == GAME_OVER) { | |
current_player = 0; | |
transition_timer = 0; | |
games[0] = create_new_game(); | |
is_two_player = FALSE; | |
/* Start a two-player game */ | |
if (ch == '2') { | |
is_two_player = TRUE; | |
games[1] = create_new_game(); | |
} | |
} | |
switch (ch) { | |
case 'q': | |
case 'Q': | |
running = FALSE; | |
continue; | |
case 'r': | |
case 'R': | |
games[current_player].mode = GAME_OVER; | |
break; | |
#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 (games[current_player].level.ghosts[i].ghost_house_state == OUTSIDE_GHOST_HOUSE) { | |
games[current_player].level.ghosts[i].mode = EYES; | |
} | |
} | |
} break; | |
case 'f': | |
case 'F': | |
games[current_player].mode = LEVEL_TRANSITION; | |
transition_timer = 0; | |
break; | |
case 'l': | |
case 'L': | |
games[current_player].mode = LOSING_A_LIFE; | |
transition_timer = 0; | |
break; | |
case '+': | |
add_extra_life(&games[current_player]); | |
break; | |
case '-': | |
if (games[current_player].num_extra_lives > 0) { | |
--games[current_player].num_extra_lives; | |
} | |
break; | |
case 'c': | |
case 'C': | |
debug_ghost_mode_overridden = TRUE; | |
games[current_player].level.is_chasing = !games[current_player].level.is_chasing; | |
break; | |
case 'd': | |
case 'D': | |
debug_no_death_mode = !debug_no_death_mode; | |
if (debug_no_death_mode) { | |
init_pair(PACMAN_PAIR, term_colors[COLOR_GRAPE_GREEN], term_colors[COLOR_GREY]); | |
} else { | |
init_pair(PACMAN_PAIR, term_colors[COLOR_PACMAN_YELLOW], term_colors[COLOR_GREY]); | |
} | |
break; | |
case 'p': | |
case 'P': | |
games[current_player] = create_new_game(); | |
transition_timer = 0; | |
games[current_player].mode = PAUSED_BEFORE_PLAYING; | |
break; | |
#endif | |
} | |
switch (ch) { | |
case KEY_LEFT: | |
next_input = LEFT; | |
break; | |
case KEY_RIGHT: | |
next_input = RIGHT; | |
break; | |
case KEY_UP: | |
next_input = UP; | |
break; | |
case KEY_DOWN: | |
next_input = DOWN; | |
break; | |
} | |
} | |
if (!games[current_player].level.pacman_turning && next_input != -1) { | |
games[current_player].level.next_dir = next_input; | |
next_input = -1; | |
} | |
frame_timer += time_in_us() - last_update; | |
last_update = time_in_us(); | |
if (frame_timer > frame_times[frame_time_index]) { | |
if (!games[current_player].level.ghost_eat_timer) { | |
++games[current_player].level.pacman_chomp_timer; | |
if (games[current_player].level.fruit_is_visible) { | |
++games[current_player].level.fruit_timer; | |
if (games[current_player].level.fruit_timer > fruit_time) { | |
games[current_player].level.fruit_is_visible = FALSE; | |
} | |
} | |
} | |
switch (games[current_player].mode) { | |
case PAUSED_BEFORE_PLAYING: { | |
uint32 time = is_two_player ? seconds_to_frames(4) : seconds_to_frames(2); | |
transition_timer++; | |
if (transition_timer >= time) { | |
transition_timer = 0; | |
games[current_player].mode = PLAYING; | |
} | |
} break; | |
case PLAYING: { | |
uint32 pacman_speed; | |
uint32 old_score = games[current_player].score; | |
num_blocked_tiles = 0; | |
if (games[current_player].level.ghost_eat_timer) { | |
--games[current_player].level.ghost_eat_timer; | |
if (!games[current_player].level.ghost_eat_timer) { | |
games[current_player].level.ghost_to_be_eaten->mode = EYES; | |
games[current_player].level.ghost_to_be_eaten = NULL; | |
} | |
} else if (games[current_player].level.pacman_powerup_timer) { | |
--games[current_player].level.pacman_powerup_timer; | |
pacman_speed = games[current_player].level_constants.pacman_powerup_speed; | |
if (!games[current_player].level.pacman_powerup_timer) { | |
int i; | |
for (i = 0; i < NUM_GHOSTS; ++i) { | |
if (games[current_player].level.ghosts[i].mode != EYES) { | |
games[current_player].level.ghosts[i].mode = NORMAL; | |
} | |
} | |
} else if (games[current_player].level.pacman_powerup_timer <= games[current_player].level_constants.num_ghost_flashes * 2 * games[current_player].level_constants.ghost_flash_time) { | |
++games[current_player].level.ghost_flash_timer; | |
} | |
} else { | |
pacman_speed = games[current_player].level_constants.pacman_speed; | |
} | |
if (games[current_player].level.pacman_eat_timer) { | |
--games[current_player].level.pacman_eat_timer; | |
} else if (!games[current_player].level.ghost_eat_timer) { | |
if (games[current_player].level.pacman_dir != games[current_player].level.next_dir) { | |
if (is_h_dir(games[current_player].level.pacman_dir) == is_h_dir(games[current_player].level.next_dir)) { | |
games[current_player].level.pacman_dir = games[current_player].level.next_dir; | |
} else { | |
struct v2 blocked_pos_s = games[current_player].level.pacman_pos; | |
bool32 blocked = test_pacman_dir_blocked(games[current_player].level.next_dir, &blocked_pos_s); | |
if (blocked) { | |
blocked_tiles[num_blocked_tiles++] = pos_to_tile(&blocked_pos_s); | |
} else { | |
games[current_player].level.pacman_turning = TRUE; | |
pacman_turn_tile = pos_to_tile(&games[current_player].level.pacman_pos); | |
} | |
} | |
} | |
if (games[current_player].level.pacman_turning) { | |
move_in_dir(games[current_player].level.next_dir, &games[current_player].level.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(games[current_player].level.pacman_dir)) { | |
int mv_amt = tile_center.x - games[current_player].level.pacman_pos.x; | |
if (mv_amt < 0) { | |
games[current_player].level.pacman_pos.x -= MIN(-mv_amt, pacman_speed); | |
reached_centerline = -mv_amt < pacman_speed; | |
} else { | |
games[current_player].level.pacman_pos.x += MIN(mv_amt, pacman_speed); | |
reached_centerline = mv_amt < pacman_speed; | |
} | |
} else { | |
int mv_amt = tile_center.y - games[current_player].level.pacman_pos.y; | |
if (mv_amt < 0) { | |
games[current_player].level.pacman_pos.y -= MIN(-mv_amt, pacman_speed); | |
reached_centerline = -mv_amt < pacman_speed; | |
} else { | |
games[current_player].level.pacman_pos.y += MIN(mv_amt, pacman_speed); | |
reached_centerline = mv_amt < pacman_speed; | |
} | |
} | |
if (reached_centerline) { | |
games[current_player].level.pacman_turning = FALSE; | |
games[current_player].level.pacman_dir = games[current_player].level.next_dir; | |
} | |
} else { | |
struct v2 blocked_pos_s = games[current_player].level.pacman_pos; | |
games[current_player].level.pacman_blocked = test_pacman_dir_blocked(games[current_player].level.pacman_dir, &blocked_pos_s); | |
if (games[current_player].level.pacman_blocked) { | |
blocked_tiles[num_blocked_tiles++] = pos_to_tile(&blocked_pos_s); | |
} else { | |
move_in_dir(games[current_player].level.pacman_dir, &games[current_player].level.pacman_pos, pacman_speed); | |
if (games[current_player].level.pacman_pos.x < -TUNNEL_WIDTH / 2) { | |
games[current_player].level.pacman_pos.x += ARENA_WIDTH + TUNNEL_WIDTH; | |
} else if (games[current_player].level.pacman_pos.x >= ARENA_WIDTH + TUNNEL_WIDTH / 2) { | |
games[current_player].level.pacman_pos.x -= ARENA_WIDTH + TUNNEL_WIDTH; | |
} | |
} | |
} | |
} | |
{ | |
struct v2 pacman_tile = pos_to_tile(&games[current_player].level.pacman_pos); | |
char ch = games[current_player].level.dot_map[pacman_tile.y][pacman_tile.x]; | |
if (ch == '.') { | |
games[current_player].level.pacman_eat_timer = 1; | |
games[current_player].score += 10; | |
} else if (ch == '*') { | |
games[current_player].level.pacman_eat_timer = 3; | |
games[current_player].level.pacman_powerup_timer = games[current_player].level_constants.pacman_powerup_time; | |
games[current_player].level.consecutive_ghosts_eaten = 0; | |
games[current_player].level.ghost_flash_timer = 0; | |
reverse_ghosts(games[current_player].level.ghosts); | |
int i; | |
for (i = 0; i < NUM_GHOSTS; ++i) { | |
if (games[current_player].level.ghosts[i].mode != EYES) { | |
games[current_player].level.ghosts[i].mode = FRIGHTENED; | |
} | |
} | |
games[current_player].score += 50; | |
} | |
if (ch == '.' || ch == '*') { | |
uint32 first_fruit_dot_counter = 70, second_fruit_dot_counter = 170; | |
games[current_player].level.dot_timer = 0; | |
++games[current_player].level.dots_eaten; | |
if (games[current_player].level.dots_eaten == first_fruit_dot_counter || games[current_player].level.dots_eaten == second_fruit_dot_counter) { | |
games[current_player].level.fruit_is_visible = TRUE; | |
games[current_player].level.fruit_timer = 0; | |
fruit_time = seconds_to_frames(9) + (rand() % seconds_to_frames(1)); | |
} else if (games[current_player].level.dots_eaten == NUM_DOTS) { | |
games[current_player].mode = LEVEL_TRANSITION; | |
transition_timer = 0; | |
} | |
if (games[current_player].level.use_global_ghost_house_dot_counter) { | |
++games[current_player].level.global_ghost_house_dot_counter; | |
} else { | |
int i; | |
for (i = PINKY; i < NUM_GHOSTS; ++i) { | |
if (games[current_player].level.ghosts[i].ghost_house_state == IN_GHOST_HOUSE) { | |
++games[current_player].level.ghosts[i].dot_counter; | |
} | |
} | |
} | |
} else { | |
++games[current_player].level.dot_timer; | |
} | |
games[current_player].level.dot_map[pacman_tile.y][pacman_tile.x] = 0; | |
} | |
if (games[current_player].level.num_ghost_mode_cycles < 4 && !games[current_player].level.pacman_powerup_timer && !debug_ghost_mode_overridden) { | |
++games[current_player].level.ghost_mode_timer; | |
if (!games[current_player].level.is_chasing && games[current_player].level.ghost_mode_timer > games[current_player].level_constants.scatter_times[games[current_player].level.num_ghost_mode_cycles]) { | |
/* Switch to chase mode */ | |
games[current_player].level.is_chasing = TRUE; | |
games[current_player].level.ghost_mode_timer = 0; | |
++games[current_player].level.num_ghost_mode_cycles; | |
reverse_ghosts(games[current_player].level.ghosts); | |
} else if (games[current_player].level.is_chasing && games[current_player].level.ghost_mode_timer > games[current_player].level_constants.chase_times[games[current_player].level.num_ghost_mode_cycles]) { | |
/* Switch to scatter mode */ | |
games[current_player].level.is_chasing = FALSE; | |
games[current_player].level.ghost_mode_timer = 0; | |
reverse_ghosts(games[current_player].level.ghosts); | |
} | |
} | |
{ /* Update all ghost positions */ | |
int ghost_name_i; | |
for (ghost_name_i = 0; ghost_name_i < NUM_GHOSTS; ++ghost_name_i) { | |
struct ghost* ghost = &games[current_player].level.ghosts[ghost_name_i]; | |
if (ghost->mode != EYES && games[current_player].level.ghost_eat_timer) { | |
continue; | |
} | |
uint32 speed; | |
struct v2 eyes_target_pos = add(scale(eyes_target_tile, TILE_SIZE), TILE_CENTER); | |
if (ghost->ghost_house_state == EXITING_GHOST_HOUSE) { | |
if (eql(&eyes_target_pos, &ghost->pos)) { | |
ghost->ghost_house_state = OUTSIDE_GHOST_HOUSE; | |
ghost->dir = ghost->chosen_dir; | |
} else 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(125); | |
uint32 ghost_house_speed = get_speed(55); | |
uint32 elroy_v2_speed = games[current_player].level_constants.elroy_v1_speed + get_speed(5); | |
uint32 elroy_v2_dots_left = games[current_player].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 = ghost_house_speed; | |
} else if (is_in_tunnel) { | |
speed = games[current_player].level_constants.ghost_tunnel_speed; | |
} else if (ghost->mode == FRIGHTENED) { | |
speed = games[current_player].level_constants.ghost_frightened_speed; | |
} else if (ghost_name_i == BLINKY && NUM_DOTS - games[current_player].level.dots_eaten <= games[current_player].level_constants.elroy_v1_dots_left) { | |
/* Oh he mad now beotch. */ | |
speed = games[current_player].level_constants.elroy_v1_speed; | |
} else if (ghost_name_i == BLINKY && NUM_DOTS - games[current_player].level.dots_eaten <= elroy_v2_dots_left) { | |
/* Cruise Elroy */ | |
speed = elroy_v2_speed; | |
} else { | |
speed = games[current_player].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_target_tile = eyes_target_tile; | |
move_in_dir(DOWN, &just_below_eyes_target_tile, 1); | |
if (eql(&just_below_eyes_target_tile, &ghost_tile)) { | |
ghost->is_path_chosen = FALSE; | |
} | |
} | |
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; | |
} | |
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 ghost_name_i; | |
bool32 found = FALSE; | |
for_array(ghost_name_i, forbidden_upward_tiles) { | |
if (eql(&forbidden_upward_tiles[ghost_name_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(&games[current_player].level.fright_ghost_seed) % NUM_DIRS; | |
best_choice = -1; | |
while (best_choice == -1) { | |
int ghost_name_i; | |
for (ghost_name_i = 0; ghost_name_i < num_paths; ++ghost_name_i) { | |
if (paths[ghost_name_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->mode == EYES) { | |
if (eql(&ghost_tile, &bottom_house_targets[ghost_name_i])) { | |
ghost->mode = NORMAL; | |
} else { | |
ghost->target_tile = bottom_house_targets[ghost_name_i]; | |
} | |
} | |
if (ghost->mode != EYES) { | |
enum ghost_name next_ghost_released; | |
for (next_ghost_released = PINKY; next_ghost_released < NUM_GHOSTS; ++next_ghost_released) { | |
if (games[current_player].level.ghosts[next_ghost_released].ghost_house_state == IN_GHOST_HOUSE) { | |
break; | |
} | |
} | |
if (ghost_name_i == BLINKY || | |
(ghost_name_i == next_ghost_released && ( | |
games[current_player].level.dot_timer >= seconds_to_frames(4) || | |
(games[current_player].level.use_global_ghost_house_dot_counter && games[current_player].level.global_ghost_house_dot_counter >= ghost_global_dot_limits[ghost_name_i]) || | |
(!games[current_player].level.use_global_ghost_house_dot_counter && ghost->dot_counter >= games[current_player].level_constants.ghost_dot_limits[ghost_name_i])))) { | |
if (ghost_name_i != BLINKY) { | |
games[current_player].level.dot_timer = 0; | |
} | |
ghost->ghost_house_state = EXITING_GHOST_HOUSE; | |
ghost->is_path_chosen = FALSE; | |
} else if (ghost->bounce_target_bottom) { | |
if (eql(&ghost_tile, &bottom_house_targets[ghost_name_i])) { | |
ghost->bounce_target_bottom = !ghost->bounce_target_bottom; | |
ghost->target_tile = top_house_targets[ghost_name_i]; | |
} else { | |
ghost->target_tile = bottom_house_targets[ghost_name_i]; | |
} | |
} else { | |
if (eql(&ghost_tile, &top_house_targets[ghost_name_i])) { | |
ghost->bounce_target_bottom = !ghost->bounce_target_bottom; | |
ghost->target_tile = bottom_house_targets[ghost_name_i]; | |
} else { | |
ghost->target_tile = top_house_targets[ghost_name_i]; | |
} | |
} | |
} | |
} else if (ghost->mode == NORMAL) { | |
get_normal_target_tile(ghost_name_i, &games[current_player].level, scatter_targets, &games[current_player].level.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 ghost_name_i, best_path = 0, best_distance = -1; | |
for (ghost_name_i = 0; ghost_name_i < num_paths; ++ghost_name_i) { | |
struct v2 tile = ghost_tile; | |
move_in_dir(paths[ghost_name_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 = ghost_name_i; | |
} | |
} | |
best_choice = paths[best_path]; | |
} | |
ghost->is_path_chosen = TRUE; | |
ghost->chosen_dir = best_choice; | |
} | |
} | |
} | |
{ | |
struct v2 pacman_tile = pos_to_tile(&games[current_player].level.pacman_pos); | |
if (games[current_player].level.fruit_is_visible && (eql(&pacman_tile, &fruit_tile))) { | |
games[current_player].level.fruit_is_visible = FALSE; | |
games[current_player].level.fruit_score_timer = seconds_to_frames(2); | |
games[current_player].score += symbol_points[games[current_player].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(&games[current_player].level.ghosts[i].pos); | |
if (eql(&ghost_tile, &pacman_tile)) { | |
if (games[current_player].level.ghosts[i].mode == FRIGHTENED && !games[current_player].level.ghost_to_be_eaten) { | |
uint32 ghost_eat_time = seconds_to_frames(1); | |
games[current_player].level.ghost_eat_timer = ghost_eat_time; | |
games[current_player].score += get_ghost_score(games[current_player].level.consecutive_ghosts_eaten++); | |
games[current_player].level.ghost_to_be_eaten = &games[current_player].level.ghosts[i]; | |
} else if (games[current_player].level.ghosts[i].mode == NORMAL && !debug_no_death_mode) { | |
games[current_player].mode = LOSING_A_LIFE; | |
games[current_player].level.extra_life_timer = 0; | |
games[current_player].level.ghost_eat_timer = 0; | |
games[current_player].level.fruit_is_visible = FALSE; | |
transition_timer = 0; | |
break; | |
} | |
} | |
} | |
} | |
high_score = MAX(games[current_player].score, high_score); | |
if (old_score / 10000 != games[current_player].score / 10000) { | |
add_extra_life(&games[current_player]); | |
} | |
if (games[current_player].level.extra_life_timer) { | |
--games[current_player].level.extra_life_timer; | |
} | |
} break; | |
case GAME_OVER: { | |
} break; | |
case LEVEL_TRANSITION: { | |
++transition_timer; | |
if (transition_timer > seconds_to_frames(1)) { | |
set_level_constants(&games[current_player].level_constants, ++games[current_player].current_level); | |
start_new_level(&games[current_player]); | |
transition_timer = 0; | |
games[current_player].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: | |
--games[current_player].level.pacman_dir; | |
if (games[current_player].level.pacman_dir == -1) { | |
games[current_player].level.pacman_dir = NUM_DIRS - 1; | |
} | |
break; | |
/* Restart the level */ | |
case LIFE_LOST_PAUSE_TIME + LIFE_LOST_TURN_TIME*8: | |
if (games[current_player].num_extra_lives) { | |
--games[current_player].num_extra_lives; | |
games[current_player].level.should_flash_extra_life = TRUE; | |
games[current_player].mode = PAUSED_BEFORE_PLAYING; | |
return_ghosts_and_pacman_to_start_position(&games[current_player].level); | |
games[current_player].level.use_global_ghost_house_dot_counter = TRUE; | |
games[current_player].level.global_ghost_house_dot_counter = 0; | |
games[current_player].level.ghost_mode_timer = 0; | |
games[current_player].level.num_ghost_mode_cycles = 0; | |
games[current_player].level.is_chasing = FALSE; | |
} else { | |
games[current_player].mode = GAME_OVER; | |
} | |
transition_timer = 0; | |
if (is_two_player) { | |
int other_player = current_player == 0 ? 1 : 0; | |
if (games[other_player].mode != GAME_OVER) { | |
current_player = other_player; | |
games[current_player].mode = PAUSED_BEFORE_PLAYING; | |
} | |
} | |
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(&games[current_player].level.ghosts[INKY].pos)); | |
} else { | |
struct v2 zero = { 0 }; | |
view.camera_target_tile = zero; | |
} | |
uint32 flash_time = 10; | |
int wall_pair; | |
if (games[current_player].level.extra_life_timer && games[current_player].level.extra_life_timer || | |
(games[current_player].mode == LEVEL_TRANSITION && transition_timer / flash_time % 2 == 1)) { | |
wall_pair = LIGHT_WALL_PAIR; | |
} else { | |
wall_pair = DARK_WALL_PAIR; | |
} | |
{ /* 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 = '+'; | |
safe_attron(COLOR_PAIR(wall_pair)); | |
break; | |
case 'x': | |
draw_ch = ' '; | |
safe_attron(COLOR_PAIR(wall_pair)); | |
break; | |
case ' ': | |
safe_attron(COLOR_PAIR(EMPTY_PAIR)); | |
break; | |
case '_': | |
safe_attron(COLOR_PAIR(DOOR_PAIR)); | |
fill_ch = ch; | |
break; | |
case '-': | |
case '|': | |
case '+': | |
safe_attron(COLOR_PAIR(wall_pair)); | |
break; | |
} | |
if (ch == '-') { | |
fill_ch = ch; | |
} | |
draw_tile(row, col, draw_ch, &view, fill_ch); | |
} | |
} | |
} | |
if (games[current_player].level.fruit_is_visible) { | |
draw_fruit(fruit_tile, games[current_player].level_constants.bonus_symbol, &view); | |
} else if (games[current_player].level.fruit_score_timer) { | |
safe_attron(COLOR_PAIR(games[current_player].level_constants.bonus_symbol + CHERRIES_PAIR)); | |
--games[current_player].level.fruit_score_timer; | |
struct v2 pos = draw_pos_v2(fruit_tile, &view); | |
mvprintw(pos.y, pos.x, "%u", symbol_points[games[current_player].level_constants.bonus_symbol]); | |
} | |
{ | |
int i; | |
int start_level = games[current_player].current_level - 6; | |
start_level = MAX(0, start_level); | |
for (i = start_level; i < games[current_player].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); | |
} | |
} | |
#ifdef DRAW_FRUIT | |
{ | |
int i; | |
for (i = 0; i < 13; ++i) { | |
struct v2 tile = { ARENA_WIDTH_IN_TILES - 2*(i+1), ARENA_HEIGHT_IN_TILES + 2 }; | |
draw_fruit(tile, get_symbol_for_level(i), &view); | |
} | |
} | |
#endif | |
{ | |
safe_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 = games[current_player].level.dot_map[row][col]; | |
if (ch == '.' || ch == '*') { | |
draw_tile(row, col, ch == '.' ? ch : '@', &view, ' '); | |
} | |
} | |
} | |
} | |
safe_attron(COLOR_PAIR(PACMAN_PAIR)); | |
char pacman_char = 'O'; | |
if (games[current_player].mode != PLAYING || games[current_player].level.pacman_blocked || games[current_player].level.pacman_chomp_timer / 3 % 3 != 0) { | |
switch (games[current_player].level.pacman_dir) { | |
case RIGHT: | |
pacman_char = '<'; | |
break; | |
case LEFT: | |
pacman_char = '>'; | |
break; | |
case UP: | |
pacman_char = 'V'; | |
break; | |
case DOWN: | |
pacman_char = '^'; | |
break; | |
} | |
} | |
{ | |
int i; | |
for (i = 0; i < games[current_player].num_extra_lives; ++i) { | |
draw_tile(ARENA_HEIGHT_IN_TILES, i + 1, '<', &view, ' '); | |
} | |
uint32 extra_life_flash_time = 15; | |
uint32 extra_life_num_flashes = 3; | |
if (games[current_player].mode == PAUSED_BEFORE_PLAYING && | |
games[current_player].level.should_flash_extra_life && | |
transition_timer < extra_life_flash_time * 2 * extra_life_num_flashes && | |
transition_timer / extra_life_flash_time % 2 == 1) { | |
draw_tile(ARENA_HEIGHT_IN_TILES, games[current_player].num_extra_lives + 1, '<', &view, ' '); | |
} | |
} | |
struct v2 pacman_tile = pos_to_tile(&games[current_player].level.pacman_pos); | |
if (!games[current_player].level.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 | |
safe_attron(COLOR_PAIR(DOOR_PAIR)); | |
int i; | |
for (i = 0; i < num_blocked_tiles; ++i) { | |
draw_tile_v2(blocked_tiles[i], 'X', &view, 'X'); | |
} | |
draw_tile_v2(eyes_target_tile, 'T', &view, 'T'); | |
for_array(i, forbidden_upward_tiles) { | |
draw_tile_v2(forbidden_upward_tiles[i], 'X', &view); | |
} | |
#endif | |
} | |
if (games[current_player].mode != LOSING_A_LIFE && games[current_player].mode != GAME_OVER) { | |
int i; | |
for (i = 0; i < NUM_GHOSTS; ++i) { | |
char draw_ch = 'm'; | |
if (games[current_player].level.ghost_eat_timer && &games[current_player].level.ghosts[i] == games[current_player].level.ghost_to_be_eaten) { | |
continue; | |
} | |
if (games[current_player].level.ghosts[i].mode == FRIGHTENED) { | |
if (games[current_player].level.ghost_flash_timer / games[current_player].level_constants.ghost_flash_time % 2 == 0) { | |
safe_attron(COLOR_PAIR(FRIGHT_PAIR)); | |
} else { | |
safe_attron(COLOR_PAIR(FRIGHT_FLASH_PAIR)); | |
} | |
if (!has_color_terminal) { | |
draw_ch = 'F'; | |
} | |
} else if (games[current_player].level.ghosts[i].mode == EYES) { | |
safe_attron(COLOR_PAIR(EYES_PAIR)); | |
if (!has_color_terminal) { | |
draw_ch = 'E'; | |
} | |
} else { | |
int curses_color_pairs[] = { BLINKY_PAIR, PINKY_PAIR, INKY_PAIR, CLYDE_PAIR }; | |
char ghost_chars[] = { 'B', 'P', 'I', 'C' }; | |
safe_attron(COLOR_PAIR(curses_color_pairs[i])); | |
if (!has_color_terminal) { | |
draw_ch = ghost_chars[i]; | |
} | |
} | |
struct v2 ghost_tile = pos_to_tile(&games[current_player].level.ghosts[i].pos); | |
if (ghost_tile.x >= 0 && ghost_tile.x < ARENA_WIDTH_IN_TILES) { | |
draw_tile_v2(ghost_tile, draw_ch, &view, ' '); | |
} | |
#if 0 | |
/* Draw Target Tile */ | |
safe_attron(COLOR_PAIR(DOT_PAIR)); | |
draw_tile_v2(ghosts[i].target_tile, ghosts[i].nickname, &view); | |
#endif | |
} | |
} | |
if (games[current_player].level.ghost_eat_timer) { | |
safe_attron(COLOR_PAIR(KEY_PAIR)); | |
struct v2 pos = draw_pos_v2(pacman_tile, &view); | |
mvprintw(pos.y, pos.x, "%d", get_ghost_score(games[current_player].level.consecutive_ghosts_eaten - 1)); | |
} | |
if (games[current_player].mode == GAME_OVER) { | |
safe_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"); | |
} else if (games[current_player].mode == PAUSED_BEFORE_PLAYING) { | |
if (is_two_player) { | |
safe_attron(COLOR_PAIR(PLAYER_TEXT_PAIR)); | |
struct v2 pos = draw_pos(HOUSE_TOP - 2, HOUSE_LEFT, &view); | |
mvprintw(pos.y, pos.x, " P L A Y E R %d", current_player + 1); | |
} | |
{ | |
safe_attron(COLOR_PAIR(PACMAN_PAIR)); | |
struct v2 pos = draw_pos(HOUSE_BOTTOM + 2, HOUSE_LEFT + 1, &view); | |
mvprintw(pos.y, pos.x, "R E A D Y !"); | |
} | |
} | |
if (view.zoom_view) { | |
safe_attron(COLOR_PAIR(PACMAN_PAIR)); | |
draw_tile_v2(pacman_tile, 'X', &view, ' '); | |
if (games[current_player].level.pacman_turning) { | |
safe_attron(COLOR_PAIR(DARK_WALL_PAIR)); | |
draw_tile_v2(pacman_turn_tile, 'O', &view, ' '); | |
} | |
safe_attron(COLOR_PAIR(BG_PAIR)); | |
mvaddch( | |
games[current_player].level.ghosts[INKY].pos.y / PIXEL_SIZE + view.top - view.camera_target_tile.y * TILE_SIZE_IN_PIXELS, | |
games[current_player].level.ghosts[INKY].pos.x / PIXEL_SIZE + view.left - view.camera_target_tile.x * TILE_SIZE_IN_PIXELS, 'O'); | |
} | |
safe_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", games[0].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", high_score); | |
mvprintw(view.top - 2, view.left + (ARENA_WIDTH_IN_TILES-5)*3, "2 U P"); | |
mvprintw(view.top - 1, view.left + (ARENA_WIDTH_IN_TILES-5)*3, "%7d", games[1].score); | |
safe_attron(COLOR_PAIR(BG_PAIR)); | |
int diag_row = view.top + ARENA_HEIGHT_IN_TILES + 1; | |
/* mvprintw(diag_row++, 0, "fruit_time=%f", fruit_time / (double)seconds_to_frames(1)); */ | |
/* mvprintw(diag_row++, 0, "num_ghost_mode_cycles=%d", games[current_player].level.num_ghost_mode_cycles); */ | |
} | |
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