用C/C++加Easyx实现俄罗斯方块游戏(爆肝4万字,完全免费)

前言

        相信大家一定玩过俄罗斯方块这款小游戏,简单容易上手是老少皆宜的小游戏,今天大家就跟着我来实现这个小游戏吧!让自己学的C语言有用武之地。

        为了让俄罗斯方块的开发更为简单些,图像更为丰富,在这里就利用了Easyx库来实现,大家如果没了解过Easyx库,可以先到官网了解下,我们用到的函数不多,大家也可以在代码中用到那个就查看文档中这个函数的用法。官网如下EasyX 文档 - flushmessage。

        由于Easyx库必须在C++文件中才能用,我们的源文件必须命名为.cpp,但不用担心,C++是兼容C的,我们依然可以使用C的语法进行操作。所以这篇博客不需要太大的C++知识,总体而言是用C语言写的,学过C语言便可以了。当然如果想简单了解下C++,也可以看我写的这几篇博客。

【从C到C++过渡知识上 - CSDN App】http://t.csdnimg.cn/eRs9m

【从C到C++过渡知识 中(为什么C++支持函数重载,而C不支持函数重载) - CSDN App】http://t.csdnimg.cn/bPaCC

【从C到C++过渡知识 下(深入理解引用与指针的关系) - CSDN App】http://t.csdnimg.cn/NAkzO

        我们最终的实现结果如下。

俄罗斯方块

目录

前言

源码

分析

游戏初始化

功能区初始化

设置背景

打印文字        

打印分数

   读取历史最高分

struct project

方块结构体

加载六色方块

初始化背景

加载七个方块

方块旋转

        方法一

方法二

    方法三

下落方块

游戏进行

下降判断

        遇到LAND或者到达底部

消除检测

正常下落

检测按键

左移

右移

下落

退出

暂停

旋转

游戏结束

保存分数


源码

        源码如下,大家在下载好Easyx后,配置好图片就可以运行了。

可以看百度网盘或者我的资源下载源码图片。

链接: https://pan.baidu.com/s/1ZRwoI9d-53BIIfCwxNIcmg 提取码: 0000 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<graphics.h>
#include<time.h>#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
enum  block_status
{EXIST,//存在NO,//不存在LAND,ROATE//旋转点
};
struct block
{IMAGE img;enum  block_status flag;int x;int y;
};enum STATUS
{OK,ESC,GAME_OVER
};typedef struct project
{struct block BackGropund[20][10];//背景IMAGE original[7];//原始六色方块图片,最后一个为背景struct block  Bag[7][4][4];//原始方块包struct block Down[4][4];//下落方块enum STATUS status;//状态int SleepTime;//速度int Score;//分数int max;//最高分ExMessage m;
}SP;//加载方块包
void LoadImg(SP* p)
{IMAGE img0;loadimage(&img0, L"../橙色方块.jpg", 40, 40);p->original[0] = img0;IMAGE img1;loadimage(&img1, L"../紫色方块.jpg", 40, 40);p->original[1] = img1;IMAGE img2;loadimage(&img2, L"../红色方块.jpg", 40, 40);p->original[2] = img2;IMAGE img3;loadimage(&img3, L"../黄色方块.jpg", 40, 40);p->original[3] = img3;IMAGE img4;loadimage(&img4, L"../绿色方块.jpg", 40, 40);p->original[4] = img4;IMAGE img5;loadimage(&img5, L"../蓝色方块.jpg", 40, 40);p->original[5] = img5;IMAGE img6;loadimage(&img6, L"../背景.jpg", 40, 40);p->original[6] = img6;}//加载方块的模板
void LoadBag(SP* p)
{int i1 = 0, i2 = 0;//长形for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i2 == 1)p->Bag[0][i1][i2].flag = EXIST;elsep->Bag[0][i1][i2].flag = NO;if(i1==2 && i2==1)p->Bag[0][i1][i2].flag = ROATE;p->Bag[0][i1][i2].x = i2 + 1;p->Bag[0][i1][i2].y = i1 - 3;}}//正方形for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((i2 == 1 || i2 == 2) && (i1==1||i1==2))p->Bag[1][i1][i2].flag = EXIST;elsep->Bag[1][i1][i2].flag = NO;if (i1 == 1 && i2 == 1)//正方形特殊处理p->Bag[1][i1][i2].flag = ROATE;p->Bag[1][i1][i2].x = i2 + 1;p->Bag[1][i1][i2].y = i1 - 3;}}//山形for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 3 && i2 < 3 || i1 == 2 && i2 == 1)p->Bag[2][i1][i2].flag = EXIST;elsep->Bag[2][i1][i2].flag = NO;if (i1 == 2 && i2 == 1)p->Bag[2][i1][i2].flag = ROATE;p->Bag[2][i1][i2].x = i2 + 1;p->Bag[2][i1][i2].y = i1 - 3;}}//右七for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 3 && i2 < 3 || i1 == 2 && i2 == 2)p->Bag[3][i1][i2].flag = EXIST;elsep->Bag[3][i1][i2].flag = NO;if (i1 == 2 && i2 == 2)p->Bag[3][i1][i2].flag = ROATE;p->Bag[3][i1][i2].x = i2 + 1;p->Bag[3][i1][i2].y = i1 - 3;}}//左七for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 3 && i2 > 0 || i1 == 2 && i2 == 1)p->Bag[4][i1][i2].flag = EXIST;elsep->Bag[4][i1][i2].flag = NO;if (i1 == 2 && i2 == 1)p->Bag[4][i1][i2].flag = ROATE;p->Bag[4][i1][i2].x = i2 + 1;p->Bag[4][i1][i2].y = i1 - 3;}}//右Zfor (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 2 && i2 < 2 || i1 == 1 && (i2 == 1 || i2 == 2))p->Bag[5][i1][i2].flag = EXIST;elsep->Bag[5][i1][i2].flag = NO;if (i1 == 2 && i2 == 1)p->Bag[5][i1][i2].flag = ROATE;p->Bag[5][i1][i2].x = i2 + 1;p->Bag[5][i1][i2].y = i1 - 3;}}//左Zfor (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 2 && i2 > 1 || i1 == 1 && (i2 == 1 || i2 == 2))p->Bag[6][i1][i2].flag = EXIST;elsep->Bag[6][i1][i2].flag = NO;if (i1 == 2 && i2 == 2)p->Bag[6][i1][i2].flag = ROATE;p->Bag[6][i1][i2].x = i2 + 1;p->Bag[6][i1][i2].y = i1 - 3;}}}
//字符转换
void Change(WCHAR* des, char* src)
{while (*des++ = *src++);
}//打印背景
void PrintBackGround(SP* p)
{int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){putimage(i2 * 40, i1 * 40, &p->BackGropund[i1][i2].img);}}FlushBatchDraw();
}
//打印分数信息
void  PrintMessage(SP* p)
{char arr1[50];WCHAR arr2[50];//打印当前分数RECT r = { 400, 200, 600, 300 };drawtext(_T("当前分数:"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 300, 600, 350 };sprintf(arr1, "%d", p->Score);Change(arr2, arr1);drawtext(arr2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//打印最高分数r = { 400, 0, 600, 200 };drawtext(_T("最高分数:"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 100, 600, 200 };sprintf(arr1, "%d", p->max);Change(arr2, arr1);drawtext(arr2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 400, 600, 450 };drawtext(_T("←向左移动→向右移动"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 450, 600, 500 };drawtext(_T("↑旋转↓快速下落"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 500, 600, 550 };drawtext(_T("Esc退出 空格暂停"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//打印版权r = { 400, 700, 600, 800 };drawtext(_T("版权所有CSDN卫胡迪"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
//打印下落方块
void PrintDown(SP* p)
{int i1, i2;for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag==ROATE){putimage(p->Down[i1][i2].x * 40, p->Down[i1][i2].y * 40, &p->Down[i1][i2].img);}}}
}
//打印全部内容
void Print(SP* p)
{PrintBackGround(p);PrintDown(p);solidrectangle(400, 0, 600, 800);PrintMessage(p);FlushBatchDraw();
}//创造下落方块
void CreatBag(SP* p)
{int t = rand() % 7;int colour = (t + rand()) % 6;int i1, i2;for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2] = p->Bag[t][i1][i2];//不同方块不同if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE){// p->Down[i1][i2].flag != NOp->Down[i1][i2].img = p->original[colour];}}}}//初始化
void Init(SP *p)
{initgraph(600, 800);// EX_SHOWCONSOLE  控制台//初始配置setlinecolor(BLACK);setlinestyle(PS_SOLID | PS_JOIN_BEVEL, 3);setbkcolor(WHITE);settextcolor(BLACK);settextstyle(20, 0, L"楷体");setfillcolor(WHITE);setbkmode(TRANSPARENT);//透明文字cleardevice();srand((unsigned int)time(NULL));BeginBatchDraw();//防止闪屏//结构体初始化p->Score = 0;p->SleepTime = 500;p->status = OK;FILE* pf=fopen("../date.text","a+");if (pf == NULL){perror("Init:fopen");return;}if (fscanf(pf, "%d", &p->max) == EOF){p->max = 0;}fclose(pf);//加载图片,基础方块LoadImg(p);//加载七个方块LoadBag(p);//创建方块CreatBag(p);//设置边界线line(400, 0, 400, 800);//初始化背景int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){p->BackGropund[i1][i2].img = p->original[6];p->BackGropund[i1][i2].flag = NO;}}//打印背景PrintBackGround(p);//打印提示版权信息PrintMessage(p);FlushBatchDraw();
}void LeftMove(SP* p)
{int i1 = 0, i2 = 0;//全部检查是否合法for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag==ROATE)&&(p->Down[i1][i2].x-1<0 ||p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x - 1].flag==LAND))return;}}for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2].x -= 1;}}
}void RightMove(SP*p)
{int i1 = 0, i2 = 0;//全部检查一遍for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE) &&(p->Down[i1][i2].x +1 >9|| p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x +1].flag == LAND))return;}}for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2].x += 1;}}
}//旋转方块
void Rorate(SP* p)
{int i1, i2;int dx, dy;struct block tmp[4][4];//找到旋转点for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == ROATE){dx = p->Down[i1][i2].x;dy = p->Down[i1][i2].y;goto end;}}}end://正方形直接退出if (i1 == 1 && i2 == 1)return;//临时旋转数组for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){tmp[i1][i2] = p->Down[i1][i2];tmp[i1][i2].x = dx+dy- p->Down[i1][i2].y;tmp[i1][i2].y = dy-dx+ p->Down[i1][i2].x;}}//判断是否合法for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((tmp[i1][i2].flag == EXIST || tmp[i1][i2].flag == ROATE) &&(tmp[i1][i2].x > 9 || tmp[i1][i2].x < 0 || tmp[i1][i2].y>19 || p->BackGropund[tmp[i1][i2].y][tmp[i1][i2].x].flag == LAND))return;}}//合法则复制for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2]= tmp[i1][i2];}}}void CheckKey(SP*p)
{peekmessage(&p->m);if (p->m.vkcode==(VK_DOWN)){p->SleepTime = 60;}else{p->SleepTime = 500;if (p->m.vkcode == (VK_UP)){Rorate(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_LEFT)){LeftMove(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_RIGHT)){RightMove(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_ESCAPE)){p->status = ESC;}else if (KEY_PRESS(VK_SPACE)){while (!KEY_PRESS(VK_SPACE)){Sleep(50);}}}//消除影响p->m.vkcode = 0;flushmessage();
}//检查删除方块
void CheckDelste(SP* p)
{int arr[20] = {0};int i1 = 0, i2 = 0;int flag = 0;int sum = 0;//查找消除行for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){if (p->BackGropund[i1][i2].flag == LAND){flag = 1;}else{flag = 0;break;}}arr[i1] = flag;sum += arr[i1];}//检查有无消除行if (sum == 0)return;p->Score += 50 * sum;//加分数,一行50分//闪烁功能for (int j = 0; j < 3; j++){int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){if(arr[i1]== 0)putimage(i2 * 40, i1 * 40, &p->BackGropund[i1][i2].img);elseputimage(i2 * 40, i1 * 40, &p->original[6]);}}FlushBatchDraw();Sleep(150);PrintBackGround(p);Sleep(150);FlushBatchDraw();}//建立临时数组struct block tmp[20][10];int t = 19;for (i1 = 19; i1 >=0; i1--){if (arr[i1] == 0){for (i2 = 0; i2 < 10; i2++){tmp[t][i2] = p->BackGropund[i1][i2];}t--;}}//多余赋值为背景for (i1 = t; i1 >= 0; i1--){for (i2 = 0; i2 < 10; i2++){tmp[t][i2].flag = NO;tmp[t][i2].img = p->original[6];}}//转换for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){p->BackGropund[i1][i2] = tmp[i1][i2];}}Print(p);
}void  DownJudge(SP*p)
{int i1, i2;//检查是否到底for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){//行为y列为xif ((p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE)&&(p->BackGropund[p->Down[i1][i2].y+1][p->Down[i1][i2].x].flag == LAND || p->Down[i1][i2].y == 19)){//复制方块,变为地for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){//if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE){if (p->Down[i1][i2].y < 0){p->status = GAME_OVER;return;}p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x].img = p->Down[i1][i2].img;p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x].flag = LAND;}}}//加分数p->Score += 10;//检测是否可以消除CheckDelste(p);//创建新的下落方块CreatBag(p);//打印图片Print(p);return;}}}//正常则下降一格for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2].y += 1;}}//打印图片Print(p);}//游戏结束
void GameOver(SP*p)
{settextcolor(RED);settextstyle(50, 0, L"楷体");//创造最高记录if (p->max < p->Score){RECT r = { 0, 300, 600, 400 };drawtext(_T("恭喜你创造了新的记录!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);FILE* pf = fopen("../date.text", "w+");fprintf(pf, "%d", p->Score);fclose(pf);}RECT r = { 0, 500, 600, 600 };drawtext(_T("欢迎下次再玩。"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 0, 700,600, 800 };drawtext(_T("请按Enter结束……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);FlushBatchDraw();//不断检测信息,直到按下enterExMessage m = getmessage();while (m.vkcode != VK_RETURN){m = getmessage();}closegraph();
}int main()
{SP a;//初始化Init(&a);while (a.status == OK){//检测按键CheckKey(&a);//下降处理DownJudge(&a);//休眠Sleep(a.SleepTime);}//游戏结束GameOver(&a);return 0;
}

        图片如下,记得在.cpp上一级目录保存,名字正确,否则会报错。可以用qq截图保存。也可以下载压缩包。

        Easyx下载地址EasyX Graphics Library for C++

分析

        我们可以先在网上搜索一些俄罗斯方块的视频游戏,然后再根据自己的想法设计游戏。最终我们可以发现整个的游戏页面可以划分为功能区和游戏区。

        游戏区主要进行方块的下落,消除,功能区主要进行分数的显示,操作的介绍,版权等信息。

        我们想要的游戏结果便是方块不断地下落,消除记录分数,游戏介绍保存最大的分数。于是我们可以将游戏的整体逻辑分为三大块,游戏的初始化,游戏进行中,游戏的结尾,显然第二个是整个游戏的核心。一次直接写出很难做到,我们可以利用C语言面向过程的特点,逐步解决每个小问题,最终解决整个问题。

游戏初始化

        在这里大家遇到不知道的函数,可以在Easyx文档中查看用法。EasyX 文档 - flushmessage为了尽量精简文章不会过多的讲解函数的用法,主要注重在程序的设计思想。

功能区初始化

       首先我们就要创建一个窗口,这个窗口的大小,大家可以自己调试设计,我在这里采用的是600*800的窗口。然后我们可以现在画图软件上设计我们的功能区文字,内容。标注出大致的位置,方块的设计。最终设计如下。

        功能区划分在(400,0) (600,800)组成的矩形中。方块的大小设置为40*40,于是便是最终的游戏区长20个方块,宽10个方块。当然这是我们在纸上的设计。下一步便是要实现。

设置背景

        initgraph是Easyx中的函数,用来创建窗口,我们刚创建的窗口是黑色的,为了为了美观,我们可以将背景设置为白色。然后用背景色刷新屏幕。效果图如下

    initgraph(600, 800);setbkcolor(WHITE);cleardevice();

        

        注意我们在这里加了个getchar,否则程序运行完就会结束,不会停留在这个界面。

        接下来我们便可以画区分线。我们可以设置线为3像素,否则太细了。我们知道两点确定一条直线,我们画直线也十分的简单,只需要用给line函数提供两个坐标即可。代码如下

//设置边界线line(400, 0, 400, 800);

打印文字        

        接下来我们便可以设计具体的操作了,我们可以利用←向左移动→向右移动↑旋转↓快速下落,光有这些还不够,我们还可以增加空格暂停,Esc退出的功能。←可以利用输入法中的特殊字符打出。

        然后就是打印字符了,但我们不可以用printf,他是在控制台输出文字,我们要在窗口中输出文字要用到drawtext来实现。他是在一个矩形框中输出文字,所以我们要定义一个矩形的位置信息这个位置可以用RECT类型变量实现。只要我们给出矩形左上顶点与右下顶点,那么这个矩形就确定了。于是便有如下的代码。_T()是将里面的内容当成宽字符存储,与我们平时使用的单字节不同。

RECT r = { 400, 400, 600, 450 };
drawtext(_T("←向左移动→向右移动"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

        由此通过不断地调试文字的位置,我们便可以写出如下的代码。

    r = { 400, 400, 600, 450 };drawtext(_T("←向左移动→向右移动"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 450, 600, 500 };drawtext(_T("↑旋转↓快速下落"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 500, 600, 550 };drawtext(_T("Esc退出 空格暂停"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//打印版权r = { 400, 700, 600, 800 };drawtext(_T("版权所有CSDN卫胡迪"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

打印分数

        分数背我们以整型的形式进行存储,但要想使用drawtext必须为宽字符,我们便需要将分数先转换为单字符,然后再转换为宽字符。将数字转为单字符我们可以利用C语言的函数sprintf,格式化输出,转换到一个临时数组中存储,然后将单字节转为多字节。

        接下来我们来实现Change函数,让单字节变为多字节。实现方式也十分的简单,多字节采用的Unicode编码兼容ASCII,数字在ASCII表中,那么数字在多字节与单字节的唯一区别便是存储大小不一样。我们只需要将单字节的内容一个个拷贝过来就行。类似于strcpy的实现,只不过类型不同罢了。

//字符转换
void Change(WCHAR* des, char* src)
{while (*des = *src){des++;src++;}
}

        注意while判断中是一个=,赋值操作,当遇到字符串的结尾时,就停止。'\0'在对于的值为0,上面的代码还可以简化为如下

void Change(WCHAR* des, char* src)
{while (*des++ = *src++);
}

        有了上面的铺垫我们就可以输出当前的分数了。具体位置信息还需要调试。

    char arr1[50];WCHAR arr2[50];//打印当前分数RECT r = { 400, 200, 600, 300 };drawtext(_T("当前分数:"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 300, 600, 350 };sprintf(arr1, "%d", Score);Change(arr2, arr1);drawtext(arr2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

   读取历史最高分

        我们在这里便可以用C语言的文件处理函数。首先我们要理解../表示上一级目录,默认当前目录为.cpp所在的目录。当我们第一次运行游戏的时候,可能还没有存储历史最高分数的文件,便可以采用fopen("../date.text","a+")方式,不存在就创建一个。

        然后fscanf(pf, "%d", &max)读取数据,第一次可能会读取失败,就初始化为0.最终的代码如下。

FILE* pf=fopen("../date.text","a+");if (pf == NULL){perror("Init:fopen");return;}if (fscanf(pf, "%d", &max) == EOF){max = 0;}fclose(pf);

        一定记得fclose,关闭文件,否则后续的文件操作就不会成功。

        综上而言,我们整个的代码如下。

//初始化
void Init(SP *p)
{initgraph(600, 800);// EX_SHOWCONSOLE  控制台setbkcolor(WHITE);cleardevice();score=0;//设置边界线line(400, 0, 400, 800);char arr1[50];WCHAR arr2[50];//打印当前分数RECT r = { 400, 200, 600, 300 };drawtext(_T("当前分数:"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 300, 600, 350 };sprintf(arr1, "%d", Score);Change(arr2, arr1);drawtext(arr2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//打印最高分数r = { 400, 0, 600, 200 };drawtext(_T("最高分数:"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 100, 600, 200 };sprintf(arr1, "%d", max);Change(arr2, arr1);drawtext(arr2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 400, 600, 450 };drawtext(_T("←向左移动→向右移动"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 450, 600, 500 };drawtext(_T("↑旋转↓快速下落"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 500, 600, 550 };drawtext(_T("Esc退出 空格暂停"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//打印版权r = { 400, 700, 600, 800 };drawtext(_T("版权所有CSDN卫胡迪"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);}

struct project

        然后我们可以定义一个结构体 struct project,用这个结构体来保存我们所需要的变量,在这里肯定会有许多人疑惑为什么要定义结构体,我一个个变量用的十分好?在这里我想说的是,定义结构体简化了我们后序设计函数的复杂度。我们设计函数参数的时候就不需要过多的考虑参数,只需要传递一个结构体指针,便可以在任何地方修改结构体内的值。这个只有当自己写完一次的时候才会恍然大悟,我刚开始也是不理解,直到自己写过几个项目后才恍然大悟,如果你目前不接受这个观点,等到写完后接受的话,就在评论区打出我悟了!

typedef struct project
{struct block BackGropund[20][10];//背景IMAGE original[7];//原始六色方块图片,最后一个为背景struct block  Bag[7][4][4];//原始方块包struct block Down[4][4];//下落方块enum STATUS status;//状态int SleepTime;//速度int Score;//分数int max;//最高分ExMessage m;
}SP;

        这里面有几个变量,max:历史最高分,Score :当前分数, SleepToime: 速度,第一次写游戏的读者可能不太清楚,速度和休眠时间有什么关系? 在我们运行俄罗斯方块游戏的时候,我们必定会写一个循环来不断地执行程序,直到游戏结束。那方块下落的速度如何体现呢? 我们便可以设定一定的时间间隔来实现,暂停500ms(ms为毫秒)后,方块下落一格打印,暂停500ms后,方块下落一个打印,如此循环就实现了方块的下落操作。说个题外话,为什么我们要主动的暂停500ms,程序的运行需要消耗时间,即使我们不主动暂停,下次打印也会有一定时间。但现在电脑的运行太快了,运行完我们写的几百行代码可能才几毫秒,远远超过了正常人的反应速度。

        由上面的分析我们可以明白,我们核心游戏下落的代码是个循环,具体为while还是for根据个人习惯,那么我们便可以设计一个循环判断变量,当这个数为0时就结束,为1的时候就继续,但这种代码的可读性较差,我们只知道0的时候结束,却不理解0时什么含义.于是我们便可以利用枚举常量来定义状态。如下代码。这样我们判断循环的结束便可以写为 while(status == OK),这样写本质还是与0,1进行比较,但他的代码可读性大大提升,代码的健壮性更好。

enum STATUS
{OK,ESC,GAME_OVER
};

        ExMessage是用来存储键盘信息的,我们上下左右的实现离不开他。可以用peekmessage函数读取键盘信息并存储在m中。

方块结构体

        相信大家一定看到了上面的多个数组,他们都是用来存储方块信息的。下面我们来一一介绍。

        首先我们可以看出他都是struct block结构体的数组,我们来看下这个结构体。

struct block
{IMAGE img;enum  block_status flag;int x;int y;
};

        每个方块都有其对应的位置,所以x,y便是其位置信息。IMAGE是Easyx中的一种变量,用来表示图片的信息,然后就是flag表示当前方块的状态,是下落,还是已经下落完,成为背景。同理,我们可以枚举来表示状态。

        这里的LAND表示已经成为背景,其他几种要在后面讲解。

enum  block_status
{EXIST,//存在NO,//不存在LAND,ROATE//旋转点
};

        首先根据我们刚才的分析,可以将游戏输出屏幕用struct block BackGropund[20][10];数组来表示。这样我们后续的消除就方便许多。

加载六色方块

        接着我们看下最重要的下落方块。我们知道最初的方块有七个如下。

       首先我们可以了解上面的图像是不同的方块组成的,我们因此想要绘制方块首先要将方块加载如文件。此时就保存在IMAGE original[7]里面。

        应为我们每个的方块大小为40*40,我们就可以在加载图片的时候缩放大小,用loadimage函数,loadimage(&img0, L"../橙色方块.jpg", 40, 40);格式。在这里../表明在.cpp上一级目录下的橙色方块.jpg图片,这个要配置好否则就会报错。接下来就是依次加载7种方块了,特殊的第七种方块是背景方块。

        在这里要记住IMAGE img0;变量只能加载一个图片,加载多个图片会出现黑屏,每加载一个图片,创建一个变量名。

        于是总的加载代码如下,在这里我们采用了一个LoadImg函数进行封装,防止主函数过长,不利于观察主题逻辑。

//加载方块包
void LoadImg(SP* p)
{IMAGE img0;loadimage(&img0, L"../橙色方块.jpg", 40, 40);p->original[0] = img0;IMAGE img1;loadimage(&img1, L"../紫色方块.jpg", 40, 40);p->original[1] = img1;IMAGE img2;loadimage(&img2, L"../红色方块.jpg", 40, 40);p->original[2] = img2;IMAGE img3;loadimage(&img3, L"../黄色方块.jpg", 40, 40);p->original[3] = img3;IMAGE img4;loadimage(&img4, L"../绿色方块.jpg", 40, 40);p->original[4] = img4;IMAGE img5;loadimage(&img5, L"../蓝色方块.jpg", 40, 40);p->original[5] = img5;IMAGE img6;loadimage(&img6, L"../背景.jpg", 40, 40);p->original[6] = img6;}

初始化背景

        我们的背景是10*20的方格,所以我们可以用数组进行保存。对背景进行初始化也十分简单。只需要将每一个方块的背景改为original[6],我们刚才加载的背景方块就可以了。然后方块的状态设置为NO。

        在这里我们要注意的是[i1][i2]表示第i1行,第i2列的方块,与坐标对应就是(i2*40,i1*40),注意数组的表示方式与我们日常坐标的表示方式不一样。i2对应X,i1对应Y。

//初始化背景
int i1 = 0, i2 = 0;
for (i1 = 0; i1 < 20; i1++)
{for (i2 = 0; i2 < 10; i2++){p->BackGropund[i1][i2].img = p->original[6];p->BackGropund[i1][i2].flag = NO;}
}

        我们可以简单的打印背景看看效果如何。我们要用到Easyx中的putimage。将对应下标输入如下。FlushBatchDraw();是刷新缓存区,让画面到屏幕上。

//打印背景
void PrintBackGround(SP* p)
{int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){putimage(i2 * 40, i1 * 40, &p->BackGropund[i1][i2].img);}}FlushBatchDraw();
}

        效果图如下

加载七个方块

        我们可以将他们都放在4*4的方格中,这样有利于我们统一的操作。然后我们就是存一份七个方块的数据,方便我们每次使用就不用在创建了,直接复制即可。

        接着要做的就是在4*4的方块中规划每个方块的位置。大家可以自行画。经过分析七个方块的位置如下。

        然后就是加载这七个的位置坐标。我们以第一个长条为例。

        我们遍历4*4方块内的每一个方块,如果为我们要的红色区域就把方块的状态改为EXIST存在,否则就改为NO,同时我们把每个方块的坐标进行更改。因为这是用来初始化每一次下落方块的,我们的y坐标一开始不能全为正,要有部分为负才可以。具体可以根据设计调。我这里采用的是全部为负,往下降落一次出现一行。

//长形
for (i1 = 0; i1 < 4; i1++)
{for (i2 = 0; i2 < 4; i2++){if (i2 == 1)p->Bag[0][i1][i2].flag = EXIST;elsep->Bag[0][i1][i2].flag = NO;p->Bag[0][i1][i2].x = i2 + 1;p->Bag[0][i1][i2].y = i1 - 3;}
}

        同理我们可以将七个方块都加载如Bag中,在这里我们先不急着加载剩下的方块,我们来讨论下方块旋转的话题。这是我们游戏核心的存在,也是最难做的一块。

方块旋转
        方法一

        我们要做的就是将对应的方块旋转90度,但这有很多做法。我们首先了解最为简单的。

如果我们将4*4的方块旋转90度,其内部的图像不就旋转了90度么。我们以下面的图像为例。

        那么如何做呢?我换个图让大家更清楚些。

        旋转后就是将行变列,列变行。我们可以创建个历史数组,然后在赋值给原来的数组。注意坐标不是原来的坐标,要进行一定的变换。

        1的坐标为(x,y),那么1旋转后的坐标为(x+3,y).

        2的坐标为(x,y),那么2旋转后的坐标为(x+2,y+1).

         3的坐标为(x,y),那么3旋转后的坐标为(x+1,y+2).

        4的坐标为(x,y),那么4旋转后的坐标为(x,y+3).

        以此类推我们不难发现新的坐标和原来的坐标存在等差数列的关系,于是我们便可以得到如下的代码。

	struct block tmp[4][4];int x = p->Down[0][0].x, y = p->Down[0][0].y;for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){tmp[i2][3-i1] = p->Down[i1][i2];tmp[i2][3 - i1].x = x + 3 - i1;tmp[i2][3 - i1].y = y + i2;}}

        这样写比较方便但有个问题是旋转幅度比较大。我们仔细看上面的旋转图可以发现有些不自然,第二到第三张图片,图片整体的位置提高了。相对而言有些不自然。

方法二

        这个方法十分简单,既然要旋转后的位置图片信息,我提前准备好不就可以了么。

        我们不用想他如何变换的,旋转一次,一次选第几个图片,不够这种方法要增加额外的变量保存旋转的次数, t=++t%4;r然后根据t来选取图像,再来初始化下降方块的坐标和图片信息。

        这种方法的旋转显然是可以尽最大努力达到自然的状态,但工作量有些大了!4*7我们要制作28个4*4方块的信息。感兴趣的读者也可以自行尝试,在这里就不赘述了。

    方法三

        这种方法要用到数学中旋转的公式,一个点绕另一个点顺时针旋转90度,其坐标是确定的。假设我们绕点a(x0,y0)顺时针旋转90度,b(a,b)将变为(x0+y0-b,y0-x0+a)。感兴趣的读者可以看以下数学证明。

        于是我们便可以设置旋转点,然后将方块都绕这个点旋转90度即可。对于长条方块而言,选择的旋转点是(1,2),我们便可以将这个方块状态标为ROATE。于是我们在初始化的时候假设ROATE,于是旋转的函数便可以采用如下的方法。为了方便统一将旋转点放在要下落的方块内,而不是NO中

//长形
for (i1 = 0; i1 < 4; i1++)
{for (i2 = 0; i2 < 4; i2++){if (i2 == 1)p->Bag[0][i1][i2].flag = EXIST;elsep->Bag[0][i1][i2].flag = NO;if(i1==2 && i2==1)p->Bag[0][i1][i2].flag = ROATE;p->Bag[0][i1][i2].x = i2 + 1;p->Bag[0][i1][i2].y = i1 - 3;}
}
//找到旋转点
for (i1 = 0; i1 < 4; i1++)
{for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == ROATE){dx = p->Down[i1][i2].x;dy = p->Down[i1][i2].y;goto end;}}
}
end:
//正方形直接退出
if (i1 == 1 && i2 == 1)return;//临时旋转数组
for (i1 = 0; i1 < 4; i1++)
{for (i2 = 0; i2 < 4; i2++){tmp[i1][i2] = p->Down[i1][i2];tmp[i1][i2].x = dx+dy- p->Down[i1][i2].y;tmp[i1][i2].y = dy-dx+ p->Down[i1][i2].x;}
}

        当然这种方法的自然度不如第二种,但代码量减少了许多。

        同理可以写出剩下6个的Bag,每个方块。在这里就不一一展开了。

下落方块

        我们在此之前完成了7个方块的准备,在这了创造一个下落方块便十分简单。这个功能在其他的地方会用到,我们就将他封装成一个函数。

        在这里我们要用到rand()函数,我们可以在主函数中初始化一次种子srand((unsigned int)time(NULL));然后再获得随机数。我们可以定义两个变量。t代表是那个方块,colour代表什么颜色。但注意颜色这里只加载了6个,方块有7种,后序读者可以根据自己设计自行添加。于是便可以得到如下代码。

 //创造下落方块
void CreatBag(SP* p)
{int t = rand() % 7;int colour = (t + rand()) % 6;int i1, i2;for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2] = p->Bag[t][i1][i2];//不同方块不同if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag==ROATE){p->Down[i1][i2].img = p->original[colour];}}}}

        循环中的if判断也可以写为p->Down[i1][i2].flag != NO,两种方式都可以。

我们可以将上述的代码封装在一个初始化Init的函数中,传入struct project结构体指针,然后进行操作,在Init中还可以再次封装,这里就不一一叙述了,总体而言处理后的代码如下。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<graphics.h>
#include<time.h>enum  block_status
{EXIST,//存在NO,//不存在LAND,ROATE//旋转点
};
struct block
{IMAGE img;enum  block_status flag;int x;int y;
};enum STATUS
{OK,ESC,GAME_OVER
};typedef struct project
{struct block BackGropund[20][10];//背景IMAGE original[7];//原始六色方块图片,最后一个为背景struct block  Bag[7][4][4];//原始方块包struct block Down[4][4];//下落方块enum STATUS status;//状态int SleepTime;//速度int Score;//分数int max;//最高分ExMessage m;
}SP;//加载方块包
void LoadImg(SP* p)
{IMAGE img0;loadimage(&img0, L"../橙色方块.jpg", 40, 40);p->original[0] = img0;IMAGE img1;loadimage(&img1, L"../紫色方块.jpg", 40, 40);p->original[1] = img1;IMAGE img2;loadimage(&img2, L"../红色方块.jpg", 40, 40);p->original[2] = img2;IMAGE img3;loadimage(&img3, L"../黄色方块.jpg", 40, 40);p->original[3] = img3;IMAGE img4;loadimage(&img4, L"../绿色方块.jpg", 40, 40);p->original[4] = img4;IMAGE img5;loadimage(&img5, L"../蓝色方块.jpg", 40, 40);p->original[5] = img5;IMAGE img6;loadimage(&img6, L"../背景.jpg", 40, 40);p->original[6] = img6;}//加载方块的模板
void LoadBag(SP* p)
{int i1 = 0, i2 = 0;//长形for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i2 == 1)p->Bag[0][i1][i2].flag = EXIST;elsep->Bag[0][i1][i2].flag = NO;if(i1==2 && i2==1)p->Bag[0][i1][i2].flag = ROATE;p->Bag[0][i1][i2].x = i2 + 1;p->Bag[0][i1][i2].y = i1 - 3;}}//正方形for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((i2 == 1 || i2 == 2) && (i1==1||i1==2))p->Bag[1][i1][i2].flag = EXIST;elsep->Bag[1][i1][i2].flag = NO;if (i1 == 1 && i2 == 1)//正方形特殊处理p->Bag[1][i1][i2].flag = ROATE;p->Bag[1][i1][i2].x = i2 + 1;p->Bag[1][i1][i2].y = i1 - 3;}}//山形for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 3 && i2 < 3 || i1 == 2 && i2 == 1)p->Bag[2][i1][i2].flag = EXIST;elsep->Bag[2][i1][i2].flag = NO;if (i1 == 2 && i2 == 1)p->Bag[2][i1][i2].flag = ROATE;p->Bag[2][i1][i2].x = i2 + 1;p->Bag[2][i1][i2].y = i1 - 3;}}//右七for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 3 && i2 < 3 || i1 == 2 && i2 == 2)p->Bag[3][i1][i2].flag = EXIST;elsep->Bag[3][i1][i2].flag = NO;if (i1 == 2 && i2 == 2)p->Bag[3][i1][i2].flag = ROATE;p->Bag[3][i1][i2].x = i2 + 1;p->Bag[3][i1][i2].y = i1 - 3;}}//左七for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 3 && i2 > 0 || i1 == 2 && i2 == 1)p->Bag[4][i1][i2].flag = EXIST;elsep->Bag[4][i1][i2].flag = NO;if (i1 == 2 && i2 == 1)p->Bag[4][i1][i2].flag = ROATE;p->Bag[4][i1][i2].x = i2 + 1;p->Bag[4][i1][i2].y = i1 - 3;}}//右Zfor (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 2 && i2 < 2 || i1 == 1 && (i2 == 1 || i2 == 2))p->Bag[5][i1][i2].flag = EXIST;elsep->Bag[5][i1][i2].flag = NO;if (i1 == 2 && i2 == 1)p->Bag[5][i1][i2].flag = ROATE;p->Bag[5][i1][i2].x = i2 + 1;p->Bag[5][i1][i2].y = i1 - 3;}}//左Zfor (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (i1 == 2 && i2 > 1 || i1 == 1 && (i2 == 1 || i2 == 2))p->Bag[6][i1][i2].flag = EXIST;elsep->Bag[6][i1][i2].flag = NO;if (i1 == 2 && i2 == 2)p->Bag[6][i1][i2].flag = ROATE;p->Bag[6][i1][i2].x = i2 + 1;p->Bag[6][i1][i2].y = i1 - 3;}}}
//字符转换
void Change(WCHAR* des, char* src)
{while (*des++ = *src++);
}//打印背景
void PrintBackGround(SP* p)
{int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){putimage(i2 * 40, i1 * 40, &p->BackGropund[i1][i2].img);}}FlushBatchDraw();
}
//打印分数信息
void  PrintMessage(SP* p)
{char arr1[50];WCHAR arr2[50];//打印当前分数RECT r = { 400, 200, 600, 300 };drawtext(_T("当前分数:"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 300, 600, 350 };sprintf(arr1, "%d", p->Score);Change(arr2, arr1);drawtext(arr2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//打印最高分数r = { 400, 0, 600, 200 };drawtext(_T("最高分数:"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 100, 600, 200 };sprintf(arr1, "%d", p->max);Change(arr2, arr1);drawtext(arr2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 400, 600, 450 };drawtext(_T("←向左移动→向右移动"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 450, 600, 500 };drawtext(_T("↑旋转↓快速下落"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 400, 500, 600, 550 };drawtext(_T("Esc退出 空格暂停"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//打印版权r = { 400, 700, 600, 800 };drawtext(_T("版权所有CSDN卫胡迪"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
//打印下落方块
void PrintDown(SP* p)
{int i1, i2;for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag==ROATE){putimage(p->Down[i1][i2].x * 40, p->Down[i1][i2].y * 40, &p->Down[i1][i2].img);}}}
}
//打印全部内容
void Print(SP* p)
{PrintBackGround(p);PrintDown(p);solidrectangle(400, 0, 600, 800);PrintMessage(p);FlushBatchDraw();
}//创造下落方块
void CreatBag(SP* p)
{int t = rand() % 7;int colour = (t + rand()) % 6;int i1, i2;for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2] = p->Bag[t][i1][i2];//不同方块不同if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE){// p->Down[i1][i2].flag != NOp->Down[i1][i2].img = p->original[colour];}}}}//初始化
void Init(SP *p)
{initgraph(600, 800);// EX_SHOWCONSOLE  控制台//初始配置setlinecolor(BLACK);setlinestyle(PS_SOLID | PS_JOIN_BEVEL, 3);setbkcolor(WHITE);settextcolor(BLACK);settextstyle(20, 0, L"楷体");setfillcolor(WHITE);setbkmode(TRANSPARENT);//透明文字cleardevice();srand((unsigned int)time(NULL));BeginBatchDraw();//防止闪屏//结构体初始化p->Score = 0;p->SleepTime = 500;p->status = OK;FILE* pf=fopen("../date.text","a+");if (pf == NULL){perror("Init:fopen");return;}if (fscanf(pf, "%d", &p->max) == EOF){p->max = 0;}fclose(pf);//加载图片,基础方块LoadImg(p);//加载七个方块LoadBag(p);//创建方块CreatBag(p);//设置边界线line(400, 0, 400, 800);//初始化背景int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){p->BackGropund[i1][i2].img = p->original[6];p->BackGropund[i1][i2].flag = NO;}}//打印背景PrintBackGround(p);//打印提示版权信息PrintMessage(p);FlushBatchDraw();
}
int main()
{SP a;//初始化Init(&a);return 0;
}

        对于这种代码可以采用逐步深入的方式,看好注释,然后从上往下读,遇到函数往上找定义。相信经过上面的分析,这段代码看起来是十分简单的。

        接下来是游戏的核心,循环处理了。读者可以休息会再看。

游戏进行

        首先我们进行循环判断的条件就十分的清楚,只需要判断,结构体a的状态就可以了。然后每次循环我们主动的休眠500ms,大家也可以自行调试时间。时间越短所需要的反应越快。整体的逻辑代码如下。

int main()
{SP a;//初始化Init(&a);while (a.status == OK){//休眠Sleep(a.SleepTime);}return 0;
}

下降判断

        这段代码显然不会是几行就能解决的,我们就可以将他封装在一个函数内。

        遇到LAND或者到达底部

        当我们的方块一直往下掉落的时候,有两者情况停止下落,一种是到达底部,另一种是遇到LAND,LAND就是其他方块到达底部后的状态。

        于是我们便可以遍历检查。

void  DownJudge(SP*p)
{int i1, i2;//检查是否到底for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){//行为y列为xif ((p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag==ROATE)&&(p->BackGropund[p->Down[i1][i2].y+1][p->Down[i1][i2].x].flag == LAND || p->Down[i1][i2].y == 19)){}}}}

        这个的判断条件比较长我们逐步来看,首先判断的是状态为EXIST或者为ROATE,因为我们选取原来EXIST中一点当作旋转点,所以ROATE也是需要判断的一点,然后是后半段判断判断下落方块的y是不是19,或者当前方块的下一个为LAND,如果满足上述判断就代表要进行。复制方块,把EXIST变为LAND的操作。

        复制的操作也十分的简单,如果这个方块的状态满足要求,就复制到背景中,并将背景方块的状态改为LAND,注意如果在某些方块存在,并且y小于0,便代表了游戏结束,把游戏状态改为GAME_OVER,并且直接返回,不进行后序操作。

		//复制方块,变为地for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE){if (p->Down[i1][i2].y < 0){p->status = GAME_OVER;return;}p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x].img = p->Down[i1][i2].img;p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x].flag = LAND;}}}

        如果没有游戏结束,那么便要考虑接下来的操作了。下落一个方块,分数加10分,并且要创建新的方块,我们可以直接调用前面写的CreatBag(p);函数。但我们还有个重要的判断,假如一行全是LAND方块,我们就可以进行消除。所以在这里我们在封装一个函数,检测消除。

消除检测

        我们消除的条件是一行全为LAND,便可以定义一个数组表示每一行是否要消除。于是便有如下代码。需要消除的行赋值为1,不需要赋值为0.如果没有要消除的直接返回,否则进行下面的代码。

//检查删除方块
void CheckDelste(SP* p)
{int arr[20] = {0};int i1 = 0, i2 = 0;int flag = 0;int sum = 0;//查找消除行for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){if (p->BackGropund[i1][i2].flag == LAND){flag = 1;}else{flag = 0;break;}}arr[i1] = flag;sum += arr[i1];}//检查有无消除行if (sum == 0)return;p->Score += 50 * sum;//加分数,一行50分
}

        接下来是处理消除行。我们如何做呢?创建一个临时数组,从后往前遍历,不是消除的复制这一行,是消除的跳过,最后临时数组还有几行全部赋值为背景,最后再打印。完整代码如下。

    //建立临时数组struct block tmp[20][10];int t = 19;for (i1 = 19; i1 >=0; i1--){if (arr[i1] == 0){for (i2 = 0; i2 < 10; i2++){tmp[t][i2] = p->BackGropund[i1][i2];}t--;}}//多余赋值为背景for (i1 = t; i1 >= 0; i1--){for (i2 = 0; i2 < 10; i2++){tmp[t][i2].flag = NO;tmp[t][i2].img = p->original[6];}}//转换for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){p->BackGropund[i1][i2] = tmp[i1][i2];}}Print(p);

        这样可以完成我们的消除操作,但消除行直接一闪而过,为了消除效果更加好,我们增加一个闪烁的效果。整体闪烁三次。

        如果消除行就打印背景方块,然后再打印PrintBackGround(p);泽里的背景方块还未处理,所以消除行还会被打印。暂停150ms,然后刷新缓存区。

	//闪烁功能for (int j = 0; j < 3; j++){int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){if(arr[i1]== 0)putimage(i2 * 40, i1 * 40, &p->BackGropund[i1][i2].img);elseputimage(i2 * 40, i1 * 40, &p->original[6]);}}FlushBatchDraw();Sleep(150);PrintBackGround(p);Sleep(150);FlushBatchDraw();}

正常下落

        如果没有检测到底,就正常下落,y坐标加一。代码如下。

	//正常则下降一格for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2].y += 1;}}//打印图片Print(p);

        总体而言这块的总代码如下。

//检查删除方块
void CheckDelste(SP* p)
{int arr[20] = {0};int i1 = 0, i2 = 0;int flag = 0;int sum = 0;//查找消除行for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){if (p->BackGropund[i1][i2].flag == LAND){flag = 1;}else{flag = 0;break;}}arr[i1] = flag;sum += arr[i1];}//检查有无消除行if (sum == 0)return;p->Score += 50 * sum;//加分数,一行50分//闪烁功能for (int j = 0; j < 3; j++){int i1 = 0, i2 = 0;for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){if(arr[i1]== 0)putimage(i2 * 40, i1 * 40, &p->BackGropund[i1][i2].img);elseputimage(i2 * 40, i1 * 40, &p->original[6]);}}FlushBatchDraw();Sleep(150);PrintBackGround(p);Sleep(150);FlushBatchDraw();}//建立临时数组struct block tmp[20][10];int t = 19;for (i1 = 19; i1 >=0; i1--){if (arr[i1] == 0){for (i2 = 0; i2 < 10; i2++){tmp[t][i2] = p->BackGropund[i1][i2];}t--;}}//多余赋值为背景for (i1 = t; i1 >= 0; i1--){for (i2 = 0; i2 < 10; i2++){tmp[t][i2].flag = NO;tmp[t][i2].img = p->original[6];}}//转换for (i1 = 0; i1 < 20; i1++){for (i2 = 0; i2 < 10; i2++){p->BackGropund[i1][i2] = tmp[i1][i2];}}Print(p);
}
void  DownJudge(SP*p)
{int i1, i2;//检查是否到底for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){//行为y列为xif ((p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE)&&(p->BackGropund[p->Down[i1][i2].y+1][p->Down[i1][i2].x].flag == LAND || p->Down[i1][i2].y == 19)){//复制方块,变为地for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE){if (p->Down[i1][i2].y < 0){p->status = GAME_OVER;return;}p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x].img = p->Down[i1][i2].img;p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x].flag = LAND;}}}//加分数p->Score += 10;//检测是否可以消除CheckDelste(p);//创建新的下落方块CreatBag(p);return;}}}//正常则下降一格for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2].y += 1;}}//打印图片Print(p);
}

检测按键

        写完上面的代码后,我们的游戏一句完成一半了,可以正常的下落。视频如下。

俄罗斯方块半成品

        接下来我们再次封装一个检测按钮的函数,用来实现左移右移与旋转。

int main()
{SP a;//初始化Init(&a);while (a.status == OK){//检测按键CheckKey(&a);//下降处理DownJudge(&a);//休眠Sleep(a.SleepTime);}return 0;
}

        在这里我们要用到peekmessage函数,注意不能用getmessage,这个函数用于获取一个消息。如果当前消息队列中没有,就一直等待。我们要立即返回的就用peekmessage。

        于是我们便可以写出如下的结构。然后对应处理每个按钮即可,为了防止这个代码过于臃肿。可以将每个按钮的操作再分装在一个函数内。

        注意我们在和函数的最后加了两条语句,一个是刷新消息缓存区,一个是初始化当前的虚拟键码,这两步都是为了消除上一次循环的影响。

void CheckKey(SP*p)
{peekmessage(&p->m);if (p->m.vkcode==(VK_DOWN)){}else if (p->m.vkcode == (VK_UP)){}else if (p->m.vkcode == (VK_LEFT)){}else if (p->m.vkcode == (VK_RIGHT)){}else if (p->m.vkcode == (VK_ESCAPE)){}else if (p->m.vkcode == (VK_SPACE)){}p->m.vkcode = 0;flushmessage();
}

左移

        这个操作十分简单只需要将方块的x值减一就可以了,但我们还要检测左移是否合法,不合法就不支持左移。VK_LEFT是虚拟键码,大家可以在官网查询虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

        如下,我们就可以完成一次左移。

void LeftMove(SP* p)
{int i1 = 0, i2 = 0;//全部检查是否合法for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag==ROATE)&&(p->Down[i1][i2].x-1<0 ||p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x - 1].flag==LAND))return;}}for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2].x -= 1;}}
}

        为了效果更加的好,我们在左移后就立即打印,并且停留200ms

else if (p->m.vkcode == (VK_LEFT))
{LeftMove(p);Print(p);Sleep(200);
}

右移

        右移与左移十分的相似,都是要检测是否合法。

void RightMove(SP*p)
{int i1 = 0, i2 = 0;//全部检查一遍for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((p->Down[i1][i2].flag == EXIST || p->Down[i1][i2].flag == ROATE) &&(p->Down[i1][i2].x +1 >9|| p->BackGropund[p->Down[i1][i2].y][p->Down[i1][i2].x +1].flag == LAND))return;}}for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2].x += 1;}}
}

下落

        此时我们要的是方块快速下落,我们就可以将SleepTime 改为60ms,就可以达到快速下落的操作,但我们要在不按↓键的时候将SleepTime改为500;我们可以在其他的分支语句中加上p->SleepTime = 500;也可以将上述的结构简单修改。如下代码。

void CheckKey(SP*p)
{peekmessage(&p->m);if (p->m.vkcode==(VK_DOWN)){p->SleepTime = 60;}else{p->SleepTime = 500;if (p->m.vkcode == (VK_UP)){}else if (p->m.vkcode == (VK_LEFT)){LeftMove(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_RIGHT)){RightMove(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_ESCAPE)){}else if (p->m.vkcode == (VK_SPACE)){}}p->m.vkcode = 0;flushmessage();
}

退出

        这个功能也十分简单就可以实现,将状态改为ESC即可

else if (p->m.vkcode == (VK_ESCAPE))
{p->status = ESC;
}

暂停

        在这里我用不论我用getmessage,还是peekmessage总会出现BUG,我也找不到具体原因,不得已之下换了一种检测按钮的方式.利用其他的函数检测。当对于的虚拟键码存在时便为真。


#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

        当我们第一次按下暂停进入分支,第二次按下退出循环。

else if (KEY_PRESS(VK_SPACE))
{while (!KEY_PRESS(VK_SPACE)){Sleep(50);}
}

        在这里我只能说抱歉,Easyx对于API进行了再次封装,里面的细节看不到,我也找不出问题所在,如果读者有什么解决办法可以发在评论区,不胜感激。当然也不排除这个函数本身的问题。

旋转

        接下来是我们的最重要的操作,将方块进行旋转。首先我们还是可以创建一个临时数组,然后检测合法性,最后在进行复制。

        首先我们要找到旋转点,求出dx,dy,其中正方形要特殊处理。应为旋转点无论旋转几次,他在4*4方格中的位置不变。我们由此就可以判断是否为正方形,如果为正方形就直接退出。

int i1, i2;
int dx, dy;
struct block tmp[4][4];//找到旋转点
for (i1 = 0; i1 < 4; i1++)
{for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == ROATE){dx = p->Down[i1][i2].x;dy = p->Down[i1][i2].y;goto end;}}
}
end:
//正方形直接退出
if (i1 == 1 && i2 == 1)return;

        接下来就是创建临时数组,每个点的坐标关系如上文方块旋转所说。在这里我们不需要将临时数组在4*4方格中的位置变换,因为我们最终看的是方块的坐标,而不是在4*4方格中的位置。

	//临时旋转数组for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){tmp[i1][i2] = p->Down[i1][i2];tmp[i1][i2].x = dx+dy- p->Down[i1][i2].y;tmp[i1][i2].y = dy-dx+ p->Down[i1][i2].x;}}

        接下来就是判断是否合法,不合法直接退出,合法就复制,

//判断是否合法
for (i1 = 0; i1 < 4; i1++)
{for (i2 = 0; i2 < 4; i2++){if ((tmp[i1][i2].flag == EXIST || tmp[i1][i2].flag == ROATE) &&(tmp[i1][i2].x > 9 || tmp[i1][i2].x < 0 || tmp[i1][i2].y>19 || p->BackGropund[tmp[i1][i2].y][tmp[i1][i2].x].flag == LAND))return;}
}//合法则复制
for (i1 = 0; i1 < 4; i1++)
{for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2]= tmp[i1][i2];}
}

        完整代码如下

//旋转方块
void Rorate(SP* p)
{int i1, i2;int dx, dy;struct block tmp[4][4];//找到旋转点for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if (p->Down[i1][i2].flag == ROATE){dx = p->Down[i1][i2].x;dy = p->Down[i1][i2].y;goto end;}}}end://正方形直接退出if (i1 == 1 && i2 == 1)return;//临时旋转数组for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){tmp[i1][i2] = p->Down[i1][i2];tmp[i1][i2].x = dx+dy- p->Down[i1][i2].y;tmp[i1][i2].y = dy-dx+ p->Down[i1][i2].x;}}//判断是否合法for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){if ((tmp[i1][i2].flag == EXIST || tmp[i1][i2].flag == ROATE) &&(tmp[i1][i2].x > 9 || tmp[i1][i2].x < 0 || tmp[i1][i2].y>19 || p->BackGropund[tmp[i1][i2].y][tmp[i1][i2].x].flag == LAND))return;}}//合法则复制for (i1 = 0; i1 < 4; i1++){for (i2 = 0; i2 < 4; i2++){p->Down[i1][i2]= tmp[i1][i2];}}}

        最终我们的按钮检测就可以如下表示。

void CheckKey(SP*p)
{peekmessage(&p->m);if (p->m.vkcode==(VK_DOWN)){p->SleepTime = 60;}else{p->SleepTime = 500;if (p->m.vkcode == (VK_UP)){Rorate(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_LEFT)){LeftMove(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_RIGHT)){RightMove(p);Print(p);Sleep(200);}else if (p->m.vkcode == (VK_ESCAPE)){p->status = ESC;}else if (KEY_PRESS(VK_SPACE)){while (!KEY_PRESS(VK_SPACE)){Sleep(50);}}}//消除影响p->m.vkcode = 0;flushmessage();
}

        到这里我们就完成了游戏的大部分了,现在游戏就可以正常的运行了,等不下的读者可以先玩了。下面是最后一部分结尾工作了。

游戏结束

        同理我们可以封装成一个函数解决,减少主函数的复杂度。

int main()
{SP a;//初始化Init(&a);while (a.status == OK){//检测按键CheckKey(&a);//下降处理DownJudge(&a);//休眠Sleep(a.SleepTime);}//游戏结束GameOver(&a);return 0;
}

保存分数

        首先我们要做的就是看看是否创造记录,如果创造记录就保存。

        在这了我们用的是w+刚好可以将原来的文件删除,创建新的文件,保存最高记录。同时为了庆祝,还可以打印些文字,读者可以自行安排。

	settextcolor(RED);settextstyle(50, 0, L"楷体");//创造最高记录if (p->max < p->Score){RECT r = { 0, 300, 600, 400 };drawtext(_T("恭喜你创造了新的记录!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);FILE* pf = fopen("../date.text", "w+");fprintf(pf, "%d", p->Score);fclose(pf);}

        然后可以打印处游戏结束语,为了不直接闪过,我们加个循环检测Enter,让用户主动结束页面。当然在最后不要忘了结束界面    closegraph();。

RECT r = { 0, 500, 600, 600 };
drawtext(_T("欢迎下次再玩。"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 0, 700,600, 800 };
drawtext(_T("请按Enter结束……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
FlushBatchDraw();
//不断检测信息,直到按下enter
ExMessage m = getmessage();
while (m.vkcode != VK_RETURN)
{m = getmessage();
}closegraph();

        读者可自行添加其他文字,总的代码如下

//游戏结束
void GameOver(SP*p)
{settextcolor(RED);settextstyle(50, 0, L"楷体");//创造最高记录if (p->max < p->Score){RECT r = { 0, 300, 600, 400 };drawtext(_T("恭喜你创造了新的记录!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);FILE* pf = fopen("../date.text", "w+");fprintf(pf, "%d", p->Score);fclose(pf);}RECT r = { 0, 500, 600, 600 };drawtext(_T("欢迎下次再玩。"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);r = { 0, 700,600, 800 };drawtext(_T("请按Enter结束……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);FlushBatchDraw();//不断检测信息,直到按下enterExMessage m = getmessage();while (m.vkcode != VK_RETURN){m = getmessage();}closegraph();
}

        到这里我们的代码就写完了。十分的不容易,感谢你可以读到此处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/299155.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式总结-原型设计模式

原型设计模式 模式动机模式定义模式结构模式分析深拷贝和浅拷贝原型模式实例与解析实例一&#xff1a;邮件复制&#xff08;浅克隆&#xff09;实例二&#xff1a;邮件复制&#xff08;深克隆&#xff09; 模式动机 在面向对象系统中&#xff0c;使用原型模式来复制一个对象自…

vue使用iview导航栏Menu activeName不生效

activeName不生效 一、问题一、解决方案&#xff0c; 一、问题 根据ivew官网的提示&#xff0c;设置了active-name和open-names以后&#xff0c;发现不管是设置静态是数据还是设置动态的数据&#xff0c;都不生效 一、解决方案&#xff0c; 在设置动态名称的时候&#xff0c…

Centos7环境下安装MySQL8详细教程

1、下载mysql安装包 下载哪个版本&#xff0c;首先需要确定一下系统的glibc版本&#xff0c;使用如下命令&#xff1a; rpm -qa | grep glibc ​​​​​​​ 2、检查是否安装过mysql ps:因为以前用yum安装过&#xff0c;所以先用yum卸载。如果不是此方式或者没安装过则跳过…

基于SpringBoot Vue求职招聘系统

一、&#x1f4dd;功能介绍 基于SpringBoot Vue招聘系统的设计与实现 角色&#xff1a;管理员、企业、用户 管理员&#xff1a;管理员进入主页面&#xff0c;主要功能包括对个人中心、企业管理、用户管理、岗位类型管理、招聘信息管理、应聘记录管理、留言反馈、系统管理等进…

笛子基础入门

文章目录 1.符号2. 笛子指法说明3. 曲谱3.1 年轮3.2 生生世世爱 1.符号 笛子简谱&#xff1a;由七个基本数字组成&#xff08;1234567&#xff09;&#xff1b;高音在数字上面加一“.”&#xff0c;低音则在下面加一“.”&#xff0c;不加点则是中音&#xff1b;数字后面加横线…

Linux从入门到精通 --- 2.基本命令入门

文章目录 第二章&#xff1a;2.1 Linux的目录结构2.1.1 路径描述方式 2.2 Linux命令入门2.2.1 Linux命令基础格式2.2.2 ls命令2.2.3 ls命令的参数和选项2.2.4 ls命令选项的组合使用 2.3 目录切换相关命令2.3.1 cd切换工作目录2.3.2 pwd查看当前工作目录2.4 相对路径、绝对路径和…

基于MPPT的风力机发电系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1风能与风力发电机模型 4.2风力机功率特性与最大功率点 4.3 MPPT 5.完整工程文件 1.课题概述 基于MPPT的风力机发电系统simulink建模与仿真。MPPT使用S函数编写实现。基于最大功率点跟踪&#xff08…

日志服务 HarmonyOS NEXT 日志采集最佳实践

作者&#xff1a;高玉龙&#xff08;元泊&#xff09; 背景信息 随着数字化新时代的全面展开以及 5G 与物联网&#xff08;IoT&#xff09;技术的迅速普及&#xff0c;操作系统正面临前所未有的变革需求。在这个背景下&#xff0c;华为公司自主研发的鸿蒙操作系统&#xff08…

网络编程套接字应用分享【Linux C/C++ 】【UDP应用 | TCP应用 | TCP线程池小项目】

目录 前提知识 1. 理解源ip&#xff0c;目的ip和Macip 2. 端口号 3. 初识TCP&#xff0c;UDP协议 4. 网络字节序 5. socket 编程 sockaddr类型 一&#xff0c;基于udp协议编程 1. socket——创建套接字 2. bind——将套接字强绑定 3. recvfrom——接受数据 4. s…

基于jsp+Spring boot+mybatis的图书管理系统设计和实现

基于jspSpring bootmybatis的图书管理系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获…

【精品教程】护网HVV实战教程资料合集(持续更新,共20节)

以下是资料目录&#xff0c;如需下载&#xff0c;请前往星球获取&#xff1a; 01-HW介绍.zip 02-HTTP&Burp课程资料.zip 03-信息收集_3.zip 04-SQL注入漏洞_2.zip 05-命令执行漏洞.zip 06-XSS漏洞.zip 07-CSRF.zip 08-中间件漏洞.zip 09-SSRF.zip 10-XXE.zip 11-Java反序列…

攻防世界 wife_wife

在这个 JavaScript 示例中&#xff0c;有两个对象&#xff1a;baseUser 和 user。 baseUser 对象定义如下&#xff1a; baseUser { a: 1 } 这个对象有一个属性 a&#xff0c;其值为 1&#xff0c;没有显式指定原型对象&#xff0c;因此它将默认继承 Object.prototype。 …

彩虹聚合DNS管理系统,附带系统搭建教程

聚合DNS管理系统&#xff0c;可以实现在一个网站内管理多个平台的域名解析&#xff0c;目前已支持的域名平台有&#xff1a;阿里云、腾讯云、华为云、西部数码、CloudFlare。 本系统支持多用户&#xff0c;每个用户可分配不同的域名解析权限&#xff1b;支持API接口&#xff0…

CSS设置字体样式

目录 前言&#xff1a; 1.font-family&#xff1a; 2.font-style&#xff1a; 3.font-weight&#xff1a; 4.font-size&#xff1a; 5.font-variant&#xff1a; 6.font&#xff1a; 前言&#xff1a; 在网页中字体是重要的组成部分&#xff0c;使用好字体可以让网页更…

17.应用负载压力测试

早些点&#xff0c;下午题考&#xff0c;最近几年出现的少&#xff1b; 备考较为简单&#xff1b;历年真题相似度高&#xff1b; 主要议题&#xff1a; 1.负载压力测试概述 注意这些测试细微的差别&#xff1b; 负载测试和压力测试的方法比较相似&#xff0c;但是目的不同&a…

HTML——5.表单、框架、颜色

一、表单 HTML 表单用于在网页中收集用户输入的数据&#xff0c;例如登录信息、搜索查询等。HTML 提供了一系列的表单元素&#xff0c;允许用户输入文本、选择选项、提交数据等。 <!DOCTYPE html> <html lang"en"> <head> <meta charset&q…

Git 如何去使用

目录 1. Git暂存区的使用 1.1. 暂存区的作用 1.2. 暂存区覆盖工作区&#xff08;注意&#xff1a;完全确认覆盖时使用&#xff09; 1.3. 暂存区移除文件 1.4. 练习 2. Git回退版本 2.1. 概念 2.2. 查看提交历史 2.3. 回退命令 2.4. 注意 3. Git删除文件 3.1. 需求 …

软件设计师-案例分析2

文章目录 除09-1520上-结构化分析20上-数据库分析20上-面向对象分析与设计 除09-15 20上-结构化分析 问题1&#xff1a; 问题2&#xff1a; 问题3&#xff1a; 问题4&#xff1a; 回答1&#xff1a;E1检测设备&#xff1b; E2管理员&#xff1b;E3检测业务员&#xff1b;…

动态属性的响应式问题和行内编辑的问题

动态属性的响应式问题 通过点击给目标添加动态数据&#xff0c;该数据不具备响应式特性 如下图&#xff1a; 点击编辑&#xff0c;前面的数据框会变成输入框&#xff0c;点取消会消失 // 获取数据 async getList () {const res await xxxthis.list res.data.rows// 1. 获…

WindowsPowerShell安装配置Vim的折腾记录

说明 vim一直以来都被称为编辑器之神一样的存在。但用不用vim完全取决于你自己&#xff0c;但是作为一个学计算机的同学来说&#xff0c;免不了会和Linux打交道&#xff0c;而大部分的Linux操作系统都预装了vim作为编辑器&#xff0c;如果是简单的任务&#xff0c;其实vim只要会…