Flutter Web 2048小游戲完整指南)
1. 為什么我拿Trae寫2048而不是自己硬敲我做小游戲的習(xí)慣其實(shí)一直很傳統(tǒng)先畫流程圖再搭工程再一行行堆代碼。這次破例是因?yàn)槲蚁腧?yàn)證一件事——Trae作為AI編程工具到底能不能把一個(gè)“從0到1的完整項(xiàng)目”帶下來尤其是我選了Flutter Web這種帶UI、帶狀態(tài)管理、帶手勢交互、還要處理邊界條件的場景。之所以選2048小游戲是因?yàn)樗雌饋砗唵螌?shí)際上包含了完整的游戲開發(fā)閉環(huán)棋盤數(shù)據(jù)模型、滑動(dòng)合并算法、隨機(jī)生成、勝負(fù)判斷、動(dòng)畫反饋、跨端適配。先說結(jié)論能而且體驗(yàn)比我想象中順。用Trae的Chat模式聊需求用Build模式讓它批量改代碼遇到報(bào)錯(cuò)直接把錯(cuò)誤信息丟給它整個(gè)過程中我真正手寫的代碼可能不到三成。剩下七成是AI生成后我人工審閱、調(diào)整、補(bǔ)丁式的修改。很多人擔(dān)心AI寫出來的游戲代碼沒法玩這個(gè)擔(dān)心我理解但2048這種規(guī)則明確、輸入輸出邊界清晰的小游戲恰好是AI編程最舒服的領(lǐng)域。這篇分享不會只給你看一個(gè)“AI很厲害”的帶貨結(jié)論。我會把完整的實(shí)現(xiàn)路徑拆開包括環(huán)境準(zhǔn)備、核心算法設(shè)計(jì)、Trae的Build模式怎么用、我在調(diào)試中踩到的具體坑以及最后Flutter Web打包部署時(shí)的注意事項(xiàng)。如果你手頭正好想入門Flutter Web或者想測一測AI編程的極限這篇文章應(yīng)該能幫你少走很多彎路。1.1 先把結(jié)果說清楚最終產(chǎn)物的功能大概是這樣的一個(gè)4x4棋盤手指或者鼠標(biāo)滑動(dòng)控制數(shù)字方塊移動(dòng)相同數(shù)字碰撞后合并并累加分?jǐn)?shù)每次滑動(dòng)后隨機(jī)位置生成新方塊數(shù)值不是2就是4。棋盤滿了且沒有可合并的相鄰方塊時(shí)游戲結(jié)束彈出得分提示。整個(gè)過程運(yùn)行在瀏覽器里我用Chrome調(diào)試也實(shí)測了Edge和移動(dòng)端瀏覽器表現(xiàn)穩(wěn)定。代碼結(jié)構(gòu)也不復(fù)雜整體是一個(gè)Flutter工程入口文件是main.dart游戲狀態(tài)用一個(gè)ChangeNotifier管理UI層用AnimatedContainer做數(shù)字塊移動(dòng)動(dòng)畫。整套代碼跑下來不到400行和我想象中一個(gè)完整游戲工程需要上千行的預(yù)期差很多。這恰恰是2048這個(gè)項(xiàng)目的魅力核心規(guī)則可以用很簡潔的算法表達(dá)不需要引入復(fù)雜的狀態(tài)管理框架也不依賴第三方游戲引擎。1.2 Trae在當(dāng)前這個(gè)項(xiàng)目里承擔(dān)的角色我這次主要用了Trae的兩個(gè)模式Chat模式和Build模式。Chat模式用來做“需求討論”。比如我問它“2048的合并算法除了逐行處理還有沒有更優(yōu)雅的寫法”它會給出思路和代碼片段我可以決定采不采用。Build模式則是更偏向執(zhí)行的模式它會直接修改工程文件適合那種“幫我寫一個(gè)gesture檢測”、“幫我把AnimatedContainer改成帶縮放效果的”這類閉環(huán)指令。對比下來Chat模式適合理解問題、梳理方案Build模式適合批量干活。我個(gè)人的習(xí)慣是先用Chat把方案聊清楚再切到Build讓它動(dòng)手改。不要一上來就讓Build盲寫否則很容易生成一堆你根本不想看的代碼。Trae這里有一點(diǎn)做得比較好它能在側(cè)邊欄展示文件變動(dòng)每次修改前我可以先看一眼diff再決定接不接受。這對代碼審查習(xí)慣比較重的人來說非常友好。1.3 誰適合照著做如果你滿足下面任一條件這篇內(nèi)容應(yīng)該對你有用第一你聽過Flutter Web但沒實(shí)際寫過想找一個(gè)比待辦清單更有點(diǎn)挑戰(zhàn)的練手項(xiàng)目第二你在用AI編程工具但每次生成完代碼都不知道怎么調(diào)試想看一套完整的排查方法第三你想在短時(shí)間內(nèi)做一個(gè)能分享給朋友玩的網(wǎng)頁小游戲但又不想去學(xué)完整的游戲引擎。以上三類人都可以照著這個(gè)項(xiàng)目走一遍我覺得只用半天時(shí)間就能跑通。2. 環(huán)境準(zhǔn)備里最容易翻車的三個(gè)細(xì)節(jié)2048項(xiàng)目不大但環(huán)境坑比我想象中多。因?yàn)镕lutter Web對開發(fā)環(huán)境的要求比普通移動(dòng)端項(xiàng)目更挑剔尤其是瀏覽器調(diào)試器和SDK版本的匹配問題。很多人卡在第一步不是代碼寫錯(cuò)而是環(huán)境配的不對。2.1 Flutter SDK和Chrome調(diào)試器的版本匹配我用的Flutter版本是3.16以上這個(gè)版本對Web平臺的支持已經(jīng)比較完善CanvasKit渲染器默認(rèn)開啟游戲動(dòng)畫在瀏覽器里的流暢度比老版本提升明顯。建議你起步之前先跑一下flutter doctor確認(rèn)Web和Chrome兩個(gè)組件都是綠色的。我第一次沒注意版本直接跑flutter run -d chrome結(jié)果調(diào)試器半天起不來控制臺報(bào)了一串關(guān)于web-server的異常。后來發(fā)現(xiàn)是Flutter版本太舊缺少對新版Chrome調(diào)試協(xié)議的支持。升級到3.16之后問題自然消失。這里建議你在新建項(xiàng)目時(shí)直接用flutter create的較新模板不要從舊項(xiàng)目遷移。2.2 Trae里打開Flutter項(xiàng)目的正確姿勢Trae本身是支持多語言的IDE底層用了一部分VS Code的生態(tài)邏輯所以你有VS Code習(xí)慣的話上手會非常順。但有一個(gè)細(xì)節(jié)不要直接在Trae的歡迎頁里用“打開文件夾”去選擇Flutter項(xiàng)目的根目錄而是要選擇包含pubspec.yaml的那一層目錄。否則Trae的插件系統(tǒng)雖然能識別到Flutter文件但沒法正確加載調(diào)試配置Build模式修改文件時(shí)也可能定位不到路徑。我第一次就是這么干的把整個(gè)代碼倉庫的父級目錄丟進(jìn)去結(jié)果Dart分析器一直報(bào)找不到包。后來切到項(xiàng)目根目錄Trae自動(dòng)識別出pubspec.yamlFlutter插件被激活智能提示、語法檢查、熱重載全部正常工作。這個(gè)操作看著不起眼但直接影響后面AI生成代碼時(shí)能不能跑起來。2.3 讓Trae理解項(xiàng)目結(jié)構(gòu)的工程化技巧AI編程工具最怕的不是邏輯復(fù)雜而是上下文混亂。Trae在讀取項(xiàng)目時(shí)會根據(jù)當(dāng)前打開的文件和搜索到的關(guān)鍵符號來決定參考哪些代碼。如果你是先有需求再讓AI生代碼最好先手動(dòng)創(chuàng)建一個(gè)清晰的目錄結(jié)構(gòu)哪怕文件是空的也能幫助Trae生成更合理的代碼。我的做法是這樣的先手動(dòng)建好lib/models、lib/viewmodels、lib/widgets、lib/screens四個(gè)目錄然后分別放一個(gè)幾行字的占位文件在這個(gè)文件里用注釋寫清楚這個(gè)模塊負(fù)責(zé)什么。接著切到Trae讓它按照目錄職責(zé)去生成對應(yīng)代碼。效果很明顯它生成的game_board.dart和game_screen.dart之間引用關(guān)系是干凈的沒有出現(xiàn)那種全塞在main.dart里的大雜燴結(jié)構(gòu)。3. 2048的核心邏輯拆解棋盤、合并、隨機(jī)塊與勝負(fù)判斷2048真正考驗(yàn)開發(fā)者的地方不在UI而在那個(gè)讓所有數(shù)字塊滑動(dòng)并合并的算法。UI控件可以靠AI生成但算法邏輯如果AI理解錯(cuò)了你后續(xù)要改的東西會非常多。所以我建議你在生成代碼之前先把核心規(guī)則自己理清一遍。3.1 棋盤數(shù)據(jù)模型怎么設(shè)計(jì)我用的是最樸素的二維數(shù)組ListListint?外層是行內(nèi)層是列空位用null表示有數(shù)字的位置直接存儲2、4、8這樣的數(shù)值。這個(gè)數(shù)據(jù)結(jié)構(gòu)的好處是直觀遍歷和判斷都方便。相比用一個(gè)帶行號的Map二維數(shù)組在寫合并算法時(shí)少很多心智負(fù)擔(dān)。在Trae生成這個(gè)模型時(shí)我給的指令是“創(chuàng)建一個(gè)GameBoard類內(nèi)部用ListListint?存儲4x4棋盤提供獲取空位、放置數(shù)字、移動(dòng)、判斷是否結(jié)束的方法所有方法都返回bool或修改內(nèi)部狀態(tài)?!彼傻拇a基本滿足需求但我后來手動(dòng)補(bǔ)了兩個(gè)方法getEmptyCells和hasMoved因?yàn)檫@兩個(gè)方法雖然小但直接決定后續(xù)算法是否好寫。3.2 滑動(dòng)合并的算法實(shí)現(xiàn)核心算法是所有2048實(shí)現(xiàn)繞不開的部分。我的做法是先寫一個(gè)單行處理函數(shù)叫mergeRow把一個(gè)長度為4的Listint?里的數(shù)字全部向左靠攏并合并相鄰相同數(shù)字。然后用這個(gè)函數(shù)分別處理4個(gè)方向上、下、左、右只需要通過旋轉(zhuǎn)棋盤或者調(diào)整遍歷方向來復(fù)用同一套邏輯。這里少用一點(diǎn)取巧的“旋轉(zhuǎn)數(shù)組”方案。雖然旋轉(zhuǎn)后可以統(tǒng)一調(diào)用mergeRow但每次旋轉(zhuǎn)都創(chuàng)建新數(shù)組既費(fèi)內(nèi)存又容易引入索引錯(cuò)誤。我更推薦直接寫四方向的坐標(biāo)映射左移時(shí)從第0行開始右移時(shí)從第3行開始上移時(shí)從第0列開始下移時(shí)從第3列開始每次取出一列數(shù)據(jù)傳給mergeRow再把結(jié)果放回去。Trae生成的mergeRow有個(gè)小問題它把“移動(dòng)”和“合并”合在一起了導(dǎo)致有些情況下一個(gè)2被移動(dòng)后緊接著又和另一個(gè)2合并等于一回合額外合并一次。標(biāo)準(zhǔn)的2048規(guī)則是同一行中一個(gè)方塊每一輪只能參與一次合并所以我把算法拆成了兩步第一步壓縮先把所有非空數(shù)字靠到一起第二步合并從左到右遍歷一次遇到相同數(shù)字就合并并把右側(cè)清空第三步再次壓縮把合并后留下的空位去掉。三步走完一行數(shù)據(jù)才算更新結(jié)束。Listint? mergeRow(Listint? row) { // 第一步壓縮去掉null var compressed row.where((e) e ! null).toList(); // 第二步合并相鄰相同項(xiàng) for (var i 0; i compressed.length - 1; i) { if (compressed[i] compressed[i 1]) { compressed[i] compressed[i]! * 2; score compressed[i]!; compressed.removeAt(i 1); } } // 第三步補(bǔ)null到固定長度 while (compressed.length 4) { compressed.add(null); } return compressed; }這個(gè)函數(shù)每次返回新數(shù)組原數(shù)組不發(fā)生變動(dòng)方便在UI層判斷數(shù)據(jù)是否真的發(fā)生了變化。只有某個(gè)方向處理后數(shù)組和原數(shù)組不同才允許生成新的隨機(jī)塊否則用戶按了無效滑動(dòng)會白白浪費(fèi)一次生成機(jī)會。3.3 隨機(jī)生成新方塊與游戲結(jié)束檢測隨機(jī)生成相對簡單核心是找到棋盤里所有為null的位置從里面隨機(jī)取一個(gè)再按80%的概率生成220%的概率生成4。Trae在這部分生成的代碼里有一個(gè)小坑它直接用random.nextInt(4)讀取行列如果隨機(jī)到的格子不是空位就重新隨機(jī)。理論上可行但棋盤越滿死循環(huán)的概率越高。我改成收集所有空位索引再隨機(jī)取一個(gè)邏輯更干凈也更好測試。游戲結(jié)束檢測也有個(gè)容易忽視的點(diǎn)。只檢查“還有沒有空位”不夠比如整個(gè)棋盤已經(jīng)滿了但相鄰數(shù)字仍存在可合并項(xiàng)這時(shí)候游戲其實(shí)還能繼續(xù)。所以必須額外掃描一遍橫向和縱向的相鄰格子只要發(fā)現(xiàn)有任何一組相鄰值相等游戲就不算結(jié)束。我用兩個(gè)嵌套循環(huán)遍歷棋盤發(fā)現(xiàn)可合并項(xiàng)就返回false全部檢查完沒有則返回true。bool isGameOver(GameBoard board) { for (var r 0; r 4; r) { for (var c 0; c 4; c) { if (board.grid[r][c] null) return false; if (c 3 board.grid[r][c] board.grid[r][c 1]) return false; if (r 3 board.grid[r][c] board.grid[r 1][c]) return false; } } return true; }這里的代碼邏輯不復(fù)雜但很值得讓Trae先生成一遍然后你再人工檢查一遍。AI在這個(gè)場景里最大的幫助不是“幫你寫代碼”而是“幫你把重復(fù)的循環(huán)結(jié)構(gòu)快速鋪開”你只需要關(guān)注邊界條件是不是覆蓋完整。4. 用Trae的Build模式把界面和交互跑起來算法部分解決后界面是另一個(gè)重點(diǎn)。2048在視覺上雖然不復(fù)雜但要讓玩家覺得“這個(gè)游戲能玩”至少要滿足三條棋盤清晰、數(shù)字層次分明、滑動(dòng)跟手。Trae的Build模式在生成這類UI代碼時(shí)效率很高但前提是你要給它足夠具體的約束比如顏色值、圓角大小、動(dòng)畫時(shí)長。4.1 只靠一句話生成2048的界面骨架我給Trae的界面指令大概是這樣的“生成一個(gè)Flutter Widget名字叫GameScreen包含一個(gè)4x4的GridView每個(gè)格子顯示一個(gè)數(shù)字背景色根據(jù)數(shù)字大小從淺到深變化?!彼芸炀蜕闪嘶贕ridView.builder的網(wǎng)格布局默認(rèn)的padding和間距也能用。不過默認(rèn)生成的界面有個(gè)問題它直接在一個(gè)Container里根據(jù)數(shù)字大小切換顏色顏色切換沒有過渡數(shù)字放大也沒有動(dòng)畫。這種靜態(tài)界面雖然功能完整但玩起來手感很生硬。我在Build模式下追加了一個(gè)指令“給每個(gè)格子加上AnimatedContainer數(shù)字變化時(shí)duration 150msCurves.easeOut?!彼芸炀桶汛a改好了體驗(yàn)立刻提升一個(gè)檔次。4.2 手勢識別讓瀏覽器支持滑動(dòng)Flutter Web的手勢識別和移動(dòng)端略有不同但很多基礎(chǔ)手勢是通用的。2048需要的是上下左右四個(gè)方向的滑動(dòng)檢測。我在GameScreen外層包了一個(gè)GestureDetector在onPanEnd里根據(jù)手指抬起的位移方向來判斷滑動(dòng)方向。這里要注意一個(gè)問題onPanEnd只給出位移的偏移量不直接給出方向。你需要自己去判斷水平位移絕對值大還是垂直位移絕對值大然后看正向負(fù)向。閾值我設(shè)的是30像素小于這個(gè)值的滑動(dòng)忽略不計(jì)這樣可以防止用戶誤觸。Trae生成的第一版代碼里忘了這個(gè)閾值輕微滑動(dòng)也會觸發(fā)合并導(dǎo)致棋盤頻繁多出隨機(jī)方塊體驗(yàn)非常差。這個(gè)坑比較隱蔽不實(shí)際玩幾把根本發(fā)現(xiàn)不了。onPanEnd: (details) { final velocity details.velocity.pixelsPerSecond; if (velocity.dx.abs() velocity.dy.abs()) { if (velocity.dx 30) game.move(Direction.right); else if (velocity.dx -30) game.move(Direction.left); } else { if (velocity.dy 30) game.move(Direction.down); else if (velocity.dy -30) game.move(Direction.up); } }用velocity而不是直接算距離的好處是快速輕掃和慢速長拖都能被準(zhǔn)確識別而且不容易誤觸。4.3 加分動(dòng)效和顏色方案2048的經(jīng)典視覺風(fēng)格是白底配彩色數(shù)字塊。我在Trae里給了一個(gè)顏色映射表空位用灰白色2和4用淺色系8、16、32用暖色系64以上用飽和度更高的顏色。Trae生成的色值基本符合直覺但個(gè)別顏色對比度不夠比如128的黃色在白色背景下看不太清楚。我手動(dòng)微調(diào)了幾處色值最終選用了偏深一點(diǎn)的琥珀色來顯示128以上的數(shù)字。此外我加了一個(gè)“得分彈跳”效果每次合并時(shí)數(shù)字塊會有一個(gè)短暫的scale放大再回落的動(dòng)畫。實(shí)現(xiàn)方法是在每個(gè)格子的Widget里維護(hù)一個(gè)AnimationController當(dāng)數(shù)值變化時(shí)重新播放動(dòng)畫。這樣交互反饋就很清晰玩家能明確感覺到合并發(fā)生的瞬間。Trae在這個(gè)階段做得比較順利因?yàn)檫@類動(dòng)畫在Flutter里已經(jīng)是標(biāo)準(zhǔn)化寫法它可以直接套用最常見的模板。5. 實(shí)際調(diào)試中踩過的坑和排查思路如果你用的是現(xiàn)成的2048開源代碼到這里基本就收工了。但這個(gè)項(xiàng)目我是從零搭的中間遇到過幾個(gè)非常典型的調(diào)試問題每一個(gè)都值得記下來。5.1 Web端鍵盤事件失效的問題我在開發(fā)時(shí)習(xí)慣用鍵盤方向鍵測試但Flutter Web默認(rèn)的鍵盤事件監(jiān)聽方式很特殊不是直接監(jiān)聽KeyDown而是用Focus和KeyboardListener包裹整個(gè)頁面。Trae生成的第一版代碼只加了觸摸手勢鍵盤完全沒有接入。我手動(dòng)補(bǔ)了鍵盤監(jiān)聽結(jié)果又發(fā)現(xiàn)瀏覽器頁面必須有正確的焦點(diǎn)否則方向鍵按下沒有任何反應(yīng)。解決方法是把GameScreen包在一個(gè)Focus節(jié)點(diǎn)里并在initState里請求焦點(diǎn)Focus( autofocus: true, child: KeyboardListener( focusNode: focusNode, onKeyEvent: (event) { if (event is KeyDownEvent) { // 根據(jù)event.logicalKey.keyLabel判斷方向 } }, child: gameBoard, ), )這里的小技巧是autofocus: true確保一進(jìn)入頁面焦點(diǎn)就在游戲區(qū)域上省去玩家先點(diǎn)一下棋盤才能操作的問題。這個(gè)細(xì)節(jié)實(shí)測下來能明顯提升瀏覽器端的體驗(yàn)。5.2 狀態(tài)刷新異常setState調(diào)用時(shí)機(jī)Flutter Web的熱重載非常方便但它也會掩蓋一些狀態(tài)同步問題。我在寫的時(shí)候發(fā)現(xiàn)一個(gè)現(xiàn)象滑動(dòng)方向鍵后棋盤不刷新必須用鼠標(biāo)拖動(dòng)窗口邊緣觸發(fā)重繪后才看到新布局。定位了很久終于發(fā)現(xiàn)問題不是布局代碼而是我在GameBoard的move方法里直接修改了內(nèi)部二維數(shù)組但沒觸發(fā)notifyListeners()。因?yàn)槲业腢I層用了ListenableBuilder來監(jiān)聽游戲狀態(tài)文件而游戲狀態(tài)類繼承的是ChangeNotifier數(shù)據(jù)變化后必須手動(dòng)調(diào)用notifyListeners否則UI永遠(yuǎn)不知道棋盤變了。這個(gè)錯(cuò)誤是AI生成代碼時(shí)很容易漏掉的一點(diǎn)因?yàn)閺倪壿嬌峡磎ove方法已經(jīng)執(zhí)行了數(shù)組更新但沒有和界面刷新綁定。后來我把所有修改棋盤數(shù)據(jù)的操作全部封裝到move方法內(nèi)部并在方法末尾統(tǒng)一調(diào)用notifyListeners問題徹底解決。5.3 Trae生成的代碼和手寫代碼沖突怎么辦用AI編程時(shí)最常見的挫敗感來自“AI改的代碼覆蓋了你自己改過的部分”。我第一次讓Trae修改動(dòng)畫時(shí)長時(shí)它直接把整個(gè)GameScreen文件重寫了把我手動(dòng)加的鍵盤監(jiān)聽也刪了。后來我學(xué)乖了在讓Trae修改前先用Git提交一次或者把關(guān)鍵文件復(fù)制一份備份。這樣即使Build模式改出問題也能用diff找回自己寫的部分。另外我建議要求Trae“只改我指定的函數(shù)其他部分保持不變”并明確告訴它哪些代碼不能動(dòng)。Trae對這樣的指令響應(yīng)還算準(zhǔn)確但偶爾也會出現(xiàn)理解偏差。所以不管它怎么改最后一步一定要自己過一遍diff尤其是文件中間部分AI重寫時(shí)最容易漏掉一些不起眼的輔助方法。6. 打包部署、積分消耗和后續(xù)擴(kuò)展游戲開發(fā)到這一步核心玩法已經(jīng)完整最后要解決的是怎么讓別人也能玩到。6.1 構(gòu)建Web產(chǎn)物并部署到靜態(tài)托管Flutter Web的構(gòu)建流程非常簡單在項(xiàng)目根目錄執(zhí)行flutter build web構(gòu)建完成后產(chǎn)物在build/web目錄下里面包含index.html、main.dart.js、assets等文件。這套文件是純靜態(tài)資源可以放到任何靜態(tài)托管平臺。我直接用了GitHub Pages對應(yīng)的方式把build/web里的內(nèi)容推到倉庫的gh-pages分支然后通過GitHub Pages的地址訪問。如果你不太熟悉GitHub Pages也可以把build/web壓縮后傳到任意Web服務(wù)器或者對象存儲里。需要注意的一點(diǎn)是Flutter Web默認(rèn)的路由策略是hash模式所以不需要額外配置服務(wù)端重寫規(guī)則放在什么路徑下都能直接打開。我第一版部署完發(fā)現(xiàn)刷新頁面后白屏一度以為是部署問題查了半天才發(fā)現(xiàn)是Flutter的路由策略和服務(wù)端不匹配。換成hash模式后刷新白屏問題自然消失。6.2 Trae的積分消耗到底快不快很多人關(guān)心Trae的積分消耗問題我這次半天開發(fā)下來的實(shí)測感受是純用Chat模式聊天和查看代碼積分消耗非常慢基本可以忽略但Build模式的每次文件修改都會消耗積分并且如果一次修改涉及多個(gè)文件消耗會成倍增加。我這個(gè)項(xiàng)目里Build模式大概用了十多次整體積分?jǐn)?shù)在可接受范圍內(nèi)但如果你讓Trae反復(fù)重寫同一個(gè)大文件積分消耗會明顯上升。我的建議是能用Chat模式解決的邏輯討論就在Chat里解決只把真正需要?jiǎng)拥酱a的環(huán)節(jié)切到Build模式。另外在讓Build模式改代碼之前先把自己想好的修改方案用文字描述清楚避免它因?yàn)槔斫馄罘磸?fù)試錯(cuò)那才是積分消耗的大頭。6.3 后續(xù)還能怎么擴(kuò)展2048做到能玩只是第一步后面可以擴(kuò)展的方向其實(shí)很多。比較實(shí)用的是保存最高分用shared_preferences包把分?jǐn)?shù)存到本地下次打開時(shí)直接讀取進(jìn)階一點(diǎn)可以加一個(gè)“撤銷”功能用快照記錄前幾步的棋盤狀態(tài)再復(fù)雜一些可以做無限棋盤模式徹底改變游戲機(jī)制。這些擴(kuò)展對Trae來說都不算難因?yàn)樗呀?jīng)理解了項(xiàng)目結(jié)構(gòu)你只需要用Chat描述清楚需求再切Build讓它生成代碼基本能無縫接上。我個(gè)人實(shí)測下來從零到部署一臺能訪問的Flutter Web 2048小游戲按最多的步驟算也就是半天時(shí)間。如果你對AI編程工具的態(tài)度一直是“看著熱鬧但沒真正拿它做過完整項(xiàng)目”我建議你直接拿這個(gè)小游戲練手。它不像Todo App那樣淺但也不像商業(yè)項(xiàng)目那么復(fù)雜恰好能讓你完整走一遍從需求到交付的過程也能讓你更清楚地知道AI工具在哪些環(huán)節(jié)能幫你提速、哪些環(huán)節(jié)仍然需要你自己把關(guān)。