# 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 降低核心和使用者層級溝通的成本 :::