用洛書幻方對抗人類玩家的井字棋程式
我們在此前的文章中,給出了一個練習題:
程式設計實現一個井字棋遊戲是傳統人工智慧中的經典問題,而計算機可以輕鬆算出三個數字的和並判斷其是否等於15。請利用這個同構編寫一個簡化的井字棋程式,並做到不被人類玩家擊敗。
現在我們給出這道題目的參考答案。我們的思路是使用《洛書》幻方來同構井字棋遊戲,用集合X, O來儲存兩個玩家所佔領的格子。對於春節廟會中描述的對局,開始時X = [],O = [],結束時X = [2, 5, 7, 1 ],Y = [ 4, 3, 8, 6 ]。為此我們需要先寫一個程式判斷一個集合中是否有3個元素相加等於15,從而知道某個玩家獲勝與否。
有兩種思路解決這個問題,第一種是列舉洛書幻方中的所有行、列、對角線,共8個三元組:[[4, 9, 2], [3, 5, 7], ..., [2, 5, 8]]。然後看是否某個三元組包含在玩家佔領的格子集合中。第二種比較有趣,假設玩家佔領了格子 X = [x_1, x_2, ..., x_n]。這些格子按照洛書幻方中的元素升序排列。我們可以先選出x_1,然後用左右兩個指標l, r分別指向下一個元素和最後一個元素,然後把這3個數加起來s = x_1 + x_l + x_r,如果等於15,說明玩家連成一條直線已經獲勝了。如果小於15,由於元素是升序排列的,我們可以把左側指標l加一,然後再次嘗試;如果大於15,我們把右側指標r減一,然後再次嘗試。如果左右指標相遇,說明固定x_1沒有找到相加等於15的三元組,我們選出x_2再次進行這樣的檢查。這樣最差情況總共進行(n - 2)+ (n - 3) + ... + 1次檢查就得知玩家是否獲勝了。
def win(s):
n = len(s)
if n < 3:
return False
s = sorted(s)
for i in range(n - 2):
l = i + 1
r = n - 1
while l < r:
total = s[i] + s[l] + s[r]
if total == 15:
return True
elif total < 15:
l = l + 1
else:
r = r - 1
return False
這樣給定X和O,就能判斷局面。如果X和O佔滿全部9個格子,還未分出勝負,則表示平局。接下來我們用傳統人工智慧中的min-max方法來實現井字棋,我們給每個局面一個評分,一方試圖讓評分最大化,稱為正方;另一方試圖讓評分最小化,稱為反方,從而實現對抗。平局的話評分為0,如果某個局面讓正方獲勝,我們設定評分為10,反方獲勝評分為-10。這個分數值完全是隨意設定的,不影響結果。
WIN = 10
INF = 1000
# Luo Shu magic square
MAGIC_SQUARE = [4, 9, 2,
3, 5, 7,
8, 1, 6]
def eval(x, o):
if win(x):
return WIN
if win(o):
return -WIN
return 0
def finished(x, o):
return len(x) + len(o) == 9
對於任何一個對局,我們都讓計算機不斷向前探索,直到找到輸贏或者平局的確定局面才停下來。探索的方法是窮盡當前所有能佔領的格子,然後轉換身份,考慮自己是對方時怎樣對抗。對於所有候選方案,如果是正方,就選擇評分高的方案,如果是反方,就選擇評分低的方案。
def findbest(x, o, maximize):
best = -INF if maximize else INF
move = 0
for i in MAGIC_SQUARE:
if (i not in x) and (i not in o):
if maximize:
val = minmax([i] + x, o, 0, not maximize)
if val > best:
best = val
move = i
else:
val = minmax(x, [i] + o, 0, not maximize)
if val < best:
best = val
move = i
return move
min-max是一個遞迴搜尋的過程,為了儘快獲勝,我們在評分上加上對向前探索步數的考慮。如果是正方,就從評分中減去遞迴深度,而對於反方,則加上遞迴深度。
def minmax(x, o, depth, maximize):
score = eval(x, o)
if score == WIN:
return score - depth
if score == -WIN:
return score + depth
if finished(x, o):
return 0 # draw
best = -INF if maximize else INF
for i in MAGIC_SQUARE:
if (i not in x) and (i not in o):
if maximize:
best = max(best, minmax([i] + x, o, depth + 1, not maximize))
else:
best = min(best, minmax(x, [i] + o, depth + 1, not maximize))
return best
現在我們就做出一個無法被人類擊敗的程式了,我們的程式在背後用洛書幻方對抗人類玩家:
def board(x, o):
for r in range(3):
print("-------")
for c in range(3):
p = MAGIC_SQUARE[r*3 + c]
if p in x:
print("|X", end="")
elif p in o:
print("|O", end="")
else:
print("| ", end="")
print("|")
print("-------")
def play():
x = []
o = []
while not (win(x) or win(o) or finished(x, o)):
board(x, o)
while True:
i = int(input("[1..9]==>"))
if i not in MAGIC_SQUARE or MAGIC_SQUARE[i-1] in x or MAGIC_SQUARE[i-1] in o:
print("invalid move")
else:
x = [MAGIC_SQUARE[i-1]] + x
break
o = [findbest(x, o, False)] + o
board(x, o)
這是《同構——程式設計中的數學》一書前言中的練習題
相關文章
- Python程式碼 | 井字棋Python
- Python:用海龜實現井字棋Python
- 採用α-β演算法實現井字棋遊戲演算法遊戲
- 用C語言編寫小遊戲——“井字棋”C語言遊戲
- 洛谷B3940 [GESP樣題 四級] 填幻方
- 強化學習實戰 | 自定義Gym環境之井字棋強化學習
- 強化學習實戰 | 表格型Q-Learning玩井字棋(一)強化學習
- 強化學習實戰 | 表格型Q-Learning玩井字棋(二)強化學習
- 基於落點打分的井字棋智慧下棋演算法(C語言實現)演算法C語言
- 二營長,快掏個CSS出來給我畫個井字棋遊戲CSS遊戲
- 對抗類遊戲應該追求“絕對的平衡”麼?遊戲
- CTF主辦方指南之對抗攪屎棍
- 強化學習實戰 | 表格型Q-Learning玩井字棋(四)遊戲時間強化學習遊戲
- 用Unity做半個2D戰棋小遊戲(三):新增對戰雙方Unity遊戲
- 醫療領域:合成資料、生成對抗網路、數字孿生的應用
- 4階最大幻和98765343210的十全數完美幻方
- Minimax 和 Alpha-beta 剪枝演算法簡介,及以此實現的井字棋遊戲(Tic-tac-toe)演算法遊戲
- 萬字綜述之生成對抗網路(GAN)
- 3階餘數幻方的普及教材
- 4階餘數幻方的普及教材
- 恰逢35歲生日 俄羅斯方塊的一切都變得井井有條
- 關於程式碼簽名證書種類的介紹及對比
- 技術人必看的各類工具書籍
- 多人對抗類遊戲的10個空間佈局原則遊戲
- “道有道”的對抗之路
- 從對抗出發,以變制變,看動態素材在驗證碼攻防對抗中的應用
- 用難測的期待去對抗既定的焦慮和迷茫
- 【組合數學】幻方、拉丁方、塗色問題
- 死亡擱淺:一封針對雲玩家的挑戰書
- AI智慧對人類未來的利弊AI
- NeurIPS 2024 | 解鎖大模型知識記憶編輯的新路徑,浙大用「WISE」對抗幻覺大模型
- 卷積生成對抗網路(DCGAN)---生成手寫數字卷積
- 實戰生成對抗網路[2]:生成手寫數字
- ICML 2024 Spotlight | 在解碼中重新對齊,讓語言模型更少幻覺、更符合人類偏好模型
- 既能欺騙機器,也能迷惑人類!Goodfellow等人提出新一代對抗樣本Go
- Java工具類 NumberUtils 對整型數字的引用Java
- 用Unity做半個2D戰棋小遊戲(四):加入玩家控制Unity遊戲
- 如何應用 matrix3d 對映變幻3D