三子棋游戏的实现

news/2024/7/7 22:51:18

        前言:相信大家都玩过三子棋吧,曾想经常和同学在考试之后玩一个晚自习的三子棋。那么,如何自己编写一个三子棋游戏呢,请看下面的分析;

1.游戏设计思路

        在写任何代码时,最好先有个大致的框架,然后在大的框架下不断填充相应的细节,来实现相应的功能。

那么,想设计一个三子棋游戏需要哪些步骤呢

            1.主函数部分

            2.初始化棋盘

            3.打印棋盘

            4.下棋(玩家和电脑)

            5.判断输赢

2.设计之前的一些准备

        可以预见到,在编写游戏时需要使用到大量的函数,如果在同一文件下既声明函数,又定义函数,最后再调用函数,会使得整个程序杂乱无章,难以阅读。这时,利用分模块编程的方法就可以很好的避免代码混乱,冗长的问题。

        所谓分模块编程,即创立三个文件,一个头文件game.h(用来存放函数的声明,包括各种库函数以及使用到的标识符常量),两个源文件game.c(用来存放函数的定义)和test.c(函数的实现以及主函数);

        同时,为了使代码的灵活性大大提高,我们可以使用#define 定义的标识符常量来定义棋盘的大小;

3.具体实现过程

        1.主函数部分

                主函数是整个游戏过程的初始化,要根据用户的要求来判断是否进行游戏;

                代码如下:

int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do            //不管三七二十一先打印一次菜单              
	{
		menu();//打印菜单
		scanf("%d",&input);
		//根据用户输入的内容来判断是否进行后续操作
		switch (input)
		{
			case 1:
				game();
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误,请重新输入");
		}
	} while (input); //只有输入0的时候才会推出循环(0为假)
	return 0;
}

        2.初始化棋盘

                分析:棋盘是用来落子的,在程序中其实就是存放数据的,又因为棋盘的形状,很自然的想到使用二维数组存放数据,所以我们可以使用二维数组来初始化棋盘

//game.h      //函数声明,定义都要指明各参数的类型
void InitBoard (char board [ROW][COL], int row, int col);

//game.c
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] = ' ';//将棋盘全部初始化为空格
		}                     //注意这里不是"",是'',因为空格本质上是个字符,引用字符要使用单引号
	}                         //若使用" ",本质上是一个字符串引用,实际上存储了两个字符,还有\0
}

//1.test.c  存放数据 ->二维数组      先用空格代替
	InitBoard(board, ROW, COL);

        3.打印棋盘

                在初始化棋盘后,发现棋盘仅仅只能存储数据(而且数据是空格),并没有边界线,所以我们要想办法打印棋盘

#define ROW 3      //这个刚开始犯了一个巨低级的错误,在最后添加了;
#define COL 3      //只有语句的结束才需要添加分号

//game.h
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);

//game.c
//打印棋盘

//版本1    只有空格  并没有分割线,不符合棋盘样式
//void DisplayBoard(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++)
//		{
//			printf("%c", board[i][j]);
//		}
//		printf("\n");
//	}
//}

//版本2      行数和列数都限制死了
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
//	int i = 0;
//	//一行一行打印
//	for (i = 0; i < row; i++)
//	{
//	//1.打印数据
//		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//	//2.打印分割线
//		if(i<row -1 )//最后一行不打印分割线
//			printf("---|---|---\n");
//	}
//
//}

//版本3       利用大事化小的思维,利用循环打印数据和分割线
void DisplayBoard(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");
		}
	}
}

//test.c
//2.打印棋盘
	DisplayBoard(board, ROW, COL);

        4.模拟下棋(玩家and电脑)

                下棋整个过程是游戏实现的重要步骤,在下棋的过程中要模拟真实世界的下棋过程(循环一直对垒,直到分出输赢),下面请看代码实现

game.h

//下棋
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
	
//电脑下棋
void ComMove(char board[ROW][COL], int row, int col);

game.c

//下棋
//玩家先步
void PlayerMove(char board[ROW][COL], int row, int col)
{
	//给用户提供坐标
	int x = 0;
	int y = 0;
	printf("玩家下棋->");
	while (1)
	{
		printf("请输入要下的坐标,中间请用空格分隔->\n");
		scanf("%d %d", &x, &y);//用户输入之后,要判断用户输入的行数和列数是否合法
		if (x >= 1 && x <= row && y >= 1 && y <= col)//输入合法
		{
			//要注意从用户的角度去思考,用户只认为的第一行是从1开始的,而不是0
			if (board[x - 1][y - 1] == ' ')//分两种情况讨论
			{                              //1.位置没有被占用
				board[x - 1][y - 1] = '*';
				break;//输入正确就挑出玩家下棋的过程
			}
			else                           //2.位置已被占用
			{
				printf("该位置已被占用,请重新输入");
			}
		}
		else   //也要考虑到玩家有可能输入的坐标值越界
		{
			printf("坐标非法,请重新输入:");
		}
	}
}

//电脑下棋
void ComMove(char board[ROW][COL], int row, int col)//可见,电脑和用户还是有很大区别的
{
	int x = 0;
	int y = 0;
	printf("电脑下棋->\n");
	//电脑下的位置是随机的  但是也要满足下棋的规则
	while (1)
	{
		x = rand() % row;//产生0-row-1的随机数
		y = rand() % col;
		if ( board[x][y] ==' ')
		{
			board[x][y] = '#';
			break;
		}
	}
	
}


test.c

while (1)//用循环模拟对战环节    下一步棋,打印一次
	{
		PlayerMove(board, ROW, COL);//玩家下棋
		DisplayBoard(board, ROW, COL);}

        5.判断输赢

                在每一次玩家或者电脑落子之后都要进行一次输赢的判断;这里的难点在于要对输赢的条件进行充分的考虑。比赛的结果分为三种,玩家赢,电脑赢,平局;

                比如获胜的情况大致可分为四种,主对角线,次对角线,横着一排,竖着一排,每种情况都要设置相应的代码;

下面请看代码实现过程

game.h

//判断输赢  在玩家或者用户每一次落子之后进行判断
//玩家赢  '*'
//电脑赢  '#'
//平局    'Q'//这样设置返回值的原因是:除了继续这种条件外,其他的都属于游戏结束的范畴
//继续    'C' //那我只需接受返回值,不是c,就跳出对垒,游戏结束        

int IsWin(char board[ROW][COL], int row, int col);


game.c

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;//找不到空格,证明满了,就是平局
}

int IsWin(char board[ROW][COL], int row, int col)
{
	//先判断赢
	//1.连成一行
	int i = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] &&board[i][0]!=' ')
			return board[i][0];
	}
	//2.连成一列
	int j = 0;
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j]!=' ')
			return board[0][j];
	}
	//3.对角线
	//主对角线
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0]!=' ')
		return board[0][0];
	//次对角线
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2]!=' ')
		return board[0][2];

	//平局
	if (IsFull(board,row,col) == 1)
		return 'Q';
	//继续
	return 'C';
}



test.c
while (1)//用循环模拟对战环节    下一步棋,打印一次
	{
		PlayerMove(board, ROW, COL);//玩家下棋
		DisplayBoard(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
			break;

		ComMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断输赢
        ret = IsWin(board, ROW, COL);
		if (ret != 'C') //只要不是c继续这种条件,其余情况都属于游戏结束的范畴
			break;
	}
	if (ret == '*')
		printf("玩家赢\n");
	else if (ret == '#')
		printf("电脑赢\n");
	else
		printf("平局\n");
	
}


4.总结部分

        通过编写三子棋游戏,很锻炼我们的思维,我们不能仅仅认为代码能跑就行,而是应该不断去优化我们的代码,这样我们的代码能力才可以提高。在编写代码时,我们要有自己的大致框架,路线是个纲,沿着正确的路线不断填充我们的细节;

最后附上各文件完整程序代码

game.h

#pragma once
//此文件是与游戏有关函数声明部分 
//有一个细节,将所有的可能用到的头文件都写到game.h之中,在其余的文件中只需引用此文件即可
#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 ComMove(char board[ROW][COL], int row, int col);

//判断输赢  在玩家或者用户每一次落子之后进行判断
//玩家赢  '*'
//电脑赢  '#'
//平局    'Q'//这样设置返回值的原因是:除了继续这种条件外,其他的都属于游戏结束的范畴
//继续    'C' //那我只需接受返回值,不是c,就跳出对垒,游戏结束        

int IsWin(char board[ROW][COL], int row, int col);

game.c

//此文件是和游戏有关函数的定义部分
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

//初始化棋盘
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] = ' ';//将棋盘全部初始化为空格
		}                     //注意这里不是"",是'',因为空格本质上是个字符,引用字符要使用单引号
	}                         //若使用" ",本质上是一个字符串引用,实际上存储了两个字符,还有\0
}

//打印棋盘

//版本1    只有空格  并没有分割线,不符合棋盘样式
//void DisplayBoard(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++)
//		{
//			printf("%c", board[i][j]);
//		}
//		printf("\n");
//	}
//}

//版本2      行数和列数都限制死了
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
//	int i = 0;
//	//一行一行打印
//	for (i = 0; i < row; i++)
//	{
//	//1.打印数据
//		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//	//2.打印分割线
//		if(i<row -1 )//最后一行不打印分割线
//			printf("---|---|---\n");
//	}
//
//}

//版本3       利用大事化小的思维,利用循环打印数据和分割线
void DisplayBoard(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");
		}
	}
}

//下棋
//玩家先步
void PlayerMove(char board[ROW][COL], int row, int col)
{
	//给用户提供坐标
	int x = 0;
	int y = 0;
	printf("玩家下棋->");
	while (1)
	{
		printf("请输入要下的坐标,中间请用空格分隔->\n");
		scanf("%d %d", &x, &y);//用户输入之后,要判断用户输入的行数和列数是否合法
		if (x >= 1 && x <= row && y >= 1 && y <= col)//输入合法
		{
			//要注意从用户的角度去思考,用户只认为的第一行是从1开始的,而不是0
			if (board[x - 1][y - 1] == ' ')//分两种情况讨论
			{                              //1.位置没有被占用
				board[x - 1][y - 1] = '*';
				break;//输入正确就挑出玩家下棋的过程
			}
			else                           //2.位置已被占用
			{
				printf("该位置已被占用,请重新输入");
			}
		}
		else
		{
			printf("坐标非法,请重新输入:");
		}
	}
}

//电脑下棋
void ComMove(char board[ROW][COL], int row, int col)//可见,电脑和用户还是有很大区别的
{
	int x = 0;
	int y = 0;
	printf("电脑下棋->\n");
	//电脑下的位置是随机的  但是也要满足下棋的规则
	while (1)
	{
		x = rand() % row;//产生0-row-1的随机数
		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;//找不到空格,证明满了,就是平局
}

int IsWin(char board[ROW][COL], int row, int col)
{
	//先判断赢
	//1.连成一行
	int i = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] &&board[i][0]!=' ')
			return board[i][0];
	}
	//2.连成一列
	int j = 0;
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j]!=' ')
			return board[0][j];
	}
	//3.对角线
	//主对角线
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0]!=' ')
		return board[0][0];
	//次对角线
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2]!=' ')
		return board[0][2];

	//平局
	if (IsFull(board,row,col) == 1)
		return 'Q';
	//继续
	return 'C';
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
//这个文件是游戏实现的文件
//三子棋游戏的实现
#include "game.h"
//菜单函数
void menu()
{
	printf("******************\n");
	printf("*****1 ->play*****\n");
	printf("*****0 ->exit*****\n");
}

//游戏函数
void game()
{
	char board[ROW][COL] = {0};
	
	//1.初始化棋盘  存放数据 ->二维数组      先用空格代替
	InitBoard(board, ROW, COL);
	
	//2.打印棋盘
	DisplayBoard(board, ROW, COL);

	//3.下棋并判断胜负
	char ret = ' ';
	while (1)//用循环模拟对战环节    下一步棋,打印一次
	{
		PlayerMove(board, ROW, COL);//玩家下棋
		DisplayBoard(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
			break;

		ComMove(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");
	
}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do              
	{
		menu();//打印菜单
		scanf("%d",&input);
		//根据用户输入的内容来判断是否进行后续操作
		switch (input)
		{
			case 1:
				game();
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误,请重新输入");
		}
	} while (input); //只有输入0的时候才会推出循环(0为假)
	return 0;
}

游戏截图(让电脑一盘!)

 


http://lihuaxi.xjx100.cn/news/1115748.html

相关文章

物联网架构和技术:如何实现物物互联和智能化控制

第一章&#xff1a;引言 物联网是一种新兴的技术领域&#xff0c;通过将物理设备、传感器和软件等连接起来&#xff0c;可以实现设备之间的互联互通&#xff0c;让各种设备可以进行数据交换和智能化控制。在这个数字化时代&#xff0c;物联网已经成为了连接万物的关键技术之一…

一分钟图情论文:《国外智慧图书馆现状研究与启示》

一分钟图情论文&#xff1a;《国外智慧图书馆现状研究与启示》 智慧图书馆在中国经过十余年的发展仍然还处于探索和发展的初期阶段&#xff0c;国内主要还是以智慧图书馆的理论研究为主&#xff0c;较为缺乏理论和实践相结合的相关研究。东北师范大学的吴闯团队在《国外智慧图…

基于STM32+RC522设计的门禁系统

一、项目背景 门禁系统是现代社会中非常重要的安全控制系统之一,其功能是在保障建筑物安全的同时,为合法用户提供便利。当前设计一种基于STM32+RC522的门禁系统设计方案,通过RFID-RC522模块实现了对用户卡的注册、识别及身份验证,通过控制SG90舵机实现门锁的开关,具有较高…

Java之线程安全

目录 一.上节回顾 1.Thread类常见的属性 2.Thread类中的方法 二.多线程带来的风险 1.观察线程不安全的现象 三.造成线程不安全现象的原因 1.多个线程修改了同一个共享变量 2.线程是抢占式执行的 3.原子性 4.内存可见性 5.有序性 四.解决线程不安全问题 ---synchroni…

数据可视化工具 - ECharts折线图的编写

1 官网找到类似实例&#xff0c; 适当分析&#xff0c;并且引入到HTML页面中 <!DOCTYPE html> <html> <head><meta charset"utf-8"/><title>ECharts</title><!-- 引入刚刚下载的 ECharts 文件 --><script src"ech…

一、Flask入门介绍

Flask 入门介绍 Flask 入门介绍 Flask 是一个基于 Python 的轻量级 Web 框架&#xff0c;它简单易用、灵活性强、可扩展性好&#xff0c;因此成为 Python Web 开发中的热门框架之一。 Flask 概念 Flask 是一个 Web 框架&#xff0c;它提供了一系列工具和库&#xff0c;用于…

数组序列-统计分数段人数

目录 问题描述 程序设计 问题描述 【问题描述】输入一批学生(人数不超过100)的整数成绩,以10分为一个分数段,统计各分数段学生人数。 【输入形式】输入一批整数代表成绩,输入-1结束。 【输出形式】输出各分数段的人数。 【样例输入】67 78 66 89 90 -1 【样例输出】 …

Apache Zeppelin系列教程第四篇——Interpreter原理分析

Interpreter 其实就是整个项目的核心&#xff0c;代码运行都是在里面进行执行的&#xff0c;首先来看下Interpreter的抽象类 以jdbc-Interpreter为例&#xff0c;可以参考下测试代码(这个代码里面可以直接测试jdbc的sql执行过程和数据返回过程) 参数和介绍可以参考官方文档&am…