Spaces:
Running
Running
Update index.html
Browse files- index.html +80 -69
index.html
CHANGED
@@ -16,7 +16,7 @@
|
|
16 |
min-height: 100vh;
|
17 |
margin: 0;
|
18 |
background-color: #f5f5f5;
|
19 |
-
overflow: hidden;
|
20 |
}
|
21 |
|
22 |
#game-container {
|
@@ -24,7 +24,7 @@
|
|
24 |
flex-direction: column;
|
25 |
align-items: center;
|
26 |
width: 100%;
|
27 |
-
max-width: 600px;
|
28 |
}
|
29 |
|
30 |
#game-title {
|
@@ -41,7 +41,7 @@
|
|
41 |
margin-bottom: 1em;
|
42 |
color: #666;
|
43 |
user-select: none;
|
44 |
-
width: 90%;
|
45 |
}
|
46 |
|
47 |
#game-board {
|
@@ -50,18 +50,16 @@
|
|
50 |
border: 1px solid #ddd;
|
51 |
background-color: #fff;
|
52 |
touch-action: none;
|
53 |
-
|
54 |
-
|
55 |
-
margin: 0 auto; /* Center horizontally */
|
56 |
-
|
57 |
}
|
58 |
|
59 |
#button-container {
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
}
|
66 |
|
67 |
.game-button {
|
@@ -87,7 +85,6 @@
|
|
87 |
background-color: #ddd;
|
88 |
}
|
89 |
|
90 |
-
|
91 |
#loading-indicator {
|
92 |
display: none;
|
93 |
margin-top: 20px;
|
@@ -112,8 +109,9 @@
|
|
112 |
#game-instructions {
|
113 |
font-size: 0.9em;
|
114 |
}
|
|
|
115 |
.game-button {
|
116 |
-
padding: 8px 16px;
|
117 |
}
|
118 |
}
|
119 |
</style>
|
@@ -125,12 +123,12 @@
|
|
125 |
<p id="game-instructions">Try to connect four stones in a row, a column, or a diagonal.</p>
|
126 |
<canvas id="game-board"></canvas>
|
127 |
<div id="button-container">
|
128 |
-
|
129 |
-
|
130 |
</div>
|
131 |
-
|
132 |
<div id="loading-indicator"></div>
|
133 |
</div>
|
|
|
134 |
<script>
|
135 |
function BoardGame(agent, num_rows, num_cols) {
|
136 |
this.agent = agent;
|
@@ -141,35 +139,46 @@
|
|
141 |
this.canvas_ctx = document.getElementById("game-board").getContext("2d");
|
142 |
this.board_scale = 1;
|
143 |
|
144 |
-
this.calculateBoardScale = function() {
|
|
|
145 |
const containerWidth = document.getElementById("game-container").offsetWidth;
|
146 |
-
// Calculate available height, accounting for other elements
|
147 |
-
const containerHeight = window.innerHeight - document.getElementById("game-title").offsetHeight
|
148 |
-
- document.getElementById("game-instructions").offsetHeight
|
149 |
-
- document.getElementById("button-container").offsetHeight - 80; // Add some margin
|
150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
|
|
|
152 |
const canvasWidth = containerWidth * 0.95;
|
|
|
|
|
153 |
const canvasHeight = canvasWidth * (num_rows + 1) / (num_cols + 1);
|
154 |
|
155 |
-
//
|
156 |
-
if(canvasHeight > containerHeight) {
|
157 |
-
|
158 |
-
const
|
|
|
159 |
this.board_scale = adjustedCanvasHeight / (num_rows + 1);
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
else{
|
|
|
164 |
this.board_scale = canvasWidth / (num_cols + 1);
|
165 |
-
|
166 |
-
|
167 |
}
|
168 |
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
|
|
173 |
this.calculateBoardScale(); // Initial calculation
|
174 |
|
175 |
|
@@ -181,13 +190,16 @@
|
|
181 |
this.who_play = 1;
|
182 |
this.ai_player = -1;
|
183 |
this.game_ended = false;
|
184 |
-
this.render();
|
185 |
};
|
|
|
186 |
|
187 |
this.get = function (row, col) {
|
188 |
return this.board[this.num_cols * row + col];
|
189 |
-
}
|
|
|
190 |
this.is_terminated = function () {
|
|
|
191 |
if (this.board.some((x) => x == 0) == false) return true;
|
192 |
for (let i = 0; i < this.num_rows; i++) {
|
193 |
for (let j = 0; j < this.num_cols; j++) {
|
@@ -209,7 +221,9 @@
|
|
209 |
}
|
210 |
return false;
|
211 |
};
|
|
|
212 |
this.submit_board = async function () {
|
|
|
213 |
document.getElementById("loading-indicator").style.display = "block";
|
214 |
await new Promise(r => setTimeout(r, 1000));
|
215 |
if (this_.is_terminated()) return { "terminated": true, "action": -1 };
|
@@ -223,14 +237,14 @@
|
|
223 |
"action": action,
|
224 |
};
|
225 |
};
|
|
|
226 |
this.end_game = function () {
|
227 |
this.game_ended = true;
|
228 |
-
// Optional: Display a message to the user here.
|
229 |
setTimeout(function () { this_.reset(); }, 3000);
|
230 |
};
|
231 |
|
232 |
this.ai_play = function () {
|
233 |
-
|
234 |
|
235 |
this_.submit_board().then(
|
236 |
function (info) {
|
@@ -243,20 +257,17 @@
|
|
243 |
this_.who_play = -this_.who_play;
|
244 |
this_.render();
|
245 |
}
|
246 |
-
|
247 |
this_.end_game();
|
248 |
}
|
249 |
-
|
250 |
}
|
251 |
).catch(function (e) {
|
252 |
console.error("AI play error:", e);
|
253 |
});
|
254 |
};
|
255 |
|
256 |
-
|
257 |
-
|
258 |
-
this.handleClick = function(x, y) {
|
259 |
-
if (this_.game_ended) return; // Don't allow moves if game is over
|
260 |
|
261 |
var loc_x = Math.floor(x / this_.board_scale - 0.5);
|
262 |
var loc_y = Math.floor(y / this_.board_scale - 0.5);
|
@@ -269,17 +280,16 @@
|
|
269 |
this_.mouse_x < this_.num_cols &&
|
270 |
this_.mouse_y < this_.num_rows
|
271 |
) {
|
272 |
-
if (this_.who_play == this_.ai_player) return
|
273 |
let i = this_.mouse_y * this_.num_cols + this_.mouse_x;
|
274 |
-
if (this_.board[i] != 0) return
|
275 |
this_.board[i] = this_.who_play;
|
276 |
this_.audio.play();
|
277 |
this_.who_play = -this_.who_play;
|
278 |
this_.render();
|
279 |
-
this_.ai_play();
|
280 |
}
|
281 |
-
}
|
282 |
-
|
283 |
document.getElementById("game-board").addEventListener('touchstart', function(e) {
|
284 |
e.preventDefault(); // Prevent other events
|
285 |
var rect = this.getBoundingClientRect();
|
@@ -298,9 +308,9 @@
|
|
298 |
this_.handleClick(x,y);
|
299 |
}, false);
|
300 |
|
301 |
-
|
302 |
this.draw_stone = function (x, y, color) {
|
303 |
-
|
|
|
304 |
y = this.num_rows - 1 - y;
|
305 |
ctx.beginPath();
|
306 |
ctx.arc(x, y, 0.40, 0, 2 * Math.PI, false);
|
@@ -310,14 +320,18 @@
|
|
310 |
ctx.strokeStyle = "rgba(0, 0, 0, 0.1)";
|
311 |
ctx.stroke();
|
312 |
};
|
|
|
313 |
this.get_candidate = function (x) {
|
|
|
314 |
for (let i = 0; i < this.num_rows; i++) {
|
315 |
let idx = i * this.num_cols + x;
|
316 |
if (this.board[idx] == 0) return [x, i];
|
317 |
}
|
318 |
return [-1, -1];
|
319 |
};
|
|
|
320 |
this.render = function () {
|
|
|
321 |
let ctx = this.canvas_ctx;
|
322 |
ctx.clearRect(-1, -1, num_cols + 1, num_rows + 1);
|
323 |
ctx.fillStyle = "#fff";
|
@@ -342,8 +356,7 @@
|
|
342 |
let x = i % this.num_cols;
|
343 |
let y = Math.floor(i / this.num_cols);
|
344 |
if (this.board[i] == 0) continue;
|
345 |
-
|
346 |
-
let color = (this.board[i] == 1) ? "#007AFF" : "#FF9500"; // iOS Blue and Orange
|
347 |
this.draw_stone(x, y, color);
|
348 |
}
|
349 |
|
@@ -357,14 +370,12 @@
|
|
357 |
if (x == -1) return;
|
358 |
this.mouse_x = x;
|
359 |
this.mouse_y = y;
|
360 |
-
// Semi-transparent preview
|
361 |
let previewColor = (this.who_play == -1) ? "rgba(255, 149, 0, 0.5)" : "rgba(0, 122, 255, 0.5)";
|
362 |
this.draw_stone(x, y, previewColor);
|
363 |
}
|
364 |
};
|
365 |
|
366 |
-
|
367 |
-
|
368 |
let rect = this.canvas_ctx.canvas.getBoundingClientRect();
|
369 |
let x, y;
|
370 |
|
@@ -394,27 +405,26 @@
|
|
394 |
this_.handleMove(e);
|
395 |
};
|
396 |
|
397 |
-
|
398 |
this_.handleMove(e);
|
399 |
-
|
400 |
|
401 |
-
window.addEventListener('resize', function() {
|
402 |
-
this_.calculateBoardScale();
|
403 |
-
this_.render();
|
404 |
});
|
405 |
|
406 |
-
// Button Event Listeners
|
407 |
document.getElementById("ai-first").onclick = function () {
|
408 |
-
this_.reset();
|
409 |
this_.ai_player = 1;
|
410 |
this_.ai_play();
|
411 |
};
|
412 |
|
413 |
-
document.getElementById("reset-button").onclick = function() {
|
414 |
-
|
415 |
};
|
|
|
416 |
|
417 |
-
};
|
418 |
const modelUrl = '/model.json';
|
419 |
|
420 |
const init_fn = async function () {
|
@@ -422,10 +432,11 @@
|
|
422 |
const model = await tf.loadGraphModel(modelUrl);
|
423 |
return model;
|
424 |
};
|
|
|
425 |
document.addEventListener("DOMContentLoaded", function (event) {
|
426 |
init_fn().then(function (agent) {
|
427 |
game = new BoardGame(agent, 6, 7);
|
428 |
-
game.render();
|
429 |
|
430 |
}).catch(error => {
|
431 |
console.error("Error loading model:", error);
|
|
|
16 |
min-height: 100vh;
|
17 |
margin: 0;
|
18 |
background-color: #f5f5f5;
|
19 |
+
overflow-x: hidden; /* Prevent horizontal scrollbar */
|
20 |
}
|
21 |
|
22 |
#game-container {
|
|
|
24 |
flex-direction: column;
|
25 |
align-items: center;
|
26 |
width: 100%;
|
27 |
+
max-width: 600px; /* Limits width on very large screens */
|
28 |
}
|
29 |
|
30 |
#game-title {
|
|
|
41 |
margin-bottom: 1em;
|
42 |
color: #666;
|
43 |
user-select: none;
|
44 |
+
width: 90%;
|
45 |
}
|
46 |
|
47 |
#game-board {
|
|
|
50 |
border: 1px solid #ddd;
|
51 |
background-color: #fff;
|
52 |
touch-action: none;
|
53 |
+
display: block;
|
54 |
+
margin: 0 auto;
|
|
|
|
|
55 |
}
|
56 |
|
57 |
#button-container {
|
58 |
+
display: flex;
|
59 |
+
gap: 10px;
|
60 |
+
margin-top: 1em;
|
61 |
+
justify-content: center;
|
62 |
+
width: 100%;
|
63 |
}
|
64 |
|
65 |
.game-button {
|
|
|
85 |
background-color: #ddd;
|
86 |
}
|
87 |
|
|
|
88 |
#loading-indicator {
|
89 |
display: none;
|
90 |
margin-top: 20px;
|
|
|
109 |
#game-instructions {
|
110 |
font-size: 0.9em;
|
111 |
}
|
112 |
+
|
113 |
.game-button {
|
114 |
+
padding: 8px 16px;
|
115 |
}
|
116 |
}
|
117 |
</style>
|
|
|
123 |
<p id="game-instructions">Try to connect four stones in a row, a column, or a diagonal.</p>
|
124 |
<canvas id="game-board"></canvas>
|
125 |
<div id="button-container">
|
126 |
+
<button type="button" id="ai-first" class="game-button">AI First</button>
|
127 |
+
<button type="button" id="reset-button" class="game-button">Restart</button>
|
128 |
</div>
|
|
|
129 |
<div id="loading-indicator"></div>
|
130 |
</div>
|
131 |
+
|
132 |
<script>
|
133 |
function BoardGame(agent, num_rows, num_cols) {
|
134 |
this.agent = agent;
|
|
|
139 |
this.canvas_ctx = document.getElementById("game-board").getContext("2d");
|
140 |
this.board_scale = 1;
|
141 |
|
142 |
+
this.calculateBoardScale = function () {
|
143 |
+
// 1. Get the container width.
|
144 |
const containerWidth = document.getElementById("game-container").offsetWidth;
|
|
|
|
|
|
|
|
|
145 |
|
146 |
+
// 2. Calculate the *available* height. This is the crucial part.
|
147 |
+
// We subtract the heights of *all* other elements *and* a margin.
|
148 |
+
const containerHeight = window.innerHeight
|
149 |
+
- document.getElementById("game-title").offsetHeight
|
150 |
+
- document.getElementById("game-instructions").offsetHeight
|
151 |
+
- document.getElementById("button-container").offsetHeight
|
152 |
+
- document.getElementById("loading-indicator").offsetHeight // Include loading indicator
|
153 |
+
- 40; // Margin (adjust as needed)
|
154 |
|
155 |
+
// 3. Calculate a *potential* canvas width (most of the container).
|
156 |
const canvasWidth = containerWidth * 0.95;
|
157 |
+
|
158 |
+
// 4. Calculate the corresponding height to maintain the aspect ratio.
|
159 |
const canvasHeight = canvasWidth * (num_rows + 1) / (num_cols + 1);
|
160 |
|
161 |
+
// 5. Check if the calculated height is *too large* for the available space.
|
162 |
+
if (canvasHeight > containerHeight) {
|
163 |
+
// 5a. If it's too large, scale based on *height*.
|
164 |
+
const adjustedCanvasHeight = containerHeight * 0.9; // Use most of available height
|
165 |
+
const adjustedCanvasWidth = adjustedCanvasHeight * (num_cols + 1) / (num_rows + 1);
|
166 |
this.board_scale = adjustedCanvasHeight / (num_rows + 1);
|
167 |
+
this.canvas_ctx.canvas.width = adjustedCanvasWidth;
|
168 |
+
this.canvas_ctx.canvas.height = adjustedCanvasHeight;
|
169 |
+
|
170 |
+
} else {
|
171 |
+
// 5b. If it fits, use the calculated width and height.
|
172 |
this.board_scale = canvasWidth / (num_cols + 1);
|
173 |
+
this.canvas_ctx.canvas.width = canvasWidth;
|
174 |
+
this.canvas_ctx.canvas.height = canvasHeight;
|
175 |
}
|
176 |
|
177 |
+
// 6. Reset the transformation and apply the new scale and translation.
|
178 |
+
this.canvas_ctx.setTransform(1, 0, 0, 1, 0, 0);
|
179 |
+
this.canvas_ctx.scale(this.board_scale, this.board_scale);
|
180 |
+
this.canvas_ctx.translate(0.5, 0.5);
|
181 |
+
};
|
182 |
this.calculateBoardScale(); // Initial calculation
|
183 |
|
184 |
|
|
|
190 |
this.who_play = 1;
|
191 |
this.ai_player = -1;
|
192 |
this.game_ended = false;
|
193 |
+
this.render();
|
194 |
};
|
195 |
+
this.reset(); // Initial reset
|
196 |
|
197 |
this.get = function (row, col) {
|
198 |
return this.board[this.num_cols * row + col];
|
199 |
+
};
|
200 |
+
|
201 |
this.is_terminated = function () {
|
202 |
+
// ... (rest of is_terminated is the same) ...
|
203 |
if (this.board.some((x) => x == 0) == false) return true;
|
204 |
for (let i = 0; i < this.num_rows; i++) {
|
205 |
for (let j = 0; j < this.num_cols; j++) {
|
|
|
221 |
}
|
222 |
return false;
|
223 |
};
|
224 |
+
|
225 |
this.submit_board = async function () {
|
226 |
+
// ... (rest of submit_board is the same) ...
|
227 |
document.getElementById("loading-indicator").style.display = "block";
|
228 |
await new Promise(r => setTimeout(r, 1000));
|
229 |
if (this_.is_terminated()) return { "terminated": true, "action": -1 };
|
|
|
237 |
"action": action,
|
238 |
};
|
239 |
};
|
240 |
+
|
241 |
this.end_game = function () {
|
242 |
this.game_ended = true;
|
|
|
243 |
setTimeout(function () { this_.reset(); }, 3000);
|
244 |
};
|
245 |
|
246 |
this.ai_play = function () {
|
247 |
+
if (this_.game_ended) return;
|
248 |
|
249 |
this_.submit_board().then(
|
250 |
function (info) {
|
|
|
257 |
this_.who_play = -this_.who_play;
|
258 |
this_.render();
|
259 |
}
|
260 |
+
if (this_.is_terminated() == true) {
|
261 |
this_.end_game();
|
262 |
}
|
|
|
263 |
}
|
264 |
).catch(function (e) {
|
265 |
console.error("AI play error:", e);
|
266 |
});
|
267 |
};
|
268 |
|
269 |
+
this.handleClick = function (x, y) {
|
270 |
+
if (this_.game_ended) return;
|
|
|
|
|
271 |
|
272 |
var loc_x = Math.floor(x / this_.board_scale - 0.5);
|
273 |
var loc_y = Math.floor(y / this_.board_scale - 0.5);
|
|
|
280 |
this_.mouse_x < this_.num_cols &&
|
281 |
this_.mouse_y < this_.num_rows
|
282 |
) {
|
283 |
+
if (this_.who_play == this_.ai_player) return;
|
284 |
let i = this_.mouse_y * this_.num_cols + this_.mouse_x;
|
285 |
+
if (this_.board[i] != 0) return;
|
286 |
this_.board[i] = this_.who_play;
|
287 |
this_.audio.play();
|
288 |
this_.who_play = -this_.who_play;
|
289 |
this_.render();
|
290 |
+
this_.ai_play();
|
291 |
}
|
292 |
+
};
|
|
|
293 |
document.getElementById("game-board").addEventListener('touchstart', function(e) {
|
294 |
e.preventDefault(); // Prevent other events
|
295 |
var rect = this.getBoundingClientRect();
|
|
|
308 |
this_.handleClick(x,y);
|
309 |
}, false);
|
310 |
|
|
|
311 |
this.draw_stone = function (x, y, color) {
|
312 |
+
// ... (rest of draw_stone is the same) ...
|
313 |
+
let ctx = this.canvas_ctx;
|
314 |
y = this.num_rows - 1 - y;
|
315 |
ctx.beginPath();
|
316 |
ctx.arc(x, y, 0.40, 0, 2 * Math.PI, false);
|
|
|
320 |
ctx.strokeStyle = "rgba(0, 0, 0, 0.1)";
|
321 |
ctx.stroke();
|
322 |
};
|
323 |
+
|
324 |
this.get_candidate = function (x) {
|
325 |
+
// ... (rest of get_candidate is the same) ...
|
326 |
for (let i = 0; i < this.num_rows; i++) {
|
327 |
let idx = i * this.num_cols + x;
|
328 |
if (this.board[idx] == 0) return [x, i];
|
329 |
}
|
330 |
return [-1, -1];
|
331 |
};
|
332 |
+
|
333 |
this.render = function () {
|
334 |
+
// ... (rest of render is the same, except for colors) ...
|
335 |
let ctx = this.canvas_ctx;
|
336 |
ctx.clearRect(-1, -1, num_cols + 1, num_rows + 1);
|
337 |
ctx.fillStyle = "#fff";
|
|
|
356 |
let x = i % this.num_cols;
|
357 |
let y = Math.floor(i / this.num_cols);
|
358 |
if (this.board[i] == 0) continue;
|
359 |
+
let color = (this.board[i] == 1) ? "#007AFF" : "#FF9500";
|
|
|
360 |
this.draw_stone(x, y, color);
|
361 |
}
|
362 |
|
|
|
370 |
if (x == -1) return;
|
371 |
this.mouse_x = x;
|
372 |
this.mouse_y = y;
|
|
|
373 |
let previewColor = (this.who_play == -1) ? "rgba(255, 149, 0, 0.5)" : "rgba(0, 122, 255, 0.5)";
|
374 |
this.draw_stone(x, y, previewColor);
|
375 |
}
|
376 |
};
|
377 |
|
378 |
+
this.handleMove = function (e) {
|
|
|
379 |
let rect = this.canvas_ctx.canvas.getBoundingClientRect();
|
380 |
let x, y;
|
381 |
|
|
|
405 |
this_.handleMove(e);
|
406 |
};
|
407 |
|
408 |
+
document.getElementById("game-board").addEventListener('touchmove', function(e) {
|
409 |
this_.handleMove(e);
|
410 |
+
}, false);
|
411 |
|
412 |
+
window.addEventListener('resize', function () {
|
413 |
+
this_.calculateBoardScale(); // Recalculate on resize
|
414 |
+
this_.render(); // Redraw
|
415 |
});
|
416 |
|
|
|
417 |
document.getElementById("ai-first").onclick = function () {
|
418 |
+
this_.reset();
|
419 |
this_.ai_player = 1;
|
420 |
this_.ai_play();
|
421 |
};
|
422 |
|
423 |
+
document.getElementById("reset-button").onclick = function () {
|
424 |
+
this_.reset();
|
425 |
};
|
426 |
+
}
|
427 |
|
|
|
428 |
const modelUrl = '/model.json';
|
429 |
|
430 |
const init_fn = async function () {
|
|
|
432 |
const model = await tf.loadGraphModel(modelUrl);
|
433 |
return model;
|
434 |
};
|
435 |
+
|
436 |
document.addEventListener("DOMContentLoaded", function (event) {
|
437 |
init_fn().then(function (agent) {
|
438 |
game = new BoardGame(agent, 6, 7);
|
439 |
+
game.render(); // Initial render
|
440 |
|
441 |
}).catch(error => {
|
442 |
console.error("Error loading model:", error);
|