C语言第十一课:优化井字棋游戏

目录

前言:

一、优手着棋判定:

        1.防守型着棋优化:

        2.进攻型着棋优化:

二、界面格式优化:

        1.Sleep休眠语句:

        2.system语句:

三、优化后最终全部代码:

        1.头文件game.h:

        2.函数功能文件game.h:

        3.程序主体文件test.c:

四、总结:


前言:

        在上一篇文章中,我和各位小伙伴们一起,通过使用我们前面学习的C语言知识,编写出了一个基础版的井字棋游戏。不过我们也发现,作为基础版的井字棋游戏,它存在着很多缺陷,而在本文中,我们将要对基础版井字棋进行着棋、界面两个方面的优化。

一、优手着棋判定:

        1.防守型着棋优化:

        对于一个程序的优化,首先我们应当对其功能缺陷进行优化,而在基础版井字棋中,最大的功能缺陷便是电脑在进行着棋时,没有进行着棋优手判定,在玩家着棋后不会对玩家的进攻性着棋进行围堵,于是这里的优化便是要尝试解决这个问题。

        电脑着棋的优化,主要在于其着棋时应当对当前场上的局势进行判断,倘若在玩家横向、纵向、斜向三个方向上出现两子连子,且第三个位置上仍为空时,就应当在第三个空位上进行着棋以实现对玩家的拦截。

        原理大致了解之后,我们来研究一下具体情况的实现。首先判定应当分为三个方向,即横向,纵向与斜向。而在每个方向上,根据规则连成三子即为胜利可以得出,当下一步即将出现胜利情况时,就应当进行阻止

        以横向为例,会出现三种情况,即在一行中前两个位置相同而第三个位置空缺,此时应当着棋于位置三;第二种情况是在一行中后两个位置相同而首位置空缺,此时应当着棋于位置一;第三种情况为一三位置相同而中间位置空缺,此时则应当着棋于中间位置。

        则纵向与斜向原理也相同。根据我们整理出来的拦截原理,我们可以轻松地写出优手拦截的程序代码:

{
	int i = 0;
	//遍历行优手判断:
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][0] == '*' && board[i][2] == ' ')
		{
			board[i][2] = 'O';
			return 1;
		}
		if (board[i][1] == board[i][2] && board[i][1] == '*' && board[i][0] == ' ')
		{
			board[i][0] = 'O';
			return 1;
		}
		if (board[i][0] == board[i][2] && board[i][0] == '*' && board[i][1] == ' ')
		{
			board[i][1] = 'O';
			return 1;
		}
	}
	//遍历列优手判断:
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[0][i] == '*' && board[2][i] == ' ')
		{
			board[2][i] = 'O';
			return 1;
		}
		if (board[1][i] == board[2][i] && board[1][i] == '*' && board[0][i] == ' ')
		{
			board[0][i] = 'O';
			return 1;
		}
		if (board[0][i] == board[2][i] && board[0][i] == '*' && board[1][i] == ' ')
		{
			board[1][i] = 'O';
			return 1;
		}
	}
	//右斜对角线优手判断:
	if (board[0][0] == board[1][1] && board[0][0] == '*' && board[2][2] == ' ')
	{
		board[2][2] = 'O';
		return 1;
	}
	if (board[0][0] == board[2][2] && board[0][0] == '*' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][2] && board[1][1] == '*' && board[0][0] == ' ')
	{
		board[0][0] = 'O';
		return 1;
	}
	//左斜对角线优手判断:
	if (board[0][2] == board[1][1] && board[0][2] == '*' && board[2][0] == ' ')
	{
		board[2][0] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][0] && board[1][1] == '*' && board[0][2] == ' ')
	{
		board[0][2] = 'O';
		return 1;
	}
	if (board[0][2] == board[2][0] && board[0][2] == '*' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	return 0;
}

        如此,优手判断模块就完成了,接着我们只需要让电脑在着棋时进行判断,若是符合优手的着棋便进行优手着棋,若没有出现需要拦截的情况,再根据我们之前定义的随机值确定着棋坐标:

void computer_move(char board[ROW][COL], int row, int col)
{
	printf("电脑正在着棋!\n");
	if (computer_better_move(board, row, col))
	{
		;
	}
	else
	{
		while (1)
		{
			int x = rand() % row;
			int y = rand() % col;
			if (board[x][y] == ' ')
			{
				board[x][y] = 'O';
				break;
			}
		}
	}
}

        这里的执行逻辑是,if 语句的判断条件为函数“ 优手判断的返回值 ”。在优手判断函数中,若符合优手着棋的条件,便进行优手着棋,同时返回值为1,此时电脑着棋函数中的if语句判断为真,随机着棋便不再进行;若局面情况均不符合优手着棋情况,不进行优手着棋则右手判断函数返回值为0,电脑着棋函数中if语句判断为假,执行随机着棋操作

        2.进攻型着棋优化:

        以上即为井字棋游戏的防守型着棋优化。我们还可以对电脑的进攻进行优化,即在进行防守判断前先进行判断,若电脑方已有连字或隔子,即拥有直接获胜条件时,走出进攻型着棋

        原理很简单且与防守相同,不过这次的着棋优化判断对象为电脑自己的着棋,只需对防守着棋优化稍加改动即可:

int computer_Better_move(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] == 'O' && board[i][2] == ' ')
		{
			board[i][2] = 'O';
			return 1;
		}
		if (board[i][1] == board[i][2] && board[i][1] == 'O' && board[i][0] == ' ')
		{
			board[i][0] = 'O';
			return 1;
		}
		if (board[i][0] == board[i][2] && board[i][0] == 'O' && board[i][1] == ' ')
		{
			board[i][1] = 'O';
			return 1;
		}
	}
	//遍历列优手判断:
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[0][i] == 'O' && board[2][i] == ' ')
		{
			board[2][i] = 'O';
			return 1;
		}
		if (board[1][i] == board[2][i] && board[1][i] == 'O' && board[0][i] == ' ')
		{
			board[0][i] = 'O';
			return 1;
		}
		if (board[0][i] == board[2][i] && board[0][i] == 'O' && board[1][i] == ' ')
		{
			board[1][i] = 'O';
			return 1;
		}
	}
	//右斜对角线优手判断:
	if (board[0][0] == board[1][1] && board[0][0] == 'O' && board[2][2] == ' ')
	{
		board[2][2] = 'O';
		return 1;
	}
	if (board[0][0] == board[2][2] && board[0][0] == 'O' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][2] && board[1][1] == 'O' && board[0][0] == ' ')
	{
		board[0][0] = 'O';
		return 1;
	}
	//左斜对角线优手判断:
	if (board[0][2] == board[1][1] && board[0][2] == 'O' && board[2][0] == ' ')
	{
		board[2][0] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][0] && board[1][1] == 'O' && board[0][2] == ' ')
	{
		board[0][2] = 'O';
		return 1;
	}
	if (board[0][2] == board[2][0] && board[0][2] == 'O' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	return 0;
}

        定义出进攻型着棋优化后,我们同样对电脑着棋函数进行改动:

void computer_move(char board[ROW][COL], int row, int col)
{
	printf("电脑正在着棋!\n");
	if (computer_Better_move(board, row, col))
	{
		;
	}
	else
	{
		if (computer_better_move(board, row, col))
		{
			;
		}
		else
		{
			while (1)
			{
				int x = rand() % row;
				int y = rand() % col;
				if (board[x][y] == ' ')
				{
					board[x][y] = 'O';
					break;
				}
			}
		}
	}
}

        其实现逻辑与防守型着棋优化完全一致,不同的是此处采用了嵌套判断,先进行进攻型判断,没有进攻条件时进行防守,既没有进攻条件也无需进行防守时,再进行随机着棋

        至此,我们关于井字棋的着棋优化就完成了。

二、界面格式优化:

       我们目前写出的代码,运行起来后是这样的:

        这样一大串的打印形式,使得我们的界面看起来非常的不美观,那么有没有办法能够使得我们的界面看起来整洁一些呢?答案是,有。 

        我们可以通过使用下面两个语句来实现对界面的优化:

        1.Sleep休眠语句

Sleep(num);

        它的作用是, 使程序在延长预定时间后继续执行下一个步骤,设置休眠时间的方式是修改Sleep后方括号内的参数num的数值,单位为毫秒

        要注意的是该语句在使用时,需要引用头文件Windows.h

#include<Windows.h>

        2.system语句

int system(const char * command)

        该函数功能为执行 dos(windows系统)shell(Linux/Unix系统) 命令,参数字符串command为命令名。另,在windows系统下参数字符串不区分大小写

        我们一般情况下使用的系统为Windows系统,故针对Windows系统进行说明。在windows系统中,system函数直接在控制台调用一个command命令

        该函数语句在使用时需引用头文件stdlib.h:

#include<stdlib.h>

        在这里对我们的井字棋游戏界面进行优化时需要用到的命令是:

system("cls");

        这个命令的作用通俗来讲就是“ 清屏 ”,使用该函数命令,可以实现将之前打印在屏幕上的内容进行清空,并继续打印接下来的内容。运用在我们的程序中,就可以实现在每一个步骤完成后进行清屏,再继续执行接下来的步骤。

        通过灵活的结合使用上面的两种语句,我们可以使我们的游戏程序界面变得简洁而流畅

三、优化后最终全部代码:

        1.头文件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 player_move(char board[ROW][COL], int row, int col);
int computer_better_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);

        2.函数功能文件game.h:

#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 player_move(char board[ROW][COL], int row, int col)
{
	while (1)
	{
		int x, y;
		x = y = 0;
		printf("请着棋:\n");
		printf("您想下在第几行:");
		scanf("%d", &x);
		Sleep(1000);
		printf("您想下在第几列:");
		scanf("%d", &y);
		Sleep(500);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				system("cls");
				break;
			}
			else
			{
				system("cls");
				printf("这个位置已经有棋子了喔!请重新输入!\n");
				Sleep(1500);
				system("cls");
				print_board(board, ROW, COL);
			}
		}
		else
		{
			system("cls");
			printf("输入坐标有误,请重新输入!\n");
			Sleep(1500);
			system("cls");
			print_board(board, ROW, COL);
		}
	}
}

//电脑防守型着棋优手判定:
int computer_better_move(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][2] = 'O';
			return 1;
		}
		if (board[i][1] == board[i][2] && board[i][1] == '*' && board[i][0] == ' ')
		{
			board[i][0] = 'O';
			return 1;
		}
		if (board[i][0] == board[i][2] && board[i][0] == '*' && board[i][1] == ' ')
		{
			board[i][1] = 'O';
			return 1;
		}
	}
	//遍历列优手判断:
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[0][i] == '*' && board[2][i] == ' ')
		{
			board[2][i] = 'O';
			return 1;
		}
		if (board[1][i] == board[2][i] && board[1][i] == '*' && board[0][i] == ' ')
		{
			board[0][i] = 'O';
			return 1;
		}
		if (board[0][i] == board[2][i] && board[0][i] == '*' && board[1][i] == ' ')
		{
			board[1][i] = 'O';
			return 1;
		}
	}
	//右斜对角线优手判断:
	if (board[0][0] == board[1][1] && board[0][0] == '*' && board[2][2] == ' ')
	{
		board[2][2] = 'O';
		return 1;
	}
	if (board[0][0] == board[2][2] && board[0][0] == '*' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][2] && board[1][1] == '*' && board[0][0] == ' ')
	{
		board[0][0] = 'O';
		return 1;
	}
	//左斜对角线优手判断:
	if (board[0][2] == board[1][1] && board[0][2] == '*' && board[2][0] == ' ')
	{
		board[2][0] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][0] && board[1][1] == '*' && board[0][2] == ' ')
	{
		board[0][2] = 'O';
		return 1;
	}
	if (board[0][2] == board[2][0] && board[0][2] == '*' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	return 0;
}

//电脑进攻型着棋判定:
int computer_Better_move(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] == 'O' && board[i][2] == ' ')
		{
			board[i][2] = 'O';
			return 1;
		}
		if (board[i][1] == board[i][2] && board[i][1] == 'O' && board[i][0] == ' ')
		{
			board[i][0] = 'O';
			return 1;
		}
		if (board[i][0] == board[i][2] && board[i][0] == 'O' && board[i][1] == ' ')
		{
			board[i][1] = 'O';
			return 1;
		}
	}
	//遍历列优手判断:
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[0][i] == 'O' && board[2][i] == ' ')
		{
			board[2][i] = 'O';
			return 1;
		}
		if (board[1][i] == board[2][i] && board[1][i] == 'O' && board[0][i] == ' ')
		{
			board[0][i] = 'O';
			return 1;
		}
		if (board[0][i] == board[2][i] && board[0][i] == 'O' && board[1][i] == ' ')
		{
			board[1][i] = 'O';
			return 1;
		}
	}
	//右斜对角线优手判断:
	if (board[0][0] == board[1][1] && board[0][0] == 'O' && board[2][2] == ' ')
	{
		board[2][2] = 'O';
		return 1;
	}
	if (board[0][0] == board[2][2] && board[0][0] == 'O' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][2] && board[1][1] == 'O' && board[0][0] == ' ')
	{
		board[0][0] = 'O';
		return 1;
	}
	//左斜对角线优手判断:
	if (board[0][2] == board[1][1] && board[0][2] == 'O' && board[2][0] == ' ')
	{
		board[2][0] = 'O';
		return 1;
	}
	if (board[1][1] == board[2][0] && board[1][1] == 'O' && board[0][2] == ' ')
	{
		board[0][2] = 'O';
		return 1;
	}
	if (board[0][2] == board[2][0] && board[0][2] == 'O' && board[1][1] == ' ')
	{
		board[1][1] = 'O';
		return 1;
	}
	return 0;
}

//电脑着棋:
//随机生成坐标,只要没有被占用,就着棋
void computer_move(char board[ROW][COL], int row, int col)
{
	printf("电脑正在着棋!\n");
	Sleep(1000);
	if (computer_Better_move(board, row, col))
	{
		;
	}
	else
	{
		if (computer_better_move(board, row, col))
		{
			;
		}
		else
		{
			while (1)
			{
				int x = rand() % row;
				int y = rand() % col;
				if (board[x][y] == ' ')
				{
					board[x][y] = 'O';
					break;
				}
			}
		}
	}
	system("cls");
}
	

//平局判定:
int is_full(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++)
		{
			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][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][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';
}

        3.程序主体文件test.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"
//引用自定头文件

void menu()
{
	printf("************************\n");
	printf("************************\n");
	printf("**** 欢迎游玩三子棋 ****\n");
	printf("****     请选择     ****\n");
	printf("****   1.开始游戏   ****\n");
	printf("****   0.退出游戏   ****\n");
	printf("************************\n");
	printf("************************\n");
}

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);
		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;
		}
	}
	system("cls");
	if (ret == '*')
	{
		printf("恭喜玩家获得胜利!\n");
		Sleep(2000);
	}
	else if (ret == 'O')
	{
		printf("很遗憾,电脑取得了胜利。\n");
		Sleep(2000);
	}
	else if (ret == 'Q')
	{
		printf("棋逢对手!战至平局!\n");
		Sleep(2000);
	}
	system("cls");
}


void test()
{
	srand((unsigned int)time(NULL));
	//用时间戳来生成随机数,用于电脑着棋位置判断
	int input = 0;
	do
	{
		menu();
		printf("请您进行选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Sleep(500);
			system("cls");
			game();
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重新输入!\n");
			break;
		}
	} while (input);
}

int main()
{
	test();

	return 0;
}

四、总结:

        当我们完成以上优化后,一个游戏程序的雏形就完成了,我们对其功能和界面都进行了相当程度的优化。但是我们对于一个程序的优化过程,不仅仅如此而已,在以后的学习过程中,我们会学到更多知识,到时候我们还可以使用更高深的知识来对我们的游戏程序进行更深层次的优化

        到这里我们今天的学习过程就告一段落啦,各位小伙伴们下去以后一定要多看多敲多理解,牢固的将知识掌握才是我们的最终目的!靠自己的力量一步步跨过泥泞,才能离想要的生活越来越近,人生中最好的贵人,永远是努力的自己!

        新人初来乍到,辛苦各位小伙伴们动动小手,三连走一走 ~ ~ ~  最后,本文仍有许多不足之处,欢迎各位看官老爷随时私信批评指正!

  • 18
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

銮崽的干货分享基地

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

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

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

打赏作者

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

抵扣说明:

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

余额充值