今天利用空余时间,便写一写三子棋的实现,可能很多人第一次看到这些东西,心里就会有一点害怕,可能会说:天呐,这得多难啊,我真的能写吗?那么回答是肯定的:能写,并且还很简单。那就让我们来看一看吧!
我们首先要分析整个三子棋的需要,因为我们不是做人机对战(过段时间博主会发一篇五子棋人机对战的实现),主要实现人机对战需要考虑的情况比较多,比较复杂,在这里既然是新手向,大家有一个制作的思路就可以了。
三子棋下棋,首先我们需要做到能看到棋盘,之后是下棋,电脑会在我们之后。每一步我们都想屏幕能清空,同时保留一次棋盘。然后是判断输赢的问题。(如果书写对战AI,还需要考虑棋盘情况来书写对应的算法)。
为了实现功能,我们将其拆解成三部分:一个test.c,一个game.c和一个game.h。
其中test.c用来书写实现逻辑,game.c用来书写函数定义,game.h则用来声明。
第一部分:
测试文件test.c,这个文件我们需要一个目录指引,也就是玩还是不玩,这个很容易实现,之后是定制棋盘,我们在头文件里会声明这些东西(ROW和COL一类的量)。首先肯定是初始化一个棋盘,这个很简单,直接给棋盘赋值空格就好(InitBoard),之后我们需要让它变成我们想要的样子(也就是DisplayBoard),这需要一点点的功夫,比如我们期望如下:
那么我们就需要在书写定义的时候用点心了,不过我们现在看的是主函数,所以我们先关注主函数:
有了棋盘我们就还需要能下子,就需要玩家下子(PlayerMove)和电脑下子(ComputerMove),之后我们需要判断输赢平局的情况(IfWin),这时候就基本上思路齐全了。
那么我们的主函数的逻辑已经比较清晰了,就直接上代码吧:
#include"game.h"
void menu()
{
printf("===========================\n");
printf("======= 1.play =======\n");
printf("======= 0.exit =======\n");
printf("===========================\n");
}
void game()
{
//存储数据-二维数组
char board[ROW][COL];
//初始化棋盘-初始化空格
InitBoard(board, ROW, COL);
//打印棋盘-本质打印数组的内容
DisplayBoard(board, ROW, COL);
char ret = 0;//用来获取游戏状态
while (1)
{
//玩家走
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IfWin(board, ROW, COL);
if (ret != 'C')
break;
//电脑走
ComputerMove(board, ROW, COL);
system("cls");
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IfWin(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*')
{
printf("玩家胜利\n");
}
else if (ret == '#')
{
printf("电脑胜利\n");
}
else
{
printf("平局\n");
}
DisplayBoard(board, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:\n");
scanf_s("%d", &input);
system("cls");
switch (input)
{
case 1:
printf("三子棋游戏:\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择:\n");
break;
}
} while (input != 0);
}
在这里需要注意IfWin返回值的情况,这个是需要提前考虑的,由于玩家使用 “ * ”我们就认为返回了“ * ”就是玩家胜利,返回“ # ”就是电脑胜利,返回“ C ”(continue)就继续,返回 “ Q "就是平局
这样会使代码更加简单,至于为什么,我们马上就能知道。
第二步,书写头文件,主函数已经有了需求,接下来在头文件里书写一下声明就OK了:
#pragma once
//头文件的包含
#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include<Windows.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 ComputerMove(char board[ROW][COL], int row, int col);
//判断输赢
//1.玩家赢 '*' 2.电脑赢 '#' 3.平局 'Q' 4.游戏继续 'C'
char IfWin(char board[ROW][COL], int row, int col);
第三步就是很关键的一步了,这一步决定了程序能否正常运行,因为它书写的是我们的函数定义,
我们已经知道,我们需要初始化一个棋盘(InitBoard),展示棋盘情况(DisplayBoard)玩家下子(PlayerMove)和电脑下子(ComputerMove),之后我们需要判断输赢平局的情况(IfWin)
那么就分开看看这些函数模块(不要忘记包含头文件#include"game.h"):
1、InitBoard
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
这个就是妥妥的二维数组赋值,全赋值空格,没什么好说的。
2、DisplayBoard
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
//打印每一行的数据 空格+数据+空格+竖杠作为一个大元素
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");
}
}
}
因为我们有所期望,就是上图的那个棋盘,所以需要思考怎么制作棋盘,也很简单,打印每一行的数据,用 空格+数据+空格+竖杠 作为一个大元素 ,同时最后一个大元素没有竖杠。用代码解释的话,就是上面所示。
之后是每一行的区别,我们需要隔离棋盘的行,那么打印分隔线即可,道理如同打印“大元素”。
这个期望,我们就解决了。
3、PlayerMove
玩家落子,我们需要思考合法性,并给出反馈,所以需要需要if条件,首先判断输入的数据是否合理,之后是判断所输入的空间是否被占用。(由于我们习惯性的把横坐标在前,纵坐标灾后,同时注意数组的下标从0开始数,所以接收的时候为了符合判断,我们颠倒一下x和y,同时3-y,x-1)
注意思考为什么是3-y(或者说是ROW - y),因为我们打印出来的列是从上向下数的,而我们看棋盘是从下向上看,所以只能倒过来接受数据才符合我们的思路。
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
printf("玩家走:\n");
printf("请输入下棋的坐标:");
//判断坐标合法性
while (1)
{
scanf_s("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//下棋
//判断坐标是否呗被占用
if (board[3 - y][x - 1] == ' ')
{
board[3 - y][x - 1] = '*';
break;
}
else
{
printf("坐标被占用,请重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
如果二者都没有,那么我们就给原来的空格赋值为 “ * ”
4、ComputerMove
电脑下子就简单的多了,只需要随机赋值就可以,同时判断一下占用情况。
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑走:\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
//判断占用
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '#';
break;
}
}
}
5、IfWin
整个代码,唯一有一点难度的应该就是这里了,这里笔者写的比较简陋,我们首先判断每一行是否相等,再每一列,再对角线,如果都没有,就进入棋盘是否满了的判断,如果棋盘满了,那么就是平局,没满,就继续下棋,这里我们需要返回值。同时注意,我们的返回值就是棋盘存储的数据,这一点简化了我们的代码。
//判断游戏是否有输赢
char IfWin(char board[ROW][COL], int row, int col)
{
int i = 0;
//判断行
for (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 (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[1][i];
}
}
//判断对角线
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];
}
//判断平局
//满了返回1,没满返回0
if (IsFull(board, ROW, COL) == 1)
{
return 'Q';
}
//都没有,继续游戏
return 'C';
}
那么我们自然需要一个判断棋盘是否已满的函数,由于笔者的电脑出了一点小问题,只能书写这种非常原始的判断方式,有能力的伙伴可以自行改善代码哦
int IsFull(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
if (board[i][0] == ' ')
return 0;
else if (board[i][1] == ' ')
return 0;
else if (board[i][2] == ' ')
return 0;
else if (board[0][i] == ' ')
return 0;
else if (board[1][i] == ' ')
return 0;
else if (board[2][i] == ' ')
return 0;
else if (board[i][i] == ' ')
return 0;
}
return 1;
}
到这里,我们的简化版三子棋就实现了,想要书写AI算法的也可以根据棋盘判断的情况进行思考,思考电脑应该如何围堵,落子,然后代码实现。
这里就不多说了,那么感谢看到这里啦!
版权归原作者 芝士工具猿 所有, 如有侵权,请联系我们删除。