您现在的位置是:首页 >技术杂谈 >任意子棋游戏设计(C语言)网站首页技术杂谈
任意子棋游戏设计(C语言)
以三子棋为例
一、实现思路
- 首先打印出菜单,让玩家可以选择想要玩的模式,按
1 人机模式
2 玩家对决
0 退出游戏 - 其次让玩家选择想玩几子棋,并且选择谁先手
- 不管选择哪种模式,首先会打印出一个3*3的棋盘,接下来由玩家选择的模式进行不同的玩法
- 判断输赢分为以下几种情况:
1.有一方获胜,返回对应的棋子
2.若棋盘被下满,则视为平局
3.以上两种都不满足,则游戏继续 - 输出获胜方
二、代码实现
我们应用多文件的形式写代码,创建三个文件
test.c — 存放三子棋游戏的整体运行逻辑
game.c —包含三子棋各游戏模块的函数实现
game.h —游戏所需的头文件及各模块的函数声明
1、创建菜单
void menu()
{
printf("**********************************
");
printf("********** 1.人机模式 ************
");
printf("********** 2.玩家对决 ***********
");
printf("********** 0.退出游戏 ***********
");
printf("请输入 1 or 0 决定是否开始游戏
");
}
2、主函数逻辑
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1 :
{
while (1)
{
printf("请输入获胜时的棋子个数:
");
scanf("%d", &n);
if (n > ROW)
{
printf("超过棋盘范围,请重新输入!!!
");
}
else
{
break;
}
}
game1();
break;
}
case 2:
{
while (1)
{
printf("请输入获胜时的棋子个数:
");
scanf("%d", &n);
if (n > ROW)
{
printf("超过棋盘范围,请重新输入!!!
");
}
else
{
break;
}
}
game2();
break;
}
case 0: printf("游戏退出
");
break;
default: printf("非法输入!!!请重新输入:
");
break;
}
} while (input);
}
可以看到里面有重复代码,主要是为了让玩家自己选择想玩几子棋,将n定义为全局变量,并在其他文件中用extern关键字引入即可
3、实现游戏功能
(1) 定义一个3*3的棋盘,初始化棋盘,打印棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0;i < row;i++)
{
for (int j = 0;j < col;j++)
{
board[i][j] =' ';
}
}
}
void PrintBoard(char board[ROW][COL],int row,int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
//打印数据
for (int j = 0;j < col;j++)
{
printf(" %c ", board[i][j]);//注意两边有空格
if (j < col - 1)//最后一列不打印|
{
printf("|");
}
}
printf("
");//打印完一行进行换行
//打印下划线
if (i < row - 1)
{
for (int j = 0;j < col;j++)
{
printf("---");
if (j < col - 1)//最后一列不打印|
{
printf("|");
}
}
printf("
");//打印完一行进行换行
}
}
}
棋盘的打印对初学者来说也有一定的难度,不会的可以看上面的注释加以理解,打印效果如下
当然棋盘也不一定是3*3,在判断输赢的代码中会优化,想看的可以点目录直接去看
(2)判断先手
//人机判断先手
int m = 0;
printf("请选择玩家先下还是电脑先下:
");
printf("1.玩家先下
");
printf("2.电脑先下
");
scanf("%d", &m);
if (m == 1)
{
//玩家下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//电脑下棋
ret = ComputerMove(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
else
{
//电脑下棋
ret = ComputerMove(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//玩家下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
//玩家与玩家判断先手
int m = 0;
printf("请选择玩家1先下还是玩家2先下:
");
printf("1.玩家1先下
");
printf("2.玩家2先下
");
scanf("%d", &m);
if (m == 1)
{
//玩家1下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//玩家2下棋
ret = PlayerMove2(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
else
{
//玩家2下棋
ret = PlayerMove2(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//玩家1下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
以上只给出了判断先手的代码,它包含在game1()和game2()中
(3) 人机模式
//人和电脑玩游戏
void game1()
{
int m = 0;
printf("请选择玩家先下还是电脑先下:
");
printf("1.玩家先下
");
printf("2.电脑先下
");
scanf("%d", &m);
//定义判断输赢的返回值
char ret = 0;
//定义棋盘
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board,ROW,COL);
while (1)
{
if (m == 1)
{
//打印棋盘
PrintBoard(board, ROW, COL);
//玩家下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//电脑下棋
ret = ComputerMove(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
else
{
//打印棋盘
PrintBoard(board, ROW, COL);
//电脑下棋
ret = ComputerMove(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//玩家下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
}
switch (ret)
{
case '*': printf("恭喜玩家获胜~~~
");
break;
case '#': printf("恭喜电脑获胜~~~
");
break;
case 'Q':printf("平局
");
break;
}
}
玩家用’*‘,电脑用’#'表示.game1()函数为玩家和电脑对决的实现逻辑,其中调用了PlayerMove1()和Comeputer()函数,PlayerMove1()函数是玩家下棋的代码具体实现,Comeputer()函数则是电脑下棋的相应代码逻辑实现
(3) 玩家对决
//玩家对决模式
void game2()
{
int m = 0;
printf("请选择玩家1先下还是玩家2先下:
");
printf("1.玩家1先下
");
printf("2.玩家2先下
");
scanf("%d", &m);
//定义判断输赢的返回值
char ret = 0;
//定义棋盘
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board, ROW, COL);
while (1)
{
if (m == 1)
{
//打印棋盘
PrintBoard(board, ROW, COL);
//玩家1下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//玩家2下棋
ret = PlayerMove2(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
else
{
//打印棋盘
PrintBoard(board, ROW, COL);
//玩家2下棋
ret = PlayerMove2(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//玩家1下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
}
switch (ret)
{
case '*': printf("恭喜玩家1获胜~~~
");
break;
case '#': printf("恭喜玩家2获胜~~~
");
break;
case 'Q':printf("平局
");
break;
}
}
玩家1’*‘,玩家2’#'表示.game2()函数为玩家对决的具体实现逻辑,里面包含PlayerMove1()函数和PlayerMove2()函数,这里只给出PlayerMove2()函数的实现逻辑PlayerMove1()函数与上方电脑对决相同,细心的已经发现玩家落子代码大同小异,拆分为两个不同的函数,是为了调用起来方便,同时逻辑上更好理解
(4) 判断输赢
char isWinner(char board[ROW][COL], int row, int col)
{
//判断 行
for (int i = 0;i < row;i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] !=' ')
{
return board[i][0];
}
}
//判断 列
for (int i = 0;i < row;i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[0][i];
}
}
//判断 对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[0][0];
}
//平局
if (isFull(board, ROW, COL))
{
return 'Q';
}
//继续
return 'C';
}
判断输赢分为4中情况:
1:某一方获胜
2:棋盘被下满,平局
4:前两种种都不满足说明没人获胜,游戏继续
4、判断输赢存在的问题并优化
可以看到上述判断输赢存在一定的问题,即代码扩展性不强,如果将棋盘改为15*15,那么if语句在判断时就有16个判断条件,那是相当麻烦的。下面代码可以很好的改变此情况
char isWinner(char board[ROW][COL],int x,int y,char set)
{
//判断行
int spsum = 1;
for (int i = y+1;i <= COL;i++)//向右找
{
if (board[x][i]==set)
{
spsum++;
}
else
{
break;
}
}
for (int i = y - 1;i >=0;i--)//向左找
{
if (board[x][i] == set)
{
spsum++;
}
else
{
break;
}
}
if (spsum >= n)
return set;
//判断列
int czsum = 1;
for (int i = x+1;i<=ROW;i++)//向下判断
{
if (board[i][y] == set)
{
czsum++;
}
else
{
break;
}
}
for (int i = x - 1;i >= 0;i--)//向上判断
{
if (board[i][y] == set)
{
czsum++;
}
else
{
break;
}
}
if (czsum >= n)
return set;
//斜向主对角线
int zsyxsum = 1;
for (int i = x + 1, j = y + 1;i <= ROW && y <= ROW;i++,j++)//斜向右下
{
if (board[i][j] == set)
{
zsyxsum++;
}
else {
break;
}
}
for (int i = x - 1, j = y - 1;i >= 0 && j >= 0;i--, j--)//斜向左上
{
if (board[i][j] == set)
{
zsyxsum++;
}
else {
break;
}
}
if (zsyxsum >= n)
return set;
//斜向副对角线
int yszxsum = 1;
for (int i = x - 1, j = y + 1;i >= 0 && j <= ROW;i--,j++)//斜向右上
{
if (board[i][j] == set)
{
yszxsum++;
}
else
{
break;
}
}
for (int i = x + 1, j = y - 1;i <= ROW && j >= 0;i++, j--)//斜向左下
{
if (board[i][j] == set)
{
yszxsum++;
}
else
{
break;
}
}
if (yszxsum >= n) return set;
if (isFull(board, ROW, COL))
return 'Q';
return 'C';
}
此代码是这次设计的精髓,它提高了程序的扩展性,可以让玩家想玩多大的棋盘都可以,并且n也可以让玩家自己选择想玩几子棋都可以,会在最终的运行效果中向大家展示
三、最终的代码实现
(1)test.c文件中的内容
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
#include <time.h>
#include <stdlib.h>
int n = 0;
//打印菜单
void menu()
{
printf("**********************************
");
printf("********** 1.人机模式 ************
");
printf("********** 2.玩家对决 ***********
");
printf("********** 0.退出游戏 ***********
");
printf("请输入 1 or 0 决定是否开始游戏
");
}
//人和电脑玩游戏
void game1()
{
//定义判断输赢的返回值
char ret = 0;
//定义棋盘
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board,ROW,COL);
//打印棋盘
PrintBoard(board, ROW, COL);
while (1)
{
//玩家下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//电脑下棋
ret = ComputerMove(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
switch (ret)
{
case '*': printf("恭喜玩家获胜~~~
");
break;
case '#': printf("恭喜电脑获胜~~~
");
break;
case 'Q':printf("平局
");
break;
}
}
void game2()
{
//定义判断输赢的返回值
char ret = 0;
//定义棋盘
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
PrintBoard(board, ROW, COL);
while (1)
{
//玩家1下棋
ret = PlayerMove1(board, ROW, COL);
PrintBoard(board, ROW, COL);
//判断输赢
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
//玩家2下棋
ret = PlayerMove2(board, ROW, COL);
PrintBoard(board, ROW, COL);
//ret = isWinner(board, ROW, COL);
if (ret != 'C') break;
}
switch (ret)
{
case '*': printf("恭喜玩家1获胜~~~
");
break;
case '#': printf("恭喜玩家2获胜~~~
");
break;
case 'Q':printf("平局
");
break;
}
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1 :
{
printf("请输入获胜时的棋子个数:
");
scanf("%d", &n);
game1();
break;
}
case 2:
{
printf("请输入获胜时的棋子个数:
");
scanf("%d", &n);
game2();
break;
}
case 0: printf("游戏退出
");
break;
default: printf("非法输入!!!请重新输入:
");
break;
}
} while (input);
}
主要是代码实现的逻辑
(2)game.c文件中的内容
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
extern int n;
void InitBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0;i < row;i++)
{
for (int j = 0;j < col;j++)
{
board[i][j] =' ';
}
}
}
void PrintBoard(char board[ROW][COL],int row,int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
//打印数据
for (int j = 0;j < col;j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("
");
//打印下划线
if (i < row - 1)
{
for (int j = 0;j < col;j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("
");
}
}
}
char PlayerMove1(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
char ret = 0;
printf("玩家1下棋->
");
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] = '*';
ret=isWinner(board,x-1,y-1,'*');
break;
}
else//不能落子
{
printf("该位置被占用!请重新输入->
");
}
}
else//坐标非法
{
printf("输入位置不合法!请重新输入->
");
}
}
return ret;
}
char PlayerMove2(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
char ret = 0;
printf("玩家2下棋->
");
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] = '#';
ret = isWinner(board, x - 1, y - 1, '#');
break;
}
else//不能落子
{
printf("该位置被占用!请重新输入->
");
}
}
else//坐标非法
{
printf("输入位置不合法!请重新输入->
");
}
}
return ret;
}
char ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
char ret = 0;
printf("电脑下棋->
");
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
ret = isWinner(board, x - 1, y - 1, '#');
break;
}
}
return ret;
}
//判断是否被下满
int isFull(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 1;
}
char isWinner(char board[ROW][COL],int x,int y,char set)
{
//判断行
int spsum = 1;
for (int i = y+1;i <= COL;i++)//向右找
{
if (board[x][i]==set)
{
spsum++;
}
else
{
break;
}
}
for (int i = y - 1;i >=0;i--)//向左找
{
if (board[x][i] == set)
{
spsum++;
}
else
{
break;
}
}
if (spsum >= n)
return set;
//判断列
int czsum = 1;
for (int i = x+1;i<=ROW;i++)//向下判断
{
if (board[i][y] == set)
{
czsum++;
}
else
{
break;
}
}
for (int i = x - 1;i >= 0;i--)//向上判断
{
if (board[i][y] == set)
{
czsum++;
}
else
{
break;
}
}
if (czsum >= n)
return set;
//斜向主对角线
int zsyxsum = 1;
for (int i = x + 1, j = y + 1;i <= ROW && y <= ROW;i++,j++)//斜向右下
{
if (board[i][j] == set)
{
zsyxsum++;
}
else {
break;
}
}
for (int i = x - 1, j = y - 1;i >= 0 && j >= 0;i--, j--)//斜向左上
{
if (board[i][j] == set)
{
zsyxsum++;
}
else {
break;
}
}
if (zsyxsum >= n)
return set;
//斜向副对角线
int yszxsum = 1;
for (int i = x - 1, j = y + 1;i >= 0 && j <= ROW;i--,j++)//斜向右上
{
if (board[i][j] == set)
{
yszxsum++;
}
else
{
break;
}
}
for (int i = x + 1, j = y - 1;i <= ROW && j >= 0;i++, j--)//斜向左下
{
if (board[i][j] == set)
{
yszxsum++;
}
else
{
break;
}
}
if (yszxsum >= n) return set;
if (isFull(board, ROW, COL))
return 'Q';
return 'C';
}
主要是对各功能需要的子模块(函数)进行实现
(3)game.h文件中的内容
#pragma once
#include <stdio.h>
#define ROW 5
#define COL 5
void InitBoard(char board[ROW][COL], int row, int col);
void PrintBoard(char board[ROW][COL], int row, int col);
char PlayerMove1(char board[ROW][COL], int row, int col);
char PlayerMove2(char board[ROW][COL], int row, int col);
char ComputerMove(char board[ROW][COL], int row, int col);
char isWinner(char board[ROW][COL], int row, int col,char set);
int isFull(char board[ROW][COL], int row, int col);
主要是对各功能需要的子模块(函数)进行声明
四、最终运行效果
在3*3棋盘上玩三子棋
在10**10棋盘上玩五子棋
五 总结
这次三子棋游戏的设计中,虽然实现了在任意棋盘上玩任意子棋,但仍然有非常多的缺陷和想法没有实现,如下:
(1)在菜单打印前,让玩家自己选择棋盘大小,这样就不用去改宏定义中的值,每次只需初始化相应大小的棋盘即可,但C语言的C99标准之前不支持变长数组(虽然可以用malloc()函数,但是对于二维数组列不能省略,所以这个想法不太行),导致创建数组时必须是常量也就是值不能改变,用VS编译器实现起来相对麻烦,若支持变长数组,那实现起来很容易,后面有时间会用java实现让大家看一看.
由于时间原因虽然实现了让玩家自己选择获胜时连在一起的棋子个数,但棋盘的大小仍要通过改变程序中的宏定义;
(2)为了便于调用和理解,程序中有大量的重复代码,这也是待改进的地方
(3)电脑下棋太随机,不智能,没有加以条件限制,导致玩家赢得很轻松.