【优化】C语言实现三子棋(二)

(本文主要运用分支与循环、数组、函数相关知识)
本文接上文“C语言实现三子棋(一)”,上文只能做3×3棋盘的三子棋,本文优化并拓展了它的功能,使其能够使用任意大小的棋盘以及“N子棋”
优化内容:
  1.能够通过只修改define定义的全局符号来修改棋盘大小
  2.“N子棋”
  3.能够刷新界面,更加接近真实三子棋游戏
  4.防止玩家看不清,电脑下棋之后能够提示玩家,电脑刚刚下在了哪里
  5.提示玩家当前模式(几行几列,几子棋)

视频效果

【优化】C语言实现三子棋(二)

源码:

 源码分三个文件,header.h,main.cpp,realization.cpp
header.h:

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<Windows.h>
#define ROW = 10;//行;
#define auto COL = 10;//列
#define N_ZIQI = 5;//N子棋

//提示行数列数,几子棋
void Tips();

//开一个游戏函数
void game();

//声明菜单
void menu();

//初始化棋盘
void InitBoard(char board[][COL], int row, int rol);

//按照目的打印棋盘
void PrintBoard(char board[][COL], int row, int col);

//玩家走
void PlayerMove(char board[][COL], int row, int col);

//电脑走
void ComputerMove(char board[][COL], int row, int col);

//判断输赢
char IsWin(char board[][COL], int row, int col);

main.cpp:

#include"header.h"
int main() {
    int input = 0;
    srand((unsigned int)time(NULL));
    do {
        menu();
        printf("请选择:>\n");
        scanf("%d",&input);//加取地址符号
        switch (input) {
        case 1:
            Tips();
            printf("三子棋游戏开始\n");
            game();
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,请重新选择\n");
            break;
        }
    } while (input);
    return 0;
}

realization.cpp:

#include"header.h"
//返回B,平局
//返回* ,玩家赢
//返回#,电脑赢

提醒玩家选择的行数列数和几子棋
void Tips() {
	printf("提示:\n");
	printf("您选择的是 %d 行,%d 列\n", ROW, COL);
	printf("您选择的是 %d 子棋,即任何一方有 %d 个棋子连起来即胜利\n",N_ZIQI,N_ZIQI);
	printf("如需修改请在header.h文件中修改\n");
	system("pause");
}
//菜单
void menu() {
	printf("********************\n");
	printf("*******1.play*******\n");
	printf("*******0.exit*******\n");
	printf("********************\n");
}

void game() {
	char ret = 0;
	char board[ROW][COL];
	//初始化
	InitBoard(board, ROW,COL);
	PrintBoard(board, ROW, COL);
	while (1) {
		//玩家走
		PlayerMove(board, ROW, COL);
		system("cls");
		PrintBoard(board, ROW, COL);
		ret = IsWin(board, ROW, COL);
		if (ret == '*') {
			printf("你赢了,你真厉害\n");
			break;
		}
		if (ret == 'C') {
			printf("平局了\n");
			break;
		}
		//电脑走
		ComputerMove(board, ROW, COL);
		ret = IsWin(board, ROW, COL);
		if (ret == '#') {
			printf("电脑赢了,下次努力\n");
			break;
		}
		if (ret == 'C') {
			printf("平局了\n");
			break;
		}
	}
}
//初始化棋盘
void InitBoard(char board[][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 PrintBoard(char board[][COL], int row, int col) {
	int i = 0;
	int j = 0;

	//打印列号
	printf("   |");
	for (j = 1; j <= col; j++) {
		//先打印一行数据
		if (j < 10) {
			printf(" %d ", j);
		}
		else if (j >= 10) {
			printf("%d ", j);
		}
		if (j <= col - 1) {
			printf("|");
		}
	}
	printf("\n");
	//再打印一行分割线
	printf("---|");//左上角那个标头,占一个格
	for (j = 0; j < col; j++) {
		printf("---");
		if (j < col - 1) {
			printf("|");//最后一列的竖杠不用打了
		}
	}
	printf("\n");

	//打印带行号的棋盘
	for (i = 0; i < row; i++) {
		//打印个行号
		if (i + 1 < 10) {
			printf(" %d |", i + 1);
		}
		else if (i + 1 >= 10) {
			printf("%d |", i + 1);
		}

		//先打印一行数据
		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("---|");
		}
		//打印棋盘
		for (j = 0; j < col; j++) {
			if (i < row - 1) {
				printf("---");
			}
			if (i == row - 1) {
				break;
			}
			if (j < col - 1) {
				printf("|");//最后一列的竖杠不用打了
			}
		}
		printf("\n");
	}
    printf("\n");
}

//玩家走
void PlayerMove(char board[][COL], int row, int col) {
	printf("请输入坐标:>\n");
	int x = 0;//玩家输入的横坐标
	int y = 0;//玩家输入的纵坐标
	while (1) {
		scanf("%d%d", &x, &y);
		if (x >= 1 && y >= 1 && x <= row && y <= col) {
			//注意玩家输入的坐标带有不确定性,要考虑全情况,不要出BUG
			if (board[x - 1][y - 1] == ' ') {
				board[x - 1][y - 1] = '*';
				break;//下了棋就跳出去
			}
			else
				printf("这里下过棋了,请重新输入坐标\n");
		}
		else
			printf("输入错误,请重新输入:\n");
	}

}

//电脑走,生成随机数,哪有空下在哪
void ComputerMove(char board[][COL], int row, int col) {
	//rand能生成的数是0-32767
	printf("电脑走:\n");
	Sleep(1000);
	while (1) {
		int x = rand() % row;//生成0-row-1
		int y = rand() % col;//生成0-col-1
		if (board[x][y] == ' ') {//电脑下棋,不用-1了
			board[x][y] = '#';
			system("cls");
			PrintBoard(board, ROW, COL);
			//提示电脑下到哪了
			printf("电脑下的坐标是:(%d,%d)\n", x + 1, y + 1);
			break;//下了就跳出去
		}
	}
}

//判断是否平局
//这个不用再声明了,因为我希望IsFull只是用来辅助判断输赢,没必要所有文件都看见它
static bool IsFull(char board[][COL], int row, int col) {
		int i = 0;
		int j = 0;
		bool flag = true;
		for (i = 0; i < row; i++) {
			for (j = 0; j < col ; j++) {
				if (board[i][j] == ' ') {
					return false;
				}
			}
		}
	 return flag;
}

//判断输赢
char IsWin(char board[ROW][COL], int row, int col) {
	int i = 0;
	int j = 0;

	//行 是否赢
	char ret = 0;
	for (i = 0; i < row; i++) {//每一行
		int j_tool = 0;
		while (j_tool < col) {
			int count = 0;
			//设置起点
			ret = board[i][j_tool];
			while (ret == board[i][j_tool] && j_tool < col) {//注意j_tool访问不要越界
				count++;
				j_tool++;
			}
			//判断是不是连起来了
			if (count == N_ZIQI && ret != ' ') {
				return ret;
			}
		}
	}
	//列
	for (j = 0; j < col; j++) {//列
		//每换一行都是从第一个开始
		int i_tool = 0;
		while (i_tool < row) {
			int count = 0;
			ret = board[i_tool][j];
			while (ret == board[i_tool][j] && i_tool < row) {
				count++;
				i_tool++;
			}
			//判断是不是连起来了
			if (count == N_ZIQI && ret != ' ') {
				return ret;
			}
		}
	}

	//斜着
	//左下部分
	for (i = 0; i < row; i++) {//行
		int j_tool = 0;
		int i_tool = i;
		//检验合法性,先满足行的要求,行 不行就不用看列了
		while (i_tool < row && j_tool < col)
		{
			int count = 0;//重新计数
			ret = board[i_tool][j_tool];//确定起点

			while (ret == board[i_tool][j_tool] && i_tool < row && j_tool < col) {
				count++;
				i_tool++;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}
	}
	
	//右上部分
	for (j = 0; j < col; j++) {//列
		int i_tool = 0;
		int j_tool = j;
		while (i_tool < row && j_tool < col) {
			int count = 0;
			ret = board[i_tool][j_tool];

			while (ret == board[i_tool][j_tool] && i_tool < row && j_tool < col) {
				count++;
				i_tool++;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}
	}

	//左上部分
	for (i = 0;i < row; i++) {//行
		int i_tool = i;
		int j_tool = 0;
		while (i_tool >= 0 && j_tool < col) {
			int count = 0;
			ret = board[i_tool][j_tool];

			while (ret == board[i_tool][j_tool] && i_tool >= 0 && j_tool < col) {
				count++;
				i_tool--;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}
	}
	 //右下部分
	for (j = row - 1; j >= 0; j--) {
		int i_tool = row-1;
		int j_tool = j;
		while (i_tool >= 0 && j_tool < col) {
			int count = 0;
			ret = board[i_tool][j_tool];

			while (ret == board[i_tool][j_tool] && i_tool >= 0 && j_tool >= 0) {
				count++;
				i_tool--;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}

	}
	
	//判断平局
	if (IsFull(board, ROW, COL)) {
		return 'C';
	}

	return 'W';//谁也没赢也没平局,是防止出现警告"不是所有路径都有返回值"
}

改动

1.能够通过只修改#define定义的全局符号来修改棋盘大小

  在header头文件里可以修改ROW和COL的值来修改棋盘的行、列。

2.“N子棋”

  "N子棋"实质上是修改胜利的条件,即IsWin函数。
  我们创建一个N_ZIQI,表示如果有N_ZIQI个连起来的棋子就算胜利。
  首先进行分析,行、列的情况和斜着的情况是不同的,所以可以把行、列归成一类,斜着的情况归成一类。

行&列判断

  大致思路是每一行筛选,在某一行里判断是否存在连续的N_ZIQI个棋子,行和列的思路相同,这里以判断行为例:
  在这里我们引入了一个计数器count,用来计算相邻的连续相同的个数。
  假定已经确定了某一行,那么只需要把这一行的棋子作为起点,然后从这个起点棋子往后找相同的棋子,如果一直连续相同,数量够N_ZIQI个,count就会中断计数,并且值为N_ZIQI。
  如果中途遇到了其他字符(空字符或者对方棋子的字符),count也会中断计数,值小于N_ZIQI,这种情况下就会进下一个while( j_tool < col) 循环,在这一条线(本行)上继续找下一个起点,因为 j_tool 已经是前面++过的了,这个起点就会变成这条线上还没有筛查过的棋子,然后按照这个起点棋子再往后找,同上,直到count == N_ZIQI 或者这一条线找到尽头count也不够N_ZIQI。
  再往下,如果count==N_ZIQI,并且这个起点棋子不是空棋,就可以说满足胜利条件,返回的起点棋子ret是谁,胜利的就是哪方。
  还有一种可能是因为 j_tool 碰到了数组的界限,就是说这一行剩下的格

char ret = 0;
	for (i = 0; i < row; i++) {//每一行
		int j_tool = 0;
		while (j_tool < col) {
			int count = 0;
			//设置起点
			ret = board[i][j_tool];
			while (ret == board[i][j_tool] && j_tool < col) {//注意j_tool访问不要越界
				count++;
				j_tool++;
			}
			//判断是不是连起来了
			if (count == N_ZIQI && ret != ' ') {
				return ret;
			}
		}
	}

斜着判断

  首先分析,斜着的情况可以分成四类,分别是左下部分,右上部分,左上部分,右下部分。

左下部分:

左下部分
  大致思路与行&列判断相同,都是定一个起始棋子,然后按图索骥往后找,最后判断count数量,在这里左上部分的起始棋子可以是每行的第0列,以它为起始往斜右下方找,然后根据具体情况和循环条件进行循环,思路同行&列判断。

for (i = 0; i < row; i++) {//行
		int j_tool = 0;
		int i_tool = i;
		//检验合法性,先满足行的要求,行 不行就不用看列了
		while (i_tool < row && j_tool < col)
		{
			int count = 0;//重新计数
			ret = board[i_tool][j_tool];//确定起点

			while (ret == board[i_tool][j_tool] && i_tool < row && j_tool < col) {
				count++;
				i_tool++;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}
	}

  其他三个部分大同小异,只不过是找的方向不同导致的坐标不同,注意设置循环条件就可以了,本文只展示其他三个部分找的方向,不再详细讲解。

右上部分:

右上部分

for (j = 0; j < col; j++) {//列
		int i_tool = 0;
		int j_tool = j;
		while (i_tool < row && j_tool < col) {
			int count = 0;
			ret = board[i_tool][j_tool];

			while (ret == board[i_tool][j_tool] && i_tool < row && j_tool < col) {
				count++;
				i_tool++;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}
	}
左上部分:

左上部分

`for (i = 0;i < row; i++) {//行
		int i_tool = i;
		int j_tool = 0;
		while (i_tool >= 0 && j_tool < col) {
			int count = 0;
			ret = board[i_tool][j_tool];

			while (ret == board[i_tool][j_tool] && i_tool >= 0 && j_tool < col) {
				count++;
				i_tool--;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}
	}`
右下部分:

右下部分

//右下部分
	for (j = row - 1; j >= 0; j--) {
		int i_tool = row-1;
		int j_tool = j;
		while (i_tool >= 0 && j_tool < col) {
			int count = 0;
			ret = board[i_tool][j_tool];

			while (ret == board[i_tool][j_tool] && i_tool >= 0 && j_tool >= 0) {
				count++;
				i_tool--;
				j_tool++;
			}
			if (count == N_ZIQI && ret != ' ')
			{
				return ret;
			}
		}

	}

3.刷新界面

  只要在每一次调用PrintBoard函数前使用清屏的命令:

system("cls");

  就可以达到清屏的效果。

4.防止玩家看不清,电脑下棋之后能够提示玩家,电脑刚刚下在了哪里

  只需要在ComputerMove函数中电脑下棋之后加一行打印,把电脑下的坐标告诉玩家。

5.提示玩家当前模式(几行几列,几子棋)

  在整个游戏的开头加一个Tips函数,类似菜单的作用,提示玩家如果想改棋盘大小或者N_ZIQI要去头文件中改。(在此,作者诚心发问:如何做到启动游戏后能够输入棋盘大小或N的值修改游戏?这个问题想好长时间了…)

提醒玩家选择的行数列数和几子棋
void Tips() {
	printf("提示:\n");
	printf("您选择的是 %d 行,%d 列\n", ROW, COL);
	printf("您选择的是 %d 子棋,即任何一方有 %d 个棋子连起来即胜利\n",N_ZIQI,N_ZIQI);
	printf("如需修改请在header.h文件中修改\n");
	system("pause");
}

本文完。
                (如果文章有不妥之处,请在评论区点醒作者,谢谢!)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嫋嫋.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值