11// =====================================================
2- // 五子棋 Ultra V18.2 · 反卡死终极修复版
2+ // 五子棋 Ultra V18.2.1 · 强制落子兜底修复版
33// =====================================================
44document . addEventListener ( 'DOMContentLoaded' , ( ) => {
55 // ---------- DOM 元素 ----------
@@ -54,6 +54,7 @@ document.addEventListener('DOMContentLoaded', () => {
5454 let soundEnabled = true ;
5555 let isAIThinking = false ;
5656 let aiTimeoutHandle = null ;
57+ let forceMoveTimer = null ;
5758
5859 const rankSystem = [
5960 { name : "初学者" , icon : "1" , min : 0 , max : 100 , color : "#6c757d" } ,
@@ -94,7 +95,7 @@ document.addEventListener('DOMContentLoaded', () => {
9495 { version : "17.2" , description : "修复快速连点漏洞:AI思考期间锁定棋盘,防止玩家连下多步" } ,
9596 { version : "18.0 Ultra" , description : "极致攻防一体化:防守系数18.0,复合棋型权重翻倍,双评估通道,深度提升至16层" } ,
9697 { version : "18.1 Ultra" , description : "移除Worker方案,回归主线程+requestAnimationFrame时间切片,帧率恢复55+" } ,
97- { version : "18.2 Ultra" , description : "修复AI卡死与深层卡顿,增加随机兜底与紧急制动机制 " }
98+ { version : "18.2 Ultra" , description : "双保险强制落子,彻底杜绝AI卡死,超时自动从最佳候选落子 " }
9899 ] ;
99100
100101 let gameState = {
@@ -263,6 +264,7 @@ document.addEventListener('DOMContentLoaded', () => {
263264 if ( gameState . mode !== 'pvp' && gameState . currentPlayer === AI ) return ;
264265 if ( gameState . gameOver || gameState . board [ row ] [ col ] !== EMPTY ) return ;
265266
267+ clearTimeout ( forceMoveTimer ) ;
266268 playSound ( placeSound ) ;
267269 const prev = JSON . parse ( JSON . stringify ( gameState . board ) ) ;
268270 gameState . board [ row ] [ col ] = gameState . currentPlayer ;
@@ -288,7 +290,6 @@ document.addEventListener('DOMContentLoaded', () => {
288290 if ( gameState . mode === 'ai' && gameState . currentPlayer === AI && ! gameState . gameOver ) {
289291 isAIThinking = true ;
290292 updateGameStatus ( 'ai' ) ;
291- // 使用 requestAnimationFrame 确保 UI 更新后再计算
292293 requestAnimationFrame ( ( ) => {
293294 aiTimeoutHandle = setTimeout ( makeAIMove , 10 ) ;
294295 } ) ;
@@ -298,7 +299,7 @@ document.addEventListener('DOMContentLoaded', () => {
298299 }
299300 }
300301
301- // ===================== AI 算法 (主线程,极致时间检查) =====================
302+ // ===================== AI 算法 =====================
302303 function findWinningMove ( player ) {
303304 for ( let r = 0 ; r < BOARD_SIZE ; r ++ ) {
304305 for ( let c = 0 ; c < BOARD_SIZE ; c ++ ) {
@@ -443,12 +444,12 @@ document.addEventListener('DOMContentLoaded', () => {
443444 }
444445 depthCount . textContent = gameState . stats . maxDepth ;
445446 winChance . textContent = '0.00%' ;
446- return bestMove || moves [ 0 ] ; // 保证至少返回一个着法
447+ return bestMove || moves [ 0 ] ;
447448 }
448449
449450 function minimax ( depth , alpha , beta , isMax , start , limit ) {
450451 if ( performance . now ( ) - start > limit ) return evaluateBoard ( ) ;
451- // 检查终局
452+ // 终局检测
452453 for ( let r = 0 ; r < BOARD_SIZE ; r ++ ) {
453454 for ( let c = 0 ; c < BOARD_SIZE ; c ++ ) {
454455 if ( gameState . board [ r ] [ c ] !== EMPTY && checkWin ( r , c ) ) {
@@ -496,26 +497,42 @@ document.addEventListener('DOMContentLoaded', () => {
496497 updateGameStatus ( 'ai' ) ;
497498 status . innerHTML = '<i class="fas fa-robot"></i> AI思考中 <span class="thinking"><span>.</span><span>.</span><span>.</span></span>' ;
498499
499- // 使用 requestAnimationFrame 确保 UI 更新后再计算
500+ // 外层强制超时:最多 5 秒必须落子
501+ forceMoveTimer = setTimeout ( ( ) => {
502+ if ( ! isAIThinking ) return ;
503+ // 强制从候选列表取第一个,或随机空位
504+ const moves = genMoves ( ) ;
505+ if ( moves . length ) {
506+ makeMove ( moves [ 0 ] . row , moves [ 0 ] . col ) ;
507+ } else {
508+ const emptyCells = [ ] ;
509+ for ( let r = 0 ; r < BOARD_SIZE ; r ++ ) for ( let c = 0 ; c < BOARD_SIZE ; c ++ ) if ( gameState . board [ r ] [ c ] === EMPTY ) emptyCells . push ( { row : r , col : c } ) ;
510+ if ( emptyCells . length ) {
511+ const rand = emptyCells [ Math . floor ( Math . random ( ) * emptyCells . length ) ] ;
512+ makeMove ( rand . row , rand . col ) ;
513+ }
514+ }
515+ } , 5000 ) ;
516+
500517 requestAnimationFrame ( ( ) => {
501518 const winMove = findWinningMove ( AI ) ;
502- if ( winMove ) { makeMove ( winMove . row , winMove . col ) ; return ; }
519+ if ( winMove ) { clearTimeout ( forceMoveTimer ) ; makeMove ( winMove . row , winMove . col ) ; return ; }
503520 const playerWin = findWinningMove ( PLAYER ) ;
504- if ( playerWin ) { makeMove ( playerWin . row , playerWin . col ) ; return ; }
521+ if ( playerWin ) { clearTimeout ( forceMoveTimer ) ; makeMove ( playerWin . row , playerWin . col ) ; return ; }
505522 const move = getUltimateAIMove ( ) ;
523+ clearTimeout ( forceMoveTimer ) ;
506524 if ( move ) {
507525 makeMove ( move . row , move . col ) ;
508526 } else {
509- // 终极兜底:随机空位
527+ // 兜底随机空位
510528 const emptyCells = [ ] ;
511529 for ( let r = 0 ; r < BOARD_SIZE ; r ++ ) for ( let c = 0 ; c < BOARD_SIZE ; c ++ ) if ( gameState . board [ r ] [ c ] === EMPTY ) emptyCells . push ( { row : r , col : c } ) ;
512530 if ( emptyCells . length ) {
513531 const rand = emptyCells [ Math . floor ( Math . random ( ) * emptyCells . length ) ] ;
514532 makeMove ( rand . row , rand . col ) ;
515533 } else {
516- // 棋盘满,平局
517534 gameState . gameOver = true ;
518- showWinner ( 0 ) ; // 平局
535+ showWinner ( 0 ) ;
519536 }
520537 }
521538 } ) ;
@@ -536,10 +553,11 @@ document.addEventListener('DOMContentLoaded', () => {
536553
537554 function showWinner ( player ) {
538555 isAIThinking = false ;
556+ clearTimeout ( forceMoveTimer ) ;
539557 if ( aiTimeoutHandle ) { clearTimeout ( aiTimeoutHandle ) ; aiTimeoutHandle = null ; }
540558 winMessage . classList . add ( 'show' ) ;
541559 let name , egg ;
542- if ( player === 0 ) { // 平局
560+ if ( player === 0 ) {
543561 name = '平局' ;
544562 egg = '棋盘已满,不分胜负。' ;
545563 } else if ( player === PLAYER ) {
@@ -562,6 +580,7 @@ document.addEventListener('DOMContentLoaded', () => {
562580
563581 function restartGame ( ) {
564582 if ( isAIThinking ) return ;
583+ clearTimeout ( forceMoveTimer ) ;
565584 if ( aiTimeoutHandle ) { clearTimeout ( aiTimeoutHandle ) ; aiTimeoutHandle = null ; }
566585 for ( let r = 0 ; r < BOARD_SIZE ; r ++ ) for ( let c = 0 ; c < BOARD_SIZE ; c ++ ) gameState . board [ r ] [ c ] = EMPTY ;
567586 gameState . currentPlayer = PLAYER ; gameState . gameOver = false ; gameState . moves = [ ] ; gameState . stats . moves = 0 ;
@@ -592,6 +611,7 @@ document.addEventListener('DOMContentLoaded', () => {
592611 function setMode ( mode ) {
593612 playSound ( clickSound ) ;
594613 isAIThinking = false ;
614+ clearTimeout ( forceMoveTimer ) ;
595615 if ( aiTimeoutHandle ) { clearTimeout ( aiTimeoutHandle ) ; aiTimeoutHandle = null ; }
596616 gameState . mode = mode ;
597617 aiModeBtn . classList . toggle ( 'active' , mode === 'ai' ) ;
0 commit comments