目录
一. 游戏规则
相信大家都有玩过五子棋,三子棋游戏则是五子棋的缩小版,游戏规则为,所下棋子首先连成三个一条线的一方获胜,如果棋盘下满双方都没有获胜,则视为平局。玩完一局后,玩家可以根据自己的意愿选择继续游戏或者退出游戏。游戏代码用多文件形式完成。
二. 知识储备
此游戏的实现,对while,do while循环语句,if else多分支语句,switch选择语句,以及函数的调用,数组等知识进行了综合应用。对于初学者来说很有综合性,能够很好的检验自己的c语言前期学习效果。此外代码中rand函数以及srand函数的解释见笔者上一篇博客,猜数字游戏。
三. 代码实现
3.1 多文件理解形式
在实际工作中,往往一个大型项目的开发需要很多人的团队一起来完成,如果在一个文件里面写代码这并不现实。因此对于大型项目的开发,都是将其分割成不同功能不同模块,分开完成再组合起来应用。这种将代码封装成不同功能的模块的形式,也能够使得代码维护变得相对容易。本游戏的代码实现,对多文件完成代码进行了简单应用,希望对初学者有所帮助。
3.2 代码实现
3.2.1 test.c
此文件中包含主函数,游戏主体,及游戏菜单函数的代码。
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//三子棋游戏,玩完一把不退出,应用多文件形式写代码
//菜单
void menu()
{
printf("****************************\n");
printf("********** 1.paly **********\n");
printf("********** 0.exit **********\n");
printf("****************************\n");
}
//游戏主体
void game()
{
char Board[ROW][COL] = {0};
char ret = 0;
InitBoard(Board,ROW,COL);
DisplayBoard(Board, ROW, COL);
while (1)
{
PlayerMove(Board, ROW, COL);
DisplayBoard(Board, ROW, COL);
//判断输赢
ret = isWin(Board, ROW, COL);
if (ret != 'C')
break;
ComputerMove(Board, ROW, COL);
DisplayBoard(Board, ROW, COL);
ret = isWin(Board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*')
{
printf("玩家胜利!\n");
}
else if (ret == '#')
{
printf("电脑胜利!\n");
}
else
printf("平局!\n");
}
int main()
{
int input = 0;
srand((unsigned)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新选择:>\n");
break;
}
} while (input);
return 0;
}
各种头文件的引用,及函数的声明都放在game.h这个头文件里,因此在test.c中要包含 #include "game.h",注意是用的双引号并不是尖括号。进入主函数,首先打印菜单,根据菜单的提示进行选择,开始游戏或者退出游戏,由switch选择语句完成,用do while循环语句实现游戏的循环。
选择1开始游戏后,则进入game函数,定义一个char类型的二维数组,用其表示棋盘,并用函数对其初始化,将数组元素都初始化为空格。接着通过DispalyBoard函数对棋盘进行优化,使其呈现出边框。 然后进入while循环,通过PlayerMove函数ComputerMove函数分别实现双方的落子,每方落子之后都再次通过DispalyBoard函数打印棋盘。并且每一方落子之后都要通过isWin函数判断输赢,并将返回值传给ret。
游戏菜单 棋盘
3.2.2 game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void InitBoard(char Board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
for (j = 0;j < col;j++)
Board[i][j] = ' ';
}
void DisplayBoard(char Board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
//打印数据
for (j = 0;j < row;j++)
{
printf(" %c ", Board[i][j]);
if ((j < col - 1))
printf("|");//保持棋盘竖分割线的连续性
}
printf("\n");
//打印棋盘分割线
for (j = 0;j < row;j++)
{
if (i < row - 1)
printf("---");
if ((i < row - 1) && (j < col - 1))
printf("|");
}
printf("\n");
}
}
void PlayerMove(char Board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋:>\n");
while (1)
{
printf("请选择要下的坐标,用空格隔开:>");
scanf("%d %d", &x, &y);
if ((x >= 1) && (x <= row) && (y >= 1) && (y <= col))
{
if (Board[x - 1][y - 1] == ' ')
{
printf("可以落子\n");
Board[x - 1][y - 1] = '*';
break;
}
else
{
printf("落子失败,坐标已被占用\n");
}
}
else
{
printf("坐标非法,请重新选择坐标:>\n");
}
}
}
void ComputerMove(char Board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋:>\n");
while(1)
{
x = rand() % row;
y = rand() % col;
if (Board[x][y] == ' ')
{
Board[x][y] = '#';
break;
}
}
}
int isFull(char Board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
if (Board[i][j] == ' ')
return 0;
}
}
return 1;
}
char isWin(char Board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
int count1 = 0;
int count2 = 0;
//行
for (i = 0;i < row;i++)
{
count1 = 0;
count2 = 0;
for (j = 0;j < col;j++)
{
if (Board[i][j] == '*')
count1++;
if (Board[i][j] == '#')
count2++;
}
if (count1 == col)
{
return '*';
}
if (count2 == col)
{
return '#';
}
}
//列
for (j = 0;j < col;j++)
{
count1 = 0;
count2 = 0;
for (i = 0;i < row;i++)
{
if (Board[i][j] == '*')
count1++;
if (Board[i][j] == '#')
count2++;
}
if (count1 == row)
{
return '*';
}
if (count2 == row)
{
return '#';
}
}
//主对角线
count1 = 0;
count2 = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
if (i == j)
{
if (Board[i][j] == '*')
count1++;
if (Board[i][j] == '#')
count2++;
}
if (count1 == row)
{
return '*';
}
if (count2 == row)
{
return '#';
}
}
}
//副对角线
count1 = 0;
count2 = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
if (i + j == row - 1)
{
if (Board[i][j] == '*')
count1++;
if (Board[i][j] == '#')
count2++;
}
if (count1 == row)
{
return '*';
}
if (count2 == row)
{
return '#';
}
}
}
if (isFull(Board, row, col) == 1)
return 'Q';
return 'C';
}
此文件中包含棋盘初始化函数,棋盘打印函数,玩家落子函数,电脑落子函数,判断输赢及棋盘是否已满函数。InitBoard函数通过两层for循环实现对二维数组表示的棋盘的全部元素初始化为空格;DisplayBoard函数,外层for循环控制行数的递增,里面还嵌套两个for循环,第一个for循环实现对数据的打印,按照空格、数据、空格加‘ | ’ 的方式打印,‘ | ’是棋盘的竖分割线,为了保证棋盘的美观性,每一行的最后一列不打印‘ | ’。第二个for循环实现对棋盘剩余分割线的打印,逐行打印,打印完一行后换行;PlayerMove函数实现玩家落子,玩家的落子方式按正常从1开始算每行每列,因此写代码时要注意对玩家输入的坐标进行减1的操作,来对应数组下标;ComputerMove函数实现电脑落子,借助rand函数实现电脑随机下棋;isFull函数判断棋盘是否已满,如果其中有一个元素是空格则返回0,否则返回1;isWin函数判断输赢,分别判断行、列、主对角线、副对角线元素,用count自增计数的方式累计一行中相等元素的个数来判断输赢。
3.2.3 game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3
//棋盘初始化
void InitBoard(char Board[ROW][COL], int row, int col);
//打印棋盘
void DisplayBoard(char Board[ROW][COL], int row, int col);
//玩家下棋
void PlayerMove(char Board[ROW][COL], int row, int col);
//电脑下棋
void ComputerMove(char Board[ROW][COL], int row, int col);
//判断输赢
char isWin(char Board[ROW][COL], int row, int col);