kevin213 commited on
Commit
5ed7ec6
·
verified ·
1 Parent(s): ee33ee3

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +772 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Gobang
3
- emoji: 🏢
4
- colorFrom: purple
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: gobang
3
+ emoji: 🐳
4
+ colorFrom: red
5
+ colorTo: red
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,772 @@
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="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>现代化五子棋对战</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .board {
11
+ display: grid;
12
+ grid-template-columns: repeat(15, 1fr);
13
+ grid-template-rows: repeat(15, 1fr);
14
+ gap: 1px;
15
+ background-color: #e5c07b;
16
+ border: 2px solid #5c3a21;
17
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
18
+ width: 600px;
19
+ height: 600px;
20
+ }
21
+
22
+ .cell {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ background-color: rgba(229, 192, 123, 0.8);
27
+ position: relative;
28
+ cursor: pointer;
29
+ transition: all 0.2s ease;
30
+ }
31
+
32
+ .cell:hover {
33
+ background-color: rgba(229, 192, 123, 0.6);
34
+ }
35
+
36
+ .cell::before, .cell::after {
37
+ content: '';
38
+ position: absolute;
39
+ background-color: #5c3a21;
40
+ }
41
+
42
+ .cell::before {
43
+ width: 100%;
44
+ height: 1px;
45
+ top: 50%;
46
+ }
47
+
48
+ .cell::after {
49
+ width: 1px;
50
+ height: 100%;
51
+ left: 50%;
52
+ }
53
+
54
+ .piece {
55
+ width: 36px;
56
+ height: 36px;
57
+ border-radius: 50%;
58
+ z-index: 10;
59
+ box-shadow: 0 3px 5px rgba(0, 0, 0, 0.3);
60
+ }
61
+
62
+ .black {
63
+ background: radial-gradient(circle at 30% 30%, #666, #000);
64
+ }
65
+
66
+ .white {
67
+ background: radial-gradient(circle at 30% 30%, #fff, #ccc);
68
+ border: 1px solid #999;
69
+ }
70
+
71
+ .last-move {
72
+ box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.7);
73
+ }
74
+
75
+ .win-line {
76
+ position: absolute;
77
+ background-color: rgba(255, 0, 0, 0.7);
78
+ z-index: 5;
79
+ transform-origin: left center;
80
+ }
81
+
82
+ @keyframes fadeIn {
83
+ from { opacity: 0; }
84
+ to { opacity: 1; }
85
+ }
86
+
87
+ .modal {
88
+ animation: fadeIn 0.3s ease-out;
89
+ }
90
+ </style>
91
+ </head>
92
+ <body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">
93
+ <div class="max-w-6xl w-full">
94
+ <h1 class="text-3xl font-bold text-center mb-6 text-gray-800">五子棋对战</h1>
95
+
96
+ <div class="flex flex-col md:flex-row gap-6 items-center md:items-start justify-center">
97
+ <!-- 游戏面板 -->
98
+ <div class="relative">
99
+ <div class="board mx-auto">
100
+ <!-- 棋盘将通过JavaScript动态生成 -->
101
+ </div>
102
+
103
+ <!-- 游戏状态指示器 -->
104
+ <div class="mt-4 flex justify-between items-center px-2">
105
+ <div class="flex items-center">
106
+ <div class="w-6 h-6 rounded-full bg-black mr-2"></div>
107
+ <span id="black-score" class="font-medium">0</span>
108
+ </div>
109
+ <div id="game-status" class="px-4 py-1 bg-gray-200 rounded-full text-sm font-medium">黑方回合</div>
110
+ <div class="flex items-center">
111
+ <div class="w-6 h-6 rounded-full bg-white border border-gray-400 mr-2"></div>
112
+ <span id="white-score" class="font-medium">0</span>
113
+ </div>
114
+ </div>
115
+ </div>
116
+
117
+ <!-- 游戏控制面板 -->
118
+ <div class="bg-white p-6 rounded-xl shadow-md w-full max-w-md">
119
+ <h2 class="text-xl font-semibold mb-4 text-gray-800">游戏设置</h2>
120
+
121
+ <div class="space-y-4">
122
+ <div>
123
+ <label class="block text-sm font-medium text-gray-700 mb-1">游戏模式</label>
124
+ <div class="flex gap-2">
125
+ <button id="pvp-btn" class="px-4 py-2 bg-blue-600 text-white rounded-md flex-1 hover:bg-blue-700 transition">
126
+ <i class="fas fa-users mr-2"></i>双人对战
127
+ </button>
128
+ <button id="ai-btn" class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md flex-1 hover:bg-gray-300 transition">
129
+ <i class="fas fa-robot mr-2"></i>人机对战
130
+ </button>
131
+ </div>
132
+ </div>
133
+
134
+ <div>
135
+ <label class="block text-sm font-medium text-gray-700 mb-1">AI难度</label>
136
+ <select id="ai-level" class="w-full p-2 border border-gray-300 rounded-md" disabled>
137
+ <option value="1">简单</option>
138
+ <option value="2" selected>中等</option>
139
+ <option value="3">困难</option>
140
+ </select>
141
+ </div>
142
+
143
+ <div class="pt-2">
144
+ <button id="new-game-btn" class="w-full py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition flex items-center justify-center">
145
+ <i class="fas fa-plus-circle mr-2"></i>新游戏
146
+ </button>
147
+ </div>
148
+
149
+ <div class="pt-2">
150
+ <button id="undo-btn" class="w-full py-2 bg-yellow-500 text-white rounded-md hover:bg-yellow-600 transition flex items-center justify-center">
151
+ <i class="fas fa-undo mr-2"></i>悔棋
152
+ </button>
153
+ </div>
154
+
155
+ <div class="pt-2">
156
+ <button id="restart-btn" class="w-full py-2 bg-red-500 text-white rounded-md hover:bg-red-600 transition flex items-center justify-center">
157
+ <i class="fas fa-redo mr-2"></i>重新开始
158
+ </button>
159
+ </div>
160
+ </div>
161
+
162
+ <div class="mt-6 pt-4 border-t border-gray-200">
163
+ <h3 class="text-sm font-medium text-gray-700 mb-2">游戏记录</h3>
164
+ <div id="move-history" class="max-h-40 overflow-y-auto text-sm space-y-1">
165
+ <div class="text-gray-500 italic">游戏尚未开始</div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <!-- 胜利模态框 -->
173
+ <div id="win-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden modal">
174
+ <div class="bg-white p-8 rounded-xl max-w-md w-full mx-4">
175
+ <div class="text-center">
176
+ <h2 id="win-title" class="text-2xl font-bold mb-4"></h2>
177
+ <div id="win-message" class="mb-6"></div>
178
+ <div class="flex gap-4 justify-center">
179
+ <button id="play-again-btn" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">
180
+ 再玩一局
181
+ </button>
182
+ <button id="close-modal-btn" class="px-6 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300 transition">
183
+ 关闭
184
+ </button>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <script>
191
+ document.addEventListener('DOMContentLoaded', () => {
192
+ // 游戏状态
193
+ const gameState = {
194
+ board: Array(15).fill().map(() => Array(15).fill(0)), // 0=空, 1=黑, 2=白
195
+ currentPlayer: 1, // 1=黑, 2=白
196
+ gameOver: false,
197
+ lastMove: null,
198
+ moveHistory: [],
199
+ gameMode: 'pvp', // 'pvp' or 'ai'
200
+ aiLevel: 2, // 1-3
201
+ scores: { black: 0, white: 0 }
202
+ };
203
+
204
+ // DOM元素
205
+ const boardElement = document.querySelector('.board');
206
+ const gameStatusElement = document.getElementById('game-status');
207
+ const blackScoreElement = document.getElementById('black-score');
208
+ const whiteScoreElement = document.getElementById('white-score');
209
+ const moveHistoryElement = document.getElementById('move-history');
210
+ const pvpBtn = document.getElementById('pvp-btn');
211
+ const aiBtn = document.getElementById('ai-btn');
212
+ const aiLevelSelect = document.getElementById('ai-level');
213
+ const newGameBtn = document.getElementById('new-game-btn');
214
+ const undoBtn = document.getElementById('undo-btn');
215
+ const restartBtn = document.getElementById('restart-btn');
216
+ const winModal = document.getElementById('win-modal');
217
+ const winTitle = document.getElementById('win-title');
218
+ const winMessage = document.getElementById('win-message');
219
+ const playAgainBtn = document.getElementById('play-again-btn');
220
+ const closeModalBtn = document.getElementById('close-modal-btn');
221
+
222
+ // 初始化棋盘
223
+ function initBoard() {
224
+ boardElement.innerHTML = '';
225
+
226
+ for (let row = 0; row < 15; row++) {
227
+ for (let col = 0; col < 15; col++) {
228
+ const cell = document.createElement('div');
229
+ cell.className = 'cell';
230
+ cell.dataset.row = row;
231
+ cell.dataset.col = col;
232
+
233
+ cell.addEventListener('click', () => handleCellClick(row, col));
234
+ boardElement.appendChild(cell);
235
+ }
236
+ }
237
+ }
238
+
239
+ // 处理点击棋盘
240
+ function handleCellClick(row, col) {
241
+ if (gameState.gameOver || gameState.board[row][col] !== 0) return;
242
+
243
+ // 人机模式下,如果是AI的回合,不允许玩家下子
244
+ if (gameState.gameMode === 'ai' && gameState.currentPlayer === 2) return;
245
+
246
+ makeMove(row, col);
247
+
248
+ // 人机模式下,玩家下完后AI下子
249
+ if (gameState.gameMode === 'ai' && !gameState.gameOver && gameState.currentPlayer === 2) {
250
+ setTimeout(() => {
251
+ makeAIMove();
252
+ }, 500);
253
+ }
254
+ }
255
+
256
+ // 下子
257
+ function makeMove(row, col) {
258
+ gameState.board[row][col] = gameState.currentPlayer;
259
+ gameState.lastMove = { row, col, player: gameState.currentPlayer };
260
+ gameState.moveHistory.push({ row, col, player: gameState.currentPlayer });
261
+
262
+ updateBoard();
263
+
264
+ // 检查是否胜利
265
+ if (checkWin(row, col)) {
266
+ gameState.gameOver = true;
267
+ const winner = gameState.currentPlayer === 1 ? '黑方' : '白方';
268
+
269
+ // 更新分数
270
+ if (gameState.currentPlayer === 1) {
271
+ gameState.scores.black++;
272
+ blackScoreElement.textContent = gameState.scores.black;
273
+ } else {
274
+ gameState.scores.white++;
275
+ whiteScoreElement.textContent = gameState.scores.white;
276
+ }
277
+
278
+ showWinModal(`${winner}胜利!`, `恭喜${winner}获得胜利!`);
279
+ return;
280
+ }
281
+
282
+ // 切换玩家
283
+ gameState.currentPlayer = gameState.currentPlayer === 1 ? 2 : 1;
284
+ updateGameStatus();
285
+
286
+ // 添加到历史记录
287
+ addToMoveHistory(row, col);
288
+ }
289
+
290
+ // AI下子
291
+ function makeAIMove() {
292
+ if (gameState.gameOver) return;
293
+
294
+ let row, col;
295
+
296
+ // 简单AI - 随机下子
297
+ if (gameState.aiLevel === 1) {
298
+ do {
299
+ row = Math.floor(Math.random() * 15);
300
+ col = Math.floor(Math.random() * 15);
301
+ } while (gameState.board[row][col] !== 0);
302
+ }
303
+ // 中等AI - 简单评估
304
+ else if (gameState.aiLevel === 2) {
305
+ const move = findBestMove(1); // 只考虑一步
306
+ row = move.row;
307
+ col = move.col;
308
+ }
309
+ // 困难AI - 更复杂的评估
310
+ else {
311
+ const move = findBestMove(2); // 考虑两步
312
+ row = move.row;
313
+ col = move.col;
314
+ }
315
+
316
+ makeMove(row, col);
317
+ }
318
+
319
+ // 寻找最佳下法 (简化版)
320
+ function findBestMove(depth) {
321
+ // 优先检查是否有可以立即获胜的位置
322
+ for (let row = 0; row < 15; row++) {
323
+ for (let col = 0; col < 15; col++) {
324
+ if (gameState.board[row][col] === 0) {
325
+ // 检查AI是否能在这里获胜
326
+ gameState.board[row][col] = 2;
327
+ if (checkWin(row, col)) {
328
+ gameState.board[row][col] = 0;
329
+ return { row, col, score: 10000 };
330
+ }
331
+ gameState.board[row][col] = 0;
332
+
333
+ // 检查玩家是否能在这里获胜 (需要阻止)
334
+ gameState.board[row][col] = 1;
335
+ if (checkWin(row, col)) {
336
+ gameState.board[row][col] = 0;
337
+ return { row, col, score: 9000 };
338
+ }
339
+ gameState.board[row][col] = 0;
340
+ }
341
+ }
342
+ }
343
+
344
+ // 如果没有立即获胜或阻止的位置,则评估棋盘
345
+ const moves = [];
346
+
347
+ // 只在已有棋子周围的空位考虑 (提高效率)
348
+ const consideredPositions = new Set();
349
+ for (let row = 0; row < 15; row++) {
350
+ for (let col = 0; col < 15; col++) {
351
+ if (gameState.board[row][col] !== 0) {
352
+ // 检查周围3x3区域
353
+ for (let r = Math.max(0, row - 1); r <= Math.min(14, row + 1); r++) {
354
+ for (let c = Math.max(0, col - 1); c <= Math.min(14, col + 1); c++) {
355
+ if (gameState.board[r][c] === 0 && !consideredPositions.has(`${r},${c}`)) {
356
+ consideredPositions.add(`${r},${c}`);
357
+ const score = evaluatePosition(r, c);
358
+ moves.push({ row: r, col: c, score });
359
+ }
360
+ }
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ // 如果没有可考虑的位置 (开局),随机选择中心区域
367
+ if (moves.length === 0) {
368
+ const centerMoves = [];
369
+ for (let row = 5; row <= 9; row++) {
370
+ for (let col = 5; col <= 9; col++) {
371
+ if (gameState.board[row][col] === 0) {
372
+ centerMoves.push({ row, col, score: 100 });
373
+ }
374
+ }
375
+ }
376
+
377
+ if (centerMoves.length > 0) {
378
+ return centerMoves[Math.floor(Math.random() * centerMoves.length)];
379
+ }
380
+
381
+ // 如果中心区域也没有位置,随机选择
382
+ let row, col;
383
+ do {
384
+ row = Math.floor(Math.random() * 15);
385
+ col = Math.floor(Math.random() * 15);
386
+ } while (gameState.board[row][col] !== 0);
387
+
388
+ return { row, col, score: 0 };
389
+ }
390
+
391
+ // 按分数排序并返回最佳移动
392
+ moves.sort((a, b) => b.score - a.score);
393
+ return moves[0];
394
+ }
395
+
396
+ // 评估位置分数
397
+ function evaluatePosition(row, col) {
398
+ let score = 0;
399
+
400
+ // 四个方向: 水平, 垂直, 对角线, 反对角线
401
+ const directions = [
402
+ [0, 1], // 水平
403
+ [1, 0], // 垂直
404
+ [1, 1], // 对角线
405
+ [1, -1] // 反对角线
406
+ ];
407
+
408
+ for (const [dx, dy] of directions) {
409
+ // 评估AI (白子) 的潜力
410
+ score += evaluateLine(row, col, dx, dy, 2) * 2;
411
+
412
+ // 评估玩家 (黑子) 的潜力 (需要阻止)
413
+ score += evaluateLine(row, col, dx, dy, 1);
414
+ }
415
+
416
+ // 中心位置更有价值
417
+ const centerDist = Math.abs(row - 7) + Math.abs(col - 7);
418
+ score += (14 - centerDist) * 10;
419
+
420
+ return score;
421
+ }
422
+
423
+ // 评估一条线上的潜力
424
+ function evaluateLine(row, col, dx, dy, player) {
425
+ let score = 0;
426
+ let count = 0; // 连续的同色棋子
427
+ let empty = 0; // 两端的空位
428
+
429
+ // 向一个方向检查
430
+ for (let i = 1; i <= 4; i++) {
431
+ const r = row + i * dx;
432
+ const c = col + i * dy;
433
+
434
+ if (r < 0 || r >= 15 || c < 0 || c >= 15) break;
435
+
436
+ if (gameState.board[r][c] === player) {
437
+ count++;
438
+ } else if (gameState.board[r][c] === 0) {
439
+ empty++;
440
+ break;
441
+ } else {
442
+ break;
443
+ }
444
+ }
445
+
446
+ // 向相反方向检查
447
+ for (let i = 1; i <= 4; i++) {
448
+ const r = row - i * dx;
449
+ const c = col - i * dy;
450
+
451
+ if (r < 0 || r >= 15 || c < 0 || c >= 15) break;
452
+
453
+ if (gameState.board[r][c] === player) {
454
+ count++;
455
+ } else if (gameState.board[r][c] === 0) {
456
+ empty++;
457
+ break;
458
+ } else {
459
+ break;
460
+ }
461
+ }
462
+
463
+ // 根据连续棋子和空位计算分数
464
+ if (count >= 4) return 10000; // 可以形成五连
465
+ if (count === 3 && empty === 2) return 5000; // 活四
466
+ if (count === 3 && empty === 1) return 1000; // 冲四
467
+ if (count === 2 && empty === 2) return 500; // 活三
468
+ if (count === 2 && empty === 1) return 100; // 冲三
469
+ if (count === 1 && empty === 2) return 50; // 活二
470
+
471
+ return 0;
472
+ }
473
+
474
+ // 检查是否胜利
475
+ function checkWin(row, col) {
476
+ const player = gameState.board[row][col];
477
+ const directions = [
478
+ [0, 1], // 水平
479
+ [1, 0], // 垂直
480
+ [1, 1], // 对角线
481
+ [1, -1] // 反对角线
482
+ ];
483
+
484
+ for (const [dx, dy] of directions) {
485
+ let count = 1; // 已经有一个棋子
486
+
487
+ // 向一个方向检查
488
+ for (let i = 1; i <= 4; i++) {
489
+ const r = row + i * dx;
490
+ const c = col + i * dy;
491
+
492
+ if (r < 0 || r >= 15 || c < 0 || c >= 15 || gameState.board[r][c] !== player) break;
493
+ count++;
494
+ }
495
+
496
+ // 向相反方向检查
497
+ for (let i = 1; i <= 4; i++) {
498
+ const r = row - i * dx;
499
+ const c = col - i * dy;
500
+
501
+ if (r < 0 || r >= 15 || c < 0 || c >= 15 || gameState.board[r][c] !== player) break;
502
+ count++;
503
+ }
504
+
505
+ if (count >= 5) {
506
+ // 绘制胜利线
507
+ drawWinLine(row, col, dx, dy, count);
508
+ return true;
509
+ }
510
+ }
511
+
512
+ return false;
513
+ }
514
+
515
+ // 绘制胜利线
516
+ function drawWinLine(row, col, dx, dy, count) {
517
+ // 找到线的起点和终点
518
+ let startRow = row;
519
+ let startCol = col;
520
+ let endRow = row;
521
+ let endCol = col;
522
+
523
+ // 向一个方向延伸
524
+ for (let i = 1; i <= 4; i++) {
525
+ const r = row + i * dx;
526
+ const c = col + i * dy;
527
+
528
+ if (r < 0 || r >= 15 || c < 0 || c >= 15 || gameState.board[r][c] !== gameState.board[row][col]) break;
529
+
530
+ endRow = r;
531
+ endCol = c;
532
+ }
533
+
534
+ // 向相反方向延伸
535
+ for (let i = 1; i <= 4; i++) {
536
+ const r = row - i * dx;
537
+ const c = col - i * dy;
538
+
539
+ if (r < 0 || r >= 15 || c < 0 || c >= 15 || gameState.board[r][c] !== gameState.board[row][col]) break;
540
+
541
+ startRow = r;
542
+ startCol = c;
543
+ }
544
+
545
+ // 创建线元素
546
+ const line = document.createElement('div');
547
+ line.className = 'win-line';
548
+
549
+ // 计算线的位置和角度
550
+ const cellSize = boardElement.offsetWidth / 15;
551
+ const startX = (startCol + 0.5) * cellSize;
552
+ const startY = (startRow + 0.5) * cellSize;
553
+ const endX = (endCol + 0.5) * cellSize;
554
+ const endY = (endRow + 0.5) * cellSize;
555
+
556
+ const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
557
+ const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
558
+
559
+ // 设置线的样式
560
+ line.style.width = `${length}px`;
561
+ line.style.height = '4px';
562
+ line.style.left = `${startX}px`;
563
+ line.style.top = `${startY}px`;
564
+ line.style.transform = `rotate(${angle}deg)`;
565
+
566
+ boardElement.appendChild(line);
567
+ }
568
+
569
+ // 更新棋盘显示
570
+ function updateBoard() {
571
+ // 清除所有棋子
572
+ document.querySelectorAll('.piece').forEach(p => p.remove());
573
+
574
+ // 重新绘制所有��子
575
+ for (let row = 0; row < 15; row++) {
576
+ for (let col = 0; col < 15; col++) {
577
+ if (gameState.board[row][col] !== 0) {
578
+ const cell = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
579
+ const piece = document.createElement('div');
580
+ piece.className = `piece ${gameState.board[row][col] === 1 ? 'black' : 'white'}`;
581
+
582
+ // 标记最后一步
583
+ if (gameState.lastMove && gameState.lastMove.row === row && gameState.lastMove.col === col) {
584
+ piece.classList.add('last-move');
585
+ }
586
+
587
+ cell.appendChild(piece);
588
+ }
589
+ }
590
+ }
591
+ }
592
+
593
+ // 更新游戏状态显示
594
+ function updateGameStatus() {
595
+ if (gameState.gameOver) {
596
+ gameStatusElement.textContent = '游戏结束';
597
+ gameStatusElement.className = 'px-4 py-1 bg-gray-400 rounded-full text-sm font-medium text-white';
598
+ return;
599
+ }
600
+
601
+ if (gameState.currentPlayer === 1) {
602
+ gameStatusElement.textContent = '黑方回合';
603
+ gameStatusElement.className = 'px-4 py-1 bg-gray-800 rounded-full text-sm font-medium text-white';
604
+ } else {
605
+ gameStatusElement.textContent = gameState.gameMode === 'pvp' ? '白方回合' : 'AI思考中...';
606
+ gameStatusElement.className = 'px-4 py-1 bg-white border border-gray-300 rounded-full text-sm font-medium';
607
+ }
608
+ }
609
+
610
+ // 添加到历史记录
611
+ function addToMoveHistory(row, col) {
612
+ const moveNum = gameState.moveHistory.length;
613
+ const player = gameState.moveHistory[moveNum - 1].player;
614
+ const moveText = `${moveNum}. ${player === 1 ? '黑' : '白'} (${String.fromCharCode(65 + col)}${15 - row})`;
615
+
616
+ // 如果历史记录为空,清除占位文本
617
+ if (moveHistoryElement.firstChild && moveHistoryElement.firstChild.textContent === '游戏尚未开始') {
618
+ moveHistoryElement.innerHTML = '';
619
+ }
620
+
621
+ const moveElement = document.createElement('div');
622
+ moveElement.className = 'flex justify-between items-center py-1 px-2 bg-gray-100 rounded';
623
+ moveElement.innerHTML = `
624
+ <span>${moveText}</span>
625
+ <button class="text-blue-600 hover:text-blue-800 text-xs" data-move="${moveNum - 1}">
626
+ <i class="fas fa-undo"></i>
627
+ </button>
628
+ `;
629
+
630
+ moveHistoryElement.prepend(moveElement);
631
+
632
+ // 为悔棋按钮添加事件
633
+ moveElement.querySelector('button').addEventListener('click', (e) => {
634
+ const moveIndex = parseInt(e.target.closest('button').dataset.move);
635
+ undoToMove(moveIndex);
636
+ });
637
+
638
+ // 限制历史记录数量
639
+ if (moveHistoryElement.children.length > 10) {
640
+ moveHistoryElement.removeChild(moveHistoryElement.lastChild);
641
+ }
642
+ }
643
+
644
+ // 悔棋到指定步数
645
+ function undoToMove(moveIndex) {
646
+ if (gameState.gameOver) return;
647
+
648
+ // 重置游戏状态
649
+ gameState.board = Array(15).fill().map(() => Array(15).fill(0));
650
+ gameState.currentPlayer = 1;
651
+ gameState.gameOver = false;
652
+ gameState.lastMove = null;
653
+
654
+ // 重新执行所有步数直到指定步
655
+ for (let i = 0; i <= moveIndex; i++) {
656
+ const move = gameState.moveHistory[i];
657
+ gameState.board[move.row][move.col] = move.player;
658
+ gameState.currentPlayer = move.player === 1 ? 2 : 1;
659
+ gameState.lastMove = move;
660
+ }
661
+
662
+ // 截断历史记录
663
+ gameState.moveHistory = gameState.moveHistory.slice(0, moveIndex + 1);
664
+
665
+ // 更新显示
666
+ updateBoard();
667
+ updateGameStatus();
668
+
669
+ // 重新生成历史记录
670
+ moveHistoryElement.innerHTML = '';
671
+ for (let i = 0; i <= moveIndex; i++) {
672
+ addToMoveHistory(gameState.moveHistory[i].row, gameState.moveHistory[i].col);
673
+ }
674
+
675
+ // 如果是AI模式且是AI的��合,让AI下子
676
+ if (gameState.gameMode === 'ai' && gameState.currentPlayer === 2) {
677
+ setTimeout(() => {
678
+ makeAIMove();
679
+ }, 500);
680
+ }
681
+ }
682
+
683
+ // 显示胜利模态框
684
+ function showWinModal(title, message) {
685
+ winTitle.textContent = title;
686
+ winMessage.innerHTML = `
687
+ <div class="flex justify-center mb-4">
688
+ <div class="w-16 h-16 rounded-full ${gameState.currentPlayer === 1 ? 'bg-black' : 'bg-white border border-gray-400'}"></div>
689
+ </div>
690
+ <p class="text-lg">${message}</p>
691
+ `;
692
+ winModal.classList.remove('hidden');
693
+ }
694
+
695
+ // 新游戏
696
+ function newGame() {
697
+ // 重置游戏状态
698
+ gameState.board = Array(15).fill().map(() => Array(15).fill(0));
699
+ gameState.currentPlayer = 1;
700
+ gameState.gameOver = false;
701
+ gameState.lastMove = null;
702
+ gameState.moveHistory = [];
703
+
704
+ // 清除胜利线
705
+ document.querySelectorAll('.win-line').forEach(line => line.remove());
706
+
707
+ // 更新显示
708
+ updateBoard();
709
+ updateGameStatus();
710
+ moveHistoryElement.innerHTML = '<div class="text-gray-500 italic">游戏尚未开始</div>';
711
+
712
+ // 如果是AI模式且AI先手,让AI下子
713
+ if (gameState.gameMode === 'ai' && gameState.currentPlayer === 2) {
714
+ setTimeout(() => {
715
+ makeAIMove();
716
+ }, 500);
717
+ }
718
+ }
719
+
720
+ // 重新开始 (重置分数)
721
+ function restartGame() {
722
+ gameState.scores = { black: 0, white: 0 };
723
+ blackScoreElement.textContent = '0';
724
+ whiteScoreElement.textContent = '0';
725
+ newGame();
726
+ }
727
+
728
+ // 事件监听器
729
+ pvpBtn.addEventListener('click', () => {
730
+ gameState.gameMode = 'pvp';
731
+ pvpBtn.className = 'px-4 py-2 bg-blue-600 text-white rounded-md flex-1 hover:bg-blue-700 transition';
732
+ aiBtn.className = 'px-4 py-2 bg-gray-200 text-gray-800 rounded-md flex-1 hover:bg-gray-300 transition';
733
+ aiLevelSelect.disabled = true;
734
+ newGame();
735
+ });
736
+
737
+ aiBtn.addEventListener('click', () => {
738
+ gameState.gameMode = 'ai';
739
+ pvpBtn.className = 'px-4 py-2 bg-gray-200 text-gray-800 rounded-md flex-1 hover:bg-gray-300 transition';
740
+ aiBtn.className = 'px-4 py-2 bg-blue-600 text-white rounded-md flex-1 hover:bg-blue-700 transition';
741
+ aiLevelSelect.disabled = false;
742
+ newGame();
743
+ });
744
+
745
+ aiLevelSelect.addEventListener('change', () => {
746
+ gameState.aiLevel = parseInt(aiLevelSelect.value);
747
+ });
748
+
749
+ newGameBtn.addEventListener('click', newGame);
750
+ undoBtn.addEventListener('click', () => {
751
+ if (gameState.moveHistory.length > 0) {
752
+ undoToMove(gameState.moveHistory.length - 2);
753
+ }
754
+ });
755
+
756
+ restartBtn.addEventListener('click', restartGame);
757
+ playAgainBtn.addEventListener('click', () => {
758
+ winModal.classList.add('hidden');
759
+ newGame();
760
+ });
761
+
762
+ closeModalBtn.addEventListener('click', () => {
763
+ winModal.classList.add('hidden');
764
+ });
765
+
766
+ // 初始化游戏
767
+ initBoard();
768
+ updateGameStatus();
769
+ });
770
+ </script>
771
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=kevin213/gobang" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
772
+ </html>