目录
前言
三子棋-C语言的小游戏之一,是学习c语言的必经之路。
一、基本流程
首先要搞清楚实现的效果:
- 能玩完一盘后选择是否继续玩
- 棋盘大小可以被修改
- 游戏的主体与实现的函数分离
按照效果梳理流程:
- 菜单界面选择是否游戏
- 是:进入游戏;否:退出游戏;其它:重新选择
- 游戏本体的实现
- 创建棋盘并初始化
- 打印棋盘
- 玩家落子(输入二位坐标)
- 判定胜负关系(输,赢,和棋,继续)
- 电脑落子(随机位置落子)
- 判定胜负关系(输,赢,和棋,继续)
- 重复3-6步骤
- 游戏结束,回到1继续执行
二、整体步骤
2.1游戏主体与实现分离
创建Tic_Tac_Toe.c、Tic_Tac_Toe_game.h、Tic_Tac_Toe_game.h三个文件,游戏的完整流程放在Tic_Tac_Toe.c中,游戏的具体函数实现放在game.c中,声明放在game.h中。
2.2游戏完整流程(Tic_Tac_Toe.c)实现
2.2.1菜单界面
menu函数:1开始游戏 0退出游戏
void menu()
{
printf("***************************************************\n");
printf("********************* 1.play ********************\n");
printf("********************* 0.exit ********************\n");
printf("***************************************************\n");
}
2.2.2选择实现开始游戏还是退出游戏
利用switch-case语句选择是否进行游戏,变量为1、0和其它
int input = 0;
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("三子棋游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
2.2.3 循环实现是否继续游戏
该循环至少进行一遍,故采用do-while循环,整体main函数展示:
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("三子棋游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
2.3game函数的实现
将game函数分为game.c和game.h,game函数里的函数具体写在game.c中,函数声明写在game.h头文件中,函数的类型为void,无参数
void game()
{
}
2.3.1创建棋盘
利用二维数组存储数据,类型是char字符类型,名字为board,行和列用宏定义,方便修改和提高代码可读性,这一步是可修改棋盘的基础
char board[ROW][COL];
将宏定义写在game.h文件中
#define ROW 3
#define COL 3
2.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] = ' ';
}
}
}
2.3.3打印棋盘
遍历数组,然后使用printf函数打印,这里要注意的就是最后想要输出的形式
void DisplayBoard(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");
}
}
}
结果展示:

2.3.4玩家与电脑下棋流程
整个游戏(玩家走-->>电脑走)不是一遍完成的,所以需要用while循环进行,走一步就打印一次棋盘,每次走完后判断输赢,如果没有结果就继续,最后整个游戏结束后,无论什么情况,都打印棋盘告诉玩家最后的情况
while (1)
{
//玩家走
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑走
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家赢了\n");
}
else if (ret == '#')
{
printf("电脑赢了\n");
}
else
{
printf("平局\n");
}
//统一最后打印棋盘
DisplayBoard(board, ROW, COL);
2.3.5玩家下棋
本质就是将玩家输入的坐标代表的数组的元素从‘ ’替换为‘*’(这里用*表示玩家的棋子)
这里需要注意的是需要判断坐标是否合法(是否在棋盘内),以及合法后是否能下棋(位置有没有被占用),合法并下棋成功就退出循环,其它情况需要重新输入
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");
}
}
}
2.3.6电脑下棋
这里的电脑下棋是利用随机生成的位置,利用srand函数生成随机数种子,只需要在main函数里运行一次就行,这是一种套路,需要头文件
#include <stdlib.h> //生成随机数
#include <time.h> //时间戳
srand((unsigned int)time(NULL));
在利用生成的数取模就可以得到电脑下棋的坐标,这个时候的坐标必定满足合法,只需要再判断是否能下棋,将‘ ’替换为‘#’
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][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
2.3.7判断输赢
赢的条件是有一行或一列或对角线上都是相同的(*/#),基于这个可以写出代码,这里需要基于前面的宏定义找出获胜时与ROW和COL的关系,这样才能做到修改棋盘后仍能判断,这里判断的关系是当前的这一个和下一个是否相同或者当前的元素为‘ ’(空格),这个函数最后需要返回结果,为了方便直接借用下棋的元素*和#,那么返回类型就是char字符类型,返回*为表示玩家赢,#为电脑赢,P为平局,C为继续
char IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
//判断行
for (i = 0; i < row; i++)
{
int flag = 0; //定义变量,方便最后的结果表示,这里不能写在外面,每检
//测完一行就要重新初始化
for (j = 0; j < col-1;j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[i][j];
}
}
//判断列
for (i = 0;i < row; i++)
{
int flag = 0;
for (j = 0;j < col-1;j++)
{
if (board[j][i] != board[j + 1][i] || board[j][i] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[j][i];
}
}
//判断对角线
int flag = 0;
//正对角线
for (i = 0;i < row-1;i++)
{
if (board[i][i] != board[i + 1][i + 1] || board[i][i] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[i][i];
}
flag = 0;
//次对角线
for (i = 0;i < row-1;i++)
{
if (board[i][row-1-i] != board[i + 1][row-2-i] || board[i][row-1-i] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[i][row-1-i];
}
//棋盘满或未满
if (IsFull(board, row, col))
{
return 'P';
}
return 'C';
}
这里还需要理解的就是怎么判断平局,那就是都没赢,最后棋盘满了,判断棋盘满的函数需要有返回类型,这里我们采用int便于返回0/1利于if直接判断
int IsFull(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;
}
三、效果展示

四、完整代码
Tic_Tac_Toe.c
//三子棋游戏
#define _CRT_SECURE_NO_WARNINGS 1
#include "three_sub-chesses_game.h" //引用后可省略stdio.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 = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑走
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(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("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("三子棋游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
Tic_Tac_Toe_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 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);
Tic_Tac_Toe_game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "three_sub-chesses_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 DisplayBoard(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 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");
}
}
}
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][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int IsFull(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 IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
//判断行
for (i = 0; i < row; i++)
{
int flag = 0;
for (j = 0; j < col-1;j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[i][j];
}
}
//判断列
for (i = 0;i < row; i++)
{
int flag = 0;
for (j = 0;j < col-1;j++)
{
if (board[j][i] != board[j + 1][i] || board[j][i] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[j][i];
}
}
//判断对角线
int flag = 0;
//正对角线
for (i = 0;i < row-1;i++)
{
if (board[i][i] != board[i + 1][i + 1] || board[i][i] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[i][i];
}
flag = 0;
//次对角线
for (i = 0;i < row-1;i++)
{
if (board[i][row-1-i] != board[i + 1][row-2-i] || board[i][row-1-i] == ' ')
{
flag = 1;
break;
}
}
if (flag == 0)
{
return board[i][row-1-i];
}
//棋盘满或未满
if (IsFull(board, row, col))
{
return 'P';
}
return 'C';
}
最后附上作者的gitee地址,想要看其它代码的也可以去作者仓库里查看

被折叠的 条评论
为什么被折叠?



