C语言---井字棋(三子棋)

news2024/11/25 0:58:05

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_ChoiceComputer_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打印,设计棋盘形状。
后续改变棋盘大小可以通过更改ROWCOL快速更改。
我们以%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 游戏代码的缺陷

这个电脑是随便下棋的,它没有动脑子,每次生产随机的坐标,不把我们放心上。不追求矢量直线。
你说如果棋盘再大一带你的,电脑也知道适量原则。人下棋的胜率会低多少呢?
不过想要和电脑下平局还是很难的。因为我总要电脑着我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1326909.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3-高可用-隔离术

隔离是指将系统或资源分割开&#xff0c;系统隔离是为了在系统发生故障时&#xff0c;能限定传播范围和影响范围&#xff0c;即发生故障后不会出现滚雪球效应&#xff0c;从而保证只有出问题的服务不可用&#xff0c;其他服务还是可用的。 比较多的隔离手段有线程隔离、进程隔…

开源堡垒机JumpServer结合内网穿透实现远程访问

开源堡垒机JumpServer结合内网穿透实现远程访问 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5. 公网远程访问Jump server6. 固定Jump server公网地址 前言 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 …

57 优化二进制序列

动态规划 #include <iostream> using namespace::std; using std::cout; using std::cin; int yhejz(int n, int nums[]) {int dp0 0;int dp1 0;int result -1;for(int i0; i<n; i){if(nums[i] 1){dp1;dp0;}else {dp1 dp01;dp0 0;}result max(dp1,max(dp0,re…

计算机毕业设计 基于SpringBoot的大学生平时成绩量化管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

在线培训考试系统的优势及应用场景介绍

在线培训考试系统是一种基于互联网的教育工具&#xff0c;它结合了在线培训和考试的功能&#xff0c;具有许多优势和广泛的应用场景。 提高学习效果 在线培训考试系统首先可以提高学习效果。系统提供了多媒体学习资源&#xff0c;例如文字、图片、音频和视频等&#xff0c;以…

Visual Studio 输入英文会在字母之间自动增加空格

问题如图&#xff1a; 原因&#xff1a;我们在输入中文的时候&#xff0c;不小心按下了shift空格键&#xff0c;比如&#xff1a; 此时&#xff0c;我们在输入中文的时候&#xff0c;再次按下shift空格键就可以恢复正常了。

docker部署个人网站项目记录(前后端分离)

背景 项目是前后端分离&#xff0c;前端有三部分&#xff0c;分别是 个人网站&#xff08;blog&#xff09;网站后台管理系统&#xff08;admin&#xff09;数据大屏&#xff08;datascreen&#xff09; 后端是基于nodejs写的后台服务 后台接口服务&#xff08;todo-nodejs…

uniapp图片上传说明

目录 1.文件上传组件 2.单文件上传 3.多文件上传 4.图片的回显 5.注意点 1.文件上传组件 前端上传组件使用uni-file-picker&#xff0c;可以自行进行下载使用。默认上传到绑定的服务空间&#xff0c;配置属性auto-upload为false关闭自动上传&#xff0c;可以限定上传的是…

【JAVA基础】DeferredResult使用详解

简介 Servlet3.0提供了基于servlet的异步处理api&#xff0c;Spring MVC只是将这些api进行了一系列的封装&#xff0c;从而实现了DeferredResult。 DeferredResult字面意思是"延迟结果"&#xff0c;它允许Spring MVC收到请求后&#xff0c;立即释放(归还)容器线程&…

OpenCV4工业缺陷检测的六种方法

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

【递归 回溯】LeetCode-17. 电话号码的字母组合

17. 电话号码的字母组合。 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digit…

轻量级web开发框架Flask本地部署及无公网ip远程访问界面

文章目录 前言1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 前言 本篇文章讲解如何在本地安装Flask&#xff0c;以及如何将其web界面发布到公网上并进行远程访问。 Flask是目前十分流行的web框架&#xff0c;采用P…

JDBC常用API

在开发JDBC程序前&#xff0c;先了解一下JDBC常用的API。JDBC API主要位于java.sql包中&#xff0c;该包定义了一系列访问数据库的接口和类。 &#xff08;1&#xff09;Driver接口 Driver接口是所有JDBC驱动程序必须实现的接口&#xff0c;该接口专门提供给数据库厂商使用。需…

windows11下载GCC编译器

下载 1. 打开MinGW官网 2. 点击下载, 进入后选择下载在Window系统 3. 选择在github上面下载, 进入后下载下面两个即可, 一个是编译器, 一个是源码. 考虑跨平台性, 下载posix版本, 只在Windows下开发应用就下载win32, 不知道就下载前者. win11选择ucrt, 之前版本选择msvcrt. 配…

pr插件|特殊编码.mkv/mov/flv/webm/avi/wmv/vob等多种格式视频素材直接导入pr的插件 Influx v1.2.5

适用于Adobe的一体式原生导入器插件&#xff08;Premiere Pro、After Effects和Media Encoder&#xff09;。支持多种格式和编解码器。 主要特点 直接在Adobe CC Video中进行本机导入 不再需要通过外部转码软件&#xff01;节省时间、磁盘空间和麻烦 在Premiere Pro中导入和编辑…

org.slf4j日志组件实现日志功能

slf4j 全称是Simple Logging Facade for Java。facade是一种设计模式。 slf4j 是一个抽象程度更高的日志组件&#xff0c;本身并不提供实际的日志功能。实际的日志功能是通过log4j等日志组件实现&#xff0c;而使用者只需要关心 slf4j 给出的API。 slf4j 仅仅是一个为Java程序提…

Redis BitMap(位图)

这里是小咸鱼的技术窝&#xff08;CSDN板块&#xff09;&#xff0c;我又开卷了 之前经手的项目运行了10多年&#xff0c;基于重构&#xff0c;里面有要实现一些诸如签到的需求&#xff0c;以及日历图的展示&#xff0c;可以用将签到信息存到传统的关系型数据库&#xff08;MyS…

基于SpringBoot的超市账单管理系统 JAVA简易版

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统设计3.1 总体设计3.2 前端设计3.3 后端设计在这里插入图片描述 四、系统展示五、核心代码5.1 查询供应商5.2 查询商品5.3 新增超市账单5.4 编辑超市账单5.5 查询超市账单 六、免责说明 一、摘要 1.1 项目介绍 基于…

JS模块化规范之ES6及UMD

JS模块化规范之ES6及总结 前言ES6模块化概念基本使用ES6实现 UMD(Universal Module Definition)总结 前言 ESM在模块之间的依赖关系是高度确定的&#xff0c;与运行状态无关&#xff0c;编译工具只需要对ESM模块做静态分析&#xff0c;就可以从代码字面中推断出哪些模块值未曾被…

快速排序(非递归)以及归并排序的递归与非递归

快速排序的非递归算法&#xff1a; 递归次数太多的缺陷&#xff1a;极端情况下&#xff08;栈帧深度太深&#xff09;会导致栈溢出&#xff0c;即使程序代码正确&#xff08;递归的深度足够深时&#xff0c;空间不足&#xff0c;就会导致栈溢出&#xff09;&#xff0c;因此在…