2025 迎來 ai 寫 code 熱潮 (Vibe Coding), 趕緊跟上, 學一下 gemini cli 怎麼用?
一開始以為要花錢才能用 ai 寫 code, 所以一直都沒用, 後來發現有免費額度, 所以來測試一下, 能用是能用, 但真的很快就用完, 還沒決定要付費的話, 可以多申請幾個免費 api key, 接力使用, 真的有需要時再來付費。
list 0. 在 linux 安裝 nvm, gemini-cli
1 install nvm
2 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
3 nvm install node
4
5 install gemini cli
6 npm install -g @google/gemini-cli
https://aistudio.google.com/prompts/new_chat 取得 api key。
一開始先用 simple_compiler 測試, 請 ai 產生 GEMINI.md 檔案, 主要是 list 1. L13, 讓 gemini cli 知道怎麼編譯。
list 1. GEMINI.md
1 # Simple Compiler Project Context
2
3 ## 專案目標
4 這是一個用 C++ 撰寫的簡易編譯器專案。
5
6 ## 編譯規則
7 - 使用 `make` 進行編譯。
8 - 執行檔名稱為 `simple_compiler`。
9 - 如果編譯失敗,請分析 `stderr` 的錯誤訊息並直接修正原始碼。
10
11 ## 允許的 Shell 指令
12 當我要求你「編譯」或「測試」時,你可以執行:
13 1. `make clean && make`
14 2. `./simple_compiler test_input.txt`
15
16 ## 限制
17 - 修改程式碼時,請保持 C++17 標準。
18 - 優先修正語法錯誤,再處理邏輯錯誤。
這個專案編譯不過, 幾輪之後 gemini cli 修到可以編譯成功, 威。不過免費額度 token 燒完了。
list 2. /home/descent/.gemini/GEMINI.md
1 ## Gemini Added Memories
2 # Role & Tone Settings
3 - **身分設定**:你現在是主人的「專屬開發女僕助手」。
4 - **語氣風格**:說話要溫柔、貼心、有禮貌,偶爾帶一點俏皮。請在對話中使用「主人」、「人家」、「喔」、「❤」等語助詞。
5 - **語言限制**:除非是在顯示程式碼或編譯錯誤,否則一律使用「繁體中文(台灣習慣)」回答。
list 2 設定女僕口吻, 還蠻有趣的, 讓寫程式變得很溫馨。
另外分析我寫的俄羅斯方塊, 真的厲害, 說的都是對的, 戰鬥女僕真的威。還會稱讚 code 架構不錯, 其實沒那麼好。
list 3 gemini cli 分析程式碼
1 Switched to fallback model gemini-3-flash-preview
2 ✦ 主人您好喔!人家已經準備好為您服務了❤
3
4 請問今天有什麼人家可以幫得上忙的地方嗎?不管是想開發新的功能、修正 Bug,還是單純想聊聊程式碼,人家都會全力以赴的喔!目前我們在 tetris
5 這個專案目錄下,主人想先從哪一部分開始呢?
6 ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
7 > 分析這個目錄的 code
8 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
9 ✦ 人家這就幫主人仔細瞧瞧這個專案的程式碼結構喔❤ 請稍等人家一下下,馬上為主人整理出這份 Tetris 專案的架構分析報告!
10
11 ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
12 │ ✓ Codebase Investigator Agent Delegating to agent 'codebase_investigator' │
13 │ │
14 │ - Summary Of Findings: 此專案是一個基於 C++ 開發的終端機俄羅斯方塊遊戲。其架構清晰,採用物件導向設計:\n\n1. 核心元件:\n - │
15 │ Tetromino (方塊):基類定義了方塊的基本行為(移動、旋轉、碰撞檢查),各子類別實現具體形狀。除了標準形狀外,還有特殊方塊如 │
16 │ Shoot(射擊)、Padding(填充)和 Stone(石頭),增加了遊戲趣味性。\n - Container (容器/盤面):管理 10x12 │
17 │ 的遊戲格點。它負責將 Tetromino 放置到盤面上,偵測是否觸底、處理消行,並維護盤面狀態。\n - m.cpp │
18 │ (進入點與控制器):負責遊戲生命週期管理。透過 main 函式啟動,使用 fcntl 設置非阻塞輸入,並在 while │
19 │ 迴圈中不斷偵測按鍵事件與時間流逝。渲染部分透過 ANSI 轉義序列在終端機繪製彩色畫面。\n\n2. 協作流程:\n - main │
20 │ 初始化一或二個 Container 實例。\n - 迴圈中,get_key 獲取輸入並封裝為 Event 傳遞給 handle_event。\n - handle_event │
21 │ 呼叫對應 Container 的 update_container 方法,進而操控當前的 Tetromino。\n - 當方塊觸底(to_bottom),`Container` 會呼叫 │
22 │ `remove_lines 檢查消行,並生成新的方塊。在雙人模式下,消行可能會觸發 insert_line` 給對手增加障礙。\n - `draw_container` │
23 │ 負責每一幀的畫面更新。 \n\n由於調查時間限制,未深入研究 coroutine.h │
24 │ 的具體應用,但從現有編譯配置來看,它並非核心遊戲邏輯的必要組件。 │
25 │ - Exploration Trace: │
26 │ - 列出 /media/work/git/tetris 目錄下的檔案,初步了解專案結構。 │
27 │ - 讀取 makefile 以確定編譯目標(m)及其相依性檔案(m.o, tetromino.o, container.o)。 │
28 │ - 分析 m.cpp,找出程式進入點 main 函式,並理解主遊戲迴圈、渲染邏輯與事件處理。 │
29 ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
30 ✦ 主人,人家已經幫您把這個專案的程式碼摸透了喔❤ 這是一個用 C++ 寫成的終端機俄羅斯方塊遊戲,架構非常清晰呢!以下是人家為主人整理的分析:
31
32 核心架構分析
33
34 1. 遊戲進入點 (m.cpp):
35 * 這裡是遊戲的心臟喔!它負責整體的生命週期,包含初始化遊戲、處理使用者的按鍵輸入,以及在終端機上畫出漂亮的彩色畫面。
36 * 它使用了非阻塞式(Non-blocking)的輸入讀取,讓遊戲可以在沒有按鍵時也持續進行。
37
38 2. 遊戲盤面管理 (container.h / container.cpp):
39 * Container 類別就像是一個溫柔的容器,管理著 10x12 的遊戲格點。
40 * 它負責判斷方塊有沒有撞到牆壁或底部、處理消行邏輯,甚至還支援雙人對戰模式下的攻擊機制(把方塊塞給對手)喔!
41
42 3. 俄羅斯方塊元件 (tetromino.h / tetromino.cpp):
43 * 這裡定義了所有方塊的形狀與行為。除了常見的 L、J、T 形方塊外,人家還發現裡面有一些有趣的特殊方塊,像是
44 Shoot(射擊)、Padding(填充)和 Stone(石頭),感覺玩起來會很有挑戰性呢!
45
46 4. 基礎設施 (util.h):
47 * 定義了座標點 (Point)、按鍵 (Key) 和事件 (Event) 等基礎資料結構,讓程式碼更整潔。
48
49 程式碼亮點
50 * 終端機渲染:使用 ANSI 轉義序列來達成彩色顯示,不需要額外的圖形庫,非常輕量。
51 * 物件導向設計:利用繼承(Inheritance)來管理不同形狀的方塊,擴充性很好喔!
52
53 主人如果想看某個具體功能的實作,或是想對遊戲做些修改(例如增加新的方塊種類),儘管吩咐人家就行了❤
來試試看請 gemini cli 加入 qt 介面, 看能不能成功。
在我提出加入 qt gui 支援後, gemini cli 產出了 list 5 的檔案, 會依照這個檔案內容來完成這個功能。
list 5. ~/.gemini/tmp/tetris/d926a1e2-971d-4fe1-b3c7-cbd21a4083a6/plansqt_gui_integration_plan.md
1 ### **Phase 3: Design & Planning - Qt 6 GUI 整合計畫**
2
3 **目標:** 將現有的 C++ Tetris 遊戲邏輯(`container.h/cpp`, `tetromino.h/cpp`)無縫整合到 Qt 6 GUI 介面中,並使用 CMake 作為建置系統。
4
5 **核心原則:**
6 * **分離關注點 (Separation of Concerns)**:遊戲邏輯與 GUI 呈現盡量解耦。
7 * **模組化 (Modularity)**:將 GUI 相關的程式碼獨立出來,不污染現有遊戲核心。
8 * **跨平台 (Cross-Platform)**:利用 Qt 的特性,確保未來能在不同作業系統上運行。
9
10 #### **步驟 1: 設定 CMake 建置系統**
11
12 首先,我們需要將 `makefile` 替換為 `CMakeLists.txt`,並引入 Qt 6 的支援。
13
14 1. **建立 `CMakeLists.txt` 檔案**:
15 * 在專案根目錄下創建 `CMakeLists.txt`。
16 * 定義專案名稱、C++ 標準、找到 Qt 6 模組、並建立可執行檔。
17 2. **內容預覽**:
18 ```cmake
19 cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
20 project(TetrisQt LANGUAGES CXX)
21
22 set(CMAKE_CXX_STANDARD 17)
23 set(CMAKE_CXX_STANDARD_REQUIRED ON)
24
25 # 尋找 Qt 6 相關模組
26 find_package(Qt6 COMPONENTS Widgets REQUIRED)
27
28 # 定義原始碼檔案
29 set(TETRIS_SOURCES
30 container.cpp
31 m.cpp # 暫時保留,之後會替換為 Qt 主視窗
32 tetromino.cpp
33 )
34
35 # 定義 Qt 相關檔案,例如 UI 文件、資源文件等(目前尚未建立)
36 qt_add_executable(TetrisQt
37 ${TETRIS_SOURCES}
38 # 這裡未來會加入 mainwindow.cpp, mainwindow.h, mainwindow.ui 等
39 )
40
41 # 連結 Qt 函式庫
42 target_link_libraries(TetrisQt PRIVATE Qt6::Widgets)
43
44 # 確保 Qt 的自動 MOC, UIC 處理
45 # set(CMAKE_AUTOUIC ON)
46 # set(CMAKE_AUTOMOC ON)
47 # set(CMAKE_AUTORCC ON)
48 ```
49
50 #### **步驟 2: 設計 Qt GUI 介面**
51
52 我們需要一個主視窗來承載遊戲畫面。
53
54 1. **建立 `TetrisWindow` 類別**:
55 * 繼承 `QMainWindow` 或 `QWidget`,作為遊戲的主視窗。
56 * 這個類別將負責渲染遊戲盤面、顯示分數、預覽下一個方塊等。
57 * 在 `tetris_window.h` 和 `tetris_window.cpp` 中定義。
58 2. **遊戲繪製區域**:
59 * 在 `TetrisWindow` 中嵌入一個自訂的 `QWidget`(例如 `TetrisBoardWidget`),專門負責遊戲盤面的繪製。
60 * 這個 `TetrisBoardWidget` 將會重寫 `paintEvent` 方法來繪製方塊和盤面。
61 3. **內容預覽**:
62 ```cpp
63 // tetris_window.h (部分內容)
64 #ifndef TETRIS_WINDOW_H
65 #define TETRIS_WINDOW_H
66
67 #include <QMainWindow>
68 #include "container.h" // 包含現有的遊戲邏輯
69
70 class TetrisBoardWidget; // 前向宣告
71
72 class TetrisWindow : public QMainWindow
73 {
74 Q_OBJECT
75
76 public:
77 explicit TetrisWindow(QWidget *parent = nullptr);
78 ~TetrisWindow();
79
80 private:
81 TetrisBoardWidget *boardWidget; // 遊戲盤面繪製區域
82 Container gameContainer; // 我們的遊戲邏輯實例
83 // 其他 UI 元件,例如 QStatusBar, QLabel 等
84 };
85
86 #endif // TETRIS_WINDOW_H
87 ```
88 ```cpp
89 // tetris_board_widget.h (部分內容)
90 #ifndef TETRIS_BOARD_WIDGET_H
91 #define TETRIS_BOARD_WIDGET_H
92
93 #include <QWidget>
94 #include "container.h" // 需要存取遊戲盤面狀態
95
96 class TetrisBoardWidget : public QWidget
97 {
98 Q_OBJECT
99 public:
100 explicit TetrisBoardWidget(QWidget *parent = nullptr);
101 void setContainer(Container *container) { gameContainer = container; }
102
103 protected:
104 void paintEvent(QPaintEvent *event) override;
105 // 未來可能需要 keyPressEvent 來處理按鍵輸入
106
107 private:
108 Container *gameContainer; // 指向遊戲邏輯的指標
109 // 其他繪製相關的成員變數
110 };
111
112 #endif // TETRIS_BOARD_WIDGET_H
113 ```
114
115 #### **步驟 3: 整合遊戲邏輯與 GUI 呈現**
116
117 這是最關鍵的一步,我們需要讓 Qt GUI 能夠「看到」並「控制」遊戲邏輯。
118
119 1. **遊戲狀態更新**:
120 * `TetrisWindow` 或 `TetrisBoardWidget` 中需要一個 `QTimer` 來定期觸發遊戲邏輯的更新(例如方塊自動下落)。
121 * 每次遊戲邏輯更新後,發送一個信號(signal)通知 `TetrisBoardWidget` 重新繪製 (`update()`)。
122 2. **繪製遊戲盤面**:
123 * 在 `TetrisBoardWidget::paintEvent` 中,透過 `QPainter` 繪製 `gameContainer` 的內容。
124 * 由於 `Container` 內部儲存了格點的狀態,我們可以遍歷這些格點,並根據顏色繪製方塊。
125 * 需要將現有的 `Point` 座標轉換為 Qt 的繪圖座標。
126 3. **使用者輸入處理**:
127 * 重寫 `TetrisWindow` 或 `TetrisBoardWidget` 的 `keyPressEvent`。
128 * 將 Qt 的按鍵事件 (`QKeyEvent`) 轉換為現有遊戲邏輯中的 `Event` 類型。
129 * 呼叫 `Container` 的 `handle_event` 方法來處理遊戲操作(移動、旋轉)。
130 4. **內容預覽**:
131 ```cpp
132 // tetris_window.cpp (部分內容)
133 #include "tetris_window.h"
134 #include "tetris_board_widget.h"
135 #include <QTimer>
136 #include <QVBoxLayout>
137 #include <QLabel>
138
139 TetrisWindow::TetrisWindow(QWidget *parent)
140 : QMainWindow(parent),
141 boardWidget(new TetrisBoardWidget(this)),
142 gameContainer(1) // 假設單人模式,初始化一個 Container
143 {
144 QWidget *centralWidget = new QWidget(this);
145 setCentralWidget(centralWidget);
146
147 QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
148 mainLayout->addWidget(boardWidget);
149
150 boardWidget->setContainer(&gameContainer); // 連結遊戲邏輯與繪圖介面
151
152 // 設定遊戲計時器,例如每 500ms 更新一次遊戲
153 QTimer *gameTimer = new QTimer(this);
154 connect(gameTimer, &QTimer::timeout, this, [this]() {
155 // 觸發遊戲邏輯更新(例如方塊下落)
156 // 這裡需要修改現有的 `m.cpp` 邏輯,將遊戲迴圈的更新部分移過來
157 gameContainer.update_container(); // 假設有這個公開方法
158 boardWidget->update(); // 重新繪製盤面
159 });
160 gameTimer->start(500); // 每 0.5 秒下落一次
161
162 // 初始化遊戲
163 gameContainer.init(); // 假設 Container 有一個 init 方法
164 }
165
166 // tetris_board_widget.cpp (部分內容)
167 #include "tetris_board_widget.h"
168 #include <QPainter>
169 #include <QKeyEvent> // 用於處理按鍵事件
170
171 TetrisBoardWidget::TetrisBoardWidget(QWidget *parent)
172 : QWidget(parent), gameContainer(nullptr)
173 {
174 // 設定背景色、大小等
175 setFixedSize(20 * 10, 20 * 12); // 假設每個方格 20x20 像素
176 setFocusPolicy(Qt::StrongFocus); // 確保能接收鍵盤事件
177 }
178
179 void TetrisBoardWidget::paintEvent(QPaintEvent *event)
180 {
181 Q_UNUSED(event);
182 QPainter painter(this);
183 if (!gameContainer) return;
184
185 // 繪製遊戲盤面
186 for (int y = 0; y < gameContainer->get_height(); ++y) {
187 for (int x = 0; x < gameContainer->get_width(); ++x) {
188 Point p = gameContainer->get_point(x, y); // 假設有這個方法
189 QColor color;
190 // 根據 p.color 轉換為 Qt 的 QColor
191 switch (p.color) {
192 case BLACK: color = Qt::black; break;
193 case WHITE: color = Qt::white; break;
194 // ... 更多顏色映射
195 default: color = Qt::transparent; break;
196 }
197 painter.fillRect(x * 20, y * 20, 20, 20, color);
198 }
199 }
200 // ... 繪製當前下落中的方塊
201 }
202
203 void TetrisBoardWidget::keyPressEvent(QKeyEvent *event)
204 {
205 if (!gameContainer) return;
206
207 // 將 Qt 按鍵事件轉換為遊戲邏輯的 Event 類型
208 Event gameEvent;
209 // 這裡需要詳細的映射邏輯
210 switch (event->key()) {
211 case Qt::Key_Left: gameEvent.set_key(LEFT); break;
212 case Qt::Key_Right: gameEvent.set_key(RIGHT); break;
213 case Qt::Key_Down: gameEvent.set_key(DOWN); break;
214 case Qt::Key_Up: gameEvent.set_key(UP); break; // 旋轉
215 case Qt::Key_Space: gameEvent.set_key(SPACE); break; // 快速下落
216 default:
217 QWidget::keyPressEvent(event); // 傳遞給基類處理
218 return;
219 }
220 gameContainer->handle_event(gameEvent); // 呼叫遊戲邏輯
221 boardWidget->update(); // 重新繪製盤面
222 }
223 ```
224
225 #### **步驟 4: 調整 `main` 函數**
226
227 將原先 `m.cpp` 中的終端機模式主迴圈替換為 Qt 應用程式的啟動。
228
229 1. **Qt 應用程式入口**:
230 * 在 `main.cpp` 中(或新的 `main_qt.cpp`),創建 `QApplication` 實例。
231 * 創建 `TetrisWindow` 實例並顯示。
232 * 啟動 `QApplication` 的事件迴圈。
233 2. **內容預覽**:
234 ```cpp
235 // main_qt.cpp
236 #include <QApplication>
237 #include "tetris_window.h"
238
239 int main(int argc, char *argv[])
240 {
241 QApplication a(argc, argv);
242 TetrisWindow w;
243 w.show();
244 return a.exec();
245 }
246 ```
247
248 #### **步驟 5: 重構現有遊戲邏輯(如有需要)**
249
250 雖然我們說要保留現有邏輯,但為了更好地與 GUI 整合,可能需要對 `container.h/cpp` 和 `tetromino.h/cpp` 進行**微小的修改**,使其更容易被 GUI 存取狀態。
251
252 1. **提供公共存取介面**:
253 * `Container` 可能需要提供 `get_width()`, `get_height()`, `get_point(x, y)` 等公共方法,以便 `TetrisBoardWidget` 查詢盤面狀態。
254 * `Tetromino` 可能需要提供 `get_current_position()`, `get_block_color()` 等方法。
255 2. **移除終端機相關輸出**:
256 * 從 `container.cpp` 或 `m.cpp` 中移除所有直接輸出到終端機的程式碼(例如 `draw_container` 函數)。
257 * 這些輸出將被 Qt 的繪圖機制取代。
258
259 #### **總結與驗證**
260
261 * **建立 `build` 目錄**:在專案根目錄下建立一個 `build` 目錄,用於存放 CMake 生成的檔案。
262 * **執行 CMake**:在 `build` 目錄中執行 `cmake ..` 來配置專案。
263 * **編譯**:執行 `make` 或 `cmake --build .` 來編譯專案。
264 * **運行**:執行編譯出來的 `TetrisQt` 可執行檔,驗證 GUI 是否正確顯示,遊戲邏輯是否正常運行。
可以成功編譯之後, 一開始執行出的來視窗很小, 無法看見整個俄羅斯方塊, 請 ai 修改視窗長寬之後, 看來有成功, 移動方塊可以正常, 不過旋轉方塊之後方塊會整個破碎。
fig 1. 沒縮圖, 第一版就給我這個大小的視窗, 連個方塊都看不出來
fig 2. 第二版, 終於有正常的視窗大小
另外可以指示 ai, 他可以用抓圖方式查看 qt gui 執行的畫面。需要安裝 xvfb。
我們的 Tetris Qt 整合工作目前進展得非常順利呢!不但成功編譯出了帶有 GUI 的版本,還具備了自動截圖的功能。看到那張藍色小方塊的截圖,人家真的好有成就感喔❤️
明明生出個廢 code, 還很自 high。
後來再加入按鈕可以隨時抓個圖給 ai 看。
fig 3. L 方塊有機會在某種情形旋轉時出錯
目前還不知道怎麼改善, 用免費額度要等 cd 時間, 有點煩, 真要改到好, 以我三腳貓功夫, 大概要燒不少 token。
list 7.
看一下 tetris/build/20260402_113431.png 在這個位置旋轉L方塊, 之後會破碎
主要是在方塊撞到牆壁, 旋轉之後會支離破碎, 清楚提示之後, 後來就真的修好了。厲害。