C语言——五子棋人机对战

2022-09-07 15:14:56 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

先说下背景吧,写下这篇博客时,博主大一在读,C语言初学者,寒假无事,便计划写几个由C语言实现的小游戏以提升编程能力。在这篇博客里分享的是可人机对战的五子棋游戏。

棋类游戏要实现初级的机器智能,其核心思想便是: 感知(SENSE)-> 思考(THINK)-> 行动(ACT)。所以,本文将尽量以这个顺序介绍实现过程。

(1) 前期准备:

此程序中,机器方将以“当前最佳下法”来主导行动,即设计一套评价体系,针对每一个可落子处,综合评分,最终选最高分处落子。所以,我定义了“位置”的结构,包含横纵坐标和得分数三个参数。进而,定义一个位置结构体数组,用于记录每次评分时的可选位置,以及记录该数组最后一个有效元素下标的变量,用于更方便存入取出。另外,诸如初始化棋盘,打印棋盘,初始化位置结构体数组等自定义函数,也一并贴在此处。

代码语言:javascript复制
#include<stdio.h>
#include<windows.h>

#define TRUE 1
#define FALSE 0

struct position {
	int x;
	int y;
	int score;
};

char chess_board[16][16];
struct position positions[50];
int position_order;

void initialize_board(void)
{
	int i, j;
	for (i = 0;i < 16;i  ) {
		for (j = 0;j < 16;j  ) {
			chess_board[i][j] = ' ';
		}
	}
}
void initial_positions(void)
{
	positions[0].x = positions[0].y = positions[0].score = 0;
	for (position_order = 1;position_order < 50;position_order  ) {
		positions[position_order] = positions[0];
	}
	position_order = 0;
}
void print_board(void)
{
	int i, j;
	/*print letters*/
	printf("  ");
	for (j = 0;j < 16;j  ) {
		printf("  %c", 'A'   j);
	}
	putchar('n');
	/*print chess board*/
	for (i = 0;i < 33;i  ) {
		if (i % 2 == 1) {
			printf("-", (i   1) / 2);
		}
		else {
			printf("  ");
		}
		for (j = 0;j < 33;j  ) {
			/*The first row*/
			if (i == 0 && j == 0) {
				printf("┌  ");
			}
			else if (i == 0 && j==32) {
				printf("┐");
			}
			else if (i == 0 && j % 2 == 0) {
				printf("┬  ");
			}
			/*The last row*/
			if (i == 32 && j == 0) {
				printf("└  ");
			}
			else if (i == 32 && j == 32) {
				printf("┘");
			}
			else if (i == 32 && j % 2 == 0) {
				printf("┴  ");
			}
			/*The other rows*/
			if (i != 0 && i != 32) {
				if (i % 2 == 0) {
					if (j == 0) {
						printf("├  ");
					}
					else if (j == 32) {
						printf("┤");
					}
					else if (j % 2 == 0) {
						printf("┼  ");
					}
				}
				else if(j % 2 == 0) {
					printf("  %c", chess_board[(i - 1) / 2][(j   1) / 2]);
				}
			}
		}
		putchar('n');
	}
}
代码语言:javascript复制
int is_full(void) {
	int i, j;
	for (i = 0;i < 16;i  ) {
		for (j = 0;j < 16;j  ) {
			if (chess_board[i][j] == ' ')
				return(FALSE);
		}
	}
	return(TRUE);
}
void is_win(int x, int y, char cp)
{
	int i, num=0;
	/*row*/
	for (i = 0;chess_board[y][x   i] == cp;i  , num  );
	for (i = -1;chess_board[y][x   i] == cp;i--, num  );
	if (num >= 5) {
		system("cls");
		print_board();
		printf("%c win!", cp);
		system("pause");
	}
	else {
		num = 0;
	}
	for (i = 0;chess_board[y   i][x] == cp;i  , num  );
	for (i = -1;chess_board[y   i][x] == cp;i--, num  );
	if (num >= 5){
		system("cls");
		print_board();
		printf("%c win!", cp);
		system("pause");
	}
	else {
		num = 0;
	}
	for (i = 0;chess_board[y   i][x   i] == cp;i  , num  );
	for (i = -1;chess_board[y   i][x   i] == cp;i--, num  );
	if (num >= 5) {
		system("cls");
		print_board();
		printf("%c win!", cp);
		system("pause");
	}
	else {
		num = 0;
	}
	for (i = 0;chess_board[y   i][x - i] == cp;i  , num  );
	for (i = -1;chess_board[y   i][x - i] == cp;i--, num  );
	if (num >= 5) {
		system("cls");
		print_board();
		printf("%c win!", cp);
		system("pause");
	}
	else {
		num = 0;
	}
}
void scan(void)
{
	char c;
	int i;
	do {
		printf("输入落子行列:");
		scanf_s("%d%c", &i, &c);
		if (!(chess_board[i - 1][c - 'A'] == ' '))
			continue;
		chess_board[i - 1][c - 'A'] = '*';
		break;
	} while (TRUE);
	is_win(c - 'A', i - 1, '*');
}
代码语言:javascript复制

贴一张实际效果图:

(2)感知:

这一步中,将遍历棋盘中所有可落子位置,从横,纵,右斜和左斜四个方向,以棋子状态进行评分,总后将有效位置的总分记录在数组中。

下面先贴出的是评分函数,其接受一个大小为5的字符数组(记录在选定位置周围截取的包含该位置的”一行“棋子状态),一个描述选定位置在该行中的位置的参数x。内部由若干分支构成,对应每种棋子状态,返回对该状态的评分。

代码语言:javascript复制
void reverse(char row[],int len)            //由于一行棋子具有对称性,故x=3,x=4的状态可翻转归结为x=0,x=1的状态
{
	char temp;
	int i,j;
	for (i = 0, j = len - 1;i <= j;i  , j--) {
		temp = row[i];
		row[i] = row[j];
		row[j] = temp;
	}
}
int score(char row[], int x)                //'O'代表白棋,'*'代表黑棋,人执黑,机器执白
{                                           //每个if分支后的注释,'_'代表空格(即可落子处),'O'为白棋,'*'为黑棋
	if (x > 2) {                        //'?'表示已可给分,该位置状态不必获取
		reverse(row,5);             //三者的排布代表截取行的棋子状态 		x = 4 - x;                  //紧接着的return,返回的便是对该位置的评分
	}
	switch (x) {
	case 0: {
		if (row[1] == 'O' && (row[2] == 'O' || row[2] == ' ') && (row[3] == 'O' || row[3] == ' ') && (row[4] == 'O' || row[4] == ' ')) {
			if (row[x   2] == ' ') {		//_O_??
				return(15);
			}
			else if (row[x   3] == ' ') {	//_OO_?
				return(50);
			}
			else if (row[x   4] == ' ') {	//_OOO_
				return(90);
			}
			else {							//_OOOO
				return(1000);
			}
		}
		else if (row[1] == '*' && (row[2] == '*' || row[2] == ' ') && (row[3] == '*' || row[3] == ' ') && (row[4] == '*' || row[4] == ' ')) {
			if (row[x   2] == ' ') {		//_*_??
				return(5);
			}
			else if (row[x   3] == ' ') {	//_**_?
				return(30);
			}
			else if (row[x   4] == ' ') {	//_***_
				return(70);
			}
			else {							//_****
				return(500);
			}
		}
	};break;
	case 1: {
		if ((row[0] == 'O' || row[0] == ' ') && (row[2] == 'O' || row[2] == ' ') && (row[3] == 'O' || row[3] == ' ') && (row[4] == 'O' || row[4] == ' ')) {
			if (row[0] == 'O') {
				if (row[2] == ' ') {		//O_ _??
					return(15);
				}
				else if (row[3] == ' ') {	//O_O_??
					return(50);
				}
				else if (row[4] == ' ') {	//O_OO_
					return(90);
				}
				else {						//O_OOO
					return(1000);
				}
			}
			else if (row[2] == ' ') {		//_ _ _??
				return(0);
			}
			else if (row[3] == ' ') {		//_ _O_?
				return(15);
			}
			else if (row[4] == ' ') {		//_ _OO_
				return(50);
			}
			else {							//_ _OOO
				return(80);
			}
		}
		else if ((row[0] == '*' || row[0] == ' ') && (row[2] == '*' || row[2] == ' ') && (row[3] == '*' || row[3] == ' ') && (row[4] == '*' || row[4] == ' ')) {
			if (row[0] == '*') {
				if (row[2] == ' ') {		//*_ _??
					return(5);
				}
				else if (row[3] == ' ') {	//*_*_?
					return(30);
				}
				else if (row[4] == ' ') {	//*_**_
					return(70);
				}
				else {						//*_***
					return(500);
				}
			}
			else if (row[2] == ' ') {		//_ _ _??
				return(0);
			}
			else if (row[3] == ' ') {		//_ _*_?
				return(5);
			}
			else if (row[4] == ' ') {		//_ _**_
				return(30);
			}
			else {							//_ _***
				return(60);
			}
		}
	}break;
	case 2: {
		if ((row[0] == 'O' || row[0] == ' ') && (row[1] == 'O' || row[2] == ' ') && (row[3] == 'O' || row[3] == ' ') && (row[4] == 'O' || row[4] == ' ')) {
			if (row[1] == 'O') {
				if (row[3] == 'O') {
					if (row[0] == 'O') {
						if (row[4] == 'O') {//OO_OO
							return(1000);
						}
						else {
							return(90);		//OO_O_
						}
					}
					else {
						if (row[4] == 'O') {//_O_OO
							return(90);
						}
						else {				//_O_O_
							return(50);
						}
					}
				}
				else {
					if (row[0] == 'O') {	//OO_ _?
						return(40);
					}
					else {					//_O_ _?
						return(15);
					}
				}
			}
			else {
				if (row[3] == 'O') {
					if (row[4] == 'O') {	//_ _ _OO
						return(40);
					}
					else {					//_ _ _O_
						return(15);
					}
				}
			}
		}
		else if ((row[0] == '*' || row[0] == ' ') && (row[1] == '*' || row[2] == ' ') && (row[3] == '*' || row[3] == ' ') && (row[4] == '*' || row[4] == ' ')) {
			if (row[1] == '*') {
				if (row[3] == '*') {
					if (row[0] == '*') {
						if (row[4] == '*') {//**_**
							return(500);
						}
						else {				//**_*_
							return(70);
						}
					}
					else {
						if (row[4] == '*') {//_*_**
							return(70);
						}
						else {				//_*_*_
							return(30);
						}
					}
				}
				else {
					if (row[0] == '*') {	//**_ _ _
						return(20);
					}
					else {					//_*_ _?
						return(5);
					}
				}
			}
			else {
				if (row[3] == '*') {		//_ _ _**
					if (row[4] == '*') {
						return(20);
					}
					else {					//_ _ _*_
						return(5);
					}
				}
			}
		}
	}break;
	}
	return(0);
}
代码语言:javascript复制

接下来呢,就是感知中的另一个主要部分,其功能是对某一位置,从四个方向,截取评分函数所需的一行五位置数组。

代码语言:javascript复制
int sense_row(int x, int y)        //横向截取,如下列一排示例中,x,y所代表的位置是第五个O{                                  //[OOOOO]OOOO,O[OOOOO]OOO,OO[OOOOO]OO......如此这般依次截取,其余方向类似	int sum = 0, i, j;	char row[5];	for (i = x - 4;i <= x;i  ) {		if (!(i >= 0 && i   4 <= 15)) {			continue;		}		else {			for (j = 0;j < 5;j  ) {				row[j] = chess_board[y][i   j];			}			sum  = score(row, x - i);		}	}	return(sum);}int sense_col(int x, int y){	int sum = 0, i, j;	char row[5];	for (i = y - 4;i <= y;i  ) {		if (!(i >= 0 && i   4 <= 15)) {			continue;		}		else {			for (j = 0;j < 5;j  ) {				row[j] = chess_board[i   j][x];			}			sum  = score(row, y - i);		}	}	return(sum);}int sense_right_bias(int x, int y){	int sum = 0, i, j;	char row[5];	for (i = -4;i <= 0;i  ) {		if (!(y   i >= 0 && x   i >= 0 && y   i   4 <= 15 && x   i   4 <= 15)) {			continue;		}		else {			for (j = 0;j < 5;j  ) {				row[j] = chess_board[y   i   j][x   i   j];			}			sum  = score(row, -i);		}	}	return(sum);}int sense_left_bias(int x, int y){	int sum = 0, i, j;	char row[5];	for (i = -4;i <= 0;i  ) {		if (!(y - i <= 15 && x   i >= 0 && y - i - 4 >= 0 && x   i   4 <= 15)) {			continue;		}		else {			for (j = 0;j < 5;j  ) {				row[j] = chess_board[y - i - j][x   i   j];			}			sum  = score(row, -i);		}	}	return(sum);}void sense(void)            //将四个方向上的评分综合并记录{	int x, y, sum = 0;	initial_positions();	for (y = 0;y < 16;y  ) {		for (x = 0;x < 16;x  ) {			if (chess_board[y][x] != ' ') {				continue;			}			sum  = sense_col(x, y);			sum  = sense_row(x, y);			sum  = sense_left_bias(x, y);			sum  = sense_right_bias(x, y);			if (sum != 0) {				positions[position_order].score = sum;				positions[position_order].x = x;				positions[position_order].y = y;				position_order  ;				sum = 0;			}		}	}}

(3)思考与行动:

思考便是在感知并评分后记录下来的位置数组中,寻找最高分位置。行动紧跟其后,在最高分位置落子。博主在实现过程中,遇到有几个相同最高分时,只取第一个最高分,读者可添加随机功能,任选其一落子。代码如下。

代码语言:javascript复制
void think_act(void){	int max = 0, max_order, i;	for (i = 0;i < position_order;i  ) {		if (positions[i].score > max) {			max = positions[i].score;			max_order = i;		}	}	chess_board[positions[max_order].y][positions[max_order].x] = 'O';	is_win(positions[max_order].x, positions[max_order].y, 'O');}

(4)主函数:

代码语言:javascript复制
代码语言:javascript复制
void main(void){	initialize_board();	print_board();	while (!is_full()) {		scan();		sense();		think_act();		system("CLS");		print_board();	}	system("pause");}

以上便是全部内容,希望可以通过博客与大家学习交流。博主编程水平有限,第一次写博客,必定有许多遗漏之处,思路也是乱糟糟的,希望诸位有何高见,不吝赐教。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/148241.html原文链接:https://javaforall.cn

0 人点赞