pacman-contribution-graph 1.0.0 → 1.0.2

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/src/svg.ts CHANGED
@@ -1,20 +1,22 @@
1
- import { CELL_SIZE, GAP_SIZE, GRID_HEIGHT, GRID_WIDTH, PACMAN_COLOR, PACMAN_COLOR_DEAD, PACMAN_COLOR_POWERUP } from './constants';
2
- import { Store } from './store';
1
+ import { CELL_SIZE, GAP_SIZE, GHOSTS, GRID_HEIGHT, GRID_WIDTH, PACMAN_COLOR, PACMAN_COLOR_DEAD, PACMAN_COLOR_POWERUP } from './constants';
2
+ import { StoreType } from './types';
3
3
  import { Utils } from './utils';
4
4
 
5
- const generateAnimatedSVG = () => {
5
+ const generateAnimatedSVG = (store: StoreType) => {
6
6
  const svgWidth = GRID_WIDTH * (CELL_SIZE + GAP_SIZE);
7
7
  const svgHeight = GRID_HEIGHT * (CELL_SIZE + GAP_SIZE) + 20;
8
8
  let svg = `<svg width="${svgWidth}" height="${svgHeight}" xmlns="http://www.w3.org/2000/svg">`;
9
- svg += `<rect width="100%" height="100%" fill="${Utils.getCurrentTheme().gridBackground}"/>`;
9
+ svg += `<rect width="100%" height="100%" fill="${Utils.getCurrentTheme(store).gridBackground}"/>`;
10
+
11
+ svg += generateGhostsPredefinition();
10
12
 
11
13
  // Month labels
12
14
  let lastMonth = '';
13
15
  for (let y = 0; y < GRID_WIDTH; y++) {
14
- if (Store.monthLabels[y] !== lastMonth) {
16
+ if (store.monthLabels[y] !== lastMonth) {
15
17
  const xPos = y * (CELL_SIZE + GAP_SIZE) + CELL_SIZE / 2;
16
- svg += `<text x="${xPos}" y="10" text-anchor="middle" font-size="10" fill="${Utils.getCurrentTheme().textColor}">${Store.monthLabels[y]}</text>`;
17
- lastMonth = Store.monthLabels[y];
18
+ svg += `<text x="${xPos}" y="10" text-anchor="middle" font-size="10" fill="${Utils.getCurrentTheme(store).textColor}">${store.monthLabels[y]}</text>`;
19
+ lastMonth = store.monthLabels[y];
18
20
  }
19
21
  }
20
22
 
@@ -23,62 +25,43 @@ const generateAnimatedSVG = () => {
23
25
  for (let y = 0; y < GRID_WIDTH; y++) {
24
26
  const cellX = y * (CELL_SIZE + GAP_SIZE);
25
27
  const cellY = x * (CELL_SIZE + GAP_SIZE) + 15;
26
- const intensity = Store.gameHistory[0].grid[x][y];
27
- const color = intensity > 0 ? getContributionColor(intensity) : Utils.getCurrentTheme().emptyContributionBoxColor;
28
+ const intensity = store.gameHistory[0].grid[x][y];
29
+ const color = intensity > 0 ? getContributionColor(store, intensity) : Utils.getCurrentTheme(store).emptyContributionBoxColor;
28
30
  svg += `<rect id="cell-${x}-${y}" x="${cellX}" y="${cellY}" width="${CELL_SIZE}" height="${CELL_SIZE}" rx="5" fill="${color}">
29
- <animate attributeName="fill" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
30
- values="${generateCellColorValues(x, y)}"
31
- keyTimes="${generateKeyTimes()}"/>
31
+ <animate attributeName="fill" dur="${store.gameHistory.length * 300}ms" repeatCount="indefinite"
32
+ values="${generateCellColorValues(store, x, y)}"
33
+ keyTimes="${generateKeyTimes(store)}"/>
32
34
  </rect>`;
33
35
  }
34
36
  }
35
37
 
36
38
  // Pacman
37
39
  svg += `<path id="pacman" d="${generatePacManPath(0.35)}"
38
- transform="translate(${Store.pacman.y * (CELL_SIZE + GAP_SIZE)}, ${Store.pacman.x * (CELL_SIZE + GAP_SIZE) + 15})">
39
- <animate attributeName="fill" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
40
- keyTimes="${generateKeyTimes()}"
41
- values="${generatePacManColors()}"/>
42
- <animateTransform attributeName="transform" type="translate" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
43
- keyTimes="${generateKeyTimes()}"
44
- values="${generatePacManPositions()}"/>
40
+ transform="translate(${store.pacman.y * (CELL_SIZE + GAP_SIZE)}, ${store.pacman.x * (CELL_SIZE + GAP_SIZE) + 15})">
41
+ <animate attributeName="fill" dur="${store.gameHistory.length * 300}ms" repeatCount="indefinite"
42
+ keyTimes="${generateKeyTimes(store)}"
43
+ values="${generatePacManColors(store)}"/>
44
+ <animateTransform attributeName="transform" type="translate" dur="${store.gameHistory.length * 300}ms" repeatCount="indefinite"
45
+ keyTimes="${generateKeyTimes(store)}"
46
+ values="${generatePacManPositions(store)}"/>
45
47
  <animate attributeName="d" dur="0.2s" repeatCount="indefinite"
46
48
  values="${generatePacManPath(0.25)};${generatePacManPath(0.05)};${generatePacManPath(0.25)}"/>
47
49
  </path>`;
48
50
 
49
51
  // Ghosts
50
- Store.ghosts.forEach((ghost, index) => {
51
- svg += `<path id="ghost${index}" d="${generateGhostPath(CELL_SIZE / 2)}" fill="${ghost.color}">
52
- <animate attributeName="fill" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
53
- keyTimes="${generateKeyTimes()}"
54
- values="${generateGhostColors(index)}"/>
55
- <animateTransform attributeName="transform" type="translate" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
56
- keyTimes="${generateKeyTimes()}"
57
- values="${generateGhostPositions(index)}"/>
58
- </path>
59
- <circle cx="${CELL_SIZE / 3}" cy="${CELL_SIZE / 3}" r="${CELL_SIZE / 8}" fill="white">
60
- <animateTransform attributeName="transform" type="translate" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
61
- keyTimes="${generateKeyTimes()}"
62
- values="${generateGhostPositions(index)}"/>
63
- </circle>
64
- <circle cx="${(CELL_SIZE * 2) / 3}" cy="${CELL_SIZE / 3}" r="${CELL_SIZE / 8}" fill="white">
65
- <animateTransform attributeName="transform" type="translate" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
66
- keyTimes="${generateKeyTimes()}"
67
- values="${generateGhostPositions(index)}"/>
68
- </circle>
69
- <circle cx="${CELL_SIZE / 3}" cy="${CELL_SIZE / 3}" r="${CELL_SIZE / 16}" fill="black">
70
- <animateTransform attributeName="transform" type="translate" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
71
- keyTimes="${generateKeyTimes()}"
72
- values="${generateGhostPositions(index)}"/>
73
- </circle>
74
- <circle cx="${(CELL_SIZE * 2) / 3}" cy="${CELL_SIZE / 3}" r="${CELL_SIZE / 16}" fill="black">
75
- <animateTransform attributeName="transform" type="translate" dur="${Store.gameHistory.length * 300}ms" repeatCount="indefinite"
76
- keyTimes="${generateKeyTimes()}"
77
- values="${generateGhostPositions(index)}"/>
78
- </circle>`;
52
+ store.ghosts.forEach((ghost, index) => {
53
+ svg += `<use id="ghost${index}" width="${CELL_SIZE}" height="${CELL_SIZE}" href="#ghost-${ghost.name}">
54
+ <animateTransform attributeName="transform" type="translate" dur="${store.gameHistory.length * 300}ms" repeatCount="indefinite"
55
+ keyTimes="${generateKeyTimes(store)}"
56
+ values="${generateGhostPositions(store, index)}"/>
57
+ <animate attributeName="href" dur="${store.gameHistory.length * 300}ms" repeatCount="indefinite"
58
+ keyTimes="${generateKeyTimes(store)}"
59
+ values="${generateGhostColors(store, index)}"/>
60
+ </use>`;
79
61
  });
80
62
 
81
63
  svg += '</svg>';
64
+ // TODO: minify SVG
82
65
  return svg;
83
66
  };
84
67
 
@@ -93,12 +76,12 @@ const generatePacManPath = (mouthAngle: number) => {
93
76
  Z`;
94
77
  };
95
78
 
96
- const generateKeyTimes = () => {
97
- return Store.gameHistory.map((_, index) => index / (Store.gameHistory.length - 1)).join(';');
79
+ const generateKeyTimes = (store: StoreType) => {
80
+ return store.gameHistory.map((_, index) => index / (store.gameHistory.length - 1)).join(';');
98
81
  };
99
82
 
100
- const generatePacManPositions = () => {
101
- return Store.gameHistory
83
+ const generatePacManPositions = (store: StoreType) => {
84
+ return store.gameHistory
102
85
  .map((state) => {
103
86
  const x = state.pacman.y * (CELL_SIZE + GAP_SIZE);
104
87
  const y = state.pacman.x * (CELL_SIZE + GAP_SIZE) + 15;
@@ -107,12 +90,12 @@ const generatePacManPositions = () => {
107
90
  .join(';');
108
91
  };
109
92
 
110
- const generatePacManColors = () => {
111
- return Store.gameHistory
93
+ const generatePacManColors = (store: StoreType) => {
94
+ return store.gameHistory
112
95
  .map((state) => {
113
- if (state.pacman.deadReaminingDuration) {
96
+ if (state.pacman.deadRemainingDuration) {
114
97
  return PACMAN_COLOR_DEAD;
115
- } else if (state.pacman.powerupReaminingDuration) {
98
+ } else if (state.pacman.powerupRemainingDuration) {
116
99
  return PACMAN_COLOR_POWERUP;
117
100
  } else {
118
101
  return PACMAN_COLOR;
@@ -121,22 +104,22 @@ const generatePacManColors = () => {
121
104
  .join(';');
122
105
  };
123
106
 
124
- const generateCellColorValues = (x: number, y: number) => {
125
- return Store.gameHistory
107
+ const generateCellColorValues = (store: StoreType, x: number, y: number) => {
108
+ return store.gameHistory
126
109
  .map((state) => {
127
110
  const intensity = state.grid[x][y];
128
- return intensity > 0 ? getContributionColor(intensity) : Utils.getCurrentTheme().emptyContributionBoxColor;
111
+ return intensity > 0 ? getContributionColor(store, intensity) : Utils.getCurrentTheme(store).emptyContributionBoxColor;
129
112
  })
130
113
  .join(';');
131
114
  };
132
115
 
133
- const getContributionColor = (intensity: number) => {
116
+ const getContributionColor = (store: StoreType, intensity: number) => {
134
117
  const adjustedIntensity = intensity < 0.2 ? 0.3 : intensity;
135
- return Utils.hexToRGBA(Utils.getCurrentTheme().contributionBoxColor, adjustedIntensity);
118
+ return Utils.hexToRGBA(Utils.getCurrentTheme(store).contributionBoxColor, adjustedIntensity);
136
119
  };
137
120
 
138
- const generateGhostPositions = (ghostIndex: number) => {
139
- return Store.gameHistory
121
+ const generateGhostPositions = (store: StoreType, ghostIndex: number) => {
122
+ return store.gameHistory
140
123
  .map((state) => {
141
124
  const ghost = state.ghosts[ghostIndex];
142
125
  const x = ghost.y * (CELL_SIZE + GAP_SIZE);
@@ -146,27 +129,35 @@ const generateGhostPositions = (ghostIndex: number) => {
146
129
  .join(';');
147
130
  };
148
131
 
149
- const generateGhostPath = (radius: number) => {
150
- return `M ${radius},${radius * 2}
151
- Q ${radius * 0.8},${radius * 1.5} ${radius * 0.5},${radius * 1.3}
152
- Q ${radius * 0.3},${radius * 1.1} 0,${radius}
153
- L 0,0
154
- L ${radius * 2},0
155
- L ${radius * 2},${radius}
156
- Q ${radius * 1.7},${radius * 1.1} ${radius * 1.5},${radius * 1.3}
157
- Q ${radius * 1.2},${radius * 1.5} ${radius},${radius * 2}
158
- Z`;
159
- };
160
-
161
- const generateGhostColors = (ghostIndex: number) => {
162
- return Store.gameHistory
132
+ const generateGhostColors = (store: StoreType, ghostIndex: number) => {
133
+ return store.gameHistory
163
134
  .map((state) => {
164
135
  const ghost = state.ghosts[ghostIndex];
165
- return ghost.scared ? 'blue' : ghost.color;
136
+ return ghost.scared ? '#scared' : '#' + ghost.name;
166
137
  })
167
138
  .join(';');
168
139
  };
169
140
 
141
+ const generateGhostsPredefinition = () => {
142
+ return `<defs>
143
+ <symbol id="blinky" viewBox="0 0 100 100">
144
+ <image href="${GHOSTS['blinky'].imgDate}" width="100" height="100"/>
145
+ </symbol>
146
+ <symbol id="clyde" viewBox="0 0 100 100">
147
+ <image href="${GHOSTS['clyde'].imgDate}" width="100" height="100"/>
148
+ </symbol>
149
+ <symbol id="inky" viewBox="0 0 100 100">
150
+ <image href="${GHOSTS['inky'].imgDate}" width="100" height="100"/>
151
+ </symbol>
152
+ <symbol id="pinky" viewBox="0 0 100 100">
153
+ <image href="${GHOSTS['pinky'].imgDate}" width="100" height="100"/>
154
+ </symbol>
155
+ <symbol id="scared" viewBox="0 0 100 100">
156
+ <image href="${GHOSTS['scared'].imgDate}" width="100" height="100"/>
157
+ </symbol>
158
+ </defs>`;
159
+ };
160
+
170
161
  export const SVG = {
171
162
  generateAnimatedSVG
172
163
  };
package/src/types.ts CHANGED
@@ -3,14 +3,16 @@ export interface Pacman {
3
3
  y: number;
4
4
  direction: string;
5
5
  points: number;
6
- deadReaminingDuration: number;
7
- powerupReaminingDuration: number;
6
+ totalPoints: number;
7
+ deadRemainingDuration: number;
8
+ powerupRemainingDuration: number;
8
9
  }
9
10
 
11
+ export type GhostName = 'blinky' | 'clyde' | 'inky' | 'pinky';
10
12
  export interface Ghost {
11
13
  x: number;
12
14
  y: number;
13
- color: string;
15
+ name: GhostName;
14
16
  scared: boolean;
15
17
  target?: { x: number; y: number };
16
18
  }
@@ -25,7 +27,7 @@ export interface StoreType {
25
27
  contributions: Contribution[];
26
28
  pacman: Pacman;
27
29
  ghosts: Ghost[];
28
- grid: number[][];
30
+ grid: { intensity: number; commitsCount: number }[][];
29
31
  monthLabels: string[];
30
32
  pacmanMouthOpen: boolean;
31
33
  gameInterval: number;
@@ -48,6 +50,7 @@ export interface Config {
48
50
  gameTheme: ThemeKeys;
49
51
  gameSpeed: number;
50
52
  enableSounds: boolean;
53
+ pointsIncreasedCallback: (pointsSum: number) => void;
51
54
  }
52
55
 
53
56
  export type ThemeKeys = 'github' | 'github-dark' | 'gitlab' | 'gitlab-dark';
package/src/utils.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { GAME_THEMES } from './constants';
2
- import { Store } from './store';
3
- import type { Contribution, GameTheme } from './types';
2
+ import type { Contribution, GameTheme, StoreType } from './types';
4
3
 
5
4
  const getGitlabContribution = async (username: string): Promise<Contribution[]> => {
6
- const response = await fetch(`https://gitlab.com/users/${username}/calendar.json`);
5
+ // const response = await fetch(`https://gitlab.com/users/${username}/calendar.json`);
6
+ const response = await fetch(
7
+ `https://v0-new-project-q1hhrdodoye-abozanona-gmailcoms-projects.vercel.app/api/contributions?username=${username}`
8
+ );
7
9
  const contributionsList = await response.json();
8
10
  return Object.entries(contributionsList).map(([date, count]) => ({
9
11
  date: new Date(date),
@@ -28,8 +30,8 @@ const getGithubContribution = async (username: string): Promise<Contribution[]>
28
30
  );
29
31
  };
30
32
 
31
- const getCurrentTheme = (): GameTheme => {
32
- return GAME_THEMES[Store.config.gameTheme] ?? GAME_THEMES['github'];
33
+ const getCurrentTheme = (store: StoreType): GameTheme => {
34
+ return GAME_THEMES[store.config.gameTheme] ?? GAME_THEMES['github'];
33
35
  };
34
36
 
35
37
  function hexToRGBA(hex: string, alpha: number): string {