文章目录
- 准备工作
- 创建菜单
- 进入游戏
- 初始化棋盘、打印棋盘
- 玩家下棋、电脑下棋、生成随机数
- 判断输赢
大家好!时隔多天,我终于写博客了,真的是开心!这一次带来的是N子棋有双人对战和单人下棋,请认真看下去,我会竭尽所能讲清楚整个思路!如果还有疑问的话,我们可以进行探讨!
首先给大家展示代码:
这是test.c:
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void game()
{
char board[ROW][COL];
char ret = 0;
init_board(board, ROW, COL);
print_board(board, ROW, COL);
while (1)
{
player1_move(board, ROW, COL);
print_board(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
computer_move(board, ROW, COL);
print_board(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
printf("玩家1赢了!\n");
else if (ret == '#')
printf("电脑赢了!\n");
else if (ret == 'P')
printf("平局!\n");
}
void menu()
{
printf(" 1. play \n");
printf(" 0. exit \n");
printf(" please enter:");
}
void test()
{
srand((unsigned int) time(NULL));
int i = 0;
do
{
menu();
scanf("%d", &i);
switch (i)
{
case 1:
game();
break;
case 0:
printf(" quit game\n");
break;
default:
printf(" please enter again!\n");
break;
}
} while (i);
}
int main()
{
test();
return 0;
}
这是game.c:
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void init_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
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 player1_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家1请下棋:");
scanf("%d %d", &x, &y);
if (x >= 1 && x - 1 < row && y >= 1 && y - 1 < col)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("已被占用!");
}
}
else
{
printf("非法输入!");
}
}
}
void computer_move(char board[ROW][COL], int row, int col)
{
int x = rand() % 3;
int y = rand() % 3;
while (1)
{
printf("电脑请下棋:");
scanf("%d %d", &x, &y);
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
static int is_full(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)
return board[i][j - 1];
}
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)
return board[j - 1][i];
}
int flag = 0;
for (i = 1; i < row; i++)
{
if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')
flag++;
if (flag == KEY - 1)
return board[i][i];
}
flag = 0;
int j = col - 1;
for (i = 1; i < row; i++)
{
if (board[i][j - 1] == board[i - 1][j] && board[i - 1][j] != ' ')
{
flag++;
j--;
}
if (flag == KEY - 1)
return board[i][0];
}
int ret = is_full(board, ROW, COL);
if (ret)
return 'P';
return 'C';
}
这是game.h:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 10
#define COL 10
#define KEY 5
void init_board(char board[ROW][COL], int row, int col);
void print_board(char board[ROW][COL], int row, int col);
void player1_move(char board[ROW][COL], int row, int col);
void computer_move(char board[ROW][COL], int row, int col);
char is_win(char board[ROW][COL], int row, int col);
准备工作
在进行设计一个游戏前,我们要思考游戏分为什么部分 ,根据模块逐个进行编程,并且写完一部分就要进行调试,看是否出错!
我们可以从简单入手,先设计一个三子棋,然后再进行扩展。
先创建文件,创建一个test.c 文件作为主函数的部分,然后创建游戏文件game.c 文件作为游戏主体的部分,最后创建一个game.h 文件作为声明函数的文件,如图:
注意:这里可以将<stdio.h>头文件放在game.h中,然后再test.c 和game.c 中引用game.h 文件,#include"stdio.h"。
创建菜单
当创建完文件之后,就可以进行写代码了。首先进入一个游戏,映入眼帘的是菜单,玩还是不玩?所以先可以创建菜单了。
void menu()//菜单说明
{
printf(" 1. play \n");
printf(" 0. exit \n");
printf(" please enter:");
}
void test()
{
int i = 0;//进行选择
do
{
menu();//进行打印菜单
scanf("%d", &i);
switch (i)
{
case 1://选择1进行游戏
game();//游戏主体部分
break;
case 0://选择0退出游戏
printf(" quit game\n");
break;
default://选择其它数字就进行报错
printf(" please enter again!\n");
break;
}
} while (i);//为0跳出循环,退出游戏
}
int main()
{
test();//测试部分
return 0;
}
//这里用到do while 语句很合适,进入游戏,不管你玩不玩,游戏界面是一定
//要显示的,体现了do while 语句的先执行一次的特点!
进入游戏
那么创建完菜单之后,进行选择,选择1进入游戏。接下来进行游戏主体部分,我们把游戏中的各种函数定义放在game.c 中,放在test.c 中会显得臃肿,函数的声明放在game.h 中。
void game()//游戏主体
{
//那么创建一个棋盘变量。棋盘分为行列,所以我们可以用二维数组来作为棋盘,
//ROW代表行,COL代表列,我们进行宏定义ROW为3,COL为3,如下图:
char board[ROW][COL];
}
宏定义ROW ,COL 为3,这样宏定义很方便,想要改变棋盘的大小,只要改变这里就行了,其它不用改变!
初始化棋盘、打印棋盘
当我们进入游戏的时候,我们看到应该是棋盘,并且没有下棋的棋盘.如下图:
void game()//游戏主体
{//在这里调用函数,在game.c 中定义函数.
char board[ROW][COL];//棋盘大小,ROW为行,COL为列
init_board(board, ROW, COL);//初始化棋盘
print_board(board, ROW, COL);//打印棋盘
}
我们要思考:如何设计一个棋盘?如何去初始化棋盘,让棋盘每一个位置都是空格?下图是一个模板:
初始化棋盘:我们直接用双层循环进行每个元素赋值为空格就行,如下图:
//记得要在game.h中声明该函数!
void init_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';//每个元素赋予空格
}
}
}
打印棋盘:我们可以将这个图案分为两个部分:
我们可以观察:无横线的部分中空格是有三个空格,因为下面有三个横线,那我们可以写双重循环,内层循环写无横线的部分,循环三列,外层循环写三行.如下图:
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);//打印三个部分,中间放字符
}
printf("\n");
}
}
这样打印结果就是如下:
第一个问题:我们可以看到在第三列不需要竖杠,只需要打印小于COL - 1 的列数,所以用一个if语句来执行,如果小于COL就打印竖杠,否则不打印!***
第二个问题:我们看到缺少了横线与竖杠,所以我们可以在内层循环结束后,进行打印横线与竖杠,但是最后一行不需要打印,所以要进行判断行小于ROW - 1 。***
所以解决途径如下:
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
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++)//循环3次,打印竖杠
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");
}
}
那么初始化棋盘,打印棋盘就完成了。
玩家下棋、电脑下棋、生成随机数
既然棋盘已经做完了,那么接下来就是下棋了。下棋分为玩家和电脑下棋。
玩家下棋:作为一个玩家,我们可以输入坐标在棋盘上显示符号,玩家输入坐标是大于等于1的,所以存入数组的时候要进行减一。
规则:输入坐标。首先判断坐标是否合法,什么是合法?合法就是要在棋盘的大小范围之内!然后判断坐标的位置是否为空格?意思就是这个坐标不能有棋子,有棋子就代表被占用了,不能下棋!这两个规则遵守了,就可以进行设计了!如下图:
void player1_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)//只有坐标合法并且正确才能跳出循环,否则继续输入坐标
{
printf("玩家1请下棋:");
scanf("%d %d", &x, &y);//坐标一定要大于等于1
if (x >= 1 && x - 1 < row && y >= 1 && y - 1 < col)//坐标合法的条件
{
if (board[x - 1][y - 1] == ' ')//坐标减一才能对应数组中的元素
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("已被占用!");//不是空格就说明
}
}
else
{
printf("非法输入!");//坐标不合法就说明
}
}
}
电脑下棋:电脑下棋不需要那么麻烦,所以只需要生成的随机数作为坐标,然后判断是否被占用,不被占用就可以赋予符号并且跳出循环!
生成随机数:我之前在写猜数字游戏的时候讲了随机数的一些知识,不了解可以去看看,这是链接http://t.csdn.cn/IgYyi。把随机数当成坐标,但是随机数很随机,很可能不合法,所以我们可以%3,那么得到的结果只能小于3,就可以作为随机数了。
void computer_move(char board[ROW][COL], int row, int col)
{
int x = rand() % 3;//srand可以放在void test()中,因为只需要生成一次就可以了!
int y = rand() % 3;//记得引用头文件#include<stdlib.h>,放在game.h中。
while (1)
{
printf("电脑请下棋:");
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
玩家下一个棋,是不是要打印一下棋盘,因为不打印棋盘,就不知道下了什么棋,所以要打印棋盘;电脑也是如此,如果电脑下完不显示,玩家就不能根据形势来下棋获得胜利了。
void game()//游戏主体
{
char board[ROW][COL];//棋盘大小,ROW为行,COL为列
char ret = 0;
init_board(board, ROW, COL);//初始化棋盘
print_board(board, ROW, COL);//打印棋盘
while (1)//这里用到for循环是因为玩家和电脑下棋,总不可能下一次棋就结束了吧!
//待会会讲到如何跳出循环
{
player1_move(board, ROW, COL);//玩家1下棋
print_board(board, ROW, COL);
computer_move(board, ROW, COL);//电脑下棋
print_board(board, ROW, COL);
}
}
当然这个电脑是有点人工智障的,它不会赌棋,让电脑赢反而成为了一种难度。
判断输赢
来到了我们的结束阶段了!
有三种情况:赢、平局、游戏继续。所以我们可以写一个判断输赢的函数,返回值为字符型。
返回值为C,则游戏继续;返回值为*,则跳出循环,并输出玩家赢了;返回值为#,则跳出循环,并输出电脑赢了;返回值为P,则跳出循环,并输出平局。
void game()//游戏主体
{
char board[ROW][COL];//棋盘大小,ROW为行,COL为列
char ret = 0;
init_board(board, ROW, COL);
print_board(board, ROW, COL);
while (1)
{
player1_move(board, ROW, COL);//玩家1下棋
print_board(board, ROW, COL);
ret = is_win(board, ROW, COL);//判断是否赢了的函数
if (ret != 'C')//返回值只要不是C,就要跳出循环,游戏结束。
{
break;
}
computer_move(board, ROW, COL);
print_board(board, ROW, COL);
ret = is_win(board, ROW, COL);//不管是玩家还是电脑都要判断赢了没有,必须出现在
//一个角色下了棋之后进行判断
if (ret != 'C')
{
break;
}
}
if (ret == '*')
printf("玩家1赢了!\n");
else if (ret == '#')
printf("电脑赢了!\n");
else if (ret == 'P')
printf("平局!\n");
}
赢有四种方式:行、列、对角线、另一条对角线
第一种:行的判断!我们只需要判断同一行的每个元素相同就行了,当然不能等于空格!所以如下:
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{//连续两个数相等并且不等于空格
if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')
flag++;//作为标志
if (flag == KEY - 1)//KEY代表玩什么棋
break;
}
if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次
return board[i][j - 1];
}
}
第二种:列的判断!列的判断类似于行的判断。列不变化,行在变化。如下:
char is_win(char board[ROW][COL], int row, int col)
{
//行的判断
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次
return board[i][j - 1];
}
//列的判断
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)
return board[j - 1][i];
}
第三种:对角线!对角线是有规律的,其中一个是坐标相同,另一个是行列变化1。
先讲一个对角线:如下图:
char is_win(char board[ROW][COL], int row, int col)
{ //行的判断
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次
return board[i][j - 1];
}
//列的判断
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)
return board[j - 1][i];
}
//对角线的判断
int flag = 0;//这个flag放在里面就会重新定义为0,这样flag最大只能为1了,不能为2!
for (i = 1; i < row; i++)
{//flag在里面的话,会重新初始化为0,最大为1,不会返回符号,就没有
//这个对角线很方便,横纵坐标都相同。
if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')
flag++;
if (flag == KEY - 1)
return board[i][i];
}
第四种:另一种对角线!另一种对角线有两种方式:1. 右上到左下。2. 左下到右上。
我是第一种方式,如下:
char is_win(char board[ROW][COL], int row, int col)
{ //行的判断
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次
return board[i][j - 1];
}
//列的判断
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)
return board[j - 1][i];
}
//对角线的判断
int flag = 0;//这个flag放在里面就会重新定义为0,这样flag最大只能为1了,不能为2!
for (i = 1; i < row; i++)
{
if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')
flag++;
if (flag == KEY - 1)
return board[i][i];
}
//另一个对角线
flag = 0;//这个跟上面的一样
int j = col - 1;//j如果放在里面的话就发生其它位置的元素对比,不符合对角线原则
for (i = 1; i < row; i++)
{//j如果放在里面的话,进行第二次循环时,i = 2, j - 1 = 1,坐标2 1不符合对角线原则
if (board[i][j - 1] == board[i - 1][j] && board[i - 1][j] != ' ')
{
flag++;
j--;
}
if (flag == KEY - 1)
return board[i][0];
}
赢的方式判断完了,就要判断平局了,我们把这函数就放在is_win函数中,因为这个平局函数就是为is_win函数服务的,所以不用在game.h中声明了。
static int is_full(char board[ROW][COL], int row, int col)
{//赋予static意思是只能在game.c 文件中看到,其它文件看不到,这个函数只是为is_win函数服务的,
//所以没必要在game.h中进行声明。
int i = 0;//这个函数返回值为整型,只要有一个为空格,就返回0,在is_win函数中设计一个if语句
//如果is_full函数返回值为真,就返回P,否则不执行。
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次
return board[i][j - 1];
}
for (i = 0; i < row; i++)
{
int j = 0;
int flag = 0;
for (j = 1; j < col; j++)
{
if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')
flag++;
if (flag == KEY - 1)
break;
}
if (flag == KEY - 1)
return board[j - 1][i];
}
int flag = 0;//这个flag放在里面就会重新定义为0,这样flag最大只能为1了,不能为2!
for (i = 1; i < row; i++)
{
if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')
flag++;
if (flag == KEY - 1)
return board[i][i];
}
flag = 0;//这个flag也是一样。
int j = col - 1;//j如果放在里面的话就发生其它位置的元素对比,不符合对角线原则
for (i = 1; i < row; i++)
{
if (board[i][j - 1] == board[i - 1][j] && board[i - 1][j] != ' ')
{
flag++;
j--;
}
if (flag == KEY - 1)
return board[i][0];
}
int ret = is_full(board, ROW, COL);//判断平局函数
if (ret)
return 'P';//为真就返回P,平局
return 'C';//上面情况都不符合,那么游戏只能继续
}
好了,这个N子棋就讲完了,我们可以在game.h中改变ROW,COL的大小,进而改变棋盘的大小;改变KEY的大小,进而决定玩什么棋,都由你决定!
双人对战只需要把电脑下棋的代码换成玩家的代码即可,然后改变一些名称,就大工完成了。
如果这个文章对你有所帮助的话,请你点个赞,谢谢!!!
^ _ ^
位卑未敢忘忧国,事定犹须待阖棺。—陆游《病起书怀》
上善若水