上图为预览图(Linux下),相信大家都做过三子棋的小程序代码,五子棋在写代码和普通的小程序是无异的,关键在于部分函数的细节,具体思路步骤放到每个函数中讲解。
这里我们使用game.c
,game.h
和main.c
三个文件实现
game.h
#pragma once
#include
#include
#define ROW 20 //行数
#define COL 20 //列数
#define PLAYER1 1
#define PLAYER2 2
#define P1_WIN 1
#define P2_WIN 2
#define DRAW 3 //平局
#define NEXT 0 //游戏是否继续的判定条件
enum Dire { //在判断连珠条件时使用
LEFT,
RIGHT,
UP,
DOWN,
LEFT_UP,
LEFT_DOWN,
RIGHT_UP,
RIGHT_DOWN
};
void Menu();//打印菜单
void Game();//游戏函数
void ShowBoard(int board[][COL], int row, int col);//打印棋盘
void PlayerMove(int board[][COL], int row, int col, int who);//玩家落子
int ChessDire(int board[][COL], int row, int col, enum Dire d);//统计棋是否成连(IsOver里的函数)
int IsOver(int board[][COL], int row, int col);//判断游戏是否结束
头文件不用赘述过多,里面的一些变量相应代码块中会使用讲解
main.c
实现游戏总体结构#include "game.h"
int main()
{
int input = 0, quit = 0;
while (!quit)
{
Menu();
scanf("%d", &input);
switch (input) //switch语句实现游戏分支
{
case 1:
Game();
break;
case 0:
quit = 1; //quit置为1,循环不再进行
printf("退出游戏\n");
break;
default:
printf("输入错误,重新输入\n");
break;
}
}
}
游戏外部进行结构
void Game()
{
int board[ROW][COL];
memset(board, '\0', sizeof(board)); //初始化数组(即棋盘)
int result = NEXT;
do {
ShowBoard(board, ROW, COL);
PlayerMove(board, ROW, COL, PLAYER1);
result = IsOver(board, ROW, COL); //IsOver返回的值赋给result,判断游戏结果
if (NEXT != result) { //当NEXT不等于result证明出结果了,此时跳出循环
break;
}
ShowBoard(board,ROW,COL);
PlayerMove(board, ROW, COL, PLAYER2);
result = IsOver(board, ROW, COL);
if (NEXT != result)
{
break;
}
} while (1);
//跳出循环证明已经出了结果
switch (result)
{
case P1_WIN:
printf("棋手1获胜!\n");
break;
case P2_WIN:
printf("棋手2获胜!\n");
break;
case DRAW:
printf("双方平局\n");
default:
break;
}
}
do_while
循环是双方下棋的过程,每有玩家下棋都需要判断游戏是否结束,如此循环往复直到游戏结束
在game.c
中有int x,y = 0;
即玩家输入的坐标为全局变量便于使用
void Menu()
{
printf("################################\n");
printf("##### 1.PLAY ###### 0.EXIT #####\n");
printf("################################\n");
printf("请输入选择:\n");
}
把打印输入信息放到
Menu()
中让main
函数更明了
void PlayerMove(int board[][COL], int row, int col, int who)
{
while (1)
{
printf("Player[%d]输入下棋坐标:\n", who);
scanf("%d %d", &x, &y);
if (x<1 || x>row || y<1 || y>col) //判断坐标是否越界
{
printf("坐标不合法,请重新输入\n");
continue;
}
else if (board[x - 1][y - 1] != 0) //判断是否被占用
{
printf("坐标被占用,请重新输入\n");
continue;
}
else
{
board[x-1][y-1] = who; //重点——board[][]的值实际记录的是最近一次下棋的玩家
break;
}
}
}
board[x-1][y-1]
中‘-1’是为了对应数组下标。
board[x-1][y-1] = who;
中记录了最近一次下棋的玩家,对后续判断起很大作用
void ShowBoard(int board[][COL], int row, int col)
{
printf("\033c"); //清屏命令,使棋盘只有一个,每次下棋自动更新棋盘
printf(" "); //使列号对应 点
int i, j;
for (i = 1; i <= col; i++)
{
printf("%3d", i); //打印列号
}
printf("\n");
for (i = 0; i < row; i++)
{
printf("%2d", i + 1);//从1开始打印行数
for (j = 0; j < col; j++)
{
if (board[i][j] == 0)
{
printf(" . ");//空白处为.
}
else if (board[i][j] == PLAYER1)
{
printf("● ");//玩家1落子
}
else
{
printf("○ "); //玩家2落子
}
}
printf("\n");
}
}
数组中值为零的部分即未下棋的部分,打印" . ";
而每次玩家下棋的坐标,此坐标都会存放相应的值(由PlayerMove
),showboard
根据值打印相应的符号并进行清屏操作;
这就是玩家双方下棋棋盘打印的本质。
对于判断是否五子连珠,我们以一个坐标为基点,分别8个方向,而五子连珠的情况一共四种,每种连珠对应两个方向想加,这样就有了大致思路。
我们在game.h
定义的枚举类型就是为了对每种方向进行定义。
(枚举中各个方向的值不做规定,在ChessDire
中用switch_case
对应)
int IsOver(int board[][COL], int row, int col)
{
//定义四个连珠条件,每个连珠条件最后+1,因为五子连珠的情况包含基点坐标的棋子
int dire1 = ChessDire(board, row, col, LEFT) + ChessDire(board, row, col, RIGHT) + 1;
int dire2 = ChessDire(board, row, col, UP) + ChessDire(board, row, col, DOWN) + 1;
int dire3 = ChessDire(board, row, col, LEFT_UP) + ChessDire(board, row, col, RIGHT_DOWN) + 1;
int dire4 = ChessDire(board, row, col, LEFT_DOWN) + ChessDire(board, row, col, RIGHT_UP) + 1;
if (dire1 >= 5 || dire2 >= 5 || dire3 >= 5 || dire4 >= 5) //当连珠数量大于等于5(满足胜利条件)
{
//一定有人获胜
if (board[x - 1][y - 1] == PLAYER1)//对应PlayerMove中存储的玩家
{
return P1_WIN;
}
else {
return P2_WIN;
}
}
//没有玩家获胜
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == 0)//判断是否还有空位,有则继续
{
return NEXT;
}
}
}
//棋盘满了,平局
return DRAW;
}
在判断平局使我们是依靠棋盘是否有空位来判断的,不能像正统游戏中"尽管棋盘还有空位,但已经不存在连珠的可能了",这方面需要算法的知识。
统计连珠的思路是在一个方向后的两个坐标和作为基点的坐标是否相等,相等计数器+1,最后返回计数个数。
比如左边是否连珠,就需要使y坐标减少,再拿当前坐标和基点坐标比较。
int ChessDire(int board[][COL], int row, int col, enum Dire d)
{
int _x = x - 1; //判断某个方向的坐标时,实际用数组下标判断
int _y = y - 1;
int count = 0;
while (1) {
switch (d) {
case LEFT:
_y--;
break;
case RIGHT:
_y++;
break;
case UP:
_x--;
break;
case DOWN:
_x++;
break;
case LEFT_UP:
_x--, _y--;
break;
case LEFT_DOWN:
_x++, _y--;
break;
case RIGHT_UP:
_x--, _y++;
break;
case RIGHT_DOWN:
_x++, _y++;
break;
default:
//无操作
break;
}
if (_x < 0 || _x > row - 1 || _y < 0 || _y > col - 1)
{
//坐标移动完毕,一定要先保证没有越界
break;
}
if (board[x - 1][y - 1] == board[_x][_y])
{
//判定指定位置和原始位置的棋子是否相同,“连珠”就体现在这里
count++;
}
else
{
//没有连珠也没有越界,直接跳出
break;
}
}
return count;
}
在
count++
的if
语句中board[_x][_y]
是改变后的坐标,即某个方向的后两个坐标,board[x-1][y-1]
因为是数组下标所以需要-1
都在开头位置,这里主要放game.c
#define _CRT_SECURE_NO_DEPRECATE 1
#pragma warning(disable:4996)
#include "game.h"
void Menu()
{
printf("################################\n");
printf("##### 1.PLAY ###### 0.EXIT #####\n");
printf("################################\n");
printf("请输入选择:\n");
}
int x = 0, y = 0;
//按照x,y作为起点,按照特定的方向,求连续相对的最大格式
int ChessDire(int board[][COL], int row, int col, enum Dire d)
{
int _x = x - 1;
int _y = y - 1;
int count = 0;
while (1) {
switch (d) {
case LEFT:
_y--;
break;
case RIGHT:
_y++;
break;
case UP:
_x--;
break;
case DOWN:
_x++;
break;
case LEFT_UP:
_x--, _y--;
break;
case LEFT_DOWN:
_x++, _y--;
break;
case RIGHT_UP:
_x--, _y++;
break;
case RIGHT_DOWN:
_x++, _y++;
break;
default:
//无操作
break;
}
if (_x < 0 || _x > row - 1 || _y < 0 || _y > col - 1)
{
//坐标移动完毕,一定要先保证没有越界
break;
}
if (board[x - 1][y - 1] == board[_x][_y])
{
//判定指定位置和原始位置的棋子是否相同,“连珠”就体现在这里
count++;
}
else
{
break;
}
}
return count;
}
int IsOver(int board[][COL], int row, int col)
{
int dire1 = ChessDire(board, row, col, LEFT) + ChessDire(board, row, col, RIGHT) + 1;
int dire2 = ChessDire(board, row, col, UP) + ChessDire(board, row, col, DOWN) + 1;
int dire3 = ChessDire(board, row, col, LEFT_UP) + ChessDire(board, row, col, RIGHT_DOWN) + 1;
int dire4 = ChessDire(board, row, col, LEFT_DOWN) + ChessDire(board, row, col, RIGHT_UP) + 1;
if (dire1 >= 5 || dire2 >= 5 || dire3 >= 5 || dire4 >= 5)
{
//一定有人获胜
if (board[x - 1][y - 1] == PLAYER1)
{
return P1_WIN;
}
else {
return P2_WIN;
}
}
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == 0)
{
return NEXT;
}
}
}
return DRAW;
}
void ShowBoard(int board[][COL], int row, int col)
{
printf("\033c");
printf(" ");
int i,j;
for (i=1; i <= col; i++)
{
printf("%3d", i);
}
printf("\n");
for (i = 0; i < row; i++)
{
printf("%2d", i + 1);//从1开始打印行数
for (j = 0; j < col; j++)
{
if (board[i][j] == 0)
{
printf(" . ");//空白处为.
}
else if (board[i][j] == PLAYER1)
{
printf("● ");//玩家1落子
}
else
{
printf("○ "); //玩家2落子
}
}
printf("\n");
}
}
void PlayerMove(int board[][COL], int row, int col, int who)
{
while (1)
{
printf("Player[%d]输入下棋坐标:\n", who);
scanf("%d %d", &x, &y);
if (x<1 || x>row || y<1 || y>col)
{
printf("坐标不合法,请重新输入\n");
continue;
}
else if (board[x - 1][y - 1] != 0)
{
printf("坐标被占用,请重新输入\n");
continue;
}
else//合法性+去重
{
board[x-1][y-1] = who;
break;
}
}
}
void Game()
{
int board[ROW][COL];
memset(board, '\0', sizeof(board));
int result = NEXT;
do {
ShowBoard(board, ROW, COL);
PlayerMove(board, ROW, COL, PLAYER1);
result = IsOver(board, ROW, COL);
if (NEXT != result) {
break;
}
ShowBoard(board,ROW,COL);
PlayerMove(board, ROW, COL, PLAYER2);
result = IsOver(board, ROW, COL);
if (NEXT != result)
{
break;
}
} while (1);
//跳出循环证明已经出了结果
switch (result)
{
case P1_WIN:
printf("棋手1获胜!\n");
break;
case P2_WIN:
printf("棋手2获胜!\n");
break;
case DRAW:
printf("双方平局\n");
default:
break;
}
}