kimhyunwoo commited on
Commit
fc163a4
verified
1 Parent(s): 018fff2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +77 -129
index.html CHANGED
@@ -15,8 +15,8 @@
15
  justify-content: center;
16
  min-height: 100vh;
17
  margin: 0;
18
- background-color: #f5f5f5;
19
- overflow-x: hidden; /* Prevent horizontal scrollbar */
20
  }
21
 
22
  #game-container {
@@ -24,7 +24,7 @@
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 {
@@ -32,68 +32,58 @@
32
  font-weight: bold;
33
  margin-bottom: 0.5em;
34
  text-align: center;
35
- color: #444;
36
  user-select: none;
37
  }
38
 
39
  #game-instructions {
40
  text-align: center;
41
  margin-bottom: 1em;
42
- color: #666;
43
  user-select: none;
44
- width: 90%;
45
  }
46
 
47
  #game-board {
48
- border-radius: 8px;
49
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
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 {
66
  padding: 10px 20px;
67
  font-size: 1em;
68
- border: 1px solid #ccc;
69
  border-radius: 5px;
70
- background-color: #f9f9f9;
71
- color: #555;
72
  cursor: pointer;
73
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
74
  user-select: none;
75
  }
76
 
77
- .game-button:hover {
78
- background-color: #eee;
79
- border-color: #bbb;
80
  color: #333;
81
  }
82
-
83
- .game-button:active {
84
  transform: translateY(1px);
85
  background-color: #ddd;
86
  }
87
 
 
88
  #loading-indicator {
89
  display: none;
90
  margin-top: 20px;
91
  border: 6px solid #f3f3f3;
92
- border-top: 6px solid #999;
93
  border-radius: 50%;
94
  width: 50px;
95
  height: 50px;
96
- animation: spin 1s linear infinite;
97
  }
98
 
99
  @keyframes spin {
@@ -109,10 +99,6 @@
109
  #game-instructions {
110
  font-size: 0.9em;
111
  }
112
-
113
- .game-button {
114
- padding: 8px 16px;
115
- }
116
  }
117
  </style>
118
  </head>
@@ -122,64 +108,47 @@
122
  <h1 id="game-title">Connect Four</h1>
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;
135
- this.audio = new Audio('/stone.ogg');
136
  this.num_cols = num_cols;
137
  this.num_rows = num_rows;
138
  var this_ = this;
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
 
185
  this.reset = function () {
@@ -190,16 +159,12 @@
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,9 +186,7 @@
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,15 +200,11 @@
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) {
251
  let x = info["action"];
@@ -265,10 +224,13 @@
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);
274
  this_.mouse_x = loc_x;
@@ -278,26 +240,27 @@
278
  this_.mouse_x >= 0 &&
279
  this_.mouse_y >= 0 &&
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();
296
  var touch = e.touches[0];
297
  var x = touch.clientX - rect.left;
298
  var y = touch.clientY - rect.top;
299
- this_.handleClick(x,y);
300
-
301
  }, false);
302
 
303
 
@@ -308,37 +271,34 @@
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);
317
  ctx.fillStyle = color;
318
  ctx.fill();
319
  ctx.lineWidth = 0.02;
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";
338
  ctx.fillRect(-0.5, -0.5, num_cols, num_rows);
339
 
340
  ctx.lineWidth = 0.1 / 2;
341
- ctx.strokeStyle = "rgba(0, 0, 0, 0.1)";
342
  for (let i = 0; i < this.num_cols; i++) {
343
  ctx.beginPath();
344
  ctx.moveTo(i, 0);
@@ -356,7 +316,7 @@
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,12 +330,12 @@
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,26 +365,16 @@
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,17 +382,15 @@
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);
443
  document.getElementById("game-instructions").textContent = "Failed to load the AI. Please try refreshing the page.";
444
  document.getElementById("ai-first").disabled = true;
445
- document.getElementById("reset-button").disabled = true;
446
  });
447
  });
448
  </script>
 
15
  justify-content: center;
16
  min-height: 100vh;
17
  margin: 0;
18
+ background-color: #f5f5f5; /* Very Light Gray */
19
+ overflow: hidden;
20
  }
21
 
22
  #game-container {
 
24
  flex-direction: column;
25
  align-items: center;
26
  width: 100%;
27
+ max-width: 600px;
28
  }
29
 
30
  #game-title {
 
32
  font-weight: bold;
33
  margin-bottom: 0.5em;
34
  text-align: center;
35
+ color: #444; /* Darker Gray */
36
  user-select: none;
37
  }
38
 
39
  #game-instructions {
40
  text-align: center;
41
  margin-bottom: 1em;
42
+ color: #666; /* Medium Gray */
43
  user-select: none;
 
44
  }
45
 
46
  #game-board {
47
+ border-radius: 8px; /* Slightly Rounded Corners */
48
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Very Subtle Shadow */
49
+ border: 1px solid #ddd; /* Light Gray Border */
50
+ background-color: #fff; /* White Background */
51
  touch-action: none;
 
 
52
  }
53
 
54
+ #ai-first {
 
 
55
  margin-top: 1em;
 
 
 
 
 
56
  padding: 10px 20px;
57
  font-size: 1em;
58
+ border: 1px solid #ccc; /* Light Gray Border */
59
  border-radius: 5px;
60
+ background-color: #f9f9f9; /* Almost White */
61
+ color: #555; /* Dark Gray Text */
62
  cursor: pointer;
63
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
64
  user-select: none;
65
  }
66
 
67
+ #ai-first:hover {
68
+ background-color: #eee; /* Slightly Darker on Hover */
69
+ border-color: #bbb; /* Darker Border on Hover */
70
  color: #333;
71
  }
72
+ #ai-first:active {
 
73
  transform: translateY(1px);
74
  background-color: #ddd;
75
  }
76
 
77
+ /* Loading indicator */
78
  #loading-indicator {
79
  display: none;
80
  margin-top: 20px;
81
  border: 6px solid #f3f3f3;
82
+ border-top: 6px solid #999; /* Medium Gray */
83
  border-radius: 50%;
84
  width: 50px;
85
  height: 50px;
86
+ animation: spin 1s linear infinite; /* Faster Spin */
87
  }
88
 
89
  @keyframes spin {
 
99
  #game-instructions {
100
  font-size: 0.9em;
101
  }
 
 
 
 
102
  }
103
  </style>
104
  </head>
 
108
  <h1 id="game-title">Connect Four</h1>
109
  <p id="game-instructions">Try to connect four stones in a row, a column, or a diagonal.</p>
110
  <canvas id="game-board"></canvas>
111
+ <button type="button" id="ai-first">AI goes first</button>
 
 
 
112
  <div id="loading-indicator"></div>
113
  </div>
 
114
  <script>
115
+ // ... (rest of your JavaScript code, with changes below) ...
116
+
117
  function BoardGame(agent, num_rows, num_cols) {
118
  this.agent = agent;
119
+ this.audio = new Audio('/stone.ogg'); // Consider a more subtle sound.
120
  this.num_cols = num_cols;
121
  this.num_rows = num_rows;
122
  var this_ = this;
123
  this.canvas_ctx = document.getElementById("game-board").getContext("2d");
124
+ this.board_scale = 1; // Initial scale
125
 
126
+ this.calculateBoardScale = function() {
 
127
  const containerWidth = document.getElementById("game-container").offsetWidth;
128
+ const containerHeight = window.innerHeight - document.getElementById("game-instructions").offsetHeight
129
+ - document.getElementById("ai-first").offsetHeight - 80;
130
 
 
 
 
 
 
 
 
 
131
 
132
+ const canvasWidth = containerWidth * 0.95; // Use 95% of container width
 
 
 
133
  const canvasHeight = canvasWidth * (num_rows + 1) / (num_cols + 1);
134
 
135
+ if(canvasHeight > containerHeight) {
136
+ const adjustedCanvasHeight = containerHeight * 0.9;
137
+ const adjustedCanvasWidth = adjustedCanvasHeight * (num_cols+1) / (num_rows+1);
 
 
138
  this.board_scale = adjustedCanvasHeight / (num_rows + 1);
139
+ this_.canvas_ctx.canvas.width = adjustedCanvasWidth;
140
+ this_.canvas_ctx.canvas.height = adjustedCanvasHeight;
141
+ }
142
+ else{
 
143
  this.board_scale = canvasWidth / (num_cols + 1);
144
+ this_.canvas_ctx.canvas.width = canvasWidth;
145
+ this_.canvas_ctx.canvas.height = canvasHeight;
146
  }
147
+ this_.canvas_ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transform
148
+ this_.canvas_ctx.scale(this.board_scale, this.board_scale);
149
+ this_.canvas_ctx.translate(0.5, 0.5);
150
+ }
151
+ this.calculateBoardScale();
 
 
152
 
153
 
154
  this.reset = function () {
 
159
  this.who_play = 1;
160
  this.ai_player = -1;
161
  this.game_ended = false;
 
162
  };
163
+ this.reset();
 
164
  this.get = function (row, col) {
165
  return this.board[this.num_cols * row + col];
166
+ }
 
167
  this.is_terminated = function () {
 
168
  if (this.board.some((x) => x == 0) == false) return true;
169
  for (let i = 0; i < this.num_rows; i++) {
170
  for (let j = 0; j < this.num_cols; j++) {
 
186
  }
187
  return false;
188
  };
 
189
  this.submit_board = async function () {
 
190
  document.getElementById("loading-indicator").style.display = "block";
191
  await new Promise(r => setTimeout(r, 1000));
192
  if (this_.is_terminated()) return { "terminated": true, "action": -1 };
 
200
  "action": action,
201
  };
202
  };
 
203
  this.end_game = function () {
204
  this.game_ended = true;
205
+ setTimeout(function () { this_.reset(); this_.render(); }, 3000);
206
  };
 
207
  this.ai_play = function () {
 
 
208
  this_.submit_board().then(
209
  function (info) {
210
  let x = info["action"];
 
224
  console.error("AI play error:", e);
225
  });
226
  };
227
+ document.getElementById("ai-first").onclick = function () {
228
+ this_.reset();
229
+ this_.ai_player = 1;
230
+ this_.ai_play();
231
+ };
232
 
233
+ this.handleClick = function(x, y) {
 
 
234
  var loc_x = Math.floor(x / this_.board_scale - 0.5);
235
  var loc_y = Math.floor(y / this_.board_scale - 0.5);
236
  this_.mouse_x = loc_x;
 
240
  this_.mouse_x >= 0 &&
241
  this_.mouse_y >= 0 &&
242
  this_.mouse_x < this_.num_cols &&
243
+ this_.mouse_y < this_.num_rows &&
244
+ this_.game_ended == false
245
  ) {
246
+ if (this_.who_play == this_.ai_player) return false;
247
  let i = this_.mouse_y * this_.num_cols + this_.mouse_x;
248
+ if (this_.board[i] != 0) return false;
249
  this_.board[i] = this_.who_play;
250
  this_.audio.play();
251
  this_.who_play = -this_.who_play;
252
  this_.render();
253
  this_.ai_play();
254
  }
255
+ }
256
+
257
  document.getElementById("game-board").addEventListener('touchstart', function(e) {
258
+ e.preventDefault();
259
  var rect = this.getBoundingClientRect();
260
  var touch = e.touches[0];
261
  var x = touch.clientX - rect.left;
262
  var y = touch.clientY - rect.top;
263
+ this_.handleClick(x, y);
 
264
  }, false);
265
 
266
 
 
271
  this_.handleClick(x,y);
272
  }, false);
273
 
274
+
275
+
276
  this.draw_stone = function (x, y, color) {
277
+ let ctx = this.canvas_ctx;
 
278
  y = this.num_rows - 1 - y;
279
  ctx.beginPath();
280
  ctx.arc(x, y, 0.40, 0, 2 * Math.PI, false);
281
  ctx.fillStyle = color;
282
  ctx.fill();
283
  ctx.lineWidth = 0.02;
284
+ ctx.strokeStyle = "rgba(0, 0, 0, 0.1)"; // Very Light Border
285
  ctx.stroke();
286
  };
 
287
  this.get_candidate = function (x) {
 
288
  for (let i = 0; i < this.num_rows; i++) {
289
  let idx = i * this.num_cols + x;
290
  if (this.board[idx] == 0) return [x, i];
291
  }
292
  return [-1, -1];
293
  };
 
294
  this.render = function () {
 
295
  let ctx = this.canvas_ctx;
296
  ctx.clearRect(-1, -1, num_cols + 1, num_rows + 1);
297
+ ctx.fillStyle = "#fff"; // White board
298
  ctx.fillRect(-0.5, -0.5, num_cols, num_rows);
299
 
300
  ctx.lineWidth = 0.1 / 2;
301
+ ctx.strokeStyle = "rgba(0, 0, 0, 0.1)"; // Very subtle grid lines
302
  for (let i = 0; i < this.num_cols; i++) {
303
  ctx.beginPath();
304
  ctx.moveTo(i, 0);
 
316
  let x = i % this.num_cols;
317
  let y = Math.floor(i / this.num_cols);
318
  if (this.board[i] == 0) continue;
319
+ let color = (this.board[i] == 1) ? "#555" : "#bbb"; // Dark Gray and Light Gray
320
  this.draw_stone(x, y, color);
321
  }
322
 
 
330
  if (x == -1) return;
331
  this.mouse_x = x;
332
  this.mouse_y = y;
333
+ let previewColor = (this.who_play == -1) ? "rgba(187, 187, 187, 0.5)" : "rgba(85, 85, 85, 0.5)"; // Semi-transparent grays
334
  this.draw_stone(x, y, previewColor);
335
  }
336
  };
337
 
338
+ this.handleMove = function(e) {
339
  let rect = this.canvas_ctx.canvas.getBoundingClientRect();
340
  let x, y;
341
 
 
365
  this_.handleMove(e);
366
  };
367
 
368
+ document.getElementById("game-board").addEventListener('touchmove', function(e) {
369
  this_.handleMove(e);
370
+ }, false);
 
 
 
 
 
 
 
 
 
 
 
371
 
 
 
 
 
372
 
373
+ window.addEventListener('resize', function() {
374
+ this_.calculateBoardScale();
375
+ this_.render();
376
+ });
377
+ };
378
  const modelUrl = '/model.json';
379
 
380
  const init_fn = async function () {
 
382
  const model = await tf.loadGraphModel(modelUrl);
383
  return model;
384
  };
 
385
  document.addEventListener("DOMContentLoaded", function (event) {
386
  init_fn().then(function (agent) {
387
  game = new BoardGame(agent, 6, 7);
388
+ game.render();
389
 
390
  }).catch(error => {
391
  console.error("Error loading model:", error);
392
  document.getElementById("game-instructions").textContent = "Failed to load the AI. Please try refreshing the page.";
393
  document.getElementById("ai-first").disabled = true;
 
394
  });
395
  });
396
  </script>