C语言实现简单的三子棋

目录

  • 前言
  • 一、设计思路
    • 菜单
    • 棋盘的实现
    • 游戏开始
    • 判断输赢
  • 二、游戏代码
    • 头文件
    • 游戏菜单
    • 游戏开始
      • 初始化和打印棋盘:
      • 玩家下棋
      • 电脑下棋
    • 判断输赢
  • 三、代码改进
  • 后记


前言

井字棋,英文名叫Tic-Tac-Toe,是一种在3*3格子上进行的连珠游戏,和五子棋类似,由于棋盘一般不画边框,格线排成井字故得名。游戏需要的工具仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X),任意三个标记形成一条直线,则为获胜。本文我们使用C语言来实现这个小游戏。


一、设计思路

菜单

在生活中,我们所玩的每一款游戏在开始界面,都会有一个菜单供我们选择,开始游戏或者结束游戏,因此我们也要先设计一个简单的菜单,让玩家进行选择。

棋盘的实现

当玩家选择开始游戏之后,我们要实现一个棋盘,对于3* 3的棋盘我们可以用一个3* 3的数组来实现,同时我们要将棋盘初始化为接近真实棋盘的模样。如下所示
C语言实现简单的三子棋_第1张图片

游戏开始

当棋盘初始化之后,我们就可以正式开始游戏,玩家根据棋盘当前的状态,可以输入他想要落子位置的坐标,如果这个坐标没有被占用,那么我们就在这个位置打印’X’代表此处已经被玩家落子。当玩家落子之后,轮到电脑落子,我们让电脑随机产生一个符合棋盘大小的坐标,并且如果坐标没有被占用,就在这个坐标打印一个’O’代表此处已被电脑落子。

判断输赢

输赢的判断有两种方式:
第一种,玩家或者电脑每走完一步,我们就判断一次是否产生赢家
第二种,当玩家或者电脑走了三步时,我们才开始判断是否产生赢家

二、游戏代码

头文件

首先我们创建一个头文件,用来声明我们所使用到的函数以及定义棋盘的大小。由于我们要让电脑随机产生一个坐标,因此我们需要用到srand,rand以及time函数,同时在游戏过程中还需要用到输入输出函数,因此我们在头文件中,将我们需要用到的标准库全都包含在内,这样在.c文件就只需要包含这一个头文件即可

//game.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#define ROW 3			//棋盘行数
#define COL 3			//棋盘列数


void InitBoard(char arr[ROW][COL], int row, int col);			//初始化棋盘
void PrintBoard(char arr[ROW][COL], int row, int col);			//打印棋盘	
void PlayerMove(char arr[ROW][COL], int row, int col);		//玩家下棋
void CopMove(char arr[ROW][COL], int row, int col);		//电脑下棋
char IsWin(char arr[ROW][COL], int row, int col);		//判断结果

游戏菜单

在main函数中,我们打印出游戏菜单,并让玩家进行选择,当玩家选择了开始游戏时,我们调用game函数。由于后面我们需要电脑产生随机的坐标,因此我们在main函数中使用srand初始化种子。对于srand和rand函数的使用方法在我的另一篇博客中进行了详细的介绍,有兴趣的读者可以作为参考。rand和srand函数用法

#define _CRT_SECURE_NO_WARNINGS
#include"game.h"

//打印菜单
void menu()
{
     
	printf("**********************************************\n");
	printf("********请选择->   1:开始游戏  0:结束游戏\n");
	printf("**********************************************\n");
}


int main()
{
     
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
     
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
     
		case 1:
			game();
			break;
		case 0:
			printf("游戏结束\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述

游戏开始

当玩家选择了开始游戏之后,我们调用game函数进行游戏, 首先我们初始化棋盘,并将棋盘打印出来,之后让玩家先走,玩家或者电脑每走完一步都打印一次棋盘并判断是否产生赢家。
当游戏结束时有三种可能:
1、玩家胜利
2、电脑胜利
3、平局
因此我们用Winer函数来判断胜者是谁。X代表玩家赢,O代表电脑赢。

//判断胜者是谁
void Winer(char ret)
{
     
	if (ret == 'X')
	{
     
		printf("恭喜你赢了\n");
	}
	else if (ret == 'O')
	{
     
		printf("很遗憾,你输了\n");
	}
	else
	{
     
		printf("平局\n");
	}
}
// 游戏开始
void game()
{
     
	char arr[ROW][COL] = {
      0 };
	InitBoard(arr, ROW, COL);					//初始化棋盘
	PrintBoard(arr, ROW, COL);					//打印棋盘
	while (1)
	{
     
		char ret = 0;
		PlayerMove(arr, ROW, COL);					//玩家下棋
		PrintBoard(arr, ROW, COL);					//打印棋盘
		ret = IsWin(arr, ROW, COL);					//判断输赢
		if (ret != 'C')								//当ret!='C'时,游戏结束(C代表continue)
		{
     
			Winer(ret);				//判断胜者是谁
			break;
		}
		CopMove(arr, ROW, COL);						//电脑下棋
		PrintBoard(arr, ROW, COL);
		ret = IsWin(arr, ROW, COL);
		if (ret != 'C')
		{
     
			Winer(ret);
			break;
		}
	}
}

初始化和打印棋盘:

在玩家落子前,我们先将棋盘初始化空

//初始化棋盘
void InitBoard(char arr[ROW][COL], int row, int col)
{
     
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
		for (j = 0; j < col; j++)
			arr[i][j] = ' ';
}

由于我们所希望的棋盘是这样的:
C语言实现简单的三子棋_第2张图片
为了实现类似的棋盘,我们需要使用|以及— 来作为分隔符,并且注意到,我们所希望的棋盘最后一行和最后一列都没有分隔符,因此我们在代码中要进行一个if语句的判断

//打印棋盘
void PrintBoard(char arr[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 ", arr[i][j]);
			if (j < col - 1)
				printf("|");		//不是最后一列,打印 | 作为分隔符
		}
		printf("\n");
		if (i < row - 1)
			for (j = 0; j < col; j++)		//不是最后一行,打印--- --- --- 作为分隔符
				printf("--- ");		
		printf("\n");
	}
}

C语言实现简单的三子棋_第3张图片

玩家下棋

我们令棋盘上的坐标从1开始,因此对应到数组的下标就要减1,并且需要判断玩家当前输入的坐标是否已经被占用,如果没被占用则赋值为X。

//玩家下棋
void PlayerMove(char arr[ROW][COL], int row, int col)
{
     
	int i = 0;
	int j = 0;
	printf("玩家下棋,请输入落子的坐标:\n");
	while (1)
	{
     
		scanf("%d%d", &i, &j);
		if (i <= 0 || j <= 0 || i > ROW || j > COL)		//超出棋盘范围
			printf("坐标输入错误,请重新输入\n");
		else
		{
     
			if (arr[i - 1][j - 1] == ' ') 			//坐标未被占用
			{
     
				arr[i - 1][j - 1] = 'X';			//当前坐标赋值为X
				break;
			}
			else printf("此处已经被占用,请重新输入\n");
		}
	}
}

电脑下棋

电脑下棋时,我们需要电脑随机生成一个[0,row]的行坐标和一个[0,col]的列坐标,用srand来实现随机的坐标。只要产生的坐标没被占用,就将其赋值为O,不需要打印提示信息。

//电脑下棋
void CopMove(char arr[ROW][COL], int row, int col)
{
     
	int i = 0;
	int j = 0;
	printf("电脑下棋:\n");
	while (1)
	{
     
		i = rand() % row;
		j = rand() % col;
		if (arr[i][j] == ' ')
		{
     
			arr[i][j] = 'O';
			break;
		}
	}
}

C语言实现简单的三子棋_第4张图片

判断输赢

想要判断输赢,我们必须要检查每一行,每一列以及两个对角线是否有连续三个相同的符号,并且这个符号不为空(因为初始化的时候我们将其置为了空)。当三个位置符号相同时,我们只需要将这个符号返回,然后在Winer函数中判断胜者到底是谁。当没有胜者产生时,我们还要判断棋盘是否落满,如果落满则表明和棋。当没有胜者并且棋盘也没有落满时,游戏继续。

//判断棋盘是否满
int IsFull(char arr[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 (arr[i][j] == ' ') return 0;		//如果有一个位置为空,表明棋盘还没落满,返回0
	return 1;			//棋盘全部落满,返回1
}
//3*3 输赢判断
char IsWin(char arr[ROW][COL], int row, int col)
{
     
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)			//判断行是否相等
	{
     
		if (arr[i][0] == arr[i][1] && arr[i][0] == arr[i][2] && arr[i][0] != ' ')  
			return arr[i][0];			//玩家胜利则arr[i][0]是X,电脑胜利则是O
	}
	for (i = 0; i < col; i++)			//判断列是否相等
	{
     
		if (arr[0][i] == arr[1][i] && arr[0][i] == arr[2][i] && arr[0][i] != ' ')
			return arr[0][i];
	}
		//判断主对角线是否相等
	if (arr[0][0] == arr[1][1] && arr[0][0] == arr[2][2] && arr[0][0] != ' ')
		return arr[0][0];
		//判断副对角线是否相等
	if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ')
		return arr[1][1];
		//判断棋盘是否落满
	int ret = IsFull(arr, row, col);
	if (ret) return 'F';	//如果ret为1,则棋盘落满,返回'F'
	else return 'C';	//程序运行到这,表明没有胜者并且棋盘不为空
						//(如果不满足这几个条件,在前面函数就已经返回,不会运行到这一步)
						//返回C则继续游戏
}

C语言实现简单的三子棋_第5张图片

三、代码改进

上面的代码只能在3*3的棋盘上判断输赢,无法对更大的棋盘进行判断,对此作出如下改进:
行判断:第 i 行连续三个位置是否相同,同时保证不超过棋盘的范围
列判断:第 j 列连续三个位置是否相同,同时保证不超过棋盘的范围
主对角线判断:连续三个主对角线位置是否相同,即arr[i][j],arr[i+1][j+1],arr[i+2][j+2]是否相同,同时保证不超过棋盘范围
副对角线判断:连续三个副对角线位置是否相同,即arr[i][j],arr[i+1][j-1],arr[i+2][j-2]是否相同,同时保证不超过棋盘范围
因为主对角线判断时,行号和列号不断增大,因此i和j从0开始,而副对角线判断时,行号不断增大,列号不断减小,因此i从0开始,j从col-1开始。

//判断任意大小棋盘输赢
char IsWin(char arr[ROW][COL], int row, int col)
{
     
	int i = 0;
	int j = 0;
	//判断行
	for (i = 0; i < row; i++)		//判断第i行连续3个位置是否相等
	{
     
		for (j = 0; j < col - 2; j++)		//不能超出棋盘范围,因此j < col - 2
		{
     
			if (arr[i][j] == arr[i][j + 1] && arr[i][j] == arr[i][j + 2] && arr[i][j] != ' ')
				return arr[i][j];
		}
	}
	//判断列
	for (j = 0; j < col; j++)		//判断第j列连续3个位置是否相等
	{
     
		for (i = 0; i < row - 2; i++)		//不能超出棋盘范围,因此i < row - 2
		{
     
			if (arr[i][j] == arr[i + 1][j] && arr[i][j] == arr[i + 2][j] && arr[i][j] != ' ')
				return arr[i][j];
		}
	}
	//判断对角线
	for (i = 0; i < row - 2; i++)		//主对角线判断
	{
     
		for (j = 0; j < col - 2; j++)
		{
     
			if (arr[i][j] == arr[i + 1][j + 1] && arr[i][j] == arr[i + 2][j + 2] && arr[i][j] != ' ')
				return arr[i][j];
		}
	}
	for (i = 0; i < row - 2; i++)		//副对角线判断
	{
     
		for (j = col - 1; j >= 2; j--)
		{
     
			if (arr[i][j] == arr[i + 1][j - 1] && arr[i][j] == arr[i + 2][j - 2] && arr[i][j] != ' ')
				return arr[i][j];
		}
	}
	int ret = IsFull(arr, row, col);
	if (ret) return 'F';
	return 'C';
}

C语言实现简单的三子棋_第6张图片

后记

本文虽然实现了一个简单的三子棋,但是由于电脑落子是随机的,因此电脑很难获胜,当棋盘比较小时,甚至想让它赢都很困难。也就是游戏难度太低,毫无游戏体验。如果想让电脑更加智能化,需要使用更复杂的算法,同时在判断输赢时,使用的是暴力枚举法,对所有的可能都进行了判断,时间复杂度为O(n^2),效率比较低。鉴于目前水平有限,只能使用最简单的算法,随着后续知识的扩展,再对算法进行改进。

你可能感兴趣的:(C语言,三子棋,算法)