三子棋需要有的功能以及实现
1. 初始棋盘
创建一个3*3的数组,由于一开始玩家没有输入,数组为空白,相对应的每一个数组单元存放内容为空格
//初始棋盘
void init_board(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] = ' '; //一开始的时候,数组没有输入数字,无法对其,所以初始化棋盘,让数组一开始是空格
}
}
}
2. 玩家移动
玩家移动所需要的注意事项:
- 选择的格子没有棋子
首先玩家不会考虑数组下标,玩家输入位置的范围只能在1到row和1到col
但是在数组内,我们有个0下标所以需要把玩家输入的坐标都减1
如果该位置有棋子,显示坐标被占用
如果该位置越界,显示坐标非法
void player_move(char board[ROW][COL], int row, int col)
{
printf("玩家下棋\n");
printf("请输入坐标:");
//判断坐标的合法性,有没有越界
//坐标是否被占用
while (1)
{
int x = 0;
int y = 0;
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");
}
}
}
3. 电脑自动移动
在现阶段我们暂时不考虑电脑智能化的移动,比如想办法赢和想办法堵,我们让电脑随机下棋
使用rand以及srand来求随机行和列
注意:使用rand和srand要引用对应的库函数
将rand() 模3出来的结果是0-2
原因:rand()函数会产生范围为0至32767的随机数,让它与3求余,变成0至2的随机数,余数不会超过3因为我们的row是3,如果余数超过3可以进1
//电脑随机下棋
void computer_move(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;
}
//不用else因为电脑不需要提示,直接回到while继续生成随机数
}
}
4. 棋盘满了
我们这里先来讨论棋盘满了情况,有助于之后思考赢,输,平局
如果棋盘满了返回1
不满返回0
如果数组中有一个元素是空格,表示没满,其他情况下都是满了
//如果棋盘满了返回1
//不满返回0
int is_full(char board[ROW][COL], int row, int col) //不用在头文件声明,因为只在game.c 这个文件中运行,不在其他文件里运行
{
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,这一个loop直接不运行,不存在return的覆盖
}
}
}
return 1;
}
5. 自动判断输赢平局
三子棋有三种情况:
1) 电脑赢 - 横着,竖着,斜着赢
2) 玩家赢 -横着,竖着,斜着赢
3)棋盘满了,平局
4) 棋盘没满,继续游戏
由于1)和 2) 逻辑相似,我们只要写一种情况,让他们返回不同的值就能知道是电脑赢还是玩家赢
所以我们返回的都是棋盘中的符号
Q代表平局
C代表继续
//判断状态
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
//横向赢
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0]!=' ') //分析行
{
return board[i][0];//直接返回棋盘里的值,不用重新写了
}
}
//纵向赢
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][1] != ' ') //分析行
{
return board[i][0];//直接返回棋盘里的值,不用重新写了
}
}
//斜着赢
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
{
return board[0][2];
}
//平局(棋盘满了且没人赢)
if (is_full(board, row, col) == 1) //看的是board这个数组,判断状态时得到的是小写row,col
{
return 'Q';
}
//继续
return 'C';
}
6. 显示给玩家
除了将棋盘打印下来,我们还要实现可以动态调整行和列的棋盘。
简单来说就是使代码更灵活,随时可以改变棋盘格数,行和列。这里我们可以是使用for loop来实现
注意:由于数组下标是从0开始的,我们在循环中i<row, j<col
//可以动态调整行和列的棋盘
void display_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++) //决定打印多少行
{
//数据
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); //写死了,只能是三列,想让他变的灵活
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)
{
//printf("---|---|---\n");//第三行分割线有点多余
for (j = 0; j < col; j++)
{
printf("---");
if(j<col-1)
printf("|");
}
printf("\n");
}
}
}
问题:
三子棋中,为什么要分大写ROW和小写row呢、
- 他们本质上没有区别,ROW是被定义过的精确数值
-在main函数中,当我们想要引用其他函数要写:
init_board(board,ROW,COL);
这个时候我们是要引用这个函数,不需要明确定义它里面的参数是什么类型的,我们只要把我们要用到的参数写进去即可
void init_board(char board[ROW][COL], int row, int col)
这个是在函数定义以及说明的时候用到的
在这里需要明确定义每一个形式参数的类型以及名称
如果以上两个代码是相关联的,我们把ROW 的值给到row,COL 的值给到col
我看了一圈感觉只有ROW被定义成数字3,row没有被定义为什么也能使用?(上面回答了)
棋盘满了,且没人赢部分为什么用的是小写row,col?
我们这里(is_full函数)用到的实参是从is_win函数里来的,is_win函数里只有小写row,col,所以is_full里也是小写
test.c 里面主要是main函数以及菜单的打印,比较简单在这里就不介绍了
有问题的话大家可以给我留言~
代码汇总:
//头文件放函数声明
#pragma once
#define ROW 3 //行是3
#define COL 3 //列是3
#include<stdio.h>
#include<stdlib.h> //rand
#include<time.h> //srand
//初始化棋牌
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void display_board(char board[ROW][COL], int row, int col);
//玩游戏
void player_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);
game.c
//这里放函数定义
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//初始棋盘
void init_board(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 display_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++) //决定打印多少行
{
//数据
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); //写死了,只能是三列,想让他变的灵活
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)
{
//printf("---|---|---\n");//第三行分割线有点多余
for (j = 0; j < col; j++)
{
printf("---");
if(j<col-1)
printf("|");
}
printf("\n");
}
}
}
void player_move(char board[ROW][COL], int row, int col)
{
printf("玩家下棋\n");
printf("请输入坐标:");
//判断坐标的合法性,有没有越界
//坐标是否被占用
while (1)
{
int x = 0;
int y = 0;
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 computer_move(char board[ROW][COL], int row, int col)
{
printf("电脑下棋\n");
while (1)
{
int x = rand() % row;//模3出来的结果是0-2,rand()函数会产生范围为0至32767的随机数
//% 让它与3求余,变成0至2的随机数,余数不会超过3因为我们的row是3,如果余数超过3可以进1
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
//不用else因为电脑不需要提示,直接回到while继续生成随机数
}
}
//如果棋盘满了返回1
//不满返回0
int is_full(char board[ROW][COL], int row, int col) //不用在头文件声明,因为只在game.c 这个文件中运行,不在其他文件里运行
{
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,这一个loop直接不运行,不存在return的覆盖
}
}
}
return 1;
}
//判断状态
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
//横向赢
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0]!=' ') //分析行
{
return board[i][0];//直接返回棋盘里的值,不用重新写了
}
}
//纵向赢
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][1] != ' ') //分析行
{
return board[i][0];//直接返回棋盘里的值,不用重新写了
}
}
//斜着赢
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
{
return board[0][2];
}
//平局(棋盘满了且没人赢)
if (is_full(board, row, col) == 1) //看的是board这个数组,判断状态时得到的是小写row,col
{
return 'Q';
}
//继续
return '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");
}
void game()
{
char ret = 0;
printf("三子棋\n");
char board[ROW][COL] = { 0 };
init_board(board, ROW, COL);
display_board(board, ROW, COL);//告诉他是3*3
//玩游戏
while (1)
{
player_move(board, ROW, COL);
display_board(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
break;
/*display_board(board, ROW, COL);*/
computer_move(board, ROW, COL);
display_board(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
break;
/*display_board(board, ROW, COL);*/
}
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else if (ret == 'Q')
{
printf("平局\n");
}
}
//玩家赢 - '*'
//电脑赢 - '#'
//平局,棋盘满了-‘Q’
//游戏继续-‘C'
int main()
{
int input = 0;
srand((unsigned int)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;
}