【C语言】都玩过三子棋游戏把,但你知道怎么用C语言实现三子棋游戏吗?让我来手把手教你。

news/2024/12/4 14:22:59/

三子棋游戏

  • 1.前言
  • 2.功能分析
    • 2.1主函数设计及菜单设计
    • 2.2打印棋盘与棋盘初始化
    • 2.3玩家下棋
    • 2.4电脑下棋
    • 2.5判断输赢
      • 2.5.1代码优化
  • 3.game.h头文件展示
  • 4.text.c源文件文件展示
  • 5.game.c源文件文件展示

所属专栏:C语言
博主首页:初阳785
代码托管:chuyang785
感谢大家的支持,您的点赞和关注是对我最大的支持!!!
博主也会更加的努力,创作出更优质的博文!!
关注我,关注我,关注我,重要的事情说三遍!!!!!!!!

1.前言

在写三子棋这个程序的时候我们不妨会用到很多的函数,我们之前也讲过了,当我们在设计一个比较复杂的程序的时候或者需要很多函数链接成的一个程序的时候,我们都会使用多个文件来编写,而每个文件的功能都是不一样的。以我们写的这个程序为例子。

  • text.c文件是我们的测试文件。
  • game.c文件是我们的函数实现文件。
  • game.h文件是我们的函数声明文件。

2.功能分析

2.1主函数设计及菜单设计

凡是我们写一个C程序,主函数是必不可少的,主函数就相当于我们程序的入口一样。
我们玩游戏肯定==不止就玩一次,而是想玩完一把再玩一把,这个时候我们就可以用到我们之前学的do……while循环,这个循环的特点是至少会循环一次,==刚好符合我们的需求。而我们选择是否玩游戏可用到我们的switch……case语句,输入1玩游戏,输入0退出游戏,输入出这两个数以外的数就重新输入。于是我们就可以这样设计:

int main()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1://选择1就就进入game玩游戏。printf("开始游戏\n\n");game();break;case 0://选择0就退出游戏printf("退出游戏\n");break;printf("选择错误,请重新选择:>");default:break;}} while (input);return 0;
}

既然我们要玩游戏,我们的游戏界面肯定不能少,有了界面玩家才知道怎么开始怎么结束游戏。
所以这里我们就简单的设计一个界面,里面包含了开始信息和结束信息。

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

效果展示:
在这里插入图片描述

2.2打印棋盘与棋盘初始化

我们三子棋又称井字棋,顾名思义他的棋盘像一个井字,所以我们不妨就设计出一个井字棋盘。
所以我们想办法设计出这样的棋盘:
在这里插入图片描述
这里我们看到了我们设计出的棋盘有三行三列,也就是说我们需要创建一个二维数组。
但是如果我们直接写成char board[3][3] = { 0 };的话,如果我们想玩的是四子棋的话,在用到二维数组的地方我们都要改数组的大小,于是我们就想到了用宏定义

#define ROW 3
#define COL 3

这样子如果我们想要玩的是四子棋,只需要改宏定义里面的数字就行了。
比如:
在这里插入图片描述

这里我们看到刚开始每个格子上都是空白的,也就是说我们棋盘刚开始初始化的是空格,毕竟空格是看不到的。
所以我们就知道了,我们的棋盘都初始化成空格。

void InitBoard(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++){board[i][j] = ' ';//全部都初始化成空格。}}
}

在打印我们的棋盘的时候我们可以把我们的棋盘分解一下,有助于我们更好的打印出我们棋盘。
我们观察一下我们的棋盘我们可以将其拆分成:
在这里插入图片描述
在这里插入图片描述
拆分成数据区和划分区:

  • 第一个区域就是我们后面要下棋的地方,而我们的第二个区域是用来分割我们的棋盘的。
  • 而我们仔细观察到我们的(|)总是比我们的棋盘的列少一,也就是说我们的棋盘是三列但是我们的(|)只有2个。
  • 而我们的(—)是有多少列就有多少个,但是我们的的(—)却总是比少一行于是我们就把井字棋划分一下打印:
    在这里插入图片描述
    一个框一个循环,而每个循环里面又套一个循环,这就是我们经典的双循环结构:
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");}
}

2.3玩家下棋

我们玩家下棋就只需要数组我么想要下棋的地方的坐标就行了。
但是我们要注意的三点点就是:

  1. 在我们的编程中我们数组的下标是从0开始的,但是在我们的日常生活中我们都是认为下标是从1开始的所以我们这里就要注意了。
  2. 还有就是我们下棋的地方必须是下在空格处,不能再已经下过棋子的地方再次下棋,同时我们给定下标也不能是超出棋盘范围的数。
  3. 如果我们输入了相同的坐标即输入的坐标已经下子了我们就要重新输入,所以还用设计一个循环结构。

同时我们给定玩家下的棋子是 ‘*’。

void PlayMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1) //这里用来一个while循环用来循环输入了相同的坐标后重新输入{printf("请输入坐标,中间以空格隔开:");scanf("%d %d", &x, &y);//判断是否超出棋盘的范围if (x >= 1 && x <= ROW && y >= 1 && y <= COL){if (board[x - 1][y - 1] == ' ')//我们输入的坐标都是比数组下标大1的所以这里我么们要减去。{board[x - 1][y - 1] = '*';break;}else{printf("该坐标已被占用,请重新输入\n");}}else{printf("输入错误,请重新输入\n");}}
}

2.4电脑下棋

电脑下棋的话我们要分析的主要有三点:

  1. 就是生成随机坐标。
  2. 生成随即坐标不能是相同的而且生成的坐标要是棋盘范围内的。
  3. 如果生成了相同的随机坐标即生成的随机坐标已经有子了就要重新生成随机坐标,这里也需要一个循环结构。
  • 而生成随机坐标我们之前也学过了,用生成随值函数rand(),而rand总是与srand搭配的。
  • 而我们的数组的下标是0-row和0-col,所以我们只需要用rand()%row和rand()%col就可以得到我们想要的下标范围了。

根据我们的分析我们就要可以写出一下代码:

void ComputerMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1)//循环防止生成相同的随机坐标{//生成随机坐标x = rand() % ROW;y = rand() % COL;//判断是空格就下子if (board[x][y] == ' '){board[x][y] = '#';break;}}
}

2.5判断输赢

判断谁赢了的方式有三种:

  1. 有一行都是一方的棋子。
  2. 有一列都是一方的棋子。
  3. 有一对角线都是一方的棋子。

而我们棋盘的状态有四种:

  1. 玩家赢了,结束。
  2. 电脑赢了,结束。
  3. 谁都还没赢,继续。
  4. 谁都没赢但是棋盘布满了,平局。

这是种状态我们分别用不同的返回值判断:

  1. 玩家赢了,结束,返回 ‘*’。
  2. 电脑赢了,结束,返回 ‘#’。
  3. 谁都还没赢,继续,返回 ‘C’。
  4. 谁都没赢但是棋盘布满了,平局,返回 ‘Q’。

所以我们就可以这样写:

int IsFull(char board[ROW][COL])
{int i = 0,j = 0;for (i = 0; i < ROW; i++){for (j = 0; j < COL; j++){if (board[i][j] == ' ')return 0;}}return 1;
}
charWhoIsWin(char board[ROW][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][0] != ' ')//行{return board[i][0];}}for (i = 0; i < COL; i++){if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')//列{return board[0][i];}}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) == 1)return 'Q';return 'C';
}

这里我们注意的是上述代码中我们并没有明摆着返回 ’ * ’ 或者 ‘#’ 而是返回的数组的值,这里其实用到了一点小技巧,就是我们以来就是说

玩家赢了返回 ’ * ’ 电脑赢了返回 ‘#’ 而刚好我们玩家下的棋子也是 ’*‘ 电脑下的棋子也是 ’#‘ 所以我们只需要返回数组的元素就行了。

2.5.1代码优化

但是这样写也有局限性,那就是当我们玩的不是三子棋的时候而是玩当四子棋的时候这个就不适用了,所以我们可以将我们的代码升级一下,如以下代码:

char WhoIsWin(char board[ROW][COL], int row, int col)
{char ret = 0;//判断行int i = 0;for (i = 0; i < row; i++){int flag = 0;int j = 0;for (j = 0; j < col-1; j++){if (board[i][j] == board[i][j + 1] && board[i][j] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[i][j];}}//判断列for (i = 0; i < col; i++){int flag = 0;int j = 0;for (j = 0; j < row-1; j++){if (board[j][i] == board[j + 1][i] && board[j][i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[j][i];}}//判断对角int flag = 0;for (i = 0; i < row-1; i++){if (board[i][i] == board[i+1][i+1] && board[i][i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1)return board[i][i];for (i = 0; i < row-1; i++){if (board[i][col - 1 - i] == board[i + 1][col - 1 - (i + 1)] && board[i][col - 1 - i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1)return board[i][col - 1 - i];//判断是否布满for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' '){flag = 1;break;}}}if (flag == 0)return 'Q';return 'C';
}

试玩展示:
在这里插入图片描述

3.game.h头文件展示

#pragma once
#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 PlayMove(char board[ROW][COL], int row, int col);
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//判断输赢
char WhoIsWin(char board[ROW][COL], int row, int col);

4.text.c源文件文件展示

void menu()
{printf("***********************\n");printf("******** 1.play *******\n");printf("******** 0.exit *******\n");printf("***********************\n");
}
void game()
{char ret = 0;//初始化棋盘char board[ROW][COL] = { 0 };InitBoard(board,ROW,COL);//打印棋盘DisplayBoard(board, ROW, COL);while (1){//玩家下棋PlayMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断输赢ret=WhoIsWin(board, ROW, COL);if (ret != 'C')break;//电脑下棋ComputerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断输赢ret = WhoIsWin(board, ROW, COL);if (ret != 'C')break;}if (ret == '*')printf("玩家赢\n");else if (ret == '#')printf("电脑赢\n");elseprintf("平局\n");}
int main()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("开始游戏\n\n");game();break;case 0:printf("退出游戏\n");break;printf("选择错误,请重新选择:>");default:break;}} while (input);return 0;
}

5.game.c源文件文件展示

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"//棋盘初始化
void InitBoard(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++){board[i][j] = ' ';}}
}//打印棋盘
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 PlayMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;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] = '*';break;}else{printf("该坐标已被占用,请重新输入\n");}}else{printf("输入错误,请重新输入\n");}}
}//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1){x = rand() % ROW;y = rand() % COL;if (board[x][y] == ' '){board[x][y] = '#';break;}}
}//判断输赢
char WhoIsWin(char board[ROW][COL], int row, int col)
{char ret = 0;//判断行int i = 0;for (i = 0; i < row; i++){int flag = 0;int j = 0;for (j = 0; j < col-1; j++){if (board[i][j] == board[i][j + 1] && board[i][j] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[i][j];}}//判断列for (i = 0; i < col; i++){int flag = 0;int j = 0;for (j = 0; j < row-1; j++){if (board[j][i] == board[j + 1][i] && board[j][i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[j][i];}}//判断对角int flag = 0;for (i = 0; i < row-1; i++){if (board[i][i] == board[i+1][i+1] && board[i][i] != ' '){flag = 1;}else{flag = 0;break;}}{if (flag == 1)return board[i][i];for (i = 0; i < row-1; i++){if (board[i][col - 1 - i] == board[i + 1][col - 1 - (i + 1)] && board[i][col - 1 - i] != ' '){flag = 1;}elseflag = 0;break;}}if (flag == 1)return board[i][col - 1 - i];//判断是否布满for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' '){flag = 1;break;}}}if (flag == 0)return 'Q';return 'C';
}

http://www.ppmy.cn/news/64424.html

相关文章

OBCP考点总结二

11.转储相关参数 minor_freeze_times n 控制两次合并之间的转储次数&#xff0c;达到此次数则自动触发合并(Major Freeze) n 设置为 0表示关闭转储&#xff0c;即每次租户MemStore使用率达到冻结阈值(freeze_trigger_percentage)都直接触发集群合并 minor_merge_concurrency…

Oracle配置方案-RAC

基本的Oracle RAC配置方案的具体步骤&#xff1a; 硬件准备&#xff1a; 确保服务器满足Oracle RAC的硬件要求&#xff0c;包括适当的CPU、内存和存储配置。配置共享存储&#xff1a;使用共享存储&#xff08;如SAN或NAS&#xff09;来存储数据库文件&#xff0c;确保所有节点都…

图片处理软件:分享6款非常实用的图片处理工具

目录 一、移动端 1、snapseed 2、一键抠图 3、pixlr 二、电脑端 1、图片编辑助手 2.GIMP 3、photopea 今天给大家分享6款非常实用的图片处理工具&#xff0c;其中包含移动端和电脑端&#xff0c;每一款都非常实用&#xff0c;希望对大家能有所帮助&#xff01; 一、移…

linux系统挂载硬盘

linux系统挂载硬盘 1、背景2、环境3、准备工作4、挂载分区4.1、查看分区信息4.2、创建分区4.3、设置分区格式4.4、创建挂载目录4.5、挂载分区4.6、设置开机自动挂载4.7、验证是否挂载成功 1、背景 日常使用过程中随着系统业务量的新增对磁盘的空间和性能提出了更高的要求&…

美句分享~程序员的放松时间~

愿努力生活的人&#xff0c;各有各的风雨灿烂。 生活就是生动的活着&#xff0c;用最美的心情过好自己的日子&#xff0c;如此甚好&#xff01; 生活百味&#xff0c;及时清醒也事事甘心。 每一个失眠的日子&#xff0c;多回头看看&#xff0c;细节全是答案。 凡是好的东西…

linux命令之crontab详解

crontab 提交和管理用户的需要周期性执行的任务 更多linux命令详解&#xff1a;linux命令在线工具 补充说明 crontab命令 被用来提交和管理用户的需要周期性执行的任务&#xff0c;与windows下的计划任务类似&#xff0c;当安装完成操作系统后&#xff0c;默认会安装此服务工…

R语言 | 认识apply家族

目录 一、apply()函数 二、sapply()函数 三、lapply()函数 四、tapply()函数 ​五、iris鸢尾花数据集 R语言提供了一个循环系统称apply家族&#xff0c;它具有类似for循环的功能&#xff0c;但是若想处理相同问题&#xff0c;apply家族函数好用太多了。 一、apply()函数 …

神奇,声网Web SDK还能这么实现直播中美颜功能

前言 本篇文章是通过使用声网Web SDK来实现直播中美颜效果的深度体验文章&#xff0c;其中发现了屏幕共享并本地合图多个视频、图片&#xff0c;声网美颜插件等功能特性十分强大和专业&#xff0c;特为此做一个技术体验的分享&#xff0c;毕竟好技术就是要去传播的。 随着音视…