T1ckbase commited on
Commit
8b94c2a
·
1 Parent(s): e2c93e4

add counter and timer

Browse files
Files changed (5) hide show
  1. generate-table.ts +2 -2
  2. images/counter.svg +1 -1
  3. images/timer.svg +1 -1
  4. main.ts +18 -4
  5. minesweeper.ts +27 -2
generate-table.ts CHANGED
@@ -5,9 +5,9 @@ const baseUrl = 'https://t1ckbase-minesweeper.hf.space';
5
  const html = '<table id="toc">\n' +
6
  ' <tr>\n' +
7
  ' <td align="center">\n' +
8
- ` <img src="${baseUrl}/mines/count" height="48px" />\n` +
9
  ` <a href="${baseUrl}/game/reset"><img src="${baseUrl}/game/status" width="48px" height="48px" /></a>\n` +
10
- ` <img src="${baseUrl}/mines/count" height="48px" />\n` +
11
  ' </td>\n' +
12
  ' </tr>\n' +
13
  Array.from({ length: rows }, (_, r) =>
 
5
  const html = '<table id="toc">\n' +
6
  ' <tr>\n' +
7
  ' <td align="center">\n' +
8
+ ` <img src="${baseUrl}/mines/count" height="48px" align="left" />\n` +
9
  ` <a href="${baseUrl}/game/reset"><img src="${baseUrl}/game/status" width="48px" height="48px" /></a>\n` +
10
+ ` <img src="${baseUrl}/timer" height="48px" align="right" />\n` +
11
  ' </td>\n' +
12
  ' </tr>\n' +
13
  Array.from({ length: rows }, (_, r) =>
images/counter.svg CHANGED
images/timer.svg CHANGED
main.ts CHANGED
@@ -1,6 +1,5 @@
1
  import { Hono } from 'hono';
2
  // import { logger } from 'hono/logger';
3
- import { serveStatic } from 'hono/deno';
4
  import { Minesweeper } from './minesweeper.ts';
5
  import { isGithubUserPath } from './utils.ts';
6
 
@@ -8,7 +7,8 @@ import { isGithubUserPath } from './utils.ts';
8
 
9
  const USER = 'T1ckbase';
10
 
11
- const minesweeper = new Minesweeper(8, 8, 10, './images');
 
12
 
13
  const app = new Hono();
14
 
@@ -23,8 +23,6 @@ if (Deno.env.get('DENO_ENV') === 'development') {
23
  app.get('/board', (c) => c.text(minesweeper.getBoard().map((row) => row.map((cell) => cell.isMine ? 'b' : cell.adjacentMines).join('')).join('\n')));
24
  }
25
 
26
- app.use('/mines/count', serveStatic({ path: './images/counter.svg' }));
27
-
28
  app.get('/cell/:row/:col/image', (c) => {
29
  const row = Number(c.req.param('row'));
30
  const col = Number(c.req.param('col'));
@@ -95,6 +93,22 @@ app.get('/game/reset', (c) => {
95
  return c.redirect(redirectUrl + '#minesweeper');
96
  });
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  Deno.serve(app.fetch);
99
 
100
  Deno.cron('keep alive', '0 0 * * *', async () => { // keep alive?
 
1
  import { Hono } from 'hono';
2
  // import { logger } from 'hono/logger';
 
3
  import { Minesweeper } from './minesweeper.ts';
4
  import { isGithubUserPath } from './utils.ts';
5
 
 
7
 
8
  const USER = 'T1ckbase';
9
 
10
+ const MINE_COUNT = 10;
11
+ const minesweeper = new Minesweeper(8, 8, MINE_COUNT, './images');
12
 
13
  const app = new Hono();
14
 
 
23
  app.get('/board', (c) => c.text(minesweeper.getBoard().map((row) => row.map((cell) => cell.isMine ? 'b' : cell.adjacentMines).join('')).join('\n')));
24
  }
25
 
 
 
26
  app.get('/cell/:row/:col/image', (c) => {
27
  const row = Number(c.req.param('row'));
28
  const col = Number(c.req.param('col'));
 
93
  return c.redirect(redirectUrl + '#minesweeper');
94
  });
95
 
96
+ app.get('/mines/count', (c) => {
97
+ const image = minesweeper.getCounterImage(MINE_COUNT);
98
+ if (!image) return c.text('Counter image is not available.', 404);
99
+ c.header('Content-Type', 'image/svg+xml');
100
+ c.header('Cache-Control', 'max-age=0, no-cache, no-store, must-revalidate');
101
+ return c.body(image);
102
+ });
103
+
104
+ app.get('/timer', (c) => {
105
+ const image = minesweeper.getTimerImage();
106
+ if (!image) return c.text('Timer image is not available.', 404);
107
+ c.header('Content-Type', 'image/svg+xml');
108
+ c.header('Cache-Control', 'max-age=0, no-cache, no-store, must-revalidate');
109
+ return c.body(image);
110
+ });
111
+
112
  Deno.serve(app.fetch);
113
 
114
  Deno.cron('keep alive', '0 0 * * *', async () => { // keep alive?
minesweeper.ts CHANGED
@@ -23,7 +23,9 @@ export type ImageKey =
23
  | 'mine_hit' // mine-red.svg (for the mine that was clicked and caused loss)
24
  | 'status_playing' // emoji-smile.svg
25
  | 'status_won' // emoji-sunglasses.svg
26
- | 'status_lost'; // emoji-dead.svg
 
 
27
 
28
  export class Minesweeper {
29
  // deno-fmt-ignore
@@ -49,6 +51,9 @@ export class Minesweeper {
49
  private imageCache: Map<ImageKey, Uint8Array>;
50
  private imageDirectory: string;
51
 
 
 
 
52
  constructor(rows: number, cols: number, mineCount: number, imageDirectory: string = './image') {
53
  // Validate input parameters
54
  if (rows <= 0 || cols <= 0) throw new Error('Board dimensions (rows, cols) must be positive integers.');
@@ -94,6 +99,8 @@ export class Minesweeper {
94
  status_playing: 'emoji-surprise-smile.svg',
95
  status_won: 'emoji-sunglasses.svg',
96
  status_lost: 'emoji-dead.svg',
 
 
97
  };
98
  }
99
 
@@ -110,7 +117,7 @@ export class Minesweeper {
110
  const filePath = path.join(this.imageDirectory, fileName); // Use path.join for cross-platform compatibility
111
  try {
112
  const fileBuffer = Deno.readFileSync(filePath);
113
- this.imageCache.set(typedKey, new Uint8Array(fileBuffer)); // Deno.readFileSync returns a Buffer, which is a Uint8Array
114
  // console.log(`Loaded image: ${filePath} for key: ${typedKey}`);
115
  } catch (error) {
116
  console.error(`Failed to load image ${filePath} for key ${typedKey}:`, error);
@@ -374,4 +381,22 @@ export class Minesweeper {
374
  return this.imageCache.get('status_playing'); // Fallback
375
  }
376
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  }
 
23
  | 'mine_hit' // mine-red.svg (for the mine that was clicked and caused loss)
24
  | 'status_playing' // emoji-smile.svg
25
  | 'status_won' // emoji-sunglasses.svg
26
+ | 'status_lost' // emoji-dead.svg
27
+ | 'counter' // counter.svg
28
+ | 'timer'; // timer.svg
29
 
30
  export class Minesweeper {
31
  // deno-fmt-ignore
 
51
  private imageCache: Map<ImageKey, Uint8Array>;
52
  private imageDirectory: string;
53
 
54
+ private decoder = new TextDecoder('utf-8');
55
+ private encoder = new TextEncoder();
56
+
57
  constructor(rows: number, cols: number, mineCount: number, imageDirectory: string = './image') {
58
  // Validate input parameters
59
  if (rows <= 0 || cols <= 0) throw new Error('Board dimensions (rows, cols) must be positive integers.');
 
99
  status_playing: 'emoji-surprise-smile.svg',
100
  status_won: 'emoji-sunglasses.svg',
101
  status_lost: 'emoji-dead.svg',
102
+ counter: 'counter.svg',
103
+ timer: 'timer.svg',
104
  };
105
  }
106
 
 
117
  const filePath = path.join(this.imageDirectory, fileName); // Use path.join for cross-platform compatibility
118
  try {
119
  const fileBuffer = Deno.readFileSync(filePath);
120
+ this.imageCache.set(typedKey, fileBuffer); // Deno.readFileSync returns a Buffer, which is a Uint8Array
121
  // console.log(`Loaded image: ${filePath} for key: ${typedKey}`);
122
  } catch (error) {
123
  console.error(`Failed to load image ${filePath} for key ${typedKey}:`, error);
 
381
  return this.imageCache.get('status_playing'); // Fallback
382
  }
383
  }
384
+
385
+ public getCounterImage(count: number): Uint8Array | undefined {
386
+ const image = this.imageCache.get('counter');
387
+ if (!image) return;
388
+ const decodedImage = this.decoder.decode(image);
389
+ const newImage = decodedImage.replace('--count: 0;', `--count: ${count};`);
390
+ return this.encoder.encode(newImage);
391
+ }
392
+
393
+ public getTimerImage(): Uint8Array | undefined {
394
+ const seconds = Math.floor(((this.gameState === 'playing' ? Date.now() : +this.endTime!) - +this.startTime) / 1000);
395
+ if (this.gameState !== 'playing') return this.getCounterImage(seconds);
396
+ const image = this.imageCache.get('timer');
397
+ if (!image) return;
398
+ const decodedImage = this.decoder.decode(image);
399
+ const newImage = decodedImage.replace('animation: timer 1000s linear infinite;', `animation: timer 1000s linear infinite -${seconds}s;`);
400
+ return this.encoder.encode(newImage);
401
+ }
402
  }