Zok213 commited on
Commit
90d53f1
·
verified ·
1 Parent(s): ecb3de9

Upload 7 files

Browse files
Files changed (7) hide show
  1. docker-compose.yml +8 -0
  2. dockerfile +9 -0
  3. game.js +285 -0
  4. icon.jpg +0 -0
  5. index.html +329 -19
  6. manifest.json +23 -0
  7. sw.js +50 -0
docker-compose.yml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ version: '3'
2
+ services:
3
+ web:
4
+ build: .
5
+ ports:
6
+ - "7860:80"
7
+ volumes:
8
+ - ./:/usr/share/nginx/html
dockerfile ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FROM nginx:alpine
2
+
3
+ RUN rm -rf /usr/share/nginx/html/*
4
+
5
+ COPY ./ /usr/share/nginx/html
6
+
7
+ EXPOSE 7860
8
+
9
+ CMD ["nginx", "-g", "daemon off;"]
game.js ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let isMacOS = false;
2
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
3
+
4
+ let iDevice = false;
5
+ if (isBrowser && navigator.platform) {
6
+ iDevice = navigator.platform.match(/^iP/);
7
+ }
8
+
9
+ if (isBrowser) {
10
+ const os = navigator.platform.toLowerCase();
11
+ isMacOS = os.includes('mac');
12
+ console.log(isMacOS ? "Running on macOS or iOS." : "Running in a browser environment.");
13
+ }
14
+
15
+ const numbers = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣'];
16
+
17
+ let feedback;
18
+ if (isBrowser) {
19
+ feedback = document.querySelector('.feedback');
20
+ }
21
+
22
+ class Game {
23
+ constructor(cols, rows, number_of_bombs, set) {
24
+ this.cols = Number(cols);
25
+ this.rows = Number(rows);
26
+ this.number_of_bombs = Number(number_of_bombs);
27
+ this.number_of_cells = this.cols * this.rows;
28
+
29
+ this.map = isBrowser ? document.getElementById('map') : null;
30
+
31
+ if (this.number_of_cells > 2500 || this.number_of_bombs >= this.number_of_cells) {
32
+ alert('Invalid game size or bomb count');
33
+ return;
34
+ }
35
+
36
+ this.emojiset = set;
37
+ this.numbermoji = [this.emojiset[0]].concat(numbers);
38
+ this.usetwemoji = false;
39
+ this.init();
40
+ }
41
+
42
+ init() {
43
+ if (!isBrowser) return;
44
+
45
+ this.prepareEmoji();
46
+ this.map.innerHTML = '';
47
+ const grid_data = this.bomb_array();
48
+
49
+ let row = document.createElement('div');
50
+ row.classList.add('row');
51
+
52
+ grid_data.forEach((isBomb, i) => {
53
+ const cell = this.createCell(i, isBomb, grid_data);
54
+ row.appendChild(cell);
55
+
56
+ if ((i + 1) % this.cols === 0) {
57
+ this.map.appendChild(row);
58
+ row = document.createElement('div');
59
+ row.classList.add('row');
60
+ }
61
+ });
62
+
63
+ this.resetMetadata();
64
+ this.bindEvents();
65
+ this.updateBombsLeft();
66
+ }
67
+
68
+ createCell(i, isBomb, grid_data) {
69
+ const cellWrapper = document.createElement('span');
70
+ cellWrapper.classList.add('cell-wrapper');
71
+
72
+ const x = (i % this.cols) + 1;
73
+ const y = Math.ceil((i + 1) / this.cols);
74
+ const neighbors_coords = this.getNeighbors(x, y);
75
+
76
+ const mine = this.mine(isBomb, neighbors_coords, grid_data, x, y);
77
+ cellWrapper.appendChild(mine);
78
+
79
+ return cellWrapper;
80
+ }
81
+
82
+ getNeighbors(x, y) {
83
+ return [
84
+ [x, y - 1], [x, y + 1],
85
+ [x - 1, y - 1], [x - 1, y], [x - 1, y + 1],
86
+ [x + 1, y - 1], [x + 1, y], [x + 1, y + 1]
87
+ ];
88
+ }
89
+
90
+ mine(isBomb, neighbors_coords, grid_data, x, y) {
91
+ const mine = document.createElement('button');
92
+ mine.type = 'button';
93
+ mine.className = `cell x${x} y${y}`;
94
+ mine.isMasked = true;
95
+ mine.isBomb = isBomb;
96
+ mine.isFlagged = false;
97
+
98
+ if (!isBomb) {
99
+ const neighbors = neighbors_coords.map(([nx, ny]) => this.getIndex(nx, ny) !== -1 ? grid_data[this.getIndex(nx, ny)] : false);
100
+ mine.mine_count = neighbors.filter(Boolean).length;
101
+ }
102
+
103
+ mine.reveal = this.reveal.bind(this, mine);
104
+ mine.appendChild(document.createTextNode(this.emojiset[3]));
105
+
106
+ return mine;
107
+ }
108
+
109
+ reveal(mine, won = false) {
110
+ const emoji = mine.isBomb ? (won ? this.emojiset[2] : this.emojiset[1]) : this.numbermoji[mine.mine_count];
111
+ const text = mine.isBomb ? (won ? "Bomb discovered" : "Boom!") : (mine.mine_count === 0 ? "Empty field" : `${mine.mine_count} bombs nearby`);
112
+
113
+ mine.innerHTML = emoji;
114
+ mine.setAttribute('aria-label', text);
115
+ mine.isMasked = false;
116
+ mine.classList.add('unmasked');
117
+
118
+ if (mine.mine_count === 0 && !mine.isBomb) {
119
+ this.revealNeighbors(mine);
120
+ }
121
+ }
122
+
123
+ getIndex(x, y) {
124
+ return x > this.cols || x <= 0 || y > this.rows || y <= 0 ? -1 : this.cols * (y - 1) + (x - 1);
125
+ }
126
+
127
+ revealNeighbors(mine) {
128
+ const x = parseInt(mine.classList[1].substring(1));
129
+ const y = parseInt(mine.classList[2].substring(1));
130
+
131
+ const neighbors_coords = this.getNeighbors(x, y);
132
+
133
+ neighbors_coords.forEach(([nx, ny]) => {
134
+ const neighbor = document.querySelector(`.x${nx}.y${ny}`);
135
+ if (neighbor && neighbor.isMasked && !neighbor.isFlagged) {
136
+ neighbor.reveal();
137
+ }
138
+ });
139
+ }
140
+
141
+ bindEvents() {
142
+ if (!isBrowser) return;
143
+
144
+ Array.from(document.getElementsByClassName('cell')).forEach(cell => {
145
+ cell.addEventListener('click', evt => this.onClick(cell, evt));
146
+ cell.addEventListener('dblclick', () => this.onDoubleClick(cell));
147
+ cell.addEventListener('contextmenu', evt => this.onRightClick(cell, evt));
148
+
149
+ if (iDevice) {
150
+ cell.addEventListener('touchstart', () => {
151
+ this.holding = setTimeout(() => cell.dispatchEvent(new Event('contextmenu')), 500);
152
+ });
153
+ cell.addEventListener('touchend', () => clearTimeout(this.holding));
154
+ }
155
+ });
156
+
157
+ window.addEventListener('keydown', evt => {
158
+ if (evt.key === 'r' || evt.key === 'R') {
159
+ this.restart();
160
+ }
161
+ });
162
+ }
163
+
164
+ onClick(cell, evt) {
165
+ if (!cell.isMasked || cell.isFlagged) return;
166
+ if (document.getElementsByClassName('unmasked').length === 0) {
167
+ this.startTimer();
168
+ }
169
+
170
+ if (cell.isBomb) {
171
+ this.reveal(cell);
172
+ this.checkGameStatus();
173
+ return;
174
+ }
175
+
176
+ cell.reveal();
177
+ this.updateFeedback(cell.getAttribute('aria-label'));
178
+ if (cell.mine_count === 0 && !cell.isBomb) this.revealNeighbors(cell);
179
+ this.checkGameStatus();
180
+ }
181
+
182
+ onDoubleClick(cell) {
183
+ if (cell.isFlagged) return;
184
+ this.moveIt();
185
+ cell.reveal();
186
+ this.revealNeighbors(cell);
187
+ this.checkGameStatus();
188
+ }
189
+
190
+ onRightClick(cell, evt) {
191
+ evt.preventDefault();
192
+ if (!cell.isMasked) return;
193
+ cell.isFlagged = !cell.isFlagged;
194
+ const emoji = cell.isFlagged ? this.emojiset[2] : this.emojiset[3];
195
+ const label = cell.isFlagged ? 'Flagged as potential bomb' : 'Field';
196
+
197
+ cell.innerHTML = emoji;
198
+ cell.setAttribute('aria-label', label);
199
+ this.updateFeedback(cell.isFlagged ? 'Flagged as potential bomb' : 'Unflagged as potential bomb');
200
+ this.updateBombsLeft();
201
+ }
202
+
203
+ checkGameStatus() {
204
+ const cells = document.getElementsByClassName('cell');
205
+ const masked = Array.from(cells).filter(cell => cell.isMasked);
206
+ const bombs = Array.from(cells).filter(cell => cell.isBomb && !cell.isMasked);
207
+
208
+ if (bombs.length > 0) {
209
+ masked.forEach(cell => cell.reveal());
210
+ this.endGame('lost');
211
+ } else if (masked.length === this.number_of_bombs) {
212
+ masked.forEach(cell => cell.reveal(true));
213
+ this.endGame('won');
214
+ }
215
+ }
216
+
217
+ endGame(result) {
218
+ this.result = result;
219
+ this.showMessage();
220
+ clearInterval(this.timer);
221
+ }
222
+
223
+ bomb_array() {
224
+ const data = Array(this.number_of_cells).fill(false);
225
+ for (let i = 0; i < this.number_of_bombs; i++) data[i] = true;
226
+ return data.sort(() => Math.random() - 0.5);
227
+ }
228
+
229
+ resetMetadata() {
230
+ document.getElementById('timer').textContent = '0.00';
231
+ document.getElementById('bombs').textContent = this.number_of_bombs;
232
+ document.getElementById('moves').textContent = '0';
233
+ document.querySelector('.wrapper').classList.remove('won', 'lost');
234
+ document.querySelector('.result-emoji').textContent = '';
235
+ document.querySelector('.default-emoji').textContent = 'Cuckoosweepers 🐦‍⬛🧹';
236
+ document.querySelector('.js-settings').style.display = 'none';
237
+ }
238
+
239
+ updateBombsLeft() {
240
+ const bombsLeft = this.number_of_bombs - document.querySelectorAll('.cell[aria-label="Flagged as potential bomb"]').length;
241
+ document.getElementById('bombs').textContent = bombsLeft;
242
+ }
243
+
244
+ updateFeedback(text) {
245
+ if (feedback) {
246
+ feedback.textContent = text;
247
+ }
248
+ }
249
+
250
+ prepareEmoji() {
251
+ }
252
+
253
+ restart() {
254
+ clearInterval(this.timer);
255
+ this.timer = null;
256
+ this.result = null;
257
+ this.init();
258
+ }
259
+
260
+ showMessage() {
261
+ const wrapper = document.querySelector('.wrapper');
262
+ const resultEmoji = document.querySelector('.result-emoji');
263
+ wrapper.classList.add(this.result);
264
+ resultEmoji.textContent = this.result === 'won' ? 'You won!!! 😎' : 'You lost... 😵';
265
+ }
266
+
267
+ moveIt() {
268
+ this.moves = (this.moves || 0) + 1;
269
+ document.getElementById('moves').textContent = this.moves;
270
+ }
271
+
272
+ startTimer() {
273
+ if (this.timer) return;
274
+ this.startTime = new Date();
275
+ this.timer = setInterval(() => {
276
+ const elapsed = ((new Date() - this.startTime) / 1000).toFixed(2);
277
+ document.getElementById('timer').textContent = elapsed;
278
+ }, 100);
279
+ }
280
+ }
281
+
282
+ // document.addEventListener('DOMContentLoaded', () => {
283
+ // const emojiSet = ['🐣', '🐦‍⬛', '🧹', '⬜'];
284
+ // const game = new Game(10, 10, 10, emojiSet);
285
+ // });
icon.jpg ADDED
index.html CHANGED
@@ -1,19 +1,329 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Cuckoosweepers</title>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="apple-mobile-web-app-capable" content="yes" />
8
+ <link rel="apple-touch-icon" href="/icon.jpg" />
9
+ <link rel="manifest" href="/manifest.json" />
10
+
11
+ <style type="text/css">
12
+ body {
13
+ font-family: AppleColorEmoji, 'Exo 2', sans-serif;
14
+ font-size: 15px;
15
+ text-align: center;
16
+ padding: 20px 0;
17
+ }
18
+
19
+ span, input, a, div, button {
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ .btn, .input {
24
+ padding: 12px 15px;
25
+ font-size: 0.65em;
26
+ border-radius: 3px;
27
+ color: #666;
28
+ font-weight: bold;
29
+ display: inline-block;
30
+ }
31
+
32
+ .btn {
33
+ background: #f7f7f7;
34
+ text-decoration: none;
35
+ text-transform: uppercase;
36
+ border: 0;
37
+ cursor: pointer;
38
+ margin-bottom: 10px;
39
+ letter-spacing: 1px;
40
+ }
41
+
42
+ .flex {
43
+ display: flex;
44
+ }
45
+
46
+ .flex-cell {
47
+ flex: 1 1 auto;
48
+ }
49
+
50
+ .action-btn {
51
+ margin-bottom: 10px;
52
+ background-color: transparent;
53
+ width: 100%;
54
+ display: block;
55
+ font-size: 1.2em;
56
+ padding: 10px;
57
+ border: 1px solid #eee;
58
+ text-decoration: none;
59
+ border-radius: 5px;
60
+ }
61
+
62
+ .action-btn:hover {
63
+ background-color: #f4f4f4;
64
+ }
65
+
66
+ .action-btn .emoji {
67
+ width: 20px;
68
+ height: 20px;
69
+ }
70
+
71
+ input[type=radio] {
72
+ margin: 0 5px 0 0;
73
+ }
74
+
75
+ input[type=number] {
76
+ padding-right: 5px;
77
+ }
78
+
79
+ .input {
80
+ border: 1px solid #e0e0e0;
81
+ }
82
+
83
+ .input-select {
84
+ width: 100%;
85
+ height: 40px;
86
+ padding: 12px;
87
+ font-size: 1em;
88
+ margin-bottom: 10px;
89
+ background: transparent;
90
+ }
91
+
92
+ .divider {
93
+ align-self: center;
94
+ flex: 0;
95
+ padding: 0 10px;
96
+ color: #999;
97
+ font-size: 0.65em;
98
+ }
99
+
100
+ .prepend-input {
101
+ padding: 7px 8px 6px 10px;
102
+ margin: 5px 0;
103
+ border-right: 1px solid #e0e0e0;
104
+ line-height: 1;
105
+ position: absolute;
106
+ }
107
+
108
+ .prepend-input + input {
109
+ width: 100%;
110
+ padding-left: 45px;
111
+ margin-bottom: 10px;
112
+ }
113
+
114
+ code {
115
+ display: none;
116
+ margin-top: 10px;
117
+ text-transform: none;
118
+ background-color: #fff;
119
+ padding: 2px 4px;
120
+ max-width: 400px;
121
+ text-align: left;
122
+ }
123
+
124
+ .cell .emoji {
125
+ width: 100%;
126
+ }
127
+
128
+ .cell {
129
+ -webkit-touch-callout: none;
130
+ width: 25px;
131
+ height: 25px;
132
+ font-size: 20px;
133
+ background-color: transparent;
134
+ border: 0;
135
+ display: inline-block;
136
+ position: relative;
137
+ padding: 2px 3px;
138
+ vertical-align: middle;
139
+ cursor: pointer;
140
+ }
141
+
142
+ .unmasked {
143
+ cursor: default;
144
+ }
145
+
146
+ #map {
147
+ white-space: nowrap;
148
+ }
149
+
150
+ .wrapper {
151
+ padding: 10px;
152
+ position: relative;
153
+ -webkit-user-select: none;
154
+ user-select: none;
155
+ border-radius: 10px;
156
+ margin-top: 10px;
157
+ border: 5px solid #f4f4f4;
158
+ display: inline-block;
159
+ min-width: 250px;
160
+ }
161
+
162
+ .won .default-emoji,
163
+ .lost .default-emoji {
164
+ display: none;
165
+ }
166
+
167
+ .won #map,
168
+ .lost #map {
169
+ pointer-events: none;
170
+ }
171
+
172
+ .lost {
173
+ box-shadow: 0 0 1px #f00;
174
+ }
175
+
176
+ .won {
177
+ box-shadow: 0 0 1px #1a1;
178
+ }
179
+
180
+ .bar {
181
+ margin: 10px -10px -10px;
182
+ background-color: #f4f4f4;
183
+ }
184
+
185
+ .stat {
186
+ width: 33.3%;
187
+ font-size: 0.85em;
188
+ padding: 8px 10px 5px;
189
+ text-align: center;
190
+ }
191
+
192
+ .small-text {
193
+ display: block;
194
+ color: #999;
195
+ margin-top: 5px;
196
+ font-size: 0.7em;
197
+ letter-spacing: 1px;
198
+ }
199
+
200
+ .settings {
201
+ position: absolute;
202
+ width: 40px;
203
+ height: 40px;
204
+ top: -20px;
205
+ right: -20px;
206
+ background-color: #f4f4f4;
207
+ text-align: center;
208
+ border-radius: 25px;
209
+ padding: 2px 8px;
210
+ border: 5px solid #fff;
211
+ cursor: pointer;
212
+ z-index: 1;
213
+ }
214
+
215
+ .settings .emoji {
216
+ width: 15px;
217
+ margin: 6px 0;
218
+ }
219
+
220
+ .settings-popup {
221
+ margin-right: 10px;
222
+ display: none;
223
+ position: absolute;
224
+ }
225
+
226
+ .settings-popup button {
227
+ width: 100%;
228
+ margin: 0;
229
+ }
230
+
231
+ .settings-popup .flex {
232
+ margin-bottom: 10px;
233
+ }
234
+
235
+ .show {
236
+ display: block;
237
+ }
238
+
239
+ .settings-popup.show ~ * {
240
+ visibility: hidden;
241
+ }
242
+
243
+ .feedback {
244
+ font-size: 1px;
245
+ width: 1px;
246
+ height: 1px;
247
+ overflow: hidden;
248
+ position: absolute;
249
+ left: -1px;
250
+ top: -1px;
251
+ }
252
+ </style>
253
+ </head>
254
+ <body>
255
+ <div class="wrapper">
256
+ <button type="button" class="settings js-settings" aria-haspopup="true" aria-expanded="false" aria-label="Settings"></button>
257
+ <div class="js-settings-popup settings-popup">
258
+ <div class="flex">
259
+ <label class="btn flex-cell" aria-label="Use Twitter Emoji"><input type="radio" name="emoji" id="twemoji" checked> Twemoji</label>
260
+ <label class="btn flex-cell" aria-label="Use Native Emoji"><input type="radio" name="emoji" id="emoji"> Native emoji</label>
261
+ </div>
262
+ <div class="flex">
263
+ <input id="cols" class="input flex-cell" type="number" value="10" min="1" max="500" aria-label="Columns">
264
+ <div class="flex-cell divider">&times;</div>
265
+ <input id="rows" class="input flex-cell" type="number" value="10" min="1" max="500" aria-label="Rows"><br>
266
+ </div>
267
+ <div class="prepend-input">💣</div>
268
+ <input id="bombs" class="input" type="number" value="10" min="1" max="2500" aria-label="Number of bombs">
269
+ <select id="emojiset" class="input input-select" aria-label="Change emoji set">
270
+ <option value="🐣 🐦‍⬛ 🧹 ◻️">🐣 🐦‍⬛ 🧹</option>
271
+ <option value="⭐ 💣 🚩 ◻️">⭐ 💣 🚩</option>
272
+ </select>
273
+ <button class="js-popup-new-game btn" type="button">Save and restart game</button>
274
+ </div>
275
+ <button type="button" class="action-btn js-new-game" aria-label="New game">
276
+ <span class="default-emoji"></span>
277
+ <span id="result" class="result-emoji"></span>
278
+ </button>
279
+ <div id="map" role="grid" aria-label="Mine field"></div>
280
+ <div class="bar flex">
281
+ <div class="stat flex-cell"><div id="bombs-left">10</div><span class="small-text">BOMBS</span></div>
282
+ <div class="stat flex-cell"><div id="moves"></div><span class="small-text">MOVES</span></div>
283
+ <div class="stat flex-cell"><div id="timer"></div><span class="small-text">TIME</span></div>
284
+ </div>
285
+ </div>
286
+ <div aria-live="assertive" class="feedback"></div>
287
+ <script src="/game.js"></script>
288
+ <script >
289
+ let emojiset = document.getElementById('emojiset').value.split(' ');
290
+ let cols = document.getElementById('cols');
291
+ let rows = document.getElementById('rows');
292
+ let bombs = document.getElementById('bombs');
293
+
294
+ let game = new Game(cols.value, rows.value, bombs.value, emojiset, document.getElementById('twemoji').checked);
295
+
296
+ document.querySelector('.js-new-game').addEventListener('click', restart);
297
+ document.querySelector('.js-popup-new-game').addEventListener('click', restart);
298
+
299
+ document.querySelector('.js-settings').addEventListener('click', function() {
300
+ const list = document.querySelector('.js-settings-popup').classList;
301
+ list.contains('show') ? list.remove('show') : list.add('show');
302
+ this.setAttribute('aria-expanded', list.contains('show'));
303
+ });
304
+
305
+ function restart() {
306
+ clearInterval(game.timer);
307
+ emojiset = document.getElementById('emojiset').value.split(' ');
308
+ game = new Game(cols.value, rows.value, bombs.value, emojiset, document.getElementById('twemoji').checked);
309
+ document.querySelector('.js-settings-popup').classList.remove('show');
310
+ return false;
311
+ }
312
+ </script>
313
+
314
+ <script>
315
+ if ('serviceWorker' in navigator) {
316
+ window.addEventListener('load', () => {
317
+ navigator.serviceWorker.register('sw.js')
318
+ .then(registration => {
319
+ console.log('Service Worker registered with scope:', registration.scope);
320
+ })
321
+ .catch(error => {
322
+ console.error('Service Worker registration failed:', error);
323
+ });
324
+ });
325
+ }
326
+ </script>
327
+
328
+ </body>
329
+ </html>
manifest.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "short_name": "Cuckoosweepers",
3
+ "name": "Cuckoosweepers",
4
+ "start_url": "/",
5
+ "display": "standalone",
6
+ "background_color": "#ffffff",
7
+ "theme_color": "#000000",
8
+ "icons": [
9
+ {
10
+ "src": "/icon.jpg",
11
+ "type": "image/png",
12
+ "sizes": "192x192"
13
+ },
14
+ {
15
+ "src": "/icon.jpg",
16
+ "type": "image/png",
17
+ "sizes": "512x512"
18
+ }
19
+ ],
20
+ "scope": "/",
21
+ "orientation": "portrait-primary",
22
+ "description": "A fun Minesweeper-like game for all devices."
23
+ }
sw.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const CACHE_NAME = 'cuckoosweepers-v2';
2
+ const urlsToCache = [
3
+ '/',
4
+ '/index.html',
5
+ '/game.js',
6
+ '/icon.jpg',
7
+ '/manifest.json',
8
+ ];
9
+
10
+ self.addEventListener('install', event => {
11
+ event.waitUntil(
12
+ caches.open(CACHE_NAME).then(cache => {
13
+ console.log('Opened cache');
14
+ return cache.addAll(urlsToCache).catch(error => {
15
+ console.error('Failed to cache resources:', error);
16
+ });
17
+ })
18
+ );
19
+ });
20
+
21
+ self.addEventListener('fetch', event => {
22
+ event.respondWith(
23
+ caches.match(event.request).then(response => {
24
+ if (response) {
25
+ console.log('Serving from cache:', event.request.url);
26
+ return response;
27
+ }
28
+ console.log('Fetching from network:', event.request.url);
29
+ return fetch(event.request);
30
+ }).catch(error => {
31
+ console.error('Fetch failed:', error);
32
+ })
33
+ );
34
+ });
35
+
36
+ self.addEventListener('activate', event => {
37
+ const cacheWhitelist = [CACHE_NAME];
38
+ event.waitUntil(
39
+ caches.keys().then(cacheNames => {
40
+ return Promise.all(
41
+ cacheNames.map(cacheName => {
42
+ if (!cacheWhitelist.includes(cacheName)) {
43
+ console.log('Deleting old cache:', cacheName);
44
+ return caches.delete(cacheName);
45
+ }
46
+ })
47
+ );
48
+ })
49
+ );
50
+ });