目录
前言
从大一到快大二, 学了这么久的C语言(当然不是一直都在学), 最近无聊重写了一遍自己大一上写的三子棋代码, 涉及的内容都是很简单的语法这样, 懂个基础语法和代码基本逻辑就可以轻松写出一个三子棋小项目, 下面我给出我代码实现的整体思路供参考(源码放在最后).
总体思路
①首先要有一个整体的代码框架逻辑, 写出简单的游戏菜单以及游戏的基本逻辑.
②实现具体的游戏功能, 先初始化棋盘.
③初始化棋盘后写一个能打印棋盘的函数.
④写一个让玩家落子的函数.
⑤写一个让电脑落子的函数.
⑥写一个判断胜负平局的函数.
下面我将会一步一步的实现三子棋, 所有相关的注解注释都打在了代码里.
代码整体框架(test.c)
#include "game.h" // 自定义的游戏函数头文件, 里面包括了stdio.h和自定义函数等等.
// 简易的菜单.
void menu() {
printf("*****************************\n");
printf("******** 1. play *********\n");
printf("******** 0. exit *********\n");
printf("*****************************\n");
}
// 游戏主体.
void game() {
char board[ROW][COL] = {0}; // 用二维的char数组模拟棋盘.
// 初始化.
InitBoard(board, ROW, COL); // 初始化棋盘每个点位为' '.
// 显示棋盘.
DisplayBoard(board, ROW, COL);
int flag = 0; // 判断有没有分出胜负.
while (flag == 0) {
// 玩家先走.
PlayerMove(board, ROW, COL);
// 走完显示棋盘.
DisplayBoard(board, ROW, COL);
// IsWin是判断有没有分出胜负的函数,分出了的话返回1.
flag = IsWin(board, ROW, COL); // 胜负若分则在函数中打印胜负结果.
if (flag == 1) { // 已经分胜负了就退出.
return;
}
// 电脑走.
ComputerMove(board, ROW, COL);
// 显示棋盘.
DisplayBoard(board, ROW, COL);
// 这里不用判断是否退出, 因为下次循环flag为1可以直接判断跳出循环.
flag = IsWin(board, ROW, COL);
}
}
int main() {
int input = 0; // 操作数.
srand((unsigned)time(NULL)); // 随机时间种子,用于后面电脑走生成的随机数字.
do {
// 游戏菜单先显示出来.
menu();
printf("请选择:>"); // 输入操作1或0决定玩游戏还是退出.
scanf("%d", &input);
switch (input) {
case 0:
printf("退出游戏成功!\n"); // 此时input为0,下面的while中会跳出.
system("pause"); // 停一下供玩家确定.
break;
case 1:
printf("正在进入游戏!加载中...\n");
Sleep(1000); // 闲着没事模拟了加载游戏的效果,将会停留1s.
game(); //执行游戏.
break;
default:
printf("输入有误!请重新输入!\n"); // 错误的输入将会重新循环输入.
break;
}
} while (input != 0); // input为0就会跳出循环.
return 0; // 程序结束.
}
这只是主体代码大致的逻辑, 具体的函数实现在后面会提到, srand是为了让后面rand()生成的随机数跟系统时间有关,做到一定意义上的随机.
自定义的头文件(game.h)
#pragma once // 这个不用管,是防止文件被重复包含,如果用vs2022写他会自动生成.
#include <Windows.h> // system调用的头文件.
#include <stdio.h> // 基本输入输出头文件.
#include <time.h> // time函数要用.
#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 ComputerMove(char board[ROW][COL], int row, int col);
// 判断输赢('*'玩家, '#'电脑).
int IsWin(char board[ROW][COL], int row, int col);
// 补充1: 判断落子是否合法(如越界或已经被落子了).
int CheckMove(char board[ROW][COL], int row, int col, int r, int l);
// 补充2: 判断这个位置的棋子能否赢.
int CheckWin(char board[ROW][COL], int row, int col, int r, int l);
// 补充3: 宣布胜利结果或平局结果.
void WhoWin(char winner);
实现自定义头文件中自定义函数的功能(game.c)
这里我想每个函数拆出来一步一步分析, 最后再附上总代码.
①初始化棋盘函数(InitBoard)
很简单的两个for循环嵌套让二维char数组中每个元素都为空格, 模拟没落子的情况.
// 初始化棋盘.
// 简单的两个循环将每个点都赋值空格代表没落子.
void InitBoard(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) {
board[i][j] = ' ';
}
}
}
②打印棋盘函数(DisplayBoard)
这里要注意一下打印棋盘的格式, 注意横线只打印两次.
// 打印棋盘.
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 %c", board[i][j], "|\n"[j == col - 1]);
// 等价于printf(" %c ", board[i][j]);
// if(j != col - 1) printf("%c",'|');
// else printf("%c", '\n');
}
// j如果等于col - 1则表达式返回1访问这个字符串"|\n"下标为1的'\n',否则就是'|'.
if (i != row - 1) { // 打印横着的线,井子棋三行只打印前两行,最后一行不打印.
for (j = 0; j < col; ++j) {
printf("%s%c", "---", "|\n"[j == col - 1]);
}
}
}
}
效果
③玩家下棋(PlayerMove)
这里要注意输入的数字是否合法, 当前位置是否有棋子能否落子, 也就是注意落子是否合法.
1.下棋代码(PlayerMove)
// 玩家下棋.
void PlayerMove(char board[ROW][COL], int row, int col) {
int flag = 1; // 控制循环退出于否, 由玩家输入数据是否合法来赋值flag.
while (flag != 0) {
printf("现在是你的回合!\n");
printf("请输入你的落子坐标(1~9):>");
int vis = 0; // 棋子坐标的和.
scanf("%d", &vis);
// 为了方便设置了1 ~ 9,后面先-1再/3和%3是为了计算棋子坐标.
int r = (vis - 1) / row; // 第几行
int l = (vis - 1) % col; // 第几列
if (CheckMove(board, ROW, COL, r, l) == 0) {
// 非法.
printf("输入非法!请重新输入!\n");
} else {
board[r][l] = '*'; // 赋值代表下子.
flag = 0; // 下次循环马上跳出去.
}
}
}
2.判断落子是否合法代码(CheckMove)
// 补充1: 判断落子是否合法.
int CheckMove(char board[ROW][COL], int row, int col, int r, int l) {
if (r < 0 || r >= row || l < 0 || l >= col) // 棋子越界了.
return 0;
else if (board[r][l] != ' ') // 该位置已经落子过了.
return 0;
else // 落子合法.
return 1;
}
④电脑下棋(ComputerMove)
这里思路是生成随机数随机落子, CheckMove判断落子合法与玩家下棋中的一致.
// 电脑下棋.
void ComputerMove(char board[ROW][COL], int row, int col) {
printf("现在是电脑的回合!\n");
Sleep(500); // 模拟加载.
int x = rand() % row; // 横坐标.
int y = rand() % col; // 纵坐标.
while (CheckMove(board, ROW, COL, x, y) == 0) {
// 不符合就一直循环.
x = rand() % row;
y = rand() % col;
}
board[x][y] = '#'; // 电脑落子.
}
⑤判断输赢(IsWin)
总体代码如下, 思路是循环遍历整个board数组.
每遇到一个有下棋的地方就判断这个位置的棋子能不能赢(循环同一行和同一列还有公共的对角线).
如果遇到一个是空的就让flag为1代表棋局还能继续.
如果flag一直为0且没有分出胜负则为平局.
1.IsWin代码(判断胜负信息以及输出胜负信息)
// 判断输赢('*'玩家, '#'电脑)
int IsWin(char board[ROW][COL], int row, int col) {
int i = 0;
int flag = 0; // 如果棋盘满了就是1,否则是0.
for (i = 0; i < row; ++i) {
int j = 0;
for (j = 0; j < col; ++j) {
if (board[i][j] != ' ') {
// 如果有棋子判断这个棋子的类型能不能赢.
if (CheckWin(board, row, col, i, j) == 1) {
// 宣布胜利.
WhoWin(board[i][j]); // 传入胜利的棋子的类型(人或者电脑).
return 1;
}
} else {
flag = 1; // 棋盘上有没落子的地方标记棋盘没满.
}
}
}
if (flag == 1) {
// 棋盘没满而且没人赢.
return 0; // 游戏继续.
} else {
WhoWin(' '); // 平局.
return 1; // 游戏结束.
}
}
2.CheckWin代码(判断是否分出胜负)
// 补充2: 判断这个位置的棋子能否赢.
int CheckWin(char board[ROW][COL], int row, int col, int r, int l) {
int i = 0;
char winner = board[r][l]; // 决定赢的那个棋子的属性.
for (i = 0; i < 3; ++i) {
int j = 0;
// 同一行.
for (j = 0; j < col; ++j) {
if (board[r][j] != winner)
break;
}
if (j == col) // 可以赢了跳出返回1.
return 1;
// 同一列.
for (j = 0; j < row; ++j) {
if (board[j][l] != winner)
break;
}
if (j == col) // 可以赢了跳出返回1.
return 1;
// 从左上到右下斜对角.
int k = 0;
for (j = 0, k = 0; j < row, k < col; ++j, ++k) {
if (board[j][k] != winner)
break;
}
if (j == row) // 可以赢了跳出返回1.
return 1;
// 从右上到左下斜对角.
for (j = 0, k = col - 1; j < row, k >= 0; ++j, --k) {
if (board[j][k] != winner)
break;
}
if (j == row) // 可以赢了跳出返回1.
return 1;
}
return 0;
}
3.WhoWin代码(得出胜负后输出胜负信息)
// 补充3: 宣布胜利.
void WhoWin(char winner) {
if (winner == ' ') {
printf("平局!\n");
system("pause");
system("cls"); // 清屏操作, 美观一点.
} else if (winner == '*') {
printf("玩家获胜!\n");
system("pause");
system("cls");
} else {
printf("电脑获胜!\n");
system("pause");
system("cls");
}
}
整体代码如下
#include "game.h" // 自定义的头文件.
// 初始化棋盘.
void InitBoard(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) {
board[i][j] = ' ';
}
}
}
// 打印棋盘.
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 %c", board[i][j], "|\n"[j == col - 1]);
} // j如果等于col - 1.
// 则表达式返回1访问这个字符串下标为1的换行,否则就是'|'.
if (i != row - 1) {
for (j = 0; j < col; ++j) {
printf("%s%c", "---", "|\n"[j == col - 1]);
}
}
}
}
// 玩家下棋.
void PlayerMove(char board[ROW][COL], int row, int col) {
int flag = 1;
while (flag != 0) {
printf("现在是你的回合!\n");
printf("请输入你的落子坐标(1~9):>");
int vis = 0; // 棋子坐标的和.
scanf("%d", &vis); // 为了方便设置了1 ~ 9,后面-1再/3和%3是为了计算棋子坐标.
int r = (vis - 1) / row; // 第几行
int l = (vis - 1) % col; // 第几列
if (CheckMove(board, ROW, COL, r, l) == 0) {
// 非法.
printf("输入非法!请重新输入!\n");
} else {
board[r][l] = '*'; // 赋值代表下子.
flag = 0; // 下次循环马上跳出去.
}
}
}
// 电脑下棋.
void ComputerMove(char board[ROW][COL], int row, int col) {
printf("现在是电脑的回合!\n");
Sleep(500);
int x = rand() % row;
int y = rand() % col;
while (CheckMove(board, ROW, COL, x, y) == 0) {
// 不符合就一直循环.
x = rand() % row;
y = rand() % col;
}
board[x][y] = '#'; // 电脑落子.
}
// 判断输赢('*'玩家, '#'电脑, 'Q'平局, 'C'继续)
int IsWin(char board[ROW][COL], int row, int col) {
int i = 0;
int flag = 0; // 如果棋盘满了就是1,否则是0.
for (i = 0; i < row; ++i) {
int j = 0;
for (j = 0; j < col; ++j) {
if (board[i][j] != ' ') {
if (CheckWin(board, row, col, i, j) == 1) {
// 宣布胜利.
WhoWin(board[i][j]);
return 1;
}
} else {
flag = 1; // 标记棋盘没满.
}
}
}
if (flag == 1) {
// 棋盘没满而且没人赢.
return 0;
} else {
WhoWin(' '); // 平局.
return 1;
}
}
// 补充1: 判断落子是否合法.
int CheckMove(char board[ROW][COL], int row, int col, int r, int l) {
if (r < 0 || r >= row || l < 0 || l >= col)
return 0;
else if (board[r][l] != ' ')
return 0;
else
return 1;
}
// 补充2: 判断这个位置的棋子能否赢.
int CheckWin(char board[ROW][COL], int row, int col, int r, int l) {
int i = 0;
char winner = board[r][l]; // 决定赢的那个棋子的属性.
for (i = 0; i < 3; ++i) {
int j = 0;
// 同一行.
for (j = 0; j < col; ++j) {
if (board[r][j] != winner)
break;
}
if (j == col) // 可以赢了跳出返回1.
return 1;
// 同一列.
for (j = 0; j < row; ++j) {
if (board[j][l] != winner)
break;
}
if (j == col) // 可以赢了跳出返回1.
return 1;
// 从左上到右下斜对角.
int k = 0;
for (j = 0, k = 0; j < row, k < col; ++j, ++k) {
if (board[j][k] != winner)
break;
}
if (j == row) // 可以赢了跳出返回1.
return 1;
// 从右上到左下斜对角.
for (j = 0, k = col - 1; j < row, k >= 0; ++j, --k) {
if (board[j][k] != winner)
break;
}
if (j == row) // 可以赢了跳出返回1.
return 1;
}
return 0;
}
// 补充3: 宣布胜利.
void WhoWin(char winner) {
if (winner == ' ') {
printf("平局!\n");
system("pause");
system("cls");
} else if (winner == '*') {
printf("玩家获胜!\n");
system("pause");
system("cls");
} else {
printf("电脑获胜!\n");
system("pause");
system("cls");
}
}
结语
三子棋到这就结束了, 总体上是一个非常简单适合新手练习基本语法和代码逻辑的小项目. 如果帮助到了你也可以点个赞点个关注, 谢谢支持. 下面附上我的源码地址供参考: