---
# System prepended metadata

title: 軟體設計 final_project

---

# 軟體設計 final_project
# [Refactor project](https://github.com/simonracz/linux-terminal-game/blob/master)

### 1. 重複的代碼（Duplicated Code）

在 handle_player 函數中，對於上下左右的移動邏輯，幾乎完全相同，只是處理的座標不同。這種重複代碼增加了維護成本。
重構建議：
可以將移動邏輯提取到一個單獨的函數中，這樣就可以避免重複的邏輯。

然後，在 handle_player 函數中，只需要根據鍵盤輸入呼叫這個 move_player 函數。

原來的 handle_player() 變成:
game_logic.c
```clike=
void move_player(GameState* state, int delta_x, int delta_y) {
    char next_cell = state->screen[state->pos_y + delta_y][state->pos_x + delta_x];
    switch (next_cell) {
        case '$': // 寶石
            state->gems_collected++;
            /* fall through */
        case ' ': // 空地
        case '.': // 泥土
            state->screen[state->pos_y][state->pos_x] = ' ';
            state->screen[state->pos_y + delta_y][state->pos_x + delta_x] = '@';
            state->pos_x += delta_x;
            state->pos_y += delta_y;
            break;
        case 'E': // 出口
            state->won = 1;
            state->screen[state->pos_y][state->pos_x] = ' ';
            state->screen[state->pos_y + delta_y][state->pos_x + delta_x] = '@';
            state->pos_x += delta_x;
            state->pos_y += delta_y; 
            break;
        case 'o': // 落石
        case 'S': // 掉落寶石
            state->dead = 1;
            break;
        default:
            break;
    }
}

void handle_player(GameState* state) {
    switch (state->key) {
        case 1: move_player(state, 0, -1); break;  // 上
        case 2: move_player(state, 0, 1); break;   // 下
        case 3: move_player(state, 1, 0); break;   // 右
        case 4: move_player(state, -1, 0); break;  // 左
        default: break;
    }
}

```
game_logic.h
```clike=
#ifndef GAME_LOGIC_H
#define GAME_LOGIC_H

// 遊戲狀態結構體
typedef struct {
    int key;
    int pos_x;
    int pos_y;
    unsigned int count;
    int gems_collected;
    int dead;
    int won;
    char old_screen[26][60]; // 預設最大大小
    char screen[26][60];
} GameState;

// 遊戲邏輯相關函數
void move_player(GameState* state, int delta_x, int delta_y);
void handle_player(GameState* state);
void update_all_elements(GameState* state);
void update(GameState* state);

#endif // GAME_LOGIC_H

```

test_move_player.c
```clike=
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "../game_logic.h"


void test_move_player() {
    // 初始化遊戲狀態
    GameState state = {
        .screen = {
            {'X', 'X', 'X', 'X', 'X'},
            {'X', '.', '.', '.', 'X'},
            {'X', '.', '@', '$', 'X'},
            {'X', '.', '.', 'E', 'X'},
            {'X', 'X', 'X', 'X', 'X'},
        },
        .pos_x = 2, // 玩家初始位置 // 第幾col
        .pos_y = 2,                // 第幾row
    };

    // 測試向右移動到寶石
    move_player(&state, 1, 0);
    assert(state.pos_x == 3); // 玩家應移動到 (2, 3)
    assert(state.pos_y == 2);
    assert(state.gems_collected == 1); // 應收集到 1 顆寶石
    assert(state.screen[2][2] == ' '); // 原位置應變為空
    assert(state.screen[2][3] == '@'); // 新位置為玩家
    printf("move right correct!\n");

    // 測試向下移動到出口
    move_player(&state, 0, 1);
    assert(state.pos_x == 3); // 玩家應移動到 (3, 3)
    assert(state.pos_y == 3);
    assert(state.won == 1);   // 玩家應達到勝利條件
    assert(state.screen[3][3] == '@'); // 新位置為玩家

    printf("test_move_player passed!\n");
}


int main() {
    test_move_player(); // 測試移動功能
    return 0;
}

```

### 巨大的函數（Large Function）
update_all_elements 函數處理了多個不同的遊戲元素，包括處理岩石、寶石以及玩家角色。這樣的函數過於龐大，且包含了過多的邏輯。

為何需要重構：
函數過大，難以維護和理解。
使代碼缺乏清晰的模組化，當添加新功能時很容易出現錯誤。
原update_all_elements()問題
職責不單一：update_all_elements 涵蓋了多個邏輯：
處理靜止岩石和寶石（handle_rocks_gems）。
處理下落岩石和寶石（handle_falling_rocks_gems）。
測試困難：無法單獨測試岩石/寶石邏輯。
可讀性差：每個條件分支（case）隱藏了重要邏輯，難以擴展或調試。
重構建議：
將每一類別的處理邏輯提取到單獨的函數中。例如，處理岩石和寶石的邏輯可以單獨放到 handle_rocks 和 handle_falling_rocks 函數中。


game_logic.c
```clike=
#include <stdio.h>
#include "game_logic.h"


#define MAX_X 60
#define MAX_Y 26

// 元素處理器表
typedef void (*ElementHandler)(GameState* state, int x, int y);
ElementHandler element_handlers[128];

// 初始化元素處理器
void initialize_element_handlers() {
    for (int i = 0; i < 128; i++) {
        element_handlers[i] = NULL;
    }
    element_handlers['O'] = handle_rocks;
    element_handlers['$'] = handle_rocks;
    element_handlers['o'] = handle_falling_rocks;
    element_handlers['S'] = handle_falling_rocks;
}
// x: col, y: row
// 處理靜止的岩石和寶石
void handle_rocks(GameState* state, int x, int y) {
    int is_gem = (state->screen[y][x] == '$');
    if (state->screen[y + 1][x] == ' ') {
        state->screen[y][x] = is_gem ? 'S' : 'o';
    }
}

// 處理下落的岩石和寶石
void handle_falling_rocks(GameState* state, int x, int y) {
    int is_gem = (state->screen[y][x] == 'S');
    if (state->screen[y + 1][x] == ' ') {
        state->screen[y][x] = ' ';
        state->screen[y + 1][x] = is_gem ? 'S' : 'o';
    } else if (state->screen[y + 1][x] == '@') {
        state->dead = 1;
    }
}

// 遍歷屏幕元素
void for_each_screen_element(GameState* state, void (*callback)(GameState*, int, int)) {
    for (int j = MAX_Y - 1; j >= 0; --j) {
        for (int i = MAX_X - 1; i >= 0; --i) {
            callback(state, i, j);
        }
    }
}

// 處理單個屏幕元素
void process_element(GameState* state, int x, int y) {
    char element = state->screen[y][x];
    if (element_handlers[element]) {
        element_handlers[element](state, x, y);
    }
}

// 更新所有元素
void update_all_elements(GameState* state) {
    for_each_screen_element(state, process_element);
}

```
game_logic.h
```clike=
#ifndef GAME_LOGIC_H
#define GAME_LOGIC_H

// 遊戲狀態結構
typedef struct {
    int key;                // 玩家輸入
    int pos_x, pos_y;       // 玩家位置
    unsigned int count;     // 更新次數
    int gems_collected;     // 收集的寶石數
    int dead;               // 玩家是否死亡
    int won;                // 玩家是否贏得遊戲
    char old_screen[26][60]; // 上一幀畫面
    char screen[26][60];     // 當前畫面
} GameState;

// 遊戲邏輯函數
void initialize_element_handlers();
void handle_rocks(GameState* state, int x, int y);
void handle_falling_rocks(GameState* state, int x, int y);
void for_each_screen_element(GameState* state, void (*callback)(GameState*, int, int));
void process_element(GameState* state, int x, int y);
void update_all_elements(GameState* state);

#endif // GAME_LOGIC_H

```
test_elements.c
```clike=
#include <assert.h>
#include <stdio.h>
#include "../game_logic.h"

// 測試靜止岩石處理
void test_handle_rocks() {
    GameState state = {
        .screen = {
            {'X', 'X', 'X', 'X', 'X'},
            {'X', ' ', 'O', ' ', 'X'},
            {'X', ' ', ' ', ' ', 'X'},
            {'X', 'X', 'X', 'X', 'X'},
        },
    };

    handle_rocks(&state, 2, 1);
    assert(state.screen[1][2] == 'o');
    printf("test_handle_rocks passed!\n");
}

// 測試下落岩石處理
void test_handle_falling_rocks() {
    GameState state = {
        .screen = {
            {'X', 'X', 'X', 'X', 'X'},
            {'X', ' ', 'o', 'o', 'X'},
            {'X', ' ', ' ', '@', 'X'},
            {'X', 'X', 'X', 'X', 'X'},
        },
        .dead = 0,
    };

    handle_falling_rocks(&state, 2, 1);
    assert(state.screen[1][2] == ' ');
    assert(state.screen[2][2] == 'o');
    assert(state.dead == 0);

    handle_falling_rocks(&state, 3, 1);
    assert(state.dead == 1);
    printf("test_handle_falling_rocks passed!\n");
}

// 測試更新所有元素
void test_update_all_elements() {
    GameState state = {
        .screen = {
            {'X', 'X', 'X', 'X', 'X'},
            {'X', ' ', '$', 'o', 'X'},
            {'X', ' ', ' ', '@', 'X'},
            {'X', 'X', 'X', 'X', 'X'},
        },
        .dead = 0,
    };

    initialize_element_handlers();
    update_all_elements(&state);

    assert(state.screen[1][2] == 'S');
    assert(state.screen[1][3] == 'o');
    assert(state.dead == 1);

    printf("test_update_all_elements passed!\n");
}

int main() {
    test_handle_rocks();
    test_handle_falling_rocks();
    test_update_all_elements();
    return 0;
}

```






清晰的結構:
每個元素的邏輯處理被封裝到特定函數中，便於維護。
高效的對應:
使用 ASCII 字元作為索引，查找對應處理函數的時間複雜度為 O(1)。
擴展性:
如果新增遊戲元素，只需將對應字元和函數添加到 initialize_element_handlers()。



重複代碼（Duplicated Code）
每個方向（上、下、左、右）的邏輯幾乎相同，但分別嵌套在多個 case 語句中。具體表現為：

相同的寶石收集、移動和狀態更新邏輯在多處重複。
例如：

```clike=
case 1: // UP
    switch (state->screen[state->pos_y - 1][state->pos_x]) {
        case '$':
            state->gems_collected++; // fallthrough
        case ' ':
        case '.':
            state->screen[state->pos_y][state->pos_x] = ' ';
            state->screen[state->pos_y - 1][state->pos_x] = '@';
            --state->pos_y;
            break;
        // ...
    }
    break;

```
影響：

重複代碼增加了維護成本。如果需要修改某個邏輯，例如處理寶石，必須在每個方向邏輯中進行重複修改，容易出現疏漏。

2. 巨大的函數（Large Function）
handle_player 包含多層嵌套的 switch 語句，導致：

函數的結構混亂，難以理解每個邏輯分支的具體功能。
嵌套層次過多，降低了代碼可讀性。
影響：

新增功能或調試時需要逐層跟蹤嵌套，容易導致錯誤。

3. 職責不單一（Violation of Single Responsibility Principle, SRP）
handle_player 同時處理：

玩家輸入（解析方向）。
移動邏輯（更新屏幕和玩家位置）。
碰撞邏輯（寶石、出口、死亡、推動岩石）。
影響：

功能耦合，測試和擴展都很困難。例如，如果需要更改移動邏輯，會影響整個函數。

4. 缺乏可測試性（Lack of Testability）
嵌套邏輯難以單獨測試特定的場景（例如處理寶石或推動岩石）。
無法單獨測試移動邏輯和輸入處理邏輯。

1. 減少重複代碼
重構後的通用函數： process_move 將所有方向的移動邏輯統一，簡化了代碼結構：
```clike=
void process_move(GameState* state, int dx, int dy) {
    int target_x = state->pos_x + dx;
    int target_y = state->pos_y + dy;
    char target_cell = state->screen[target_y][target_x];

    switch (target_cell) {
        case '$': // 收集寶石
            state->gems_collected++;
        case ' ': // 空格
        case '.': // 泥土
            state->screen[state->pos_y][state->pos_x] = ' ';
            state->screen[target_y][target_x] = '@';
            state->pos_x = target_x;
            state->pos_y = target_y;
            break;
        case 'E': // 出口
            state->won = 1;
            break;
        case 'o': // 下落岩石
        case 'S': // 下落寶石
            state->dead = 1;
            break;
        case 'O': // 推動岩石
            if (state->screen[target_y][target_x + dx] == ' ') {
                state->screen[target_y][target_x + dx] = 'O';
                state->screen[state->pos_y][state->pos_x] = ' ';
                state->screen[target_y][target_x] = '@';
                state->pos_x = target_x;
                state->pos_y = target_y;
            }
            break;
        default:
            break;
    }
}

```
通用邏輯提取後，減少了方向處理中的冗餘代碼。

2. 簡化主函數邏輯
重構後的 handle_player：
```clike=
void handle_player(GameState* state) {
    switch (state->key) {
        case 1: process_move(state, 0, -1); break; // 上
        case 2: process_move(state, 0, 1); break;  // 下
        case 3: process_move(state, 1, 0); break;  // 右
        case 4: process_move(state, -1, 0); break; // 左
        default: break;
    }
}

```
函數結構更加清晰，僅處理方向輸入和調用通用邏輯。
每個分支對應一個方向，便於維護。

3. 提高職責單一性
handle_player 的職責限定為處理玩家輸入。
具體的移動和碰撞邏輯完全由 process_move 處理，降低了耦合性。



重構後的優勢總結
![image](https://hackmd.io/_uploads/Hyku52ZUkl.png)







test_handle_player.c
```clike=
#include <assert.h>
#include <stdio.h>
#include "../game_logic.h"

// 測試處理移動
void test_process_move() {
    GameState state = {
        .screen = {
            {'X', 'X', 'X', 'X', 'X'},
            {'X', '.', '.', '.', 'X'},
            {'X', '.', '@', '$', 'X'},
            {'X', '.', '.', 'E', 'X'},
            {'X', 'X', 'X', 'X', 'X'},
        },
        .pos_x = 2,
        .pos_y = 2,
    };

    process_move(&state, 1, 0); // 向右移動
    assert(state.pos_x == 3);
    assert(state.pos_y == 2);
    assert(state.gems_collected == 1);
    assert(state.screen[2][2] == ' ');
    assert(state.screen[2][3] == '@');
    printf("move right correct!\n");

    process_move(&state, 0, 1); // 向下移動到出口
    assert(state.pos_x == 3);
    assert(state.pos_y == 3);
    assert(state.won == 1);
    assert(state.screen[3][3] == '@');

    printf("test_process_move passed!\n");
}

// 測試處理玩家輸入
void test_handle_player() {
    GameState state = {
        .screen = {
            {'X', 'X', 'X', 'X', 'X'},
            {'X', '.', '.', '.', 'X'},
            {'X', '.', '@', '$', 'X'},
            {'X', '.', '.', 'E', 'X'},
            {'X', 'X', 'X', 'X', 'X'},
        },
        .pos_x = 2,
        .pos_y = 2,
        .key = 3, // 向右
    };

    handle_player(&state);
    assert(state.pos_x == 3);
    assert(state.pos_y == 2);
    assert(state.gems_collected == 1);
    assert(state.screen[2][2] == ' ');
    assert(state.screen[2][3] == '@');

    state.key = 2; // 向下
    handle_player(&state);
    assert(state.pos_x == 3);
    assert(state.pos_y == 3);
    assert(state.won == 1);
    assert(state.screen[3][3] == '@');

    printf("test_handle_player passed!\n");
}

int main() {
    test_process_move();
    test_handle_player();
    return 0;
}

```
game_logic.c
```clike=
#include "game_logic.h"

// 處理玩家移動的共用邏輯
void process_move(GameState* state, int dx, int dy) {
    int target_x = state->pos_x + dx;
    int target_y = state->pos_y + dy;
    char target_cell = state->screen[target_y][target_x];

    switch (target_cell) {
        case '$': // 收集寶石
            state->gems_collected++;
            /* fall through */
        case ' ': // 空格
        case '.': // 泥土
            state->screen[state->pos_y][state->pos_x] = ' ';
            state->screen[target_y][target_x] = '@';
            state->pos_x = target_x;
            state->pos_y = target_y;
            break;
        case 'E': // 出口
            state->screen[state->pos_y][state->pos_x] = ' ';
            state->screen[target_y][target_x] = '@';
            state->pos_x = target_x;
            state->pos_y = target_y;
            state->won = 1;
            break;
        case 'o': // 下落岩石
        case 'S': // 下落寶石
            state->dead = 1;
            break;
        case 'O': // 推動岩石
            if (state->screen[target_y][target_x + dx] == ' ') {
                state->screen[target_y][target_x + dx] = 'O';
                state->screen[state->pos_y][state->pos_x] = ' ';
                state->screen[target_y][target_x] = '@';
                state->pos_x = target_x;
                state->pos_y = target_y;
            }
            break;
        default:
            break;
    }
}

// 處理玩家輸入
void handle_player(GameState* state) {
    switch (state->key) {
        case 1: process_move(state, 0, -1); break; // 上
        case 2: process_move(state, 0, 1); break;  // 下
        case 3: process_move(state, 1, 0); break;  // 右
        case 4: process_move(state, -1, 0); break; // 左
        default: break;
    }
}

```
game_logic.h
```clike=
#ifndef GAME_LOGIC_H
#define GAME_LOGIC_H

// 遊戲狀態結構
typedef struct {
    int key;                // 玩家輸入
    int pos_x, pos_y;       // 玩家位置
    unsigned int count;     // 更新次數
    int gems_collected;     // 收集的寶石數
    int dead;               // 玩家是否死亡
    int won;                // 玩家是否贏得遊戲
    char old_screen[26][60]; // 上一幀畫面
    char screen[26][60];     // 當前畫面
} GameState;

// 遊戲邏輯函數
void initialize_element_handlers();
void handle_rocks(GameState* state, int x, int y);
void handle_falling_rocks(GameState* state, int x, int y);
void for_each_screen_element(GameState* state, void (*callback)(GameState*, int, int));
void process_element(GameState* state, int x, int y);
void update_all_elements(GameState* state);
void process_move(GameState* state, int dx, int dy);
void handle_player(GameState* state);

#endif // GAME_LOGIC_H

```