我们的C语言已经了解了数组的基础知识,下面我们将进行两个小项目,三子棋与扫雷,来对我们的数组与前面所学的知识做一个小的综合复习,加深印象
三子棋小游戏
在我们编写小游戏之前,我们首先需要对这个小游戏进行一个整体的框架性认识;
首先:大家应该都玩过三子棋,在九宫格中先完成三子连珠的那一方取得胜利,而今天,我们对三子棋进行两个模式的完成,人机对战or双人对战,因为我们还未接触到鼠标windows图形编程方面的知识,所以我们使用键盘对于小游戏进行操作
三子棋小游戏设计流程(自顶而下的设计方式):
基础准备:在这个游戏中,我们利用多文件来将项目模块化,分别为game.h,game.c以及main.c文件,在game.h中,我们来完成对项目的基础宏的声明以及其他准备工作,可以想到的是,我们的棋盘大小一定是3*3的,所以这组长宽一定是一组宏,而后三子棋的黑白落子,也一定是一组宏,剩下的等我们到需要设计时再来定义
//game.h
#ifndef __GAME_H__
#define __GAME_H__
#include<stdio.h>
#include<windows.h>
#define ROW 3
#define COL 3
#define WHITE 'X'
#define BLACK 'O'
#pragma warning(disable:4996)
#endif
1.设计菜单:
在我们正常的逻辑思维中,设计一款游戏映入眼帘的一定是菜单选项,所以我们需要先创建菜单,将其主架构放入菜单中,在我们这个三子棋游戏中,我们一共射击了两种模式,所以会对应有两个模式选项,以及一个退出选项,接下来我们便来完成菜单的设计
//main.C
#include"game.h"//
void Menu(){//建立菜单的主要面板
printf("+----------------------+\n");
printf("+1.Play-2.double-0.Exit+\n");
printf("+----------------------+\n");
}
int main(){
int select = 0;//用户输入
int quit = 0;//死循环控制菜单显示
while (!quit){
Menu();
printf("Please Select#");
scanf("%d", &select);
switch (select){
case 1:
Game1;
break;
case 2:
Game2;
break;
case 0:
quit = 1;//关闭死循环,停止菜单显示
break;
default:
printf("Enter Error,Try Again!\n");
break;
}
}
printf("ByeBye!\n");//退出提示
system("pause");
return 0;
}
此时我们便已经完成了对于这个游戏的最外层设计,现在最主要的就是在我们的函数实现文件game.c中去设计我们的游戏逻辑Game1(Game2与Game1有着相似之处,我们在最后会将其引入)
2.设计游戏逻辑
现在,我们来思考一下游戏的步骤,首先,需要一个棋盘,这个棋盘中除了我们的O与X之外应该还要有空格,所以我们需要增加一个宏InitBoard来代表空格,接下来我们就可以来下棋了,玩家来先下创建一个PlayerMove函数,我们下棋一定是重复的多次落子,所以外层会有一个while循环,我们需要看到落子的变化,所以在落子之前之后等地方一定会有一个ShowBoard函数来显示棋盘,当我们显示出来后,我们还应该需要一个IsEnd函数来判断落子之后的结果,,这时候又有一个很关键的判断了,那就是判断IsEnd函数结束的方式,结束的方式有几种呢?根据我们的经验,会有玩家胜利,电脑胜利,平局,以及走下一步,所以除了黑白两子获胜外还需要定义两个宏DRAW ‘D’以及NEXT‘N’, 那么在判断完毕之后除NEXT之外就都结束直接break了,没有break的话便会轮到电脑再来落子,判断方式依旧和玩家判断方式相同,当跳出这个判断落子情况的循环情况时,便会得出游戏的其他三种结束方式之一,所以在跳出之后还有一个switch case语句去判断,那么我们用代码来实现上述思考
//game.h
#define ROW 3
#define COL 3
#define WHITE 'X'
#define BLACK 'O'
#define DRAW 'D'
#define NEXT 'N'
void Game(){
char board[ROW][COL];//设置棋盘
InitBoard(board, ROW, COL);//初始化棋盘
char result = 0;//定义初始值接收落子结果
while (1){
ShowBoard(board, ROW, COL);//显示
PlayerMove(board, ROW, COL);//玩家落子
result = IsEnd(board, ROW, COL);//判断落子结果
if (result != NEXT){
break;
}
ShowBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);//电脑落子
result = IsEnd(board, ROW, COL);
if (result != NEXT){
break;
}
}
switch (result){//判断落子结果
case WHITE://最后退出的是白子
printf("You Win!\n");
break;
case BLACK://最后退出的是黑子
printf("You lose!\n");
break;
case DRAW://平局
printf("equals!\n");
break;
default:
printf("BUG!\n");
break;
}
}
3.设计函数
现在,我们主要的游戏框架便完成了,现在只需要解决这个框架中出现的函数就可以,因为我们不需要将我们游戏内部的函数对外部进行暴露,所以对游戏内部的函数都使用static关键字进行修饰,只需要对外部暴露一个Game就可以
static InitBoard(board, ROW, COL){}//初始化(棋盘)二维数组
static ShowBoard(board, ROW, COL){}//显示棋盘
static PlayerMove(board, ROW, COL){}//玩家落子
static IsEnd(board, ROW, COL){}//判断落子结果
static ComputerMove(board, ROW, COL){}//电脑落子
现在我们对这些函数一一进行实现
InitBoard函数
这个函数作用是对二维数组进行初始化,初始化为空格
static void InitBoard(char board[][COL], int row, int col){
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++){
board[i][j] = 'INIT';//使用INIT宏而不使用含义相同的‘ ’ 对棋盘进行初始化,方便后期拓展维护
}
}
}
PlayerMove函数
这个函数的作用是控制玩家落子,首先,玩家落子必须落正确的位置,而且肯定不止一次,循环尝试,所以外部会有while循环来控制合法输入,其次,落子一定是通过输入二维数组坐标的方式来完成的,当玩家落子完成,便开始对落子坐标进行合法性判断(是否在3*3棋盘中,是否落子在可以落子的地方),当用户落子坐标合法,可以进行落子时,便开始落子
static void PlayerMove(char board[][COL], int row, int col){
while (1){//死循环重复判断
int x = 0;
int y = 0;
printf("Please Enter Postion<x,y>#");
scanf("%d %d",&x, &y);//玩家输入坐标
if (x < 1 || y < 1 || x>3 || y>3){//判断是否在棋盘内
printf("Enter Postion Error!\n");
continue;
}
if (board[x - 1][y - 1] == INIT){//判断落子地点是否为空(是否已经落子)
board[x - 1][y - 1] = WHITE;//玩家落白子
break;
}
else{//已经落子,返回while重新输入
printf("Enter Is Not Empty!\n");
}
}
}
IsEnd函数
我们的IsEnd函数用来判断落子后的情况判断,分为4种情况分别判断
static char IsEnd(char board[][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][0] != INIT){
return board[i][0];
}
}
for (int j = 0; j < COL; j++){
if (board[0][j] == board[1][j] && \
board[1][j] == board[2][j] && \
board[0][j] != INIT){
return board[0][j];
}
}
if (board[0][0] == board[1][1] && \
board[1][1] == board[2][2] && \
board[1][1] != INIT){
return board[1][1];
}
if (board[0][2] == board[1][1] && \
board[1][1] == board[2][0] && \
board[1][1] != INIT){
return board[1][1];
}
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++){
if (board[i][j] == INIT){
return NEXT;
}
}
}
return DRAW;
}
ComputerMove函数
ComputerMove和PlayerMove类似,不过电脑生成棋子是随机的,此时我们就需要利用随机数去生成坐标,并且再去判断位置是否为空,是否合法,若合法,则再去落子,下面我们完成电脑的落子
static void ComputerMovechar(char board[][COL], int row, int col){
while(1){
int x = rand() % row;//随机的x值模3后为0,1,2
int y = rand() % col;//随机的y值模3后为0,1,2
if (board[x][y] == INIT){//判断为空,合法
board[x][y] = BLACK;//落黑子
break;
}
}
}
ShowBoard函数
注意,我们先在画图板上构思一下棋盘的样子,是这个样子的,对于空格进行一系列的优化处理之后,得出代码
static void ShowBoard(char board[][COL], int row, int col)
{
system("cls");//清理屏幕(使得我们的三子棋在一张图上完成)
printf(" ");
for (int i = 0; i < col; i++){
printf("%4d", i + 1);//预留四个空位
}
printf("\n--------------\n");
for (int i = 0; i < row; i++){
printf("%-2d", i + 1); //2
for (int j = 0; j < col; j++){
printf("| %c ", board[i][j]); //12
}
printf("\n--------------\n");
}
}
此时便完成了我们的三子棋小游戏,
那么我们如果想加上双人模式呢,只需要将PlayerMove拷贝一份在修改名称与最后的落子名称,而后再拷贝Game在Game2中将ComeputerMove换成Player2Move,将这两段代码加到原先的代码中即可。
//game.c
static void Player2Move(char board[][COL], int row, int col){
while (1){//死循环重复判断
int x = 0;
int y = 0;
printf("Please Enter Postion<x,y>#");
scanf("%d %d", &x, &y);//玩家输入坐标
if (x < 1 || y < 1 || x>3 || y>3){//判断是否在棋盘内
printf("Enter Postion Error!\n");
continue;
}
if (board[x - 1][y - 1] == INIT){//判断落子地点是否为空(是否已经落子)
board[x - 1][y - 1] = BLACK;//玩家落白子
break;
}
else{
printf("Enter Is Not Empty!\n");
}
}
}
void Game2(){
char board[ROW][COL];//设置棋盘
InitBoard(board, ROW, COL);//初始化棋盘
//srand((unsigned long)time(NULL));
char result = 0;//定义初始值接收落子结果
while (1){
ShowBoard(board, ROW, COL);//显示
PlayerMove(board, ROW, COL);//玩家落子
result = IsEnd(board, ROW, COL);//判断落子结果
if (result != NEXT){
break;
}
ShowBoard(board, ROW, COL);
Player2Move(board, ROW, COL);//二号玩家落子
result = IsEnd(board, ROW, COL);
if (result != NEXT){
break;
}
}
ShowBoard(board, ROW, COL);
switch (result){//判断落子结果
case WHITE://最后退出的是白子
printf("WHITE Win!\n");
break;
case BLACK://最后退出的是黑子
printf("BLACK lose!\n");
break;
case DRAW://平局
printf("equals!\n");
break;
default:
printf("BUG!\n");
break;
}
}
下面我们演示完整的三子棋练习代码
//game.h
#ifndef __GAME_H__
#define __GAME_H__
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <windows.h>
#define ROW 3
#define COL 3
#define INIT ' '
#define WHITE 'X' //Player
#define BLACK 'O' //Computer
#define DRAW 'D'
#define NEXT 'N'
#pragma warning(disable:4996)
extern void Game();
#endif
//game.c
#include"game.h";
static void InitBoard(char board[][COL], int row, int col){
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++){
board[i][j] = INIT;
}
}
}//初始化(棋盘)二维数组
static void ShowBoard(char board[][COL], int row, int col)
{
system("cls");
printf(" ");
for (int i = 0; i < col; i++){
printf("%4d", i + 1);
}
printf("\n--------------\n");
for (int i = 0; i < row; i++){
printf("%-2d", i + 1); //2
for (int j = 0; j < col; j++){
printf("| %c ", board[i][j]); //12
}
printf("\n--------------\n");
}
}
static void PlayerMove(char board[][COL], int row, int col){
while (1){//死循环重复判断
int x = 0;
int y = 0;
printf("Please Enter Postion<x,y>#");
scanf("%d %d",&x, &y);//玩家输入坐标
if (x < 1 || y < 1 || x>3 || y>3){//判断是否在棋盘内
printf("Enter Postion Error!\n");
continue;
}
if (board[x - 1][y - 1] == INIT){//判断落子地点是否为空(是否已经落子)
board[x - 1][y - 1] = WHITE;//玩家落白子
break;
}
else{
printf("Enter Is Not Empty!\n");
}
}
}//玩家落子
static void Player2Move(char board[][COL], int row, int col){
while (1){//死循环重复判断
int x = 0;
int y = 0;
printf("Please Enter Postion<x,y>#");
scanf("%d %d", &x, &y);//玩家输入坐标
if (x < 1 || y < 1 || x>3 || y>3){//判断是否在棋盘内
printf("Enter Postion Error!\n");
continue;
}
if (board[x - 1][y - 1] == INIT){//判断落子地点是否为空(是否已经落子)
board[x - 1][y - 1] = BLACK;//玩家落白子
break;
}
else{
printf("Enter Is Not Empty!\n");
}
}
}
static char IsEnd(char board[][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][0] != INIT){
return board[i][0];
}
}
for (int j = 0; j < COL; j++){
if (board[0][j] == board[1][j] && \
board[1][j] == board[2][j] && \
board[0][j] != INIT){
return board[0][j];
}
}
if (board[0][0] == board[1][1] && \
board[1][1] == board[2][2] && \
board[1][1] != INIT){
return board[1][1];
}
if (board[0][2] == board[1][1] && \
board[1][1] == board[2][0] && \
board[1][1] != INIT){
return board[1][1];
}
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++){
if (board[i][j] == INIT){
return NEXT;
}
}
}
return DRAW;
}//判断落子结果
static void ComputerMove(char board[][COL], int row, int col){
while (1){
int x = rand() % row;//随机的x值模3后为0,1,2
int y = rand() % col;//随机的y值模3后为0,1,2
if (board[x][y] == INIT){//判断为空,合法
board[x][y] = BLACK;//落黑子
break;
}
}
}//电脑落子
void Game(){
char board[ROW][COL];//设置棋盘
InitBoard(board, ROW, COL);//初始化棋盘
srand((unsigned long)time(NULL));
char result = 0;//定义初始值接收落子结果
while (1){
ShowBoard(board, ROW, COL);//显示
PlayerMove(board, ROW, COL);//玩家落子
result = IsEnd(board, ROW, COL);//判断落子结果
if (result != NEXT){
break;
}
ShowBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);//电脑落子
result = IsEnd(board, ROW, COL);
if (result != NEXT){
break;
}
}
ShowBoard(board, ROW, COL);
switch (result){//判断落子结果
case WHITE://最后退出的是白子
printf("You Win!\n");
break;
case BLACK://最后退出的是黑子
printf("You lose!\n");
break;
case DRAW://平局
printf("equals!\n");
break;
default:
printf("BUG!\n");
break;
}
}
void Game2(){
char board[ROW][COL];//设置棋盘
InitBoard(board, ROW, COL);//初始化棋盘
//srand((unsigned long)time(NULL));
char result = 0;//定义初始值接收落子结果
while (1){
ShowBoard(board, ROW, COL);//显示
PlayerMove(board, ROW, COL);//玩家落子
result = IsEnd(board, ROW, COL);//判断落子结果
if (result != NEXT){
break;
}
ShowBoard(board, ROW, COL);
Player2Move(board, ROW, COL);//二号玩家落子
result = IsEnd(board, ROW, COL);
if (result != NEXT){
break;
}
}
ShowBoard(board, ROW, COL);
switch (result){//判断落子结果
case WHITE://最后退出的是白子
printf("WHITE Win!\n");
break;
case BLACK://最后退出的是黑子
printf("BLACK lose!\n");
break;
case DRAW://平局
printf("equals!\n");
break;
default:
printf("BUG!\n");
break;
}
}
//main.c
#include"game.h"
void Menu(){//建立菜单的主要面板
printf("+----------------------+\n");
printf("+1.Play-2.double-0.Exit+\n");
printf("+----------------------+\n");
}
int main(){
int select = 0;//用户输入
int quit = 0;//死循环控制菜单显示
while (!quit){
Menu();
printf("Please Select#");
scanf("%d", &select);
switch (select){
case 1:
Game();
break;
case 2:
Game2();
break;
case 0:
quit = 1;//关闭死循环,停止菜单显示
break;
default:
printf("Enter Error,Try Again!\n");
break;
}
}
printf("ByeBye!\n");//退出提示
system("pause");
return 0;
}
这便是我们三子棋小游戏所有的代码,下面我们对其进行思维导图逻辑总结
这便是整个三子棋小游戏的实现与总结,共同学习,共同进步