前言:相信大家都玩过三子棋吧,曾想经常和同学在考试之后玩一个晚自习的三子棋。那么,如何自己编写一个三子棋游戏呢,请看下面的分析;
1.游戏设计思路
在写任何代码时,最好先有个大致的框架,然后在大的框架下不断填充相应的细节,来实现相应的功能。
那么,想设计一个三子棋游戏需要哪些步骤呢
1.主函数部分
2.初始化棋盘
3.打印棋盘
4.下棋(玩家和电脑)
5.判断输赢
2.设计之前的一些准备
可以预见到,在编写游戏时需要使用到大量的函数,如果在同一文件下既声明函数,又定义函数,最后再调用函数,会使得整个程序杂乱无章,难以阅读。这时,利用分模块编程的方法就可以很好的避免代码混乱,冗长的问题。
所谓分模块编程,即创立三个文件,一个头文件game.h(用来存放函数的声明,包括各种库函数以及使用到的标识符常量),两个源文件game.c(用来存放函数的定义)和test.c(函数的实现以及主函数);
同时,为了使代码的灵活性大大提高,我们可以使用#define 定义的标识符常量来定义棋盘的大小;
3.具体实现过程
1.主函数部分
主函数是整个游戏过程的初始化,要根据用户的要求来判断是否进行游戏;
代码如下:
int main() { srand((unsigned int)time(NULL)); int input = 0; do //不管三七二十一先打印一次菜单 { menu();//打印菜单 scanf("%d",&input); //根据用户输入的内容来判断是否进行后续操作 switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("输入错误,请重新输入"); } } while (input); //只有输入0的时候才会推出循环(0为假) return 0; }
2.初始化棋盘
分析:棋盘是用来落子的,在程序中其实就是存放数据的,又因为棋盘的形状,很自然的想到使用二维数组存放数据,所以我们可以使用二维数组来初始化棋盘
//game.h //函数声明,定义都要指明各参数的类型 void InitBoard (char board [ROW][COL], int row, int col); //game.c void InitBoard(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { board[i][j] = ' ';//将棋盘全部初始化为空格 } //注意这里不是"",是'',因为空格本质上是个字符,引用字符要使用单引号 } //若使用" ",本质上是一个字符串引用,实际上存储了两个字符,还有\0 } //1.test.c 存放数据 ->二维数组 先用空格代替 InitBoard(board, ROW, COL);
3.打印棋盘
在初始化棋盘后,发现棋盘仅仅只能存储数据(而且数据是空格),并没有边界线,所以我们要想办法打印棋盘
#define ROW 3 //这个刚开始犯了一个巨低级的错误,在最后添加了; #define COL 3 //只有语句的结束才需要添加分号 //game.h //打印棋盘 void DisplayBoard(char board[ROW][COL], int row, int col); //game.c //打印棋盘 //版本1 只有空格 并没有分割线,不符合棋盘样式 //void DisplayBoard(char board[ROW][COL], int row, int col) //{ // int i = 0; // int j = 0; // for (i = 0; i < row; i++) // { // for (j = 0; j < col; j++) // { // printf("%c", board[i][j]); // } // printf("\n"); // } //} //版本2 行数和列数都限制死了 //void DisplayBoard(char board[ROW][COL], int row, int col) //{ // int i = 0; // //一行一行打印 // for (i = 0; i < row; i++) // { // //1.打印数据 // printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); // //2.打印分割线 // if(i<row -1 )//最后一行不打印分割线 // printf("---|---|---\n"); // } // //} //版本3 利用大事化小的思维,利用循环打印数据和分割线 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; //一行一行打印 for (i = 0; i < row; i++) { int j = 0; //拆分为更小的单位 for (j = 0; j < col; j++) { //打印数据 printf(" %c ",board[i][j]); if (j < col - 1)//最后一个|不打印 { printf("|"); } } printf("\n"); //打分割线 if (i < row - 1) { int j = 0; for (j = 0; j < col; j++)//有几列就有几组 { printf("---"); if (j < col - 1) { printf("|"); } } printf("\n"); } } } //test.c //2.打印棋盘 DisplayBoard(board, ROW, COL);
4.模拟下棋(玩家and电脑)
下棋整个过程是游戏实现的重要步骤,在下棋的过程中要模拟真实世界的下棋过程(循环一直对垒,直到分出输赢),下面请看代码实现
game.h //下棋 //玩家下棋 void PlayerMove(char board[ROW][COL], int row, int col); //电脑下棋 void ComMove(char board[ROW][COL], int row, int col); game.c //下棋 //玩家先步 void PlayerMove(char board[ROW][COL], int row, int col) { //给用户提供坐标 int x = 0; int y = 0; printf("玩家下棋->"); while (1) { printf("请输入要下的坐标,中间请用空格分隔->\n"); scanf("%d %d", &x, &y);//用户输入之后,要判断用户输入的行数和列数是否合法 if (x >= 1 && x <= row && y >= 1 && y <= col)//输入合法 { //要注意从用户的角度去思考,用户只认为的第一行是从1开始的,而不是0 if (board[x - 1][y - 1] == ' ')//分两种情况讨论 { //1.位置没有被占用 board[x - 1][y - 1] = '*'; break;//输入正确就挑出玩家下棋的过程 } else //2.位置已被占用 { printf("该位置已被占用,请重新输入"); } } else //也要考虑到玩家有可能输入的坐标值越界 { printf("坐标非法,请重新输入:"); } } } //电脑下棋 void ComMove(char board[ROW][COL], int row, int col)//可见,电脑和用户还是有很大区别的 { int x = 0; int y = 0; printf("电脑下棋->\n"); //电脑下的位置是随机的 但是也要满足下棋的规则 while (1) { x = rand() % row;//产生0-row-1的随机数 y = rand() % col; if ( board[x][y] ==' ') { board[x][y] = '#'; break; } } } test.c while (1)//用循环模拟对战环节 下一步棋,打印一次 { PlayerMove(board, ROW, COL);//玩家下棋 DisplayBoard(board, ROW, COL);}
5.判断输赢
在每一次玩家或者电脑落子之后都要进行一次输赢的判断;这里的难点在于要对输赢的条件进行充分的考虑。比赛的结果分为三种,玩家赢,电脑赢,平局;
比如获胜的情况大致可分为四种,主对角线,次对角线,横着一排,竖着一排,每种情况都要设置相应的代码;
下面请看代码实现过程
game.h //判断输赢 在玩家或者用户每一次落子之后进行判断 //玩家赢 '*' //电脑赢 '#' //平局 'Q'//这样设置返回值的原因是:除了继续这种条件外,其他的都属于游戏结束的范畴 //继续 'C' //那我只需接受返回值,不是c,就跳出对垒,游戏结束 int IsWin(char board[ROW][COL], int row, int col); game.c int IsFull(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; } } return 1;//找不到空格,证明满了,就是平局 } int IsWin(char board[ROW][COL], int row, int col) { //先判断赢 //1.连成一行 int i = 0; for (i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] &&board[i][0]!=' ') return board[i][0]; } //2.连成一列 int j = 0; for (j = 0; j < col; j++) { if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j]!=' ') return board[0][j]; } //3.对角线 //主对角线 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0]!=' ') return board[0][0]; //次对角线 if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2]!=' ') return board[0][2]; //平局 if (IsFull(board,row,col) == 1) return 'Q'; //继续 return 'C'; } test.c while (1)//用循环模拟对战环节 下一步棋,打印一次 { PlayerMove(board, ROW, COL);//玩家下棋 DisplayBoard(board, ROW, COL); //判断输赢 ret = IsWin(board, ROW, COL); if (ret != 'C') break; ComMove(board, ROW, COL); DisplayBoard(board, ROW, COL); //判断输赢 ret = IsWin(board, ROW, COL); if (ret != 'C') //只要不是c继续这种条件,其余情况都属于游戏结束的范畴 break; } if (ret == '*') printf("玩家赢\n"); else if (ret == '#') printf("电脑赢\n"); else printf("平局\n"); }
4.总结部分
通过编写三子棋游戏,很锻炼我们的思维,我们不能仅仅认为代码能跑就行,而是应该不断去优化我们的代码,这样我们的代码能力才可以提高。在编写代码时,我们要有自己的大致框架,路线是个纲,沿着正确的路线不断填充我们的细节;
最后附上各文件完整程序代码
game.h
#pragma once //此文件是与游戏有关函数声明部分 //有一个细节,将所有的可能用到的头文件都写到game.h之中,在其余的文件中只需引用此文件即可 #include <stdio.h> #include <stdlib.h> #include <time.h> #define ROW 3 //这个刚开始犯了一个巨低级的错误,在最后添加了; #define COL 3 //只有语句的结束才需要添加分号 //这样定义的好处可以自由改变棋盘的大小 //初始化棋盘 //函数声明,定义都要指明各参数的类型 void InitBoard (char board [ROW][COL], int row, int col); //打印棋盘 void DisplayBoard(char board[ROW][COL], int row, int col); //下棋 //玩家下棋 void PlayerMove(char board[ROW][COL], int row, int col); //电脑下棋 void ComMove(char board[ROW][COL], int row, int col); //判断输赢 在玩家或者用户每一次落子之后进行判断 //玩家赢 '*' //电脑赢 '#' //平局 'Q'//这样设置返回值的原因是:除了继续这种条件外,其他的都属于游戏结束的范畴 //继续 'C' //那我只需接受返回值,不是c,就跳出对垒,游戏结束 int IsWin(char board[ROW][COL], int row, int col);
game.c
//此文件是和游戏有关函数的定义部分 #define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" //初始化棋盘 void InitBoard(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { board[i][j] = ' ';//将棋盘全部初始化为空格 } //注意这里不是"",是'',因为空格本质上是个字符,引用字符要使用单引号 } //若使用" ",本质上是一个字符串引用,实际上存储了两个字符,还有\0 } //打印棋盘 //版本1 只有空格 并没有分割线,不符合棋盘样式 //void DisplayBoard(char board[ROW][COL], int row, int col) //{ // int i = 0; // int j = 0; // for (i = 0; i < row; i++) // { // for (j = 0; j < col; j++) // { // printf("%c", board[i][j]); // } // printf("\n"); // } //} //版本2 行数和列数都限制死了 //void DisplayBoard(char board[ROW][COL], int row, int col) //{ // int i = 0; // //一行一行打印 // for (i = 0; i < row; i++) // { // //1.打印数据 // printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); // //2.打印分割线 // if(i<row -1 )//最后一行不打印分割线 // printf("---|---|---\n"); // } // //} //版本3 利用大事化小的思维,利用循环打印数据和分割线 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; //一行一行打印 for (i = 0; i < row; i++) { int j = 0; //拆分为更小的单位 for (j = 0; j < col; j++) { //打印数据 printf(" %c ",board[i][j]); if (j < col - 1)//最后一个|不打印 { printf("|"); } } printf("\n"); //打分割线 if (i < row - 1) { int j = 0; for (j = 0; j < col; j++)//有几列就有几组 { printf("---"); if (j < col - 1) { printf("|"); } } printf("\n"); } } } //下棋 //玩家先步 void PlayerMove(char board[ROW][COL], int row, int col) { //给用户提供坐标 int x = 0; int y = 0; printf("玩家下棋->"); while (1) { printf("请输入要下的坐标,中间请用空格分隔->\n"); scanf("%d %d", &x, &y);//用户输入之后,要判断用户输入的行数和列数是否合法 if (x >= 1 && x <= row && y >= 1 && y <= col)//输入合法 { //要注意从用户的角度去思考,用户只认为的第一行是从1开始的,而不是0 if (board[x - 1][y - 1] == ' ')//分两种情况讨论 { //1.位置没有被占用 board[x - 1][y - 1] = '*'; break;//输入正确就挑出玩家下棋的过程 } else //2.位置已被占用 { printf("该位置已被占用,请重新输入"); } } else { printf("坐标非法,请重新输入:"); } } } //电脑下棋 void ComMove(char board[ROW][COL], int row, int col)//可见,电脑和用户还是有很大区别的 { int x = 0; int y = 0; printf("电脑下棋->\n"); //电脑下的位置是随机的 但是也要满足下棋的规则 while (1) { x = rand() % row;//产生0-row-1的随机数 y = rand() % col; if ( board[x][y] ==' ') { board[x][y] = '#'; break; } } } int IsFull(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; } } return 1;//找不到空格,证明满了,就是平局 } int IsWin(char board[ROW][COL], int row, int col) { //先判断赢 //1.连成一行 int i = 0; for (i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] &&board[i][0]!=' ') return board[i][0]; } //2.连成一列 int j = 0; for (j = 0; j < col; j++) { if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j]!=' ') return board[0][j]; } //3.对角线 //主对角线 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0]!=' ') return board[0][0]; //次对角线 if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2]!=' ') return board[0][2]; //平局 if (IsFull(board,row,col) == 1) return 'Q'; //继续 return 'C'; }
test.c
#define _CRT_SECURE_NO_WARNINGS 1 //这个文件是游戏实现的文件 //三子棋游戏的实现 #include "game.h" //菜单函数 void menu() { printf("******************\n"); printf("*****1 ->play*****\n"); printf("*****0 ->exit*****\n"); } //游戏函数 void game() { char board[ROW][COL] = {0}; //1.初始化棋盘 存放数据 ->二维数组 先用空格代替 InitBoard(board, ROW, COL); //2.打印棋盘 DisplayBoard(board, ROW, COL); //3.下棋并判断胜负 char ret = ' '; while (1)//用循环模拟对战环节 下一步棋,打印一次 { PlayerMove(board, ROW, COL);//玩家下棋 DisplayBoard(board, ROW, COL); //判断输赢 ret = IsWin(board, ROW, COL); if (ret != 'C') break; ComMove(board, ROW, COL); DisplayBoard(board, ROW, COL); //判断输赢 ret = IsWin(board, ROW, COL); if (ret != 'C') break; } if (ret == '*') printf("玩家赢\n"); else if (ret == '#') printf("电脑赢\n"); else printf("平局\n"); } int main() { srand((unsigned int)time(NULL)); int input = 0; do { menu();//打印菜单 scanf("%d",&input); //根据用户输入的内容来判断是否进行后续操作 switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("输入错误,请重新输入"); } } while (input); //只有输入0的时候才会推出循环(0为假) return 0; }
游戏截图(让电脑一盘!)