目录
前言
一、何为N子棋游戏?
二、游戏思路
三、游戏实现
3.1 模块化
3.2 游戏棋盘
3.3 下棋操作
3.3.1 玩家下棋
3.3.2 电脑下棋
3.4 判断输赢
总结
前言
三子棋小游戏相信大家都玩过吧,类似的5子琪等等,这篇文章将带着大家从0到1实现一个简易版的N子棋小游戏,当然,是不带图形界面的。通过本文,可以让初学者对C语言的基础知识点的了解更进一步。
一、何为N子棋游戏?
鉴于文章的严谨性,可能有的老铁没有玩过N子棋类的游戏,这里有个网页版的游戏,可以去体验体验:三子棋(井字棋)
N代表的连线的棋子,如五子棋等等。
二、游戏思路
实现该游戏大致分为以下几步:
- 分模块进行,三个文件,分别为游戏测试逻辑、游戏逻辑、游戏声明。
- 棋盘准备。
- 玩家下棋。
- 判断输赢
- 电脑下棋
- 判断输赢
三、游戏实现
我们规定:
- * 为玩家下棋的字符标志
- # 为电脑下棋的字符标志
3.1 模块化
C语言中,对于一个完整的项目来说,不可能只在一个源文件下实现,往往需要分模块进行,在C语言标准中:
- .h头文件放置函数声明和头文件包含
- 专门一个.c源文件用于进行测试逻辑,也就是用于调用函数。
- 专门一个.c源文件用于函数的实现(定义)
- .....
在该游戏中,我们要用到三个文件,分别是:
- 游戏测试逻辑(.c文件)
- 游戏实现逻辑(.c文件)
- 游戏声明(.h文件)
下面我带着大家进行创建:
3.2 游戏棋盘
该类游戏都会有一个棋盘,类似于三子棋:
既然是下棋,那就需要构建一个棋盘,可以看出,棋盘由行和列组成,那么我们可以用二维数组来表示,因为二维数组就是以行和列组成的,当然,为了游戏的完整性,我们可以先打印一个菜单供用户选择。
test.c 文件
#include "game.h" //引入头文件
void mnue()
{
printf("*******************\n");
printf("*****1、play*******\n");
printf("*****0、exit*******\n");
printf("*******************\n");
}
int main()
{
int input = 0;
//一进来就打印菜单,让用户选择,用do...while最合适
do
{
mnue();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
break;
}
} while (input);
return 0;
}
game.h文件
#include <stdio.h>
因为用户一进游戏,无论用户是否选择开始游戏,都会进行一次打印菜单,因此这里用do...while结构非常合适。
菜单准备完毕,下面开始准备棋盘:
定义一个二维数组来存放下棋数据,也相当于棋盘:
char board[ROW][COL] = { 0 };
这里的数组大小不能写死,因为我们做的游戏是N子棋,对于数组大小,我们用#define标识符定义:
game.h文件
//棋盘大小
#define ROW 3
#define COL 3
数组是否要进行初始化?要进行的,并且要全部初始化为空格,为什么呢?我们可以观察一下棋盘:
我们发现,在没有下棋时,格子的位置明显是一个空格进行占位,因此我们将数组初始化为空格,进行占位。
test.c 文件
void game()
{
//1、定义一个二维数组来存放下棋数据,也相当于棋盘
//让棋盘大小可变,定义#define标识符
char board[ROW][COL] = { 0 };
//2、初始化棋盘,让数组内容均为空格,这样做是为了占位
Initialize_Board(board, ROW, COL);
}
game.h 文件
#pragma once
#include<stdio.h>
#include<string.h>
//棋盘大小
#define ROW 3
#define COL 3
//初始化棋盘
void Initialize_Board(char board[ROW][COL],int row, int col);
game.c 文件
#include "game.h"
//初始化棋盘
void Initialize_Board(char board[ROW][COL], int row,int col)
{
//方法1:嵌套for循环
/*for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}*/
//方法二:memset库函数,修改执行字节大小的值
// 三个参数:起始地址,修改内容,字节个数
memset(&board[0][0], ' ', row*col *sizeof(board[0][0]));
}
进行初始化操作有两个方法:
- 嵌套for循环(常规)
- memset库函数,制定修改n字节的内容,具体可看文档:memset
初始化完毕后,接下来就该构造出棋盘了,其实就是打印棋盘操作,基本思路如下:
- 棋盘组成:一行内容|,一行---|
- 边界条件:一行内容|中的|为COL-1列;一行---|中---为ROW-1行,|为COL-1列。
test.c 文件
void game()
{
//1、定义一个二维数组来存放下棋数据,也相当于棋盘
//让棋盘大小可变,定义#define标识符
char board[ROW][COL] = { 0 };
//2、初始化棋盘,让数组内容均为空格,这样做是为了占位
Initialize_Board(board, ROW, COL);
//3、打印棋盘
Print_Board(board, ROW, COL);
}
game.h 文件
#pragma once
#include<stdio.h>
#include<string.h>
//棋盘大小
#define ROW 3
#define COL 3
//初始化棋盘
void Initialize_Board(char board[ROW][COL],int row, int col);
//打印棋盘
void Print_Board(char board[ROW][COL], int row, int col);
game.c 文件
#include "game.h"
//打印棋盘
void Print_Board(char board[ROW][COL], int row, int col)
{
//两部分:
//1、内容+|
//2、--- + |
for (int i=0; i<row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
//如3×3棋盘,那就打印两列|就可以了
if (j<col-1)
{
printf("|");
}
}
//给内容+| 和 ---|之间换行
printf("\n");
//这里打印---|
//也一样,只打印两行---即可
if (i<row-1)
{
//为每一列打印---|
for (int i = 0; i < col; i++)
{
printf("---");
//同样的,只打印2列|
if (i<col-1)
{
printf("|");
}
}
}
//换行,表示这一行打印完成
printf("\n");
}
}
棋盘准备完毕,我们运行看看效果:
3.3 下棋操作
棋盘完成后,进入下棋逻辑,下棋我们要考虑几个方面:
- 下棋后应打印出最新的棋盘。
- 判断输赢
3.3.1 玩家下棋
玩家根据棋盘的坐标进行下棋,这里的逻辑有几个注意点:
- 玩家眼中的坐标与数组表示的下标不同。
- 输入的坐标有边界条件:x>=1 && x<=row && y>=1 && y<=col
- 判断输入的坐标中是否已下过棋
test.c 文件
void game()
{
//1、定义一个二维数组来存放下棋数据,也相当于棋盘
//让棋盘大小可变,定义#define标识符
char board[ROW][COL] = { 0 };
//2、初始化棋盘,让数组内容均为空格,这样做是为了占位
Initialize_Board(board, ROW, COL);
//3、打印棋盘
Print_Board(board, ROW, COL);
//4、玩家下棋
Root_Play(board, ROW, COL);
//5、打印棋盘
Print_Board(board, ROW, COL);
}
game.h 文件
#pragma once
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
//棋盘大小
#define ROW 3
#define COL 3
//初始化棋盘
void Initialize_Board(char board[ROW][COL],int row, int col);
//打印棋盘
void Print_Board(char board[ROW][COL], int row, int col);
//玩家下棋
void Root_Play(char board[ROW][COL], int row, int col);
game.c 文件
#include "game.h"
//玩家下棋
// * 为玩家下棋的字符标志
// # 为电脑下棋的字符标志
void Root_Play(char board[ROW][COL], int row, int col)
{
//根据坐标下棋
int x = 0;
int y = 0;
//边界条件
//最小不能为x!=0,y!=0
//最大x>row+1,y>col+1
//边界条件为:x ==0 || y == 0 || x > row || y > col
//还要判断输入的坐标中是否已下过棋
while (1)
{
printf("请玩家输入下棋坐标:>");
scanf("%d %d", &x, &y);
//如果超出边界条件,那就提示一下,然后重新输入
if (x ==0 || y == 0 ||
x>row || y>col)
{
printf("坐标超出范围...\n");
}
//如果输入的坐标中已下过棋,则重新下棋
else if (board[x - 1][y - 1] == '*' ||
board[x - 1][y - 1] == '#')
{
printf("该坐标已下过棋...\n");
}
else
{
//为坐标棋盘赋值
//x-1,y-1是因为计算机中下标是从0开始
//但用户不懂这些,按照正常坐标,因此要-1
board[x - 1][y - 1] = '*';
system("cls");
break;
}
}
}
在下棋时,需要将坐标-1,因为用户理解的坐标是常规的,但在编写代码时,数组以下标进行访问,因此坐标-1。我们看看效果:
在玩家下完棋后,使用system("cls");清空屏幕,这样更加好看。
因为下完棋打印最新的棋盘就是打印棋盘的逻辑,这里就不讲了,只需要调用打印棋盘函数即可。
3.3.2 电脑下棋
关于电脑下棋这一功能,我们用随机数生成坐标即可。与玩家下棋不同,电脑下棋通过生成随机坐标,范围是由我们定义的,因此坐标也符合我们的想法,这里就不用添加边界条件了,但要判断输入的坐标中是否已下过棋。
test.c 文件
void game()
{
//1、定义一个二维数组来存放下棋数据,也相当于棋盘
//让棋盘大小可变,定义#define标识符
char board[ROW][COL] = { 0 };
//2、初始化棋盘,让数组内容均为空格,这样做是为了占位
Initialize_Board(board, ROW, COL);
//3、打印棋盘
Print_Board(board, ROW, COL);
//4、玩家下棋
Root_Play(board, ROW, COL);
//5、打印棋盘
Print_Board(board, ROW, COL);
//4、电脑下棋
Ai_Play(board, ROW, COL);
//5、打印棋盘
Print_Board(board, ROW, COL);
}
int main()
{
int input = 0;
//定义随机数生成棋,一次就够
srand((unsigned int)time(NULL));
//一进来就打印菜单,让用户选择,用do...while最合适
do
{
mnue();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
break;
}
} while (input);
return 0;
}
game.h 文件
#pragma once
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
//棋盘大小
#define ROW 3
#define COL 3
//初始化棋盘
void Initialize_Board(char board[ROW][COL],int row, int col);
//打印棋盘
void Print_Board(char board[ROW][COL], int row, int col);
//玩家下棋
void Root_Play(char board[ROW][COL], int row, int col);
//电脑下棋
void Ai_Play(char board[ROW][COL], int row, int col);
game.c 文件
#include "game.h"
//电脑下棋
// * 为玩家下棋的字符标志
// # 为电脑下棋的字符标志
void Ai_Play(char board[ROW][COL], int row, int col)
{
printf("电脑下棋....\n");
//因为是随机数生成,不存在边界条件
//还要判断输入的坐标中是否已下过棋
//如果输入的坐标中已下过棋,则重新下棋
while (1)
{
// 随机数生成坐标
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
//为坐标棋盘赋值
board[x][y] = '#';
Sleep(500);
system("cls");
break;
}
}
}
电脑下棋中,关键点在于生成随机坐标,生成随机数使用srand和rand就可以了,自定义随机数的范围: int x = rand() % row; int y = rand() % col; 最后我们看看效果:
电脑下棋时,有短暂的暂停效果,这是因为加入了:Sleep(500); 睡眠。同样的,在电脑下完棋后,调用打印棋盘函数。
3.4 判断输赢
不管是玩家下完棋还是电脑下完棋,都应该进行输赢的判断,因此写一个函数即可。
判断输赢的条件如下:
- 任意一行连成线
- 任意一列连成线
- 任意一条对角线连成线
不同的几种结果:
- 玩家胜利(1)
- 电脑胜利(-1)
- 平局(2)
- 继续(0)
不可能一次下棋就能判断出结果,大多情况是继续,因此下棋操作一个循环。
判断输赢函数:
//判断输赢
/*
获胜条件:
1、任意一行连成线
2、任意一列连成线
3、两条对角线任一连成线
返回结果:
1、玩家胜利返回1
2、电脑胜利返回-1
3、平局返回2
4、继续返回0
*/
//判断是否平局
int Isdraw(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
//当数组还要空格,说明还没结束棋局
if (board[i][j] == ' ')
{
return 0;
}
}
}
//此时棋局结束,平局
return 2;
}
int Judge_Wins_And_Losers(char board[ROW][COL], int row, int col)
{
//任意一行连成线
for (int i = 0; i < row; i++)
{
int flag1 = 0;//玩家赢
int flag2 = 0;//电脑赢
for (int j = 0; j < col; j++)
{
if (board[i][j] == '*')
{
flag1++;
}
else if(board[i][j] == '#')
{
flag2++;
}
}
if (flag1 == ROW)
{
return 1;
}
else if (flag2 == ROW)
{
return -1;
}
}
//任意一列连成线
for (int i = 0; i < col; i++)
{
int flag1 = 0;//玩家赢
int flag2 = 0;//电脑赢
for (int j = 0; j < row; j++)
{
if (board[j][i] == '*')
{
flag1++;
}
else if (board[j][i] == '#')
{
flag2++;
}
}
if (flag1 == ROW)
{
return 1;
}
else if (flag2 == ROW)
{
return -1;
}
}
//任意一对角线连成线
//左边到右边对角线
int flag1 = 0;//玩家赢
int flag2 = 0;//电脑赢
int j = 0;
int k = 0;
for (int i = 0; i < col; i++)
{
if (board[j][i] == '*')
{
j++;
flag1++;
}
else if (board[k][i] == '#')
{
k++;
flag2++;
}
if (flag1 == ROW)
{
return 1;
}
else if (flag2 == ROW)
{
return -1;
}
}
//右边到左边对角线
flag1 = 0;//玩家赢
flag2 = 0;//电脑赢
j = 0;
k = 0;
for (int i = col-1; i >= 0; i--)
{
if (board[j][i] == '*')
{
j++;
flag1++;
}
else if (board[k][i] == '#')
{
k++;
flag2++;
}
if (flag1 == ROW)
{
return 1;
}
else if (flag2 == ROW)
{
return -1;
}
}
//谁都没有胜利,直接返回
//是否平局
if (Isdraw(board, row, col))
{
return 2;
}
//继续
return 0;
}
对于判断输赢函数逻辑,关键在于判断棋盘中是否有构成一条线的情况,我们用两个计数变量进行统计。
主函数中接收返回值,并进行判断输赢:
void game()
{
//1、定义一个二维数组来存放下棋数据,也相当于棋盘
//让棋盘大小可变,定义#define标识符
char board[ROW][COL] = { 0 };
//2、初始化棋盘,让数组内容均为空格,这样做是为了占位
Initialize_Board(board, ROW, COL);
//3、打印棋盘
Print_Board(board, ROW, COL);
int num = 0;
while (1)
{
//4、玩家下棋
Root_Play(board, ROW, COL);
//5、打印棋盘
Print_Board(board, ROW, COL);
//6、判断输赢
//1---玩家胜利
//-1---电脑胜利
//2---平局
//0---继续
num = Judge_Wins_And_Losers(board, ROW, COL);
if (num == 1 || num == -1 || num == 2)
{
break;
}
//4、电脑下棋
Ai_Play(board, ROW, COL);
//5、打印棋盘
Print_Board(board, ROW, COL);
//6、判断输赢
num = Judge_Wins_And_Losers(board, ROW, COL);
if (num == 1 || num == -1 || num == 2)
{
break;
}
}
//打印获胜方
if (num == 1)
{
printf("玩家胜利\n");
}
else if (num == -1)
{
printf("电脑胜利\n");
}
else
{
printf("平局\n");
}
}
当出现不是继续的情况,则break跳出循环,然后再进行输赢判断。
对应的游戏声明代码:
#pragma once
/*
游戏逻辑
用于存放头文件和函数的声明
*/
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define ROW 3
#define COL 3
//初始化棋盘
void Initialize_Board(char board[ROW][COL],int row, int col);
//打印棋盘
void Print_Board(char board[ROW][COL], int row, int col);
//玩家下棋
void Root_Play(char board[ROW][COL], int row, int col);
//判断输赢
int Judge_Wins_And_Losers(char board[ROW][COL], int row, int col);
//电脑下棋
// * 为玩家下棋的字符标志
// # 为电脑下棋的字符标志
void Ai_Play(char board[ROW][COL], int row, int col);
运行演示一下吧:
N子棋游戏运行测试
总结
这就是关于C语言实现N子棋小游戏,希望对您有所帮助,关注我,干货满满!!源代码自取。