详解5个C语言简单易懂小游戏 | 您所在的位置:网站首页 › 简单小游戏app › 详解5个C语言简单易懂小游戏 |
文章目录
前言一、准备工作二、游戏菜单三、游戏内容1.猜数字2.三子棋3.扫雷4.五子棋5.飞行棋
总结
前言
我们通过5个简单易懂的小游戏来加强我们对C语言的认识,这五个小游戏不仅有单人,还有人机对战和人人对战。让我们在学习之余来上一局紧张刺激的小游戏吧! 一、准备工作我们要做5个小游戏,我们要分别为5个小游戏创建一个头文件和一个源文件。分别为game1.h/game1.c,game2.h/game2.c,game3.h/game3.c,game4.h/game4.c,game5.h/game5.c。这样做的目的是把每个游戏所分开构建,方便日后对我们小游戏的重构等操作。我们还须要一个main.c,用来包含这5个小游戏的头文件。main函数只负责调用,我们把所有小游戏代码函数在其他文件中实现并用static加以修饰,使用户使用game函数间接的调用我们小游戏的代码。 二、游戏菜单我们先构建main.c: 1.它需要包含五个小游戏的头文件 2.构建我们的main函数 #include"game1.h"//包含小游戏1的头文件 #include"game2.h"//包含小游戏2的头文件 #include"game3.h"//包含小游戏3的头文件 #include"game4.h"//包含小游戏4的头文件 #include"game5.h"//包含小游戏5的头文件 #include int main() { srand((unsigned int)time(NULL));//用来生成随机数 do { int optional = 0;//创建一个接收选择的变量 menu(); printf("请输入你的选项:\n"); scanf("%d", &optional); switch (optional) { case 1: game1(); break; case 2: game2(); break; case 3: game3(); break; case 4: game4(); break; case 5: game5(); break; case 0: printf("感谢你的游玩,欢迎下次使用。\n"); exit(0); default: printf("你的选项输入有误,请重新输入:\n"); break; } } while (1); }我们在main函数中用了do-while循环,目的是让用户进行选择,直到用户选择退出或者捣乱才进行退出。 scanf函数有风险,当用户捣乱输入字母时,会造成缓冲异常。所以我们把要接收的选择变量放在循环中,并且赋初始值为0,当用户输入字母时使程序退出。 srand函数用来产生随机数,当我们只使用rand函数时所产生的随机数会不变,会使我们重复游玩的体验感变差。 我们在为这几个游戏的合集创造一个菜单,让用户选择具体游玩那个游戏。 void menu() { printf("*****************************\n"); printf("*****1.猜数字 2.三子棋*****\n"); printf("*****3.扫 雷 4.五子棋*****\n"); printf("*****5.飞行棋 0.退 出*****\n"); printf("*****************************\n"); }基本菜单已经做好了,我们进入每个小游戏的菜单。 我们先来创建小游戏的头文件: #pragma once #include #include void game1();小游戏几我们在小游戏的头文件中就对应game几,我以小游戏1为例,其他四个小游戏和这个差不多。 我们再为每个创建源文件: #include"game1.h" static void menu1() { printf("*****************************\n"); printf("***** 猜数字 *****\n"); printf("***** 1.Start the game ******\n"); printf("***** 0.Exit the game ******\n"); printf("*****************************\n"); } void game1() { int optional = 0;//创建一个接收选择的变量 do { optional = 0; menu1(); printf("请输入你的选项:\n"); scanf("%d", &optional); switch (optional) { case 1: //game(); break; case 0: printf("猜数字小游戏以退出。\n"); break; default: printf("你的选项输入有误,请重新输入:\n"); break; } } while (optional); }还是以game1为例,其他的小游戏中的选项和game1相同,只把其中的菜单函数中的小游戏换一下名字。循环中的game函数用来调用这个小游戏中的组成函数。 我们现在来运行一下,来测试一下代码是否是我们所期望的那样 思路:系统随机生成一个随机数,通过我们输入的数和生成数进行比较,给予我们提示猜大了还是猜小了。 static void game() { int num = rand() % 100 + 1;//创建一个变量,用来存放随机数。 int guess = 0;//创建一个变量,用来存放所猜的数字。 //rand函数是用来生成随机数的,我们用rand函数生成的随机数对100取模并加1,可以产生1~100的随机数 while(1) { printf("请输入你猜测的数字:"); scanf("%d", &guess); if (guess == num) { printf("恭喜你,猜对了!\n"); break; } else if (guess > num) { printf("你猜的数字过大,请重新猜测吧!\n"); } else if (guess char win;//创建一个变量,存放判段获胜条件的字符。 //我们把C代表继续,D代表平局,*代表玩家获胜,#代表电脑获胜 char checkerboard[ROW][COL] = { 0 };//创建一个数组,用来存放棋盘信息 initialization(checkerboard,ROW,COL);//初始化数组,把数组中的每个元素初始化为空格 ptintinitialization(checkerboard, ROW, COL);//打印棋盘 while(1) { Player(checkerboard, ROW, COL, '*');//玩家下棋,玩家的标志为 * win = Iswin(checkerboard, ROW, COL);//判断是否获胜 if (win != 'C') { break; } ptintinitialization(checkerboard, ROW, COL);//打印棋盘 Computer(checkerboard, ROW, COL, '#');//电脑下棋,电脑的标志为 # win = Iswin(checkerboard, ROW, COL); if (win != 'C') { break; } ptintinitialization(checkerboard, ROW, COL);//打印棋盘 } if (win == 'D') { printf("平局\n"); ptintinitialization(checkerboard, ROW, COL); } else if (win == '*') { printf("恭喜你获得胜利\n"); ptintinitialization(checkerboard, ROW, COL); } else { printf("很遗憾,你输掉了比赛\n"); ptintinitialization(checkerboard, ROW, COL); } }我们先创建我们所用到的数组和一个可以判断输赢的变量,我们把数组进行初始化,并且进行打印,让玩家知道可以下棋的位置,通过玩家下棋函数和电脑下棋函数来实现人机对战。 初始化数组函数: static void initialization(char arr[ROW][COL],int row,int col)//初始化数组 { int i = 0; int j = 0; for (i = 0; i arr[i][j] = ' ';//把数组中的每个元素赋值为空格 } } }打印棋盘函数: static void ptintinitialization(char arr[ROW][COL], int row, int col)//打印棋盘 { int i = 0; for (i = 0; i if (j printf(" %c ", arr[i][j]); } } printf("\n"); if(i if (j int x = 0; int y = 0; while(1) { printf("请输入你要下棋的坐标:"); scanf("%d %d", &x, &y); if (xrow || ycol)//判断坐标是否合法 { printf("你输入的坐标不合法,请重新输入。\n"); } else if (arr[x - 1][y - 1] != ' ')//判断输入坐标是否被占用 { printf("你输入的坐标已被占用,请重新输入。\n"); } else { arr[x - 1][y - 1] = ch;//数组下标从0开始,所以玩家的真实坐标要进行减1 break; } } }电脑下棋函数: static void Computer(char arr[ROW][COL], int row, int col, char ch) { while (1) { int x = rand() % 3;//产生0~2的数字 int y = rand() % 3; if (arr[x][y] == ' ')//如果坐标未被占用,则电脑下棋,否则产生新的坐标进行判断 { arr[x][y] = ch;//电脑的坐标的下标和数组下标对应,不需要进行减1操作 break; } } }判断是否获胜函数: static char Iswin(char arr[ROW][COL], int row, int col)//判断是否获胜 { int i = 0; int j = 0; for (i = 0; i return arr[i][1]; } } for (i = 0; i return arr[1][i]; } } if ((arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ') || (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' '))//判断对角线上是否有人获胜 { return arr[1][1]; } for (i = 0; i if (arr[i][j] == ' ');//判断是否还有空位 { return 'C'; } } } return 'D'; }让我们运行一下看看结果吧! 思路:用数组存储雷的信息,通过排查,找到没有雷的地方。 1.打印地图 2.玩家进行扫雷 3.判断玩家是否踩雷 4.更新地图 分析: 在这里插入图片描述 然后对我们的game函数进行补充,来看看具体的思路吧: static void game() { int winnum = ROW * COL - MINENUM;//设置一个变量,用来存放还有多少个安全的地方 char minemap[ROWS][COLS] = { 0 };//雷分布的地图 char showmap[ROWS][COLS] = { 0 };//向玩家展示的地图 Initialization(minemap, showmap, ROWS, COLS);//初始化数组,把雷分布随机分布在地图中,并且把向玩家展示的地图全部替换为 * //Ptintinitialization(minemap, ROW, COL);//测试代码,用来打印地图 while (winnum) { int x = 0; int y = 0; Ptintinitialization(showmap, ROW, COL);//打印展示地图 printf("请输入排雷坐标:"); scanf("%d %d", &x, &y); if (xROW || yCOL)//判断坐标是否合法 { printf("你输入的坐标不合法,请重新输入。\n"); } else if (showmap[x][y] != '*')//判断坐标是否已被排查过 { printf("你输入的坐标已被排过雷了,请重新输入。\n"); } else { if (!Determine(minemap, x, y))//!Determine(minemap, x, y)用来判断是否踩到雷 { Ptintinitialization(minemap, ROW, COL);//打印地雷地图,让玩家知道地雷在哪里。 break; } Renewmap(minemap, showmap, x, y);//用来更新向玩家展示的地图 winnum--;//对安全的地方进行减1 } } if(winnum == 0)//当安全地方为0时,证明排雷成功 { printf("恭喜你排雷成功!\n"); Ptintinitialization(showmap, ROW, COL);//打印展示地图,让玩家知道自己获胜地图 } }初始化数组函数: static void Initialization(char minemap[ROWS][COLS], char showmap[ROWS][COLS], int rows, int cols) { int count = MINENUM;//创建一个变量,用来计算放置的雷的数量 int i = 0; int j = 0; for (i = 0; i minemap[i][j] = '0'; } } while (count)//把雷分布随机分布在地雷地图中 //我们把0代表安全,1代表雷区 { int x = rand() % ROW + 1;//创建随机变量范围在1~9 int y = rand() % COL + 1;//创建随机变量范围在1~9 if (minemap[x][y] == '0')//判断该位置是否已有雷 { minemap[x][y] = '1';//放置地雷 count--; } } for (i = 0; i showmap[i][j] = '*'; } } }
我们从1开始打印,打印到ROW处,只打印我们所需要的地方。 判断是否踩到雷: static bool Determine(char minemap[ROWS][COLS],int x,int y) { if (minemap[x][y] == '1') { printf("排雷失败,你已死亡!\n"); return false; } else { return true; } }更新展示的地图: static void Renewmap(char minemap[ROWS][COLS], char showmap[ROWS][COLS], int x, int y) { showmap[x][y] = (minemap[x - 1][y - 1] + minemap[x - 1][y] + minemap[x - 1][y + 1] + minemap[x][y - 1] + minemap[x][y + 1] + minemap[x + 1][y - 1] + minemap[x + 1][y] + minemap[x + 1][y + 1]) - 8*'0' + '0'; }
思路:判断那个玩家先进行了五子连珠(一行或一列或者对角线有连续的5颗一样的棋子) 1.打印棋盘 2.玩家1进行下棋 3.判断玩家1是否获胜 4.玩家2进行下棋 5.判断玩家2是否获胜 我们还是先用宏定义棋盘大小: #define ROW 9 //棋盘的行 #define COL 9 //棋盘的列再来看game思路: static void game() { char win;//创建一个变量,存放判段获胜条件的字符。 //我们把C代表继续,D代表平局,白棋(@)代表玩家1获胜,黑棋(O)代表玩家2获胜 char checkerboard[ROW][COL] = { 0 };//棋盘数组 Initialization(checkerboard, ROW, COL);//初始化数组,把棋盘的所有位置都赋值为 * Ptintinitialization(checkerboard, ROW, COL);//打印棋盘 while (1) { Player(checkerboard, ROW, COL, '@');//玩家1下棋,玩家1的标志为 @ win = Iswin(checkerboard, ROW, COL);//判断是否获胜 if (win != 'C') { break; } Ptintinitialization(checkerboard, ROW, COL);//打印棋盘 Player(checkerboard, ROW, COL, 'O');//玩家2下棋,玩家2的标志为 O win = Iswin(checkerboard, ROW, COL);//判断是否获胜 if (win != 'C') { break; } Ptintinitialization(checkerboard, ROW, COL);//打印棋盘 } if (win == 'D') { printf("平局\n"); Ptintinitialization(checkerboard, ROW, COL); } else if (win == '@') { printf("玩家1获胜\n"); Ptintinitialization(checkerboard, ROW, COL); } else { printf("玩家2获胜\n"); Ptintinitialization(checkerboard, ROW, COL); } }五子棋的思路和三子棋的思路一模一样,只是把电脑换成了另一个玩家。所以game函数代码基本没有太大变化。而玩家下棋思路一致,所以我们只需要一个函数就可以解决两个玩家下棋的思路。 初始化数组函数: static void Initialization(char arr[ROW][COL], int row, int col)//初始化数组,把棋盘的所有位置都赋值为 * { int i = 0; int j = 0; for (i = 0; i arr[i][j] = '*'; } } }对数组进行初始化和扫雷中向玩家展示的地图的思路一模一样。 打印地图函数: static void Ptintinitialization(char arr[ROW][COL], int row, int col)//打印棋盘 { int i = 0; int j = 0; for (i = 1; i printf(" "); } if (i printf(" %d", i); } } printf("\n"); for (i = 0; i printf(" %c ", arr[i][j]);//需要多一个个空格,目的为了和地图对齐 } printf("\n"); } }五子棋的地图可以随便放大缩小,一定要注意地图是否和上面的行和列进行了对齐。 玩家下棋函数: int x = 0;//用来接收玩家下棋的坐标 int y = 0;//用来接收玩家下棋的坐标 static void Player(char arr[ROW][COL], int row, int col, char ch)//玩家下棋函数,通过ch来判断是玩家1还是玩家2 { int player = 0;//创建一个变量来确定是玩家几 if (ch == '@')//如果ch为白棋(@),则代表玩家1 { player = 1; } else//反之则为玩家2 { player = 2; } while (1) { printf("请玩家 %d 输入要下棋的坐标:",player); scanf("%d %d", &x, &y); if (xrow || ycol)//判断坐标是否合法 { printf("你输入的坐标不合法,请重新输入。\n"); } else if (arr[x - 1][y - 1] != '*')//判断输入坐标是否被占用 { printf("你输入的坐标已被占用,请重新输入。\n"); } else { arr[x - 1][y - 1] = ch;//数组下标从0开始,所以玩家的真实坐标要进行减1 break; } } }我们把玩家下棋的坐标设置为全局变量,方便我们判断是否是五子连珠的情况。 判断是否获胜函数: 我们需要统计这8个方向的棋子相同个数。不会产生5个连着 * 的情况,我们保存的地址永远是上一个玩家落子的地址。我们把8个方向用menu类型进行表示。 static int Wincount(char arr[ROW][COL], int row, int col, enum Direction dir) { int count = 0;//记录相同棋子个数 int _x = x - 1;//要对坐标进行减1,因为我们的棋盘从1开始,数组下标从0开始 int _y = y - 1;//要对坐标进行减1,因为我们的棋盘从1开始,数组下标从0开始 //用局部变量保存,避免在这里修改全局变量的值 while (1) { switch (dir) { case LEFT: _y--;//对中心坐标的列坐标进行减1 break; case RIGHT: _y++;//对中心坐标的列坐标进行加1 break; case UP: _x++; break; case DOWN: _x--; break; case LEFTUP: _y--; _x++; break; case RIGHTDOWN: _y++; _x--; break; case RIGHTUP: _y++; _x++; break; case LEFTDOWN: _y--; _x--; break; default: break; } if (_x ROW || _y COL)//判断位置是否合法 { return count; } else if(arr[_x][_y] != arr[x-1][y-1])//判断是否和上一个玩家的棋子相同 { return count; } else { count++; } } }我们设置为死循环,用来判断相同方向中具体棋子个数。menu类型更具我们传的参数不同,在switch分支中走不同的路线。 注意: 数组的越界。 对五子连珠的判断 重构思路:可以加入网络,实现网路对战,可以加入悔棋功能等 5.飞行棋玩法:1.除了起始位置是一样的,其余时间当一名玩家位置和另一名玩家位置相同时发生踩踏。 2.当玩家踩到道具时,进行相应的道具操作 3.那个玩家先到终点即为获胜 思路: 1.用数组来存储地图信息 2.用一个结构体来构造玩家信息 3.用一个结构体数组来存储玩家信息 4.用随机函数模拟筛子操作 我们先对game思路进行构造: int map[100];//创建一个地图大小为100的地图。并且把地图元素全部初始化为0 //为了对数组进行方便的操作,我们把数组设置为全局变量,使每个函数可以直接访问数组 static void game() { struct Player play[2];//创建一个玩家数组,里面存放玩家信息 Initializationmap();//初始化地图数组,把地图相应位置填上道具元素, //我们把地图数组设置为了全局函数,所以不要需要传参 Initializationplay(play);//初始化玩家数组,加入玩家信息,并把玩家位置置于地图开始位置 Ptintinitialization(play);//打印棋盘,地图数组为全局函数,所以不要对地图传参。 //需要玩家信息来判断地图要填的字符,所以要对玩家进行传参 while (1)//使玩家交替进行游玩,直到产生胜利者 { if (play[0].flags == false)//判断玩家1是否处于暂停回合。 { Play(&play[0], &play[1],play);//玩家1进行游戏 Ptintinitialization(play);//打印棋盘 } else//处于暂停回合,把暂停回合改为继续 { play[0].flags = false; } if (play[0].position >= 99)//判断玩家1是否获胜 { printf("%s侥幸赢了%s\n", play[0].name, play[1].name); break; } if (play[1].flags == false)//判断玩家2是否处于暂停回合。 { Play(&play[1], &play[0],play);//玩家2进行游戏 Ptintinitialization(play);//打印棋盘 } else { play[1].flags = false;//更改暂停选项 } if (play[1].position >= 99)//判断玩家2是否获胜 { printf("%s侥幸赢了%s\n", play[1].name, play[0].name); break; } } }我们为了方便把地图数组设置位全局数组,这样减少传参。并且数组在初始化后是不需要改变的。我们另外设置了一个结构体数组用来存放玩家信息(位置,是否处于暂停位)。 初始化地图函数: static void Initializationmap()//初始化地图数组 //地图数组中 //0代表什么都没有 //1代表道具1(幸运轮盘),2代表道具2(地雷),3代表道具3(暂停),4代表道具4(时空隧道) { int luckyturn[] = {1, 20, 45, 60, 75, 90};//在相应的位置放入 幸运轮盘 for (int i = 0; i 3, 6, 19, 25, 36, 49, 69, 70, 80 };//在相应的位置放入 地雷 for (int i = 0; i 2, 11, 26, 35, 44, 59, 71, 88 };//在相应的位置放入 暂停 for (int i = 0; i 5, 15, 30, 50, 77 };//在相应的位置放入 时空隧道 for (int i = 0; i printf("请输入玩家A的姓名\n"); scanf("%s", &(play[0].name)); while (!strcmp(play[0].name,""))//判断玩家A的姓名是否为空 { printf("玩家姓名不能为空,请重新输入玩家A的姓名\n"); scanf("%s", &(play[0].name)); } printf("请输入玩家B的姓名\n"); scanf("%s", &(play[1].name)); while (1) { if (!strcmp(play[1].name, ""))//判断玩家B的姓名是否为空 { printf("玩家姓名不能为空,请重新输入玩家B的姓名\n"); scanf("%s", &(play[1].name)); } else if(!strcmp(play[1].name, play[0].name))//判断玩家B的姓名和玩家A是否相同 { printf("玩家姓名不能一致,请重新输入玩家B的姓名\n"); scanf("%s", &(play[1].name)); } else { break; } } play[0].position = 0;//把玩家A的位置置于0位置(地图开始位置) play[0].flags = false;//把玩家A的暂停条件置为假。 play[1].position = 0;//把玩家B的位置置于0位置(地图开始位置) play[1].flags = false;//把玩家B的暂停条件置为假。 }在这个函数中我们进行了字符串比较,字符串比较不可以直接用 ==,必须要用strcmp函数 展示地图函数: extern char Drawstrmap(const struct Player* play, int i);//对Drawstrmap函数进行声明 static void Ptintinitialization(const struct Player* play) //打印棋盘 //由于不会对玩家信息进行更改,我们把参数设置为const,防止在函数中误改 { printf("图例:幸运轮盘:# 地雷:@ 暂停:I 时空隧道:> \n");//向玩家展示道具信息 //第一段 for (int i = 0; i for (int j = 0; j printf("%c ", Drawstrmap(play, i)); } printf("\n"); //第二数列 for (int i = 65; i printf("%c ", Drawstrmap(play, i)); } printf("\n");//画完地图换行 } static char Drawstrmap(const struct Player* play, int i)//打印地图元素 { char ch; if (play[0].position == play[1].position && play[0].position == i) //当玩家1和玩家2的位置处于起始位置时 { ch = 'M'; } else if (play[0].position == i)//当玩家1位于当前位置时 { ch = 'A'; } else if (play[1].position == i)//当玩家2位于当前位置时 { ch = 'B'; } else { switch (map[i]) { case 0://地图数组元素为0时 ch = 'O'; break; case 1: ch = '#'; break; case 2: ch = '@'; break; case 3: ch = 'I'; break; case 4: ch = '>'; break; } } return ch; }
我们在游玩函数中来进行筛子的模拟和道具的使用。我们设置清除缓冲区是为了解决scanf函数缓冲区问题,避免出现死循环。 注意: 数组的越界。我们要确保每个玩家的位置处于地图中。 字符串的比较 重构思路:1.可以加入网络,实现网路对战,2.把幸运轮盘单独封装为一个函数。3.加入更多道具。4.加入其他玩家。5可以加入积分,用积分换取道具。 总结我们的五个小游戏已经全部做完。下面是做小游戏要注意的点: scanf函数是不安全,我们可以寻找一个安全的函数进行替代,或者解决scanf的安全问题 谨防数组越界,我们好几个小游戏都用了数组进行存储,有字符型数组,还有整形数组,要谨防数组越界,注意数组传参。 把经常使用的代码构造为函数,减少代码冗余。 |
CopyRight 2018-2019 实验室设备网 版权所有 |