C语言三子棋与电脑难度提升
大家好,这一篇博客讲的是我对 C语言三字棋 的理解。以下代码使用的是 VS2022环境下的 C语言,如果有错误,还请读者大大们指出。
开始之前准备好 game.h、game.c、test.c文件。
三子棋基础内容实现
界面代码
在game.h中:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#define ROW 3 // 方便扩展
#define COL 3
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
在test.c中:
#include "game.h"
void menu()
{
printf("*************************\n");
printf("**** 1.play 0.exit ****\n");
printf("*************************\n");
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL)); // 为后续的电脑随机下棋准备
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
//game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误!\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
初始化与棋盘打印代码
在game.h中:
void initBoard(char board[ROW][COL], int row, int col);
void display(char board[ROW][COL], int row, int col);
在game.c中:
#include "game.h"
void initBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
board[i][j] = ' ';
}
void display(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1) // 打印纵分割线
printf("|");
}
printf("\n");
if (i < row - 1) // 打印横分割线
{
for (int j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");
}
}
玩家操作与游戏框架代码
在game.h中:
void playerMove(char board[ROW][COL], int row, int col);
在game.c中:
void playerMove(char board[ROW][COL], int row, int col)
{
int y, x;
printf("玩家下棋:>\n");
while (1)
{
printf("请输入坐标:>");
scanf("%d %d", &y, &x);
if (x >= 1 && x <= 3 && y >= 1 && y <= 3 && board[y - 1][x - 1] == ' ') // 限定范围
{
board[y - 1][x - 1] = '*'; // 这里要对应数组下标
break;
}
else
printf("坐标非法或被占用,请重新输入\n");
}
}
在test.c中:
void game()
{
char board[ROW][COL] = { 0 };
initBoard(board, ROW, COL);
display(board, ROW, COL);
}
电脑操作代码
在game.h中:
void computerMove(char board[ROW][COL], int row, int col);
在game.c中:
void computerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋\n");
int y, x;
while (1) // 这里使用了随机下棋
{
y = rand() % row;
x = rand() % col;
if (board[y][x] == ' ') // 是空格才下
{
board[y][x] = '#';
break;
}
}
}
游戏输、赢、平局判定
在game.h中:
char IsWin(char board[ROW][COL], int row, int col);
在game.c中:
int IsFull(char board[ROW][COL], int row, int col)
{// 判断棋盘是否满了
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
if (board[i][j] == ' ')
return 0;
return 1;
}
char IsWin(char board[ROW][COL], int row, int col)
{// 检测行、列、对角线棋子情况
for (int i = 0; i < row; i++) // 行
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
return board[i][1]; // 若当行三子相同,返回其棋子
for (int j = 0; j < col; j++) // 列
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
return board[1][j];// 若当列三子相同,返回其棋子
// 对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
return board[1][1];// 若当对角线三子相同,返回其棋子
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
return board[1][1];
if (IsFull(board, row, col))
return 'Q'; // 字符Q表示棋盘满了
return 'C'; // 字符C表示下棋正常进行
}
在test.c中:
void game()
{
char board[ROW][COL] = { 0 };
char ret = 0; // 接收棋盘下棋的信息
initBoard(board, ROW, COL);
display(board, ROW, COL);
while (1) // 使用循环
{
playerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL); // 接收信息
display(board, ROW, COL);
if (ret != 'C') // 若不为正常情况,跳出循环
break;
computerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL);
display(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*') // 跳出循环时判断下棋之后的情况
printf("玩家赢\n");
else if (ret == '#')
printf("电脑赢\n");
else if (ret == 'Q')
printf("平局\n");
}
整体源代码
在game.h中:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#define ROW 3 // 方便扩展
#define COL 3
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void initBoard(char board[ROW][COL], int row, int col);
void display(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);
char IsWin(char board[ROW][COL], int row, int col);
在game.c中:
#include "game.h"
void initBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
board[i][j] = ' ';
}
void display(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1) // 打印纵分割线
printf("|");
}
printf("\n");
if (i < row - 1) // 打印横分割线
{
for (int j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");
}
}
void computerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋\n");
int y, x;
while (1) // 这里使用了随机下棋
{
y = rand() % row;
x = rand() % col;
if (board[y][x] == ' ') // 是空格才下
{
board[y][x] = '#';
break;
}
}
}
void playerMove(char board[ROW][COL], int row, int col)
{
int y, x;
printf("玩家下棋:>\n");
while (1)
{
printf("请输入坐标:>");
scanf("%d %d", &y, &x);
if (x >= 1 && x <= 3 && y >= 1 && y <= 3 && board[y - 1][x - 1] == ' ') // 限定范围
{
board[y - 1][x - 1] = '*'; // 这里要对应数组下标
break;
}
else
printf("坐标非法或被占用,请重新输入\n");
}
}
int IsFull(char board[ROW][COL], int row, int col)
{// 判断棋盘是否满了
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
if (board[i][j] == ' ')
return 0;
return 1;
}
char IsWin(char board[ROW][COL], int row, int col)
{// 检测行、列、对角线棋子情况
for (int i = 0; i < row; i++) // 行
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
return board[i][1]; // 若当行三子相同,返回其棋子
for (int j = 0; j < col; j++) // 列
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
return board[1][j];// 若当列三子相同,返回其棋子
// 对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
return board[1][1];// 若当对角线三子相同,返回其棋子
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
return board[1][1];
if (IsFull(board, row, col))
return 'Q'; // 字符Q表示棋盘满了
return 'C'; // 字符C表示下棋正常进行
}
在test.c中:
#include "game.h"
void menu()
{
printf("*************************\n");
printf("**** 1.play 0.exit ****\n");
printf("*************************\n");
}
void game()
{
char board[ROW][COL] = { 0 };
char ret = 0; // 接收棋盘下棋的信息
initBoard(board, ROW, COL);
display(board, ROW, COL);
while (1) // 使用循环
{
playerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL); // 接收信息
display(board, ROW, COL);
if (ret != 'C') // 若不为正常情况,跳出循环
break;
computerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL);
display(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*') // 跳出循环时判断下棋之后的情况
printf("玩家赢\n");
else if (ret == '#')
printf("电脑赢\n");
else if (ret == 'Q')
printf("平局\n");
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL)); // 为后续的电脑随机下棋准备
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误!\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
以上是C语言三子棋的基础内容实现。
电脑难度提升
这里使用的是 暴力枚举 的方法,也就是使用循环加判断的方式将各种情况进行分析。
不过,在三子棋3×3对称且有下棋方法的棋盘中大部分枚举的情况是可以省略的。
先创建 computerDifficult.c源文件
三子连接防御
三子连接防御:当玩家下出两个连接的棋子,再下一颗连接成三子就胜利,这里电脑需要阻拦玩家连接成三个棋子。
绿色圆为玩家棋子,红色菱形为电脑棋子。这意味着我们需要将所有的行、列、对角线的每个位置进行判断。
在computerDifficult.c中:
#include "game.h"
int difficult(char board[ROW][COL], char set1, char set2)
{
for (int i = 0; i < 3; i++)
{
// 行的防御
if (board[i][0] == board[i][1] && board[i][0] == set1 && board[i][2] == set2) // 行尾
{
board[i][2] = '#';
return 1;
}
if (board[i][0] == board[i][2] && board[i][0] == set1 && board[i][1] == set2) // 行中
{
board[i][1] = '#';
return 1;
}
if (board[i][1] == board[i][2] && board[i][1] == set1 && board[i][0] == set2) // 行头
{
board[i][0] = '#';
return 1;
}
// 列的防御
if (board[0][i] == board[1][i] && board[0][i] == set1 && board[2][i] == set2) // 列尾
{
board[2][i] = '#';
return 1;
}
if (board[0][i] == board[2][i] && board[0][i] == set1 && board[1][i] == set2) // 列中
{
board[1][i] = '#';
return 1;
}
if (board[1][i] == board[2][i] && board[1][i] == set1 && board[0][i] == set2) // 列头
{
board[0][i] = '#';
return 1;
}
}
// 对角线的防左斜
if (board[0][0] == board[1][1] && board[1][1] == set1 && board[2][2] == set2) // 对角线左斜下
{
board[2][2] = '#';
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == set1 && board[1][1] == set2) // 对角线左斜中
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == set1 && board[0][0] == set2) // 对角线左斜上
{
board[0][0] = '#';
return 1;
}
// 对角线的防右斜
if (board[0][2] == board[1][1] && board[1][1] == set1 && board[2][0] == set2) // 对角线右斜下
{
board[2][0] = '#';
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == set1 && board[1][1] == set2) // 对角线右斜中
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == set1 && board[0][2] == set2) // 对角线右斜上
{
board[0][2] = '#';
return 1;
}
return 0;
}
在game.h中:
int difficult(char board[ROW][COL], char set1, char set2);
在game.c中:
void computerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋\n");
int y, x;
if (difficult(board, '*', ' ')); // 当防御时返回1,否则返回0。
else
{
while (1) // 这里使用了随机下棋
{
y = rand() % row;
x = rand() % col;
if (board[y][x] == ' ') // 是空格才下
{
board[y][x] = '#';
break;
}
}
}
}
三子连接攻击
三子连接攻击:当电脑下出两个连接的棋子,再下一颗连接成三子就胜利。
这里只需要将 difficult函数 的参数更改一下。
不过要注意,三子连接攻击的优先度应高于三子连接防御,如:
若到电脑下棋,此时应下3行3列获得胜利。
在game.c中:
void computerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋\n");
int y, x;
// 三子连接攻击 三子连接防御
if (difficult(board, '#', ' ') || difficult(board, '*', ' ')); // 当防御时返回1,否则返回0。
else
{
while (1) // 这里使用了随机下棋
{
y = rand() % row;
x = rand() % col;
if (board[y][x] == ' ') // 是空格才下
{
board[y][x] = '#';
break;
}
}
}
}
二子连接
二子连接:当电脑下出一个棋子时,对其进行连接。
通过观察将棋子下在不同位置,其优劣是不同的:
在中心有四条线可以连接成三子,在角有三条线可以连接成三子,在边有二条线可以连接成三子。
我们可以继续使用 difficult函数 ,不过需要更改函数中的一些代码 和 下棋位置的优先度。
在computerDifficult.c中:
#include "game.h"
int difficult(char board[ROW][COL], char set1, char set2)
{
// 对角线的防与攻左斜
if (board[0][0] == board[1][1] && board[1][1] == set1 && board[2][2] == set2) // 对角线左斜下
{
board[0][0] == ' ' ? (board[0][0] = '#') : (board[2][2] = '#');
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == set1 && board[1][1] == set2) // 对角线左斜中
{
board[0][0] == ' ' ? (board[0][0] = '#') : (board[1][1] = '#');
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == set1 && board[0][0] == set2) // 对角线左斜上
{
board[2][2] == ' ' ? (board[2][2] = '#') : (board[0][0] = '#');
return 1;
}
// 对角线的防与攻右斜
if (board[0][2] == board[1][1] && board[1][1] == set1 && board[2][0] == set2) // 对角线右斜下
{
board[0][2] == ' ' ? (board[0][2] = '#') : (board[2][0] = '#');
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == set1 && board[1][1] == set2) // 对角线右斜中
{
board[0][2] == ' ' ? (board[0][2] = '#') : (board[1][1] = '#');
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == set1 && board[0][2] == set2) // 对角线右斜上
{
board[2][0] == ' ' ? (board[2][0] = '#') : (board[0][2] = '#');
return 1;
}
for (int i = 0; i < 3; i++)
{
// 行的防御
if (board[i][0] == board[i][1] && board[i][0] == set1 && board[i][2] == set2) // 行尾
{
board[i][0] == ' ' ? (board[i][0] = '#') : (board[i][2] = '#');
return 1;
}
if (board[i][0] == board[i][2] && board[i][0] == set1 && board[i][1] == set2) // 行中
{
board[i][0] == ' ' ? (board[i][0] = '#') : (board[i][1] = '#');
return 1;
}
if (board[i][1] == board[i][2] && board[i][1] == set1 && board[i][0] == set2) // 行头
{
board[i][2] == ' ' ? (board[i][2] = '#') : (board[i][0] = '#');
return 1;
}
// 列的防御
if (board[0][i] == board[1][i] && board[0][i] == set1 && board[2][i] == set2) // 列尾
{
board[0][i] == ' ' ? (board[0][i] = '#') : (board[2][i] = '#');
return 1;
}
if (board[0][i] == board[2][i] && board[0][i] == set1 && board[1][i] == set2) // 列中
{
board[0][i] == ' ' ? (board[0][i] = '#') : (board[1][i] = '#');
return 1;
}
if (board[1][i] == board[2][i] && board[1][i] == set1 && board[0][i] == set2) // 列头
{
board[2][i] == ' ' ? (board[2][i] = '#') : (board[0][i] = '#');
return 1;
}
}
return 0;
}
在game.c中:
void computerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋\n");
int y, x;
// 三子连接攻击 三子连接防御 二子连接
if (difficult(board, '#', ' ') || difficult(board, '*', ' ') || difficult(board, ' ', '#')); // 当防御时返回1,否则返回0。
else
{
while (1) // 这里使用了随机下棋
{
y = rand() % row;
x = rand() % col;
if (board[y][x] == ' ') // 是空格才下
{
board[y][x] = '#';
break;
}
}
}
}
当执行二子连接时,此代码优先考虑棋盘的四个角。
开局处理
根据上一部分我们可知电脑下棋位置的优先度应为中心、角、边。
开局时若中心没有棋子,则电脑优先选择,反之选择四个角的其中一个。
在computerDifficult.c中:
int first(char board[3][3])
{
if (board[1][1] == ' ') // 开局占中心
{
board[1][1] = '#';
return 1;
}
else// 开局占四角
{
int arr[2] = { 0, 2 };
int i = arr[rand() % 2];
int j = arr[rand() % 2];
if (board[i][j] == ' ')
{
board[i][j] = '#';
return 1;
}
}
return 0;
}
在game.h中:
int first(char board[3][3]);
在game.c中:
void computerMove(char board[ROW][COL], int row, int col, int* firstMove)
{
printf("电脑下棋\n");
int y, x;
// 三子连接攻击 三子连接防御 二子连接
if (difficult(board, '#', ' ') || difficult(board, '*', ' ') || difficult(board, ' ', '#')); // 当防御时返回1,否则返回0。
else if (*firstMove && first(board)) // 开局
*firstMove = 0;
else
{
while (1) // 这里使用了随机下棋
{
y = rand() % row;
x = rand() % col;
if (board[y][x] == ' ') // 是空格才下
{
board[y][x] = '#';
break;
}
}
}
}
在test.c中:
void game()
{
int firstMove = 1; // 判断是否是开局
char board[ROW][COL] = { 0 };
char ret = 0; // 接收棋盘下棋的信息
initBoard(board, ROW, COL);
display(board, ROW, COL);
while (1) // 使用循环
{
playerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL); // 接收信息
display(board, ROW, COL);
if (ret != 'C') // 若不为正常情况,跳出循环
break;
computerMove(board, ROW, COL, &firstMove);
ret = IsWin(board, ROW, COL);
display(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*') // 跳出循环时判断下棋之后的情况
printf("玩家赢\n");
else if (ret == '#')
printf("电脑赢\n");
else if (ret == 'Q')
printf("平局\n");
}
穷举特殊情况
上面针对提升的电脑难度的代码,已经可以保证大部分情况电脑不会输给玩家。
不过还有些情况没有覆盖到,这里我们可以穷举出来。
同边情况
如图:
当玩家先手且下出同边情况,由于电脑下棋的优先度导致 1-4 情况无法防御,所以我们需要单独列出。
边角情况
如图:
当玩家先手且下出边角情况,由于电脑下棋的优先度并不能防御 2-1、2-2、2-3、2-4 这四种情况,所以我们需要单独列出。
对角情况
如图:
当玩家先手且下出对角情况,电脑第二颗棋下在 角 本身就一定防御不住,所以我们需要单独列出。
代码实现
在computerDifficult.c中:
#include "game.h"
int defense(char board[ROW][COL])
{
// 同边情况
if (board[2][1] == '*' && board[1][2] == '*')
{
board[2][2] = '#';
return 1;
}
// 边角情况
if ((board[1][0] == '*' && board[2][2] == '*') || (board[2][1] == '*' && board[0][0] == '*'))
{
board[2][0] = '#';
return 1;
}
if ((board[2][1] == '*' && board[0][2] == '*') || (board[1][2] == '*' && board[2][0] == '*'))
{
board[2][2] = '#';
return 1;
}
// 对角情况
if ((board[0][0] == '*' && board[2][2] == '*') || (board[0][2] == '*' && board[2][0] == '*'))
{
board[0][1] = '#';
return 1;
}
return 0;
}
int difficult(char board[ROW][COL], char set1, char set2)
{
if (set1 == ' ' && set2 == '#') // 当为二子连接时
if (defense(board)) // 对特殊情况穷举
return 1;
// 对角线的防与攻左斜
if (board[0][0] == board[1][1] && board[1][1] == set1 && board[2][2] == set2) // 对角线左斜下
{
board[0][0] == ' ' ? (board[0][0] = '#') : (board[2][2] = '#');
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == set1 && board[1][1] == set2) // 对角线左斜中
{
board[0][0] == ' ' ? (board[0][0] = '#') : (board[1][1] = '#');
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == set1 && board[0][0] == set2) // 对角线左斜上
{
board[2][2] == ' ' ? (board[2][2] = '#') : (board[0][0] = '#');
return 1;
}
// 对角线的防与攻右斜
if (board[0][2] == board[1][1] && board[1][1] == set1 && board[2][0] == set2) // 对角线右斜下
{
board[0][2] == ' ' ? (board[0][2] = '#') : (board[2][0] = '#');
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == set1 && board[1][1] == set2) // 对角线右斜中
{
board[0][2] == ' ' ? (board[0][2] = '#') : (board[1][1] = '#');
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == set1 && board[0][2] == set2) // 对角线右斜上
{
board[2][0] == ' ' ? (board[2][0] = '#') : (board[0][2] = '#');
return 1;
}
for (int i = 0; i < 3; i++)
{
// 行的防御
if (board[i][0] == board[i][1] && board[i][0] == set1 && board[i][2] == set2) // 行尾
{
board[i][0] == ' ' ? (board[i][0] = '#') : (board[i][2] = '#');
return 1;
}
if (board[i][0] == board[i][2] && board[i][0] == set1 && board[i][1] == set2) // 行中
{
board[i][0] == ' ' ? (board[i][0] = '#') : (board[i][1] = '#');
return 1;
}
if (board[i][1] == board[i][2] && board[i][1] == set1 && board[i][0] == set2) // 行头
{
board[i][2] == ' ' ? (board[i][2] = '#') : (board[i][0] = '#');
return 1;
}
// 列的防御
if (board[0][i] == board[1][i] && board[0][i] == set1 && board[2][i] == set2) // 列尾
{
board[0][i] == ' ' ? (board[0][i] = '#') : (board[2][i] = '#');
return 1;
}
if (board[0][i] == board[2][i] && board[0][i] == set1 && board[1][i] == set2) // 列中
{
board[0][i] == ' ' ? (board[0][i] = '#') : (board[1][i] = '#');
return 1;
}
if (board[1][i] == board[2][i] && board[1][i] == set1 && board[0][i] == set2) // 列头
{
board[2][i] == ' ' ? (board[2][i] = '#') : (board[0][i] = '#');
return 1;
}
}
return 0;
}
提升难度后的整体源代码
在game.h中:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#define ROW 3 // 方便扩展
#define COL 3
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void initBoard(char board[ROW][COL], int row, int col);
void display(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);
char IsWin(char board[ROW][COL], int row, int col);
int difficult(char board[ROW][COL], char set1, char set2);
int first(char board[3][3]);
在game.c中:
#include "game.h"
void initBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
board[i][j] = ' ';
}
void display(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1) // 打印纵分割线
printf("|");
}
printf("\n");
if (i < row - 1) // 打印横分割线
{
for (int j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");
}
}
void computerMove(char board[ROW][COL], int row, int col, int* firstMove)
{
printf("电脑下棋\n");
int y, x;
// 三子连接攻击 三子连接防御 二子连接
if (difficult(board, '#', ' ') || difficult(board, '*', ' ') || difficult(board, ' ', '#')); // 当防御时返回1,否则返回0。
else if (*firstMove && first(board)) // 开局
*firstMove = 0;
else
{
while (1) // 这里使用了随机下棋
{
y = rand() % row;
x = rand() % col;
if (board[y][x] == ' ') // 是空格才下
{
board[y][x] = '#';
break;
}
}
}
}
void playerMove(char board[ROW][COL], int row, int col)
{
int y, x;
printf("玩家下棋:>\n");
while (1)
{
printf("请输入坐标:>");
scanf("%d %d", &y, &x);
if (x >= 1 && x <= 3 && y >= 1 && y <= 3 && board[y - 1][x - 1] == ' ') // 限定范围
{
board[y - 1][x - 1] = '*'; // 这里要对应数组下标
break;
}
else
printf("坐标非法或被占用,请重新输入\n");
}
}
int IsFull(char board[ROW][COL], int row, int col)
{// 判断棋盘是否满了
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
if (board[i][j] == ' ')
return 0;
return 1;
}
char IsWin(char board[ROW][COL], int row, int col)
{// 检测行、列、对角线棋子情况
for (int i = 0; i < row; i++) // 行
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
return board[i][1]; // 若当行三子相同,返回其棋子
for (int j = 0; j < col; j++) // 列
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
return board[1][j];// 若当列三子相同,返回其棋子
// 对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
return board[1][1];// 若当对角线三子相同,返回其棋子
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
return board[1][1];
if (IsFull(board, row, col))
return 'Q'; // 字符Q表示棋盘满了
return 'C'; // 字符C表示下棋正常进行
}
在test.c中:
#include "game.h"
void menu()
{
printf("*************************\n");
printf("**** 1.play 0.exit ****\n");
printf("*************************\n");
}
void game()
{
int firstMove = 1; // 判断是否是开局
char board[ROW][COL] = { 0 };
char ret = 0; // 接收棋盘下棋的信息
initBoard(board, ROW, COL);
display(board, ROW, COL);
while (1) // 使用循环
{
playerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL); // 接收信息
display(board, ROW, COL);
if (ret != 'C') // 若不为正常情况,跳出循环
break;
computerMove(board, ROW, COL, &firstMove);
ret = IsWin(board, ROW, COL);
display(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*') // 跳出循环时判断下棋之后的情况
printf("玩家赢\n");
else if (ret == '#')
printf("电脑赢\n");
else if (ret == 'Q')
printf("平局\n");
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL)); // 为后续的电脑随机下棋准备
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误!\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
在computerDifficult.c中:
#include "game.h"
int defense(char board[ROW][COL])
{
// 同边情况
if (board[2][1] == '*' && board[1][2] == '*')
{
board[2][2] = '#';
return 1;
}
// 边角情况
if ((board[1][0] == '*' && board[2][2] == '*') || (board[2][1] == '*' && board[0][0] == '*'))
{
board[2][0] = '#';
return 1;
}
if ((board[2][1] == '*' && board[0][2] == '*') || (board[1][2] == '*' && board[2][0] == '*'))
{
board[2][2] = '#';
return 1;
}
// 对角情况
if ((board[0][0] == '*' && board[2][2] == '*') || (board[0][2] == '*' && board[2][0] == '*'))
{
board[0][1] = '#';
return 1;
}
return 0;
}
int difficult(char board[ROW][COL], char set1, char set2)
{
if (set1 == ' ' && set2 == '#') // 当为二子连接时
if (defense(board)) // 对特殊情况穷举
return 1;
// 对角线的防与攻左斜
if (board[0][0] == board[1][1] && board[1][1] == set1 && board[2][2] == set2) // 对角线左斜下
{
board[0][0] == ' ' ? (board[0][0] = '#') : (board[2][2] = '#');
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == set1 && board[1][1] == set2) // 对角线左斜中
{
board[0][0] == ' ' ? (board[0][0] = '#') : (board[1][1] = '#');
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == set1 && board[0][0] == set2) // 对角线左斜上
{
board[2][2] == ' ' ? (board[2][2] = '#') : (board[0][0] = '#');
return 1;
}
// 对角线的防与攻右斜
if (board[0][2] == board[1][1] && board[1][1] == set1 && board[2][0] == set2) // 对角线右斜下
{
board[0][2] == ' ' ? (board[0][2] = '#') : (board[2][0] = '#');
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == set1 && board[1][1] == set2) // 对角线右斜中
{
board[0][2] == ' ' ? (board[0][2] = '#') : (board[1][1] = '#');
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == set1 && board[0][2] == set2) // 对角线右斜上
{
board[2][0] == ' ' ? (board[2][0] = '#') : (board[0][2] = '#');
return 1;
}
for (int i = 0; i < 3; i++)
{
// 行的防御
if (board[i][0] == board[i][1] && board[i][0] == set1 && board[i][2] == set2) // 行尾
{
board[i][0] == ' ' ? (board[i][0] = '#') : (board[i][2] = '#');
return 1;
}
if (board[i][0] == board[i][2] && board[i][0] == set1 && board[i][1] == set2) // 行中
{
board[i][0] == ' ' ? (board[i][0] = '#') : (board[i][1] = '#');
return 1;
}
if (board[i][1] == board[i][2] && board[i][1] == set1 && board[i][0] == set2) // 行头
{
board[i][2] == ' ' ? (board[i][2] = '#') : (board[i][0] = '#');
return 1;
}
// 列的防御
if (board[0][i] == board[1][i] && board[0][i] == set1 && board[2][i] == set2) // 列尾
{
board[0][i] == ' ' ? (board[0][i] = '#') : (board[2][i] = '#');
return 1;
}
if (board[0][i] == board[2][i] && board[0][i] == set1 && board[1][i] == set2) // 列中
{
board[0][i] == ' ' ? (board[0][i] = '#') : (board[1][i] = '#');
return 1;
}
if (board[1][i] == board[2][i] && board[1][i] == set1 && board[0][i] == set2) // 列头
{
board[2][i] == ' ' ? (board[2][i] = '#') : (board[0][i] = '#');
return 1;
}
}
return 0;
}
int first(char board[3][3])
{
if (board[1][1] == ' ') // 开局占中心
{
board[1][1] = '#';
return 1;
}
else// 开局占四角
{
int arr[2] = { 0, 2 };
int i = arr[rand() % 2];
int j = arr[rand() % 2];
if (board[i][j] == ' ')
{
board[i][j] = '#';
return 1;
}
}
return 0;
}
结语
以上针对电脑难度提升的代码,个人觉得电脑应该不会出现输的情况了。当然,若读者测试出此电脑还是有问题,可以私信或评论区告知我。
谢谢阅读