相信大家都知道五子棋的游戏规则和游戏布局,即当某方5个棋子连续(包括斜向)时,获得胜利,三子棋本质和五子棋相同,不过胜利条件变成了某方3个棋子连续(包括斜向)时,获得胜利。下面就让我们来做一个三子棋吧!加油噢!
目录
一:思路
1.首先我们需要登录游戏,输入账号密码,此功能的实现我们之前以前讲过(判断和循环(实战收尾篇2—猜数字游戏)),不再赘述,直接使用
2.接下来我们需要一个游戏菜单
3.然后我们需要一个棋盘
1.1棋盘的搭建需要一个打印一个井字表格
1.2需要一个二维数组,并将该数组放在打印的井字格中,便于数据的更改和添加。
4.棋盘有了,我们就要开始游戏了
4.1首先是玩家先手下棋
4.2其次是电脑的下棋,此处要用到随机数
5.每一方下完棋后,我们都需要再次打印棋盘,并判断正负,棋盘打印可直接调用第三步函数,我们接下来要实现的是判断输赢的函数。
该游戏我们使用多文件操作,下图是main.c中的主要功能实现思路
#include"main.h"
//main.c
int main()
{
//设置随机数
srand((unsigned long)time(NULL));
Log_In();
int input;
do
{
Menu();
printf("请做出你的选择>");
scanf("%d", &input);
switch (input)
{
case 0:
return;
break;
case 1:
Game();
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
return 0;
}
进入main函数后,首先设置随机数,为后续的使用做铺垫。然后调用登录函数,登录成功后进入do-while语句,首先调用菜单函数,然后就开始进入分支选择,输入1则进入Game函数,0退出,其它打印“选择错误,请重新选择!”并重新进入循环。
void Game()
{
//倒计时函数
My_Time();
int x=0, y=0;
printf("三子棋游戏开始!\n");
//设置棋盘
char my_board[ROW][COL];
//棋盘初始化
Init_Board(my_board,ROW,COL);
//打印棋盘
Print_Board(my_board,ROW,COL);
// //玩家下棋
while (1)
{
Player(my_board, ROW, COL,x,y);
Print_Board(my_board, ROW, COL);
//判断胜负
int ret = Is_Win(my_board,ROW,COL,x,y);
if (1 == ret)
{
printf("恭喜玩家获得胜利!\n");
break;
}
else if (0 == ret)
{
}
else
{
printf("平局\n");
break;
}
//计算机下棋
Computer(my_board, ROW, COL,x,y);
Print_Board(my_board, ROW, COL);
//判断胜负
ret = Is_Win(my_board, ROW, COL, x, y);
if (1 == ret)
{
printf("很遗憾,玩家失败!\n");
break;
}
else if (0 == ret)
{
}
else
{
printf("平局\n");
break;
}
}
return;
}
若输入1,进入Game函数后,首先调用倒计时函数,实现游戏开始的倒计时小功能,之后依次设置棋盘,初始化棋盘,打印棋盘,然后进入玩家下棋,下完后进行棋盘打印并通过胜负函数Is—_Win的返回值判断输赢,若玩家胜利或平局,结束游戏,若无事发生,则进入电脑下棋阶段,然后同样进行棋盘打印和输赢判断。
在了解大致思路后让我们来一点点的把它们具体实现吧。
二:具体实现
1.模拟登录
void Log_In()
{
char account[15]="\0";
char password[15] = "\0";
while (1)
{
printf("请输入账号和密码\n");
printf("账号>");
scanf("%s", account);
printf("密码>");
scanf("%s", password);
//检验账号密码的正确性
if (((strcmp(account, ACCOUNT)) == 0) && ((strcmp(password, PASSWORD)) == 0))
{
printf("登陆成功!\n");
break;
}
else
{
printf("账号或密码错误,请重新输入!\n");
}
}
return;
}
对于ACCOUNT(账号)和PASSWORD(密码)我们在头文件中会进行宏定义,以便后续的修改。
2.游戏菜单
void Menu()
{
printf("********** 欢迎来到三子棋游戏 **********\n");
printf("**** 1.Play 0.Exit ****\n");
printf("****************************************\n");
return;
}
3.棋盘的初始化
void Init_Board(char my_board[][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
my_board[i][j] = ' ';
}
}
}
将二维数组my_board各个元素均初始化为空格,以便于棋盘的打印,同时使得后续对数组的元素的更改可以同步反映到棋盘上,并在打印函数中打印出来。
4.棋盘的打印
void Print_Board(char my_board[][COL],int row,int col)
{
//刷新界面
system("cls");
//打印棋盘
for (int i=0;i<row;i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ",my_board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
if (i < row - 1)
{
for (j = 0; j < col; j++)
{
if (j < col - 1)
{
printf("---|");
}
else
{
printf("---");
}
}
}
printf("\n");
}
return;
}
每次调用打印函数前,首先进行清屏处理,增加游戏体验感,而"cls"就是清屏命令,通过windows.h头文件来引进命令行的使用,使用system(“cls”)进行清屏操作(即页面刷新)。
之后则利用for的嵌套和多个if语句来对棋盘进行具体打印,第一个for语句代表着行数的循环,之后进入每行的具体打印
我们打印的棋盘如上图所示,第一行为:空格+“|”+空格+“|”+空格
使用第二个for循环进行第一行各列的打印,循环打印为 空格+“|”,但很显然最后一列会多出一个“|”
为使得最后一列不打印“|”,所以我们使用第一个if语句,控制 “|” 不打印在最后一行
打印为第一行后,通过换行进入第二行,第二行为: “---|”+“---|”+“---”,先不看第二个if语句,
直接进入第三个for循环,通过if语句来实现除最后一列外,打印“---|”,而最后一列打印为“---”,
若如此循环,则最后一行也会打印为:“---|”+“---|”+“---” ,因此我们通过第二个if语句来使得最后一行不打印:“---|”+“---|”+“---”。
打印完第二行后进行换行,然后循环最外侧的for,依次打印,结果如上图所示。
5.接下来进入玩家开始落子
void Player(char my_board[][COL], int row, int col,int x,int y)
{
while (1)
{
printf("请输入坐标(x,y)\n");
scanf("%d %d", &x, &y);
x = x - 1;
y = y - 1;
//判断坐标合法性
if (!(x >= 0 && x < row && y < col && y >= 0))
{
printf("坐标不合法!\n");
}
else if (my_board[x][y] != ' ')
{
printf("该坐标已被占用!\n");
}
else
{
my_board[x][y] = 'O';
break;
}
}
return;
}
因为玩家坐标的输入可能不具有合法性,因此首先设立一个while循环,进入循环后,让玩家输入落子坐标因为不是每一个玩家都是程序员,明白数组首元素下标是0,因此我们为方便玩家进行坐标输入,在玩家输入坐标后,对坐标进行减1操作,使得坐标合法同时方便玩家,如玩家输入坐标为(1,1)其实是想在真实坐标(1,1)处落子,但实际上会落到坐标(2,2)处,我们对玩家输入的坐标减1后就会落到数组(0,0),即真实坐标(1,1)处了。
当玩家输入坐标后,首先进行坐标合法性判断,若坐标不在棋盘范围内,则输出“坐标不合法”,并重新进入循环再次输入坐标,合法性判断后,再次判断该坐标是否已被占用,即该坐标处元素值是否为初始值“空格”,若并非空格,则已被占用,并重新循环。以上条件街皆满足后,将该坐标处元素值改为玩家棋子“O”并退出循环,结束玩家下棋。
6.电脑下棋
void Computer(char my_board[][COL], int row, int col,int x,int y)
{
while (1)
{ //电脑的随机下棋
x = rand() % row;
y = rand() % col;
if (my_board[x][y] == ' ')
{
my_board[x][y] = 'X';
break;
}
}
return;
}
电脑下棋的实现较为简单,对坐标进行随机化并使得随机在合法范围内即可,若随机处已被占用,则重新随机,若未占用,则更改此处元素初始值“空格”为电脑棋子“X”。
7.判断输赢
每次电脑或玩家下完棋后都需要进行输赢的判断。
int Is_Win(char my_board[][COL], int row, int col, int x, int y)
{
//0—无事发生 2—平局 1—电脑/玩家胜利
//判断是否平局
int count = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (my_board[i][j] == ' ') {
count++;
}
}
}
if (count == 0)
{
return 2;
}
//判断横向
count = 1;
for (int tmp = 1; y - tmp >= 0; tmp++)
{
if (my_board[x][y - tmp] == my_board[x][y])
count++;
else break;
}
for (int tmp = 1; y + tmp < col; tmp++)
{
if (my_board[x][y + tmp] == my_board[x][y])
count++;
else break;
}
if (count >= 3)
return 1;
//纵向
count = 1;
for (int tmp = 1; (x - tmp >= 0); tmp++)
{
if (my_board[x - tmp][y] == my_board[x][y])
count++;
else
break;
}
for (int tmp = 1; (x + tmp < col); tmp++)
{
if (my_board[x + tmp][y] == my_board[x][y])
count++;
else
break;
}
if (count >= 3)
return 1;
//正斜 右上:x-tmp, y+tmp; 左下:x+tmp, y-tmp
count = 1;
for (int tmp = 1; (x - tmp >= 0) && (y + tmp < col); tmp++) {
if (my_board[x - tmp][y + tmp] == my_board[x][y])
count++;
else
break;
}
for (int tmp = 1; (x + tmp < row) && (y - tmp >= 0); tmp++) {
if (my_board[x + tmp][y - tmp] == my_board[x][y])
count++;
else
break;
}
if (count >= 3)
return 1;
//反斜 左上:x-tmp, y-tmp; 右下:x+tmp,y+tmp
count = 1;
for (int tmp = 1; (x - tmp >= 0) && (y - tmp >= 0); tmp++)
{
if (my_board[x - tmp][y - tmp] == my_board[x][y])
count++;
else
break;
}
for (int tmp = 1; (x + tmp < row) && (y + tmp < col); tmp++)
{
if (my_board[x + tmp][y + tmp] == my_board[x][y])
count++;
else
break;
}
if (count >= 3)
return 1;
return 0;
}
首先进行平局的判断
直接对二维数组进行遍历查找即可,当发现其中所有元素值均不为空格时(即count==0),即为平局,返回2;若有值(即count>=0),则进入后面的判断。
接下来判断输赢,只要有横向或纵向或斜向任意三个连续位置棋子相同,则说明游戏结束。那现在问题就是怎样来判断连续的三个位置呢,现在我们打印的九宫格的三子棋,每一个方向只有三个连续格子,可以直接对三个格子进行判断,看看是否相等。但若我们改变棋盘为5*5(改变棋盘行列的宏定义), 那么我们怎样判断呢?
这时候我们就需要一个新的变量tmp作为坐标的偏移量,如(x,y)为一坐标点,那么(x-tmp,y)就是该点向左偏移tmp个单位位置后的元素坐标。由于不同方向的判断思路相同,我们在这里只分析纵向的判断。
横向中,纵坐标不变,横坐标偏移,我们把tmp初始化为1,为保证坐标的合法性,将第一个for循环的判断条件设置为 x - tmp>=0,判断原坐标(x,y)左边相同的棋子个数,若 my_board[x - tmp][y] == my_board[x][y],则说明左边第一个棋子和原棋子相同,相同则count+1,不同则结束该for循环,此时的count即为(x,y)及左边连续相同的棋子个数(包括判断原点(x,y)),只后进入第2个for循环,判断(x,y)右边的相同的连续棋子个数,结束两个for循环后,若count为3,则说明有三个连续且相同的棋子,则返回1,代表一方胜出。
纵向与横向相同,不过是横坐标不变,总左边改变,要保证纵坐标的合法性。但要注意每次判断完一个方向后都要重新把count赋值为1。
正斜思路与横向类似,不过坐标变化为行减列加或行加列减,且坐标合法性要同时检验横纵坐标。 反斜不再赘述。
8.倒计时函数
void My_Time()
{
int times = 5;
printf("\n \n \n");
while (times)
{
Sleep(1000);
printf(" ************* 游戏将会在 %d s后开始 *****************\r", times);
times--;
}
}
很简单的一个小功能,前面说过很多次,此处不赘述。
三:全部源码
#include"main.h"
//main.c
int main()
{
//设置随机数
srand((unsigned long)time(NULL));
Log_In();
int input;
do
{
Menu();
printf("请做出你的选择>");
scanf("%d", &input);
switch (input)
{
case 0:
return;
break;
case 1:
Game();
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
} while (input);
return 0;
}
#pragma once
//main.h
#pragma warning(disable:4996)
#include<stdio.h>
#include<windows.h>
#include<time.h>
#include<stdlib.h>
#define ACCOUNT "ranjianmin"
#define PASSWORD "20030512"
#define ROW 4
#define COL 4
extern void Game();
extern void Menu();
extern void Log_In();
extern void Init_Board(char my_board[][COL], int row, int col);
extern void Print_Board(char my_board[][COL], int row, int col);
extern void Player(char my_board[][COL], int row, int col, int x, int y);
extern int Is_Win(char my_board[][COL], int row, int col, int x, int y);
extern void Computer(char my_board[][COL], int row, int col, int x, int y);
#include"main.h"
//text.c
void Log_In()
{
char account[15]="\0";
char password[15] = "\0";
while (1)
{
printf("请输入账号和密码\n");
printf("账号>");
scanf("%s", account);
printf("密码>");
scanf("%s", password);
//检验账号密码的正确性
if (((strcmp(account, ACCOUNT)) == 0) && ((strcmp(password, PASSWORD)) == 0))
{
printf("登陆成功!\n");
break;
}
else
{
printf("账号或密码错误,请重新输入!\n");
}
}
return;
}
void Menu()
{
printf("********** 欢迎来到三子棋游戏 **********\n");
printf("**** 1.Play 0.Exit ****\n");
printf("****************************************\n");
return;
}
void Init_Board(char my_board[][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
my_board[i][j] = ' ';
}
}
}
void Print_Board(char my_board[][COL], int row, int col)
{
//刷新界面
system("cls");
//打印棋盘
for (int i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", my_board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
if (i < row - 1)
{
for (j = 0; j < col; j++)
{
if (j < col - 1)
{
printf("---|");
}
else
{
printf("---");
}
}
}
printf("\n");
}
return;
}
void Player(char my_board[][COL], int row, int col, int x, int y)
{
while (1)
{
printf("请输入坐标(x,y)\n");
scanf("%d %d", &x, &y);
x = x - 1;
y = y - 1;
//判断坐标合法性
if (!(x >= 0 && x < row && y < col && y >= 0))
{
printf("坐标不合法!\n");
}
else if (my_board[x][y] != ' ')
{
printf("该坐标已被占用!\n");
}
else
{
my_board[x][y] = 'O';
break;
}
}
return;
}
void Computer(char my_board[][COL], int row, int col, int x, int y)
{
while (1)
{ //电脑的随机下棋
x = rand() % row;
y = rand() % col;
if (my_board[x][y] == ' ')
{
my_board[x][y] = 'X';
break;
}
}
return;
}
int Is_Win(char my_board[][COL], int row, int col, int x, int y)
{
//0—无事发生 2—平局 1—电脑/玩家胜利
//判断是否平局
int count = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (my_board[i][j] == ' ') {
count++;
}
}
}
if (count == 0)
{
return 2;
}
//判断横向
count = 1;
for (int tmp = 1; y - tmp >= 0; tmp++)
{
if (my_board[x][y - tmp] == my_board[x][y])
count++;
else break;
}
for (int tmp = 1; y + tmp < col; tmp++)
{
if (my_board[x][y + tmp] == my_board[x][y])
count++;
else break;
}
if (count >= 3)
return 1;
//纵向
count = 1;
for (int tmp = 1; (x - tmp >= 0); tmp++)
{
if (my_board[x - tmp][y] == my_board[x][y])
count++;
else
break;
}
for (int tmp = 1; (x + tmp < col); tmp++)
{
if (my_board[x + tmp][y] == my_board[x][y])
count++;
else
break;
}
if (count >= 3)
return 1;
//正斜 右上:x-tmp, y+tmp; 左下:x+tmp, y-tmp
count = 1;
for (int tmp = 1; (x - tmp >= 0) && (y + tmp < col); tmp++) {
if (my_board[x - tmp][y + tmp] == my_board[x][y])
count++;
else
break;
}
for (int tmp = 1; (x + tmp < row) && (y - tmp >= 0); tmp++) {
if (my_board[x + tmp][y - tmp] == my_board[x][y])
count++;
else
break;
}
if (count >= 3)
return 1;
//反斜 左上:x-tmp, y-tmp; 右下:x+tmp,y+tmp
count = 1;
for (int tmp = 1; (x - tmp >= 0) && (y - tmp >= 0); tmp++)
{
if (my_board[x - tmp][y - tmp] == my_board[x][y])
count++;
else
break;
}
for (int tmp = 1; (x + tmp < row) && (y + tmp < col); tmp++)
{
if (my_board[x + tmp][y + tmp] == my_board[x][y])
count++;
else
break;
}
if (count >= 3)
return 1;
return 0;
}
void My_Time()
{
int times = 5;
printf("\n \n \n");
while (times)
{
Sleep(1000);
printf(" ************* 游戏将会在 %d s后开始 *****************\r", times);
times--;
}
}
void Game()
{
//倒计时函数
My_Time();
int x = 0, y = 0;
printf("三子棋游戏开始!\n");
//设置棋盘
char my_board[ROW][COL];
//棋盘初始化
Init_Board(my_board, ROW, COL);
//打印棋盘
Print_Board(my_board, ROW, COL);
// //玩家下棋
while (1)
{
Player(my_board, ROW, COL, x, y);
Print_Board(my_board, ROW, COL);
//判断胜负
int ret = Is_Win(my_board, ROW, COL, x, y);
if (1 == ret)
{
printf("恭喜玩家获得胜利!\n");
break;
}
else if (0 == ret)
{
}
else
{
printf("平局\n");
break;
}
//计算机下棋
Computer(my_board, ROW, COL, x, y);
Print_Board(my_board, ROW, COL);
//判断胜负
ret = Is_Win(my_board, ROW, COL, x, y);
if (1 == ret)
{
printf("很遗憾,玩家失败!\n");
break;
}
else if (0 == ret)
{
}
else
{
printf("平局\n");
break;
}
}
return;
}
补充 :
假如需要将三子棋改为五子棋,只要将判断输赢函数里改为count>=5,并把棋盘大小(ROW和COL)改成适合的大小即可。
下一篇我们会进行扫雷的讲解,晚安喔!留个赞赞吧(小声)。
最后:
愿每一个奋斗者都有着直面苍天的勇气,在孤独的旅途中,自信的踏向茫茫的明天!或许不会有很多人看到我们的努力,那就让他们看到我们的成功!