前言
三子棋是语言学习前期中较为综合的一次思维训练,会大量的用到循环、判断语句,以及各种函数嵌套调用,能够很好的训练学习者对知识的掌握与运用。
问题描述
《三子棋》是一款古老的民间传统游戏,又被称为井字棋、九宫棋等。游戏分为双方对战,双方依次在九宫格棋盘上摆放棋子,率先将自己的三个棋子连成一条线(横向、纵向,对角)的一方则视为胜利者。
步骤分工
-
游戏菜单:玩游戏和退出游戏
-
游玩时:模式选择、棋盘的创建、哪方先手、如何落子、如何判断胜负及和棋
在我们完成一个完整庞大的项目时,明确步骤分工是十分重要的,在我们进行步骤分工时,就可以初步在脑中构建出整体逻辑,初步确定所需的函数(菜单函数、模式选择函数、游玩函数等),避免敲代码时步骤混乱无章,并思考我们是否能够实现步骤操作?如果不能,如果是逻辑问题?具体代码实现时画图尝试解决,如果是知识问题?那么还需要什么知识?目前的语言是否能够有效解决?
具体步骤实现
注意:<具体步骤实现>中展示的代码是逻辑代码,后期我们为了三子棋游玩时的美观性、可玩性进行了一些细节补充,会在<具体代码展示>部分进行注释讲解。
游戏菜单
这部分没什么好说的,如果想要更加美观可以自行用 ‘-’ ‘_’ ‘——’ ‘=’ ‘+’ ‘|’,等符号进行修改,这里我就懒了。
void menu()
{
printf("****************************\n");
printf("***** 1、玩游戏 2、退出 *****\n");
printf("****************************\n");
}
有了菜单之后就会进行选择,选择时需要注意选择错误情况,进行提示处理
menu();
printf("请进行选择>>>");
int input = 0;
scanf("%d", &input);
if (input != 1 && input != 2)
{
printf("选择错误,请重新选择\n");
continue;//外层是一个while(1)大循环
}
菜单的两种选择我们用switch进行分类
switch (input)
{
case 1:
game_play();
break;
case 2:
printf("退出成功!\n");
return 0;//直接结束程序
}
游玩
这里是游玩部分的外层函数,分别进入人机模式函数和真人对战函数
void game_play()
{
//选择人机模式或者真人对战
int input = game_mode();
switch (input)
{
case 1:
man_machine();
break;
case 2:
man_man();
break;
}
}
模式选择
同样需要注意选择错误的情况
后面我分析的是人机模式,真人模式与之很类似,希望读者自行在<具体代码展示>中学习
int game_mode()
{
while (1)
{
int input = 0;
printf("********************\n");
printf("**** 1、人机对战 ****\n");
printf("**** 2、真人对战 ****\n");
printf("********************\n");
printf("请选择游戏模式>>>");
scanf("%d", &input);
if (input != 1 && input != 2)
{
printf("选择错误,请重新选择\n");
continue;
}
switch (input)
{
case 1:
return 1;
case 2:
return 2;
}
}
}
棋盘处理
这里的棋盘与前面的菜单不同,棋盘需要改变,改变的内容就是我们下的棋子的位置,因此在这个九宫格中的九个位置都用 %c占位置,棋盘的初始化状态也就是arr[ i ][ j ]中存储的都是 ‘空格符’,当我们后面进行落子时,棋盘某个位置的arr[ i ][ j ]发生改变,就会再次使用这个棋盘函数,做到更新棋盘。
void chessboard(char arr[3][3])//棋盘
{
printf("|-----------------|\n");
printf("| %c | %c | %c |\n", arr[0][0], arr[0][1], arr[0][2]);
printf("|-----------------|\n");
printf("| %c | %c | %c |\n", arr[1][0], arr[1][1], arr[1][2]);
printf("|-----------------|\n");
printf("| %c | %c | %c |\n", arr[2][0], arr[2][1], arr[2][2]);
printf("|-----------------|\n");
}
先手问题
谁先手的问题,我这里使用了产生伪随机数的rand()函数,并在main()主函数中用srand()得到随机数种子,使得rand做到真正随机(具体srand的处理在<整体代码展示>部分能够看到具体分析)
回到先手问题,rand()能够得到0 ~ RAND_MAX(32767)之间的整数,我们用rand() 模上2,就能够得到0 和 1 的随机数,我这里设计是: 0 - 玩家先手 ,1 - 电脑先手
并与后面代码相配合使用input变量(这里展示的先手问题是人机对战的先手问题,如果是真人对战,只需要改成"玩家一先手",“玩家二先手”,input依旧存储 0 和 1)
int input = rand() % 2;//得到随机的0和1
if (input == 0)
{
printf("玩家先手\n");
}
else
{
printf("电脑先手\n");
}
落子
落子的具体代码也就是给一个三乘三的二维数组中的某一个元素进行赋值,这个操作就是落子,落子之后,就需要调用棋盘函数,将棋盘进行更新,但是我们赋值是否合法,如何进行循环落子,就需要分析实现。
玩家的落子逻辑是玩家在电脑上输入行和列进行落子,而电脑是通过随机数的方式进行落子,因此需要分别分析。
并且玩家和电脑落子是依次循环执行的,所以先手问题中的input变量,在这里发挥了作用,如果前面先手中拿到input == 0,那么就是玩家先落子,在玩家落子后将input赋值为1,循环中就到了电脑落子,电脑落子后,将input赋值为0,实现循环落子。
玩家落子
玩家的落子我设定为 * (星号)
用 i 和 j 定义行和列,注意,我们落子的位置需要合法,第一,落子处原来无子(为空格符),第二,落子处在九宫格内,不能 i 和 j 大于3或者小于0了。
并且玩家落子,不是依照C语言中下标为0开始的,需要按照人的逻辑,例如 1 1 是第一行第一列,2 3 是第二行第三列,因此,在判断合法时、落子阶段需要 i–,j-- 去符合C语言的下标要求。
落子后更新棋盘,input赋值为1
int i = 1;
int j = 1;
if (input == 0)
{
printf("请选择您的下棋位置>>>");
scanf("%d %d", &i, &j);
i -= 1;
j -= 1;
if (arr[i][j] == ' ' && i >= 0 && i <= 3 && j >= 0 && j <= 3)
{
arr[i][j] = '*';
chessboard(arr);
}
else
{
printf("选择错误,请重新选择\n");
continue;
}
input = 1;
电脑落子
电脑落子我设定为 # (井号)
电脑落子就不需要考虑人的思维了,直接用 rand() % 3,随机生成下棋位置,然后进行合法性判断,我们用了 %3,因此就不存在 i 和 j 小于0大于3的情况,只需要考虑落子处是否为空格符,如果合法的话,这里我还打印出 i + 1 和 j + 1,来模拟人的scanf操作
落子后更新棋盘,input赋值为0
i = rand() % 3;
j = rand() % 3;
if (arr[i][j] == ' ')
{
printf("电脑选择下棋位置>>>");
printf("%d %d\n", i + 1, j + 1);
arr[i][j] = '#';
chessboard(arr);
}
else
{
continue;
}
input = 0;
判断胜负及和棋
胜负
我这里处理胜负的方式比较粗糙,但是逻辑简单,应该还会有更好的方式,如果我找到了的话,或者评论区有更好的实现方式的话,我会更新这里的代码的。
我们在if中分别判断:
(行是否都相等,并且其中一个是 * (星号)) 或者
(列是否都相等,并且其中一个是 * (星号)) 或者
(对角是否相等,并且其中一个是 * (星号))
用这种方式判断玩家胜
同理可以用这种方式判断电脑胜
int k = 0;
while (k < 3)
{
if (
(arr[k][0] == arr[k][1] && arr[k][1] == arr[k][2] && arr[k][0] == '*') ||
(arr[0][k] == arr[1][k] && arr[1][k] == arr[2][k] && arr[0][k] == '*') ||
(arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] == '*') ||
(arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[0][2] == '*')
)
{
printf("玩家获胜!!!\n\n\n");
}
if (
(arr[k][0] == arr[k][1] && arr[k][1] == arr[k][2] && arr[k][0] == '#') ||
(arr[0][k] == arr[1][k] && arr[1][k] == arr[2][k] && arr[0][k] == '#') ||
(arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] == '#') ||
(arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[0][2] == '#')
)
{
printf("人机获胜!!!\n\n\n");
}
k++;
}
和棋
和棋的判断我是通过判断 三乘三的二维数组 中元素是否为空格符进行判断。
我用了count计数器,当arr[m][n]中不为空格符时,计数器++,如果count最后为9时,即棋盘下满,和棋
注意这里和棋判断是在胜负判断之后,因为存在最后一步棋 占满了棋盘 并且分出了胜负的情况。
int m = 0;
int count = 0;
for (m = 0; m < 3; m++)
{
int n = 0;
for (n = 0; n < 3; n++)
{
if (arr[m][n] != ' ')
{
count++;
}
}
}
if (count == 9)
{
printf("平局!!!\n\n\n");
}
整体代码展示
代码的话我是分成了三个文件:
第一个是头文件chess.h 用于存放所有所需函数的声明,以及所需的头文件(stdio.h stdlib.h…)
第二个是函数文件chess.c用于具体函数的实现
第三个是主函数文件,也就是将所有函数进行组装在一起
chess.h文件
//chess.h文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
void menu();//主菜单
int game_mode();//模式选择
void game_play();//游玩
void man_machine();//人机
void man_man();//真人
void chessboard(char arr[3][3]);//棋盘
chess.c文件
//chess.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"chess.h"
void menu()
{
printf("****************************\n");
printf("**** 1、玩游戏 2、退出 ****\n");
printf("****************************\n");
}
int game_mode()
{
while (1)
{
int input = 0;
printf("****************************\n");
printf("**** 1、人机对战 ****\n");
printf("**** 2、真人对战 ****\n");
printf("****************************\n");
printf("请选择游戏模式>>>");
scanf("%d", &input);
if (input != 1 && input != 2)
{
printf("选择错误,请重新选择\n");
continue;
}
switch (input)
{
case 1:
return 1;
case 2:
return 2;
}
}
}
void chessboard(char arr[3][3])//棋盘
{
printf("|-----------------|\n");
printf("| %c | %c | %c |\n", arr[0][0], arr[0][1], arr[0][2]);
printf("|-----------------|\n");
printf("| %c | %c | %c |\n", arr[1][0], arr[1][1], arr[1][2]);
printf("|-----------------|\n");
printf("| %c | %c | %c |\n", arr[2][0], arr[2][1], arr[2][2]);
printf("|-----------------|\n");
}
void man_machine()//人机
{
char arr[3][3] = { {' ', ' ', ' '}, {' ',' ',' '}, {' ',' ',' '} };
chessboard(arr);
// 创建棋盘
// 选择一方开始
int input = rand() % 2;//得到随机的0和1
if (input == 0)
{
printf("玩家先手\n");
}
else
{
printf("电脑先手\n");
}
while (1)
{
int i = 1;
int j = 1;
if (input == 0)
{
printf("请选择您的下棋位置>>>");
scanf("%d %d", &i, &j);
i -= 1;
j -= 1;
if (arr[i][j] == ' ' && i >= 0 && i <= 3 && j >= 0 && j <= 3)
{
arr[i][j] = '*';
chessboard(arr);
input = 1;
}
else
{
printf("选择错误,请重新选择\n");
continue;
}
}
else
{
Sleep(1000);
i = rand() % 3;
j = rand() % 3;
if (arr[i][j] == ' ')
{
printf("电脑选择下棋位置>>>");
printf("%d %d\n", i + 1, j + 1);
arr[i][j] = '#';
chessboard(arr);
input = 0;
}
else
{
continue;
}
}
// 判断胜负
int k = 0;
while (k < 3)
{
if (
(arr[k][0] == arr[k][1] && arr[k][1] == arr[k][2] && arr[k][0] == '*') ||
(arr[0][k] == arr[1][k] && arr[1][k] == arr[2][k] && arr[0][k] == '*') ||
(arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] == '*') ||
(arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[0][2] == '*')
)
{
printf("玩家获胜!!!\n\n\n");
goto FINISH;// 这里获胜之后需要直接跳出两层循环,因此用goto语句实现跳出
}
if (
(arr[k][0] == arr[k][1] && arr[k][1] == arr[k][2] && arr[k][0] == '#') ||
(arr[0][k] == arr[1][k] && arr[1][k] == arr[2][k] && arr[0][k] == '#') ||
(arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] == '#') ||
(arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[0][2] == '#')
)
{
printf("人机获胜!!!\n\n\n");
goto FINISH;// 道理同上
}
k++;
}
int m = 0;
int count = 0;
for (m = 0; m < 3; m++)
{
int n = 0;
for (n = 0; n < 3; n++)
{
if (arr[m][n] != ' ')
{
count++;
}
}
}
if (count == 9)
{
printf("平局!!!\n\n\n");
goto FINISH;// 同上
}
}
FINISH:
;
}
void man_man()//真人
{
char arr[3][3] = { {' ', ' ', ' '}, {' ',' ',' '}, {' ',' ',' '} };
chessboard(arr);
// 创建棋盘
// 选择一方开始
int input = rand() % 2;//得到随机的0和1
if (input == 0)
{
printf("玩家一先手\n");
}
else
{
printf("玩家二先手\n");
}
while (1)
{
int i = 1;
int j = 1;
if (input == 0)
{
printf("玩家一,请选择您的下棋位置>>>");
scanf("%d %d", &i, &j);
i -= 1;
j -= 1;
if (arr[i][j] == ' ' && i >= 0 && i <= 3 && j >= 0 && j <= 3)
{
arr[i][j] = '*';
chessboard(arr);
input = 1;
}
else
{
printf("选择错误,请重新选择\n");
continue;
};
}
else
{
printf("玩家二,请选择您的下棋位置>>>");
scanf("%d %d", &i, &j);
i--, j--;
if (arr[i][j] == ' ' && i >= 0 && i <= 3 && j >= 0 && j <= 3)
{
arr[i][j] = '#';
chessboard(arr);
input = 0;
}
else
{
printf("选择错误,请重新选择\n");
continue;
}
}
// 判断胜负
int k = 0;
while (k < 3)
{
if (
(arr[k][0] == arr[k][1] && arr[k][1] == arr[k][2] && arr[k][0] == '*') ||
(arr[0][k] == arr[1][k] && arr[1][k] == arr[2][k] && arr[0][k] == '*') ||
(arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] == '*') ||
(arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[0][2] == '*')
)
{
printf("玩家一获胜!!!\n\n\n");
goto FINISH;
}
if (
(arr[k][0] == arr[k][1] && arr[k][1] == arr[k][2] && arr[k][0] == '#') ||
(arr[0][k] == arr[1][k] && arr[1][k] == arr[2][k] && arr[0][k] == '#') ||
(arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] == '#') ||
(arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[0][2] == '#')
)
{
printf("玩家二获胜!!!\n\n\n");
goto FINISH;
}
k++;
}
int m = 0;
int count = 0;
for (m = 0; m < 3; m++)
{
int n = 0;
for (n = 0; n < 3; n++)
{
if (arr[m][n] != ' ')
{
count++;
}
}
}
if (count == 9)
{
printf("平局!!!\n\n\n");
goto FINISH;
}
}
FINISH:
;
}
void game_play()
{
// 选择人机模式或者真人对战
int input = game_mode();
switch (input)
{
case 1:
system("cls");// 在我们选择模式之后,将选择页面进行清除,system是stdlib的函数
man_machine();
break;
case 2:
system("cls");// 同上
man_man();
break;
}
}
main.c文件
//main.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"chess.h"
//#include"chess.h"就相当于我们引用头文件,只不过此时引用的头文件是我们自己写的,我们写的
//chess.h中有所有函数的声明,有了这些声明,我们就可以在main.c文件中使用这些函数(函数在chess.c中已经实现的情况下使用)
int main()
{
while (1)
{
menu();
printf("请进行选择>>>");
int input = 0;
scanf("%d", &input);
if (input != 1 && input != 2)
{
printf("选择错误,请重新选择\n");
continue;
}
switch (input)
{
case 1:
system("cls");// 选择了玩游戏后,清屏菜单
srand((unsigned)time(NULL));
// 我们为了得到一个随机数,需要给伪随机数设定种子
// 但是如果种子不发生变化,那么还是伪随机数
// 因此,我们用一个随时都在发生变化的数 - 时间
// 涉及到time函数得到的返回值是time_t类型的,我们需要强制类型转换为unsigned类型
game_play();
Sleep(5000);// 完成一次游玩后,在胜利页面停留5000ms,让玩家感受胜利的喜悦和失败的悲伤哈哈哈哈
system("cls");// 之后就会清屏
break;// break离开switch,进入下一次循环,来到菜单页面
case 2:
printf("退出成功!\n");
// 如果退出成功,那么我们就直接return 0
// 一个函数只能有一个return,这是无容置疑的
// 但是我们可以在函数的不同路径(条件下),中设定多个return
return 0;
}
}
return 0;
}