三子棋 -- 数组的运用
实现效果
1、菜单及棋盘展示
2、玩家与电脑下棋
3、判断输赢
模块的分装
1、test.c / 测试模块
目标:建立游戏的“入口”,同时希望这个入口便于测试
功能:显示菜单 → 玩家选择 → play / exit
2、game.c / 游戏模块
功能:打印棋盘 → 玩家下棋 → 判断输赢 → 电脑下棋 → 判断
3、game.h / 头文件
附上标准库与创建的函数,另外创建一个头文件使得引用更简练。
代码实现
1、头文件模块
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 3
#define COL 3
#include<stdio.h>
#include<time.h>
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void print_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);
(1)由#define定义行列标识符
下文在测试模块中将提到具体思路。
(2)#include “game.h”
可以看到这边包含了标准库的引用,此后在各个函数中将在代码块前段附上该引用。
2、测试模块
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//三子棋
void menu()
{
printf("**********************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("**********************\n");
}
void test()//测试模块
{
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("三子棋\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入。\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
(1) 主函数与test()模块分开
便于前期测试:即代码未完全时,依旧可以时刻检查正常与否。
(2)do while循环
游戏至少进行一次。
(3)switch(input)
由input判断下一步执行操作。
3、游戏模块
(1)三子棋 → 多字棋?
当我们只是想单一地实现“三”字棋的功能时,打印当然很容易实现,仅需运用一个最基本的嵌套循环打印即可。
//初始化棋盘
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++)
{
//关键代码,运用行数打印
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
if(i<row-1)
printf("---|---|---\n");
}
}
(注意最后一行无需打印分隔线,故运用if语句去掉最后一行)
效果也尽如人意。
但问题来了:有三字棋,是不是有四字、五字甚至很多很多字呢?这时显然刻板的一个根据行打印没办法满足要求。
回到棋盘的显示,既然行的循环行不通,我又想让棋盘的显示根据我想要多少字就显示多少个元素,往后退一格,我是不是可以根据元素组合进行打印呀。
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
//printf(" %c | %c | %c \n")
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");
}
}
(2)下棋操作
根据三子棋规则,每下一步,需要观察对局是否产生输赢结果。因而代码每一步应该包含功能:输入坐标 - 显示下子 - 判断输赢。
这里用*代表玩家下子,#代表电脑下子。请注意:下子与后边的输赢判断有关。
void player_move(char board[ROW][COL], int row, int col)
{
//输入位置 → 是否合法 → 显示下子
printf("玩家下棋\n");
while (1)
{
printf("请输入下棋的坐标:>\n");
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;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
显示下子的步骤只需在test.c模块的game函数包含上相关函数即可,这边都是一一照应。
void game()
{
char board[ROW][COL];
char ret = 0;
init_board(board, ROW, COL);
print_board(board, ROW, COL);
while (1)
{
player_move(board, ROW, COL);
print_board(board, ROW, COL);
computer_move(board, ROW, COL);
print_board(board, ROW, COL);
}
(3)判断输赢
1)无外乎:你赢,电脑赢,平局三种情况。
2)赢的情况需要根据是否落子连续的情况,分别可能是:横排、竖排、对角线。因此需要有三个判断条件。
3)借由返回的坐标来指示输赢,平局使判断结束时返回C。
//is_full函数仅是为了支持is_win函数功能实现,只在函数内部使用,无需声明
static int is_full(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++)
{
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++)
{
if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] != ' ')
{
return board[i][0];
}
}
//判断列
for (i = 0;i < row;i++)
{
if (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
//对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//平局
if (is_full(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
同样的test.c中的对应:
while (1)
{
player_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("电脑赢了\n");
else if (ret == '*')
printf("玩家赢了\n");
else if (ret == 'Q')
printf("平局\n");
}