File size: 4,148 Bytes
b268bfa
c58b31c
a0040c1
 
 
 
 
8b94c2a
 
a0040c1
b268bfa
 
7910130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a0040c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b268bfa
 
d460348
a0040c1
 
 
7910130
a0040c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db0618c
a0040c1
 
 
 
 
fb8feb1
 
d460348
a0040c1
 
 
7910130
a0040c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db0618c
b268bfa
 
8b94c2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b268bfa
a9fbe0c
21ba00e
a9fbe0c
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { Minesweeper } from './minesweeper.ts';
import { isGithubUserPath } from './utils.ts';

const USER = 'T1ckbase';

const MINE_COUNT = 10;
const minesweeper = new Minesweeper(8, 8, MINE_COUNT, './images');

const app = new Hono();

const customLogger = (message: string, ...rest: string[]) => {
  const timestamp = new Intl.DateTimeFormat('sv-SE', {
    timeZone: 'Asia/Taipei',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
  })
    .format(new Date());
  console.log(`[${timestamp}] ${message}`, ...rest);
};

app.get('/', (c) => c.text(`Play minesweeper:\nhttps://github.com/${USER}`));

app.get('/headers', (c) => c.text(Array.from(c.req.raw.headers).join('\n')));

if (Deno.env.get('DENO_ENV') === 'development') {
  // app.get('/board', (c) => c.text(JSON.stringify(minesweeper.getBoard(), null, 2)));
  app.get('/board', (c) => c.text(minesweeper.getBoard().map((row) => row.map((cell) => cell.isMine ? 'b' : cell.adjacentMines).join('')).join('\n')));
}

app.get('/cell/:row/:col/image', (c) => {
  const row = Number(c.req.param('row'));
  const col = Number(c.req.param('col'));
  if (Number.isNaN(row) || Number.isNaN(col)) return c.text('Invalid coordinates', 400);

  const cellImage = minesweeper.getCellImage(row, col);
  if (!cellImage) return c.text(`Not Found: Image for cell (${row}, ${col}) could not be found. Coordinates may be invalid or no image is defined for this cell state.`, 404);

  c.header('Content-Type', 'image/svg+xml');
  c.header('Cache-Control', 'max-age=0, no-cache, no-store, must-revalidate');
  return c.body(cellImage);
});

app.get('/cell/:row/:col/click', logger(customLogger), (c) => {
  const row = Number(c.req.param('row'));
  const col = Number(c.req.param('col'));
  if (Number.isNaN(row) || Number.isNaN(col)) return c.text('Invalid coordinates', 400);

  const referer = c.req.header('Referer');
  let redirectUrl = `https://github.com/${USER}`;
  if (referer) {
    if (isGithubUserPath(referer, USER)) {
      redirectUrl = referer;
    } else {
      console.warn(`Invalid or non-GitHub referer: ${referer}`);
      // return c.text('?', 403);
    }
  } else {
    console.warn('Referer header is missing.');
    // return c.text('?', 403);
  }

  minesweeper.revealCell(row, col);

  return c.redirect(redirectUrl + '#minesweeper');
});

app.get('/game/status', (c) => {
  const image = minesweeper.getGameStatusImage();
  if (!image) return c.text('Status image is not available.', 404);

  c.header('Content-Type', 'image/svg+xml');
  c.header('Cache-Control', 'max-age=0, no-cache, no-store, must-revalidate');
  return c.body(image);
});

app.get('/game/reset', logger(customLogger), (c) => {
  const referer = c.req.header('Referer');
  let redirectUrl = `https://github.com/${USER}`;
  if (referer) {
    if (isGithubUserPath(referer, USER)) {
      redirectUrl = referer;
    } else {
      console.warn(`Invalid or non-GitHub referer: ${referer}`);
      // return c.text('?', 403);
    }
  } else {
    console.warn('Referer header is missing.');
    // return c.text('?', 403);
  }

  try {
    minesweeper.resetGame();
  } catch (e) {
    console.warn(e instanceof Error ? e.message : e);
  }

  return c.redirect(redirectUrl + '#minesweeper');
});

app.get('/mines/count', (c) => {
  const image = minesweeper.getCounterImage(MINE_COUNT);
  if (!image) return c.text('Counter image is not available.', 404);
  c.header('Content-Type', 'image/svg+xml');
  c.header('Cache-Control', 'max-age=0, no-cache, no-store, must-revalidate');
  return c.body(image);
});

app.get('/timer', (c) => {
  const image = minesweeper.getTimerImage();
  if (!image) return c.text('Timer image is not available.', 404);
  c.header('Content-Type', 'image/svg+xml');
  c.header('Cache-Control', 'max-age=0, no-cache, no-store, must-revalidate');
  return c.body(image);
});

Deno.serve(app.fetch);

Deno.cron('keep alive', '0 0 * * *', async () => { // keep alive?
  await fetch('https://t1ckbase-minesweeper.hf.space');
});