EGE示例程序——2048
EGE專欄:EGE專欄
目錄
- 一、程序源碼下載
- 1. 百度網(wǎng)盤(示例二 2048)
- 2. CSDN(示例二 2048)
- 二、2048 游戲介紹
- 三、EGE制作的2048游戲界面
- 四、制作流程
- 1. 素材獲取
- 2. 素材整理
- 3. 界面設計
- 4. 功能分析
- 五、最基本的實現(xiàn)
- 1. 游戲設計
- 1.1 數(shù)據(jù)表示
- 1.2 圖像加載
- 1.3 四方向移動
- 1.3.1 按鍵控制移動方向
- 1.3.2 移動
- 1.4 添加隨機數(shù)
- 2. 基礎(chǔ)版程序
- 六、完整功能版實現(xiàn)
- 1. 重新開始
- 2. 添加音效
- 3. 計分
- 4. 自動存檔,讀檔
- 5. 游戲結(jié)束判斷
- 6. 添加移動動畫(難點)
- 七、完整版代碼
一、程序源碼下載
1. 百度網(wǎng)盤(示例二 2048)
https://pan.baidu.com/s/1BUDGLeenbIxpAfqd1XfNqg
??源代碼分享于 百度網(wǎng)盤,里面也有程序用到的資源文件,可以查看下載。
2. CSDN(示例二 2048)
https://download.csdn.net/download/qq_39151563/12154829
??CSDN資源,設定為0積分,無需積分即可下載。
二、2048 游戲介紹
2048在線游戲鏈接: https://2048game.com/
2048 游戲規(guī)則
2048小游戲相信應該都玩過,規(guī)則很簡單,上面也貼出了在線游戲鏈接,可以先玩一玩,了解一下游戲規(guī)則。
- 游戲主體為 4×44\times 44×4 的格子,每個小格子可以放置一個方塊,方塊帶有一個數(shù)字。
- 游戲一開始隨機出現(xiàn)兩個數(shù)字方塊,數(shù)字為2或4。
- 方塊可以 上下左右 四個方向滑動。滑動后,同方向上兩個相同的相鄰方塊會合并成更大的方塊,合成后的方塊數(shù)字是原來兩個方塊之和, 并且在一次滑動中,一個方塊只能和相同的方塊合成一次,不能連續(xù)合成,且在移動方向前面的兩個優(yōu)先合成。
- 每滑動一次,會在隨機的一個空格中生成一個數(shù)字(2 或4, 大多是2)
- 如果16個格子都填滿且無法滑動,則游戲結(jié)束。
計分規(guī)則
??每合成一個方塊加進行加分, 分值 = 合成的方塊上的數(shù)字。
三、EGE制作的2048游戲界面
??下面EGE制作的2048界面
四、制作流程
1. 素材獲取
??網(wǎng)上有很多 Android 手機的游戲,想要相關(guān)的圖片和音樂資源的可以先下載應用安裝包,然后用 apktool 解析資源文件,得到里面的素材。(apktool 下載使用方法可以自行百度)
??示例程序鏈接中也放有我從一款2048游戲中解析得到的素材。(這款游戲一大堆廣告,各種彈窗,真的**)
2. 素材整理
??一般別人做的游戲都比較花里胡哨的,東西很多,可以從中挑選一些自己用到的。有些圖片尺寸不對,可以自行用PS, 或者其它的圖像處理軟件縮放一下,調(diào)成合適的尺寸, 并且改成合適文件名(可以在用到的時候再選取,并取好點的文件名)。
?? 下面則是我從中挑出的所需要的圖片和音樂素材, 并且對圖片縮放過,以適配界面尺寸。
素材鏈接:https://download.csdn.net/download/qq_39151563/12154829
3. 界面設計
??這時候考慮窗口的大小,界面的布局,在哪里顯示什么內(nèi)容,界面跳轉(zhuǎn)等等。
??再來說一下筆記本顯示比例的問題,我筆記本是125%放大顯示,估計一般的筆記本都是這樣。因為對于筆記本來說,設置為100%的顯示比例的時候,界面上的文字和圖標真的很小。
??所以如果設置EGE窗口是 500x500, 那么你將窗口截屏,會發(fā)現(xiàn)截下來的圖片,分辨率約為 625 * 625。如果你看到屏幕上某個尺寸挺合適,截圖下來后查看分辨率,需要除以縮放比例才能得到原圖的大小,以這樣的大小繪制,才會得到想要的尺寸。
?? 根據(jù)素材所設計的界面布局(參考上面在線2048的布局)
4. 功能分析
基本實現(xiàn)
- 4×44\times 44×4 方格
- 四方向移動,出現(xiàn)數(shù)字的組合(難點)
- 移動后隨機在一個空格中出現(xiàn)2或4
附加項
- 計分,只出現(xiàn)在合并時 (自娛自樂,沒有也行)
- 游戲結(jié)束判斷:無法移動(16個格子滿,且相鄰格子都不同)
- 移動動畫(難點)
- 游戲音效
- 游戲自動存檔讀檔 (常用)
- 游戲重新開始 (必備)
五、最基本的實現(xiàn)
??即僅僅實現(xiàn)基礎(chǔ)功能:在 4×44\times 44×4 格子中移動合并數(shù)字,并隨機出現(xiàn)數(shù)字,游戲界面只有4x4個格子。不計分,不作游戲結(jié)束判斷,不存檔,無動畫效果,無音效。
1. 游戲設計
1.1 數(shù)據(jù)表示
??4x4 格子用二維數(shù)組表示即可。
int grid[4][4];??由于方塊上數(shù)字是 2,4,8,16,...2, 4, 8, 16,...2,4,8,16,...,是222的nnn次方, 所以可以考慮存儲 nnn 即可, 這樣方便編號,特別是圖片, 編號 nnn從111到171717,分別對應數(shù)字2n\ 2^n?2n ,空格用000表示。因為元素就是編號,所以繪圖時直接根據(jù)元素繪制即可。
為什么是1到17?
??因為隨機出現(xiàn)的數(shù)字最大是4, 即 2 的2次方,從4開始,一共能排上16個數(shù)字,加上2,那么一共17個數(shù)字,即可能出現(xiàn)的最大數(shù)字是 217=1310722^{17} = 131072217=131072。(牛逼牛逼,131072只存在于理論上吧)
??如下圖所示:
??上圖每個方塊所對應的存儲數(shù)據(jù)分別為:
??即如果方塊的值為 2n2^n2n,那么存儲的數(shù)值為 nnn。
1.2 圖像加載
??既然數(shù)字 222 到 131072131072131072, 分別對應編號 111 到 171717, 那么圖片數(shù)據(jù)用長度為 181818 的 PIMAGE 數(shù)組 blockImgs[] 存儲即可, 方塊 2n2^{n}2n 的圖片就存儲于blockImgs[n],方塊 2,4,?,1310722, 4, \cdots, 1310722,4,?,131072 分別存儲于 blockImgs[1], blockImgs[2], … , blockImgs[17], 一共18個,blockImgs[0]不使用。另外還需要存儲一張 4×44\times 44×4 格子的背景圖。
#define NUM_BLOCK 18 PIMAGE blockImgs[NUM_BLOCK]; PIMAGE backgroundImg;??數(shù)字對應的圖片名字命名格式為 "block_數(shù)字", 存放于"resource\\\\image" 文件夾中。
??圖片可以使用如下方式獲取:(利用sprintf()生成文件名字符串)
1.3 四方向移動
??這是整個游戲最核心的部分。
??首先移動需要檢測按鍵,常用 AWDS 和四個方向鍵。
1.3.1 按鍵控制移動方向
??只需要一個變量 direct來記錄移動的方向。
??數(shù)值 0~3 分別對應:左、上、右、下,這個可以用枚舉,宏等進行定義,含義更清晰。
??讀取后,如果不等于-1,說明按下了代表方向的按鍵。后面就根據(jù)direction的值進行移動。
1.3.2 移動
??移動時需要根據(jù)移動方向來檢測數(shù)字,向左移動,那么就要對每一行,從左往右檢測,即移動方向和檢測方向是相反的,因為在前面的會優(yōu)先合成。
??下圖中的左邊部分即為左移時的檢測順序,包含初始位置,下一個元素的位置偏移以及下一行(列)的位置偏移。
??四個方向,區(qū)別就是檢測起點不同,檢測方向不同,由此可以根據(jù)四個方向,得到這些數(shù)據(jù)。
??(x0, y0)為檢測起點坐標,firstOffset為當前行(列)中下一個元素的坐標偏移量, secondOffset 為下一行(列)的坐標偏移量。
??所以,對于移動方向direction, 按檢測順序遍歷每個元素則為:
for (int i = 0; i < 4; i++) {//計算每一行(列)起點位置坐標int x = x0[direction] + i * lineOffset[direction][0];int y = y0[direction] + i * lineOffset[direction][1];for (int j = 0; j < 4; j++) {//這里可以檢測元素grid[y][x];//移動至下一個元素x += elemOffset[direction][0];y += elemOffset[direction][1];} }??這樣,就完成了四個方向遍歷的統(tǒng)一。
合并問題變換:
??這個問題變換為:一個長度為n的數(shù)組a,向下標為0的方向移動,忽略值為0的元素,相鄰并且相同的元素將合并成一個值為兩數(shù)之和的元素,并且每個元素只能參與一次合并,多個相同的元素相鄰時,下標小的優(yōu)先合并。
方法一:
??因為忽略值為0的元素,所以可以用right表示遍歷到的非零元素,0 ~ left - 1 為左邊完成移動合并的元素。left 代表可能將要移動到的空位或可能參與下一次合并的元素。
方法二:
??直接忽略元素0,只在非零元素間判斷,相同則合并,處理完成后,元素中間可能會夾雜許多0,這時再次遍歷,像刪掉字符串中的某個字符一樣,除去元素中間的0。
??算法已經(jīng)實現(xiàn),然后回到二維數(shù)組,分別對每一行或每一列進行合并即可。于是得到下面的移動算法:(使用的是方法一)
??move() 函數(shù)返回是否有格子發(fā)生的變動,這樣可以根據(jù)是否進行元素移動或合并來決定需不需要添加一個隨機數(shù)。如果返回 false,即格子沒有變動,那么這次移動是無效動作,不需要添加隨機數(shù)。
??emptyBlocks 表示當前的空格數(shù),代碼中根據(jù)這個值來判斷是否還能添加隨機數(shù)的。每產(chǎn)生一次合并,方塊數(shù)會少1,所以空格數(shù)加1。
1.4 添加隨機數(shù)
??根據(jù)空格數(shù) emptyBlock, 生成一個000到 empty-1 之間的隨機數(shù)randEmptyBlock ,然后查找第randEmptyBlock個空格(從0開始編號), 往這個空格里添加一個2或4的方塊,出現(xiàn)2的概率應該是大于4的,出現(xiàn)4的情況很少, 這里取 0.90.90.9 的概率出現(xiàn)數(shù)字 222, 0.10.10.1 的概率出現(xiàn)數(shù)字 444。參數(shù) n 為添加的隨機數(shù)個數(shù),添加后,空格數(shù) emptyBlock 減少 n,在這個過程中,也要判斷空格數(shù)是否大于0,沒有空格后就無法添加隨機數(shù),函數(shù)返回。
void addRandomNum(int n) {while ((emptyBlock > 0) && (n-- > 0)) {int randEmptyBlock = rand() % emptyBlock; // 隨機選取一個空格int i = 0, count = 0;int* gridList = &grid[0][0];// 對數(shù)組進行遍歷,查找對應的空格(空格從0開始編號)for (i = 0; i < 4 * 4; i++) {if ((gridList[i] == 0) && (count++ == randEmptyBlock))break;}//隨機數(shù)字2或4,0.9概率是1,0.1概率是2gridList[i] = (rand() % 10 < 1) ? 2 : 1;emptyBlock--;} }2. 基礎(chǔ)版程序
??綜合上面,得到下面的代碼, 共160行。
??圖片放在 “./resource/image” 目錄,并且數(shù)字圖片命名格式為 “block_數(shù)字.png”, 背景圖片命名為 “background.png”。
程序界面截圖
六、完整功能版實現(xiàn)
使用的素材
界面展示
1. 重新開始
重新開始按鈕
??在圖中添加了重新開始按鈕,當檢測到鼠標左鍵點擊時,就判斷點擊位置是否在區(qū)域內(nèi)。
??下面代碼為判斷點擊位置是否在按鈕區(qū)域,因為只有一個按鈕,所以直接取了固定值。
??鼠標消息處理,判斷是否有鼠標點擊
//鼠標點擊檢測 bool leftClick = false; while (mousemsg()) {mouse_msg mouseMsg = getmouse();if (mouseMsg.is_left() && mouseMsg.is_down()) { //左鍵按下leftClick = true;xClick = mouseMsg.x;yClick = mouseMsg.y;} }??點擊按鈕后標記需要重新開始。
// 重新開始按鈕的點擊判斷 if (leftClick && clickBtnRestart(xClick, yClick)) {restartGameFlag = true; }游戲結(jié)束后按回車鍵
??在游戲結(jié)束后,可以直接按回車鍵重新開始,不需要用鼠標。游戲沒有結(jié)束時,防止誤碰,不對回車鍵響應。
重新開始所需要做的工作
??重新開始需要把格子清零,空格數(shù)emptyBlock 設置為16,本局分數(shù)清零,還有結(jié)束標記 gameOver 清零,做好后,再做一些其它相關(guān)的操作。
2. 添加音效
(音效可以不添加,因為EGE播放音樂會有點卡頓,影響流暢度)
??音效的添加很簡單,先用MUSIC類打開音樂文件,然后在合適的時候調(diào)用Music.Play(0) 播放即可,因為Music.Play()中插了一個延時,動畫會出現(xiàn)一幀的卡頓,如果是在移動動畫中播放,延時一幀是可以感知到的卡頓,稍稍有點不流暢。所以可以看情況,決定要不要放音樂。
??選擇了開始時和合并時播放音樂。
合并音效
??合并是在 move() 函數(shù)中檢測的,用mergeMusic_flag 標記是否有合并。目前是設置為不在播放狀態(tài)時才重新播放音效。這樣的話如果音效放到一半又有其它方塊合并,則并不會再次播放。
開始音效
??剛打開 和 重新開始 時播放
3. 計分
??分數(shù)分為 最高分數(shù),當前分數(shù),最大合成數(shù)字
??因為計分是出現(xiàn)在合并的時候,所以在move() 函數(shù)中加入。
??合并后計分,分值為合成的數(shù)字,同時更新最高分、最大合成數(shù)字。
4. 自動存檔,讀檔
數(shù)據(jù)文件名
const char* recordFile = "game2048Record.txt";讀檔
??不能因為沒有記錄文件就無法運行,因為程序的運行不需要依賴記錄文件。如果沒有記錄文件,那就自己初始化數(shù)據(jù),重新開始。一開始沒有運行過的游戲,哪來的記錄文件呢?記錄應該由程序自己生成,而不是自己手動添加。
??如果有記錄文件,就讀取記錄,并且要適當?shù)貦z查記錄數(shù)據(jù)的正確性。
存檔
??因為需要退出游戲后保存記錄,所以初始化模式需要添加INIT_NOFORCEEXIT ,即關(guān)閉窗口后不強制結(jié)束程序,以便進行游戲保存工作
??為了方便看到保存的游戲數(shù)據(jù),所以設置成文本文件格式保存。
5. 游戲結(jié)束判斷
??游戲結(jié)束,那必定是在出現(xiàn)隨機數(shù)后,或者一開始讀取的記錄就是已經(jīng)結(jié)束的數(shù)據(jù)。
??當空格數(shù) emptyBlock 為 0,并且不存在相鄰的格子相同的情況,即為游戲結(jié)束,此時結(jié)束標記gameOver 置位,并且繪制上gameOver 圖片。
6. 添加移動動畫(難點)
??因為要添加動畫,所以要修改 move() 函數(shù),最基本實現(xiàn)中是一次得到最終的移動結(jié)果,考慮到各個方塊需要移動的距離不一定相同等情況,采用每次只整體移動一格,然后繪制動畫的方式。因為最多移動三格,所以遍歷三次即可,把要移動的格子作標記。然后在偏移位置繪制相應的圖片即可,具體實現(xiàn)看完整代碼。
??三次遍歷,每次遍歷之間沒什么不同,無法區(qū)分是否被合并過,所以要做合并標記,兩個數(shù)都沒合并標記才能合并,因為只能合并一次。在本次移動操作中,方塊合并后就不會再移動。
??增加了整體移動標記,如果一次檢測沒有移動,那么直接結(jié)束檢測。
??三次遍歷,增加的是檢測的工作,因為只有4x4大小,相對窗口幾十萬個像素的修改來說,無關(guān)緊要,耗時部分是動畫,動畫繪制次數(shù)依然不變。
七、完整版代碼
??為了方便修改和整理,所以定義了一些宏和全局常量。
??由于庫本身代碼的原因,播放音樂時會有一幀的卡頓,所以示例程序中默認不播放合并音效版,使移動動畫更為流暢。==
#include <graphics.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <math.h>//控制是否播放合并音效,0:關(guān)閉,1: 播放 #define ENABLE_PLAY_MERGE_MUSIC 0void load(); //加載資源 void loadImage(); //加載圖片 bool loadRecord(); //讀取游戲記錄 void loadMusic(); //加載音樂 void gameSave(); //游戲保存 int scoring(int mergeNum); //根據(jù)合并的數(shù)字計分 void addScore(int score); //加分 void updateMaxMergeNum(int mergeNum); //更新最大合成數(shù)字 void releaseImage(); //釋放圖片資源 void releaseMusic(); //關(guān)閉音樂文件,釋放資源 void draw(); //繪制畫面 void drawGameInfo(); //繪制游戲信息 void addRandomNum(int n); //增加隨機數(shù)字 bool move(int direct); //按方向移動格子 void drawBlocks(); //繪制格子 void restart(); //重新游戲 void gameOverCheck(); //游戲結(jié)束檢測//界面布局參數(shù) const int AREA_LEFT = 20, AREA_TOP = 178, AREA_WIDTH = 500, AREA_HEIGHT = 500; const int GRID_WIDTH = 106, DEVIDE = 15; const int SCR_WIDTH = AREA_WIDTH + AREA_LEFT * 2, SCR_HEIGHT = AREA_HEIGHT + AREA_TOP + AREA_LEFT;//顏色參數(shù) const color_t textScoreColor = EGERGB(241, 231, 214); const color_t backgroundColor = EGERGB(250, 248, 239);//動畫參數(shù) const int animationDuration_ms = 420; //移動動畫持續(xù)時長(ms),指最遠距離時//按鈕點擊判斷 inline bool clickBtnRestart(int x, int y) {return (20 < x && x < 20 + 222) && (110 < y && y < 110 + 50); }//圖片 #define NUM_BLOCK 18 PIMAGE blockImgs[NUM_BLOCK];#define NUM_IMG 5 PIMAGE pimgs[NUM_IMG]; const int ID_IMG_BACKGROUND = 0, ID_IMG_LOGO = 1, ID_IMG_SCORE_BG = 2, ID_IMG_RESTART = 3; const int ID_IMG_GAMEOVER = 4;//圖片文件位置 const char* imgFileDirection = "./resource/image"; const char* imgFiles[NUM_IMG] = {"background.png", "gamelogo.png", "scorebg.png", "restart.png", "gameOver.png", };//數(shù)據(jù)文件 const char* recordFileName = "game2048Record.txt";//音樂 MUSIC mergeMusic; MUSIC startMusic; const char* mergeMusicFile = "./resource/music/merge.mp3"; const char* startMusicFile = "./resource/music/start.mp3";const int LEFT = 0, UP = 1, RIGHT = 2, DOWN = 3; //方向偏移 const int dx[4] = { -1, 0, 1, 0 }; const int dy[4] = { 0, -1, 0, 1 };struct GameInfo {int score;int topScore;int maxNum; }; GameInfo gameInfo;int grid[4][4]; //格子 int emptyBlock = 16; //空格子數(shù) bool gameOver = false;int main() {//注意要INIT_NOFORCEEXIT, 即關(guān)閉窗口不強制退出程序,以便進行游戲保存工作initgraph(SCR_WIDTH, SCR_HEIGHT, INIT_RENDERMANUAL | INIT_NOFORCEEXIT);setcaption("2048");setbkcolor(backgroundColor);setbkmode(TRANSPARENT);srand((unsigned int)time(0));delay_ms(0); //刷新窗口load();startMusic.Play(0);gameOverCheck();int xClick, yClick;bool redrawFlag = true;for (; is_run(); delay_fps(60)) {//按鍵檢測int direction = -1;int key = 0;bool restartGameFlag = false;while (kbmsg()) {key_msg keyMsg = getkey();if (keyMsg.msg == key_msg_down) {switch (keyMsg.key) {case 'A': case key_left: direction = 0; break;case 'W': case key_up: direction = 1; break;case 'D': case key_right: direction = 2; break;case 'S': case key_down: direction = 3; break;}}else if (keyMsg.msg == key_msg_up) {key = keyMsg.key;}}//鼠標點擊檢測bool leftClick = false;while (mousemsg()) {mouse_msg mouseMsg = getmouse();if (mouseMsg.is_left() && mouseMsg.is_down()) { //左鍵按下leftClick = true;xClick = mouseMsg.x;yClick = mouseMsg.y;}}if (!gameOver) {// 游戲沒有結(jié)束,處理移動操作if (direction != -1 && move(direction)) {addRandomNum(1);gameOverCheck();redrawFlag = true;}}else {// 游戲結(jié)束后,可以通過按回車鍵重新開始if (key == key_enter)restartGameFlag = true;}// 重新開始按鈕的點擊判斷if (leftClick && clickBtnRestart(xClick, yClick)) {restartGameFlag = true;}if (restartGameFlag){restart();startMusic.Play(0);redrawFlag = true;}if (redrawFlag) {cleardevice();draw();redrawFlag = false;}}gameSave();releaseImage();releaseMusic();closegraph();return 0; }void drawGameInfo() {putimage_withalpha(NULL, pimgs[ID_IMG_LOGO], AREA_LEFT + 14, 30); //圖標putimage_withalpha(NULL, pimgs[ID_IMG_SCORE_BG], 260, 10); //游戲分數(shù)背景putimage_withalpha(NULL, pimgs[ID_IMG_RESTART], 20, 110); //重新開始按鈕//游戲分數(shù)setcolor(textScoreColor);setfont(30, 0, "黑體");xyprintf(370, 24, "%8d", gameInfo.topScore);xyprintf(370, 72, "%8d", gameInfo.score);xyprintf(370, 120, "%8d", gameInfo.maxNum); }void draw() {drawGameInfo();drawBlocks();if (gameOver) {setfillcolor(EGEACOLOR(0x60, WHITE));ege_fillrect(AREA_LEFT, AREA_TOP, AREA_WIDTH, AREA_HEIGHT);putimage_withalpha(NULL, pimgs[ID_IMG_GAMEOVER], 120, 400);} }void restart() {gameInfo.score = 0;gameOver = false;memset(grid, 0, 16 * sizeof(int));emptyBlock = 16;addRandomNum(2); }void drawBlocks() {putimage_withalpha(NULL, pimgs[ID_IMG_BACKGROUND], AREA_LEFT, AREA_TOP);for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {int x = AREA_LEFT + (j + 1) * DEVIDE + j * GRID_WIDTH;int y = AREA_TOP + (i + 1) * DEVIDE + i * GRID_WIDTH;if (grid[i][j] != 0)putimage_withalpha(NULL, blockImgs[grid[i][j]], x, y);}} }void gameOverCheck() {if (emptyBlock != 0)return;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if ((j + 1 < 4 && grid[i][j] == grid[i][j + 1])|| (i + 1 < 4 && grid[i][j] == grid[i + 1][j]))return;}}gameOver = true; }void addRandomNum(int n) {while ((emptyBlock > 0) && (n-- > 0)) {int randEmptyBlock = rand() % emptyBlock; // 隨機選取一個空格int i = 0, count = 0;int* gridList = &grid[0][0];// 對數(shù)組進行遍歷,查找對應的空格(空格從0開始編號)for (i = 0; i < 4 * 4; i++) {if ((gridList[i] == 0) && (count++ == randEmptyBlock))break;}//隨機數(shù)字2或4,0.9概率是1,0.1概率是2gridList[i] = (rand() % 10 < 1) ? 2 : 1;emptyBlock--;} }bool move(int direction) {//索引0~3分別對應移動方向左上右下//初始檢測位置static int x0[4] = { 0, 0, 3, 0 }, y0[4] = { 0, 0, 0, 3 };// 分別對應四個移動方向的下一個元素的位置偏移(位置偏移與移動方向相反)static int elemOffset[4][2] = { {1, 0},{0, 1},{-1, 0}, {0, -1} };// 分別對應四個移動方向的下一行(列)的位置偏移(位置偏移與移動方向相反)static int lineOffset[4][2] = { {0, 1}, {1, 0}, {0, 1}, {1, 0} };bool blockMergeFlag[4][4] = { false }; //記錄方塊是否被合并過bool mergeFlag = false, movingFlag = false;clock_t startClock = clock();for (int check = 3; check > 0; --check) {int oldGrid[4][4]; //未移動前數(shù)據(jù)保存memcpy(oldGrid, grid, sizeof(int) * 4 * 4);bool blockMovingFlag[4][4] = { false };bool singleMovingFlag = false;int singleMaxMergeNum = 0; // 單次移動最大合成數(shù)字int singleScore = 0; // 單次移動加分//整體單格移動for (int i = 0; i < 4; i++) {int xCur = x0[direction] + i * lineOffset[direction][0];int yCur = y0[direction] + i * lineOffset[direction][1];for (int nextPos = 1; nextPos < 4; nextPos++) {int xNext = xCur + elemOffset[direction][0];int yNext = yCur + elemOffset[direction][1];//尋找下一個非空方塊if (grid[yNext][xNext] != 0) {//方塊前為空格,前移if (grid[yCur][xCur] == 0) {grid[yCur][xCur] = grid[yNext][xNext];grid[yNext][xNext] = 0;//標記方塊移動singleMovingFlag = blockMovingFlag[yNext][xNext] = true;}// 相等且沒有參與合并過,則進行合并else if ((grid[yCur][xCur] == grid[yNext][xNext]) && (!blockMergeFlag[yCur][xCur])&& (!blockMergeFlag[yNext][xNext])) {++grid[yCur][xCur];grid[yNext][xNext] = 0;emptyBlock++;mergeFlag = blockMergeFlag[yCur][xCur] = true;singleMovingFlag = blockMovingFlag[yNext][xNext] = true;// 合并時計算分數(shù),分值 = 2的n次方(n為方塊的值)int num = 1 << grid[yCur][xCur];if (num > singleMaxMergeNum)singleMaxMergeNum = num;singleScore += scoring(num);}}xCur = xNext;yCur = yNext;}}// 是否有單格移動if (singleMovingFlag) {//移動動畫cleardevice();drawGameInfo();setfillcolor(getbkcolor());const int totalDistance = (GRID_WIDTH + DEVIDE);double tBegin = 0.0, tEnd = 1.0;double dt = (tEnd - tBegin) / (animationDuration_ms / (1000.0 * (4 - 1))* 60.0);int lastPosLeft = 0;bool first = true;for (double t = tBegin; t < tEnd; t += dt) {if (fabs(t - tEnd) < 1E-8)break;int distance = round(t * totalDistance);bar(AREA_LEFT, AREA_TOP, AREA_LEFT + AREA_WIDTH, AREA_TOP + AREA_HEIGHT); //清除區(qū)域putimage_withalpha(NULL, pimgs[ID_IMG_BACKGROUND], AREA_LEFT, AREA_TOP); //繪制背景//繪制方塊for (int i = 0; i < 4; i++) {int xLine = x0[direction] + i * lineOffset[direction][0];int yLine = y0[direction] + i * lineOffset[direction][1];for (int pos = 0; pos < 4; pos++) {int x = xLine + pos * elemOffset[direction][0];int y = yLine + pos * elemOffset[direction][1];if (oldGrid[y][x] != 0) {// 計算方塊左上角的位置坐標int left = AREA_LEFT + (x + 1) * DEVIDE + x * GRID_WIDTH;int top = AREA_TOP + (y + 1) * DEVIDE + y * GRID_WIDTH;if (blockMovingFlag[y][x]) {left += distance * dx[direction];top += distance * dy[direction];}putimage_withalpha(NULL, blockImgs[oldGrid[y][x]], left, top);}}}delay_jfps(60);}}// 移動動畫完成后才更新分值addScore(singleScore);updateMaxMergeNum(singleMaxMergeNum);if (singleMovingFlag)movingFlag = true;else { // 無法繼續(xù)移動,退出循環(huán)break;}}#if ENABLE_PLAY_MERGE_MUSICif (mergeFlag && mergeMusic.GetPlayStatus() != MUSIC_MODE_PLAY)mergeMusic.Play(0); #endifreturn movingFlag; }void load() {loadImage();loadMusic();if (!loadRecord())restart(); }void loadMusic() {mergeMusic.OpenFile(mergeMusicFile);startMusic.OpenFile(startMusicFile); }bool loadRecord() {FILE* fp = fopen(recordFileName, "r");if (fp == NULL)return false;int topScore, score, maxNum;if (fscanf(fp, "topScore:%d score:%d maxNum:%d", &topScore, &score, &maxNum) != 3) {fclose(fp);return false;}gameInfo.topScore = topScore;gameInfo.score = score;gameInfo.maxNum = maxNum;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {int readInCount = fscanf(fp, "%d", &grid[i][j]);// 讀取數(shù)據(jù)出錯或者數(shù)據(jù)無效if ((readInCount != 1) || (grid[i][j] < 0) || (NUM_BLOCK <= grid[i][j])) {fclose(fp);return false;}if (grid[i][j] != 0)emptyBlock--;}}fclose(fp);return true; }int scoring(int mergeNum) {return mergeNum; }void addScore(int score) {gameInfo.score += score;if (gameInfo.score > gameInfo.topScore)gameInfo.topScore = gameInfo.score; }void updateMaxMergeNum(int mergeNum) {if (mergeNum > gameInfo.maxNum)gameInfo.maxNum = mergeNum; }void gameSave() {//數(shù)據(jù)寫入FILE* fp = fopen(recordFileName, "w");if (fp == NULL)return;fprintf(fp, "topScore:%d\nscore:%d\nmaxNum:%d\n",gameInfo.topScore, gameInfo.score, gameInfo.maxNum);for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++)fprintf(fp, "%d ", grid[i][j]);fprintf(fp, "\n");}fclose(fp); }void loadImage() {//創(chuàng)建一個可以容納生成的字符串的字符數(shù)組,用于保存圖片路徑char imgPath[64];//獲取圖片for (int i = 0; i < NUM_IMG; i++) {//生成圖片文件名,存儲到imgName[]中sprintf(imgPath, "%s/%s", imgFileDirection, imgFiles[i]);pimgs[i] = newimage();getimage(pimgs[i], imgPath);}//獲取數(shù)字圖片for (int i = 1, num = 2; i < NUM_BLOCK; i++, num *= 2) {sprintf(imgPath, "%s/block_%d.png", imgFileDirection, num);blockImgs[i] = newimage();getimage(blockImgs[i], imgPath);} }void releaseImage() {//釋放所有圖片資源for (int i = 0; i < NUM_BLOCK; i++)delimage(blockImgs[i]);for (int i = 0; i < NUM_IMG; i++)delimage(pimgs[i]); }void releaseMusic() {if (mergeMusic.IsOpen())mergeMusic.Close();if (startMusic.IsOpen())startMusic.Close(); }EGE專欄:EGE專欄
總結(jié)
以上是生活随笔為你收集整理的EGE示例程序——2048的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sci 论文参考文献期刊引用名PubMe
- 下一篇: 全局空间自相关算法:Join Count