# 2025q1 Homework3 (kxo)
contributed by <`Andrushika`>
## 在 user level 繪製棋盤
commit [d203aed](https://github.com/Andrushika/kxo/commit/d203aed523067f5dcbeb0a2fe852418d55747566)
在 `xo-user.c` 中加入以下程式碼:
```c
static int draw_board(const char *table)
{
printf("\n\n");
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
printf(" %c ", table[i * BOARD_SIZE + j]);
if (j < BOARD_SIZE - 1)
printf("|");
}
printf("\n");
if (i < BOARD_SIZE - 1) {
for (int j = 0; j < BOARD_SIZE * 4 - 1; j++) {
printf("-");
}
printf("\n");
}
}
printf("\n");
}
```
此處有對原本的 `draw_board` 做了一些修改,稍微提高了程式的可讀性;但因為用到了乘法運算,效能可能沒有原本來得好,是可以改進的地方。
這樣一來,在 kernel level 中就無須在乎棋盤的繪製方式,只要專注於將 data 本身利用 kfifo 傳輸給 user level 就好。
接下來修改 `main.c` 中原本的 `produce_board`,可以直接使用 `table` 的內容,並更名為更貼近實際行為的 `send_board_data`:
```c
/* send chess board data into the kfifo buffer */
static void send_board_data(void)
{
unsigned int len = kfifo_in(&rx_fifo, table, N_GRIDS);
if (unlikely(len < N_GRIDS) && printk_ratelimit())
pr_warn("%s: %zu bytes dropped\n", __func__, N_GRIDS - len);
pr_debug("kxo: %s: in %u/%u bytes\n", __func__, len, kfifo_len(&rx_fifo));
}
```
而在 `drawboard_work_func` 和 `timer_handler` 中,不再需要 `draw_board`:
```c
static void drawboard_work_func(struct work_struct *w)
{
int cpu;
/* This code runs from a kernel thread, so softirqs and hard-irqs must
* be enabled.
*/
WARN_ON_ONCE(in_softirq());
WARN_ON_ONCE(in_interrupt());
/* Pretend to simulate access to per-CPU data, disabling preemption
* during the pr_info().
*/
cpu = get_cpu();
pr_info("kxo: [CPU#%d] %s\n", cpu, __func__);
put_cpu();
read_lock(&attr_obj.lock);
if (attr_obj.display == '0') {
read_unlock(&attr_obj.lock);
return;
}
read_unlock(&attr_obj.lock);
/* Store data to the kfifo buffer */
mutex_lock(&producer_lock);
mutex_lock(&consumer_lock);
send_board_data();
mutex_unlock(&producer_lock);
mutex_unlock(&consumer_lock);
wake_up_interruptible(&rx_wait);
}
```
## 按下 ctrl-q 離開時顯示對局歷史
在實作時遇到一個問題:無論進行了幾局遊戲,`games_count` 始終為 0(即 history 停留在第一局遊戲)。
因此,在 `track_moves` 加上以下程式碼進行偵錯:
```c
static void track_moves(const char *board)
{
printf("track_moves called. Current move_count: %d, game_count: %d\n", move_count, game_count);
bool reset_detected = is_board_reset(board);
printf("is_board_reset returned: %s\n", reset_detected ? "true" : "false");
/* Check if the board has been reset */
if (reset_detected) {
printf("Reset detected!\n");
if (move_count > 0) {
printf("Saving game %d with %d moves.\n", game_count + 1, move_count);
save_current_game();
printf("After save_current_game, game_count is now: %d\n", game_count);
}else{
printf("Reset detected, but move_count is 0, not saving.\n");
}
move_count = 0;
printf("move_count reset to 0.\n");
memcpy(prev_board, board, N_GRIDS);
return;
}
/* Track new moves */
for (int i = 0; i < N_GRIDS; i++) {
if (board[i] != ' ' && prev_board[i] == ' ') {
if (move_count < N_GRIDS) {
current_moves[move_count].pos = i;
current_moves[move_count].player = board[i];
move_count++;
}
}
}
/* Update previous board state */
memcpy(prev_board, board, N_GRIDS);
}
```
之後發現,即使棋局已經分出勝負並重置,`is_board_reset` 都未被觸發,導致不同棋局的 `moves` 仍會繼續被寫在同一條 `game` 紀錄中:
```
track_moves called. Current move_count: 0, game_count: 0
track_moves called. Current move_count: 1, game_count: 0
track_moves called. Current move_count: 1, game_count: 0
track_moves called. Current move_count: 2, game_count: 0
track_moves called. Current move_count: 3, game_count: 0
track_moves called. Current move_count: 3, game_count: 0
track_moves called. Current move_count: 4, game_count: 0
track_moves called. Current move_count: 5, game_count: 0
track_moves called. Current move_count: 5, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 7, game_count: 0
track_moves called. Current move_count: 8, game_count: 0
track_moves called. Current move_count: 8, game_count: 0
track_moves called. Current move_count: 9, game_count: 0
track_moves called. Current move_count: 10, game_count: 0
is_board_reset returned: false
```
原本的 `is_board_reset` 是這樣實作的:
```c
static bool is_board_empty(const char *board)
{
return memcmp(board, empty_board, N_GRIDS) == 0;
}
static bool is_board_reset(const char *board)
{
if (!is_board_empty(board))
return false;
if (is_board_empty(prev_board))
return false;
return true;
}
```
此處使用了檢查棋盤是否為 empty 的方式進行判斷;然而,因為每次的畫面更新都是在 AI 決策完才被觸發,所以不會有 `is_board_reset` 條件成立的時候。
:::danger
降低核心和使用者層級溝通的成本
:::