三子棋是一款十分具有趣味性的游戏,规则是玩家在3*3的棋盘上下棋,当只要有一方的棋子占据了同一行或同一列或两条对角线其中一条时,这时候该玩家就获得了比赛的胜利。下面我们用C语言来设计一个人机交互博弈的这么一个趣味小游戏。
首先,在写代码前我们要明确我们想要的是什么效果,这样我们才能直到我们需要完成什么动作。首先,我们需要一个棋盘来下棋,再其次我们需要设计玩家下棋我方式和方法,接着我们还要设计电脑的下棋方法,每下一步棋我们还需要观察棋盘的情况来决定下一步走子,因此我们还要打印每步棋下完后的棋盘,我们还要考虑的对弈过程中可能出现的4种情况(玩家胜、电脑胜、平局、继续)这四种情况,总之我们游戏的大致框架如下。
1.初始化棋盘(Initboard)。
2.打印棋盘(Displayboard)。
3.玩家走子(Player_move)。
4.电脑走子(Computer_move)。
5.判断局势(Is_win)。
因为三子棋是一个小的项目,所以我们按照标准的工程化,用game.h存放库函数头文件及函数说明,game.c来实现游戏的核心内容。 test.c来测试游戏.
头文件game.h如下
#pragma once //防止头文件重复包含
#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 Player_move(char board[ROW][COL], int row, int col);
//电脑走子
void Computer_move(char board[ROW][COL], int row, int col);
//判断胜负
char Is_win(char board[ROW][COL], int row, int col);
自定义好了属于我们自己的头文件以后,就可以像包含库头文件一样使用include指令包含到其他需要这个头文件的地方。
我们需要一个棋盘,很自然的联想到了可以用二维数组初始化棋盘。初始的棋盘应该什么都没有,
所以我们选择用空格作为棋盘的初始状态。
//初始化棋盘
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] = ' ';//这里是空格
}
}
}
接着我们要开始打印棋盘,我们希望打印出来如下效果的棋盘(画的比较丑,请见谅)
那么我们的打印就要分两步:第一步,打印数据:空格 数据 空格
第二步 打印竖线和 分割线 打印部分的代码如下:
//打印棋盘
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]);
//打印|
if (j < col - 1)
{
printf("|");
}
}
//处理完一行后换个行
printf("\n");
//打印分割线
for (j = 0; j < col; ++j)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
接下来写玩家走子,玩家的棋子的标志是'*',玩家通过输入横纵坐标来下棋,有这几种情况。
1.玩家不小心输入了非法的横、纵坐标-提示坐标非法。
2.坐标已经被占用了。-提示被占用。
3.坐标合法且为空-才能落子。
综上述考虑来看,下棋是一个重复动作,所以我们应该用一个循环。玩家下棋代码如下:
//玩家走子--玩家是下‘*’
void Player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家走,请输入坐标 : >");
scanf("%d", &x);
scanf("%d", &y);
//判断坐标合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{ //坐标和法,但是可能被占用
//数组下标是从0开始(用户可能不知道,该设计体现了与用户的交互性)
if (' ' == board[x - 1][y - 1])
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标已被占用,请重新输入\n");
}
}
else
{
printf("坐标非法\n");
}
}
}
设计完了玩家下子,接着就是电脑下棋,具体原理和玩家下棋大致相同。代码如下:
//电脑走子,使用随机数生成坐标,不用考虑越界问题和合法性问题
//类似与玩家走子的设计
void Computer_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑走: \n");
while(1)
{
x = rand() % row;
y = rand() % col;
if (' ' == board[x][y])
{
board[x][y] = '#';
break;
}
}
}
接着讨论上述我们四种情况的判定。
//判断胜负
/*
比赛有四种情况 1.玩家获胜 返回 '*'
2.电脑获胜 返回 '#'
3.平局 返回 'Q'
4. 继续 返回 'C'
其中 3和4 通过判断棋盘是否满了来确定 ,写一个函数判断是否棋盘满了
如果满了就返回1 ,没有满就返回0
*/
int Is_full(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;
}
char Is_win(char board[ROW][COL], int row, int col)
{ //这几种情况相同并且不是空格,返回其中的一个元素
//同行 00 01 02 找三行
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];
}
}
//同列 00 10 20 找三列
for (i = 0; i < row; ++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[2][0] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//执行到这里就要返回平局和继续的情况
if (1 == Is_full(board, ROW, COL))
{
return 'Q';
}
else
{
return 'C';
}
}
接下来是测试部分
注意:常量写左边是为了能够防止==号少写一个=带来的逻辑错误(把逻辑错误转化为语法错误)
完整代码如下:
game.h
#pragma once
#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 Player_move(char board[ROW][COL], int row, int col);
//电脑走子
void Computer_move(char board[ROW][COL], int row, int col);
//判断胜负
char Is_win(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] = ' ';
}
}
}
//打印棋盘
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]);
//打印|
if (j < col - 1)
{
printf("|");
}
}
//处理玩一行后换个行
printf("\n");
//打印分割线
for (j = 0; j < col; ++j)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
//玩家走子--玩家是下‘*’
void Player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家走,请输入坐标 : >");
scanf("%d", &x);
scanf("%d", &y);
//判断坐标合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{ //坐标和法,但是可能被占用
//数组下标是从0开始
if (' ' == board[x - 1][y - 1])
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标已被占用,请重新输入\n");
}
}
else
{
printf("坐标非法\n");
}
}
}
//电脑走子,使用随机数生成坐标,不用考虑越界问题和合法性问题
//类似与玩家走子的设计
void Computer_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑走: \n");
while(1)
{
x = rand() % row;
y = rand() % col;
if (' ' == board[x][y])
{
board[x][y] = '#';
break;
}
}
}
//判断胜负
/*
比赛有四种情况 1.玩家获胜 返回 '*'
2.电脑获胜 返回 '#'
3.平局 返回 'Q'
4. 继续 返回 'C'
其中 3和4 通过判断棋盘是否满了来确定 ,写一个函数判断是否棋盘满了
如果满了就返回1 ,没有满就返回0
*/
int Is_full(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;
}
char Is_win(char board[ROW][COL], int row, int col)
{ //这几种情况相同并且不是空格,返回其中的一个元素
//同行 00 01 02 找三行
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];
}
}
//同列 00 10 20 找三列
for (i = 0; i < row; ++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[2][0] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//执行到这里就要返回平局和继续的情况
if (1 == Is_full(board, ROW, COL))
{
return 'Q';
}
else
{
return 'C';
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void game()
{
char board[ROW][COL] = { 0 };
char ret = 0;
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
while (1)
{
Player_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断一下棋盘有没有满
ret = Is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
Computer_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = Is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
//执行到这里 就要判断 玩家胜利 ,还是电脑胜利,还是平局
if ('*' == ret)
{
printf("小伙子挺厉害的啊,还不错\n");
}
else if ('#' == ret)
{
printf("连入门级都打不过,真菜\n");
}
else
{
printf("平局\n");
}
}
void menu()
{
printf("**********************\n");
printf("**** 1. play *******\n");
printf("**** 0. exit *******\n");
printf("***********************\n");
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择: >\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误!\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
如果有错误还望各位大神指正一下,希望我的博客能对大家学习有一定的帮助,也希望大家能够一起共同进步