前言
这是我作为C语言入门者也是一名大一新生的第一篇博客,在此之前也经过了一小段时间的学习,成功写出了一个简易的三子棋小游戏,如有需改正之处,望大家批评指正。
思路
整个游戏的代码我计划分为两个源文件实现,第一个源文件test.c用于实现整个游戏的大体流程,第二个源文件game.c用于实现游戏具体步骤的逻辑,也因此需要一个头文件game.h来简化game.c中的函数在test.c中的声明。
在打开游戏时需要打印一份菜单供玩家选择开始游戏还是结束游戏,当玩家完成一盘游戏后,还需要询问玩家是否需要重新开始,因此可以通过循环来实现。
在游戏过程中,流程为打印空棋盘-->玩家下棋-->打印棋盘-->电脑下棋-->打印棋盘-->玩家下棋-->打印棋盘-->电脑下棋-->打印棋盘……循环至一方连成三颗棋或是棋盘被完全占用时,游戏结束。
游戏实现
头文件game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定义棋盘大小,可修改
#define ROW 5
#define COL 5
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);
头文件game.h的作用在于声明函数、包含系统头文件、以及定义常量。
声明函数:简化在tect.c中声明game.c中函数的过程
包含系统头文件:两个源文件中只需包含game.h即可
定义常量:只需在game.h中改变常量即可改变棋盘大小
源文件test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("******************************\n");
printf("**********1.开始游戏**********\n");
printf("**********0.结束游戏**********\n");
printf("******************************\n");
}
void game()
{
char board[ROW][COL];
char ret;
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("结束游戏");
break;
default:
printf("输入错误,请重新输入");
break;
}
}while (input);
return 0;
}
1.main函数
int main()
{
int input = 0;
srand((unsigned)time(NULL));
do//初始菜单
{
menu();//打印菜单
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("结束游戏");
break;
default:
printf("输入错误,请重新输入");
break;
}
}while (input);
return 0;
}
在main函数中我们需要实现游戏的大体流程。
因为需要允许多次游玩,并且至少游玩一次(开始游戏或结束游戏都算),所以使用do while循环。
此处使用变量input接受玩家菜单页面选择结果,可直接使用input作为do while循环的判断条件(0为假,非0为真)。
switch语句的使用是为了更加直观,也可使用if语句。
2.menu函数
void menu()
{
printf("******************************\n");
printf("**********1.开始游戏**********\n");
printf("**********0.结束游戏**********\n");
printf("******************************\n");
}
此函数负责打印菜单,目的在于使main函数中的思路更加直观。
menu函数效果
3.game函数
void game()
{
char board[ROW][COL];
char ret;
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");
}
此函数同样是为了使main函数中的思路更加直观,将整个三子棋游戏逻辑单独显示。
由于棋盘是二维的,因此需要创建一个二维数组并完全初始化。初始化后则需要将棋盘打印出来展示给玩家,此后需要通过循环时间玩家和电脑反复下棋并判断游戏是否结束。
源文件game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char board[ROW][COL], int row, int col)
{
int i, j;
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, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j != col - 1)
printf("|");
}
printf("\n");
if (i != row - 1)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j != col - 1)
printf("|");
}
}
printf("\n");
}
}
void PlayerMove(char board[ROW][COL], int row, int col)
{
while (1)
{
int x,y;
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] = '*';
break;
}
else
printf("输入坐标已被占用,请重新输入\n");
}
else
printf("输入坐标非法,请重新输入\n");
}
}
void ComputerMove(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] = '#';
break;
}
}
}
char IsWin(char board[ROW][COL], int row, int col)
{
int x, y;
for (x = 0; x < row - 2; x++)
{
for (y = 0; y < col; y++)//判断一列
{
if (board[x][y] == board[x + 1][y] && board[x + 1][y] == board[x + 2][y] && board[x][y] != ' ')
return board[x][y];
}
for (y = 0;y < col - 2; y++)//判断右斜
{
if (board[x][y] == board[x + 1][y + 1] && board[x + 1][y + 1] == board[x + 2][y + 2] && board[x][y] != ' ')
return board[x][y];
}
for (y = 3; y < col; y++)//判断左斜
{
if (board[x][y] == board[x + 1][y - 1] && board[x + 1][y - 1] == board[x + 2][y - 2] && board[x][y] != ' ')
return board[x][y];
}
}
for (x = 0; x < row; x++)
{
for (y = 0; y < col - 2; y++)//判断一列
{
if (board[x][y] == board[x][y + 1] && board[x][y + 1] == board[x][y + 2] && board[x][y] != ' ')
return board[x][y];
}
}
for (x = 0; x < row; x++)
{
for (y = 0; y < col; y++)//判断满格
{
if (board[x][y] == ' ')
return 'C';
}
}
return 'P';
}
1.InitBoard函数
void InitBoard(char board[ROW][COL], int row, int col)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
InitBoard函数通过两个for循环将二维数组全部初始化为' '。
2.DisplayBoard函数
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j != col - 1)
printf("|");
}
printf("\n");
if (i != row - 1)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j != col - 1)
printf("|");
}
}
printf("\n");
}
DisplayBoard函数同样是通过双重for循环打印出二维数组以及棋盘的分界线。
DisplayBoard函数效果
3.PlayerMove函数
void PlayerMove(char board[ROW][COL], int row, int col)
{
while (1)
{
int x,y;
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] = '*';
break;
}
else
printf("输入坐标已被占用,请重新输入\n");
}
else
printf("输入坐标非法,请重新输入\n");
}
}
当玩家下棋时,需要玩家成功完成下棋步骤才能够执行下一步,因此需要一个while(1)死循环使得当玩家输入坐标非法或被占用时能够重新下棋。
PlayerMove函数中需要注意二维数组的行和列都是从0开始,因此需要将玩家输入的坐标各减去1才能对应上。
PlayerMove函数效果
4.ComputerMove函数
void ComputerMove(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] = '#';
break;
}
}
}
为防止电脑随机下棋位置非法,使用rand()% row和 rand()% col限定随机数的只能取0到(row-1)或(col-1)。
同时为了防止电脑随机下棋位置已被占用后可以重新下棋,同样需要一个while(1)死循环和if语句判断并break。
ComputerMove函数效果
5.IsWin函数
char IsWin(char board[ROW][COL], int row, int col)
{
int x, y;
for (x = 0; x < row - 2; x++)
{
for (y = 0; y < col; y++)//判断一列
{
if (board[x][y] == board[x + 1][y] && board[x + 1][y] == board[x + 2][y] && board[x][y] != ' ')
return board[x][y];
}
for (y = 0;y < col - 2; y++)//判断右斜
{
if (board[x][y] == board[x + 1][y + 1] && board[x + 1][y + 1] == board[x + 2][y + 2] && board[x][y] != ' ')
return board[x][y];
}
for (y = 3; y < col; y++)//判断左斜
{
if (board[x][y] == board[x + 1][y - 1] && board[x + 1][y - 1] == board[x + 2][y - 2] && board[x][y] != ' ')
return board[x][y];
}
}
for (x = 0; x < row; x++)
{
for (y = 0; y < col - 2; y++)//判断一列
{
if (board[x][y] == board[x][y + 1] && board[x][y + 1] == board[x][y + 2] && board[x][y] != ' ')
return board[x][y];
}
}
for (x = 0; x < row; x++)
{
for (y = 0; y < col; y++)//判断满格
{
if (board[x][y] == ' ')
return 'C';
}
}
return 'P';
}
因为涉及到二维数组各个量,因此每次判断都需要双重for循环。
需要注意的是无论是判断一行、一列、左斜还是右斜,都需要限制x和y的范围,防止超出二维数组的范围。
另外还得确保对于是否满格平局的判断放在最后,即使判断满格和判断一列外层的for函数一致也不能放到一起,否则前四种情况还没完成判断时就返回了'C',导致即使玩家或电脑赢了,游戏依旧继续。
最后通过返回值判断电脑赢了、玩家赢了、平局还是继续。
结语
目前该函数还有优化的空间,尤其是对电脑下棋的算法进行设计,但目前我还不具备足够的能力去设计,日后也许会填坑。
第一次写博客的感觉很奇妙,本来是已经想好了大致的思路,但一上手后还是有点混乱。经过反复的打磨,我也对自己写的代码有了更深刻的认识,也许几天后就忘记的代码一下子就印在了脑海里,这让我更加确信写博客并不是浪费时间,而是对知识点的巩固,与此同时还能收获大家得指正,何乐而不为呢?
在这之后我也会利用闲暇时间将自己写过的代码以及学习经历写成博客分享出来,预计下一篇是这几天刚写完的扫雷游戏。