首页 > 其他分享 >C语言实现三子棋小游戏

C语言实现三子棋小游戏

时间:2024-08-28 10:22:31浏览次数:12  
标签:int 三子 C语言 COLUMN 小游戏 board printf ROW row

前言与概述

本文章讲述如何通过C语言开发一款三子棋的小游戏。笔者才识浅薄,如有错误,欢迎各位编程大佬在评论区批评指正,笔者不胜感激。

游戏介绍

三子棋是一款益智的趣味小游戏。多名玩家在3*3的棋盘下棋,棋盘共九个方格,每个方格最多只能放置一枚棋子。只要有一名玩家下的三个棋子可以连成一条直线,则该玩家取得游戏的胜利,游戏结束。如果棋盘已满,还是没有玩家下的棋子可以连成直线,那么游戏平局。

游戏实现路径

①:创建并初始化棋盘 ②:打印棋盘 ③:玩家和电脑下棋 ④:判断游戏状态

游戏运行全过程

当用户输入1进入游戏,程序会打印未下棋子的棋盘,用户通过输入要下棋子的坐标,将棋子下在棋盘的指定位置。电脑会在棋盘的随机位置下棋,打印下棋后棋盘的状况。如果玩家或电脑下的三个棋子可以连成直线,则玩家或电脑取胜。如果棋盘已满,但玩家和电脑下的棋子都不能连成直线,则游戏平局。否则,游戏继续。当游戏结束,用户输入0可以退出程序。

游戏开发前准备

为了更好的实现游戏的相关功能,笔者在该工程目录下创建了四个文件,分别是main.cpp、test.cpp、game.cpp、game.h。main.cpp用于实现用户与玩家的交互界面。test.cpp用于测试游戏的实现逻辑。game.cpp用于游戏实现的相关函数。game.h用于存放源文件所需要的头文件、符号常量、函数声明,当其它源文件需要调用这些内容,直接引用即可。

交互界面

实现要求

游戏至少运行一次,如果玩家玩完一把,感觉不过瘾,还可以继续玩。直到玩家想主动退出游戏。这里就可以使用do-while循环语句。用户输入1,进入游戏;用户输入0,退出游戏;用户输入其它数字,提示:“输入错误,请重新输入”,直到用户输入正确数字。这里就可以使用switch-case语句,对用户输入的情况进行判断,并作出相应的反馈。当然,我们需要制作并打印一个简易的菜单,让用户可以轻松的与程序进行交互。

示例代码

//用于实现玩家与游戏的交互和打印主界面
#include "game.h"
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			printf("玩游戏!\n");
			break;
		}
		case 0:
		{
			printf("退出游戏!\n");
			break;
		}
		default:
		{
			printf("输入错误,请重新输入!\n");
			break;
		}
		}
	} while (input);
	return 0;
}

menu()函数代码

void menu()
{
	printf("***************************\n");
	printf("********   1.play   *******\n");
	printf("********   0.exit   *******\n");
	printf("***************************\n");
}

模拟运行 

 

补充说明

①:do-while循环语句,循环条件是变量input的值。变量input保存着用户输入的值,当用户输入的值非0,循环条件一直成立,程序持续运行,如果用户输入的值为0,循环条件不成立,程序退出。

②:main.cpp文件引用了自定义的头文件,引用方式为”头文件名.h”引用该头文件,相当于将头文件里所有的内容复制到main.cpp文件中。

创建和初始化棋盘

创建棋盘

三子棋是3*3的棋盘,在C语言中可以抽象的理解为3*3的二维数组。二维数组的行和列可以是3与3的常量,也可以是#define定义常量3。笔者更倾向于#define定义常量3。因为数组需要存放“棋子”,所以数组类型是char类型,数组可以初始化为0。

创建二维数组示例代码

	//创建二维数组
	char board[3][3] = { 0 };//常量3作为二维数组行和列
	#define ROW 3
	#define COLUMN 3
	char board[ROW][COLUMN] = { 0 };//使用#define 定义常量3作为二维数组行和列

初始化棋盘

初始化棋盘就是把数组里所有的元素都改为空格。for循环可以用作遍历数组里的元素,“=”赋值符可以修改数组里任意元素的内容。

示例代码

void format_board(char board[ROW][COLUMN], int row, int column)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < column; j++)
		{
			board[i][j] = ' ';
		}
	}
}

代码分析

对棋盘初始化不需要任何返回值,所以返回类型使用void类型(空类型)即:没有返回值,也不需要返回值。在实参方面,因为需要修改数组,所以需要将数组名传过去(本质上传的是数组首元素的地址),同时for循环遍历二维数组需要知道数组的行和列,需要将常量ROW和常量COLUMN传过去。在形参方面,因为数组是char类型的二维数组,所以需要交代数组行和列的大小(行可以省去)。同时传来的还有常量ROW和常量COLUMN的值,所以需要用int类型的变量接收(注意:如果使用变量名是row和column,必须小写,防止与常量ROW和常量COLUMN发生冲突,导致编译器报错)。循环变量i、j的初始值是0,因为数组下标从0开始,for循环的循环条件是变量i或j的值小于变量row或变量column,而不是小于等于,是为了避免出现数组元素溢出。“=”将空格赋予数组,不可以只敲一个 ,因为空格是字符,所以需要以字符的形式(‘ ‘)传给数组。

打印棋盘

打印棋盘的实质就是访问数组中的每个元素,为了使棋盘更加美观,我们可以适当的使用空格、-(中划线-英文)、|(竖线)。

示例代码1

void print_board(char board[ROW][COLUMN], int row, int column)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		printf(" %c | %c | %c ", board[i][0], board[i][1], board[i][2]);
		printf("\n");
		if (i < row - 1)
		{
			printf("---|---|---");
			printf("\n");
		}
	}
}

 示例代码2

void print_board(char board[ROW][COLUMN], int row, int column)
{
	int i, j;
	for (i = 0; i < row;i++)
	{
		for (j = 0; j < column;j++)
		{
			printf(" %c ", board[i][j]);
			if (j < column - 1)
			{
				printf("|");
			}
		}
		printf("\n");
		int z = 0;
		if (i < row - 1)
		{
			for (z = 0; z < row; z++)
			{
				printf("---");
				if (z < row - 1)
				{
					printf("|");
				}
			}
			printf("\n");
		}
	}
}

模拟运行 

 

补充说明

示例代码1相对简单,易于理解。不过仅仅只适用于3*3的数组中,示例代码2代码量更大,使用多个if语句和for循环,将单行打印拆分成多个部分,使得该函数可以打印任意行的数组。

玩家与电脑下棋

玩家下棋

玩家通过输入坐标,将棋子放置在棋盘的指定位置。对坐标有一定的限制:首先,坐标不可以越界,即横坐标和纵坐标必须是大于0小于4的整数。其次,一个方格只能放一个棋子,如果坐标上已经有棋子,就不可以再放了。玩家棋子用“*”表示。

示例代码

void player_move(char board[ROW][COLUMN], int row, int column)
{
	while (1)
	{
		int x, y;
		printf("玩家下棋>");
		scanf("%d %d", &x, &y);
		if (x > 0 && x < 4 && y > 0 && y < 4)
		{
			if (board[x-1][y-1] == ' ')
			{
				board[x-1][y-1] = '*';
				break;
			}
			else
			{
				printf("该处已布置棋子,请重新下棋>\n");
			}
		}
		else
		{
			printf("坐标不合规,请重新输入>\n");
		}
	}
}

运行结果 

 

代码分析

函数内使用while循环,因为玩家在输入坐标时,可能会输错。当玩家下错棋,程序会提示错因,并等待重新输入坐标,直到坐标正确,就会将棋子放在棋盘指定的位置,并通过break语句跳出循环。判断坐标是否被占用、棋子放置棋盘都将变量x和变量y的值减一,是因为玩家输入的坐标始终比实际在数组中的位置横纵坐标各多一。例如玩家输入的坐标是1 1,实际在数组中的位置是下标0 0。

电脑下棋

电脑下棋同样也是通过坐标,将棋子放置在棋盘的指定位置。不过,对于计算机而言,我们可以通过控制计算机生成随机数的范围,来避免出现坐标不合规的问题。由于,计算机生成的坐标是随机的,所以可能存在坐标被占用的情况,当生成的坐标被占用,就让计算机重新生成坐标,直到生成的坐标没有被占用,就将棋子放置在生成的坐标位置处。

示例代码

void computer_move(char board[ROW][COLUMN], int row, int column)
{
	int x = 0;
	int y = 0;
	printf("电脑下>\n");
	while (1)
	{
		x = rand() % row;
		y = rand() % column;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

运行结果 

 

代码分析

rand()函数用于生成随机数,需要先调用srand()函数,同时srand()函数需要整型数字作为种子,但如果种子不变,那生成的随机数就不变,所以需要一个时刻变化的数作为种子。时间戳是无时无刻不在变化。time(NULL)用于获取当前时间的时间戳,但时间戳并不是整型数字,就需要强制转换为int类型((unsigned int))。在程序运行中,srand((unsigned int)time(NULL))只需要调用一次即可,多次快速调用,会导致生成的随机数相近甚至相同。rand()函数生成的随机数较大,通过% row,就可以将生成的随机数控制在0-2之间。

判断游戏状态

玩家赢或电脑赢

算法

三子棋获胜的规则:任何一方下的三个棋子,可以连成直线。连成直线有三种可能:同一行、同一列、对角线。

 

在同一行中,横坐标的值相等;在同一列中,纵坐标的值相等。由于同一行、同一列都各有三种可能性,所以可以使用for循环,遍历三次。

三个棋子可以连成直线,就是数组中三个元素的值相等。判断三个元素相等的方式是第一个元素等于第二个元素,第二个元素等于第三个元素。为了避免数组中三个元素等于空格,导致程序误判,所以需要判断其中一个元素不等于空格。

返回三个相同元素中的一个,根据返回的元素判断谁赢。如果返回的是*(玩家下的棋),表明玩家赢;如果返回的是#(电脑下的棋),表示电脑赢。

示例代码

char win(char board[ROW][COLUMN], int row, int column)
{
	int i = 0;
	int j = 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];
		}
	}
	for (j = 0; j < column; 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];
	}
	int result = 0;
	result = is_full(board, row, column);
	if (result == 0)
	{
		return 'c';
	}
	else
	{
		return 'q';
	}
}

平局或游戏继续

算法

判断平局的实现方式:在任何一方都没有取胜的情况下,棋盘已经没有空余的方格。实际上就是遍历完数组所有元素,没有一个元素是空格。游戏继续的实现方式:在任何一方都没有取胜的情况下,棋盘还有空余的方格。实际上就是遍历数组,还有数组的元素是空格。笔者定义变量result,用于接收返回值。如果返回值是0,说明数组中还有元素是空格,棋盘未满,返回字符c,表示游戏继续;如果返回值是1,说明数组中所有元素都不是空格,棋盘已满,返回字符q,表示平局。

示例代码

int  is_full(char board[ROW][COLUMN], int row, int column)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < column; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;
			}
		}
	}
	return 1;
}

游戏总测试 

 

源代码 

main.cpp源代码

#define _CRT_SECURE_NO_WARNINGS 1
//用于实现玩家与游戏的交互和打印主界面
#include "game.h"
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");
			break;
		}
		}
	} while (input);
	return 0;
}

test.cpp源代码 

#define _CRT_SECURE_NO_WARNINGS 1
//用于测试游戏的实现逻辑
#include "game.h"
void game()
{
	//创建二维数组
	char board[ROW][COLUMN] = { 0 };
	//格式化棋盘
	format_board(board, ROW, COLUMN);
	//打印棋盘
	print_board(board, ROW, COLUMN);
	//玩家下棋
	while (1)
	{
		char amount = 0;
		player_move(board, ROW, COLUMN);
		amount = win(board, ROW, COLUMN);
		if (amount == '*')
		{
			printf("玩家赢!\n");
			print_board(board, ROW, COLUMN);
			break;
		}
		else if (amount == '#')
		{
			printf("电脑赢!\n");
			print_board(board, ROW, COLUMN);
		}
		else if (amount == 'q')
		{
			printf("平局!\n");
			print_board(board, ROW, COLUMN);
			break;
		}
		computer_move(board, ROW, COLUMN);
		amount = win(board, ROW, COLUMN);
		if (amount == '*')
		{
			printf("玩家赢!\n");
			print_board(board, ROW, COLUMN);
			break;
		}
		else if (amount == '#')
		{
			printf("电脑赢!\n");
			print_board(board, ROW, COLUMN);
		}
		else if (amount == 'q')
		{
			printf("平局!\n");
			print_board(board, ROW, COLUMN);
			break;
		}
		print_board(board, ROW, COLUMN);
	}
}

game.h源代码 

//用于存放源文件所需要的头文件、符号常量、函数声明
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu();
void game();
#define ROW 3
#define COLUMN 3
void format_board(char board[ROW][COLUMN], int row, int column);
void print_board(char board[ROW][COLUMN], int row, int column);
void player_move(char board[ROW][COLUMN], int row, int column);
void computer_move(char board[ROW][COLUMN], int row, int column);
char win(char board[ROW][COLUMN], int row, int column);
int  is_full(char board[ROW][COLUMN], int row, int column);

 game.cpp源代码

#define _CRT_SECURE_NO_WARNINGS 1
//用于游戏实现的相关函数
#include "game.h"
void menu()
{
	printf("***************************\n");
	printf("********   1.play   *******\n");
	printf("********   0.exit   *******\n");
	printf("***************************\n");
}
void format_board(char board[ROW][COLUMN], int row, int column)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < column; j++)
		{
			board[i][j] = ' ';
		}
	}
}
void print_board(char board[ROW][COLUMN], int row, int column)
{
	int i, j;
	for (i = 0; i < row;i++)
	{
		for (j = 0; j < column;j++)
		{
			printf(" %c ", board[i][j]);
			if (j < column - 1)
			{
				printf("|");
			}
		}
		printf("\n");
		int z = 0;
		if (i < row - 1)
		{
			for (z = 0; z < row; z++)
			{
				printf("---");
				if (z < row - 1)
				{
					printf("|");
				}
			}
			printf("\n");
		}
	}
}
void player_move(char board[ROW][COLUMN], int row, int column)
{
	while (1)
	{
		int x, y;
		printf("玩家下棋>");
		scanf("%d %d", &x, &y);
		if (x > 0 && x < 4 && y > 0 && y < 4)
		{
			if (board[x-1][y-1] == ' ')
			{
				board[x-1][y-1] = '*';
				break;
			}
			else
			{
				printf("该处已布置棋子,请重新下棋>\n");
			}
		}
		else
		{
			printf("坐标不合规,请重新输入>\n");
		}
	}
}
void computer_move(char board[ROW][COLUMN], int row, int column)
{
	int x = 0;
	int y = 0;
	printf("电脑下>\n");
	while (1)
	{
		x = rand() % row;
		y = rand() % column;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

char win(char board[ROW][COLUMN], int row, int column)
{
	int i = 0;
	int j = 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];
		}
	}
	for (j = 0; j < column; 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];
	}
	int result = 0;
	result = is_full(board, row, column);
	if (result == 0)
	{
		return 'c';
	}
	else
	{
		return 'q';
	}
}
int  is_full(char board[ROW][COLUMN], int row, int column)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < column; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;
			}
		}
	}
	return 1;
}

标签:int,三子,C语言,COLUMN,小游戏,board,printf,ROW,row
From: https://blog.csdn.net/2401_84689376/article/details/141537637

相关文章

  • 数据结构链表(C语言版)
    链表定义链表是一种常见的基础数据结构,它由一系列节点(Node)组成,每个节点包含数据域和指向列表中下一个节点的指针(在双向链表中还会有指向前一个节点的指针)。链表的一个优点是它允许有效地在序列中插入和删除元素。节点(Node)一个节点通常包含两个部分:数据域(DataField):存储实......
  • C语言典型例题53
    《C程序设计教程(第四版)——谭浩强》例题4.5用π/4=1-1/3+1/5-1/7+……公式来计算π的近似值,知道某一项的绝对值小于10的六次方为止代码://《C程序设计教程(第四版)——谭浩强》//例题4.5用π/4=1-1/3+1/5-1/7+……公式来计算π的近似值,知道某一项的绝对值小于10的六次方......
  • C语言:编程世界的基石
    在计算机科学的世界里,C语言就像一座坚固的桥梁,连接着硬件和软件的两端。自从20世纪70年代诞生以来,C语言以其简洁、高效和强大的特性,成为了编程领域的经典之作。本文将探讨C语言在不同工作领域中的应用,以及它为何能够历经时间的考验,依旧活跃在现代编程的舞台上。目录1.系......
  • C语言程序设计:链表删除相关结点
        创建一个链表,每个结点包括:学号、姓名、性别、年龄。输入一个年龄,如果链表中的结点所包含的年龄等于此年龄,则将此结点删去。1.声明结构体类型结构体类型structStudent,包含成员学生学号(整型)、学生姓名(字符数组)、性别(字符型)、年龄(整型),next结构体指针。声明全局变量n......
  • C语言字符函数和字符串函数的详解及模拟实现(超详细)
    目录1.求字符串长度1.1strlen1.1.1.strlen函数介绍1.1.2.strlen函数模拟实现 2.长度不受限制的字符串函数 2.1strcpy2.1.1.strcpy函数介绍2.1.2.strcpy函数模拟实现 2.2strcat2.2.1.strcat函数介绍2.2.2.strcat函数模拟实现 2.3strcmp 2.3.1.strcmp函数介绍......
  • C语言数据类型
    关键字的介绍数据类型关键字有12个:控制语句关键字有12个:存储类型关键字有4个:其他关键字有4个:字符和ASCII码的介绍数据介绍什么是数据类型?数据类型的具体组成字符:整形:浮点型布尔类型数据类型的取值范围关键字的介绍C语言的关键字有:1、数据类型关键字2、控制语......
  • C++与C语言中基础数据类型详解
    目录引言基础数据类型分类实际编程中的应用建议结论引言在C++与C语言的编程世界中,理解并正确使用基础数据类型是每个程序员的必备技能。不同的数据类型在内存中的占用和表示方式直接影响到程序的性能和行为。本文将详细介绍C++与C语言中常见的基础数据类型,探讨它们......
  • 探索C语言中数组作为函数参数的奥秘
    在C语言的世界里,数组是一种基础且强大的数据结构,它允许我们存储相同类型的数据集合。然而,在处理函数和数组的关系时,尤其是在数组作为函数参数传递时,初学者往往会感到困惑。今天,我们就来深入探讨这一话题,通过具体的代码示例来揭开其神秘面纱。数组作为函数参数的两种形式在C语......
  • 全网最易懂的解题——C语言“打印一个数的每一位(递归)”
    很简单吧递归我们做了很多题,逆序打印数字和逆序打印数组我们也做过代码就直接附上了voidmy_print(intnum){ if(num<10)//说明只有一位数字 { printf("%d",num); } else { my_print(num/10); printf("%d",num%10); }}//打印数字的每一位intmain(......
  • 全网最易懂的解题——C语言“求斐波那契数(递归)”
    那先来知道什么是斐波那契数列吧前两个数相加等于第三个数,如果其中数字都满足此条件,那么这就是斐波那契数列 现在我们要求第n个斐波那契数,代码框架先搭出来吧,找斐波那契数的函数就命名为Fib吧//求斐波那契数intmain(){ intn=0; printf("请输入你想知道第几个斐波......