前言:相信大家都玩过三子棋吧,曾想经常和同学在考试之后玩一个晚自习的三子棋。那么,如何自己编写一个三子棋游戏呢,请看下面的分析;
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;
}
游戏截图(让电脑一盘!)