Re: 又有一個新的弈棋創意:拚數棋!
把遊戲名字改為「拚數迷陣」,英文為「NumClash Labyrinth」,副標題為「孤獨或生存,敵人亦朋友」,英文為「Isolation or survival, enemies are friends」。
上次由 ejsoon 在 2026年 9月 22日 15:01,总共编辑 1 次。
https://ejsoon.vip/
金梭越空:極速暢遊天地
金梭越空:極速暢遊天地
Re: 又有一個新的弈棋創意:拚數棋!
在這一局中:
q d6m e6m d3q e4u e3o c6t g3l f6u c1m b5p b7s b6j a6s b4r d2r a5n g1m f1o f2s e1t g4q a2l f5r b1o g7n
AI控制的後手方,在最後兩手棋下出了f5r和g7n,這兩手棋都是直接把它的棋子變成「孤獨數」,直接扣分的。為什麼AI會下出直接使它自己扣分的棋?AI是否理解到底怎麼下會得分,怎麼下會送分?
檢查代碼,查找原因並修復。如果有要修改的地方,給出修改代碼的python腳本。
q d6m e6m d3q e4u e3o c6t g3l f6u c1m b5p b7s b6j a6s b4r d2r a5n g1m f1o f2s e1t g4q a2l f5r b1o g7n
AI控制的後手方,在最後兩手棋下出了f5r和g7n,這兩手棋都是直接把它的棋子變成「孤獨數」,直接扣分的。為什麼AI會下出直接使它自己扣分的棋?AI是否理解到底怎麼下會得分,怎麼下會送分?
檢查代碼,查找原因並修復。如果有要修改的地方,給出修改代碼的python腳本。
https://ejsoon.vip/
金梭越空:極速暢遊天地
金梭越空:極速暢遊天地
Re: 又有一個新的弈棋創意:拚數棋!
當點擊控制區的棋子時,棋子會因點擊的不同位置而轉到不同的角度。現在增加功能,當電腦鼠標或手機觸控按住拖動時,所拖動的方向將是棋子要旋轉的方向。
https://ejsoon.vip/
金梭越空:極速暢遊天地
金梭越空:極速暢遊天地
Re: 又有一個新的弈棋創意:拚數棋!
移除 tanh,改用原始分差。(不要一會兒使用目前行動方視角、一會兒使用根節點玩家視角。回傳視角必須與 backpropagation 的正負號邏輯一致。移除 tanh 後,要重新調整 UCT exploration constant,因為 exploitation 從 [-1, 1] 變成了實際分數單位)
根節點候選提高至約 12~32 個,依時間調整。
強制納入所有會改變孤獨數狀態的候選。
rollout 隨機率從 17% 降低
根節點候選提高至約 12~32 個,依時間調整。
強制納入所有會改變孤獨數狀態的候選。
rollout 隨機率從 17% 降低
代码: 全选
此前的分析是:
根節點並不是一次完整比較全部走法,而是每次抽取少量候選:
const sampleSize = Math.min(
pool.remaining,
mode === 'tree' ? 6 : 5
);
rollout 還有 17% 隨機選擇:
const epsilon =
mode === 'tree'
? 0.02
: 0.17;
在七乘七棋盤的大分支數下,3~12 秒的 MCTS 結果可能有相當大的抽樣噪音。
最終分數經 tanh 壓縮
return Math.tanh(score / 12);
當 rollout 結果的絕對分數較大時,tanh 接近 -1 或 1,相差 2~3 分的影響會被壓得很小。這使確定的孤獨數扣分容易被隨機 rollout 的差異掩蓋。
分析完畢。
現在要修改為:
移除 tanh,改用原始分差。(不要一會兒使用目前行動方視角、一會兒使用根節點玩家視角。回傳視角必須與 backpropagation 的正負號邏輯一致。移除 tanh 後,要重新調整 UCT exploration constant,因為 exploitation 從 [-1, 1] 變成了實際分數單位)
根節點候選提高至約 12~32 個,依時間調整。
強制納入所有會改變孤獨數狀態的候選。
rollout 隨機率從 17% 降低
回答要求:給出修改代碼的python腳本。- 附件
-
battlenumber245.html.7z- (47.88 KiB) 尚未被下载
https://ejsoon.vip/
金梭越空:極速暢遊天地
金梭越空:極速暢遊天地
Re: 又有一個新的弈棋創意:拚數棋!
battlenumber250.html
還沒加上「對方下一手連接模擬」及「關鍵接點封鎖」判定。
https://gpt.quanquan.space/share/T5vVjg ... 5rfYpFrXjE
還沒加上「對方下一手連接模擬」及「關鍵接點封鎖」判定。
https://gpt.quanquan.space/share/T5vVjg ... 5rfYpFrXjE
- 附件
-
battlenumber250.html.7z- (61.84 KiB) 已下载 1 次
https://ejsoon.vip/
金梭越空:極速暢遊天地
金梭越空:極速暢遊天地
Re: 又有一個新的弈棋創意:拚數棋!
o g3o c4m d3i d2r c3n b4t g1o d5l g4q f3l f2j f4q g2l d6s c5v e2i f1h e1r b5i b6o a5q b7s a4h
應該少放「單一」和「叉零」,注意盡量多的放「圍三」。
應該少放「單一」和「叉零」,注意盡量多的放「圍三」。
https://ejsoon.vip/
金梭越空:極速暢遊天地
金梭越空:極速暢遊天地
Re: 又有一個新的弈棋創意:拚數棋!
檢查代碼,是否這些調整有促使AI更多的下「單一」和「叉零」
sol
代码: 全选
經過前面的調整之後,現在AI都傾向於優先落下「單一」和「叉零」,這是不對的,因為「圍三」、「雙二」和「角二」在後面都很容易成為「孤獨棋」,而「圍三」如果隨意放的話會被對放封口,因此應優先放置不會被對方封口的「雙二」和「角二」,而當「圍三」能接上己方的棋子時,就應優先把握機會。
之前在沒有加入這些調整時(包括不會落下一個直接成為「孤獨棋」、不會落下一個棋子被對方下一回合封口等),MCTS雖然有時會落下一個孤獨棋,但是倒是能算出優先落下「雙二」或「角二」,然後用「圍三」接上。現在加入了這些調整後,反倒不會了。檢查代碼,是否這些調整有促使AI更多的下「單一」和「叉零」。
但是不是說「單一」和「叉零」以後就都不能下了,也要根據棋局的情況,比如當對方有一個棋子被封口後就會成為「孤獨棋」,則我方就有可能下「單一」封上,為了創造貼上「叉零」而不成為「孤獨棋」的條件,而會落下一個「叉零」,等等。這些都可以交給MCTS算。
回答要求:給出修改代碼的python腳本。代码: 全选
我會先定位候選硬性篩選、棋型編碼、孤獨棋/封口檢查及 MCTS 根節點選擇邏輯,確認是哪一層把「雙二、角二、圍三」過早排除,再據此製作可重複執行的補丁腳本。
有,現有 `battlenumber250.html` 中有三處會促使 AI 偏向「單一」和「叉零」:
1. `preferLowerNarrowClosureRisk()` 把封閉風險當成**硬性篩選**。
「單一」通常有三個開口,「叉零」分值為零,較容易取得風險 0;「雙二、角二」只剩兩個開口,較容易被整批排除。
2. `chooseMultiPlyRootEntries()` 找到少量已證明安全的棋後,最多只把約 6 手交給 MCTS。
其餘沒有被搜尋到的「雙二、角二、接己方棋的圍三」也會被提前刪除。
3. `applyAiAction()` 會在 MCTS 完成後,再用非 MCTS 的 `fallbackAction()` 替換結果。
fallback 主要看當前分數,同分時近似隨機,可能把 MCTS 算出的戰略棋換成「單一」或「叉零」。
以下補丁會:
- 保留「立即形成己方孤獨棋」的硬性防護;
- 保留「對方下一手能封口/立即得分」的硬性防護;
- 只排除已經被多層搜尋**明確證明**會強制失分的候選;
- 不再因為只搜尋到少量安全棋,就把其他未搜尋候選全部刪除;
- 把狹窄區域風險改成 MCTS 的**軟性先驗**,不再硬切候選;
- 軟性優先展開安全的「雙二、角二」;
- 「圍三」的唯一開口若能接上己方棋子,給予更高先驗;
- 不禁止「單一」和「叉零」;
- 立即封住對方孤獨棋等戰術,仍由原本的 MCTS 戰術機制優先處理;
- 最終執行前只攔截直接危險,不再因長期風險用 fallback 覆蓋 MCTS 結果。
將以下內容保存為 `patch_battlenumber251.py`:
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修正 battlenumber250.html 中根節點安全過濾過度偏向
「單一」及「叉零」的問題。
預設:
輸入:battlenumber250.html
輸出:battlenumber251.html
用法:
python patch_battlenumber251.py
或:
python patch_battlenumber251.py \
battlenumber250.html \
battlenumber251.html
"""
from __future__ import annotations
import argparse
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
PATCH_MARKER = "STRATEGIC_PIECE_BALANCE_MCTS_PATCH_V1"
def replace_once(
text: str,
old: str,
new: str,
label: str,
) -> str:
count = text.count(old)
if count != 1:
raise RuntimeError(
f"{label}:預期找到 1 個替換位置,"
f"實際找到 {count} 個。"
)
return text.replace(old, new, 1)
def replace_region(
text: str,
start_marker: str,
end_marker: str,
replacement: str,
label: str,
) -> str:
start_count = text.count(start_marker)
if start_count != 1:
raise RuntimeError(
f"{label}:起始標記預期找到 1 個,"
f"實際找到 {start_count} 個。"
)
start = text.find(start_marker)
if start < 0:
raise RuntimeError(
f"{label}:找不到起始標記。"
)
end = text.find(
end_marker,
start + len(start_marker),
)
if end < 0:
raise RuntimeError(
f"{label}:找不到結束標記。"
)
return (
text[:start]
+ replacement
+ text[end:]
)
def patch_html(source: str) -> str:
# ----------------------------------------------------------
# 1. 加入版本標記
# ----------------------------------------------------------
source = replace_once(
source,
""" <!-- NARROW_MIXED_CLOSURE_RISK_GUARD_V1 -->
<script id="mctsWorkerSource" type="text/plain">""",
""" <!-- NARROW_MIXED_CLOSURE_RISK_GUARD_V1 -->
<!-- STRATEGIC_PIECE_BALANCE_MCTS_PATCH_V1 -->
<script id="mctsWorkerSource" type="text/plain">""",
"加入戰略棋種平衡版本標記",
)
# ----------------------------------------------------------
# 2. Worker:加入棋種發展的軟性先驗
# ----------------------------------------------------------
worker_strategic_code = r"""
// STRATEGIC_PIECE_BALANCE_MCTS_PATCH_V1
//
// 只作為 MCTS 展開次序、rollout policy 及 prior 的軟性提示,
// 不會直接禁止任何棋種。
//
// 目的:
// 1. 安全的雙二、角二應較早進入搜尋;
// 2. 圍三的唯一開口若能接上己方棋子,應把握機會;
// 3. 單一和叉零仍可由 MCTS 在有戰術價值時選擇;
// 4. 狹窄混合區域風險改為先驗扣分,不再硬刪候選。
function placementConnectionProfile(
state,
action
) {
const empty = {
occupiedContacts: 0,
ownContacts: 0,
opponentContacts: 0,
ownOpenConnections: 0,
opponentOpenConnections: 0
};
if (
!state ||
!action ||
action.kind !== 'place' ||
!Number.isInteger(action.i) ||
action.i < 0 ||
action.i >= SIZE
) {
return empty;
}
const player = state.turn;
const orient =
action._o !== undefined
? action._o
: orientationId(
action.t,
action.r
);
const candidateEdges =
EDGE_MASK[orient];
const row =
Math.floor(action.i / N);
const col =
action.i % N;
let occupiedContacts = 0;
let ownContacts = 0;
let opponentContacts = 0;
let ownOpenConnections = 0;
let opponentOpenConnections = 0;
for (
let direction = 0;
direction < 4;
direction++
) {
const nextRow =
row + D4[direction][0];
const nextCol =
col + D4[direction][1];
if (
nextRow < 0 ||
nextRow >= N ||
nextCol < 0 ||
nextCol >= N
) {
continue;
}
const neighbour =
state.board[
nextRow * N + nextCol
];
if (!neighbour) {
continue;
}
occupiedContacts++;
const neighbourPlayer =
tilePlayer(neighbour);
if (neighbourPlayer === player) {
ownContacts++;
} else {
opponentContacts++;
}
const candidateHasEdge =
(
candidateEdges &
(1 << direction)
) !== 0;
const neighbourHasEdge =
codeHasEdge(
neighbour,
(direction + 2) & 3
);
// 兩邊都沒有實邊,才是真正的區域連接。
if (
!candidateHasEdge &&
!neighbourHasEdge
) {
if (neighbourPlayer === player) {
ownOpenConnections++;
} else {
opponentOpenConnections++;
}
}
}
return {
occupiedContacts,
ownContacts,
opponentContacts,
ownOpenConnections,
opponentOpenConnections
};
}
function strategicPieceDevelopmentHeuristic(
state,
action
) {
if (
!state ||
!action ||
action.kind !== 'place'
) {
return 0;
}
const orient =
action._o !== undefined
? action._o
: orientationId(
action.t,
action.r
);
const type =
ORIENT_TYPE[orient];
const profile =
placementConnectionProfile(
state,
action
);
const totalPlaced =
state.place0 + state.place1;
const progress =
clamp(
totalPlaced /
Math.max(1, SIZE - 1),
0,
1
);
let value = 0;
if (
type === TYPE_DOUBLE ||
type === TYPE_CORNER
) {
// 雙二、角二後期較難找到兩個安全開口,
// 因此在已通過直接安全檢查後,較早展開。
value +=
1.10 +
progress * 0.55;
value +=
profile.ownOpenConnections *
0.18;
// 與己方棋相鄰但沒有真正連通,仍只有極小提示,
// 避免單純貼邊被誤當成有效連接。
value +=
Math.max(
0,
profile.ownContacts -
profile.ownOpenConnections
) *
0.025;
} else if (
type === TYPE_SURROUND
) {
if (
profile.ownOpenConnections > 0
) {
// 圍三只有一個開口。
// 該開口能接入己方棋群時,是應把握的機會。
value +=
1.80 +
progress * 0.45 +
Math.min(
0.40,
(
profile.ownOpenConnections -
1
) *
0.20
);
} else if (
profile.opponentOpenConnections >
0
) {
// 接入對方棋群不一定是壞棋,
// 但不能取得「接己方棋」的優先獎勵。
value += 0.04;
}
} else if (
type === TYPE_SINGLE
) {
// 單一不被禁止。
// 真正接上己方棋時只給極小提示。
value +=
profile.ownOpenConnections *
0.06;
} else if (
type === TYPE_CROSS
) {
// 叉零仍可為提子路徑、解除孤獨數等目的落下。
// 不給負分,也不因數值為零而硬性降低優先級。
value +=
profile.ownContacts > 0
? 0.04
: 0;
}
return value;
}
function rootPlacementSoftRiskAdjustment(
action
) {
if (!action) {
return 0;
}
const maximum =
Number(
action
._rootClosureMaximumLoss
);
const weighted =
Number(
action
._rootClosureWeightedLoss
);
const safeMaximum =
Number.isFinite(maximum)
? Math.max(0, maximum)
: 0;
const safeWeighted =
Number.isFinite(weighted)
? Math.max(0, weighted)
: 0;
// 狹窄混合區域仍會影響展開次序,
// 但不再在 MCTS 開始前直接刪除整個候選。
return -(
safeMaximum * 0.65 +
safeWeighted * 0.30
);
}
"""
source = replace_once(
source,
""" function localPlacementHeuristic(state, action) {""",
worker_strategic_code
+ """ function localPlacementHeuristic(state, action) {""",
"插入 Worker 棋種發展軟性先驗",
)
# 把棋種發展及狹窄區域風險加入局部啟發值。
source = replace_once(
source,
""" return lonelySwing * 14 + contactCount * 0.08;""",
""" return (
lonelySwing * 14 +
contactCount * 0.08 +
strategicPieceDevelopmentHeuristic(
state,
action
) +
rootPlacementSoftRiskAdjustment(
action
)
);""",
"更新 Worker 局部落子啟發值",
)
# ----------------------------------------------------------
# 3. Worker:把封閉風險附加在根行動上,供軟性 prior 使用
# ----------------------------------------------------------
source = replace_once(
source,
""" action._rootLonelyCreated =
effect.rootLonelyCreated;
action
._rootNextReplyLonelyValue =""",
""" action._rootLonelyCreated =
effect.rootLonelyCreated;
action._rootClosureMaximumLoss =
Number(
effect
.latentClosureMaximumLoss
) || 0;
action._rootClosureWeightedLoss =
Number(
effect
.latentClosureWeightedLoss
) || 0;
action
._rootNextReplyLonelyValue =""",
"為 alpha-beta 根行動附加軟性封閉風險",
)
source = replace_once(
source,
""" action._rootLonelyCreated =
effect.rootLonelyCreated;
entries.push({""",
""" action._rootLonelyCreated =
effect.rootLonelyCreated;
action._rootClosureMaximumLoss =
Number(
effect
.latentClosureMaximumLoss
) || 0;
action._rootClosureWeightedLoss =
Number(
effect
.latentClosureWeightedLoss
) || 0;
entries.push({""",
"為 MCTS 根行動附加軟性封閉風險",
)
# ----------------------------------------------------------
# 4. Worker:多層搜尋只刪除已證明 forced 的候選
#
# 舊版一旦找到少量 provenSafe,就只回傳那幾手,
# 未被搜尋的候選也全部消失,令 MCTS 無法比較棋種發展。
# ----------------------------------------------------------
worker_choose_function = r""" function chooseMultiPlyRootEntries(
state,
entries,
rootPlayer,
context
) {
if (
!entries.length ||
!context
) {
return entries;
}
const ordered =
entries.map(
entry => ({
entry,
order:
rootEntryTacticalOrder(
state,
entry
)
})
);
ordered.sort(
(first, second) =>
second.order -
first.order
);
const candidates =
ordered
.slice(
0,
Math.min(
context.candidateLimit,
ordered.length
)
)
.map(
item => item.entry
);
for (
const entry of candidates
) {
const result =
forcedFutureScoringThreat(
state,
entry.action,
rootPlayer,
context
);
entry.effect
.futureThreatStatus =
result.status;
entry.effect
.forcedFutureScoreLoss =
result.loss;
entry.effect
.forcedFutureDistance =
result.distance;
entry.effect
.forcedFutureLine =
result.line;
}
// 關鍵修正:
//
// safe:
// 已證明安全,保留。
//
// unknown:
// 尚未搜完,不可視為失敗,保留交給 MCTS。
//
// 未進入 tactical candidate limit:
// 同樣沒有失敗證明,保留交給 MCTS。
//
// forced:
// 只有這一類候選才會在存在其他選擇時被排除。
const nonForced =
entries.filter(
entry =>
entry.effect
.futureThreatStatus !==
'forced'
);
if (nonForced.length) {
return nonForced;
}
// 若所有候選都已被明確證明會失分,
// 才使用原本的最小損失、最晚失分策略。
let remaining =
entries.slice();
let minimumLoss =
Infinity;
for (
const entry of remaining
) {
minimumLoss = Math.min(
minimumLoss,
Number(
entry.effect
.forcedFutureScoreLoss
) || 0
);
}
remaining =
remaining.filter(
entry =>
Math.abs(
(
Number(
entry.effect
.forcedFutureScoreLoss
) || 0
) -
minimumLoss
) <=
TACTICAL_SCORE_EPSILON
);
let maximumDistance = 0;
for (
const entry of remaining
) {
maximumDistance = Math.max(
maximumDistance,
Number(
entry.effect
.forcedFutureDistance
) || 0
);
}
remaining =
remaining.filter(
entry =>
(
Number(
entry.effect
.forcedFutureDistance
) || 0
) >=
maximumDistance
);
let bestImmediate =
-Infinity;
for (
const entry of remaining
) {
bestImmediate = Math.max(
bestImmediate,
entry.effect.immediateDelta
);
}
return remaining.filter(
entry =>
entry.effect.immediateDelta >=
bestImmediate -
TACTICAL_SCORE_EPSILON
);
}
"""
source = replace_region(
source,
""" function chooseMultiPlyRootEntries(
""",
""" // ROOT_LONELY_SAFETY_POLICY_V2""",
worker_choose_function,
"更新 Worker 多層根候選保留策略",
)
# ----------------------------------------------------------
# 5. Worker:狹窄區域風險不再硬性只保留最低值
# ----------------------------------------------------------
source = replace_once(
source,
""" const closurePreferredEntries =
preferLowerNarrowClosureRisk(
onePlySafeEntries
);
return chooseMultiPlyRootEntries(
state,
closurePreferredEntries,
rootPlayer,
tacticalContext
);""",
""" // 狹窄區域風險已透過
// rootPlacementSoftRiskAdjustment 影響 MCTS prior。
//
// 不再硬性只保留風險絕對最低的一組,避免單一和叉零
// 因較容易取得零風險而壟斷根候選。
return chooseMultiPlyRootEntries(
state,
onePlySafeEntries,
rootPlayer,
tacticalContext
);""",
"取消 Worker 狹窄區域風險硬篩選",
)
# ----------------------------------------------------------
# 6. 主執行緒:加入相同的棋種發展啟發值
# ----------------------------------------------------------
main_strategic_code = r"""
// STRATEGIC_PIECE_BALANCE_MCTS_PATCH_V1_MAIN
//
// 主執行緒只把這個值用於 fallback 的戰術搜尋次序。
// 它不是合法性規則,也不會禁止單一或叉零。
function strategicPieceDevelopmentHeuristicMain(
gameState,
action
) {
if (
!gameState ||
!action ||
action.kind !== 'place' ||
!Number.isInteger(action.i) ||
action.i < 0 ||
action.i >= SIZE
) {
return 0;
}
const player =
gameState.turn;
const tile = {
player,
type: action.t,
rot: action.r
};
const row =
Math.floor(action.i / N);
const col =
action.i % N;
let ownContacts = 0;
let ownOpenConnections = 0;
let opponentOpenConnections = 0;
for (
let direction = 0;
direction <
DIRECTIONS.length;
direction++
) {
const nextRow =
row +
DIRECTIONS[direction][0];
const nextCol =
col +
DIRECTIONS[direction][1];
if (
nextRow < 0 ||
nextRow >= N ||
nextCol < 0 ||
nextCol >= N
) {
continue;
}
const neighbour =
gameState.board[
nextRow * N + nextCol
];
if (!neighbour) {
continue;
}
if (
neighbour.player === player
) {
ownContacts++;
}
const connected =
!tileHasEdge(
tile,
direction
) &&
!tileHasEdge(
neighbour,
(direction + 2) & 3
);
if (!connected) {
continue;
}
if (
neighbour.player === player
) {
ownOpenConnections++;
} else {
opponentOpenConnections++;
}
}
const totalPlaced =
gameState.placementCount[0] +
gameState.placementCount[1];
const progress =
Math.max(
0,
Math.min(
1,
totalPlaced /
Math.max(1, SIZE - 1)
)
);
if (
action.t === TYPE_DOUBLE ||
action.t === TYPE_CORNER
) {
return (
1.10 +
progress * 0.55 +
ownOpenConnections * 0.18 +
Math.max(
0,
ownContacts -
ownOpenConnections
) *
0.025
);
}
if (
action.t === TYPE_SURROUND
) {
if (ownOpenConnections > 0) {
return (
1.80 +
progress * 0.45 +
Math.min(
0.40,
(
ownOpenConnections -
1
) *
0.20
)
);
}
return opponentOpenConnections > 0
? 0.04
: 0;
}
if (
action.t === TYPE_SINGLE
) {
return (
ownOpenConnections * 0.06
);
}
if (
action.t === TYPE_CROSS
) {
return ownContacts > 0
? 0.04
: 0;
}
return 0;
}
"""
source = replace_once(
source,
""" function tacticalActionOrderMain(
gameState,
action
) {""",
main_strategic_code
+ """ function tacticalActionOrderMain(
gameState,
action
) {""",
"插入主執行緒棋種發展軟性先驗",
)
source = replace_once(
source,
""" return (
neighbours * 20 +
TRIANGLES[action.t] *
0.15 +
Math.random() * 0.01
);""",
""" return (
neighbours * 20 +
strategicPieceDevelopmentHeuristicMain(
gameState,
action
) +
TRIANGLES[action.t] *
0.03 +
Math.random() * 0.01
);""",
"更新主執行緒戰術行動排序",
)
# ----------------------------------------------------------
# 7. 主執行緒:多層搜尋保留 safe、unknown 及未搜尋候選
# ----------------------------------------------------------
main_choose_function = r""" function chooseMultiPlyRootEntriesMain(
gameState,
entries,
context
) {
if (
!entries.length ||
!context
) {
return entries;
}
const ordered =
entries.map(
entry => ({
entry,
order:
entry.immediateDelta *
16 +
tacticalActionOrderMain(
gameState,
entry.action
) -
(
Number(
entry
.nextReplyScoringReplies
) || 0
) *
0.15 +
Math.random() * 0.2
})
);
ordered.sort(
(first, second) =>
second.order -
first.order
);
const candidates =
ordered
.slice(
0,
Math.min(
context.candidateLimit,
ordered.length
)
)
.map(
item => item.entry
);
for (
const entry of candidates
) {
const result =
forcedFutureScoringThreatMain(
gameState,
entry.action,
{
context
}
);
entry.futureThreatStatus =
result.status;
entry.forcedFutureScoreLoss =
result.loss;
entry.forcedFutureDistance =
result.distance;
entry.forcedFutureLine =
result.line;
}
// 只排除已經被明確證明為 forced 的行動。
//
// unknown 及未進入 tactical candidate limit 的行動
// 必須保留,不能因搜尋時間不足而被當成壞棋。
const nonForced =
entries.filter(
entry =>
entry.futureThreatStatus !==
'forced'
);
if (nonForced.length) {
return nonForced;
}
let remaining =
entries.slice();
let minimumLoss =
Infinity;
for (
const entry of remaining
) {
minimumLoss = Math.min(
minimumLoss,
Number(
entry
.forcedFutureScoreLoss
) || 0
);
}
remaining =
remaining.filter(
entry =>
Math.abs(
(
Number(
entry
.forcedFutureScoreLoss
) || 0
) -
minimumLoss
) <=
TACTICAL_MAIN_EPSILON
);
let maximumDistance = 0;
for (
const entry of remaining
) {
maximumDistance = Math.max(
maximumDistance,
Number(
entry
.forcedFutureDistance
) || 0
);
}
remaining =
remaining.filter(
entry =>
(
Number(
entry
.forcedFutureDistance
) || 0
) >=
maximumDistance
);
let bestImmediate =
-Infinity;
for (
const entry of remaining
) {
bestImmediate = Math.max(
bestImmediate,
entry.immediateDelta
);
}
return remaining.filter(
entry =>
entry.immediateDelta >=
bestImmediate -
TACTICAL_MAIN_EPSILON
);
}
"""
source = replace_region(
source,
""" function chooseMultiPlyRootEntriesMain(
""",
""" // 主執行緒版根節點安全過濾。""",
main_choose_function,
"更新主執行緒多層根候選保留策略",
)
# ----------------------------------------------------------
# 8. 主執行緒 fallback:取消狹窄區域風險硬篩選
# ----------------------------------------------------------
source = replace_once(
source,
""" const closurePreferred =
preferLowerNarrowClosureRiskMain(
onePlySafe
);
const selected =
chooseMultiPlyRootEntriesMain(
gameState,
closurePreferred,
tacticalContext
);""",
""" // fallback 同樣不再因狹窄區域風險,
// 硬性刪除雙二、角二及接己方棋的圍三。
const selected =
chooseMultiPlyRootEntriesMain(
gameState,
onePlySafe,
tacticalContext
);""",
"取消主執行緒狹窄區域風險硬篩選",
)
# ----------------------------------------------------------
# 9. 最終行動檢查:
#
# 只處理可確定的直接危險。
# 不再因狹窄區域或多層靜態判定,以 fallback 覆蓋 MCTS。
# ----------------------------------------------------------
final_safety_block = r""" // STRATEGIC_PIECE_BALANCE_MCTS_PATCH_V1_FINAL_CHECK
//
// MCTS 完成後只攔截三種可確定的直接危險:
//
// 1. 新棋立即成為己方孤獨數;
// 2. 對方下一手能把新棋封成孤獨數;
// 3. 對方下一手能令目前實際區域分差下降。
//
// 狹窄區域及多層未來棋形已交回 MCTS 比較,
// 不再用只看當前分數的 fallback 覆蓋搜尋結果。
if (
action &&
action.kind === 'place'
) {
const createsImmediateLonely =
placementCreatesOwnLonelyNumberMain(
state,
action
);
const replyThreat =
nextReplyLonelyThreatMain(
state,
action
);
const replyScoreThreat =
nextReplyScoringThreatMain(
state,
action,
false
);
const hasDirectDanger =
createsImmediateLonely ||
replyThreat.value > 0 ||
replyScoreThreat.loss >
TACTICAL_MAIN_EPSILON;
if (hasDirectDanger) {
const saferAction =
fallbackAction(action);
if (
saferAction &&
actionIsLegal(saferAction)
) {
const saferImmediateLonely =
placementCreatesOwnLonelyNumberMain(
state,
saferAction
);
const saferReplyThreat =
nextReplyLonelyThreatMain(
state,
saferAction
);
const saferReplyScoreThreat =
nextReplyScoringThreatMain(
state,
saferAction,
false
);
// 依序比較:
// 1. 是否立即製造己方孤獨數;
// 2. 對方下一手最大得分;
// 3. 新棋被封成孤獨數的分值;
// 4. 對方得分回覆數;
// 5. 對方封新棋回覆數。
//
// 只有 fallback 確實更安全時才替換,
// 同級時保留 MCTS 原本選出的行動。
const originalDanger = [
createsImmediateLonely
? 1
: 0,
Number(
replyScoreThreat.loss
) || 0,
Number(
replyThreat.value
) || 0,
Number(
replyScoreThreat.replies
) || 0,
Number(
replyThreat.replies
) || 0
];
const candidateDanger = [
saferImmediateLonely
? 1
: 0,
Number(
saferReplyScoreThreat.loss
) || 0,
Number(
saferReplyThreat.value
) || 0,
Number(
saferReplyScoreThreat.replies
) || 0,
Number(
saferReplyThreat.replies
) || 0
];
let strictlySafer = false;
for (
let index = 0;
index <
originalDanger.length;
index++
) {
if (
candidateDanger[index] <
originalDanger[index] -
TACTICAL_MAIN_EPSILON
) {
strictlySafer = true;
break;
}
if (
candidateDanger[index] >
originalDanger[index] +
TACTICAL_MAIN_EPSILON
) {
break;
}
}
if (strictlySafer) {
action = saferAction;
}
}
}
}
"""
source = replace_region(
source,
""" // ONE_PLY_LONELY_REPLY_GUARD_V1_FINAL_CHECK""",
""" if (action.kind === 'place') {
commitPlacement(action, 2100);""",
final_safety_block,
"更新 AI 最終行動安全檢查",
)
# ----------------------------------------------------------
# 10. 靜態驗證
# ----------------------------------------------------------
required_fragments = [
"<!-- STRATEGIC_PIECE_BALANCE_MCTS_PATCH_V1 -->",
"function placementConnectionProfile(",
"function strategicPieceDevelopmentHeuristic(",
"function rootPlacementSoftRiskAdjustment(",
"strategicPieceDevelopmentHeuristic(\n state,\n action",
"action._rootClosureMaximumLoss =",
"const nonForced =\n entries.filter(",
"function strategicPieceDevelopmentHeuristicMain(",
"const nonForced =\n entries.filter(",
"STRATEGIC_PIECE_BALANCE_MCTS_PATCH_V1_FINAL_CHECK",
"const originalDanger = [",
"const candidateDanger = [",
"if (strictlySafer) {",
]
missing = [
fragment
for fragment in required_fragments
if fragment not in source
]
if missing:
raise RuntimeError(
"補丁完成後驗證失敗,缺少以下內容:\n - "
+ "\n - ".join(missing)
)
unique_functions = [
"function placementConnectionProfile(",
"function strategicPieceDevelopmentHeuristic(",
"function rootPlacementSoftRiskAdjustment(",
"function strategicPieceDevelopmentHeuristicMain(",
"function chooseMultiPlyRootEntries(",
"function chooseMultiPlyRootEntriesMain(",
]
duplicates = [
fragment
for fragment in unique_functions
if source.count(fragment) != 1
]
if duplicates:
raise RuntimeError(
"補丁完成後發現函式重複或缺失:\n - "
+ "\n - ".join(duplicates)
)
forbidden_fragments = [
"const closurePreferredEntries =",
"const closurePreferred =",
"const hasNarrowClosureDanger =",
"const futureScoreThreat =\n"
" forcedFutureScoringThreatMain(",
]
remaining_old_logic = [
fragment
for fragment in forbidden_fragments
if fragment in source
]
if remaining_old_logic:
raise RuntimeError(
"補丁完成後仍殘留會覆蓋 MCTS 的舊邏輯:\n - "
+ "\n - ".join(remaining_old_logic)
)
return source
def optional_node_syntax_check(
html: str,
) -> str:
"""
如果系統有 Node.js,就檢查 Worker 與主程式的 JavaScript 語法。
沒有 Node.js 時不視為錯誤。
"""
node = shutil.which("node")
if not node:
return "未找到 Node.js,已略過 JavaScript 語法檢查。"
worker_match = re.search(
r'<script\s+id="mctsWorkerSource"'
r'\s+type="text/plain">\s*(.*?)'
r'</script>',
html,
flags=re.DOTALL,
)
if not worker_match:
raise RuntimeError(
"找不到 Worker JavaScript,無法執行語法檢查。"
)
script_matches = re.findall(
r"<script(?:\s[^>]*)?>(.*?)</script>",
html,
flags=re.DOTALL,
)
if not script_matches:
raise RuntimeError(
"找不到主執行緒 JavaScript。"
)
worker_source = worker_match.group(1)
main_source = script_matches[-1]
with tempfile.TemporaryDirectory(
prefix="battle_number_js_check_"
) as temp_directory:
temp_path = Path(temp_directory)
worker_path = (
temp_path / "worker.js"
)
main_path = (
temp_path / "main.js"
)
worker_path.write_text(
worker_source,
encoding="utf-8",
)
main_path.write_text(
main_source,
encoding="utf-8",
)
for label, path in [
("Worker", worker_path),
("主執行緒", main_path),
]:
result = subprocess.run(
[node, "--check", str(path)],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
if result.returncode != 0:
details = (
result.stderr.strip()
or result.stdout.strip()
or "未知語法錯誤"
)
raise RuntimeError(
f"{label} JavaScript 語法檢查失敗:\n"
f"{details}"
)
return "Worker 與主執行緒 JavaScript 語法檢查通過。"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"修正 Battle Number 250 的棋種偏向,"
"恢復 MCTS 對雙二、角二及接己方圍三的比較能力。"
)
)
parser.add_argument(
"input",
nargs="?",
default="battlenumber250.html",
help="輸入 HTML,預設為 battlenumber250.html",
)
parser.add_argument(
"output",
nargs="?",
default="battlenumber251.html",
help="輸出 HTML,預設為 battlenumber251.html",
)
parser.add_argument(
"--skip-node-check",
action="store_true",
help="不執行可選的 Node.js 語法檢查",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
input_path = Path(args.input)
output_path = Path(args.output)
if not input_path.is_file():
print(
f"錯誤:找不到輸入檔案:{input_path}",
file=sys.stderr,
)
return 1
try:
source = input_path.read_text(
encoding="utf-8"
)
except Exception as error:
print(
f"錯誤:無法讀取輸入檔案:{error}",
file=sys.stderr,
)
return 1
if PATCH_MARKER in source:
try:
output_path.parent.mkdir(
parents=True,
exist_ok=True,
)
output_path.write_text(
source,
encoding="utf-8",
)
except Exception as error:
print(
f"錯誤:無法寫入輸出檔案:{error}",
file=sys.stderr,
)
return 1
print(
f"輸入檔案已包含 {PATCH_MARKER};"
f"未重複修改,已輸出至:{output_path}"
)
return 0
try:
patched = patch_html(source)
if args.skip_node_check:
syntax_message = (
"已依參數略過 JavaScript 語法檢查。"
)
else:
syntax_message = (
optional_node_syntax_check(
patched
)
)
except Exception as error:
print(
f"補丁失敗:{error}",
file=sys.stderr,
)
return 1
try:
output_path.parent.mkdir(
parents=True,
exist_ok=True,
)
output_path.write_text(
patched,
encoding="utf-8",
)
except Exception as error:
print(
f"錯誤:無法寫入輸出檔案:{error}",
file=sys.stderr,
)
return 1
print("補丁完成。")
print(f"輸入:{input_path}")
print(f"輸出:{output_path}")
print(syntax_message)
print()
print("主要修正:")
print(
" 1. 狹窄混合區域風險由硬篩選改為軟性 MCTS prior"
)
print(
" 2. 未完成多層搜尋的候選不再被當成失敗"
)
print(
" 3. 只排除已明確證明為 forced 的多層失分棋"
)
print(
" 4. 安全的雙二、角二會較早進入搜尋"
)
print(
" 5. 圍三的唯一開口接上己方棋時獲得較高先驗"
)
print(
" 6. 單一和叉零沒有被禁止或固定扣分"
)
print(
" 7. 最終檢查不再因長期風險用 fallback 覆蓋 MCTS"
)
print(
" 8. 立即己方孤獨棋及對方下一手封口防護仍然保留"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
```
執行:
```bash
python patch_battlenumber251.py battlenumber250.html battlenumber251.html
```
如果電腦沒有安裝 Node.js,腳本仍可正常產生輸出,只會顯示略過 JavaScript 語法檢查。也可以主動略過:
```bash
python patch_battlenumber251.py \
battlenumber250.html \
battlenumber251.html \
--skip-node-check
```
這個版本的決策關係是:
```text
立即形成己方孤獨棋
或對方下一手可直接封口/得分
↓
存在安全棋時硬性排除
已明確證明的多層強制失分
↓
存在其他候選時排除
雙二、角二的發展價值
圍三接上己方棋的機會
狹窄混合區域的長期風險
單一封住對方棋的戰術
叉零解除孤獨棋或建立提子路徑
↓
作為 MCTS 的軟性先驗及完整模擬內容
```
因此「雙二、角二、接己方棋的圍三」會較早獲得搜尋資源,但「單一、叉零」仍然可以在封閉對方孤獨棋、保護己方棋或建立提子路徑時由 MCTS 選出。- 附件
-
battlenumber251.html.7z- (63.46 KiB) 尚未被下载
https://ejsoon.vip/
金梭越空:極速暢遊天地
金梭越空:極速暢遊天地
-
- 相似主题
- 回复总数
- 阅读次数
- 最新帖子