pacman-contribution-graph 1.0.14 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +59 -9
  2. package/dist/pacman-contribution-graph.min.js +1 -1
  3. package/package.json +44 -26
  4. package/.prettierrc +0 -8
  5. package/.vscode/extensions.json +0 -5
  6. package/.vscode/settings.json +0 -6
  7. package/assets/packman-demo.png +0 -0
  8. package/dist/pacman-contribution-graph.js +0 -1776
  9. package/dist/pacman-contribution-graph.js.map +0 -1
  10. package/embeded/canvas.html +0 -41
  11. package/github-action/action.yml +0 -16
  12. package/github-action/dist/index.js +0 -26901
  13. package/github-action/package.json +0 -14
  14. package/github-action/pnpm-lock.yaml +0 -77
  15. package/github-action/src/index.js +0 -47
  16. package/index.html +0 -528
  17. package/pacman.abozanona.me/index.js +0 -47
  18. package/pacman.abozanona.me/package.json +0 -8
  19. package/server/api/contributions/route.ts.z +0 -31
  20. package/server/page.zts.z +0 -13
  21. package/src/assets/images/ghosts/blinky.png +0 -0
  22. package/src/assets/images/ghosts/clyde.png +0 -0
  23. package/src/assets/images/ghosts/inky.png +0 -0
  24. package/src/assets/images/ghosts/pinky.png +0 -0
  25. package/src/assets/images/ghosts/scared.png +0 -0
  26. package/src/assets/sounds/pacman_beginning.wav +0 -0
  27. package/src/assets/sounds/pacman_chomp.wav +0 -0
  28. package/src/assets/sounds/pacman_death.wav +0 -0
  29. package/src/assets/sounds/pacman_eatghost.wav +0 -0
  30. package/src/canvas.ts +0 -244
  31. package/src/constants.ts +0 -102
  32. package/src/game.ts +0 -231
  33. package/src/grid.ts +0 -127
  34. package/src/index.ts +0 -48
  35. package/src/movement/ghosts-movement.ts +0 -183
  36. package/src/movement/movement-utils.ts +0 -40
  37. package/src/movement/pacman-movement.ts +0 -230
  38. package/src/music-player.ts +0 -119
  39. package/src/store.ts +0 -23
  40. package/src/svg.ts +0 -254
  41. package/src/types.ts +0 -78
  42. package/src/utils.ts +0 -81
  43. package/tsconfig.json +0 -11
  44. package/webpack.common.js +0 -19
  45. package/webpack.dev.js +0 -23
  46. package/webpack.prod.js +0 -18
package/src/game.ts DELETED
@@ -1,231 +0,0 @@
1
- import { Canvas } from './canvas';
2
- import { DELTA_TIME, GHOST_NAMES, GRID_HEIGHT, GRID_WIDTH, MONTHS, PACMAN_DEATH_DURATION } from './constants';
3
- import { GhostsMovement } from './movement/ghosts-movement';
4
- import { PacmanMovement } from './movement/pacman-movement';
5
- import { MusicPlayer, Sound } from './music-player';
6
- import { SVG } from './svg';
7
- import { StoreType } from './types';
8
-
9
- const initializeGrid = (store: StoreType) => {
10
- store.pacman.points = 0;
11
- store.pacman.totalPoints = 0;
12
- store.grid = Array.from({ length: GRID_WIDTH }, () => Array.from({ length: GRID_HEIGHT }, () => ({ commitsCount: 0, intensity: 0 })));
13
- store.monthLabels = Array(GRID_WIDTH).fill('');
14
- let maxCommits = 1;
15
-
16
- const now = new Date();
17
- const startOfCurrentWeek = new Date(now);
18
- startOfCurrentWeek.setDate(now.getDate() - now.getDay());
19
-
20
- store.contributions.forEach((contribution) => {
21
- const contributionDate = new Date(contribution.date);
22
- const dayOfWeek = contributionDate.getDay();
23
- const weeksAgo = Math.floor((+startOfCurrentWeek - +contributionDate) / (1000 * 60 * 60 * 24 * 7));
24
-
25
- if (weeksAgo >= 0 && weeksAgo < GRID_WIDTH && dayOfWeek >= 0 && dayOfWeek < GRID_HEIGHT) {
26
- store.grid[GRID_WIDTH - 1 - weeksAgo][dayOfWeek] = { commitsCount: contribution.count, intensity: 0 };
27
- if (contribution.count > maxCommits) maxCommits = contribution.count;
28
- }
29
- });
30
-
31
- for (let x = 0; x < GRID_WIDTH; x++) {
32
- for (let y = 0; y < GRID_HEIGHT; y++) {
33
- if (store.grid[x][y].commitsCount > 0) {
34
- store.grid[x][y].intensity = store.grid[x][y].commitsCount / maxCommits;
35
- }
36
- }
37
- }
38
-
39
- for (let x = 0; x < GRID_WIDTH; x++) {
40
- const weeksAgo = GRID_WIDTH - 1 - x;
41
- const columnDate = new Date(startOfCurrentWeek);
42
- columnDate.setDate(columnDate.getDate() - weeksAgo * 7);
43
- store.monthLabels[x] = MONTHS[columnDate.getMonth()];
44
- }
45
- };
46
-
47
- const placePacman = (store: StoreType) => {
48
- store.pacman = {
49
- x: 0,
50
- y: 0,
51
- direction: 'right',
52
- points: 0,
53
- totalPoints: 0,
54
- deadRemainingDuration: 0,
55
- powerupRemainingDuration: 0,
56
- recentPositions: []
57
- };
58
- if (store.config.outputFormat == 'canvas') Canvas.drawPacman(store);
59
- };
60
-
61
- const placeGhosts = (store: StoreType) => {
62
- store.ghosts = [];
63
- // Center gjosts in mid grid
64
- store.ghosts.push({ x: 23, y: 3, name: GHOST_NAMES[0], scared: false, target: undefined });
65
- store.ghosts.push({ x: 24, y: 3, name: GHOST_NAMES[1], scared: false, target: undefined });
66
- store.ghosts.push({ x: 27, y: 3, name: GHOST_NAMES[2], scared: false, target: undefined });
67
- store.ghosts.push({ x: 28, y: 3, name: GHOST_NAMES[3], scared: false, target: undefined });
68
- if (store.config.outputFormat == 'canvas') Canvas.drawGhosts(store);
69
- };
70
-
71
- const stopGame = async (store: StoreType) => {
72
- clearInterval(store.gameInterval);
73
- };
74
-
75
- const startGame = async (store: StoreType) => {
76
- if (store.config.outputFormat == 'canvas') {
77
- store.config.canvas = store.config.canvas;
78
- Canvas.resizeCanvas(store);
79
- Canvas.listenToSoundController(store);
80
- }
81
-
82
- store.frameCount = 0;
83
- store.ghosts.forEach((ghost) => (ghost.scared = false));
84
-
85
- initializeGrid(store);
86
- if (store.config.outputFormat == 'canvas') Canvas.drawGrid(store);
87
-
88
- if (store.config.outputFormat == 'canvas') {
89
- if (!store.config.enableSounds) {
90
- MusicPlayer.getInstance().mute();
91
- }
92
- await MusicPlayer.getInstance().preloadSounds();
93
- MusicPlayer.getInstance().startDefaultSound();
94
- await MusicPlayer.getInstance().play(Sound.BEGINNING);
95
- }
96
-
97
- const remainingCells = () => store.grid.some((row) => row.some((cell) => cell.intensity > 0));
98
- if (remainingCells()) {
99
- placePacman(store);
100
- placeGhosts(store);
101
- }
102
-
103
- if (store.config.outputFormat == 'svg') {
104
- while (remainingCells()) {
105
- await updateGame(store);
106
- }
107
- // One more time to generate svg
108
- await updateGame(store);
109
- } else {
110
- clearInterval(store.gameInterval);
111
- store.gameInterval = setInterval(async () => await updateGame(store), DELTA_TIME);
112
- }
113
- };
114
-
115
- const updateGame = async (store: StoreType) => {
116
- store.frameCount++;
117
- if (store.frameCount % store.config.gameSpeed !== 0) {
118
- store.gameHistory.push({
119
- pacman: { ...store.pacman },
120
- ghosts: store.ghosts.map((ghost) => ({ ...ghost })),
121
- grid: store.grid.map((row) => [...row.map((col) => col.intensity)])
122
- });
123
- return;
124
- }
125
-
126
- if (store.pacman.deadRemainingDuration) {
127
- store.pacman.deadRemainingDuration--;
128
- if (!store.pacman.deadRemainingDuration) {
129
- // IT'S ALIVE!
130
- placeGhosts(store);
131
- if (store.config.outputFormat == 'canvas')
132
- MusicPlayer.getInstance()
133
- .play(Sound.GAME_OVER)
134
- .then(() => MusicPlayer.getInstance().startDefaultSound());
135
- }
136
- }
137
-
138
- if (store.pacman.powerupRemainingDuration) {
139
- store.pacman.powerupRemainingDuration--;
140
- if (!store.pacman.powerupRemainingDuration) {
141
- store.ghosts.forEach((ghost) => (ghost.scared = false));
142
- store.pacman.points = 0;
143
- }
144
- }
145
-
146
- const remainingCells = store.grid.some((row) => row.some((cell) => cell.intensity > 0));
147
- if (!remainingCells) {
148
- if (store.config.outputFormat == 'canvas') {
149
- clearInterval(store.gameInterval);
150
- if (store.config.outputFormat == 'canvas') {
151
- Canvas.renderGameOver(store);
152
- MusicPlayer.getInstance()
153
- .play(Sound.BEGINNING)
154
- .then(() => MusicPlayer.getInstance().stopDefaultSound());
155
- }
156
- }
157
-
158
- if (store.config.outputFormat == 'svg') {
159
- const animatedSVG = SVG.generateAnimatedSVG(store);
160
- store.config.svgCallback(animatedSVG);
161
- }
162
-
163
- store.config.gameOverCallback();
164
- return;
165
- }
166
-
167
- PacmanMovement.movePacman(store);
168
- checkCollisions(store);
169
- if (!store.pacman.deadRemainingDuration) {
170
- GhostsMovement.moveGhosts(store);
171
- checkCollisions(store);
172
- }
173
-
174
- store.pacmanMouthOpen = !store.pacmanMouthOpen;
175
-
176
- store.gameHistory.push({
177
- pacman: { ...store.pacman },
178
- ghosts: store.ghosts.map((ghost) => ({ ...ghost })),
179
- grid: store.grid.map((row) => [...row.map((col) => col.intensity)])
180
- });
181
-
182
- if (store.config.outputFormat == 'canvas') Canvas.drawGrid(store);
183
- if (store.config.outputFormat == 'canvas') Canvas.drawPacman(store);
184
- if (store.config.outputFormat == 'canvas') Canvas.drawGhosts(store);
185
- if (store.config.outputFormat == 'canvas') Canvas.drawSoundController(store);
186
- };
187
-
188
- const checkCollisions = (store: StoreType) => {
189
- if (store.pacman.deadRemainingDuration) return;
190
-
191
- store.ghosts.forEach((ghost, index) => {
192
- if (ghost.x === store.pacman.x && ghost.y === store.pacman.y) {
193
- if (store.pacman.powerupRemainingDuration && ghost.scared) {
194
- respawnGhost(store, index);
195
- store.pacman.points += 10;
196
- if (store.config.outputFormat == 'canvas') {
197
- MusicPlayer.getInstance().play(Sound.EAT_GHOST);
198
- }
199
- } else {
200
- store.pacman.points = 0;
201
- store.pacman.powerupRemainingDuration = 0;
202
- store.pacman.deadRemainingDuration = PACMAN_DEATH_DURATION;
203
- if (store.config.outputFormat == 'canvas') {
204
- MusicPlayer.getInstance()
205
- .play(Sound.GAME_OVER)
206
- .then(() => MusicPlayer.getInstance().stopDefaultSound());
207
- }
208
- }
209
- }
210
- });
211
- };
212
-
213
- const respawnGhost = (store: StoreType, ghostIndex: number) => {
214
- let x, y;
215
- do {
216
- x = Math.floor(Math.random() * GRID_WIDTH);
217
- y = Math.floor(Math.random() * GRID_HEIGHT);
218
- } while ((Math.abs(x - store.pacman.x) <= 2 && Math.abs(y - store.pacman.y) <= 2) || store.grid[x][y].intensity === 0);
219
- store.ghosts[ghostIndex] = {
220
- x,
221
- y,
222
- name: GHOST_NAMES[ghostIndex % GHOST_NAMES.length],
223
- scared: false,
224
- target: undefined
225
- };
226
- };
227
-
228
- export const Game = {
229
- startGame,
230
- stopGame
231
- };
package/src/grid.ts DELETED
@@ -1,127 +0,0 @@
1
- import { GRID_HEIGHT, GRID_WIDTH, setWall } from './constants';
2
-
3
- const setSymmetricWall = (x: number, y: number, direction: 'horizontal' | 'vertical', sym: '' | 'x' | 'y' | 'xy', lineId: string) => {
4
- if (direction == 'horizontal') {
5
- setWall(x, y, 'horizontal', lineId);
6
- if (sym == 'x') {
7
- setWall(GRID_WIDTH - x - 1, y, 'horizontal', lineId);
8
- } else if (sym == 'y') {
9
- setWall(x, GRID_HEIGHT - y, 'horizontal', lineId);
10
- } else if (sym == 'xy') {
11
- setWall(GRID_WIDTH - x - 1, y, 'horizontal', lineId);
12
- setWall(x, GRID_HEIGHT - y, 'horizontal', lineId);
13
- setWall(GRID_WIDTH - x - 1, GRID_HEIGHT - y, 'horizontal', lineId);
14
- }
15
- } else {
16
- setWall(x, y, 'vertical', lineId);
17
- if (sym == 'x') {
18
- setWall(GRID_WIDTH - x, y, 'vertical', lineId);
19
- } else if (sym == 'y') {
20
- setWall(x, GRID_HEIGHT - y - 1, 'vertical', lineId);
21
- } else if (sym == 'xy') {
22
- setWall(GRID_WIDTH - x, y, 'vertical', lineId);
23
- setWall(x, GRID_HEIGHT - y - 1, 'vertical', lineId);
24
- setWall(GRID_WIDTH - x, GRID_HEIGHT - y - 1, 'vertical', lineId);
25
- }
26
- }
27
- };
28
-
29
- const buildWalls = () => {
30
- // Left and right wings
31
- // L1
32
- setSymmetricWall(0, 2, 'horizontal', 'xy', 'L1');
33
- setSymmetricWall(1, 2, 'horizontal', 'xy', 'L1');
34
- // L2
35
- setSymmetricWall(4, 0, 'vertical', 'x', 'L2');
36
- setSymmetricWall(4, 1, 'vertical', 'x', 'L2');
37
- setSymmetricWall(4, 2, 'vertical', 'x', 'L2');
38
- setSymmetricWall(4, 3, 'vertical', 'x', 'L2');
39
- setSymmetricWall(4, 4, 'vertical', 'x', 'L2');
40
- // L3
41
- setSymmetricWall(3, 3, 'horizontal', 'x', 'L3');
42
- setSymmetricWall(2, 3, 'horizontal', 'x', 'L3');
43
- // L4
44
- setSymmetricWall(4, 5, 'horizontal', 'x', 'L4');
45
- // L5
46
- setSymmetricWall(6, 4, 'vertical', 'x', 'L5');
47
- setSymmetricWall(6, 3, 'vertical', 'x', 'L5');
48
- setSymmetricWall(6, 2, 'vertical', 'x', 'L5');
49
- // L6
50
- setSymmetricWall(6, 2, 'horizontal', 'x', 'L6');
51
- setSymmetricWall(7, 2, 'horizontal', 'x', 'L6');
52
- setSymmetricWall(8, 2, 'horizontal', 'x', 'L6');
53
- setSymmetricWall(9, 2, 'horizontal', 'x', 'L6');
54
- // L7
55
- setSymmetricWall(13, 2, 'horizontal', 'xy', 'L7');
56
- setSymmetricWall(14, 2, 'horizontal', 'xy', 'L7');
57
- setSymmetricWall(15, 2, 'horizontal', 'xy', 'L7');
58
- setSymmetricWall(16, 2, 'horizontal', 'xy', 'L7');
59
- setSymmetricWall(17, 2, 'horizontal', 'xy', 'L7');
60
- setSymmetricWall(18, 2, 'horizontal', 'xy', 'L7');
61
- // L8
62
- setSymmetricWall(16, 2, 'vertical', 'xy', 'L8');
63
- // L9
64
- setSymmetricWall(8, 1, 'horizontal', 'x', 'L9');
65
- setSymmetricWall(9, 1, 'horizontal', 'x', 'L9');
66
- setSymmetricWall(10, 1, 'horizontal', 'x', 'L9');
67
- setSymmetricWall(11, 1, 'horizontal', 'x', 'L9');
68
- // L10
69
- setSymmetricWall(12, 1, 'vertical', 'x', 'L10');
70
- setSymmetricWall(12, 3, 'vertical', 'x', 'L10');
71
- // L11
72
- setSymmetricWall(11, 4, 'horizontal', 'x', 'L11');
73
- setSymmetricWall(10, 4, 'horizontal', 'x', 'L11');
74
- setSymmetricWall(9, 4, 'horizontal', 'x', 'L11');
75
- setSymmetricWall(8, 4, 'horizontal', 'x', 'L11');
76
- // L12
77
- setSymmetricWall(8, 4, 'vertical', 'x', 'L12');
78
- setSymmetricWall(8, 5, 'vertical', 'x', 'L12');
79
- setSymmetricWall(8, 6, 'vertical', 'x', 'L12');
80
- // L13
81
- setSymmetricWall(23, 2, 'horizontal', 'x', 'L13');
82
- setSymmetricWall(24, 2, 'horizontal', 'x', 'L13');
83
- setSymmetricWall(23, 4, 'horizontal', 'x', 'L13');
84
- setSymmetricWall(24, 4, 'horizontal', 'x', 'L13');
85
- setSymmetricWall(25, 4, 'horizontal', 'x', 'L13');
86
- // L14
87
- setSymmetricWall(23, 2, 'vertical', 'x', 'L14');
88
- setSymmetricWall(23, 3, 'vertical', 'x', 'L14');
89
- // L15
90
- setSymmetricWall(26, 4, 'vertical', 'x', 'L15');
91
- setSymmetricWall(26, 5, 'vertical', 'x', 'L15');
92
- // L16
93
- setSymmetricWall(23, 6, 'horizontal', 'x', 'L16');
94
- setSymmetricWall(24, 6, 'horizontal', 'x', 'L16');
95
- setSymmetricWall(25, 6, 'horizontal', 'x', 'L16');
96
- // L17
97
- setSymmetricWall(26, 0, 'vertical', 'x', 'L17');
98
- // L18
99
- setSymmetricWall(24, 1, 'vertical', 'x', 'L18');
100
- setSymmetricWall(23, 1, 'horizontal', 'x', 'L18');
101
- setSymmetricWall(22, 1, 'horizontal', 'x', 'L18');
102
- setSymmetricWall(21, 1, 'horizontal', 'x', 'L18');
103
- setSymmetricWall(21, 1, 'vertical', 'x', 'L18');
104
- setSymmetricWall(21, 2, 'vertical', 'x', 'L18');
105
- setSymmetricWall(21, 3, 'vertical', 'x', 'L18');
106
- setSymmetricWall(20, 4, 'horizontal', 'x', 'L18');
107
- setSymmetricWall(19, 4, 'horizontal', 'x', 'L18');
108
- setSymmetricWall(19, 3, 'vertical', 'x', 'L18');
109
- setSymmetricWall(18, 3, 'horizontal', 'x', 'L18');
110
- // L19
111
- setSymmetricWall(22, 5, 'vertical', 'x', 'L19');
112
- setSymmetricWall(21, 5, 'horizontal', 'x', 'L19');
113
- setSymmetricWall(20, 5, 'horizontal', 'x', 'L19');
114
- setSymmetricWall(20, 5, 'vertical', 'x', 'L19');
115
- // L20
116
- setSymmetricWall(1, 6, 'horizontal', 'x', 'L20');
117
- setSymmetricWall(2, 6, 'horizontal', 'x', 'L20');
118
- setSymmetricWall(3, 5, 'vertical', 'x', 'L20');
119
- setSymmetricWall(3, 4, 'vertical', 'x', 'L20');
120
- // L21
121
- setSymmetricWall(5, 6, 'horizontal', 'x', 'L21');
122
- setSymmetricWall(6, 6, 'horizontal', 'x', 'L21');
123
- };
124
-
125
- export const Grid = {
126
- buildWalls
127
- };
package/src/index.ts DELETED
@@ -1,48 +0,0 @@
1
- import { Game } from './game';
2
- import { Grid } from './grid';
3
- import { Store } from './store';
4
- import { Config, StoreType } from './types';
5
- import { Utils } from './utils';
6
-
7
- export class PacmanRenderer {
8
- store: StoreType;
9
- conf: Config;
10
-
11
- constructor(conf: Config) {
12
- this.store = structuredClone(Store);
13
- this.conf = { ...conf };
14
- Grid.buildWalls();
15
- }
16
-
17
- public async start() {
18
- const defaultConfing: Config = {
19
- platform: 'github',
20
- username: '',
21
- canvas: undefined as unknown as HTMLCanvasElement,
22
- outputFormat: 'svg',
23
- svgCallback: (_: string) => {},
24
- gameOverCallback: () => () => {},
25
- gameTheme: 'github',
26
- gameSpeed: 1,
27
- enableSounds: false,
28
- pointsIncreasedCallback: (_: number) => {}
29
- };
30
- this.store.config = { ...defaultConfing, ...this.conf };
31
-
32
- switch (this.conf.platform) {
33
- case 'gitlab':
34
- this.store.contributions = await Utils.getGitlabContribution(this.store);
35
- break;
36
-
37
- case 'github':
38
- this.store.contributions = await Utils.getGithubContribution(this.store);
39
- break;
40
- }
41
-
42
- Game.startGame(this.store);
43
- }
44
-
45
- public stop() {
46
- Game.stopGame(this.store);
47
- }
48
- }
@@ -1,183 +0,0 @@
1
- import { GRID_HEIGHT, GRID_WIDTH } from '../constants';
2
- import { Ghost, Point2d, StoreType } from '../types';
3
- import { MovementUtils } from './movement-utils';
4
-
5
- const moveGhosts = (store: StoreType) => {
6
- store.ghosts.forEach((ghost) => {
7
- if (ghost.scared || Math.random() < 0.15) {
8
- moveScaredGhost(ghost, store);
9
- } else {
10
- moveGhostWithPersonality(ghost, store);
11
- }
12
- });
13
- };
14
-
15
- // When scared, ghosts move randomly but with some intelligence
16
- const moveScaredGhost = (ghost: Ghost, store: StoreType) => {
17
- if (!ghost.target || (ghost.x === ghost.target.x && ghost.y === ghost.target.y)) {
18
- ghost.target = getRandomDestination(ghost.x, ghost.y);
19
- }
20
-
21
- const validMoves = MovementUtils.getValidMoves(ghost.x, ghost.y);
22
- if (validMoves.length === 0) return;
23
-
24
- // Move toward target but with some randomness to appear "scared"
25
- const dx = ghost.target.x - ghost.x;
26
- const dy = ghost.target.y - ghost.y;
27
-
28
- // Filter moves that generally go toward the target
29
- let possibleMoves = validMoves.filter((move) => {
30
- const moveX = move[0];
31
- const moveY = move[1];
32
- return (dx > 0 && moveX > 0) || (dx < 0 && moveX < 0) || (dy > 0 && moveY > 0) || (dy < 0 && moveY < 0);
33
- });
34
-
35
- // If no valid moves toward target, use any valid move
36
- if (possibleMoves.length === 0) {
37
- possibleMoves = validMoves;
38
- }
39
-
40
- // Choose a random move from the possible moves
41
- const [moveX, moveY] = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
42
-
43
- // If Pacman has power-up, ghosts move slower
44
- if (store.pacman.powerupRemainingDuration && Math.random() < 0.5) return;
45
-
46
- ghost.x += moveX;
47
- ghost.y += moveY;
48
- };
49
-
50
- // Move ghost according to its personality
51
- const moveGhostWithPersonality = (ghost: Ghost, store: StoreType) => {
52
- const target = calculateGhostTarget(ghost, store);
53
- ghost.target = target;
54
-
55
- const nextMove = BFSTargetLocation(ghost.x, ghost.y, target.x, target.y);
56
- if (nextMove) {
57
- ghost.x = nextMove.x;
58
- ghost.y = nextMove.y;
59
- }
60
- };
61
-
62
- // Find the next position to move to using BFS
63
- const BFSTargetLocation = (startX: number, startY: number, targetX: number, targetY: number): Point2d | null => {
64
- // If we're already at the target, no need to move
65
- if (startX === targetX && startY === targetY) return null;
66
-
67
- const queue: { x: number; y: number; path: Point2d[] }[] = [{ x: startX, y: startY, path: [] }];
68
- const visited = new Set<string>();
69
- visited.add(`${startX},${startY}`);
70
-
71
- while (queue.length > 0) {
72
- const current = queue.shift()!;
73
- const { x, y, path } = current;
74
-
75
- const validMoves = MovementUtils.getValidMoves(x, y);
76
-
77
- for (const [dx, dy] of validMoves) {
78
- const newX = x + dx;
79
- const newY = y + dy;
80
- const key = `${newX},${newY}`;
81
-
82
- if (visited.has(key)) continue;
83
- visited.add(key);
84
-
85
- const newPath = [...path, { x: newX, y: newY }];
86
-
87
- if (newX === targetX && newY === targetY) {
88
- return newPath.length > 0 ? newPath[0] : null;
89
- }
90
-
91
- queue.push({ x: newX, y: newY, path: newPath });
92
- }
93
- }
94
-
95
- // If no path found, no need to move
96
- return null;
97
- };
98
-
99
- // Calculate ghost target based on personality
100
- const calculateGhostTarget = (ghost: Ghost, store: StoreType): Point2d => {
101
- const { pacman } = store;
102
- let pacDirection = [0, 0];
103
- switch (ghost.name) {
104
- case 'blinky': // Red ghost - directly targets Pacman
105
- return { x: pacman.x, y: pacman.y };
106
-
107
- case 'pinky': // Pink ghost - targets 4 spaces ahead of Pacman
108
- pacDirection = getPacmanDirection(store);
109
-
110
- const lookAhead = 4;
111
- let fourAhead = {
112
- x: pacman.x + pacDirection[0] * lookAhead,
113
- y: pacman.y + pacDirection[1] * lookAhead
114
- };
115
-
116
- fourAhead.x = Math.min(Math.max(fourAhead.x, 0), GRID_WIDTH - 1);
117
- fourAhead.y = Math.min(Math.max(fourAhead.y, 0), GRID_HEIGHT - 1);
118
- return fourAhead;
119
-
120
- case 'inky': // Blue ghost - complex targeting based on Blinky's position
121
- const blinky = store.ghosts.find((g) => g.name === 'blinky');
122
- pacDirection = getPacmanDirection(store);
123
-
124
- // Target is 2 spaces ahead of Pacman
125
- let twoAhead = {
126
- x: pacman.x + pacDirection[0] * 2,
127
- y: pacman.y + pacDirection[1] * 2
128
- };
129
-
130
- // Then double the vector from Blinky to that position
131
- if (blinky) {
132
- twoAhead = {
133
- x: twoAhead.x + (twoAhead.x - blinky.x),
134
- y: twoAhead.y + (twoAhead.y - blinky.y)
135
- };
136
- }
137
- twoAhead.x = Math.min(Math.max(twoAhead.x, 0), GRID_WIDTH - 1);
138
- twoAhead.y = Math.min(Math.max(twoAhead.y, 0), GRID_HEIGHT - 1);
139
- return twoAhead;
140
-
141
- case 'clyde': // Orange ghost - targets Pacman when far, runs away when close
142
- const distanceToPacman = MovementUtils.calculateDistance(ghost.x, ghost.y, pacman.x, pacman.y);
143
- if (distanceToPacman > 8) {
144
- return { x: pacman.x, y: pacman.y };
145
- } else {
146
- return { x: 0, y: GRID_HEIGHT - 1 };
147
- }
148
-
149
- default:
150
- // Default behavior targets Pacman directly
151
- return { x: pacman.x, y: pacman.y };
152
- }
153
- };
154
-
155
- const getPacmanDirection = (store: StoreType): [number, number] => {
156
- switch (store.pacman.direction) {
157
- case 'right':
158
- return [1, 0];
159
- case 'left':
160
- return [-1, 0];
161
- case 'up':
162
- return [0, -1];
163
- case 'down':
164
- return [0, 1];
165
- default:
166
- return [0, 0];
167
- }
168
- };
169
-
170
- // Get a random destination for scared ghosts
171
- const getRandomDestination = (x: number, y: number) => {
172
- const maxDistance = 8;
173
- const randomX = x + Math.floor(Math.random() * (2 * maxDistance + 1)) - maxDistance;
174
- const randomY = y + Math.floor(Math.random() * (2 * maxDistance + 1)) - maxDistance;
175
- return {
176
- x: Math.max(0, Math.min(randomX, GRID_WIDTH - 1)),
177
- y: Math.max(0, Math.min(randomY, GRID_HEIGHT - 1))
178
- };
179
- };
180
-
181
- export const GhostsMovement = {
182
- moveGhosts
183
- };
@@ -1,40 +0,0 @@
1
- import { GRID_HEIGHT, GRID_WIDTH, WALLS } from '../constants';
2
-
3
- // Check for walls and grid edges
4
- const getValidMoves = (x: number, y: number): [number, number][] => {
5
- const directions: [number, number][] = [
6
- [-1, 0], // left
7
- [1, 0], // right
8
- [0, -1], // up
9
- [0, 1] // down
10
- ];
11
- return directions.filter(([dx, dy]) => {
12
- const newX = x + dx;
13
- const newY = y + dy;
14
-
15
- if (newX < 0 || newX >= GRID_WIDTH || newY < 0 || newY >= GRID_HEIGHT) {
16
- return false;
17
- }
18
-
19
- if (dx === -1) {
20
- return !WALLS.vertical[x][y].active;
21
- } else if (dx === 1) {
22
- return !WALLS.vertical[x + 1][y].active;
23
- } else if (dy === -1) {
24
- return !WALLS.horizontal[x][y].active;
25
- } else if (dy === 1) {
26
- return !WALLS.horizontal[x][y + 1].active;
27
- }
28
-
29
- return true;
30
- });
31
- };
32
-
33
- const calculateDistance = (x1: number, y1: number, x2: number, y2: number): number => {
34
- return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
35
- };
36
-
37
- export const MovementUtils = {
38
- getValidMoves,
39
- calculateDistance
40
- };