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.
- package/README.md +59 -9
- package/dist/pacman-contribution-graph.min.js +1 -1
- package/package.json +44 -26
- package/.prettierrc +0 -8
- package/.vscode/extensions.json +0 -5
- package/.vscode/settings.json +0 -6
- package/assets/packman-demo.png +0 -0
- package/dist/pacman-contribution-graph.js +0 -1776
- package/dist/pacman-contribution-graph.js.map +0 -1
- package/embeded/canvas.html +0 -41
- package/github-action/action.yml +0 -16
- package/github-action/dist/index.js +0 -26901
- package/github-action/package.json +0 -14
- package/github-action/pnpm-lock.yaml +0 -77
- package/github-action/src/index.js +0 -47
- package/index.html +0 -528
- package/pacman.abozanona.me/index.js +0 -47
- package/pacman.abozanona.me/package.json +0 -8
- package/server/api/contributions/route.ts.z +0 -31
- package/server/page.zts.z +0 -13
- package/src/assets/images/ghosts/blinky.png +0 -0
- package/src/assets/images/ghosts/clyde.png +0 -0
- package/src/assets/images/ghosts/inky.png +0 -0
- package/src/assets/images/ghosts/pinky.png +0 -0
- package/src/assets/images/ghosts/scared.png +0 -0
- package/src/assets/sounds/pacman_beginning.wav +0 -0
- package/src/assets/sounds/pacman_chomp.wav +0 -0
- package/src/assets/sounds/pacman_death.wav +0 -0
- package/src/assets/sounds/pacman_eatghost.wav +0 -0
- package/src/canvas.ts +0 -244
- package/src/constants.ts +0 -102
- package/src/game.ts +0 -231
- package/src/grid.ts +0 -127
- package/src/index.ts +0 -48
- package/src/movement/ghosts-movement.ts +0 -183
- package/src/movement/movement-utils.ts +0 -40
- package/src/movement/pacman-movement.ts +0 -230
- package/src/music-player.ts +0 -119
- package/src/store.ts +0 -23
- package/src/svg.ts +0 -254
- package/src/types.ts +0 -78
- package/src/utils.ts +0 -81
- package/tsconfig.json +0 -11
- package/webpack.common.js +0 -19
- package/webpack.dev.js +0 -23
- 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
|
-
};
|