背景:Hayaizo学长想要玩坦克大战,但是他不知道去哪里安装,于是他决定让他的学弟学妹直接给他用c语言做一个,请解决Hayaizo的生理需求,拜托啦!
一.游戏介绍
战场是一个12行12列的空间,每一格最多有一个坦克。
战场中有3种物体:
敌人:白色坦克,开局出生在战场任意位置,每一轮会随机上下左右移动一格(但是不会跑出边界)或者发射子弹
玩家:红色坦克,开局出生在战场任意位置,每一轮可以选择上下左右移动一格或者2格,或者选择发射子弹,之后选择发射子弹的方向。
子弹:黑色子弹,会按照一开始设置的方向一直前进,每轮前进一格,直到跑出战场或者触碰到任意坦克。如果是玩家,则选择结束游戏或者重新开始,如果是敌人则该敌人和子弹都会消失,紧接着地图上边界任意处生成一个新的坦克。
要求可以自定义坦克数量
地图信息用字符串输出,请尽量美观。
二.游戏的实现
一.大体的思路
首先,我们要打印一个12X12的游戏地图,这里我首先想到的是二级数组。但要考虑到边界的情况,所以这里我用了一个14排14列的二维数组,最外围一圈作为游戏边界。
其次,游戏中有许多《随机》要求,为了实现随机性,这里我用srand和time时间戳重新定义rand的种子这样rand就会给出一个随机值,通过公式随机值的范围int a=m+rand()%(n-m+1),这样就可以得到自己想要的值。
为了使游戏更美观,本篇会用很多system("CLS")来进行清空屏幕操作,Sleep()来让游戏公告慢下来。
这次,我依旧是建立一个头文件(name.h),一个运行文件(run.c),一个用来实现各个逻辑的功能文件(function.c),如下图
二.精致的开始
这次我尝试做一个比较漂亮,让玩家有代入感的开头,而不是空空的弄个菜单出来,所以这次我建立一个startmenu函数,首先,我会适当加入一些清除屏幕的函数,和Sleep函数让玩家都能看清公告,虽然给了玩家有趣的体验,但是再一次游戏后结束再玩游戏时这个公告又会弹出来,所以我之后想到可以文件的方式,当玩家开始一次游戏后,可以在电脑中生成一个有特殊的文件,下次游戏时如果读取到文件,则代表开始过游戏,便不会再弹出公告。
//打印开头引语
void startmenu()
{
printf("------------------------------------\n");
printf("------------------------------------\n");
printf("----------- 欢迎来到! -----------\n");
printf("--- 为解决Hayaizo学长的生理需求的 --\n");
printf("---------- 坦克游戏! ---------\n");
printf("------------------------------------\n");
printf("------------------------------------\n");
Sleep(3000);
system("CLS");
printf("游戏规则如下:>\n");
Sleep(1500);
printf("玩家坦克图标为:P\n");
Sleep(1500);
printf("电脑坦克图标位: @\n");
Sleep(1500);
printf("敌人和玩家在一开始时都会重生到随机的不同的位置\n");
Sleep(2000);
printf("敌人和玩家每一轮会随机上下左右移动一格或者发射子弹\n");
Sleep(2000);
printf("玩家开局出生在战场任意位置,每一轮可以选择上下左右移动一格或者2格,或者选择发射子弹,之后选择发射子弹的方向\n");
Sleep(4000);
printf("当玩家一轮操作完毕后,如果附近四行里有电脑坦克,会被击毙!!!(重要!)\n");
Sleep(2000);
printf("如果子弹打中了玩家,则游戏会结束\n");
Sleep(2000);
printf("如果子弹打中了电脑坦克,会在地图上边界任意处生成一个新的坦克,直到玩家战死为止\n");
Sleep(2000);
printf("地图规格为12X12,再输入敌方坦克数量的时候请不要大于143,不然游戏将直接退出\n");
Sleep(2000);
printf("游戏难度过高,请仔细思考每一步怎么走\n");
Sleep(4000);
system("CLS");
printf("那么现在,游戏正式开始!!\n");
}
打印菜单后便让玩家输入坦克的数量,注意,地图规格是12X12,所以除去玩家只有143的空位,所以不能让玩家输入超过143,代码如下
int main(void)
{
srand((unsigned)time(NULL));
startmenu();
printf("请输入敌方坦克的数量:>");
int ctank = 0;
scanf("%d", &ctank);
if (ctank > 143)
{
printf("输入的坦克数量过大,地图容不下\n");
return 0;
}
return 0;
}
则接下来,就是游戏地图的初始化和打印以及游玩了,所以这里我先把主函数全部写出来,之后在function.c里逐一实现
int main(void)
{
srand((unsigned)time(NULL));
startmenu();
printf("请输入敌方坦克的数量:>");
int ctank = 0;
scanf("%d", &ctank);
if (ctank > 143)
{
printf("输入的坦克数量过大,地图容不下\n");
return 0;
}
char arr[14][14] = { 0 };
//玩家的位置
int px = 0;
int py = 0;
Initmenu(arr,ctank,&px,&py);
play(arr, px, py);
return 0;
}
三.地图的初始化
首先,将arr数组除了边缘之外的内容赋值为空格,而边缘则用*来表示,之后用rand随机值给玩家定一个位置,并将玩家的位置返回,之后边再考虑电脑坦克的位置。如下图
void Initmenu(char arr[14][14], int ctank, int px, int py)
{
int i = 0;
int j = 0;
//初始化地图
for (i = 0; i < 14; i++)
{
for (j = 0; j < 14; j++)
{
if (i == 0 || i == 13 || j == 0 || j == 13)
{
arr[i][j] = '';
}
else
{
arr[i][j] = ' ';
}
}
}
int playx = 1 + rand() % 12;
int playy = 1 + rand() % 12; px = playx;//返回玩家的位置
py = playy;
//放置玩家
if (arr[playx][playy] != '' && arr[playx][playy] != '@')
{
arr[playx][playy] = 'P';
}
//放置电脑坦克
for (i = 0; i < ctank; i++)
{
int cx = 1 + rand() % 12;
int cy = 1 + rand() % 12;
if (arr[cx][cy] != '*' && arr[cx][cy] != 'P' && arr[cx][cy] != '@')
{
arr[cx][cy] = '@';
}
else
{
i--;
}
}
}
这样,地图的初始化就完成啦。
四.游戏逻辑的实现
游戏的实现主要分为:1.玩家的移动2.玩家或者电脑坦克导弹的发射3.玩家死亡或者电脑死亡4.电脑在边缘处如何随机复活5.以及玩家是否在电脑的击杀线上6.敌方坦克的随机移动,6大点,那我们一一实现
1.玩家的移动
玩家在随机位置出生后可以往上下左右(wsad)四个方向任意移动一格或者两格,所以这里我们就用到了一开始传回来的玩家的位置了。
这里我是用switch来选择方向,if来判断这个方向可不可以走,如果可以走的话,原来的位置就会变成空格,而新的位置就会变成玩家的标志P了,代码如下图
void play(char arr[14][14],int px,int py)
{
while (1)
{
system("CLS");
commove(arr);
menu(arr);
printf("请输入wdsa或者F来决定往哪个方向走或者发射导弹(输入错误将重新选择):>");
char pchoice = 0;
scanf(" %c", &pchoice);
int footn = 0;
char whe = 0;
if (pchoice == 'w' || pchoice == 'd' || pchoice == 's' || pchoice == 'a')
{
printf("步数输入错误将重新输入!\n");
printf("请输入向前走的步数(1或2):>");
scanf("%d", &footn);
}
else if(pchoice=='F')
{
printf("请输入要发射的方向(wdsa):>");
scanf(" %c", &whe);
}
else
{
continue;
}
switch (pchoice)
{
case 'w':
if (footn == 1)
{
if (arr[px - 1][py] == '*' || arr[px - 1][py] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px - 1][py] = 'P';
arr[px][py] = ' ';
px -= 1;
}
}
else if (footn == 2)
{
if (arr[px - 2][py] == '*' || arr[px - 2][py] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px - 2][py] = 'P';
arr[px][py] = ' ';
px -= 2;
}
}
break;
case 'd':
if (footn == 1)
{
if (arr[px][py + 1] == '*' || arr[px][py + 1] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px][py + 1] = 'P';
arr[px][py] = ' ';
py += 1;
}
}
else if (footn == 2)
{
if (arr[px][py+2] == '*' || arr[px][py+2] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px][py + 2] = 'P';
arr[px][py] = ' ';
py += 2;
}
}
break;
case 's':
if (footn == 1)
{
if (arr[px + 1][py] == '*' || arr[px + 1][py] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px + 1][py] = 'P';
arr[px][py] = ' ';
px += 1;
}
}
else if (footn == 2)
{
if (arr[px + 2][py] == '*' || arr[px + 2][py] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px + 2][py] = 'P';
arr[px][py] = ' ';
px += 2;
}
}
break;
case 'a':
if (footn == 1)
{
if (arr[px][py - 1] == '*' || arr[px][py - 1] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px][py - 1] = 'P';
arr[px][py] = ' ';
py -= 1;
}
}
else if (footn == 2)
{
if (arr[px][py - 2] == '*' || arr[px][py - 2] == '@')
{
printf("你不能往那边移动,因为那边是边界(或敌方坦克\n");
}
else
{
arr[px][py - 2] = 'P';
arr[px][py] = ' ';
py -= 2;
}
}
break;
}
2.子弹的发射
关于发射子弹,我这里也考虑的子弹的动画,原理就是子弹每往指定的方向走了之后,每走一格就会清除屏幕并重新打印,而为了判断是玩家打的子弹还是坦克打的子弹,我给fire函数加了一个int变量,如果变量为1,就是玩家打的,如果变量是0,就是电脑打的。这里的主要逻辑是和上面走路是差不多的,不过加入了不同的情况,就如果玩家的子弹打中了电脑坦克,电脑坦克就会消失,并在边缘复活。
void fire(char arr[14][14], int who,char whe,int x,int y)
{
int cur_x = x,cur_y = y;
switch (whe)
{
case 'w':
{
cur_x = x - 1;
cur_y = y;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_x--;
}
}
break;
}
case 'd':
{
cur_x = x;
cur_y = y + 1;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_y++;
}
}
break;
}
case 's':
{
cur_x = x+1;
cur_y = y ;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_x++;
}
}
break;
}
case 'a':
{
cur_x = x;
cur_y = y-1;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_y--;
}
}
break;
}
}
}
3.电脑坦克的复活
当电脑坦克被玩家击毙之后,会紧跟着一个fuhuo函数来复活电脑坦克,在边缘复活则是可以在上下左右四个边缘的任意位置复活,所以我们一样用rand函数来随机弄一个1到4的数,如果是1就是上,2就是下,3就是左,4就是右。然后,再用rand函数随机弄一个1到12的数字,这样就找到了随机坦克的位置。但要判断当前位置是否可以放坦克。代码如下
void fuhuoba(char arr[14][14])
{
int n = 1 + rand() % 4;
int n1 = 0;
int n2 = 0;
int n3 = 0;
int n4 = 0;
switch (n)
{
case 1:
while (1)
{
n1 = 1 + rand() % 12;
if (arr[1][n1] != '@' && arr[1][n1] != 'P')
{
arr[1][n2] = '@';
break;
}
}
break;
case 2:
while (1)
{
n1 = 1 + rand() % 12;
if (arr[12][n1] != '@' && arr[12][n1] != 'P')
{
arr[12][n2] = '@';
break;
}
}
break;
case 3:
while (1)
{
n1 = 1 + rand() % 12;
if (arr[n1][1] != '@' && arr[n1][1] != 'P')
{
arr[n1][1] = '@';
break;
}
}
break;
case 4:
while (1)
{
n1 = 1 + rand() % 12;
if (arr[n1][12] != '@' && arr[n1][12] != 'P')
{
arr[n1][12] = '@';
break;
}
}
break;
}
}
4.敌方坦克的随机移动
这个我感觉是我想的最久的一个环节了,因为坦克的数量未知,所以我只能想到用循环,如果某个位置是敌方坦克,则在上下左右走一次。
想象是美好的,直到我发现有些坦克一轮会飞到不知道哪里去,并不是上下左右走一格,这时我找了真的很久的原因,直到每一次调试的时候找到有一个坦克它随机向下走,然后再循环到它的时候又向下走,这样这个坦克就经历了两次移动。
而且坦克也会时不时的消失几辆,这里是我出bug最多的环节。
先解决第一个,这里我想到的解决办法就是建立一个新的二维数组,把已经跳过的坦克的位置存进去。
int arrxy[144][2] = { 0 };
在每次找到敌方坦克时,都进行一次循环遍历这里的坦克是否已经移动过
for (k = 0; k <= n4; k++)
{
if ((arrxy[k][0] == i) && (arrxy[k][1] == j))
{
ifsame = 1;
break;
}
}
之后便用rand()弄一个1到4的数字分别代表着上下左右,所以全部代码就是
void commove(char arr[14][14])
{
//menu(arr);
int i = 0;
int j = 0;
int arrxy[144][2] = { 0 };
int n4 = 0;
int ifsame = 0;
int k = 0;
for (i = 1; i <= 12; i++)
{
ifsame = 0;
for (j = 1; j <= 12; j++)
{
if ((arr[i][j] == '@')
&& (arr[i + 1][j] == 'P' || arr[i + 1][j] == '')
&& (arr[i - 1][j] == 'P' || arr[i - 1][j] == '')
&& (arr[i][j - 1] == 'P' || arr[i][j - 1] == '')
&& (arr[i][j + 1] == 'P' || arr[i][j + 1] == ''))
{
continue;
}
if (arr[i][j] == '@')
{
for (k = 0; k <= n4; k++)
{
if ((arrxy[k][0] == i) && (arrxy[k][1] == j))
{
ifsame = 1;
break;
}
}
if (ifsame != 1)
{
int move = 1 + rand() % 4;
if (move == 1)
{
if (arr[i + 1][j] != 'P' && arr[i + 1][j] != ''&& arr[i + 1][j] != '@')
{
arr[i][j] = ' ';
arr[i + 1][j] = '@';
arrxy[n4][0] = i + 1;
arrxy[n4][1] = j;
n4++;
}
else
{
j--;
}
}
else if (move == 2)
{
if (arr[i - 1][j] != 'P' && arr[i - 1][j] != '' && arr[i - 1][j] != '@')
{
arr[i][j] = ' ';
arr[i - 1][j] = '@';
arrxy[n4][0] = i - 1;
arrxy[n4][1] = j;
n4++;
}
else
{
j--;
}
}
else if (move == 3)
{
if (arr[i][j - 1] != 'P' && arr[i][j - 1] != '' && arr[i][j - 1] != '@')
{
arr[i][j] = ' ';
arr[i][j - 1] = '@';
arrxy[n4][0] = i;
arrxy[n4][1] = j - 1;
n4++;
}
else
{
j--;
}
}
else if (move == 4)
{
if (arr[i][j + 1] != 'P' && arr[i][j + 1] != '' && arr[i][j + 1] != '@')
{
arr[i][j] = ' ';
arr[i][j + 1] = '@';
arrxy[n4][0] = i;
arrxy[n4][1] = j + 1;
n4++;
}
else
{
j--;
}
}
}
}
}
}
}
5.子弹的发射和动画效果
//发射炮弹的方向和运动和结果的判断
void fire(char arr[14][14],int who,char whe,int px,int py);
who就是玩家还是电脑发射的,玩家就是1,电脑就是0,whe就是方向wdsa,px,py就是发射者的具体位置
如果是玩家发射到敌人身上,就会触发fuhuo函数
如果是敌人发射到玩家身上,则会触发endmenu函数(最后再说)
每次炮弹位置被赋值*后,打印完就会重新被赋值成空格,这样子撞到边界直接打印游戏地图就发现炮弹不见了
所以代码就如下图
void fire(char arr[14][14], int who,char whe,int x,int y)
{
int cur_x = x,cur_y = y;
switch (whe)
{
case 'w':
{
cur_x = x - 1;
cur_y = y;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_x--;
}
}
break;
}
case 'd':
{
cur_x = x;
cur_y = y + 1;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_y++;
}
}
break;
}
case 's':
{
cur_x = x+1;
cur_y = y ;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_x++;
}
}
break;
}
case 'a':
{
cur_x = x;
cur_y = y-1;
while (1)
{
if (arr[cur_x][cur_y] == '@' && who == 1)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
menu(arr);
Sleep(1000);
fuhuoba(arr);
break;
}
else if (arr[cur_x][cur_y] == 'P' && who == 0)
{
arr[cur_x][cur_y] = ' ';
system("CLS");
Sleep(1000);
endmenu();
Sleep(3000);
exit(-1);
}
else if (arr[cur_x][cur_y] == '*')
{
system("CLS");
menu(arr);
break;
}
else
{
arr[cur_x][cur_y] = '*';
system("CLS");
menu(arr);
Sleep(1000);
arr[cur_x][cur_y] = ' ';
cur_y--;
}
}
break;
}
}
}
6.最后游戏结束菜单的打印
为了让玩家更有参与感,这次我的结束菜单也做的格外的隆重。
void endmenu()
{
system("color 04");
printf("---------------------------------\n");
printf("---------------------------------\n");
printf("---------------------------------\n");
printf("-------- GAME OVER!! -----\n");
printf("---------------------------------\n");
printf("---------------------------------\n");
printf("---------------------------------\n");
Sleep(2000);
system("CLS");
printf("---------------------------------\n");
printf("---------------------------------\n");
printf("---------------------------------\n");
printf("--- Thank you for playing!!! ----\n");
printf("---------------------------------\n");
printf("---------------------------------\n");
printf("---------------------------------\n");
Sleep(2000);
system("CLS");
double x, y, a;
for (y = 1.5; y > -1.5; y -= 0.1214)
{
for (x = -1.5; x < 1.5; x += 0.05)
{
a = x x + y y - 1;
if (a a a - x x y y y <= 0)
{
printf("*");
}
else
{
printf(" ");
}
}
printf("\n");
}
Sleep(2000);
}
最后一个是打印一个变红的爱心。
三.赛后
全部代码已经提交到gitee上了 点个收藏就太感谢了
GitHub 点个收藏就太感谢了
最后,感谢您的观看