1. 整体思路
本篇文章使用C语言实现一个简易的三子棋游戏,实现的语法非常基础,只涉及到了条件判断、循环、数组等,非常适合刚学习C语言不久的同学复习、巩固C语言的基础语法。由于没有使用UI框架,游戏将会在控制台窗口中运行。
整个程序代码分别放在了game.h、game.c、test.c三个文件中:
- game.h:头文件的引入、游戏相关的函数的声明以及标识符常量的定义
- game.c:游戏相关的函数的定义
- test.c:对游戏进行测试,是三子棋游戏程序运行的入口
在两个.c文件的开头添加了一条语句:
#define _CRT_SECURE_NO_WARNINGS 1
,这是由于VS不支持使用scanf(),加了这条语句解决这个问题。
2. game.h文件
game.h文件是一个头文件,该文件主要用于声明实现三子棋游戏的主要函数以及引入要使用的外部头文件,同时还需要定义标识符常量,避免出现常量修改导致源代码中多处需要修改的情况出现。
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定义标识符常量
#define ROW 3
#define COL 3
//打印游戏菜单
void menu();
//初始化棋盘为空格
void InitBoard(char board[ROW][COL], int row, int col);
//打印棋盘
void PrintBoard(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 IsFull(char board[ROW][COL], int row, int col);
3. game.c文件
在game.c文件中定义了game.h文件中声明的所有函数。
3.1 打印游戏菜单
menu()函数打印游戏菜单,打印的样式随意即可,根据自己喜好来。
//打印游戏菜单
void menu()
{
printf("***************************\n");
printf("******** 1. play ********\n");
printf("******** 0. exit ********\n");
printf("***************************\n");
}
3.2 初始化游戏棋盘
在每一局游戏开始时需要将棋盘的每个位置初始化为空格 。
//初始化棋盘为空格
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] = ' ';
}
}
}
3.3 打印棋盘
打印棋盘的代码稍微繁琐一点,由于棋盘一开始被初始化为空格,打印出来也什么都看不见,因此打印一些边框会更加美观,根据个人喜好来设计即可。
//打印棋盘
void PrintBoard(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");
//打印横向分割线,横向分割线比棋盘的行数少1行
if (i < row - 1)
{
for (int j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
printf("\n");
}
棋盘打印效果图如下:
3.4 玩家下棋
玩家下棋的逻辑如下:
- 输入棋盘格子的坐标落子。
- 当前格子没有棋子,落子成功。
- 当前格子有棋子,落子失败,重新输入棋盘格坐标 。
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋>:\n");
while (1)
{
printf("请输入下棋的坐标,中间使用空格隔开>:");
scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
if (board[x - 1][y - 1] == ' ')
{
//下棋成功
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("当前位置已经有棋子,请重新输入坐标!\n");
}
}
else
{
printf("当前坐标不合法,请重新输入!\n");
}
}
}
3.5 电脑下棋
电脑下棋实现的方式比较简单粗暴,直接随机生成下棋位置处的坐标。
//电脑下棋
void ComputerMove(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;
}
}
}
3.6 判断输赢或平局
实现逻辑:
- 玩家赢或电脑赢。
- 某一行的所有字符相同且不为空格。
- 某一列的所有字符相同且不为空格。
- 主对角线所有字符相同且不为空格。
- 副对角线所有字符相同且不为空格。
- 平局,在双方都没有分出胜负的情况下,棋盘格子已经下满棋子时,平局。
函数共有4种返回值,*
表示玩家获胜,#
表示电脑获胜,C
表示没有任何一方获胜,对局继续进行,Q
表示平局。
//判断输赢
char IsWin(char board[ROW][COL], int row, int col)
{
int flag; //1表示某行或某列或主副对角线上元素相同
//通过行判断输赢
for (int i = 0; i < row; i++)
{
flag = 1;
int j = 0;
for (; j < col - 1; j++)
{
//当前行有不相同的元素
if (board[i][j] != board[i][j + 1])
{
flag = 0;
}
}
if (flag == 1 && board[i][j] != ' ')
{
//如果当前行的元素都相同且不为空格,此时代表某一方已经获胜,直接返回
return board[i][j];
}
}
//通过列判断输赢
for (int j = 0; j < col; j++)
{
int i = 0;
flag = 1;
for (; i < row - 1; i++)
{
//当前列有不同的元素
if (board[i][j] != board[i + 1][j])
{
flag = 0;
}
}
if (flag == 1 && board[i][j] != ' ')
{
//如果当前列的元素都相同且不为空格,此时代表某一方已经获胜,直接返回
return board[i][j];
}
}
//通过主对角线判断输赢
flag = 1;
int i = 0;
for (; i < row - 1; i++)
{
if (board[i][i] != board[i + 1][i + 1])
{
flag = 0;
}
}
if (flag == 1 && board[i][i] != ' ')
{
return board[i][i];
}
//通过副对角线判断输赢
flag = 1;
i = 0;
int j = col - 1;
for (; i < row - 1; i++, j--)
{
if (board[i][j] != board[i + 1][j - 1])
{
flag = 0;
}
}
if (flag == 1 && board[i][j] != ' ')
{
return board[i][j];
}
//如果棋盘已满则平局
if (IsFull(board, row, col) == 1)
{
return 'Q';
}
//对局继续
return 'C';
}
3.7 棋盘是否下满
判断棋盘是否已经下满棋的实现非常简单,只要循环判断数组中是否还有空格字符即可。
//判断棋盘是否下满
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;
}
4. test.c文件
test.c文件中包含了main()(程序入口)和game(),在game函数中通过调用game.c文件中定义的函数进行游戏。函数调用的顺序和正常下棋是一个逻辑:玩家下棋 → 判断输赢 → 电脑下棋 → 判断输赢
,一直重复这一过程直到分出胜负。
main()的代码如下:
int main()
{
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 != 0);
return 0;
}
game()的代码如下:
void game()
{
char board[ROW][COL] = {0};
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//下棋
char res = ' ';
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//判断输赢
res = IsWin(board, ROW, COL);
if (res != 'C')
{
break;
}
IsWin(board, ROW, COL);
//电脑下棋
ComputerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//判断输赢
res = IsWin(board, ROW, COL);
if (res != 'C')
{
break;
}
}
if (res == '*')
{
printf("玩家赢\n");
}
else if (res == '#')
{
printf("电脑赢\n");
}
else if (res == 'Q')
{
printf("平局\n");
}
}
5.运行结果
玩家获胜:
电脑获胜:
平局: