Tic-Tac-Toe
- 1 游戏介绍和随机数
- 1.1 游戏介绍
- 1.2 随机数的生成
- 1.3 棋盘大小和符号
- 2 设计游戏
- 2.1 初始化棋盘
- 2.2 打印棋盘
- 2.3 玩家下棋
- 2.4 电脑下棋
- 2.5 判断输赢
- 2.6 game()函数
- 2.7 main()函数
- 3 完整三子棋代码
- 3.1 Tic_Tac_Toe.h
- 3.2 Tic_Tac_Toe.c
- 3.3 Test.c
- 4 游戏代码的缺陷
1 游戏介绍和随机数
1.1 游戏介绍
井字棋,英文名叫Tic-Tac-Toe,是一种在3*3格子上进行的连珠游戏,由于棋盘一般不画边框,格线排成井字故得名。游戏需要的工具仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X),任意三个标记形成一条直线,则为获胜。
点此进入井字棋链接
1.2 随机数的生成
- 1 rand()函数
int rand(void)
生成随机数
返回0到RAND_MAX之间的伪随机整数。(RAND_MAX是32767,二进制原码最大为0111111111111111=215-1=32767)
这个数字是由一个算法生成的,该算法每次被调用时都会返回一个明显不相关的数字序列。该算法使用种子来生成序列,该序列应使用函数srand初始化为某个独特的值。
RAND_MAX是中定义的常数。
使用rand在确定的范围内生成平凡伪随机数的典型方法是使用返回值乘以范围跨度的模,并添加范围的初始值:
v1 = rand() % 100; // v1 范围 0 to 99
v2 = rand() % 100 + 1; // v2 范围 1 to 100
v3 = rand() % 30 + 1985; // v3 范围 1985-2014
请注意,这种取模运算并不会在跨度中生成均匀分布的随机数。
- 2 sand函数是用来初始化随机数生成器的
void srand(unsigned int seed);
- 在调用rand前调用srand,修改seed参数生成随机数种子
int main()
{
void srand(unsigned int seed);
return 0;
}
- 3 time()
time函数可以获得时间,需要引用头文件time.h
time_t time(time_t* timer);
- 生成真的随机数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int main()
{
srand((unsigned int)time(NULL));
//srand 的参数是unsigned int 类型,强制转换,
//time(NULL)返回一段时间戳
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
1.3 棋盘大小和符号
在
Tic-Tac-Toe.h
中定义棋盘行列大小,玩家和电脑的符号,方便后续修改
#define ROW 3
#define COL 3
#define Player_Choice 'X'
#define Computer_Choice 'O'
//#define Player_Choice '√'
//#define Computer_Choice '×'
2 设计游戏
2.1 初始化棋盘
将二维数组的所有元素定义为“空”,等待后续放入
Player_Choice
和Computer_Choice
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] = ' ';
}
}
}
2.2 打印棋盘
我们朝向这个
%c | %c | %c \n
和---|---|---\n
打印,设计棋盘形状。
后续改变棋盘大小可以通过更改ROW
和COL
快速更改。
我们以%c |
为一组,在最后一组是没有|
的,---|
也是同理。需要单独判断
//2 打印棋盘
void DispalyBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//打印数据
int j = 0;
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");
}
}
}
2.3 玩家下棋
有以下3点要注意
1 输入的坐标格式
2 输入的坐标是否被占用
3 输入的坐标范围是否正确
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] = Player_Choice;
break;
}
else
{
printf("坐标被占用,不能下棋,请选择其他位置\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
2.4 电脑下棋
为了让电脑生产真正的随机数,我们前面已经讲了如何做。
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
int x = 0;
int y = 0;
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = Computer_Choice;
break;
}
}
}
2.5 判断输赢
判断输赢要从三方面,行,列,对角线。分三种情况判定。
先判断棋盘是否填满。
棋盘满了返回1,判断平局和胜负,
棋盘不满返回0,游戏继续进行,知道决出结局。
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;
}
我们规定
玩家赢的时候,IsWin
函数返回符号'*'
,
电脑赢的时候,IsWin
函数返回符号'#'
,
平局的时候,IsWin
函数返回符号'Q'
,
没结果还要继续时候,IsWin
函数返回符号'C'
因此,规定IsWin
函数返回类型是char
。
char IsWin(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];
}
}
//列
int j = 0;
for (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';
}
//游戏继续
return 'C';
}
2.6 game()函数
game()
函数放在Test.c
中。一旦选择了1 开始游戏
,启动game()
函数。
一定注意下棋的逻辑顺序。
初始化棋盘后,放置“空”元素进去。
开始下棋后,我们规定玩家先走,电脑后走。
玩家走一步,判断输赢,展示棋盘,电脑再走,再判断输赢。
第一回合是没必要输赢。但是不妨碍游戏逻辑。
玩家赢 -
IsWin
函数返回 -Player_Choice
电脑赢 -IsWin
函数返回 -Computer_Choice
平局 -IsWin
函数返回 -'Q'
void game()
{
char result = 0;
char board[ROW][COL] = { 0 };
//初始化棋盘的函数
InitBoard(board, ROW, COL);
DispalyBoard(board, ROW, COL);
//下棋
while (1)
{
PlayerMove(board, ROW, COL);
//判断输赢
result = IsWin(board, ROW, COL);
if (result != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
//判断输赢
result = IsWin(board, ROW, COL);
if (result != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
}
if (result == Player_Choice)
{
printf("你赢了\n");
}
else if (result == Computer_Choice)
{
printf("电脑赢了\n");
}
else
{
printf("你和电脑打成平局\n");
}
DispalyBoard(board, ROW, COL);
}
2.7 main()函数
其实main()函数一开始就写了,笔者最后以是修改
main()
结尾的,就把它放最后了。
设置随机数的生成起点的。srand((unsigned int)time(NULL));
我忘了这个逻辑。电脑生产伪随机数。
每次进行游戏时,电脑都是固定位置,没有意义。
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误!\n");
break;
}
} while (input);
return 0;
}
3 完整三子棋代码
主要利用二维数组实现井字棋,主要分以下三个模块。
Test.c
负责测试游戏的逻辑
Tic_Tac_Toe.c
负责游戏代码的实现
Tic_Tac_Toe.h
负责游戏代码的声明
3.1 Tic_Tac_Toe.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 3
#define COL 3
#define Player_Choice 'X'
#define Computer_Choice 'O'
//#define Player_Choice '√'
//#define Computer_Choice '×'
//1 初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
//2 打印棋盘
void DispalyBoard(char board[ROW][COL], int row, int col);
//3 玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
//4 电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//5 判断输赢
char IsWin(char board[ROW][COL], int row, int col);
3.2 Tic_Tac_Toe.c
#include "Tic_Tac_Toe.h"
//1 初始化棋盘
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 DispalyBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (i = 0; i < row; i++)
// {
// //打印数据
// printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
// //打印分割信息
// if(i<row-1)
// printf("---|---|---\n");
// }
//}
//2 打印棋盘
void DispalyBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//打印数据
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//打印分割信息
//printf("---|---|---\n");
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
//3 玩家下棋
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] = Player_Choice;
break;
}
else
{
printf("坐标被占用,不能下棋,请选择其他位置\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
//4 电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
int x = 0;
int y = 0;
while (1)
{
x = rand() % row;//0~2
y = rand() % col;//0~2
if (board[x][y] == ' ')
{
board[x][y] = Computer_Choice;
break;
}
}
}
//5 判断输赢
//棋盘满了就返回1
//棋盘不满返回0
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;
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];
}
}
//列
int j = 0;
for (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';
}
//游戏继续
return 'C';
}
//返回 Player_Choice 玩家赢
//返回 Computer_Choice 玩家赢
//返回 Q 平局
//返回 C 继续
3.3 Test.c
#include "Tic_Tac_Toe.h"
void menu()
{
printf("*****************************\n");
printf("***********1 开始游戏*********\n");
printf("***********0 退出游戏*********\n");
printf("*****************************\n");
}
void game()
{
char result = 0;
char board[ROW][COL] = { 0 };
//初始化棋盘的函数
InitBoard(board, ROW, COL);
DispalyBoard(board, ROW, COL);
//下棋
while (1)
{
PlayerMove(board, ROW, COL);
//判断输赢
result = IsWin(board, ROW, COL);
if (result != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
//判断输赢
result = IsWin(board, ROW, COL);
if (result != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
}
if (result == Player_Choice)
{
printf("----------你赢了----------\n");
}
else if (result == Computer_Choice)
{
printf("----------电脑赢了----------\n");
}
else
{
printf("----------平局----------\n");
}
DispalyBoard(board, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();//打印菜单
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误!\n");
break;
}
} while (input);
return 0;
}
4 游戏代码的缺陷
这个电脑是随便下棋的,它没有动脑子,每次生产随机的坐标,不把我们放心上。不追求矢量直线。
你说如果棋盘再大一带你的,电脑也知道适量原则。人下棋的胜率会低多少呢?
不过想要和电脑下平局还是很难的。因为我总要电脑让着我。