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
@@ -1,230 +0,0 @@
1
- import { GRID_HEIGHT, GRID_WIDTH, PACMAN_POWERUP_DURATION } from '../constants';
2
- import { Point2d, StoreType } from '../types';
3
- import { MovementUtils } from './movement-utils';
4
-
5
- const RECENT_POSITIONS_LIMIT = 5;
6
-
7
- const movePacman = (store: StoreType) => {
8
- if (store.pacman.deadRemainingDuration) {
9
- return;
10
- }
11
-
12
- const hasPowerup = !!store.pacman.powerupRemainingDuration;
13
- const scaredGhosts = store.ghosts.filter((ghost) => ghost.scared);
14
-
15
- let targetPosition: Point2d;
16
-
17
- if (hasPowerup && scaredGhosts.length > 0) {
18
- const ghostPosition = findClosestScaredGhost(store);
19
- if (ghostPosition) {
20
- targetPosition = ghostPosition;
21
- } else {
22
- targetPosition = findOptimalTarget(store);
23
- }
24
- } else if (store.pacman.target) {
25
- if (store.pacman.target.x == store.pacman.x && store.pacman.target.y == store.pacman.y) {
26
- targetPosition = findOptimalTarget(store);
27
- store.pacman.target = { x: targetPosition?.x, y: targetPosition?.y };
28
- } else {
29
- targetPosition = store.pacman.target;
30
- }
31
- } else {
32
- targetPosition = findOptimalTarget(store);
33
- store.pacman.target = { x: targetPosition?.x, y: targetPosition?.y };
34
- }
35
-
36
- const nextPosition = calculateOptimalPath(store, targetPosition);
37
-
38
- if (nextPosition) {
39
- updatePacmanPosition(store, nextPosition);
40
- } else {
41
- makeDesperationMove(store);
42
- }
43
-
44
- checkAndEatPoint(store);
45
- };
46
-
47
- const findClosestScaredGhost = (store: StoreType) => {
48
- const scaredGhosts = store.ghosts.filter((ghost) => ghost.scared);
49
-
50
- if (scaredGhosts.length === 0) return null;
51
-
52
- return scaredGhosts.reduce(
53
- (closest, ghost) => {
54
- const distance = MovementUtils.calculateDistance(ghost.x, ghost.y, store.pacman.x, store.pacman.y);
55
- return distance < closest.distance ? { x: ghost.x, y: ghost.y, distance } : closest;
56
- },
57
- { x: store.pacman.x, y: store.pacman.y, distance: Infinity }
58
- );
59
- };
60
-
61
- const findOptimalTarget = (store: StoreType) => {
62
- let pointCells: { x: number; y: number; value: number }[] = [];
63
-
64
- for (let x = 0; x < GRID_WIDTH; x++) {
65
- for (let y = 0; y < GRID_HEIGHT; y++) {
66
- if (store.grid[x][y].intensity > 0) {
67
- const distance = MovementUtils.calculateDistance(x, y, store.pacman.x, store.pacman.y);
68
- const value = store.grid[x][y].intensity / (distance + 1);
69
- pointCells.push({ x, y, value });
70
- }
71
- }
72
- }
73
-
74
- pointCells.sort((a, b) => b.value - a.value);
75
- return pointCells[0];
76
- };
77
-
78
- const calculateOptimalPath = (store: StoreType, target: Point2d) => {
79
- const queue: { x: number; y: number; path: Point2d[]; score: number }[] = [
80
- { x: store.pacman.x, y: store.pacman.y, path: [], score: 0 }
81
- ];
82
- const visited = new Set<string>();
83
- visited.add(`${store.pacman.x},${store.pacman.y}`);
84
-
85
- const dangerMap = createDangerMap(store);
86
-
87
- while (queue.length > 0) {
88
- queue.sort((a, b) => b.score - a.score);
89
- const current = queue.shift()!;
90
- const { x, y, path } = current;
91
-
92
- if (x === target.x && y === target.y) {
93
- return path.length > 0 ? path[0] : null;
94
- }
95
-
96
- const validMoves = MovementUtils.getValidMoves(x, y);
97
-
98
- for (const [dx, dy] of validMoves) {
99
- const newX = x + dx;
100
- const newY = y + dy;
101
- const key = `${newX},${newY}`;
102
-
103
- if (!visited.has(key)) {
104
- const newPath = [...path, { x: newX, y: newY }];
105
- const danger = dangerMap.get(key) || 0;
106
- const pointValue = store.grid[newX][newY].intensity;
107
- const distanceToTarget = MovementUtils.calculateDistance(newX, newY, target.x, target.y);
108
-
109
- let revisitPenalty = 0;
110
- if (store.pacman.recentPositions?.includes(key)) {
111
- revisitPenalty += 100; // Penalize recently visited positions
112
- }
113
-
114
- queue.push({
115
- x: newX,
116
- y: newY,
117
- path: newPath,
118
- score: pointValue - danger - distanceToTarget / 10 - revisitPenalty
119
- });
120
- visited.add(key);
121
- }
122
- }
123
- }
124
-
125
- return null;
126
- };
127
-
128
- const createDangerMap = (store: StoreType) => {
129
- const dangerMap = new Map<string, number>();
130
- const hasPowerup = !!store.pacman.powerupRemainingDuration;
131
-
132
- store.ghosts.forEach((ghost) => {
133
- if (ghost.scared) return;
134
-
135
- for (let dx = -5; dx <= 5; dx++) {
136
- for (let dy = -5; dy <= 5; dy++) {
137
- const x = ghost.x + dx;
138
- const y = ghost.y + dy;
139
-
140
- if (x >= 0 && x < GRID_WIDTH && y >= 0 && y < GRID_HEIGHT) {
141
- const key = `${x},${y}`;
142
- const distance = Math.abs(dx) + Math.abs(dy);
143
- const dangerValue = 15 - distance;
144
-
145
- if (dangerValue > 0) {
146
- const currentDanger = dangerMap.get(key) || 0;
147
- dangerMap.set(key, Math.max(currentDanger, dangerValue));
148
- }
149
- }
150
- }
151
- }
152
- });
153
-
154
- if (hasPowerup) {
155
- for (const [key, value] of dangerMap.entries()) {
156
- dangerMap.set(key, value / 5);
157
- }
158
- }
159
-
160
- return dangerMap;
161
- };
162
-
163
- const makeDesperationMove = (store: StoreType) => {
164
- const validMoves = MovementUtils.getValidMoves(store.pacman.x, store.pacman.y);
165
-
166
- if (validMoves.length === 0) return;
167
-
168
- const safestMove = validMoves.reduce(
169
- (safest, [dx, dy]) => {
170
- const newX = store.pacman.x + dx;
171
- const newY = store.pacman.y + dy;
172
-
173
- let minGhostDistance = Infinity;
174
-
175
- store.ghosts.forEach((ghost) => {
176
- if (!ghost.scared) {
177
- const distance = MovementUtils.calculateDistance(ghost.x, ghost.y, newX, newY);
178
- minGhostDistance = Math.min(minGhostDistance, distance);
179
- }
180
- });
181
-
182
- return minGhostDistance > safest.distance ? { dx, dy, distance: minGhostDistance } : safest;
183
- },
184
- { dx: 0, dy: 0, distance: -Infinity }
185
- );
186
-
187
- const newX = store.pacman.x + safestMove.dx;
188
- const newY = store.pacman.y + safestMove.dy;
189
-
190
- updatePacmanPosition(store, { x: newX, y: newY });
191
- };
192
-
193
- const updatePacmanPosition = (store: StoreType, position: Point2d) => {
194
- store.pacman.recentPositions ||= [];
195
- store.pacman.recentPositions.push(`${position.x},${position.y}`);
196
- if (store.pacman.recentPositions.length > RECENT_POSITIONS_LIMIT) {
197
- store.pacman.recentPositions.shift();
198
- }
199
-
200
- const dx = position.x - store.pacman.x;
201
- const dy = position.y - store.pacman.y;
202
-
203
- if (dx > 0) store.pacman.direction = 'right';
204
- else if (dx < 0) store.pacman.direction = 'left';
205
- else if (dy > 0) store.pacman.direction = 'down';
206
- else if (dy < 0) store.pacman.direction = 'up';
207
-
208
- store.pacman.x = position.x;
209
- store.pacman.y = position.y;
210
- };
211
-
212
- const checkAndEatPoint = (store: StoreType) => {
213
- if (store.grid[store.pacman.x][store.pacman.y].intensity > 0) {
214
- store.pacman.totalPoints += store.grid[store.pacman.x][store.pacman.y].commitsCount;
215
- store.pacman.points++;
216
- store.config.pointsIncreasedCallback(store.pacman.totalPoints);
217
- store.grid[store.pacman.x][store.pacman.y].intensity = 0;
218
-
219
- if (store.pacman.points >= 10) activatePowerUp(store);
220
- }
221
- };
222
-
223
- const activatePowerUp = (store: StoreType) => {
224
- store.pacman.powerupRemainingDuration = PACMAN_POWERUP_DURATION;
225
- store.ghosts.forEach((ghost) => (ghost.scared = true));
226
- };
227
-
228
- export const PacmanMovement = {
229
- movePacman
230
- };
@@ -1,119 +0,0 @@
1
- export enum Sound {
2
- DEFAULT = 'https://cdn.jsdelivr.net/npm/pacman-contribution-graph/src/assets/sounds/pacman_chomp.wav',
3
- BEGINNING = 'https://cdn.jsdelivr.net/npm/pacman-contribution-graph/src/assets/sounds/pacman_beginning.wav',
4
- GAME_OVER = 'https://cdn.jsdelivr.net/npm/pacman-contribution-graph/src/assets/sounds/pacman_death.wav',
5
- EAT_GHOST = 'https://cdn.jsdelivr.net/npm/pacman-contribution-graph/src/assets/sounds/pacman_eatghost.wav'
6
- }
7
-
8
- export class MusicPlayer {
9
- private static instance: MusicPlayer;
10
- private audioContext: AudioContext;
11
- private sounds: Map<Sound, AudioBuffer> = new Map();
12
- private currentSource: AudioBufferSourceNode | null = null;
13
- private defaultSource: AudioBufferSourceNode | null = null;
14
- public isMuted: boolean = false;
15
-
16
- private constructor() {
17
- this.audioContext = new AudioContext();
18
- }
19
-
20
- public static getInstance(): MusicPlayer {
21
- if (!MusicPlayer.instance) {
22
- MusicPlayer.instance = new MusicPlayer();
23
- }
24
- return MusicPlayer.instance;
25
- }
26
-
27
- public async preloadSounds(): Promise<void> {
28
- for (const sound of Object.values(Sound)) {
29
- const response = await fetch(sound);
30
- const arrayBuffer = await response.arrayBuffer();
31
- const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
32
- this.sounds.set(sound as Sound, audioBuffer);
33
- }
34
- }
35
-
36
- public async play(sound: Sound): Promise<void> {
37
- if (this.isMuted) {
38
- return;
39
- }
40
- if (this.currentSource) {
41
- try {
42
- this.currentSource.stop();
43
- } catch (ex) {}
44
- }
45
-
46
- const buffer = this.sounds.get(sound);
47
- if (!buffer) {
48
- console.error(`Sound ${sound} not found`);
49
- return;
50
- }
51
-
52
- this.currentSource = this.audioContext.createBufferSource();
53
- this.currentSource.buffer = buffer;
54
- this.currentSource.connect(this.audioContext.destination);
55
-
56
- if (!this.isMuted) {
57
- this.currentSource.start();
58
- }
59
-
60
- return new Promise((resolve) => {
61
- this.currentSource!.onended = () => {
62
- this.currentSource = null;
63
- resolve();
64
- };
65
- });
66
- }
67
-
68
- public startDefaultSound(): void {
69
- if (this.defaultSource) {
70
- try {
71
- this.defaultSource.stop();
72
- } catch (ex) {}
73
- }
74
-
75
- const buffer = this.sounds.get(Sound.DEFAULT);
76
- if (!buffer) {
77
- console.error('Default sound not found');
78
- return;
79
- }
80
-
81
- this.defaultSource = this.audioContext.createBufferSource();
82
- this.defaultSource.buffer = buffer;
83
- this.defaultSource.loop = true;
84
- this.defaultSource.connect(this.audioContext.destination);
85
-
86
- if (!this.isMuted) {
87
- this.defaultSource.start();
88
- }
89
- }
90
-
91
- public stopDefaultSound(): void {
92
- if (this.defaultSource) {
93
- try {
94
- this.defaultSource.stop();
95
- } catch (ex) {}
96
- this.defaultSource = null;
97
- }
98
- }
99
-
100
- public mute(): void {
101
- this.isMuted = true;
102
- if (this.currentSource) {
103
- this.currentSource.disconnect();
104
- }
105
- if (this.defaultSource) {
106
- this.defaultSource.disconnect();
107
- }
108
- }
109
-
110
- public unmute(): void {
111
- this.isMuted = false;
112
- if (this.currentSource) {
113
- this.currentSource.connect(this.audioContext.destination);
114
- }
115
- if (this.defaultSource) {
116
- this.defaultSource.connect(this.audioContext.destination);
117
- }
118
- }
119
- }
package/src/store.ts DELETED
@@ -1,23 +0,0 @@
1
- import type { Config, StoreType } from './types';
2
-
3
- export const Store: StoreType = {
4
- frameCount: 0,
5
- contributions: [],
6
- pacman: {
7
- x: 0,
8
- y: 0,
9
- direction: 'right',
10
- points: 0,
11
- totalPoints: 0,
12
- deadRemainingDuration: 0,
13
- powerupRemainingDuration: 0,
14
- recentPositions: []
15
- },
16
- ghosts: [],
17
- grid: [],
18
- monthLabels: [],
19
- pacmanMouthOpen: true,
20
- gameInterval: 0,
21
- gameHistory: [],
22
- config: undefined as unknown as Config
23
- };
package/src/svg.ts DELETED
@@ -1,254 +0,0 @@
1
- import {
2
- CELL_SIZE,
3
- DELTA_TIME,
4
- GAP_SIZE,
5
- GHOSTS,
6
- GRID_HEIGHT,
7
- GRID_WIDTH,
8
- PACMAN_COLOR,
9
- PACMAN_COLOR_DEAD,
10
- PACMAN_COLOR_POWERUP,
11
- WALLS
12
- } from './constants';
13
- import { AnimationData, StoreType } from './types';
14
- import { Utils } from './utils';
15
-
16
- const generateAnimatedSVG = (store: StoreType) => {
17
- const svgWidth = GRID_WIDTH * (CELL_SIZE + GAP_SIZE);
18
- const svgHeight = GRID_HEIGHT * (CELL_SIZE + GAP_SIZE) + 20;
19
- let svg = `<svg width="${svgWidth}" height="${svgHeight}" xmlns="http://www.w3.org/2000/svg">`;
20
- svg += `<desc>Generated with https://github.com/abozanona/pacman-contribution-graph on ${new Date()}</desc>`;
21
- svg += `<rect width="100%" height="100%" fill="${Utils.getCurrentTheme(store).gridBackground}"/>`;
22
-
23
- svg += generateGhostsPredefinition();
24
-
25
- // Month labels
26
- let lastMonth = '';
27
- for (let y = 0; y < GRID_WIDTH; y++) {
28
- if (store.monthLabels[y] !== lastMonth) {
29
- const xPos = y * (CELL_SIZE + GAP_SIZE) + CELL_SIZE / 2;
30
- svg += `<text x="${xPos}" y="10" text-anchor="middle" font-size="10" fill="${Utils.getCurrentTheme(store).textColor}">${store.monthLabels[y]}</text>`;
31
- lastMonth = store.monthLabels[y];
32
- }
33
- }
34
-
35
- // Grid
36
- for (let x = 0; x < GRID_WIDTH; x++) {
37
- for (let y = 0; y < GRID_HEIGHT; y++) {
38
- const cellX = x * (CELL_SIZE + GAP_SIZE);
39
- const cellY = y * (CELL_SIZE + GAP_SIZE) + 15;
40
- const cellColorAnimation = generateChangingValuesAnimation(store, generateCellColorValues(store, x, y));
41
- svg += `<rect id="c-${x}-${y}" x="${cellX}" y="${cellY}" width="${CELL_SIZE}" height="${CELL_SIZE}" rx="5" fill="${Utils.getCurrentTheme(store).emptyContributionBoxColor}">
42
- <animate attributeName="fill" dur="${store.gameHistory.length * DELTA_TIME}ms" repeatCount="indefinite"
43
- values="${cellColorAnimation.values}"
44
- keyTimes="${cellColorAnimation.keyTimes}"/>
45
- </rect>`;
46
- }
47
- }
48
-
49
- // Walls
50
- for (let x = 0; x < GRID_WIDTH; x++) {
51
- for (let y = 0; y < GRID_HEIGHT; y++) {
52
- if (WALLS.horizontal[x][y].active) {
53
- svg += `<rect id="wh-${x}-${y}" x="${x * (CELL_SIZE + GAP_SIZE) - GAP_SIZE}" y="${y * (CELL_SIZE + GAP_SIZE) - GAP_SIZE + 15}" width="${CELL_SIZE + GAP_SIZE}" height="${GAP_SIZE}" rx="5" fill="${Utils.getCurrentTheme(store).wallColor}"></rect>`;
54
- }
55
- if (WALLS.vertical[x][y].active) {
56
- svg += `<rect id="wv-${x}-${y}" x="${x * (CELL_SIZE + GAP_SIZE) - GAP_SIZE}" y="${y * (CELL_SIZE + GAP_SIZE) - GAP_SIZE + 15}" width="${GAP_SIZE}" height="${CELL_SIZE + GAP_SIZE}" rx="5" fill="${Utils.getCurrentTheme(store).wallColor}"></rect>`;
57
- }
58
- }
59
- }
60
-
61
- // Pacman
62
- const pacmanColorAnimation = generateChangingValuesAnimation(store, generatePacManColors(store));
63
- const pacmanPositionAnimation = generateChangingValuesAnimation(store, generatePacManPositions(store));
64
- const pacmanRotationAnimation = generateChangingValuesAnimation(store, generatePacManRotations(store));
65
- svg += `<path id="pacman" d="${generatePacManPath(0.55)}"
66
- >
67
- <animate attributeName="fill" dur="${store.gameHistory.length * DELTA_TIME}ms" repeatCount="indefinite"
68
- keyTimes="${pacmanColorAnimation.keyTimes}"
69
- values="${pacmanColorAnimation.values}"/>
70
- <animateTransform attributeName="transform" type="translate" dur="${store.gameHistory.length * DELTA_TIME}ms" repeatCount="indefinite"
71
- keyTimes="${pacmanPositionAnimation.keyTimes}"
72
- values="${pacmanPositionAnimation.values}"
73
- additive="sum"/>
74
- <animateTransform attributeName="transform" type="rotate" dur="${store.gameHistory.length * DELTA_TIME}ms" repeatCount="indefinite"
75
- keyTimes="${pacmanRotationAnimation.keyTimes}"
76
- values="${pacmanRotationAnimation.values}"
77
- additive="sum"/>
78
- <animate attributeName="d" dur="0.5s" repeatCount="indefinite"
79
- values="${generatePacManPath(0.55)};${generatePacManPath(0.05)};${generatePacManPath(0.55)}"/>
80
- </path>`;
81
-
82
- // Ghosts
83
- store.ghosts.forEach((ghost, index) => {
84
- const ghostPositionAnimation = generateChangingValuesAnimation(store, generateGhostPositions(store, index));
85
- const ghostColorAnimation = generateChangingValuesAnimation(store, generateGhostColors(store, index));
86
- svg += `<use id="ghost${index}" width="${CELL_SIZE}" height="${CELL_SIZE}" href="#ghost-${ghost.name}">
87
- <animateTransform attributeName="transform" type="translate" dur="${store.gameHistory.length * DELTA_TIME}ms" repeatCount="indefinite"
88
- keyTimes="${ghostPositionAnimation.keyTimes}"
89
- values="${ghostPositionAnimation.values}"/>
90
- <animate attributeName="href" dur="${store.gameHistory.length * DELTA_TIME}ms" repeatCount="indefinite"
91
- keyTimes="${ghostColorAnimation.keyTimes}"
92
- values="${ghostColorAnimation.values}"/>
93
- </use>`;
94
- });
95
-
96
- svg += '</svg>';
97
- return svg;
98
- };
99
-
100
- const generatePacManPath = (mouthAngle: number) => {
101
- const radius = CELL_SIZE / 2;
102
- const startAngle = mouthAngle;
103
- const endAngle = 2 * Math.PI - mouthAngle;
104
-
105
- return `M ${radius},${radius}
106
- L ${radius + radius * Math.cos(startAngle)},${radius + radius * Math.sin(startAngle)}
107
- A ${radius},${radius} 0 1,1 ${radius + radius * Math.cos(endAngle)},${radius + radius * Math.sin(endAngle)}
108
- Z`;
109
- };
110
-
111
- const generatePacManPositions = (store: StoreType): string[] => {
112
- return store.gameHistory.map((state) => {
113
- const x = state.pacman.x * (CELL_SIZE + GAP_SIZE);
114
- const y = state.pacman.y * (CELL_SIZE + GAP_SIZE) + 15;
115
- return `${x},${y}`;
116
- });
117
- };
118
-
119
- const generatePacManRotations = (store: StoreType): string[] => {
120
- const pivit = CELL_SIZE / 2;
121
- return store.gameHistory.map((state) => {
122
- switch (state.pacman.direction) {
123
- case 'right':
124
- return `0 ${pivit} ${pivit}`;
125
- case 'left':
126
- return `180 ${pivit} ${pivit}`;
127
- case 'up':
128
- return `270 ${pivit} ${pivit}`;
129
- case 'down':
130
- return `90 ${pivit} ${pivit}`;
131
- default:
132
- return `0 ${pivit} ${pivit}`;
133
- }
134
- });
135
- };
136
-
137
- const generatePacManColors = (store: StoreType): string[] => {
138
- return store.gameHistory.map((state) => {
139
- if (state.pacman.deadRemainingDuration) {
140
- return PACMAN_COLOR_DEAD;
141
- } else if (state.pacman.powerupRemainingDuration) {
142
- return PACMAN_COLOR_POWERUP;
143
- } else {
144
- return PACMAN_COLOR;
145
- }
146
- });
147
- };
148
-
149
- const generateCellColorValues = (store: StoreType, x: number, y: number): string[] => {
150
- return store.gameHistory.map((state) => {
151
- const intensity = state.grid[x][y];
152
- if (intensity > 0) {
153
- const adjustedIntensity = intensity < 0.2 ? 0.3 : intensity;
154
- return Utils.hexToHexAlpha(Utils.getCurrentTheme(store).contributionBoxColor, adjustedIntensity);
155
- } else {
156
- return Utils.getCurrentTheme(store).emptyContributionBoxColor;
157
- }
158
- });
159
- };
160
-
161
- const generateGhostPositions = (store: StoreType, ghostIndex: number): string[] => {
162
- return store.gameHistory.map((state) => {
163
- const ghost = state.ghosts[ghostIndex];
164
- const x = ghost.x * (CELL_SIZE + GAP_SIZE);
165
- const y = ghost.y * (CELL_SIZE + GAP_SIZE) + 15;
166
- return `${x},${y}`;
167
- });
168
- };
169
-
170
- const generateGhostColors = (store: StoreType, ghostIndex: number): string[] => {
171
- return store.gameHistory.map((state) => {
172
- const ghost = state.ghosts[ghostIndex];
173
- return '#' + (ghost.scared ? ghostShort('scared') : ghostShort(ghost.name));
174
- });
175
- };
176
-
177
- const generateGhostsPredefinition = () => {
178
- return `<defs>
179
- <symbol id="${ghostShort('blinky')}" viewBox="0 0 100 100">
180
- <image href="${GHOSTS['blinky'].imgDate}" width="100" height="100"/>
181
- </symbol>
182
- <symbol id="${ghostShort('clyde')}" viewBox="0 0 100 100">
183
- <image href="${GHOSTS['clyde'].imgDate}" width="100" height="100"/>
184
- </symbol>
185
- <symbol id="${ghostShort('inky')}" viewBox="0 0 100 100">
186
- <image href="${GHOSTS['inky'].imgDate}" width="100" height="100"/>
187
- </symbol>
188
- <symbol id="${ghostShort('pinky')}" viewBox="0 0 100 100">
189
- <image href="${GHOSTS['pinky'].imgDate}" width="100" height="100"/>
190
- </symbol>
191
- <symbol id="${ghostShort('scared')}" viewBox="0 0 100 100">
192
- <image href="${GHOSTS['scared'].imgDate}" width="100" height="100"/>
193
- </symbol>
194
- </defs>`;
195
- };
196
-
197
- const ghostShort = (ghostName: string): string => {
198
- switch (ghostName) {
199
- case 'blinky':
200
- return 'gb';
201
- case 'clyde':
202
- return 'gc';
203
- case 'inky':
204
- return 'gi';
205
- case 'pinky':
206
- return 'gp';
207
- case 'scared':
208
- return 'gs';
209
- default:
210
- return ghostName;
211
- }
212
- };
213
-
214
- const generateChangingValuesAnimation = (store: StoreType, changingValues: string[]): AnimationData => {
215
- if (store.gameHistory.length !== changingValues.length) {
216
- throw new Error('The length of changingValues must match the length of gameHistory');
217
- }
218
-
219
- const totalFrames = store.gameHistory.length;
220
- let keyTimes: number[] = [];
221
- let values: string[] = [];
222
- let lastValue: string | null = null;
223
- let lastIndex: number | null = null;
224
-
225
- changingValues.forEach((currentValue, index) => {
226
- if (currentValue !== lastValue) {
227
- if (lastValue !== null && lastIndex !== null && index - 1 !== lastIndex) {
228
- // Add a keyframe right before the value change
229
- keyTimes.push(Number(((index - 0.000001) / (totalFrames - 1)).toFixed(6)));
230
- values.push(lastValue);
231
- }
232
- // Add the new value keyframe
233
- keyTimes.push(Number((index / (totalFrames - 1)).toFixed(6)));
234
- values.push(currentValue);
235
- lastValue = currentValue;
236
- lastIndex = index;
237
- }
238
- });
239
-
240
- // Ensure the last frame is always included
241
- if (keyTimes[keyTimes.length - 1] !== 1) {
242
- keyTimes.push(1);
243
- values.push(lastValue!);
244
- }
245
-
246
- return {
247
- keyTimes: keyTimes.join(';'),
248
- values: values.join(';')
249
- };
250
- };
251
-
252
- export const SVG = {
253
- generateAnimatedSVG
254
- };
package/src/types.ts DELETED
@@ -1,78 +0,0 @@
1
- export type Point2d = {
2
- x: number;
3
- y: number;
4
- };
5
-
6
- export interface Pacman {
7
- x: number;
8
- y: number;
9
- direction: 'right' | 'left' | 'up' | 'down';
10
- points: number;
11
- totalPoints: number;
12
- deadRemainingDuration: number;
13
- powerupRemainingDuration: number;
14
- recentPositions: string[];
15
- target?: Point2d;
16
- }
17
-
18
- export type GhostName = 'blinky' | 'clyde' | 'inky' | 'pinky';
19
- export interface Ghost {
20
- x: number;
21
- y: number;
22
- name: GhostName;
23
- scared: boolean;
24
- target?: Point2d;
25
- }
26
-
27
- export interface Contribution {
28
- date: Date;
29
- count: number;
30
- }
31
-
32
- export interface StoreType {
33
- frameCount: number;
34
- contributions: Contribution[];
35
- pacman: Pacman;
36
- ghosts: Ghost[];
37
- grid: { intensity: number; commitsCount: number }[][];
38
- monthLabels: string[];
39
- pacmanMouthOpen: boolean;
40
- gameInterval: number;
41
- gameHistory: {
42
- pacman: Pacman;
43
- ghosts: Ghost[];
44
- grid: number[][];
45
- }[];
46
- config: Config;
47
- }
48
-
49
- export interface Config {
50
- platform: 'github' | 'gitlab';
51
- username: string;
52
- canvas: HTMLCanvasElement;
53
- outputFormat: 'canvas' | 'svg';
54
- svgCallback: (blonUrl: string) => void;
55
- gameOverCallback: () => void;
56
- gameTheme: ThemeKeys;
57
- gameSpeed: number;
58
- enableSounds: boolean;
59
- pointsIncreasedCallback: (pointsSum: number) => void;
60
- githubSettings?: {
61
- accessToken: string;
62
- };
63
- }
64
-
65
- export type ThemeKeys = 'github' | 'github-dark' | 'gitlab' | 'gitlab-dark';
66
-
67
- export interface GameTheme {
68
- textColor: string;
69
- gridBackground: string;
70
- contributionBoxColor: string;
71
- emptyContributionBoxColor: string;
72
- wallColor: string;
73
- }
74
-
75
- export interface AnimationData {
76
- keyTimes: string;
77
- values: string;
78
- }