目录
前言
五子棋,又称连珠棋,是一种双人对弈的棋类游戏。游戏目标是在一个棋盘上,通过在横、竖、斜线上依次放置棋子,使自己的五个棋子连成一线,即横线、竖线或斜线,且无被对手堵住的空位,从而获胜。
实现简单的五子棋游戏,需要有二维数组、函数调用等知识。本项目代码量大致为两三百行。
一、游戏规则
1.功能分析
五子棋的功能比较简单,只有黑棋落子和白棋落子,有些五子棋还有悔棋功能、显示胜率、记录走的步数等功能,但是初学时可以只实现简单的落子功能即可。
2.玩法分析
由黑棋先走,鼠标左击即可落子,然后轮到白棋回合,如此循环直到游戏结束。
3.胜负判定条件
五子棋的棋盘通常为15×15的格子,双方轮流在空位上放置自己的棋子,先连成五子的一方获胜。如果棋盘填满但没有五子连成一线,游戏结束为平局。
二、游戏实现思路
- 使用坐标输入代替鼠标点击,坐标应该符合人们使用习惯从1开始;(【鼠标左击】功能)
- 若有人胜利提示胜利方为谁,并结束游戏;
- 若无人胜利,且棋盘未满,提示继续;
- 若棋盘已满,提示平局;
- 实现清屏功能,每次落完子之后刷新屏幕;
- 可以加入进度条,在游戏开始时展示。
三、代码实现与函数封装
1.项目文件创建
将源代码分为五个文件,两个头文件(.h),用于声明函数,分别声明进度条和游戏函数;
三个源文件(.c),两个用于写函数体,实现进度条和游戏主体,一个为主函数,调用函数。
2.头文件说明
1.为了防止在项目中多次申明同一个头文件,需要加入防重复判断语句:
cpp
#pragma once
2.头文件中可以直接引用需要的标准库,然后在源文件中只需要引用自己写的头文件即可;
cpp
#include "game.h"
#include "ProBar.h"
3.函数封装
棋盘设置为15×15,且可更改方便之后的更改。
1)菜单实现
cpp
void Menu()
{
printf("#########################\n");
printf("### 1.Play 0.Exit ###\n");
printf("#########################\n");
printf("Please Select:> ");
}
2)进度条实现
'\r'使光标每次回到开头的位置,使用fflush函数刷新输出界面,并使用usleep函数进行延迟,达到很好的效果。
cpp
#define NUM 100
void process_bar()
{
char bar[NUM+1];
memset(bar, '\0', sizeof(bar));
const char* lable = "|/-\\";
int i = 0;
while(i <= NUM)
{
printf("Load...[%-100s][%-3d%%][%c]\r", bar, i, lable[i%4]);
fflush(stdout);
bar[i++] = '#';
usleep(10000);
}
printf("\n");
}
3)main函数实现
使用quit变量来控制循环的退出;
Game函数实现游戏的主体。
cpp
int main()
{
int quit = 0;
int select = 0;
while(!quit)
{
Menu();
scanf("%d", &select);
switch(select)
{
case 1:
process_bar();
Game();
break;
case 0:
quit = 1;
printf("Exit Success!\n");
break;
defualt :
printf("Enter Error, Try Again!\n");
break;
}
}
return 0;
}
4)Game函数
- 建立棋盘
- 使用memset函数初始化棋盘;
- do...while执行游戏运行;
- IsOver函数判断游戏是否结束;
- PlayerMove函数玩家走一步棋;
- Showboard 打印出棋盘;
- switch...case 显示出游戏结果。
cpp
#define ROW 20
#define COL 20
#define PLAYER1 1
#define PLAYER2 2
#define NEXT 0
#define PLAYER1_WIN 1
#define PLAYER2_WIN 2
#define DRAW 3
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);
//p1 win , p2 win, draw
ShowBoard(board, ROW, COL);
switch(result)
{
case PLAYER1_WIN:
printf("congratulate Player1, you win!\n");
break;
case PLAYER2_WIN:
printf("congratulate player2, you win!\n");
break;
case DRAW:
printf("draw!\n");
break;
default:
break;
}
}
5)ShowBoard函数实现
刷新屏幕
printf("\033c");
cpp
void ShowBoard(int board[ROW][COL], int row, int col)
{
//clear screen
//printf("\e[1;1H\e[2J")"]]");
//printf(" ");
printf("\033c");
printf("\n\n ");
for(int i=0; i<col; i++)
{
printf("%3d", i+1);
}
printf("\n");
for(int i=0; i<row; i++)
{
printf("%2d ", i+1);
for(int j=0; j<col; j++)
{
if(board[i][j] == 0)
{
printf(" . ");
}
else if(board[i][j] == PLAYER1)
{
printf(" x ");
}
else
{
printf(" o ");
}
}
printf("\n");
}
}
6)PlayerMove函数实现
int x = 0;
int y = 0;
设置为全局变量,由用户输入,从1开始
cpp
int x = 0;
int y = 0;
void PlayerMove(int board[ROW][COL], int row, int col, int player)
{
while(1)
{
printf("\nPlayer[%d] Please Enter Your Pos:>", player);
scanf("%d %d", &x, &y);
//判断合法坐标
if(x<1 || x> row || y<1 || y>col)
{
printf("Pos is not right!\n");
continue;
}
else if(board[x-1][y-1] != 0)
{
printf("Pos is occpuied!\n");
continue;
}
else{
board[x-1][y-1] = player;//谁落子,就放置谁的数据
break;
}
}
}
7)ChessCount函数实现
要判断是否继续下棋需要判断是否已经五子连珠,就需要数下的这个子周围有没有出现五子连珠,由于每次下棋都会判断一次,所以不会出现漏判的情况。
cpp
enum Dir
{
LEFT,
RIGHT,
UP,
DOWN,
LEFT_UP,
LEFT_DOWN,
RIGHT_UP,
RIGHT_DOWN
};
int ChessCount(int board[ROW][COL], int row, int col, enum Dir 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:
//Do nothing
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;
}
8)IsOver函数实现
任何落子位置都有八个方向,所以判定五子连珠,本质是判定1,5方向之和,2,6方向之和,3,7方向之和,4,8方向 之和,其中任意一个出现相同的连续五个棋子,即游戏结束
cpp
int IsOver(int board[ROW][COL], int row, int col)
{
//注意,每次统计的时候,都没有统计当前节点,需要单独+1
int count1 = ChessCount(board, row, col, LEFT) +
ChessCount(board, row, col, RIGHT) + 1;
int count2 = ChessCount(board, row, col, UP) +
ChessCount(board, row, col, DOWN) + 1;
int count3 = ChessCount(board, row, col, LEFT_UP) +
ChessCount(board, row, col, RIGHT_DOWN) + 1;
int count4 = ChessCount(board, row, col, LEFT_DOWN) +
ChessCount(board, row, col, RIGHT_UP) + 1;
if(count1 >= 5 || count2>=5 || count3>=5 || count4>=5)
{
//谁赢返回谁
//return board[x-1][y-1];
if(board[x-1][y-1] == PLAYER1)
{
return PLAYER1_WIN;
}
else
{
return PLAYER2_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.main.c
cpp
#include "game.h"
#include "ProBar.h"
int main()
{
int quit = 0;
int select = 0;
while(!quit)
{
Menu();
scanf("%d", &select);
switch(select)
{
case 1:
process_bar();
Game();
break;
case 0:
quit = 1;
printf("Exit Success!\n");
break;
defualt :
printf("Enter Error, Try Again!\n");
break;
}
}
return 0;
}
2.ProBar.h
cpp
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define NUM 100
void process_bar();
3.probar.c
cpp
#include "ProBar.h"
void process_bar()
{
char bar[NUM+1];
memset(bar, '\0', sizeof(bar));
const char* lable = "|/-\\";
int i = 0;
while(i <= NUM)
{
printf("Load...[%-100s][%-3d%%][%c]\r", bar, i, lable[i%4]);
fflush(stdout);
bar[i++] = '#';
usleep(10000);
}
printf("\n");
}
4.game.h
cpp
#include <string.h>
#include <stdlib.h>
#define ROW 20
#define COL 20
#define PLAYER1 1
#define PLAYER2 2
#define NEXT 0
#define PLAYER1_WIN 1
#define PLAYER2_WIN 2
#define DRAW 3
enum Dir
{
LEFT,
RIGHT,
UP,
DOWN,
LEFT_UP,
LEFT_DOWN,
RIGHT_UP,
RIGHT_DOWN
};
void Menu();
void Game();
int IsOver(int board[ROW][COL], int row, int col);
void ShowBoard(int board[ROW][COL], int row, int col);
int ChessCount(int board[ROW][COL], int row, int col, enum Dir d);
void Playermove(int board[ROW][COL], int row, int col, int player);
5.game.c
cpp
#include "game.h"
#include "ProBar.h"
int x = 0;
int y = 0;
void Menu()
{
printf("#########################\n");
printf("### 1.Play 0.Exit ###\n");
printf("#########################\n");
printf("Please Select:> ");
}
//four possbilities:
//NEXT: continue
//1: 1 win
//2: 2 win
//3: draw
int IsOver(int board[ROW][COL], int row, int col)
{
//import && hard
//wu zi lian zhu
int count1 = ChessCount(board, row, col, LEFT) +
ChessCount(board, row, col, RIGHT) + 1;
int count2 = ChessCount(board, row, col, UP) +
ChessCount(board, row, col, DOWN) + 1;
int count3 = ChessCount(board, row, col, LEFT_UP) +
ChessCount(board, row, col, RIGHT_DOWN) + 1;
int count4 = ChessCount(board, row, col, LEFT_DOWN) +
ChessCount(board, row, col, RIGHT_UP) + 1;
if(count1 >= 5 || count2>=5 || count3>=5 || count4>=5)
{
//return board[x-1][y-1];
if(board[x-1][y-1] == PLAYER1)
{
return PLAYER1_WIN;
}
else
{
return PLAYER2_WIN;
}
}
for(int i=0; i<row; i++)
{
for(int j=0; j<col; j++)
{
if(board[i][j] == 0)
{
return NEXT;
}
}
}
return DRAW;
}
int ChessCount(int board[ROW][COL], int row, int col, enum Dir 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:
//Do nothing
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;
}
void ShowBoard(int board[ROW][COL], int row, int col)
{
//clear screen
//printf("\e[1;1H\e[2J")"]]");
//printf(" ");
printf("\033c");
printf("\n\n ");
for(int i=0; i<col; i++)
{
printf("%3d", i+1);
}
printf("\n");
for(int i=0; i<row; i++)
{
printf("%2d ", i+1);
for(int j=0; j<col; j++)
{
if(board[i][j] == 0)
{
printf(" . ");
}
else if(board[i][j] == PLAYER1)
{
printf(" x ");
}
else
{
printf(" o ");
}
}
printf("\n");
}
}
void PlayerMove(int board[ROW][COL], int row, int col, int player)
{
while(1)
{
printf("\nPlayer[%d] Please Enter Your Pos:>", player);
scanf("%d %d", &x, &y);
if(x<1 || x> row || y<1 || y>col)
{
printf("Pos is not right!\n");
continue;
}
else if(board[x-1][y-1] != 0)
{
printf("Pos is occpuied!\n");
continue;
}
else{
board[x-1][y-1] = player;
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);
//p1 win , p2 win, draw
ShowBoard(board, ROW, COL);
switch(result)
{
case PLAYER1_WIN:
printf("congratulate Player1, you win!\n");
break;
case PLAYER2_WIN:
printf("congratulate player2, you win!\n");
break;
case DRAW:
printf("draw!\n");
break;
default:
break;
}
}
总结
实际上这个版本还是一个非常简易的版本,在之后学习到别的模块之后可以对这个项目再进行改进,比如可以尝试以下功能:
- 人机对战
- 功能扩展:颜色提示,步数记录,先手随机交换等
- 网络版本
码云Gitee项目链接:GoBangGame · Kevin Ray/LinuxPractice - 码云 - 开源中国 (gitee.com)