在 handle_player 函數中,對於上下左右的移動邏輯,幾乎完全相同,只是處理的座標不同。這種重複代碼增加了維護成本。
重構建議:
可以將移動邏輯提取到一個單獨的函數中,這樣就可以避免重複的邏輯。
然後,在 handle_player 函數中,只需要根據鍵盤輸入呼叫這個 move_player 函數。
原來的 handle_player() 變成:
game_logic.c
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
#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
#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;
}
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
#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
#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
#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 語句中。具體表現為:
相同的寶石收集、移動和狀態更新邏輯在多處重複。
例如:
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;
影響:
重複代碼增加了維護成本。如果需要修改某個邏輯,例如處理寶石,必須在每個方向邏輯中進行重複修改,容易出現疏漏。
函數的結構混亂,難以理解每個邏輯分支的具體功能。
嵌套層次過多,降低了代碼可讀性。
影響:
新增功能或調試時需要逐層跟蹤嵌套,容易導致錯誤。
玩家輸入(解析方向)。
移動邏輯(更新屏幕和玩家位置)。
碰撞邏輯(寶石、出口、死亡、推動岩石)。
影響:
功能耦合,測試和擴展都很困難。例如,如果需要更改移動邏輯,會影響整個函數。
缺乏可測試性(Lack of Testability)
嵌套邏輯難以單獨測試特定的場景(例如處理寶石或推動岩石)。
無法單獨測試移動邏輯和輸入處理邏輯。
減少重複代碼
重構後的通用函數: process_move 將所有方向的移動邏輯統一,簡化了代碼結構:
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;
}
}
通用邏輯提取後,減少了方向處理中的冗餘代碼。
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;
}
}
函數結構更加清晰,僅處理方向輸入和調用通用邏輯。
每個分支對應一個方向,便於維護。
重構後的優勢總結
test_handle_player.c
#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
#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
#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