C 实现植物大战僵尸(四)
C 实现植物大战僵尸(四)
C 实现植物大战僵尸,完结撒花(还有个音频稍卡顿的性能问题,待有空优化解决)。目前基本的功能模块已经搭建好了,感兴趣的友友可自行尝试编写后续游戏内容
因为 C 站不能上传动图,所以游戏实际效果可看后续文章更新,插一条试玩视频
后面项目全部源代码会上传至 C 站(待上传)
十三 实现僵尸吃植物
实现和原 UP 有差异,僵尸捕获植物感觉很奇怪,不如设计成植物同样有血量,当植物血量为 0 时,植物死亡
调整植物和僵尸结构体,以及增加变量
/* 僵尸相关结构和变量 */
#define MAX_ZOMBIE_NUM 10
#define MAX_ZOMBIE_DEAD_PIC_NUM 10
#define MAX_ZOMBIE_EAT_PIC_NUM 21
#define MAX_ZOMBIE_PIC_NUM 22
typedef struct Zombie {int x; //当前 X 轴坐标int y; //当前 Y 轴坐标int frameId; //当前图片帧编号int speed; //僵尸移动的速度int row; //僵尸所在行int blood; //默认僵尸血条为 100bool isDead; //僵尸是否死亡bool isEating; //僵尸是否在吃植物, 这些状态改用枚举更好, 待优化bool used; //是否在使用
} Zombie;
Zombie zombies[MAX_ZOMBIE_NUM];
IMAGE imgZombies[MAX_ZOMBIE_PIC_NUM];
IMAGE imgDeadZombies[MAX_ZOMBIE_DEAD_PIC_NUM];
IMAGE imgZombiesEat[MAX_ZOMBIE_EAT_PIC_NUM];/* 植物相关结构和变量 */
typedef struct Plant // 植物结构体
{int type; //植物类型, -1 表示草地int frameId; //表示植物摆动帧int blood; //植物血量
} Plant;
游戏初始化接口 gameInit,加载图片至内存
for (int i = 0; i < MAX_ZOMBIE_EAT_PIC_NUM; ++i) //加载僵尸吃植物图片
{sprintf(name, "res/zm_eat/0/%d.png", i + 1);loadimage(&imgZombiesEat[i], name);
}
游戏更新窗口接口,渲染图片至输出窗口
for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //渲染僵尸
{if (zombies[i].used) {if (zombies[i].isDead) putimagePNG(zombies[i].x, zombies[i].y + 30, &imgDeadZombies[zombies[i].frameId]);else if (zombies[i].isEating) putimagePNG(zombies[i].x, zombies[i].y + 30, &imgZombiesEat[zombies[i].frameId]);else putimagePNG(zombies[i].x, zombies[i].y + 30, &imgZombies[zombies[i].frameId]);}
}
更新游戏属性的接口,增加 eatPlants
/* 更新游戏属性的接口 */
void updateGame()
{updatePlantsPic();createSunshine();updateSunshine();createZombie();updateZombie();shoot();updateBullets();collsionCheck();eatPlants();
}
/* 移除死亡的植物 */
Plant* plantDeath(Plant* plant)
{assert(plant);if (plant->type == PEA) //释放对应种植植物内存free((PeaShooter*)plant);else if (plant->type == SUNFLOWER)free((SunFlower*)plant);Grass* grassPtr = (Grass*)calloc(1, sizeof(Grass)); //重置为草地assert(grassPtr);grassPtr->plant.type = -1;return (Plant*)grassPtr;
}/* 僵尸吃植物接口 */
void eatPlants()
{PeaShooter* peaShooter = NULL;int row = 0, plantX = 0, zombieCurrX = 0;for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //遍历是否存在僵尸{if (zombies[i].used && !zombies[i].isDead) //僵尸正在使用中, 且存活{row = zombies[i].row;for (int j = 0; j < GRASS_GRID_COL; ++j) //遍历当前行是否存在植物{if (plants[row][j]->type >= PEA) {plantX = GRASS_LEFT_MARGIN + j * GRASS_GRID_WIDTH + 5; zombieCurrX = zombies[i].x + 80;if (zombieCurrX > plantX + 10 && zombieCurrX < plantX + 60) //当僵尸已经到达植物附近{zombies[i].isEating = true;plants[row][j]->blood -= 1; //植物扣血if (plants[row][j]->blood <= 0) //植物被杀死{plants[row][j] = plantDeath(plants[row][j]); //移除死亡的植物zombies[i].frameId = 0;zombies[i].isEating = false; //僵尸解除吃植物状态}}}}}}
}
最后更新僵尸状态,在这里进行帧处理
void updateZombie()
{static int CallCnt = 0; //延缓函数调用次数if (++CallCnt < 3) return;CallCnt = 0;for (int i = 0; i < MAX_ZOMBIE_NUM; ++i){if (zombies[i].used){if (zombies[i].isDead){if (++zombies[i].frameId >= MAX_ZOMBIE_DEAD_PIC_NUM) //僵尸死亡则更换死亡帧zombies[i].used = false; //重置僵尸状态}else if (zombies[i].isEating){zombies[i].frameId = ++zombies[i].frameId % MAX_ZOMBIE_EAT_PIC_NUM; //僵尸更换图片帧}else{zombies[i].x -= zombies[i].speed; //僵尸行走zombies[i].frameId = ++zombies[i].frameId % MAX_ZOMBIE_PIC_NUM; //僵尸更换图片帧}if (zombies[i].x < 170) //目前先这样写待优化{printf("GAME OVER !");MessageBox(NULL, "over", "over", 0);exit(0);}}}
}
效果展示
僵尸会对一条道路上的植物进行啃食,在啃食期间会正常受到豌豆射手的攻击,啃食结束后,植物死亡
十四 向日葵生成阳光
实现和原 UP 有差异,想保留原随机阳光球逻辑,所以这里是做了兼容处理逻辑,具体实现如下
向日葵结构体增加变量
enum SUN_SHINE_STATUS { UNUSED, PRODUCE, GROUND, COLLECT };/* 向日葵结构体 */
typedef struct SunFlower
{Plant plant;/* 这里也可以使用数组, 一个向日葵有多个阳光球成员*/SunShineBall sunShine; //向日葵生产的阳光球int timeInterval; //向日葵生产阳光的计时器int status; //向日葵生产的阳光球状态float t; //贝塞尔曲线时间点float speed; //阳光球移动速度vector2 p1, p2, p3, p4; //贝塞尔曲线位置点vector2 pCurr; //当前阳光球的位置
} SunFlower;
实现向日葵生产阳光的接口
需要注意的是在收集向日葵生产太阳球时,需要重置贝塞尔曲线
/* 实现向日葵生产太阳球 */
void produceSunShine()
{SunFlower* sunFlower = NULL;for (int i = 0; i < GRASS_GRID_ROW; ++i) //遍历二维指针数组{for (int j = 0; j < GRASS_GRID_COL; ++j){if (plants[i][j]->type == SUNFLOWER){sunFlower = (SunFlower*)plants[i][j];switch (sunFlower->status){case COLLECT:sunFlower->t += sunFlower->speed; //设置贝塞尔曲线开始时间sunFlower->pCurr = sunFlower->p1 +sunFlower->t * (sunFlower->p4 - sunFlower->p1); //构建贝塞尔曲线if (sunFlower->t > 1) { sunShineVal += 25;sunFlower->status = UNUSED;resetVecotrVal(sunFlower, i, j);}break;case GROUND:if (--sunFlower->timeInterval <= 0) //超时则阳光消失{sunFlower->status = UNUSED; //重置状态sunFlower->timeInterval = MAX_TIME_INTERVAL * (4 + rand() % 5);}break;case PRODUCE:sunFlower->t += sunFlower->speed; //设置贝塞尔曲线开始时间sunFlower->pCurr = calcBezierPoint(sunFlower->t,sunFlower->p1, sunFlower->p2, sunFlower->p3, sunFlower->p4); //构建贝塞尔曲线if (sunFlower->t > 1){sunFlower->t = 0;sunFlower->status = GROUND;sunFlower->timeInterval = MAX_TIME_INTERVAL * (4 + rand() % 5);}break;case UNUSED:if (--sunFlower->timeInterval <= 0){sunFlower->status = PRODUCE;sunFlower->timeInterval = MAX_TIME_INTERVAL * (4 + rand() % 5);}break;default:printf("ERROR");break;}}}}
}
/* 重置贝塞尔曲线坐标值 */
void resetVecotrVal(SunFlower* sunFlower, int x, int y)
{assert(sunFlower);if (sunFlower->status == COLLECT){sunFlower->p1 = sunFlower->pCurr;sunFlower->p4 = vector2(262, 0);sunFlower->t = 0;const float distance = dis(sunFlower->p1 - sunFlower->p4);sunFlower->speed = 1.0 / (distance / 16.0);}else if (sunFlower->status == UNUSED){const int distance = (50 + rand() % 50); //只往右抛即可const int currPlantX = GRASS_LEFT_MARGIN + y * GRASS_GRID_WIDTH + 5;const int currPlantY = GRASS_TOP_MARGIN + x * GRASS_GRID_HIGHT + 10;sunFlower->t = 0;sunFlower->timeInterval = MAX_TIME_INTERVAL * (4 + rand() % 5);sunFlower->speed = 0.05;sunFlower->p1 = vector2(currPlantX, currPlantY);sunFlower->p2 = vector2(sunFlower->p1.x + distance * 0.3, sunFlower->p1.y - 100);sunFlower->p3 = vector2(sunFlower->p1.x + distance * 0.7, sunFlower->p1.y - 100);sunFlower->p4 = vector2(currPlantX + distance, currPlantY +imgPlant[SUNFLOWER][0]->getheight() - imgSunShineBall[0].getheight());}
}
在更新游戏属性的接口中调用
/* 更新游戏属性的接口 */
void updateGame()
{updatePlantsPic();createSunshine();produceSunShine();updateSunshine();createZombie();updateZombie();shoot();updateBullets();collsionCheck();eatPlants();
}
其次,在种植向日葵的时候需要进行新增成员的初始化
/* 种植植物接口, 主要释放草格子内存, 二维指针数组对应位置,指向初始化的植物 */
Plant* growPlants(Plant* plant, int type, int x, int y)
{assert(plant);free((Grass*)plant); //释放该位置草格子内存if (type == PEA) //根据类型初始化 PeaShooter{PeaShooter* peaShooter = (PeaShooter*)calloc(1, sizeof(PeaShooter)); //calloc 函数替代 malloc, 省略 memsetassert(peaShooter);peaShooter->shootSpeed = DEFAULT_SHOOT_TIME; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹peaShooter->plant.blood = 100;return (Plant*)peaShooter;}else if (type == SUNFLOWER) //根据类型初始化 SunFlower{SunFlower* sunFlower = (SunFlower*)calloc(1, sizeof(SunFlower));assert(sunFlower);sunFlower->plant.type = 1;sunFlower->plant.blood = 100;sunFlower->timeInterval = MAX_TIME_INTERVAL * (4 + rand() % 5); //增加游戏随机性/* 初始化贝塞尔曲线 */const int distance = (50 + rand() % 50); //只往右抛即可const int currPlantX = GRASS_LEFT_MARGIN + y * GRASS_GRID_WIDTH + 5;const int currPlantY = GRASS_TOP_MARGIN + x * GRASS_GRID_HIGHT + 10;sunFlower->t = 0;sunFlower->speed = 0.05;sunFlower->p1 = vector2(currPlantX, currPlantY);sunFlower->p2 = vector2(sunFlower->p1.x + distance * 0.3, sunFlower->p1.y - 100);sunFlower->p3 = vector2(sunFlower->p1.x + distance * 0.7, sunFlower->p1.y - 100);sunFlower->p4 = vector2(currPlantX + distance, currPlantY +imgPlant[SUNFLOWER][0]->getheight() - imgSunShineBall[0].getheight());return (Plant*)sunFlower;}
}
在更新阳光球接口,添加新增更新向日葵生产阳光球帧的逻辑
/* 更新随机阳光球接口, 主要更新随机阳光球的图片帧和处理飞跃状态时的 X Y 轴偏移 */
void updateSunshine()
{for (int i = 0; i < MAX_BALLS_NUM; ++i) {if (balls[i].used){if (balls[i].y < balls[i].destination)balls[i].y += 2; //每次移动两个像素else //当阳光下落至目标位置时, 停止移动{if (balls[i].timer < MAX_TIME_INTERVAL) ++balls[i].timer;else balls[i].used = false;}balls[i].frameId = ++balls[i].frameId % SUM_SHINE_PIC_NUM; //修改当前图片帧编号, 并在到达 SUM_SHINE_PIC_NUM 时重置图片帧为 0}else if (balls[i].xOffset) //阳光球处于飞跃状态{if (balls[i].y > 0 && balls[i].x > 262){const float angle = atan((float)(balls[i].y - 0) / (float)(balls[i].x - 262)); //不断调整阳光球的位置坐标balls[i].xOffset = 16 * cos(angle);balls[i].yOffset = 16 * sin(angle);balls[i].x -= balls[i].xOffset;balls[i].y -= balls[i].yOffset;}else{balls[i].xOffset = 0; //阳光球飞至计分器位置, 则将 xOffset 置 0, 且加上 25 积分balls[i].yOffset = 0;sunShineVal += 25;}}}/* 更新向日葵生产的日光 */SunFlower* sunFlower = NULL;for (int i = 0; i < GRASS_GRID_ROW; ++i) //遍历二维指针数组{for (int j = 0; j < GRASS_GRID_COL; ++j){if (plants[i][j]->type == SUNFLOWER){sunFlower = (SunFlower*)plants[i][j];if (sunFlower->status == GROUND || sunFlower->status == PRODUCE)sunFlower->sunShine.frameId = ++sunFlower->sunShine.frameId % SUM_SHINE_PIC_NUM; //修改当前图片帧编号, 并在到达 SUM_SHINE_PIC_NUM 时重置图片帧为 0 }}}
}
在收集随机阳光接口中添加上收集向日葵生产的日光 新增逻辑
/* 收集随机阳光接口 */
void collectSunShine(ExMessage* msg)
{IMAGE* imgSunShine = NULL;for (int i = 0; i < MAX_BALLS_NUM; ++i) //遍历阳光球{if (balls[i].used) //阳光球在使用中{imgSunShine = &imgSunShineBall[balls[i].frameId]; //找到对应的阳光球图片if (msg->x > balls[i].x && msg->x < balls[i].x + imgSunShine->getwidth()&& msg->y > balls[i].y && msg->y < balls[i].y + imgSunShine->getheight()) //判断鼠标移动的位置是否处于当前阳光球的位置{PlaySound("res/audio/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC); //异步播放收集阳光球音效balls[i].used = false; //将阳光球状态更改为未使用 (飞跃状态, 因为 xOffset 赋值了)const float angle = atan((float)(balls[i].y - 0) / (float)(balls[i].x - 262)); //使用正切函数balls[i].xOffset = 16 * cos(angle); //计算 X 轴偏移balls[i].yOffset = 16 * sin(angle); //计算 Y 轴偏移}}}/* 收集向日葵生产的日光 */SunFlower* sunFlower = NULL;for (int i = 0; i < GRASS_GRID_ROW; ++i) //遍历二维指针数组{for (int j = 0; j < GRASS_GRID_COL; ++j){if (plants[i][j]->type == SUNFLOWER){sunFlower = (SunFlower*)plants[i][j];imgSunShine = &imgSunShineBall[sunFlower->sunShine.frameId]; //找到对应的阳光球图片if (sunFlower->status == GROUND) {if (msg->x > sunFlower->pCurr.x && msg->x < sunFlower->pCurr.x + imgSunShine->getwidth()&& msg->y > sunFlower->pCurr.y && msg->y < sunFlower->pCurr.y + imgSunShine->getheight()) //判断鼠标移动的位置是否处于当前阳光球的位置{PlaySound("res/audio/sunshine.wav", NULL, SND_FILENAME | SND_ASYNC); //异步播放收集阳光球音效sunFlower->status = COLLECT;resetVecotrVal(sunFlower, i, j); //更改曲线坐标}}}}}
}
最后只需要在 updateWindow 接口中渲染一下向日葵生产的阳光即可
SunFlower* sunFlower = NULL;
for (int i = 0; i < GRASS_GRID_ROW; ++i) //渲染向日葵阳光
{for (int j = 0; j < GRASS_GRID_COL; ++j){if (plants[i][j]->type == SUNFLOWER){sunFlower = ((SunFlower*)plants[i][j]);if (sunFlower->status > UNUSED){putimagePNG(sunFlower->pCurr.x, sunFlower->pCurr.y,&imgSunShineBall[sunFlower->sunShine.frameId]);}} }
}
效果展示
向日葵可以生产阳光,生产阳光球后会以类似抛物线的形式(贝塞尔曲线)随机掉落在右一格的位置。鼠标移动至阳光球处,阳光将会被收集,阳光值增加 25
十五 片头僵尸展示
优化片头效果,实现函数如下,开局会先展示路边的僵尸
/* 展示界面的僵尸相关变量 */
#define VIEW_ZOMBIE_NUM 9
#define VIEW_ZOMBIE_PIC_NUM 11
IMAGE imgViewZombies[VIEW_ZOMBIE_PIC_NUM];/* 游戏开始前展示僵尸 */
void viewScence()
{int Xmin = WIN_WIDTH - imgBg.getwidth(); //-500vector2 zombieVec[VIEW_ZOMBIE_NUM] = { //展示场景中, 僵尸初始位置{550,80},{530,160},{630,170},{530,200},{515,270},{565,370},{605,340},{705,280},{690,340}};int frameIndexArr[VIEW_ZOMBIE_NUM];for (int i = 0; i < VIEW_ZOMBIE_NUM; ++i)frameIndexArr[i] = rand() % VIEW_ZOMBIE_PIC_NUM;int cycleNum = 0; //利用循环计数, 解决僵尸抖动过快for (int x = 0; x >= Xmin; x -= 2) //缓慢移动展示僵尸{BeginBatchDraw(); //双缓冲解决闪屏putimage(x, 0, &imgBg);++cycleNum; //当循环十次后, 更换每只僵尸的帧图片for (int i = 0; i < VIEW_ZOMBIE_NUM; ++i) //循环僵尸个数{putimagePNG(zombieVec[i].x - Xmin + x, zombieVec[i].y, &imgViewZombies[frameIndexArr[i]]); //渲染僵尸图片if (cycleNum > 2)frameIndexArr[i] = (++frameIndexArr[i]) % VIEW_ZOMBIE_PIC_NUM; //更换帧图}if (cycleNum > 2) cycleNum = 0; //重置循环计数EndBatchDraw();Sleep(5);}//停留 3 S 展示for (int k = 0; k < MAX_TIME_INTERVAL / 2; ++k){BeginBatchDraw(); //双缓冲解决闪屏putimage(Xmin, 0, &imgBg); //相当于把图片向左移动 500 个像素for (int i = 0; i < VIEW_ZOMBIE_NUM; ++i) //循环僵尸个数{putimagePNG(zombieVec[i].x, zombieVec[i].y, &imgViewZombies[frameIndexArr[i]]); //渲染僵尸图片frameIndexArr[i] = (++frameIndexArr[i]) % VIEW_ZOMBIE_PIC_NUM; //更换帧图}EndBatchDraw();Sleep(30);}//移动回主界面cycleNum = 0;for (int x = Xmin; x <= 0; x += 2){BeginBatchDraw(); //双缓冲解决闪屏putimage(x, 0, &imgBg);++cycleNum; //当循环十次后, 更换每只僵尸的帧图片for (int i = 0; i < VIEW_ZOMBIE_NUM; ++i) //循环僵尸个数{if (zombieVec[i].x - Xmin + x > 0){putimagePNG(zombieVec[i].x - Xmin + x, zombieVec[i].y, &imgViewZombies[frameIndexArr[i]]); //渲染僵尸图片if (cycleNum > 2)frameIndexArr[i] = (++frameIndexArr[i]) % VIEW_ZOMBIE_PIC_NUM; //更换帧图}}if (cycleNum > 2) cycleNum = 0; //重置循环计数EndBatchDraw();Sleep(5);}
}
在主函数中调用
效果展示
游戏开场会缓慢的移动窗口至马路边,停顿观察路边僵尸(僵尸会一摇一摇的抖动),然后游戏镜头会再缓慢移动至原界面
十六 植物栏滑动
在上述游戏界面拉回主界面过程中,植物菜单栏会缓慢滑动出现,具体实现如下
/* 植物栏滑动 */
void barsDown()
{int imgBarHeight = imgBar.getheight();for (int i = -imgBarHeight; i <= 6; ++i) //这里因为微调了植物卡片位置为 6{BeginBatchDraw();putimage(0, 0, &imgBg); //渲染地图if (i <= 0) putimagePNG(250, i, &imgBar); //但植物栏的位置为 0else putimagePNG(250, 0, &imgBar); //渲染植物栏for (int j = 0; j < PLANT_CNT; ++j) //遍历植物卡牌putimage(PIC_LEFT_MARGIN + j * PIC_WIDTH, i, &imgCards[j]); //渲染植物卡牌 EndBatchDraw();Sleep(10);}Sleep(1000);
}
在主函数中调用
效果展示
在上述开场游戏界面拉回主界面过程中,植物菜单栏会缓慢滑动出现
十六 判断游戏结束
相关结构和变量
/* 游戏输赢相关的结构和变量 */
enum { GAMEING, WIN, FAIL };
#define INGAME_ZOMBIE_NUM 15
int killZombies = 0;
int gameStatus = GAMEING;
创建僵尸接口时判断杀死的僵尸是否满足该局僵尸的数目了,如果是则不再创建
/* 创建僵尸接口, 主要用于初始化僵尸 */
void createZombie()
{if (killZombies >= INGAME_ZOMBIE_NUM) return;static int zombieCallCnt = 0; //延缓函数调用次数并增加些随机性static int randZombieCallCnt = 500;if (zombieCallCnt++ < randZombieCallCnt) return;randZombieCallCnt = 300 + rand() % 200;zombieCallCnt = 0;for (int i = 0; i < MAX_ZOMBIE_NUM; ++i) //找一个未在界面的僵尸初始化{if (!zombies[i].used){zombies[i].row = rand() % GRASS_GRID_ROW; //僵尸出现在第几行(从 0 开始)zombies[i].x = WIN_WIDTH;zombies[i].y = zombies[i].row * GRASS_GRID_HIGHT; //出现在草地的任意一格上zombies[i].frameId = 0;zombies[i].speed = 1; //僵尸的移动速度zombies[i].blood = 100; //默认僵尸血条为 100zombies[i].isDead = false; //僵尸存活zombies[i].isEating = false;zombies[i].used = true;break; //结束循环}}
}
在原子弹和僵尸碰撞接口 collsionCheck 中 ,若杀死僵尸数大于或等于该局游戏僵尸数目,则改变游戏状态
原更新僵尸接口中,若僵尸已移动至最左端,则游戏失败
最后在 main 函数中调用检验游戏状态的函数,即可判断游戏输赢
checkGameOver 会用到 在线 MP3 音频转 WAV
/* 判断游戏输赢 */
IMAGE imgGameOver; //工具栏图片
bool checkGameOver()
{if (gameStatus == WIN){Sleep(500);PlaySound("res/audio/win.wav", NULL, SND_FILENAME | SND_ASYNC); //异步播放音效loadimage(0, "res/gameWin.png");return true;}else if (gameStatus == FAIL){Sleep(500);PlaySound("res/audio/lose.wav", NULL, SND_FILENAME | SND_ASYNC); //异步播放音效loadimage(&imgGameOver, "res/gameFail.png");putimagePNG(300, 140, &imgGameOver);return true;}return false;
}/* 主函数 */
int main()
{gameInit(); //不能把 startUI 放在 gameInit 前, gameInit 包含了创建游戏图形窗口startUI();viewScence();barsDown();updateWindow(); //窗口视图展示int timer = 0; //用以计时 20 毫秒更新一次while (1){userClick(); //监听窗口鼠标事件timer += getDelay();if (timer > 20){updateWindow(); //更新窗口视图updateGame(); //更新游戏动画帧if (checkGameOver()) break; //判断游戏输赢timer = 0;}}destroyPlants(); //释放内存system("pause");return 0;
}
效果展示
一些游戏体验优化
① 豌豆不能太提前射击僵尸
在射击接口 shoot 里,校验僵尸和窗口右端的距离即可
② 卡牌太阳值不够不能选取
如果阳光值不够选取植物,则渲染为灰色,阳光值不够不能种植该植物;且植物有冷却时间,在冷却时间内植物不能种植
/* 游戏体验优化, 阳光值不足或植物冷却时不能种植 */
IMAGE imgBlackCards[PLANT_CNT]; //植物不能种植卡片
IMAGE imgFreezeCards[PLANT_CNT]; //植物冷却卡片
#define PEA_FREEZE_TIME 500
#define SUMFLOWER_FREEZE_TIME 200
static int peaPlantInterval = 500;
static int sumFlowerPlantInterval = 200;enum PLANT_CARD_STATUS { BRIGHT, GREY, FREEZE };
int plantCardStatus[PLANT_CNT]; //植物卡片状态数组
更新植物卡牌状态函数代码
/* 更新植物卡牌状态 */
void updatePlantCardStatus()
{for (int i = 0; i < PLANT_CNT; ++i) //判断植物卡牌状态{if (i == PEA){if (sunShineVal < 100) //阳光值不够plantCardStatus[i] = GREY; //卡片灰色else if (sunShineVal >= 100 && peaPlantInterval < PEA_FREEZE_TIME) //阳光值够但在冷却时间内plantCardStatus[i] = FREEZE; //卡片冻结elseplantCardStatus[i] = BRIGHT; //卡片原色}else if (i == SUNFLOWER){if (sunShineVal < 50)plantCardStatus[i] = GREY;else if (sunShineVal >= 50 && sumFlowerPlantInterval < SUMFLOWER_FREEZE_TIME)plantCardStatus[i] = FREEZE;elseplantCardStatus[i] = BRIGHT;}}
}
修改植物栏滑动逻辑
/* 植物栏滑动 */
void barsDown()
{int imgBarHeight = imgBar.getheight();updatePlantCardStatus();for (int i = -imgBarHeight; i <= 6; ++i) //这里因为微调了植物卡片位置为 6{BeginBatchDraw();putimage(0, 0, &imgBg); //渲染地图if (i <= 0) putimagePNG(250, i, &imgBar); //但植物栏的位置为 0else putimagePNG(250, 0, &imgBar); //渲染植物栏for (int j = 0; j < PLANT_CNT; ++j) //遍历植物卡牌{if (plantCardStatus[j] == BRIGHT)putimage(PIC_LEFT_MARGIN + j * PIC_WIDTH, i, &imgCards[j]); //渲染植物卡牌else if (plantCardStatus[j] == GREY)putimage(PIC_LEFT_MARGIN + j * PIC_WIDTH, i, &imgBlackCards[j]);elseputimage(PIC_LEFT_MARGIN + j * PIC_WIDTH, i, &imgFreezeCards[j]);} EndBatchDraw();Sleep(10);}Sleep(1000);
}
种植植物时记得扣除太阳值和重置冷却
/* 种植植物接口, 主要释放草格子内存, 二维指针数组对应位置,指向初始化的植物 */
Plant* growPlants(Plant* plant, int type, int x, int y)
{assert(plant);free((Grass*)plant); //释放该位置草格子内存if (type == PEA) //根据类型初始化 PeaShooter{PeaShooter* peaShooter = (PeaShooter*)calloc(1, sizeof(PeaShooter)); //calloc 函数替代 malloc, 省略 memsetassert(peaShooter);peaShooter->shootSpeed = DEFAULT_SHOOT_TIME; //豌豆射击速度, 或者叫豌豆发射子弹的时间间隔, -1 表示可发射子弹peaShooter->plant.blood = 100;//扣除太阳值和重置冷却sunShineVal -= 100;peaPlantInterval = 0;updatePlantCardStatus();return (Plant*)peaShooter;}else if (type == SUNFLOWER) //根据类型初始化 SunFlower{SunFlower* sunFlower = (SunFlower*)calloc(1, sizeof(SunFlower));assert(sunFlower);sunFlower->plant.type = 1;sunFlower->plant.blood = 100;sunFlower->timeInterval = MAX_TIME_INTERVAL * (4 + rand() % 5);/* 初始化贝塞尔曲线 */const int distance = (50 + rand() % 50); //只往右抛即可const int currPlantX = GRASS_LEFT_MARGIN + y * GRASS_GRID_WIDTH + 5;const int currPlantY = GRASS_TOP_MARGIN + x * GRASS_GRID_HIGHT + 10;sunFlower->t = 0;sunFlower->speed = 0.05;sunFlower->p1 = vector2(currPlantX, currPlantY);sunFlower->p2 = vector2(sunFlower->p1.x + distance * 0.3, sunFlower->p1.y - 100);sunFlower->p3 = vector2(sunFlower->p1.x + distance * 0.7, sunFlower->p1.y - 100);sunFlower->p4 = vector2(currPlantX + distance, currPlantY +imgPlant[SUNFLOWER][0]->getheight() - imgSunShineBall[0].getheight());sunShineVal -= 50; //扣除太阳值和重置冷却sumFlowerPlantInterval = 0;updatePlantCardStatus();return (Plant*)sunFlower;}
}
原 updatePlantsPic 接口中更新 peaPlantInterval 和 sumFlowerPlantInterval
/* 更新植物图片帧接口, 主要用于实现植物摇摆 */
void updatePlantsPic()
{++peaPlantInterval;++sumFlowerPlantInterval;updatePlantCardStatus();for (int i = 0; i < GRASS_GRID_ROW; ++i) //遍历二维指针数组{for (int j = 0; j < GRASS_GRID_COL; ++j){if (plants[i][j]->type >= PEA && //找到非草地的植物imgPlant[plants[i][j]->type][++plants[i][j]->frameId] == NULL) //将植物图片增加一, 判断是否到达图片帧末尾 plants[i][j]->frameId = 0; //重置图片帧为零}}
}
最后修改渲染卡片窗口的 updateWindow 函数
效果展示
如果阳光值不够选取植物,则渲染为灰色,阳光值不够不能种植该植物;且植物有冷却时间,在冷却时间内植物不能种植
③ 添加各种音乐
加上音效
初始背景音乐
/* 游戏开始前的菜单界面 */
void startUI()
{IMAGE imageBg, imgMenu1, imgMenu2;loadimage(&imageBg, "res/menu.png");loadimage(&imgMenu1, "res/menu1.png");loadimage(&imgMenu2, "res/menu2.png");PlaySound("res/audio/bg.wav", NULL, SND_FILENAME | SND_ASYNC); //异步播放音效bool mouseStatus = false; //0 表示鼠标未移动至开始游戏位置while (1) {BeginBatchDraw(); //双缓冲解决闪屏putimage(0, 0, &imageBg);putimagePNG(UI_LEFT_MARGIN, UI_TOP_MARGIN, mouseStatus ? &imgMenu2 : &imgMenu1); //根据鼠标是否移动至游戏开始位置, 显示不同的图片ExMessage msg;if (peekmessage(&msg)) //监听鼠标事件{if (msg.x > UI_LEFT_MARGIN && msg.x < UI_LEFT_MARGIN + UI_WIDTH&& msg.y > UI_TOP_MARGIN && msg.y < UI_TOP_MARGIN + UI_HIGHT) //当鼠标移动至开始游戏位置, 界面高亮{putimagePNG(UI_LEFT_MARGIN, UI_TOP_MARGIN, &imgMenu2);mouseStatus = true; //表示鼠标移动至开始游戏位置, 如果一直不移动鼠标则一直高亮if (msg.message == WM_LBUTTONDOWN) //当鼠标点击时, 进入游戏{PlaySound(0, 0, SND_FILENAME);EndBatchDraw();return; //结束函数}}else mouseStatus = false; //当鼠标未移动至开始游戏位置, 界面不高亮}EndBatchDraw();}
}
片头背景音乐
僵尸来了背景音乐
在 createZombie 接口中,添加如下代码
if (createZombies == 1) PlaySound("res/audio/zombiescoming.wav", NULL, SND_FILENAME | SND_ASYNC); //异步播放音效
选取植物背景音乐
种植物音乐,种到不合适地方的音乐
豌豆射击的音乐
花了两块大洋买了原曲,支持一下(其实是为了游戏背景曲,哈哈)
遗留问题
音频播放同时播放两个音频,可以实现功能就是没用到其它音频库,导致游戏试玩时当有大量音频需要加载播放时,会稍有卡顿,待有空找个 Win 音频三方库优化一下吧
全部源代码和资源文件待后续把项目上传
相关文章:
C 实现植物大战僵尸(四)
C 实现植物大战僵尸(四) C 实现植物大战僵尸,完结撒花(还有个音频稍卡顿的性能问题,待有空优化解决)。目前基本的功能模块已经搭建好了,感兴趣的友友可自行尝试编写后续游戏内容 因为 C 站不能…...
GitHub Fork 和 Clone 的深度指南:操作解析与 Pull Request 完整流程20241231
GitHub Fork 和 Clone 的深度指南:操作解析与 Pull Request 完整流程 快速导航 引言Fork 与 Clone 概念对比完整开发流程Pull Request 最佳实践常见问题与解决方案最佳实践建议实战案例 引言 在开发者的协作世界中,GitHub 就像一座桥梁,连…...
hypothesis testing
p 值、t 值、显著水平值的意义 在统计学中,p 值、t 值和显著水平值 ( α (\alpha (α)是常用的概念,用于判断研究数据是否支持某个假设。以下是它们的定义、意义和相互关系。 1. p 值 定义 p 值是指在假设原假设 ( H 0 H_0 H0) 为真的前提下&#x…...
解決當前IP地址僅適用於本地網路
想要解決“當前IP地址僅適用於本地網路”其實並不困難。本篇文章將介紹其發生的原因以及如何解決。 “僅限本地網路”是什麼意思? 當IP地址為“僅限本地網路”時,意味著設備正在使用私人網路內部IP地址,但無法連接到互聯網。如果將本地IP視…...
一个最简单的ios程序(object_c)的编写
前言 如何在苹果系统MacOS创建一个简单的ios(iphone)程序,貌似非常的简单。但是,作为习惯了Windows开发的程序员来说,有时候还觉得有点麻烦,至少开始有点很不习惯。 本博文试着把这个过程展现一下ÿ…...
HTML5实现好看的新年春节元旦网站源码
HTML5实现好看的新年春节元旦网站源码 前言一、设计来源1.1 主界面1.2 新年由来界面1.3 文章详细界面1.4 登录界面1.5 注册界面1.6 新年图册界面1.7 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好看的新年春节元旦网站源码,春节新…...
NLP模型工程化部署
文章目录 一、理论-微服务、测试与GPU1)微服务架构2)代码测试3)GPU使用 二、实践-封装微服务,编写测试用例和脚本,并观察GPU1)微服务封装(RestFul和RPC)①RestFul接口②RPC接口 2)测试编写(unit…...
git时常混淆的操作的笔记
git时常混淆的操作的笔记 写在前面git rebase与merge的不同git am 与git apply的不同删除远端分支 写在前面 离开OS公司后,git的使用不再那么频繁。许多指令,时常忘记,这里作一点笔记。 git rebase与merge的不同 首先,这二者的…...
大模型WebUI:Gradio全解系列8——Additional Features:补充特性(上)
大模型WebUI:Gradio全解系列8——Additional Features:补充特性(上) 前言本篇摘要8. Additional Features:补充特性8.1 队列8.1.1 使用方法8.1.2 配置队列演示 8.2 输入输出流8.2.1 输出流1. 生成器yield2. 流媒体 8.2…...
vue3 Suspense组件
当等待数据的时间比开发人员希望的时间要长时在Vue3中无须自定义代码即可实现 只需要通过Suspense组件管理这一过程。 该组件除了可以给定默认加载数据后的渲染视图,还可以设置加载数据时的应急视图。 例如,在数据加载过程中,会先显示fall…...
Linux 内核调试
系列文章目录 Linux内核学习 QEMU 虚拟机 Linux 调试视频 近阶段补充知识 文章目录 系列文章目录一、WSL二、QEMU1、安装2、退出 三、构建根文件系统1、下载 BusyBox2、编译3、构建文件目录:Makefileinit 四、内核编译1、下载2、构建 五、调试1、GDB 命令调试2、VSC…...
【华为OD-E卷 - 机房布局 100分(python、java、c++、js、c)】
【华为OD-E卷 - 机房布局 100分(python、java、c、js、c)】 题目 小明正在规划一个大型数据中心机房,为了使得机柜上的机器都能正常满负荷工作,需要确保在每个机柜边上至少要有一个电箱。 为了简化题目,假设这个机房…...
Cursor小试1.生成一个网页的接口请求工具
一般开发过程中,会涉及到接口的调试,往往有时候开发的电脑不是我们自己的,没有安装一些类似postman 的接口调用工具,所以发现问题或者要测试某些接口是否正常的时候会很麻烦,而且现在网上也没有找到很好的免费的网页端接口请求的网址,所以我们使用Cursor来编写这样一个小工具, …...
免费的量化交易股票API有哪些局限性?
免费的量化交易股票 API 存在以下多方面的局限性: 功能限制 数据获取方面: 数据种类不完整:可能仅提供基本的行情数据,如开盘价、收盘价、最高价、最低价等,而深度行情数据(如买卖盘的详细挂单情况…...
leetcode之hot100---148排序链表(C++)
题目要求将一个无序的链表按照升序返回,涉及排序算法,下面对每个排序算法进行回顾 一、交换排序 1.冒泡排序 算法思想:反复比较相邻的两个元素,将它们中较大的(或较小的)元素逐步“冒泡”到数组的末尾。…...
Redis下载与安装
Redis下载与安装 注意:官网没有提供Windows版本,只有Linux版本。 GitHub下载地址: https://github.com/microsoftarchive/redis/releases 这里演示解压版的” Redis-x64-3.2.100.zip”,下载完毕后解压即可。 目录解析: 以管理员…...
TF-IDF(Term Frequency-Inverse Document Frequency)详解:原理和python实现(中英双语)
中文版 TF-IDF算法详解:理解与应用 TF-IDF(Term Frequency-Inverse Document Frequency)是信息检索与文本挖掘中常用的算法,广泛应用于搜索引擎、推荐系统以及各种文本分析领域。TF-IDF的核心思想是通过计算一个词在文档中的重要…...
论文研读:Text2Video-Zero 无需微调,仅改动<文生图模型>推理函数实现文生视频(Arxiv 2023-03-23)
论文名:Text2Video-Zero: Text-to-Image Diffusion Models are Zero-Shot Video Generators 1. 摘要 1.1 方法总结 通过潜空间插值, 实现动作连续帧。 以第一帧为锚定,替换原模型的self-attention,改为cross-attention 实现 保证图片整体场…...
.Net Core配置系统
目录 Json文件配置 读取配置原始方法 绑定读取配置 用法 传统Web.config配置的缺点为了兼容,仍然可以使用Web.config和ConifgurationManager类,但不推荐.NET中的配置系统支持丰富的配置源,包括文件(json、xml、ini等ÿ…...
互联网直播点播平台EasyDSS无人机视频推拉流技术实现工地远程监控巡检直播
在建筑行业,施工现场的安全管理和实时监控一直是项目管理中的重点。随着技术的进步,无人机工地直播技术成为了一种新兴的解决方案,它不仅能够提高施工透明度,还能够加强现场安全管理。EasyDSS作为一种先进的流媒体技术平台&#x…...
VirtualBox新版本报错 Invalid installation directory解决方案
最近需要使用到VirtualBox,但是下载最新的安装时发现如下Invalid installation directory,经过多番查找终于找到了解决问题方法,并进行了测试,现将解决办法附上 步骤一、将需要安装VirtualBox的目录下创建个目录 步骤二、使用cmd&…...
PY_11_01
前言 PY_11_01 抄就行了👻 一、代码步骤 抄就行了👻 def fact(n):if n1:return 1else:return n*fact(n-1)while True:minput(请输入一个正整数m:)if not m.isdigit() or int(m)0:print(输入错误,请重新输入!)else:mint(m)brea…...
golang syscall 三种加载DLL方式
1. syscall.MustLoadDLL() MustLoadDLL 是一种加载 DLL 的函数,它会在加载 DLL 时,如果发生错误,直接 panic。 错误处理:如果 DLL 加载失败,它会调用 panic,导致程序崩溃。dll : syscall.MustLoadDLL(&qu…...
Zabbix企业级分布式监控系统
第一章:监控概念及Zabbix部署 监控概述 对于监控系统在企业架构中不是新的技术,但却是必不可少的重要组成部分,所谓无监控,不运维! 监控系统可以帮助运维、开发、测试等人员及时的发现服务器出现的故障,…...
JetPack——Lifecycle
Lifecycle是什么? Lifecycle 是一个类,用于存储有关组件(如 activity 或 fragment)的生命周期状态的信息,并允许其他对象观测此状态 Lifeclcle解决什么问题? class GPS(private val context: Context,pr…...
本地小主机安装HomeAssistant开源智能家居平台打造个人AI管家
文章目录 前言1. 添加镜像源2. 部署HomeAssistant3. HA系统初始化配置4. HA系统添加智能设备4.1 添加已发现的设备4.2 添加HACS插件安装设备 5. 安装cpolar内网穿透5.1 配置HA公网地址 6. 配置固定公网地址 前言 大家好!今天我要向大家展示如何将一台迷你的香橙派Z…...
大模型Weekly 03|OpenAI o3发布;DeepSeek-V3上线即开源!
大模型Weekly 03|OpenAI o3发布;DeepSeek-V3上线即开源!DeepSeek-V3上线即开源;OpenAI 发布高级推理模型 o3https://mp.weixin.qq.com/s/9qU_zzIv9ibFdJZ5cTocOw?token47960959&langzh_CN 「青稞大模型Weekly」,持…...
【0379】Postgres内核 Walreceiver (libpqwalreceiver API)分析
文章目录 1. libpqwalreceiver API1.1 四个函数2. Walreceiver IPC3. Walsender IPC4. Walsender - walreceiver protocol1. libpqwalreceiver API walreceiver 中与传输相关的部分,其负责连接主服务器、接收 WAL 文件以及发送消息,是动态加载的,以避免主服务器的二进制文件…...
easybox
title: 解锁 EasyBox:智能运维的便捷之选 date: ‘2024-12-31’ category: blog tags: EasyBox智能运维效率提升自动化运维 sig: memsafety archives: ‘2024-12’ author:way_back summary: EasyBox 作为一款智能运维工具,以其简洁高效的特性ÿ…...
Prompt工程--AI开发--可置顶粘贴小工具
PROMPT 1.背景要求:我需要开发一个简单的粘贴小工具,用于方便地粘贴和管理文本内容。该工具需要具备以下功能:粘贴功能:提供一个文本框,用户可以粘贴内容。窗口置顶:支持窗口置顶功能,确保窗口…...
【AI日记】24.12.31 kaggle 比赛 2-19
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 参加:kaggle 比赛 Regression with an Insurance Dataset时间:9 小时 读书 书名:论婚姻与道德时间:1.5 小时 律己 工作时间:良作息&#x…...
jenkins集成工具(一)部署php项目
目录 什么是CI 、CD Jenkins集成工具 一、Jenkins介绍 二、jenkins的安装和部署 环境部署 安装jenkins 安装gitlab 配置镜像源进行安装 修改密码 安装git工具 上传测试代码 Jenkins部署php项目wordpress 发布php代码 安装插件 测试代码发布 实现发布成功发送邮件…...
ubuntu 使用samba与windows共享文件[注意权限配置]
在Ubuntu上使用Samba服务与Windows系统共享文件,需要正确配置Samba服务以及相应的权限。以下是详细的步骤: 安装Samba 首先,确保你的Ubuntu系统上安装了Samba服务。 sudo apt update sudo apt install samba配置Samba 安装完成后,…...
【GridView渐进全解】第四部分GridView分页进阶
目录 一、启用分页 二、修改GridView分页模板 1.进入控件模板修改视图: 2.进入页码模板(PagerTemplate)视图 3.添加导航按钮控件 4.修改导航控件属性 三、输入页号跳转 1.进入页码模板视图 2.添加文本框及按钮控件 3.编写代码 【接…...
K8s集群平滑升级(Smooth Upgrade of K8S Cluster)
简介: Kubernetes (简称K8s)是一个开源的容器编排和管理平台,由Google开发并维护。它最初是为了解决谷歌内部大规模容器管理的问题而设计的,后来在2014年开源,成为云原生技术的核心组成部分。1 K8…...
HarmonyOS-面试整理
目录 为什么选择HarmonyOS/ 优点/特点鸿蒙系统的权限有哪些说一说鸿蒙系统的安全机制说一说鸿蒙系统的微内核与安卓的内核区别鸿蒙操作系统的微内核架构有哪些优势分布式能力在鸿蒙系统中如何实现请解释一下鸿蒙系统中的分布式软总线技术如何在鸿蒙操作系统中进行多设备协同开发…...
基于编程语言的知识图谱表示增强大模型推理能力研究,一种提升LLM推理准确率达91.5%的结构化数据方法
基于编程语言的知识图谱表示增强大模型推理能力研究,一种提升LLM推理准确率达91.5%的结构化数据方法 理解数据分析全流程提问问题:知识的表示方式如何影响模型的推理能力?问题:为什么编程语言会是一个更好的知识表示选择ÿ…...
【北京迅为】iTOP-4412全能版使用手册-第七十章 Linux内核移植
iTOP-4412全能版采用四核Cortex-A9,主频为1.4GHz-1.6GHz,配备S5M8767 电源管理,集成USB HUB,选用高品质板对板连接器稳定可靠,大厂生产,做工精良。接口一应俱全,开发更简单,搭载全网通4G、支持WIFI、蓝牙、…...
使用 ASP.NET Core wwwroot 上传和存储文件
在 ASP.NET Core 应用程序中上传和存储文件是用户个人资料、产品目录等功能的常见要求。本指南将解释使用wwwroot存储图像(可用于文件)的过程以及如何在应用程序中处理图像上传。 步骤 1:设置项目环境 确保您的 ASP.NET 项目中具有必要的依…...
什么是 Azure OpenAI ?了解微软 Azure OpenAI 和 OpenAI 的关系
一、什么是Azure OpenAI ? 微软已与 OpenAI 合作以实现三个主要目标: ⦿利用 Azure 的基础结构(包括安全性、合规性和区域可用性),帮助用户构建企业级应用程序。 ⦿在微软产品(包括 Azure AI 产品以及以外…...
Flink CDC 自定义函数处理 SQLServer XML类型数据 映射 doris json字段方案
Flink CDC 自定义函数处理 SQLServer XML类型数据方案 1. 背景 因业务使用SQLServer数据库,CDC同步到doris 数仓。对于SQLServer xml类型,doris没有相应的字段对应, 可以使用json来存储xml数据。需要进行一步转换。从 flink 自定义函数入手…...
导致启动nacos报错Caused by: java.lang.IllegalStateException: No DataSource set 的两种原因
Java资深小白,不足之处,或者有任何错误欢迎指出。 --蓝紫报错代码如下: C:\Windows\System32>cd D:\nacos-server-2.2.3\nacos\binC:\Windows\System32>d:D:\nacos-server-2.2.3\nacos\bin>startup.cmd -m standalone "nacos is starting…...
mysql乱码、mysql数据中文问号
网上排出此错误方法的很多,但是 都不简洁,找不到根本原因 主要排查两点: 1.代码中jdbc链接的编码规则 urljdbc:mysql://localhost:3306/title?useUnicodetrue&characterEncodingutf8 将characterEncoding设置为utf8 2.设置mysq…...
python RAG 管道
RAG(RetrievalAugmented Generation)管道是一种结合了信息检索(Retrieval)和文本生成(Generation)的技术框架,主要用于生成高质量、基于事实的文本。它通过从外部知识源(如文档、数据…...
018-spring-基于aop的事务控制
1 先配置平台事务管理器 2 在配置 spring提供的advice 3 事务增强的aop 总结: 事务就是要做2个配置: <!-- 1 开启事务管理器 不同的框架对应不同的事务管理器 --> <bean id"transactionManager" class"org.springframework.j…...
HTML 轮播图(Carousel)详细讲解
HTML 轮播图(Carousel)详细讲解 轮播图(Carousel)是一种常见的用户界面组件,用于在同一位置展示多个图像或内容,允许用户通过滑动或自动播放的方式查看不同的内容。它通常用于展示产品、图片、广告等。 1…...
计算机网络-数据链路层(ppp协议)
2.2 ppp协议 点对点协议ppp是目前使用最广泛的点对点数据链路层协议。 2.3 媒体接入控制基本概念 共享信道要着重考虑的一个问题就是如何协调多个发送和接收站点对一个共享传输媒体的占用,即媒体接入控制MAC。 2.3.1 静态划分信道 频分复用 时分复用 波分复用 码分复…...
如何优化Python网络爬虫的数据清洗流程,以提升数据质量并有效应对网站反爬虫机制?
优化爬虫数据清洗流程,应对反爬虫机制 一、数据清洗的重要性 在网络爬虫中,数据清洗是关键环节。打个比方,我们从网页抓取到的原始数据就像一堆杂乱的杂物,里面有各种格式、错误和重复信息。比如抓取到的文本可能包含HTML标签、…...
QSharedMemory 实现数据exe间共享
定义共享数据结构 首先,需要定义一个结构体来包含要共享的数据。这个结构体应该包含所有需要在多个类的实例之间共享的成员变量 struct SharedData {int intValue;QString stringValue;// 可以添加更多需要共享的成员变量}; 使用 QSharedMemory 进行数据共享 在写…...
强化学习(1)
Reinforcement Learning Goal-directed learing from ineraction with the environment. 1. Basic Element 基本元素 1.1 Agent 玩家 1.2 Environment 1.3 Goal 2. Main Element 主要元素 2.1 State 2.2 Action 状态与行为往复 2.3 Reward 目标:最大化总…...