contributed by <I-Ying-Tsai
>
commit e066b23 c48480c
實作報告:將棋盤繪製搬移至 user space 並實現 kernel/user 通訊
不要濫用 ---
標示
原設計下,棋盤畫面繪製與顯示由 kernel module 處理,user space 僅為被動觀察者。這會導致:
目標:
注意用語
.read
,無 .write
handler。kxo_write
:
kxo_read
:
static DEFINE_MUTEX(kxo_lock);
,所有 read/write 流程都在 mutex 保護下,避免多執行緒 race condition。xo_common.h
:
dmesg 如下:
未來可能的修改目標:
1 : 因為目前把棋盤資訊寫入 kernel 的通訊量仍然可以降低(現在寫進去的是一個陣列以及當前玩家),之後會嘗試利用 hash 來減少寫入成本。
2 : 可能可以利用 queue 來讓 kernel 裡的 ai 再不是自己的回合時往後多算幾步並存進去 queue ,下一步可以直接查表。
新的commit c48480c
實現了繪製棋盤、處理鍵盤事件以及向 kernel space 拿 ai 運算的下一步
注意書寫規範:中英文間用一個半形空白字元區隔
tinync
的 coroutine 實作方式。使用巨集與 goto
配合結構體儲存程式跳轉點與執行狀態,模擬 coroutine 行為,而非依賴 POSIX thread。
其關鍵實作方式為:
struct cr
儲存 coroutine 狀態與跳轉點。cr_begin
, cr_end
, cr_wait
, cr_exit
等巨集實現狀態控制。select()
搭配非阻塞 I/O 配合 coroutine 排程。原本的 xo-user.c
採用 select()
機制等待 stdin
或 /dev/kxo
的輸入,並使用同步方式處理鍵盤與棋盤更新事件。為了支援 coroutine 式排程,我將其改寫為多 coroutine 架構,分工如下:
Ctrl+P
切換畫面刷新(read_attr)Ctrl+Q
終止程式並通知 kernel 模組(write 1
至 sysfs)。/sys/class/kxo/kxo/kxo_state
。should_redraw
顯示雙棋盤畫面。read_attr == true
時輸出,配合鍵盤切換。ai1_loop_0
, ai2_loop_0
操作棋盤 0(Game 1)ai1_loop_1
, ai2_loop_1
操作棋盤 1(Game 2)ai1_loop_*
: 執行 mcts()
決策ai2_loop_*
: 執行 negamax_predict()
決策finished[i] = true
non-blocking
設定 stdin
讓 coroutine 不會卡住。raw_mode_enable()
啟用原始鍵盤輸入模式以接收 Ctrl+P/Q。commit 6555a92
本模組移植了一個基於 xoroshiro128+ 的輕量級隨機數產生器,主要目的是提供高效能的亂數來源給 MCTS 演算法使用。該實作包含以下幾個核心函式:
xoro_next
: 根據 xoroshiro128+ 的設計,從目前狀態產出下一個 64-bit 的隨機數。xoro_jump
: 執行一次跳躍操作,可用於平行亂數序列產生,使不同序列互不重複。xoro_init
: 使用 clock_gettime
中的 tv_sec
與 tv_nsec
初始化亂數種子,確保每次執行產生不同亂數。程式中使用的 state_array
結構儲存了 128-bit 的內部狀態,以兩個 64-bit unsigned 整數陣列成員組成。
主要驗證 xoro_next
輸出的值是否採用接近於均勻分佈,是否可用於開發通用性模擬。
& 0xFF
檢驗每 8-bit 的表現xoroshiro128+
wyhash
seed 生成參考commit 80d53d1
zobrist_init()
:wyhash
式樣的無狀態 PRNG 實作產生亂數。在 user-space
中用 clock_gettime()
作為種子;在 kernel-space
中使用 ktime_get()
。
zobrist_get(uint64_t key)
:
從雜湊表中查找指定 key
的節點(zobrist_entry_t
),若存在則回傳該節點;否則回傳 NULL
。
zobrist_put(uint64_t key, int score, int move)
:
新增一筆包含 key
、score
與 move
的紀錄至雜湊表中。
zobrist_clear()
:
清空整個雜湊表,包括所有節點,釋放記憶體,並重設 head
。
char table[]
把盤面轉成雜湊值hash_value
被視為目前盤面的唯一識別碼我們說:「n 個 hash key 全部落在不同的桶」的機率是:
取對數:
因為 ,我們可以用泰勒展開:
保留第一項近似就好:
所以總和變成:
兩邊取 exp:
commit 96b92ec
此模組負責管理井字棋(Tic-Tac-Toe)的棋盤邏輯與勝負判斷,支援彈性定義棋盤大小 (UTIL_BOARD_SIZE
) 及勝利條件 (UTIL_GOAL
)。
#define UTIL_BOARD_SIZE 4
:棋盤邊長。#define UTIL_GOAL 3
:連續幾子算勝。#define UTIL_N_GRIDS
:棋盤總格數(UTIL_BOARD_SIZE²
)。typedef game_table_t
:以 1 維陣列方式儲存棋盤格子。typedef util_line_t
:定義一條可供掃描的線段方向與邊界條件。char check_win(const char *table)
UTIL_GOAL
個連續相同的棋子。'D'
,代表和局);否則回傳 ' '
(比賽尚未結束)。util_fixed_point_t calculate_win_value(char win, char player)
win == player
:回傳 1.0(1 << UTIL_FIXED_SCALE_BITS
)。1 << (UTIL_FIXED_SCALE_BITS - 1)
)。int *available_moves(const char *table)
-1
結尾)。free()
返回的指標。commit 0889166
此模組移植了基於定點數 (fixed_point_t
) 的蒙地卡羅樹搜尋(MCTS),提供 mcts()
作為 AI 的決策入口。MCTS 結合 UCT (Upper Confidence Bound for Trees) 演算法來平衡探索與利用,並能在不支援浮點數運算的環境下運作。
mcts()
函式uct_score()
)。expand()
建立子節點。simulate()
模擬遊戲至結束,依據結果 backpropagate()
分數回傳至父節點。uct_score()
函式Q/n + C * sqrt(ln(N)/n)
計算每個子節點的探索價值(其中 C 是探索係數),但實作在 fixed-point 上為:參考了2025 年 Linux 核心設計課程作業 —— kxo (A)
此函式計算 Q23.8 固定點格式的自然對數 ln(y)
。考慮到 log
的泰勒展開當計算點遠離展開點(即y=1)時,泰勒展開的收斂速度變慢、精度下降。,改以中點對數逼近計算 log 值,方法如下:
L
與 R
,以 __builtin_clz()
找出最接近的 2 的冪。L
, R
為乘積的平方根中心值,並同時逼近 log(L)
與 log(R)
之間的值。log2
的線性內插結果,最後乘上 ln(2)
轉為自然對數。在區間 [0.5, 10]
對每個值與對應 ln()
比較,結果如下所示:
結果:
本模組實作了井字棋遊戲中使用的 Negamax 演算法,並搭配 Zobrist Hashing 優化搜尋過程中的重複局面判斷與結果記憶。
negamax_predict
: 對外介面,接受目前的棋盤與當前玩家,回傳最佳移動與評分。negamax
: 遞迴實作 Negamax 演算法,使用 alpha-beta 剪枝與 PV-search 優化。negamax_init
: 初始化 Zobrist 表格與雜湊值。history_score_sum
, history_count
: 用於計算每個步驟的歷史平均評分,作為 move ordering 的依據。hash_value
: 使用 zobrist_table 依據棋盤內容 XOR 建立,用於辨識當前局面並查表。negamax_predict
函數zobrist_table
建立 hash_value
,對應目前棋盤狀態。negamax
遞迴搜尋score = -negamax(...)
的方式對立評分。hash_value
進行狀態轉移。在嘗試每個步驟時:
(-β, -α)
。(-α-1, -α)
來快速判斷是否可能更好,再回頭做完整搜索。這種方式稱為 Principal Variation Search(PVS),能有效加速搜尋。
對於後續的每個子節點
我們可以僅使用:
這是一個 null window,寬度為 1。
假設我們的主呼叫是:
我們想知道是否存在某個 。這等價於:
如果我們用 ,
所以,null-window 會 快速驗證這個分支是否可能比當前 α 更好。
使用 zobrist_get
與 zobrist_put
快速存取重複局面的最佳步驟與分數,降低重複計算次數。