C语言第十课(上):编写井字棋游戏(综合练习1)

news2024/12/28 3:31:32

目录

前言:

一、文件建立:

        1.头文件game.h:

        2.函数定义文件game.c:

        3.工程测试文件test.c:

 二、编写井字棋游戏:

        1.程序整体执行思路:

        2.menu菜单函数实现:

        3.game游戏函数逻辑:

        4.game函数中各功能的实现:

        ①.头文件game.h内容:

        ②.棋盘的初始化init_board:

        ③.打印棋盘print_board:

        ④.玩家着棋player_move:

        ⑤.电脑着棋computer_move:

        ⑥.胜利判定is_win:

        ⑦.交替着棋:

三、完整程序代码:

        1.game.h:

        2.game.c:

        3.test.c:

四、总结:


前言:

        在前面的九课中,我们学习了C语言程序的各个重要组成部分,结合往期文末的一些练习,想必现在大家对于知识已经有了不错的掌握,本文我将带领各位小伙伴们运用我们之前学过的知识,使用标准工程格式来写出我们的第一个小游戏——井字棋(又名三子棋)。

一、文件建立:

        按照我们之前提到过的标准工程格式,我们首先建立三个文件:

        1.头文件game.h:

        该文件用于包含其它头文件,并存放功能实现函数的函数声明。好处是可以整合整个程序中所用到的全部头文件和函数声明,一目了然。并在引用时只需引用头文件game.h,其它被包含的头文件与函数声明均生效,省时省力:

        2.函数定义文件game.c:

        该文件用于书写所有的程序功能实现的函数定义。使用这样格式的好处主要有三点:一来将所有定义书写在一起,便于我们进行阅读和修改;二是大大提升了程序的可移植性,当他人想要使用我们定义的函数时,只需将整个game.c文件发送给对方即可;第三点也是最重要的一点,我们可以通过将其设置为静态库,使其被转化为二进制码而进行加密,从而对我们的核心函数实现进行隐藏

        3.工程测试文件test.c:

        该文件用于书写我们的程序主体部分,供我们通过使用代码来对程序进行执行逻辑的编辑和检查。结合前两个文件的使用,可以使得我们的程序逻辑变得清晰,极有利于我们进行程序的运行逻辑检查

 二、编写井字棋游戏:

        1.程序整体执行思路:

        各位小伙伴们一定都玩过各种各样的游戏,对于游戏也都有一定的了解。而游戏最中心的程序主体也不过是界面游戏实现两个部分,依此我们可以直接写出井字棋小游戏的最核心主体

void menu(void)
//菜单函数
{
	...
}

void game(void)
//游戏函数
{
	...
}

void test()
//整体流程
{
    menu();
    game();
    ...
}

int main()
{
    test();

	return 0;
}

        他的执行流程很明确:自定义两个函数,menu为菜单函数,负责向玩家打印游戏菜单game为游戏函数,负责实现整个游戏的逻辑实现。当程序开始编译运行后,将会按照顺序,先执行menu函数打印出游戏菜单,接着执行game函数让玩家们进行游戏。

        并且我们都知道,大多数时候玩家往往会选择多次进行游戏,所以我们通过在数函数部分结合我们在之前介绍过的的循环和分支语句很容易实现该目的:

void test()
{
	int input = 0;
	do
	{
        menu();
        printf("请您进行选择:");
	    scanf("%d", input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重新输入!\n");
			break;
		}
	} while (input);

	return 0;
}

        通过使用do...while循环语句,既可以保证我们的玩家选择判断至少被执行一次,又可以供玩家选择继续游戏。

        又通过使用switch分支语句,在打印出游戏菜单后让玩家进行选择,若玩家选择1则继续进行游戏,选择0则退出游戏。同时对输入数据进行合法性检测,使输入数据既不是1也不是0时要求玩家重新进行输入。

        我们将程序运行起来检查一下我们的程序主体逻辑功能:

        可以看到整个循环与分支选择以及功能模块都已成功实现,至此程序主体部分就已经确定好了,接下来只需完成功能模块的逻辑实现即可。 

        2.menu菜单函数实现:

        确定了程序的主体逻辑结构,我们就开始着手进行具体功能的实现。首当其冲的便是向玩家们展示游戏内容的menu菜单函数

        实质上这一部分并没有什么难度,这部分的功能仅仅是向玩家们打印出游戏功能供玩家们阅读,并没有实质的逻辑处理,因此仅使用printf函数即可轻松实现:

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

        没错,菜单函数就是这么朴实无华,运行起来看看效果:

        轻轻松松,对于现在的小伙伴们来说正能说是是小菜一碟。菜单功能已经实现,可以进行到下一模块的编写。

        3.game游戏函数逻辑:

        (注:此处我们只关心实现逻辑,而具体功能实现后面会逐一进行研究)

        程序的主体确定好了,游戏菜单也成功打印出来了,但是我们都很清楚,三子棋最重要的,还是游戏内容的实现,毕竟没有玩家会愿意去玩一个仅仅只有菜单的“游戏”。

        首先我们要来了解一下三子棋游戏的游戏规则和胜利条件,只有了解了这些我们才能进行相关功能的实现。三子棋顾名思义,首先它是一种棋类游戏,它有标准的3×3九宫格棋盘,由游戏双方交替着棋,两方相互进行阻拦,当其中一方达成同一排同一列斜向三颗棋子连线时,该玩家获得胜利。

        梳理了规则,我们开始尝试实现。

        在游戏函数中,首先我们进行棋盘的初始化,即在game函数内打印出内留着棋位置的九宫格棋盘。具体的实现方式是,创建一个九宫格数组,用于处理玩家与电脑的着棋操作,同时在没有着棋的位置应当进行初始化,即打印空格:

void game()
{
	char board[ROW][COL];
	//创建九宫格数组,用于处理着棋
	char ret = 0;
	//定义字符,用于判断输赢后跳出着棋循环
	init_board(board, ROW, COL);
	//将数组与行列数传递给封装函数进行初始化
	print_board(board, ROW, COL);
	//将初始化完成的棋盘进行打印
}

        棋盘进行过初始化并打印后,就可以正式开始我们的下棋游戏了,很明显,在这一阶段要实现的功能就是玩家与电脑交替进行着棋,并在每次着棋后对棋局进行判断,若达成胜利条件则停止着棋并宣布获胜方未达成胜利条件则继续循环着棋

(这里的输赢判断中的字符判断暂且不考虑,后面部分的实现过程会详细讲解)

char ret=0;
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 == 'O')
{
	printf("很遗憾,电脑取得了胜利。\n");
}
else if (ret == 'Q')
{
	printf("棋逢对手!战至平局!\n");
}

        这样,游戏的大致逻辑就编辑完成了,而接下来就是重点的各项功能的实现。

        4.game函数中各功能的实现:

        ①.头文件game.h内容:

        上面我们说过,在我们的头文件中,存放的是我们的其他头文件、宏定义与函数的声明

        首先是宏定义

(此处为定义棋盘参数行ROW列COL,并均赋值为3,即三行三列)

#define ROW 3
#define COL 3
//进行宏定义,好处是进行游戏修改时不需要对每个参数都进行修改

        我们的程序为了更好的可拓展性,往往会对程序进行扩展。例如我们现在书写的是井字棋,倘若日后想要把他改写为五子棋等等,只需要对这两个宏定义的量进行改变即可,而免去了挨个修改参数的麻烦。

        接着是头文件,为了使test.c文件看起来更加简洁清晰,我们可以将其他的头文件引用在game.h文件中,而同时,因为我们的test.c文件中又会引用我们的game.h头文件,所以被包含在game.h头文件中的其他头文件也会被附带引用至我们的test.c文件中:

(此处为本游戏所需要用到的三个头文件,关于其作用后面会逐一讲解)

#include<stdio.h>
#include<stdlib.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 playr_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);

        ②.棋盘的初始化init_board:

        这里的实现很简单,我们在之前就已经定义好了一个二维字符型数组board,在函数接收该数组后,首先对二维数组中的内容进行初始化。我们看到的空棋盘实际上并不为空,而是在未着棋处打印了一个空格字符。故其初始化也很简单,只需将其遍历,使数组board中的每一项均为空格字符即可:

//棋盘初始化(使无着棋位置均使用空格占位):
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] = ' ';
		}
	}
}

        ③.打印棋盘print_board:

        无论是我们将棋盘进行了初始化并准备开始游戏之后,还是玩家与电脑双方选择着棋位置后,都需要将当前的棋局反馈打印在我们的屏幕上,通样的我们可以使用遍历的思想来逐行进行处理:

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");
		}
	}
}

        我们可以看到在每一行的处理中,我们都以列为单位进行处理交替执行打印字符与打印列分隔符“ | ”,并同时在打印列分隔符“ | ”时进行边界限制

        而在数据行打印后,同样每个数据行也与一组行分隔符“ ------- ”一同打印,且行分隔符间也使用与数据行同样的方式进行列的分隔。同时在使用行分隔符时也进行了边界限制

        如此循环每个数据行的处理,最终完成棋盘的打印,我们将其编译运行来看效果:

        可以看到当前我们定义的棋盘格式已经完成了打印操作。

        ④.玩家着棋player_move:

        棋盘已经初始化完成了,这下我们就可以开始下棋了,要实现玩家的着棋操作,原理是由玩家输入着棋坐标,由函数进行处理后对数组内相应位置的数据元素进行符号替换

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);
		printf("您想下在第几列:");
		scanf("%d", &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");
		}
	}
}

        在这里有两个要注意的地方,一个是我们输入坐标时的行与列均是从1开始,而二维数组中的数据元素下标均为从0开始,所以在对下标进行操作时,需要将玩家输入的行和列均减一后进行操作。

        第二点是在进行着棋时应当进行着棋可行性判断,若输入坐标不在棋盘范围内,应当予以提示“输入坐标有误,请重新输入”,若着棋处已经下过棋子了,即不为空格符,应当提示“这个位置已经有棋子了喔!请重新输入”。

        ⑤.电脑着棋computer_move:

        当玩家着棋后,将会交替至电脑进行着棋操作,那么电脑如何着棋呢?

        首先我们来看电脑的着棋步骤:

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

        很明显,电脑着棋的原理与玩家着棋原理完全一致,均是通过输入一个坐标,通过数组下标对数组内数据元素进行替换完成着棋。但问题是,电脑不同于玩家,并没有很强的随意性,可我们总不能让电脑顺着数组中的位置一个位置一个位置着棋吧?

        所以这里显然我们需要想个办法来让电脑的着棋具有一定的随机性——使用时间戳

        大家对rand这个函数还有印象吗?

        我们通过在头文件中引用头文件<time.h>

#include<time.h>

        并在主程序中使用srand函数,并根据时间戳来规定随机值起点。同时这个随机数的起点在一次游戏中只需要生成一次即可,故应当放置在主函数的循环之外。并且这里一定注意,time(NULL)的返回值为time_t类型,而srand函数的参数应当为整形,故在此需要使用(unsigned int)来进行强制类型转换,将time(NULL)的返回值转换为无符号整型:

srand((unsigned int)time(NULL));
//用时间戳来生成随机数,用于电脑着棋位置判断

        现在我们就可以使用根据时间戳规定起点生成的随机数了:

int x = rand();
int y = rand();

        可是这样就行了吗?显然不是,这个随机数的起点在哪里谁也不知道,而我们棋盘最多也只有三行三列,除非你的运气好到离谱,否则大概率会导致电脑着棋在这一步卡住,那么我们怎样进行处理呢?答案很简单,我们只需要让生成的随机数对我们的行和列进行求余即可,求余的结果刚好是0到行/列数-1,即刚好对应了我们二维数组的下标:

int x = rand() % row;
int y = rand() % col;

        所以最终电脑着棋的函数定义就实现了:

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] = 'O';
			break;
		}
	}
}

        ⑥.胜利判定is_win:

        我们的游戏一定是需要进行胜利判定的,不可能无休止的进行下去。

        在进行胜利判定时,首先分为两种情况,即棋盘已满棋盘未满

        棋盘已满分为两种情况,一是若此时刚好分出胜负,就可以宣布胜利者了,另一种则是棋盘已满但并未分出胜负,此时应当宣布平局

        棋盘未满则分为三种情况,一是玩家获得了胜利,此时应当停止着棋并宣布玩家获得胜利;二是电脑获得了胜利,此时应当停止着棋并宣布电脑获胜;第三种情况便是棋盘既没有满,同时也没有获胜方出现,此时应当继续进行着棋:

        首先我们逐行对行进行判定,若一行中三个数据元素均相同且不为空格符,则返回胜利者的棋子类型:

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];
}

        再往后,我们使用遍历思想进行平局判定,我们依次检查每一个元素,只要存在元素为空格符就返回0,过程中遇到不为空格符的数据元素则不进行操作。但若所有元素都不为空格符,即棋盘已满,则返回1

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;
}

        接着在我们的判定函数中接收平局判定结果,若判定结果为真,且没有达成前述任何一个胜利条件,则返回平局符号Q

if (is_full(board, row, col) == 1)
{
	return 'Q';
}

        但若上述情况都没有发生,即棋盘既没有满,也没有任何一方获胜,则返回继续着棋符号C:

return 'C';

        把它们组装在一起,于是我们得到了以下完整的胜利判定函数

//平局判定:
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';
}

        ⑦.交替着棋:

        在胜利判定函数中我们看到,经由函数的判定,我们可以得到四种不同的返回值:" * "表示玩家胜利," O "表示电脑胜利," Q "表示平局," 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;
	}
}

        若返回值为表示继续游戏的C,则由循环条件可以得出将会继续进行着棋和判定的循环;若返回值不为C,则由break语句跳出循环

        当跳出循环后,就意味着已经产生了游戏结果,此时再根据三种不同的返回值来判定和宣布游戏的胜利者

if (ret == '*')
{
	printf("恭喜玩家获得胜利!\n");
}
else if (ret == 'O')
{
	printf("很遗憾,电脑取得了胜利。\n");
}
else if (ret == 'Q')
{
	printf("棋逢对手!战至平局!\n");
}

        至此,井字棋游戏最基础的游戏功能就得到了实现。

三、完整程序代码:

        1.game.h:

#pragma once

#define ROW 3
#define COL 3
//进行宏定义,好处是进行游戏修改时不需要对每个参数都进行修改

#include<stdio.h>
#include<stdlib.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);

        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 player_move(char board[ROW][COL], int row, int col)
{
	while (1)
	{
		int x, y;
		x = y = 0;
		printf("请着棋:\n");
		printf("您想下在第几行:");
		scanf("%d", &x);
		printf("您想下在第几列:");
		scanf("%d", &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] = 'O';
			break;
		}
	}
}

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


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

int main()
{
	test();

	return 0;
}

四、总结:

        以上就是井字棋游戏的最基础功能的实现,但我们发现它仍存在着很多问题。例如它还没有优手判定,即电脑的着棋过分随机而不会对玩家的着棋威胁进行堵截;再比如我们的界面显示格式不够优美,同一屏幕打印显得游戏较为凌乱等等。

        而这些问题我将会在下一篇文章中为各位小伙伴们进行介绍。希望各位小伙伴们下去以后仔细思考,认真研究,将这个综合性较强的阶段练手充分消化吸收。

        以上就是今天我为大家介绍的基础版井字棋的知识啦!所有的努力,不是为了让别人觉得你了不起,而是让自己过得充实而有追求!

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/14222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux环境下基于VSCode和CMake实现C/C++开发

layout: post title: Linux环境下基于VSCode和CMake实现C/C开发 description: Linux环境下基于VSCode和CMake实现C/C开发 tag: 开发工具 文章目录Linux开发环境Linux目录结构常用指令选项ls&#xff1a;list directory contentscd&#xff1a;change directorytouch&#xff1a…

【附源码】计算机毕业设计JAVA校园讲座管理

【附源码】计算机毕业设计JAVA校园讲座管理 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA mybati…

linux驱动之mmap地址映射

应用场景 首先在linux中应用程序无法是直接访问驱动程序的数据的, 需要通过 copy_to_user 和 copy_from_user才能实现数据传输, 那么数据量大了以后如LCD的数据, 那么就会有很长的耗时, 为了解决这一问题, 引入mmap, 将底层物理地址映射出来, 让应用程序得以直接读写这一块内存…

Linux进程控制(下)--->进程程序替换

文章目录什么是进程程序替换为什么要进行进程程序替换怎么进行进程程序替换execlexecvexeclpexecvpexecleexecvpe使用c的可执行程序调用一个python脚本如何理解进程程序替换进程程序替换接口的返回值从进程独立性体会程序替换什么是进程程序替换 在讲进程程序替换之前&#xf…

[附源码]java毕业设计兰州市邮政公司新邮预订户管理信息系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

计算机毕业设计springboot+vue+elementUI在线漫画周边销售购物交流系统

项目介绍 任何系统都要遵循系统设计的基本流程&#xff0c;本系统也不例外&#xff0c;同样需要经过市场进行调研&#xff0c;漫画需求进行分析&#xff0c;概要设计&#xff0c;系统详细设计&#xff0c;测试和编码等步骤&#xff0c;设计并实现了“漫画之家”系统 。系统选用…

web前端设计与开发期末作品_期末大作业-疫情

Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业&#xff0c;击疫情致敬逆行者感人类题材 | 致敬逆行者网页设计作品 | 大学生抗疫感动专题网页设计作业模板 | 等网站的设计与制作 | HTML期末大学生网页设计作业 HTML&#xff1a…

大一学生Web课程设计 红酒美食主题网页制作(HTML+CSS+JavaScript)

Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 茶文化网站 | 中华传统文化题材 | 京剧文化水墨风书画 | 中国民间年画文化艺术网站 | HTML期末大学生网页设计作业 HTML&#xff1a;结构 CSS&#xff1a;样式 在操作方面上运…

通用后台管理系统前端界面Ⅵ——首页、登录页、404页面

登录页 1、为了方便起见&#xff0c;先将element-ui的使用改为全局引入的方式。修改main.js文件如下&#xff1a; import Vue from vue import App from ./App.vue import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css //这个是局部引入&#xff0…

idea创建javaweb项目步骤超详细(2022最新版本)

目录 前言&#xff1a; 一、新建文件 1.在idea里面点击文件-新建-项目 2.新建项目-更改名称为自己想要的项目名称-创建 3.右键自己建立的项目-添加框架支持 4.勾选Web应用程序-确定 5.建立成功界面 二、配置tomcat 6.点击添加配置文件 7.点击这个 8.选择这个tomcat的本…

数据分析利器:XGBoost算法最佳解析

XGBoost是一种经典的集成式提升算法框架&#xff0c;具有训练效率高、预测效果好、可控参数多、使用方便等特性&#xff0c;是大数据分析领域的一柄利器。在实际业务中&#xff0c;XGBoost经常被运用于用户行为预判、用户标签预测、用户信用评分等项目中。XGBoost算法框架涉及到…

javaWeb项目基于tomcat运行部署后访问方案总结

javaWeb项目基于tomcat运行部署后访问方案总结 1.需求背景 最近接到一个老项目,这个是一个前后没有分离的java+jsp项目,所以前后端的代码是在一个项目里面的,因此在这个项目上开发就需要面临第一个问题:启动运行项目。简介:Java Web,是用Java技术来解决相关web互联网领域…

字符串的算法题目-字符串

题目一&#xff1a; 描述 对于一个长度为 n 字符串&#xff0c;我们需要对它做一些变形。 首先这个字符串中包含着一些空格&#xff0c;就像"Hello World"一样&#xff0c;然后我们要做的是把这个字符串中由空格隔开的单词反序&#xff0c;同时反转每个字符的大小写…

【Java】数组中值得说的那些事

文章目录前言一、数组的创建及初始化&#x1f333;1、数组的创建&#x1f333;2、数组的初始化&#x1f351;&#xff08;1&#xff09;动态初始化&#x1f351;&#xff08;2&#xff09;静态初始化二、数组的使用&#x1f333;1、数组中元素访问&#x1f333;2、遍历数组&…

力扣LeatCode算法题-两数之和(二)

力扣算法题第二题&#xff0c;两数相加算法题&#xff1a; 要求&#xff1a; //给出两个 非空 的链表用来表示两个非负的整数。其中&#xff0c;它们各自的位数是按照 逆序 的方式存储的&#xff0c;并且它们的每个节点只能存储 一位 数字。 //如果&#xff0c;我们将这两个数…

企业使用有线和5G主备双链路上网配置案例

场景介绍 典型的企业分支通常还是采用有线链路作为主链路&#xff0c;例如以太链路、MPLS专线等。为了提升分支站点的可靠性&#xff0c;企业一般都会部署两条上行链路&#xff0c;一条为主链路&#xff0c;一条为备链路。如果两条上行链路都采用有线&#xff0c;成本会比较高&…

JVM StringTable

文章目录学习资料StringTableString的基本特性String的内存分配StringTable为什么要调整&#xff1f;String的基本操作字符串拼接操作拼接操作与append操作的效率对比intern()的使用学习资料 【尚硅谷宋红康JVM全套教程&#xff08;详解java虚拟机&#xff09;】 【阿里巴巴Ja…

prometheus exporter 监控主机

前提要求部署Grafana 前言 有许多库和服务器可以帮助将第三方系统中的现有指标导出为Prometheus指标。在无法直接使用Prometheus度量(例如&#xff0c;HAProxy或Linux系统统计数据)对给定系统进行检测的情况下&#xff0c;这是非常有用的。 node-exporter Linux操作系统采集&…

【Hack The Box】windows练习-- Scrambled

HTB 学习笔记 【Hack The Box】windows练习-- Scrambled &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月17日&#x1f334; &…

第2-3-5章 删除附件的接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss

文章目录5.4 接口开发-根据id删除附件5.4.1 接口文档5.4.2 代码实现5.4.3 接口测试5.4.4 测试ALI和FAST_DFS以及MINIO上传和删除的接口5.4.4.1 阿里云OSS上传和删除5.4.4.2 FastDFS上传和删除5.4.4.3 Minio上传和删除5.5 接口开发-根据业务类型/业务id删除附件5.5.1 接口文档5.…