文法不好,尝试用图文来代替文字写一次博客,写的不好,有点乱,思路可以参考,代码会放在页尾,主要给自己看的,对学到知识的一个总结,渣渣一个,不喜勿喷T_T
第一次写三子棋的时候,还是很慌的,毕竟以前没有写过这么大的工程,但其实把他想明白了,不难的,你会发现,他就是个缝合怪,把他拆分成若干个函数,你会发现都是以前学过的东西。
第一次写的时候bug很多,还找不出来,也懒得去调试,总会留下没有解决的隐患,写完之后,以前没解决的问题,变成连锁反应暴露出来了,第二次写的时候,把要实现的功能罗列出来,思路清晰了,做一小块就调试一下,保证每个模块都能正常运行,就算以后写代码出bug了,也不用担心是前面代码的问题,因为前面是调试过,保证没问题的
0. 三子棋介绍
1.游戏入口
1.菜单:menu
//打印:菜单
void menu()
{
printf("********************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("********************\n\n");
}
2.选择:玩不玩?
int input;
do
{
menu(); //打印菜单
printf("请选择(1/0):");
scanf("%d", &input);
//清缓冲区
//while (getchar() != '\n')
//{
//}
switch (input)
{
case 1:
printf("三子棋\n\n");
game(); //进入三子棋游戏
break;
case 0:
printf("退出游戏\n\n");
break;
default:
printf("选择错误,请重新选择\n\n");
break;
}
} while (input);
2.游戏的实现
1.棋子:二维数组
2.初始化数组
//初始化数组
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] = ' ';
}
}
}
题外话!!
1.关于函数
我们要养成:把行数多,且功能独立的代码,封装成函数,的良好习惯
比如:这是三子棋的运行流程,有序的一个接一个的运行,阅读起来简洁易懂,没有障碍
不敢想象,把所有东西都塞进主程序是什么样的效果,杂乱无章,想改都无从改起,维护成本极高。。
2.头文件/源文件
若要调用game.c的内容,则需要
1.game.h的返回类型 / 函数名 / 形参;与定义时一致
2.test.c 需引头文件 “game.h”
在:
//在主程序,test.c中
#include"game.h"
{
InitBoard(board, ROW, COL);
}
//在函数程序,game.c中
void InitBoard(char board[ROW][COL], int row, int col)
{
//函数效果;
}
//头文件,game.h中
void InitBoard(char board[ROW][COL], int row, int col);
// 返回类型 / 函数名 / 形参 / 必须与game.c一致
3.宏定义
格式如下:
//在game.h中
#define ROW 3
#define COL 3
棋盘
那么。。。在不能用手指的情况下,要怎么弄呢。。
我们先看看棋盘长什么样
| |
---|---|---
| |
---|---|---
| |
不会??这其实就是刷题中的,操控循环和if语句,控制输出的内容,最终输出某种特定图形
图为牛客网中的,各种牛鬼蛇神
那么在有大概的认识之后,就可以分析一波棋盘的构成,以及如何用学过的知识来输出了
代码如下:
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) //最后1列,不输出 |
printf("|");
}
printf("\n");
//分割行
if (i < row - 1) //最后1行,不输出 ---|---|---
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1) //最后1列,不输出 |
printf("|");
}
printf("\n");
}
}
printf("\n");
}
那么棋盘/ 棋子/的问题都已经解决了
接下来我们来实现下棋的代码
下棋
玩家下棋回合:代码如下:
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x, y;
while (1)
{
printf("玩家走:");
scanf("%d%d", &x, &y);
//清缓冲区
while (getchar() != '\n')
{
}
//坐标是否越界?
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//坐标是否有棋子
if (board[x - 1][y - 1] == ' ')
{
//下棋'*'
board[x - 1][y - 1] = '*';
break;
}
else
printf("坐标已有棋子,");
}
else
printf("坐标不在棋盘内,:");
}
}
电脑下棋:代码如下:
//与玩家的下棋一样,随机数代替输入,且不用考虑玩家角度的坐标,以及越界的问题
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x, y;
while (1)
{
x = rand() % row; //余0 1 2
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
printf("电脑走:%d %d\n", x + 1, y + 1);
}
判断输赢
代码如下:
//行 00 01 02 10 11 12
for (i = 0; i < row; i++)
{
cnt = 0;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')
cnt++;
if (cnt == col - 1)
{
ch = board[i][j];
goto win;
}
}
//列 00 10 20 01 11 21
for (j = 0; j < col; j++)
{
cnt = 0;
for (i = 0; i < col - 1; i++)
{
if (board[i][j] == board[i + 1][j] && board[i][j] != ' ')
cnt++;
if (cnt == col - 1)
{
ch = board[i][j];
goto win;
}
}
}
//对角线1 00 11 22
//创建a,b用于替代循环变量i,j
int a, b;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//防止a+1 b+1 越界
cnt = 0;
for (a = i, b = j; a < row - 1 && b < col - 1; a++, b++)
if (board[a][b] == board[a + 1][b + 1] && board[a][b] != ' ')
cnt++;
if (cnt == col - 1)
{
ch = board[i][j];
goto win;
}
}
}
//对角线2 02 11 20
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//防止a+1 b-1 越界
cnt = 0;
for (a = i, b = j; a < row - 1 && b >= 1; a++, b--)
if (board[a][b] == board[a + 1][b - 1] && board[a][b] != ' ')
cnt++;
if (cnt == col - 1)
{
ch = board[i][j];
goto win;
}
}
}
代码如下:
//不可以在IsWin函数内定义函数哟(嵌套定义)
int IsFull(char board[ROW][COL], int row, int col)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
//棋盘有空的,返回0
if (board[i][j] == ' ')
return 0;
}
//满了,返回1
return 1;
}
//调用函数IsFull()
int full = IsFull(board, ROW, COL);
//下列条件,满足其一,返回0 (中止游戏)
win:
if (ch == '*')
{
printf("玩家赢!\n\n");
return 0;
}
else if (ch == '#')
{
printf("电脑赢!\n\n");
return 0;
}
else if (full == 1)
{
printf("平局\n\n");
return 0;
}
//否则返回1 (继续游戏)
return 1;
}
执行流程:
1.先初始化棋盘,下棋之前打印1次棋盘
2.进入循环,玩家和电脑轮流下,每下完一步棋,输出1次棋盘,可以观察下棋的实时状况
3.若电脑或玩家的其中一方,有3连子了,或棋盘满了,都会中止游戏,已经没有玩下去的必要了
测试
我们还可以将游戏改成5子棋,棋盘大小也可以调节,因为棋盘大小,几行几列,都是用宏来当作参数传给函数的,n连子也是用利用循环的,没有把代码写死,那么我们只需要修改一下宏的定义,即可修改成更大的棋盘,和更难的胜利条件
各个方位都能胜利,目前没有发现BUG
但由于棋盘大小的增大,电脑下棋的坐标是随机的,堵到的概率很低,所以很无聊
可以写的更智能一点,如5子棋,玩家在棋盘上超过2个或3个子就会开堵
或者说,电脑可以检测在棋盘上自己有没有多个相连的棋子,如果有,就不堵玩家,优先下够5个相连的棋子
说得简单,做起来很烧脑,而且代码量会翻一倍吧
代码:
主程序 test.c
//主程序 test.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
//三子棋
// | |
//---|---|---
// | |
//---|---|---
// | |
void menu()
{
printf("********************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("********************\n\n");
}
void game()
{
char board[ROW][COL] = { 0 }; //游戏需输入坐标,坐标为x,y,即2维数组
int win = 0; //win 接收IsWin的返回值,通过返回值决定是否继续
InitBoard(board, ROW, COL);
printf("%d子棋,棋盘大小:%d*%d\n",WinCnt, ROW, COL);
DisplayBoard(board, ROW, COL);
while (1)
{
PlayerMove(board, ROW, COL); //玩家走
DisplayBoard(board, ROW, COL); //走一步看一步
win = IsWin(board, ROW, COL); //判断输赢
if (win == 0)
break;
ComputerMove(board, ROW, COL); //电脑走
DisplayBoard(board, ROW, COL); //走一步看一步
win = IsWin(board, ROW, COL); //判断输赢
if (win == 0)
break;
}
}
void test()
{
int input;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择(1/0):");
scanf("%d", &input);
//清缓冲区
while (getchar() != '\n')
{
}
switch (input)
{
case 1:
printf("%d子棋\n\n",WinCnt);
game(); //进入游戏
break;
case 0:
printf("退出游戏\n\n");
break;
default:
printf("选择错误,请重新选择\n\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
game.c 游戏函数程序
//game.c 游戏函数程序
#define _CRT_SECURE_NO_WARNINGS
#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");
}
}
printf("\n");
}
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x, y;
while (1)
{
printf("玩家走:");
scanf("%d%d", &x, &y);
//清缓冲区
while (getchar() != '\n')
{
}
//坐标是否越界?
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//坐标是否有棋子
if (board[x - 1][y - 1] == ' ')
{
//下棋'*'
board[x - 1][y - 1] = '*';
break;
}
else
printf("坐标已有棋子,");
}
else
printf("坐标不在棋盘内,:");
}
}
//与玩家的下棋一样,随机数代替输入,且不用考虑玩家角度的坐标,以及越界的问题
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x, y;
while (1)
{
x = rand() % row; //余0 1 2
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
printf("电脑走:%d %d\n", x + 1, y + 1);
}
//判断棋盘是否已满
int IsFull(char board[ROW][COL], int row, int col)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
//棋盘有空的,返回0
if (board[i][j] == ' ')
return 0;
}
//满了,返回1
return 1;
}
int IsWin(char board[ROW][COL], int row, int col)
{
int i, j;
int cnt = 0; //连续棋子统计
char ch = 0;
//行 00 01 02 10 11 12
for (i = 0; i < row; i++)
{
cnt = 0;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')
cnt++;
if (cnt == WinCnt -1)
{
ch = board[i][j];
goto win;
}
}
//列 00 10 20 01 11 21
for (j = 0; j < col; j++)
{
cnt = 0;
for (i = 0; i < col - 1; i++)
{
if (board[i][j] == board[i + 1][j] && board[i][j] != ' ')
cnt++;
if (cnt == WinCnt - 1)
{
ch = board[i][j];
goto win;
}
}
}
//对角线1 00 11 22
//创建a,b用于替代循环变量i,j
int a, b;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//防止a+1 b+1 越界
cnt = 0;
for (a = i, b = j; a < row - 1 && b < col - 1; a++, b++)
if (board[a][b] == board[a + 1][b + 1] && board[a][b] != ' ')
cnt++;
if (cnt == WinCnt - 1)
{
ch = board[i][j];
goto win;
}
}
}
//对角线2 02 11 20
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//防止a+1 b-1 越界
cnt = 0;
for (a = i, b = j; a < row - 1 && b >= 1; a++, b--)
if (board[a][b] == board[a + 1][b - 1] && board[a][b] != ' ')
cnt++;
if (cnt == WinCnt - 1)
{
ch = board[i][j];
goto win;
}
}
}
int full = IsFull(board, ROW, COL);
//下列条件,满足其一,返回0 (中止游戏)
win:
if (ch == '*')
{
printf("玩家赢!\n\n");
return 0;
}
else if (ch == '#')
{
printf("电脑赢!\n\n");
return 0;
}
else if (full == 1)
{
printf("平局\n\n");
return 0;
}
//否则返回1 (继续游戏)
return 1;
}
}
game.h 声明头文件
//game.h 声明头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9 //棋盘( N )行
#define COL 9 //棋盘( N )列
#define WinCnt 5 //( N )子棋
//初始化棋盘
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);
//判断输赢
int IsWin(char board[ROW][COL], int row, int col);
//IsWin的子函数,用于检查棋盘是否满了
int IsFull(char board[ROW][COL], int row, int col);