diff options
-rw-r--r-- | README.md | 98 | ||||
-rw-r--r-- | field.c | 4 | ||||
-rw-r--r-- | field.h | 6 | ||||
-rw-r--r-- | ghosts.c | 21 | ||||
-rw-r--r-- | pacman.c | 10 | ||||
-rw-r--r-- | pacman.png | bin | 0 -> 12471 bytes |
6 files changed, 135 insertions, 4 deletions
@@ -1 +1,99 @@ +# Pac-Man (Console Version with `ncurses`) + +A terminal-based Pac-Man game written in C with `ncurses`. It faithfully +replicates the original ghost AI from the classic 1980 arcade game, including +their behavioral modes, targeting rules, and unique pathfinding algorithms. + +<img src = "pacman.png" /> + +This version contains one fixed level and implements core gameplay mechanics: +Pac-Man movement, ghost AI, lives, energizers (power pellets), frightened mode, +collision handling, and score tracking. + +## Features + +- **Classic Pac-Man logic** — core mechanics faithfully implemented +- **Ghost behavior inspired by the original game** — based on *The Pac-Man Dossier* +- **Mode switching** — scatter, chase, frightened +- **Unique AI logic per ghost** — each ghost uses different rules and pathfinding +- **Life system** — Pac-Man has limited lives +- **Power pellets** — enable Pac-Man to eat ghosts in frightened mode +- **Colored rendering** — `ncurses`-based graphics with different colors for each ghost and Pac-Man +- **Directional movement logic** — buffered input for responsive turns +- **Single static level** — pre-defined grid layout with maze walls and teleport tunnel +- **Maze collision and coin collection** — walls, tunnels, and dot eating logic +- **Pause support** — press spacebar to pause/resume the game + +## Technologies Used + +- **Language:** C (C99) +- **Graphics:** `ncurses` +- **Terminal:** ANSI-compatible +- **Manual memory management**, no external libraries beyond `ncurses` + +## Controls + +- Arrow keys — Move Pac-Man +- ESC — Quit the game +- Spacebar - Pause + +## Ghost AI Logic + +Each ghost behaves differently depending on the current game mode: + +### Game Modes: + +- **Chase** — Ghosts pursue targets based on individual AI +- **Scatter** — Ghosts retreat to their fixed corners +- **Frightened** — Ghosts move randomly after Pac-Man eats a power pellet +- **Returning** — Ghosts return to their house after being eaten + +### Algorithms Used + +- **BFS (Breadth-First Search)** — used for actual pathfinding on the map +- **Manhattan Distance** — used for heuristic target evaluation +- **Random Choice** — used when ghosts are frightened + +### Ghost Behaviors: + +#### Blinky (Red) +- **Behavior:** Always targets Pac-Man's current tile in chase mode +- **Algorithm:** BFS + +#### Pinky (Pink) +- **Behavior:** Targets the tile 2 cells ahead of Pac-Man in his current direction +- **Algorithm:** Manhattan distance + +#### Inky (Cyan) +- **Behavior:** + + 1. Computes the tile two cells ahead of Pac-Man + 2. Calculates a vector from Blinky's position to that tile + 3. Doubles the vector to determine the target + +- **Algorithm:** Manhattan distance + +#### Clyde (Orange) +- **Behavior:** + + - If farther than 8 tiles from Pac-Man: behaves like Blinky (chases) + - If closer: retreats to scatter corner + +- **Algorithm:** Manhattan distance and BFS hybrid + +## Build Instructions + +Install `ncurses` (if needed): +```bash +sudo apt install libncurses5-dev libncursesw5-dev +``` +Then: + +```bash +git clone https://git.scratko.xyz/pacman +cd pacman +make +./pacman + +``` Windows version: <a href="https://scratko.xyz/games/pacman.exe" target="_blank">Download</a> @@ -295,8 +295,8 @@ enum intersection_type get_intersection(const game_space field, /* * last conditions x-1 == left_outside_tunnel_x and - * x+1 == right_outside_tunnel_x are used by only pacman (while - * cheking remaining direction) + * x+1 == right_outside_tunnel_x are used by only pacman + * because it's only checked for ghosts at intersections. */ struct free_directions find_free_directions(const game_space field, int x, int y) @@ -16,6 +16,12 @@ enum intersection_type { one_path = '1', two_paths = '2', three_paths = '3', + /* + * Yellow blocks': ghosts can't go up from these tiles during normal + * movement. When entering from left or right, they can only continue + * straight. This restriction is ignored in frightened mode or during + * mode transitions. + */ yellow_block = 'y', direct_path }; @@ -157,6 +157,11 @@ struct coordinates identify_blue_target(struct pacman pac, return result_point; } +/* + * determining the target point for the pink ghost (4 cells in front of + * Pac-Man). + * Also involved in determining the midpoint for the blue ghost + */ struct coordinates identify_target_in_front(struct pacman pac, int shift) { int x, y; @@ -243,6 +248,10 @@ static int is_bfs_search_for_orange(enum search_flag *current_width, return *move_count > orange_search_limit; } +/* + * The search is performed from Pac-Man to the ghost so that at the end + * of the algorithm we immediately know the closest point to the ghost. + */ void breadth_first_search(game_space field, struct ghost_type *ghost, struct coordinates target_point) { @@ -283,6 +292,10 @@ void breadth_first_search(game_space field, struct ghost_type *ghost, if(ghost->color == orange) { if(!is_bfs_search_for_orange(¤t_width, &move_count)) { + /* + * Instead of two_paths, it could be anything except + * direct_path + */ redirect(field, two_paths, ghost, ghost->home_position, compute_distance_between_points); @@ -315,10 +328,14 @@ void breadth_first_search(game_space field, struct ghost_type *ghost, static int standard_distance(int x1, int y1, int x2, int y2) { int dx, dy; + dx = abs(x1 - x2); - dx = dx > field_width ? field_width - dx : dx; + if (dx > field_width / 2) + dx = field_width - dx; + dy = abs(y1 - y2); - return sqrt(dx*dx + dy*dy); + /* Manhattan distance */ + return dx + dy; } static int is_minimal_distance(int min_distance, int tmp_distance, @@ -207,6 +207,7 @@ static void change_mode(struct mode_type *mode_params, struct pacman *pac) mode_params->current_mode = scatter; mode_params->chase_counter = 0; ++mode_params->phase_number; + /* Leave the chase phase until the end of the game */ if(mode_params->phase_number == phase_limit) mode_params->current_mode = chase; if(mode_params->phase_number != phase_limit) @@ -511,6 +512,15 @@ int main() struct mode_type mode_params; struct game_params_type game_options; int key; + /* + * This stores the direction requested by the player (via arrow keys). + * If Pac-Man can't immediately switch to that direction (due to a wall), + * the input is remembered here. On the next iteration, the game checks + * again whether turning in this stored direction is possible, and performs + * the turn if so. This adds a small input buffer window, making controls + * more forgiving: if the player pressed the key slightly early, the turn + * still happens smoothly. + */ enum movement_direction stored_direction; srand(time(NULL)); initscr(); diff --git a/pacman.png b/pacman.png Binary files differnew file mode 100644 index 0000000..c4be68e --- /dev/null +++ b/pacman.png |