所谓三子棋游戏就是生成一个3*3的表格,共计9个格子,玩家/电脑轮流往格子里落子,当任意三个格子的棋能够连成一条直线时,判定为获胜,也就是说共有行、列、斜边三种获胜方式。那么我们怎样利用C/C++实现这个游戏呢?下面是我梳理的思路:
我将使用两个源文件和一个头文件实现这个代码,两个源文件分别为My_Three_chess.c、My_game.c,头文件是My_game.h。
(现在介绍的思路顺序对应着待会的代码顺序,方便查看)
My_Three_chess.c:
这个源文件中包含了主函数main、游戏实现函数game,主函数main中包含了游戏实现函数game和游戏菜单打印函数menu,而游戏函数game中包含了数组定义char board、数组初始化Rec_board、棋盘打印Put_board、玩家落子Player_Move、电脑落子Computer_Move、判定胜负条件It_Win等函数,这一堆函数用于实现游戏运行的各种需求,值得一提的是main函数中还包含了一个随机数生成函数stran((unsigned int)time(NULL)),通过当前时间生成随机数帮助电脑落子。
My_game.c
这个是游戏函数game中各种函数实现的源文件,里面包含了game中各种需求代码的具体实现方式,同时也是这个工程中代码量最多的地方
My_game.h
这个头文件就像是一座桥,让My_Three_chess.c和My_game.c能够顺利联系,实现代码互通,让My_Three_chess.c能够成功调用到My_game.c中的各种函数,而且这个头文件还起到了声明的作用,能够轻而易举的定义或改变一个全局变量,各种函数就存放在其中。
## 下面就是具体的实现代码
My_Three_chess.c:
#define _CRT_SECURE_NO_WARNINGS 1//一段神奇的代码,可以有效避免 “4996” scanf 问题
#include"My_game.h"
//调用My_game.h这个头文件,因为里面包含了stdio.h这个头文件,所以调用它就是在调用stdio.h,类似于嵌套调用
//三子棋
void game()
{
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | char tmp = 0;//tmp的作用是为了判定条件是否满足 srand((unsigned int)time(NULL)); //time函数简单来说就是通过当前系统时间,来获取时间戳,其头文件是time.h 同样包含在了My_game.h中 //srand和rand都是为了获取随机数,这里配合time使用,头文件为stdlib.h 同上 char board[ROW][COL] = { 0 }; Rec_board(board, ROW, COL);//初始化函数,用来初始化数组 Put_board(board, ROW, COL);//打印数组函数,用来打印棋盘 while (1)//电脑与玩家开始进行PK { Player_Move(board,ROW,COL);//玩家回合 Put_board(board, ROW, COL);//更新战况函数(即打印数组) tmp=It_Win(board, ROW, COL);//tmp是裁判,It_Win是判断输赢函数 if (tmp!='C') break;//判断依据 Computer_Move(board,ROW,COL);//电脑回合 Put_board(board, ROW, COL);//同上 tmp = It_Win(board, ROW, COL);//同上 if (tmp!='C') break;//判断依据 } if (tmp == '*') printf("n恭喜你!玩家,你获得了胜利!n"); else if (tmp == '#') { printf("n很遗憾!玩家,你败给了电脑!n"); printf("n胜负乃兵家常事,还请大侠重新来过!n"); } else printf("nAmazing!玩家,你与电脑旗鼓相当!n"); Put_board(board, ROW, COL); |
}//游戏本体函数
int main() {
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int input = 0; do { menu();//菜单函数,输出简易游戏菜单 printf("请输入你的选择(1/0):>"); scanf("%d", &input); switch (input)//switch语句,判断不同的情况,输出不同的结果 { case 1: printf("n游戏开始!n"); game();//游戏函数,游戏执行本体 break; case 0: printf("n退出游戏!n"); break; default: printf("n输入错误,需重新输入!n"); break; } } while (input);//直接使用input进行判断,若为0则直接跳出循环,结束循环 return 0; |
}
My_game.c:
#define _CRT_SECURE_NO_WARNINGS 1//一段神奇的代码,可以有效避免 “4996” scanf 问题
#include"My_game.h"
void menu()
{
| 1 2 3 4 5 | printf("**************************n"); printf("****** 1.开始游戏 ******n"); printf("****** 0.退出游戏 ******n"); printf("**************************n"); printf("nnn"); |
}//菜单函数的实现,简单打印一个选择页面
void Rec_board(char board[ROW][COL], int row, int col)
{
| 1 2 3 4 5 6 7 8 9 | int i = 0; int j = 0; for (i = 0; i < ROW; i++) { for (j = 0; j < COL; j++) { board[i][j] = ' '; } } |
}//初始化数组的实现
void Put_board(char board[ROW][COL], int row, int col)
{
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | printf("n"); int j = 0; for (j = 0; j < row; j++) { int i = 0; for (i = 0; i < col; i++) { printf(" %c ", board[j][i]);//这里需要同时用i和j,避免一次输出一行或一列 //这里我想说一下注意这个%c,别大意弄成%d,弄错就得不到空棋盘 if (i < col-1) printf("|"); } printf("n"); if (j < row-1) { int i = 0; for (i = 0; i < col; i++) { printf("---"); if (i < col-1) printf("|"); } printf("n"); } } printf("n"); |
}//打印棋盘函数的实现
void Player_Move(char board[ROW][COL], int row, int col)
{
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | int x = 0; int y = 0; printf("现在轮到你的回合n"); printf("请输入一个坐标用于下棋:>"); while (1) { scanf("%d %d", &x, &y); if (board[x - 1][y - 1] == ' ') { printf("落子成功!n"); board[x - 1][y - 1] = '*'; break; } else printf("落子失败!n此坐标非法或已被占用,请重新输入:>"); } |
}//玩家回合落子实现
void Computer_Move(char board[ROW][COL], int row, int col)
{
| 1 2 3 4 5 6 7 8 9 10 11 12 | printf("现在轮到电脑回合n"); printf("电脑输入坐标:>"); while (1) { int x = rand() % row;//使用srand生成随机数,模上row或col是为了缩小范围 int y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } |
}//电脑回合落子实现
int IsFull(char board[ROW][COL], int row, int col)
{
| 1 2 3 4 5 6 7 8 9 10 11 12 | int i = 0; int j = 0; int count = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0;//棋盘未满 } } return 1;//棋盘已满 |
}//判断棋盘是否已满
char It_Win(char board[ROW][COL], int row, int col)
{
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | int i = 0; for (i = 0; i < row; i++) { int ret = IsFull(board, row, col); if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ') return board[i][1]; }//判断行 for (i = 0; i < col; i++) { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ') return board[1][i]; }//判断列 if (board[0][0] == board[1][1] && board[2][2] == board[1][1] && board[1][1] != ' ') return board[1][1]; //判断正斜 if (board[0][2] == board[1][1] && board[2][0] == board[1][1] && board[0][2] != ' ') return board[1][1]; //判断反斜 int ret = IsFull(board, row, col); if (ret == 1) return 'H'; //判断和棋,得先判断棋盘是否已满且没有人赢 return 'C'; //继续 |
}
//是不是感觉很冗长,没错,我昨天写的时候也是这样想的
My_game.h:
#pragma once//这个是创建这个头文件时自带的
#include
#define ROW 3//define直接进行定义,全局使用
#define COL 3//同上,主要用途就是更改棋盘的大小
#include
#include
void menu();//打印菜单
void Rec_board(char board[ROW][COL], int row, int col);//初始化棋盘
void Put_board(char board[ROW][COL], int row, int col);//打印棋盘
void Player_Move(char board[ROW][COL], int row, int col);//玩家回合
void Computer_Move(char board[ROW][COL], int row, int col);//电脑回合
char It_Win(char board[ROW][COL], int row, int col);//判断输赢
int IsFull(char board[ROW][COL], int row, int col);//判断满缺
以上就是所有代码的实现,肯定有很多同学跟我一样迫不及待想看效果,下面就是!
这个是选择菜单界面,只有1跟0有效,否则就会提示输入错误
这个是棋盘的打印,现在只是一个3*3的小棋盘,其实我们可以通过修改My_game.h这个头文件中的ROW和COL来分别控制输出行跟列,这就是我前面说的头文件可以轻而易举改变全局变量
下棋界面大概就是这么个样子
第一把大意了就输给了电脑......
一雪前耻,成功打败电脑(其实这个电脑不是很聪明,可以很容易就胜利)
这就是第三种情况,和棋(这个比让电脑赢还难)
写在最后
三子棋这个小游戏本质上就是通过不断修改二维数组中的元素来达成胜利条件,代码量不是很大,很适合初学者,然后这个游戏的实现方式也能让初学者学到很多干货,for、if、switch、while多种循环都有体现,如果你想写出年轻人的第一个小游戏,选它就对了!
细心的读者不难发现,我在文中说过,可以通过更改ROW和COL实现各种尺寸棋盘的打印,那么我们是否可以不再局限于三子棋,尝试更高级的五子棋呢?答案是可以,但目前我这个代码无法实现,因为棋盘更改很容易,但我在三子棋中三子相连的判定条件不易更改,所以如果有需要的小伙伴可以尝试着去改变It_Win这个函数,实现下棋自由!
鸣谢
感谢比特鹏哥的教导,让我学会了三子棋!
感谢比特波哥的纠正,解决了困扰我许久的小细节!



