鹏哥C语言复习——三子棋

2024-02-29 22:30
文章标签 语言 复习 三子 鹏哥

本文主要是介绍鹏哥C语言复习——三子棋,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一.三子棋介绍

二.代码思路

三.开始菜单

四.游戏实现

五.输赢判断

六.游戏优化(更多功能)


一.三子棋介绍

三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战,双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子走成一条线就视为胜利,而对方就算输了,但是三子棋在很多时候会出现和棋的局面。

二.代码思路

首先创建三个文件,分别是:

test.c //测试游戏的逻辑

game.c //游戏代码的实现    game.h //游戏代码的声明(函数声明,符号定义)

上述内容详细介绍请看本文:函数基础

要在游戏中实现以下几个目标:

开始菜单➡游戏实现➡输赢判断➡游戏优化(更多功能)

三.开始菜单

在开始菜单中要完成三个功能,分别是退出游戏,开始游戏和打印菜单

菜单打印自定义一个函数,代码如下:

 void menu() {printf("**************************\n");printf("*****  1.play 0.exit *****\n");printf("**************************\n");}
//在test.c文件中

开始和退出游戏使用do-while语句(至少循环一次),退出条件可以是玩家输入值为0,代码如下:

    int main() {int input;do {menu();printf("请选择:>");scanf("%d", &input);switch (input) {case 1:game();break;case 0:printf("退出游戏");break;default : printf("输入错误");break;}} while (input);return 0;}
//在test.c文件中

使用switch语句来打印给玩家的提示,或者正式进入游戏

四.游戏实现

游戏实现要分为两步:1.棋盘打印与棋盘初始化、2.玩家和电脑比拼

1.棋盘打印与初始化:

在game.h文件中声明一个函数Initboard,在game.c文件定义该函数,实现棋盘的初始化,在test.c文件中使用该函数需要的棋盘效果如下图:

为实现该效果,可先定义一个二维数组,并在数组中赋初值空格(一定要,要不然就都是随机数了),并且每一个地址都要赋空格(一定要,要不然除了赋空格的地址以外其他位置初始化为0),并把该二维数组作为后续电脑与玩家对战的平台

定义完二维数组后,开始初始化整个棋盘,通过上图不难发现,要打印的棋盘是三行三列,即自定义函数中在使用时为行为3、列为3,并且是一个三行三列的二维数组。

void InitBoard(char board[3][3], 3, 3)

在三个文件中都需要用到row和col,并且都为3;同时,如果有扩大或缩小棋盘的需要时,需要把整个游戏项目的row和col都改掉,工作量较大,比较繁琐,因此我们可以在game.h中定义两个宏,日后只需更改宏就能完成缩小或扩大,如下所示:

#define ROW 3
#define COL 3
//在game.h文件中

那么上述的InitBoard就可以换成char board[ROW][COL], ROW, COL,同时由于是在game.h中定义了宏,所以需要在test.c中引用

#include"game.h"void InitBoard(char board[ROW][COL], ROW, COL)
//在test.c文件中

现在开始初始化棋盘,此处是在自定义一个函数,因此需要定义row,col两个整型变量形参,board[ROW][COL]由于数组的特性,因此只需要在数组名前面加上char即可(以后会和指针与函数一起讲),如下代码是二维数组赋值的普遍操作

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] = ' ';}}
}
//在game.c文件中

又因为game.c文件中也使用了宏,因此也要加上#include"game.h"语句,同时在game.h文件中声明void InitBoard(char board[ROW][COL], int row, int col) 

初始化棋盘以后,开始打印棋盘的操作,还是在game.c文件中实现功能,在game.h文件中声明,在test.c文件中使用

由于test.c文件和game.c文件都需要有打印操作,所以可以在game.h文件中引用stdio.h这一头文件,减少代码量

按照上图的棋盘,不难发现棋盘即为

_空格_|_空格_|_空格_

---------|----------|---------

这种形式,因此代码如下:

void DisplayBoard(char board[ROW][COL],int row,int col)
{for(int i=0;i<row;i++){for(int j=0;j<col;j++){printf(" %c ",board[i][j]);if(j<col-1)  //最后一列不打印printf("|");}printf("\n"); //一行打完换一行if(i<row-1){  //最后一行不打印for(int j=0;j<col;j++){printf("---");if(j<col-1)printf("|");}printf("\n");}}
}
//在game.c文件中

tip:如上代码就可以随意修改棋盘大小,只需要更改宏值即可

2.电脑和玩家对战:

该游戏是电脑下一步棋,玩家下一步棋,笔者这边假定了玩家为先手,则代码如下:

while (1) {PlayMove(board[ROW][COL], ROW, COL); //玩家下棋//判断输赢(在输赢已分后跳出循环)DisplayBoard(board[ROW][COL], ROW, COL); //下完棋后的棋盘ComputerMove(board[ROW][COL], ROW, COL); //电脑下棋//判断输赢DisplayBoard(board[ROW][COL], ROW, COL); //下完棋后的棋盘
}
//在test.c文件中

注:函数声明、定义和使用笔者在此不再进行解释

玩家下棋函数(这里为了便于玩家理解与游玩,输入值可以是1~3,对应数组下标0~2)

void PlayMove(board[ROW][COL], int row, int col) {int x = 0, y = 0;printf("玩家下棋:>\n");while (1) {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] = '*'; //玩家棋子为‘*’break;} elseprintf("坐标被占用,请重新输入\n");} else {printf("坐标非法,请重新输入\n") ;}}
}
//在game.c文件中

电脑下棋函数:

在讲解本函数前,首先需要知道srand()函数,rand()函数和time()函数

time()函数:

本函数即为时间戳,时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。time(NULL)的返回值即为这个总秒数,因此这个返回值无时无刻在变化。

注:本函数使用前需要引头文件<time.h>

rand()函数:

本函数是用来产生随机数的,但该随机数是个伪随机数,范围在0~32767。在调用本函数前,可以使用srand()函数设置随机数种子,如果没有设置,电脑自动设计随机数种子为1。随机数种子相同,那么产生随机数相同(恒为1)。

注:本函数使用前需要引头文件<stdlib.h>

srand()函数:

该函数需要一个无符号整数seed作为参数,这个seed在大小上没有要求,只是要求seed无时无刻在变化(不变的情况下,随机数种子相同,产生随机数相同),所以一般用time(NULL)作为该函数的参数。

注:本函数使用前需要引头文件<stdlib.h>

通过上述解释,电脑下棋的代码便水到渠成了

首先在test.c文件的main函数中,调用srand函数和time函数(设置随机数的生成起点and函数调用时也能使用)

int main()
{srand((unsigned)time(NULL));……
}
//在test.c文件中

头文件引用都在game.h中(只需要引用一次)

接下来就是实现电脑下棋的函数了

void ComputerMove(board[ROW][COL], int row, int col){printf("电脑下棋\n");int x = 0, y = 0;while (1) {x = rand() % row; // 0~2y = rand() % col; // 0~2if (board[x][y] =='') {board[x][y] == '#'; //电脑棋子为‘#’break;}}
}
//在test.c文件中

五.输赢判断

共有四种不同情况,分别是电脑赢,玩家赢,平局和继续游戏

因此我们可以定义一个字符型IsWin()函数,不同情况返回不同值,不同值打印不同结果,并将返回值作为循环语句的退出条件

电脑赢---'#'  平局---'Q’

玩家赢---'*'  继续---'C'

代码如下:

void game()
{……;while(1){……;ret = IsWin(board[ROW][COL], ROW, COL);if (ret != 'C') {break;}……;ret = IsWin(board[ROW][COL], ROW, COL);if (ret != 'C'){break;}……;if (ret == '*') {printf("玩家赢\n");}if (ret == '#'){printf("电脑赢\n");}else {printf("平局\n");}}

现在就可以把IsWin()函数分成两种不同情况,进行判断,分别是平局和谁赢谁输(如果两者都不是的话就在函数最后返回'C')

1.平局:

char IsWin(char board[ROW][COL],int row,int col)
{……;//输和赢的判断(谁输谁赢)if(IsFull(board,row,col)){return 'Q';}return 'C';  //继续游戏
}//满了返回1,不满返回0
int IsFull(char board[ROW][COL],int row,int col)
{for(int i=0;i<row;i++){for(int j=0;j<row;j++){if(board[i][j]==' ')return 0;}}return 1;
}

判断数组每个元素是不是,都不是空格则满,返回1

2.谁赢谁输:

在鹏哥C语言的视频里是使用了board[i][0]==board[i][1]&&board[i][1]==[i][2]&&board[i][1]!=' '的判断方法,不便于日后的优化(胜利条件更改),因此笔者在此修改了一下,代码如下:

int i=0,j=0;
for(i=0;i<=row-1;i++)  //行
{for(j=0;j<col-1;j++){if(board[i][j]!=board[i][j+1]||board[i][j]==' ')break;}if(j==col-1)return board[i][j];
}for(i=0;i<=col-1;i++)  //列
{for(j=0;j<row-1;j++){if(board[j][i]!=board[j+1][i]||board[j][i]==' ')break;}if(j==row-1)return board[j][i];
}//对角线
for(i=0;i<row-1;i++) 
{if(board[i][i]!=board[i+1][i+1]||board[i][i]==' ')break;if(i==row-2)return board[0][0];
}
for(i=0,j=col-1;i<row-1,j>0;i++,j--)
{if(board[i][j]!=board[i+1][j-1]||board[i][j]==' ')break;if(i==row-2&&j==1)return board[i][j]
}

具体思路即为一个个判断,不相等停止循环,查看是否循环到胜利棋子数量

注:笔者并未进行编译调试,如有错误,敬请指正

六.游戏优化(更多功能)

先提供可优化部分:

1.上述方法无法在扩大棋盘的同时,更换胜利条件(只有胜利条件所需要的棋子数和棋盘的行或列大小相同时才行)

tip:在行列与对角线判断外再加一句循环语句,遍历棋盘行列与对角线的数,行列循环语句i和j可以不从0开始,退出条件可以是胜利条件

2.上述代码一直都是玩家先手,可以通过rand()函数以及与之相配的函数来生产随机数,把这个随机数作为谁先手的判断

3.上述代码电脑的难度每次都不同,时难时简单,根据百度

一般来说,第二步下在中间最有利(因为第一步不能够下在中间),下在角上次之,下在边上再次之。

可以优化游戏,做出不同难度的三子棋游戏,再做出一个pvp的模式,最后在菜单中给出不同难度不同模式的选择,一款半成型的小游戏便完成了(还有图案、音乐等……)

4. .……

5.终于写完了!

更多:

扫雷游戏

这篇关于鹏哥C语言复习——三子棋的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/760312

相关文章

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

C 语言中enum枚举的定义和使用小结

《C语言中enum枚举的定义和使用小结》在C语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看... 目录1、引言2、基本定义3、定义枚举变量4、自定义枚举常量的值5、枚举与switch语句结合使用6、枚

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

C语言中的数据类型强制转换

《C语言中的数据类型强制转换》:本文主要介绍C语言中的数据类型强制转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C语言数据类型强制转换自动转换强制转换类型总结C语言数据类型强制转换强制类型转换:是通过类型转换运算来实现的,主要的数据类型转换分为自动转换

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

C语言实现两个变量值交换的三种方式

《C语言实现两个变量值交换的三种方式》两个变量值的交换是编程中最常见的问题之一,以下将介绍三种变量的交换方式,其中第一种方式是最常用也是最实用的,后两种方式一般只在特殊限制下使用,需要的朋友可以参考下... 目录1.使用临时变量(推荐)2.相加和相减的方式(值较大时可能丢失数据)3.按位异或运算1.使用临时

使用C语言实现交换整数的奇数位和偶数位

《使用C语言实现交换整数的奇数位和偶数位》在C语言中,要交换一个整数的二进制位中的奇数位和偶数位,重点需要理解位操作,当我们谈论二进制位的奇数位和偶数位时,我们是指从右到左数的位置,本文给大家介绍了使... 目录一、问题描述二、解决思路三、函数实现四、宏实现五、总结一、问题描述使用C语言代码实现:将一个整

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st