2048游戏系列---优化模块第二稿【计分模块】
(更新中……)
参考博客:https://blog.csdn.net/qq_39151563/article/details/104283217
参考博客:https://blog.csdn.net/qq_39151563/article/details/104342730
由于放在一篇会导致篇幅太长,所以分成了几篇。(可能有个10篇吧=.=)
目前写完的:
- 【C++】2048游戏系列—总览篇
- 【C++】2048游戏系列—功能模块第一稿【矩阵操作】
- 【C++】2048游戏系列—功能模块第二稿【键盘输入】
- 【C++】2048游戏系列—功能模块第三稿【添加新数】
- 【C++】2048游戏系列—功能模块第四稿【结束检测】
- 【C++】2048游戏系列—优化模块第一稿【加载图片】
本篇讲解计分模块的激活。
一、布局和计算
从参考博客偷来的布局图:
1.1-通过图片尺寸计算
1.2-一些定义的含义
我的思路是,先把它们紧密地拼凑在一起,再计算图片之间的间隔,只要加
DEVIDE
的就好了
const int WIDTH = 500+2*DEVIDE;//画面总体宽度
const int HEIGHT = 660+2*DEVIDE;//画面总体长度
const int DEVIDE = 15;//间隔宽度
const int GRID_WIDTH = 106;//数字块宽度(正方形)
const int BOARD_X = 0+DEVIDE;//棋盘起始点
const int BOARD_Y = 160+DEVIDE
const int LOGO_X = 25+DEVIDE;//Logo图起始点
const int LOGO_Y = 20;
const int RESTART_X = 0+DEVIDE;//restart图起始点
const int RESTART_Y = 90;
const int SCOREGB_X = 230+DEVIDE;//计分背景图起始点
const int SCOREGB_Y = 0;
1.3-一些关键的修改
具体的可以参考后面的源码
initgraph(WIDTH, HEIGHT);
//图片
PIMAGE GameLogoImg;
PIMAGE RestartImg;
PIMAGE ScoreBgImg;
GameLogoImg = newimage();
RestartImg = newimage();
ScoreBgImg = newimage();
getimage(GameOverImg, "image\\gameOver.png");
getimage(GameLogoImg, "image\\gamelogo.png");
getimage(RestartImg, "image\\restart.png");
getimage(ScoreBgImg, "image\\scorebg.png");
delimage(GameLogoImg);
delimage(RestartImg);
delimage(ScoreBgImg);
//Draw函数里面
putimage_withalpha(NULL, GameLogoImg, LOGO_X, LOGO_Y);//Logo图
putimage_withalpha(NULL, RestartImg, RESTART_X, RESTART_Y);//重新开始图
putimage_withalpha(NULL, ScoreBgImg, SCOREGB_X, SCOREGB_Y);//计分背景图
int x = (j+1)*DEVIDE + j*GRID_WIDTH + BOARD_X;
int y = (i+1)*DEVIDE + i*GRID_WIDTH + BOARD_Y;
1.4-运行结果
后来我又在上面添加了一个
DEVIDE
,也就是每一张图片的y + DEVIDE
,大家可以根据自己喜欢调整布局,感觉有那味了
二、激活重新开始
按键
现在的
重新开始
按键就是摆设,鼠标点击它并没有什么反应,我们要做的工作就是激活它!!!
2.1-鼠标检测模板
bool flag = false;//判断标志位
int xClick = yClick = 0;//记录鼠标位置
for(;is_run();delay_fps())
{
flag = false;
while (mousemsg())
{
mouse_msg msg = getmouse();
if (msg.is_left() && msg.is_down())
{
isClick = true;
xClick = msg.x;
yClick = msg.y;
}
}
if(flag)
{
...//具体操作
}
}
具体操作原理可以参看参考博客,与键盘检测逻辑类似
2.2-检测是否点击在矩形区域内
const int RESTART_X = 0+DEVIDE;
const int RESTART_Y = 90+DEVIDE;
const int RESTART_WIDTH = 220;//restart 宽度
const int RESTART_HEIGHT = 50;//restart 高度
bool isClicInRectangle(int xClick,int yClick,int Rectangle_X,int Rectangle_Y,
int Rectangle_WIDTH,int Rectangle_HEIGHT)
{
if(xClick>Rectangle_X&&xClick<Rectangle_X+Rectangle_WIDTH &&
yClick>Rectangle_Y&&yClick<Rectangle_Y+Rectangle_HEIGHT)
return true;
return false;
}
2.3-重新开始函数
//重新开始
void RestartDate()
{
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
grid[i][j] = 0;
Addnum(2);
}
2.4-主函数修改代码
//鼠标检测
bool Clickflag= false;
while (mousemsg())
{
mouse_msg msg = getmouse();
if (msg.is_left() && msg.is_down()&&
isClicInRectangle(msg.x,msg.y,RESTART_X,RESTART_Y,RESTART_WIDTH,RESTART_HEIGHT))
{
Clickflag = true;
}
}
if(Clickflag)
{
RestartDate();
Draw();
}
2.5-运行结果
三、激活合成最大值
输出
3.1-计算合成的最大值
只有在合并的时候才要比较一下最大值,所以要添加
MaxValue = GetMaxValue();
到Move()
函数合并时的情况
int MaxValue = 0;//记录合成的最大值
//计算合成的最大值
int GetMaxValue()
{
int temp=0;
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
{
if(grid[i][j]>temp) temp = grid[i][j];
}
return temp;
}
//Move()函数中
...
else if(grid[t1y][t1x]==grid[t2y][t2x])
{
grid[t1y][t1x]++;//合并
MaxValue = GetMaxValue();//更新最大值
grid[t2y][t2x] = 0;
t1x += firstOffset[dir][0];
t1y += firstOffset[dir][1];
}
...
3.2-图形输出最大值
文字输出函数:
xyprintf()
记得设置背景模式:
setbkmode(TRANSPARENT)
const int MAXVALUE_X = SCOREGB_X+160;
const int MAXVALUE_Y = SCOREGB_Y+112;
const color_t TEXT_COLOR = EGERGB(241, 231, 214); //保持与面板字体一样的颜色
const color_t BG_COLOR = EGERGB(250,248, 239);//背景色:淡黄色
//Draw()函数中
setcolor(TEXT_COLOR);
xyprintf(MAXVALUE_X, MAXVALUE_Y,"%4d",(int)pow(2,MaxValue));//要添加<cmath>库
//main()函数中
setbkcolor(BG_COLOR);
setfont(25,0,"黑体");
setbkmode(TRANSPARENT);//文字输出为透明,默认带背景
3.3-运行结果
四、激活本次分数
输出
4.1-计算和输出最大值
加的分数就是合成的数字值,要改 Draw() 函数 和 Move() 函数
int Score = 0;//记录分数
const int SCORE_X = MAXVALUE_X;
const int SCORE_Y = MAXVALUE_Y-45;
//Draw()函数中
...
xyprintf(SCORE_X, SCORE_Y, "%4d",Score);
...
//Move()函数中
...
else if(grid[t1y][t1x]==grid[t2y][t2x])
{
grid[t1y][t1x]++;//合并
MaxValue = GetMaxValue();
Score += (int)pow(2,grid[t1y][t1x]);
grid[t2y][t2x] = 0;
t1x += firstOffset[dir][0];
t1y += firstOffset[dir][1];
}
...
4.2-运行结果
五、激活最高分数
输出
最高分数
输出(脑子有点晕,放到下次更新内容中吧)
重点在于记录下历史最高分,所以涉及到
文件操作
九层之台,起于垒土。还是一步步来吧
``
六、全部代码和运行结果
#include <iostream>
#include "graphics.h"
#include <cmath>
#include <fstream>
#include <cstdio>
using namespace std;
//测试矩阵
int grid[4][4] = {0
// {1,2,3,4},
// {4,5,6,7},
// {7,8,9,10},
// {1,1,1,0}
};
int EmptyBlock = 4 ; //空格数
int dir = -1; // 0-左,1-上,2-右,3-下
//打印函数
void PrintGrid()
{
for(int i=0; i<4; i++)
{
for(int j=0; j<4; j++)
cout << grid[i][j] << " ";
cout << endl;
}
cout << endl;
}
//计算空格函数
int CalculateEmpty()
{
int cnt = 0;
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
if(grid[i][j]==0) cnt++;
return cnt;
}
//显示信息
void ShowInfo()
{
cout << "dir = " << dir << endl;
cout<< "EmptyBlock = " << CalculateEmpty() << endl;
cout << "grid[4][4] = " << endl;
PrintGrid();
}
const int DEVIDE = 15;
const int GRID_WIDTH = 106;
const int BOARD_X = 0+DEVIDE;//棋盘起始点x
const int BOARD_Y = 160+2*DEVIDE;//棋盘起始点y
const int LOGO_X = 25+DEVIDE;
const int LOGO_Y = 20+DEVIDE;
const int RESTART_X = 0+DEVIDE;
const int RESTART_Y = 90+DEVIDE;
const int RESTART_WIDTH = 220;//restart 宽度
const int RESTART_HEIGHT = 50;//restart 高度
const int SCOREGB_X = 230+DEVIDE;
const int SCOREGB_Y = 0+DEVIDE;
const color_t TEXT_COLOR = EGERGB(241, 231, 214); //字体颜色
int MaxValue = 0;//记录合成的最大值
const int MAXVALUE_X = SCOREGB_X+160;
const int MAXVALUE_Y = SCOREGB_Y+112;
int Score = 0;//记录分数
const int SCORE_X = MAXVALUE_X;
const int SCORE_Y = MAXVALUE_Y-45;
int TopScore = 0;//记录最高分数
const int TOP_SCORE_X = SCORE_X;
const int TOP_SCORE_Y = SCORE_Y-45;
//计算合成的最大值
int GetMaxValue()
{
int temp=0;
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
{
if(grid[i][j]>temp) temp = grid[i][j];
}
return temp;
}
void GameSave();
//移动函数
static int x0[4] = {0, 0, 3, 0};
static int y0[4] = {0, 0, 0, 3};
static int firstOffset[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
static int secondOffset[4][2] = {{0,1},{1,0},{0,1} ,{1,0}};
void Move(int dir)
{
//bool moved = false;
if(dir==-1) return;
int tx, ty;
int t1x, t1y;
int t2x, t2y;
for(int i=0; i<4; i++)
{
tx = x0[dir] + i*secondOffset[dir][0];
ty = y0[dir] + i*secondOffset[dir][1];
//cout << "(" << tx << ", " << ty << ")" << endl;
t1x = tx;
t1y = ty;
t2x = tx + firstOffset[dir][0];
t2y = ty + firstOffset[dir][1];
for( ;t2x>=0&&t2x<=3&&t2y>=0&&t2y<=3; t2x+=firstOffset[dir][0],t2y+=firstOffset[dir][1])
{
if(grid[t2y][t2x]!=0)
{
if(grid[t1y][t1x]==0)
{
grid[t1y][t1x] = grid[t2y][t2x];
grid[t2y][t2x] = 0;
// moved = true;
}
else if(grid[t1y][t1x]==grid[t2y][t2x])
{
grid[t1y][t1x]++;//合并
if(MaxValue<GetMaxValue())
{
MaxValue=GetMaxValue();
cout << "teststst" << endl;
GameSave();
}
Score += (int)pow(2,grid[t1y][t1x]);
if(TopScore<Score)
{
TopScore=Score;
GameSave();
}
grid[t2y][t2x] = 0;
t1x += firstOffset[dir][0];
t1y += firstOffset[dir][1];
// moved = true;
}
else if(t1x+firstOffset[dir][0]!=t2x||t1y+firstOffset[dir][1]!=t2y)
{
grid[t1y+firstOffset[dir][1]][t1x+firstOffset[dir][0]] = grid[t2y][t2x];
grid[t2y][t2x] = 0;
t1x += firstOffset[dir][0];
t1y += firstOffset[dir][1];
//cout << "Move Test" << endl;
// moved = true;
}
else
{
t1x += firstOffset[dir][0];
t1y += firstOffset[dir][1];
}
}
}
}
//return moved;
}
//添加新数
void Addnum(int n)
{
while(n--)//添加n个
{
EmptyBlock = CalculateEmpty();
if(EmptyBlock<=0)
{
//cout << "addnum EmptyBlock = " << EmptyBlock << endl;
//cout << "addnum Test1" << endl;
return;
}
int cnt = random(EmptyBlock)+1;//随机得到一个空格数以内的数
//cout << "找到第" << cnt << "个空位" << endl;
//cout << "cnt = " << cnt << endl;
int *p = &grid[0][0]-1;//记录矩阵首地址前一个 ,因为后面的 p 在找到时还会 ++
//cout << "n = "<< n <<endl;
for(int i=0; i<4&&cnt; i++)
for(int j=0; j<4&&cnt; j++)
{
if(grid[i][j]==0 && cnt)//如果有空格并且cnt有效
{
//cout << "cnt = " << cnt << endl;
cnt--;//找到一个划一个
}
p++;//p 指向下一个再进行判断
}
//循环结束时 p 指向我们之前随机指定的空格
*p = (random(10)==0)?2:1;// 0.1 的概率为2,0.9 的概率为1
//cout << "插入成功" << endl;
//*p = (random(10)==0)+1;//这样写也可以
EmptyBlock--;
}
}
PIMAGE BlockImgs[18];//EGE图片形式
PIMAGE GameOverImg;
PIMAGE GameLogoImg;
PIMAGE RestartImg;
PIMAGE ScoreBgImg;
//加载图片
void LoadImgs()
{
char imgAdress[40];
for(int i=1,num=2; i<18; i++,num*=2)
{
sprintf(imgAdress,"image\\block_%d.png",num);
BlockImgs[i] = newimage();
getimage(BlockImgs[i], imgAdress);
//cout << imgAdress << endl;
}
BlockImgs[0] = newimage();
GameOverImg = newimage();
GameLogoImg = newimage();
RestartImg = newimage();
ScoreBgImg = newimage();
getimage(BlockImgs[0], "image\\background.png");
getimage(GameOverImg, "image\\gameOver.png");
getimage(GameLogoImg, "image\\gamelogo.png");
getimage(RestartImg, "image\\restart.png");
getimage(ScoreBgImg, "image\\scorebg.png");
cout<< "读取图片成功" << endl;
}
//释放图片
void ReleaseImgs()
{
for(int i=0; i<18; i++)
{
delimage(BlockImgs[i]);
}
delimage(GameOverImg);
delimage(GameLogoImg);
delimage(RestartImg);
delimage(ScoreBgImg);
}
//绘制图像
void Draw()
{
cleardevice();
putimage_withalpha(NULL, BlockImgs[0], BOARD_X, BOARD_Y);//格子背景图
putimage_withalpha(NULL, GameLogoImg, LOGO_X, LOGO_Y);//Logo图
putimage_withalpha(NULL, RestartImg, RESTART_X, RESTART_Y);//重新开始图
putimage_withalpha(NULL, ScoreBgImg, SCOREGB_X, SCOREGB_Y);//计分背景图
//MaxValue = GetMaxValue();//更新最大值
setcolor(TEXT_COLOR);
xyprintf(MAXVALUE_X, MAXVALUE_Y,"%4d",(int)pow(2,MaxValue));
xyprintf(SCORE_X, SCORE_Y, "%4d",Score);
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
int x = (j+1)*DEVIDE + j*GRID_WIDTH + BOARD_X ;
int y = (i+1)*DEVIDE + i*GRID_WIDTH + BOARD_Y ;
//cout << "(x,y) = " << "(" << x << ","<< y << ")" << endl;
if(grid[i][j]!=0)
{
putimage_withalpha(NULL,BlockImgs[grid[i][j]],x,y);
}
}
}
}
//游戏结束
bool gameOver()
{
EmptyBlock = CalculateEmpty();
if(EmptyBlock>0) return false;
for(int i=0;i<4;i++)
{
int t1=0,t2=1;
while(t2<=3)
{
if(grid[i][t1]==grid[i][t2] || grid[t1][i]==grid[t2][i])// 横 ||纵
{
return false;
}
else
{
t1++;
t2++;
}
}
}
return true;
}
//重新开始
void RestartDate()
{
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
grid[i][j] = 0;
Addnum(2);
MaxValue = GetMaxValue();
}
//检测是否点击在矩形区域内
bool isClicInRectangle(int xClick,int yClick,int Rectangle_X,int Rectangle_Y,
int Rectangle_WIDTH,int Rectangle_HEIGHT)
{
if(xClick>Rectangle_X&&xClick<Rectangle_X+Rectangle_WIDTH &&
yClick>Rectangle_Y&&yClick<Rectangle_Y+Rectangle_HEIGHT)
return true;
return false;
}
const char RECORD_FILE_NAME[15] = "RECORD.txt";
void GameSave()
{
ofstream ofile(RECORD_FILE_NAME);
char RecordString[30];
sprintf(RecordString, "TopScore:%d\nMaxValue:%d\n",TopScore,MaxValue);
ofile << RecordString << endl;
ofile.close();
}
void LoadRecord()
{
ifstream ifile(RECORD_FILE_NAME); //定义输入文件,并打开文件 FILE_NAME
char InputRecordString[50]; //暂存字符串
int i=0;
int cnt = 0;
while(true)
{
if(ifile.get(InputRecordString[i])) cnt++; //读取到一个字符并给 InputRecordString[i]
if(InputRecordString[i]=='\0') break;
i++;
}
ifile.close();
cout << InputRecordString << endl;
int num[15];
int NumCnt = 0;
int flag = 0;
TopScore = MaxValue = 0;
for(int i=0; i<cnt; i++)//初始值为 8 因为 "TopSocre" 有 7 个字符(从 0 开始算), 第 8 个正好是 ':'
{
cout << i << ": " << InputRecordString[i]<< endl;
if(InputRecordString[i]==':')
{
cout << "i = " << i << endl;
flag++;
i++;
while((int)InputRecordString[i]>=48&&(int)InputRecordString[i]<=57)//'0'的 AscII 码 = 48,'9'为57
{
num[NumCnt] = (int)InputRecordString[i]-48;
//cout << NumCnt << ": " << num[NumCnt] << endl;
NumCnt++;
i++;
}
cout << NumCnt << endl;
for(int i=0; i<NumCnt; i++)//根据 flag 的值分别给 变量 1,2...赋值
{
if(flag==1)
{
cout << "num[i] = " << num[i] << endl;
cout << "(int)pow(10,NumCnt-i-1) = " << (int)pow(10,NumCnt-i-1) << endl;
TopScore += num[i] * (int)pow(10,NumCnt-i-1);
cout << "TopScore = " << TopScore << endl;
}
else if(flag==2)
{
MaxValue += num[i] * (int)pow(10,NumCnt-i-1);
}
}
}
NumCnt = 0; //置 0 准备下次读值
}
cout << "TopScore = " << TopScore << endl;
cout << "MaxValue = " << MaxValue << endl;
}
const int WIDTH = 500+2*DEVIDE;
const int HEIGHT = 660+3*DEVIDE;
const color_t BG_COLOR = EGERGB(250,248, 239);//淡黄色
int main()
{
initgraph(WIDTH, HEIGHT);
setbkcolor(BG_COLOR);
setfont(25,0,"黑体");
setbkmode(TRANSPARENT);//文字输出为透明
//GameSave();
LoadRecord();
Addnum(2); //在随机2个位置添加新数
MaxValue = GetMaxValue();
ShowInfo();
LoadImgs();
Draw();
for ( ; is_run(); delay_fps(60) )
{
//cleardevice();
// todo: 逻辑更新(数据更新)
//按键检测
while(kbmsg())
{
key_msg keyMsg = getkey();
if(keyMsg.msg == key_msg_down)
{
switch(keyMsg.key)
{
case 'A':case key_left : dir = 0; break;//左
case 'W':case key_up : dir = 1; break;//上
case 'D':case key_right : dir = 2; break;//右
case 'S':case key_down : dir = 3; break;//下
}
}
}
// todo: 图形更新
if(dir!=-1)
{
system("cls");
switch(dir)
{
case 0: cout << "按下了 A/左 键" << endl; break;//左
case 1: cout << "按下了 W/上 键" << endl; break;//上
case 2: cout << "按下了 D/右 键" << endl; break;//右
case 3: cout << "按下了 S/下 键" << endl; break;//下
}
bool flag = false; //移动标志位
int tempGrid[4][4]; //暂存数组
int i,j;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
tempGrid[i][j] = grid[i][j];
Move(dir);
//比较
for(i=0; i<4; i++)
for(j=0; j<4; j++)
if(grid[i][j]!=tempGrid[i][j])
{
flag = true;
break;
}
if(flag)
{
cout << "有效移动" << endl;
Addnum(1);
}
else cout << "无效移动" << endl;
ShowInfo();
cout << "最大值:" << pow(2,GetMaxValue())<< endl;
cout << "gameover: " << (gameOver()?"是":"否") << endl;
Draw();
dir = -1;//将 dir 置为无效,否则控制台会一直刷新
}
//鼠标检测
bool Clickflag= false;
while (mousemsg())
{
mouse_msg msg = getmouse();
if (msg.is_left() && msg.is_down()&&
isClicInRectangle(msg.x,msg.y,RESTART_X,RESTART_Y,RESTART_WIDTH,RESTART_HEIGHT))
{
Clickflag = true;
}
}
if(Clickflag)
{
RestartDate();
Draw();
}
if(gameOver())
{
cout << "Game Over!" << endl;
//putimage(150,150,GameOver);
putimage_withalpha(NULL,GameOverImg,120,200);
break;
}
}
ReleaseImgs();
getch();
closegraph();
return 0;
}
状态不太好,要考试了!本篇有些细节没有处理好,等以后有时间再细细更改。