文章目录
前言
井字棋,一个小游戏,两个玩家轮流下棋,在3x3的棋盘下棋,先以横、竖、斜方向上的3子连成一线则获胜。如果双方将棋盘下满仍未分出胜负,则为平局,那我们思考C语言如何实现这样一个功能呢?
一、游戏的准备阶段
1.打印游戏菜单:menu()
void menu()
{
printf("***************************\n");
printf("******* 1.开始 **********\n");
printf("******* 0.结束 **********\n");
printf("***************************\n");
printf("请选择是否开始游戏:>");
}
2.保证游戏循环进行
定义input变量,通过switch分支语句,选择是否开始游戏,
通过do while循环使游戏结束后,可以再次选择是否开始游戏
游戏的函数调用置于game函数中
Sleep(4000)和system("cls")包含在<Windows.h>头文件中,用于优化游戏体验
void test()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏,棋盘:\n");
game();
Sleep(4000);
system("cls");
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误,请重新输入\n\n");
break;
}
} while (input);
}
game函数不同于game头文件,它是用来调用game头文件中的自定义函数
该函数中的 ret 用于接收is_win函数返回的值,然后判断游戏处于什么情景
void game()
{
char ret = 0;
char board[ROW][COL];
//初始化棋盘
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
while (1)
{
//玩家下棋
gamer_move(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
//电脑下棋
Sleep(1000);//模拟下棋思考过程
computer_move(board, ROW, COL,pn);
//打印棋盘
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
}
if (ret == '*')
printf("\n恭喜你,胜利了\n");
else if (ret == '#')
printf("\n哦豁,你被电脑击败了\n");
else if (ret == 'q')
printf("\n游戏平局\n");
printf("**等待4秒后游戏再次开始**\n");
}
二、数组的定义和初始化
1.数组的定义
整个游戏过程都在棋盘上进行,由于需要3*3的棋盘,
我们需要定义二维数组char board[ROW][COL],用来储存我们下棋的数据,
并且定义ROW(行)、COL(列)常量
放在game头文件中(在test.c和game.c文件中加上#include "game.h")
//放在game函数中,方便game.c源文件中的自定义函数传参和调用
char board[ROW][COL];
//放在game.h头文件
#define ROW 3
#define COL 3
2.数组的初始化:init_board()
将init_board函数放在game.c文件
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] = ' ';
}
}
三、游戏过程(循环)
1.玩家下棋:gamer_move()
因为玩家不会总是程序员,并不知道数组的首元素下标从0开始,输入的坐标自然需要处理
并且输入的数据存在3种可能:
- 输入坐标超过3*3的棋盘范围(坐标非法)
- 输入的坐标已经被下过棋
- 输入的坐标合法
所以程序需要循环输入直到坐标合法退出,此处用到while(1),让循环一直进行
void gamer_move(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
printf("\n玩家下棋\n");
printf("请输入想要下棋的坐标(行 列):>");
while (1)
{
scanf("%d %d", &x, &y);
if (x <= ROW && x>0 && y <= COL && y>0)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("\n坐标被占用\n");
printf("请再次输入:>");
}
}
else
printf("坐标非法\n");
}
}
2.打印井字棋棋盘:print_board()
打印函数其实可以更加简单,但是为了方便日后在此函数的基础上改成五子棋的棋盘,
将棋盘调整为了可通过改变#define ROW 和define COL的值,改变棋盘大小
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");
}
}
}
打印效果:
3.判断输赢:is_win()
这一步其实比较简单,我们将需要考虑的情况罗列:
- 玩家胜利
- 电脑胜利
- 平局
- 游戏未结束
判断玩家和或电脑胜利,考虑行、列、对角线即可
我们需要把函数返回类型定义为char类型,return返回值时
可以直接返回board数组内的符号来判断是玩家还是电脑胜利,避免将同类型语句写两次
游戏继续需要判断数组内部是否还剩下 ' ' 字符,如果以上情况都不满足,则已经平局
char is_win(char board[ROW][COL], int row, int col)
{
//判断逻辑
//如果玩家胜利,return *
//如果电脑胜利,return #
//如果平局,return q
//如果以上均不成立,return c
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
//判断列
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
return board[0][i];
//判断行
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
return board[i][0];
}
//判断对角线
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];
//判断平局
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 'c';//如果数组内有空格,则游戏继续
}
}
return 'q';//如果没有空格,则游戏已经平局
}
4.电脑下棋:computer_move()
电脑下棋需要生成随机数,所以调用rand函数来生成随机数
v1 = rand() % 100; // v1 in the range 0 to 99
但是这样我们会发现每次程序每次生成的随机数是相同的
因为此随机数由算法生成,该算法在每次调用时都会返回一系列明显不相关的数字
而且该算法使用种子来生成序列随机种子初始化为表示当前时间(调用)的值
以便在每次运行程序时生成不同的值,我们需要调用srand函数调用当前时间:
将srand((unsigned int)time(NULL));放在test函数中调用一次即可
rand函数和srand函数包含在<stdlib.h>中,调用时间需要<time.h>
int computer_move(char board[ROW][COL], int row, int col)
{
printf("\n电脑下棋\n");
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x - 1][y - 1] == ' ' && *pn == 0)
{
board[x - 1][y - 1] = '#';
break;
}
}
*pn+=1;
return 0;
}
5.打印井字棋棋盘(重复引用函数即可)
6.判断输赢(重复引用函数即可)
四、游戏试运行
五、对程序的优化
此处优化主要是指对于电脑下棋的优化,因为电脑下棋的位置非常随机,并不会像是玩家之间下棋的时候那种博弈,所以我们为了完善程序的体验,需要优化电脑下棋的逻辑,每个人的优化方式不同,我给出我的优化想法如下:
分别判断行、列、对角线上的符号是什么,如果满足将要连成一线的情况,则对玩家的棋子进行堵截,在下面的代码中,并没有完全考虑所有可能会让玩家赢的情况,如果考虑过多可能反而会降低游戏的乐趣,这个难度的设置全看个人的意愿,可以在此基础上将代码再次优化
int computer_move(char board[ROW][COL], int row, int col)
{
int i = 0;
printf("\n电脑下棋\n");
//行优化
for (i = 0; i < col; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == '*'&& board[i][2] == ' ')
{
board[i][2] = '#';
return 0;
}
if (board[i][2] == board[i][1] && board[i][1] == '*'&& board[i][0] == ' ')
{
board[i][0] = '#';
return 0;
}
}
//列优化
for (i = 0; i < row; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == '*'&& board[2][i] == ' ')
{
board[2][i] = '#';
return 0;
}
if (board[2][i] == board[1][i] && board[1][i] == '*'&& board[0][i] == ' ')
{
board[0][i] = '#';
return 0;
}
}
//对角线优化
if (board[0][0] == board[1][1] && board[1][1] == '*'&& board[2][2] == ' ')
{
board[2][2] = '#';
return 0;
}
if (board[1][1] == board[2][2] && board[1][1] == '*'&& board[0][0] == ' ')
{
board[0][0] = '#';
return 0;
}
if (board[0][2] == board[1][1] && board[1][1] == '*' && board[2][0] == ' ')
{
board[2][0] = '#';
return 0;
}
if (board[2][0] == board[1][1] && board[1][1] == '*' && board[0][2] == ' ')
{
board[0][2] = '#';
return 0;
}
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '#';
break;
}
}
return 0;
}
六、源代码展示
1.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"
void menu()
{
printf("***************************\n");
printf("******* 1.开始 **********\n");
printf("******* 0.结束 **********\n");
printf("***************************\n");
printf("请选择是否开始游戏:>");
}
void game()
{
char ret = 0;
char board[ROW][COL];
//初始化棋盘
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
while (1)
{
//玩家下棋
gamer_move(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
//电脑下棋
Sleep(1000);//模拟下棋思考过程
computer_move(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
}
if (ret == '*')
printf("\n恭喜你,胜利了\n");
else if (ret == '#')
printf("\n哦豁,你被电脑击败了\n");
else if (ret == 'q')
printf("\n游戏平局\n");
printf("**等待4秒后游戏再次开始**\n");
}
void test()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏,棋盘:\n");
game();
Sleep(4000);
system("cls");
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误,请重新输入\n\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
2.game.c
#define _CRT_SECURE_NO_WARNINGS 1
#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 gamer_move(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
printf("\n玩家下棋\n");
printf("请输入想要下棋的坐标(行 列):>");
while (1)
{
scanf("%d %d", &x, &y);
if (x <= ROW && x>0 && y <= COL && y>0)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("\n坐标被占用\n");
printf("请再次输入:>");
}
}
else
printf("坐标非法\n");
}
}
int computer_move(char board[ROW][COL], int row, int col,int* pn)
{
int i = 0;
printf("\n电脑下棋\n");
//行优化
for (i = 0; i < col; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == '*'&& board[i][2] == ' ')
{
board[i][2] = '#';
return 0;
}
if (board[i][2] == board[i][1] && board[i][1] == '*'&& board[i][0] == ' ')
{
board[i][0] = '#';
return 0;
}
}
//列优化
for (i = 0; i < row; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == '*'&& board[2][i] == ' ')
{
board[2][i] = '#';
return 0;
}
if (board[2][i] == board[1][i] && board[1][i] == '*'&& board[0][i] == ' ')
{
board[0][i] = '#';
return 0;
}
}
//对角线优化
if (board[0][0] == board[1][1] && board[1][1] == '*'&& board[2][2] == ' ')
{
board[2][2] = '#';
return 0;
}
if (board[1][1] == board[2][2] && board[1][1] == '*'&& board[0][0] == ' ')
{
board[0][0] = '#';
return 0;
}
if (board[0][2] == board[1][1] && board[1][1] == '*' && board[2][0] == ' ')
{
board[2][0] = '#';
return 0;
}
if (board[2][0] == board[1][1] && board[1][1] == '*' && board[0][2] == ' ')
{
board[0][2] = '#';
return 0;
}
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '#';
break;
}
}
return 0;
}
char is_win(char board[ROW][COL], int row, int col)
{
//判断逻辑
//如果玩家胜利,return *
//如果电脑胜利,return #
//如果平局,return q
//如果以上均不成立,return c
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
//判断列
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
return board[0][i];
//判断行
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
return board[i][0];
}
//判断对角线
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];
//判断平局
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 'c';//如果数组内有空格,则游戏继续
}
}
return 'q';//如果没有空格,则游戏已经平局
}
3.game.h
#pragma once
#define ROW 3
#define COL 3
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void print_board(char board[ROW][COL], int row, int col);
//玩家下棋
void gamer_move(char board[ROW][COL], int row, int col);
//电脑下棋
int computer_move(char board[ROW][COL], int row, int col);
//判断输赢
char is_win(char board[ROW][COL], int row, int col);
总结
井字棋代码按照顺序一步步思考其实不难实现,主要是对二维数组的运用和整个游戏逻辑的自洽,通过练习这个项目可以巩固自己的代码能力和对具体问题解决的思路训练,在实际工作中所遇到的问题,往往就是需要有足够的耐心和冷静的分析,合理运用自己的能力,拆分看待,逐个解决