Spaces:
Paused
Paused
T1ckbase
commited on
Commit
·
8b94c2a
1
Parent(s):
e2c93e4
add counter and timer
Browse files- generate-table.ts +2 -2
- images/counter.svg +1 -1
- images/timer.svg +1 -1
- main.ts +18 -4
- 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}/
|
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
|
|
|
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'
|
|
|
|
|
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,
|
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 |
}
|