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

C语言——五子棋人机对战         先说下背景吧,写下这篇博客时,博主大一在读,C语言初学者,寒假无事,便计划写几个由C语言实现的小游戏以提升编程能力。在这篇博客里分享的是可人机对战的五子棋游戏。         棋类游戏要实现初级的机器智能,其核心思想便是:感知(SENSE)->思考(THINK)->行动(ACT)。所以,本文将尽量以这个顺序介绍实现过程。(1)前期准备:    此程序中,机器…

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

         先说下背景吧,写下这篇博客时,博主大一在读,C语言初学者,寒假无事,便计划写几个由C语言实现的小游戏以提升编程能力。在这篇博客里分享的是可人机对战的五子棋游戏。
         棋类游戏要实现初级的机器智能,其核心思想便是:
感知(SENSE)->
思考(THINK)->
行动(ACT)。所以,本文将尽量以这个顺序介绍实现过程。
(1) 前期准备:
        此程序中,机器方将以“当前最佳下法”来主导行动,即设计一套评价体系,针对每一个可落子处,综合评分,最终选最高分处落子。所以,我定义了“位置”的结构,包含横纵坐标和得分数三个参数。进而,定义一个位置结构体数组,用于记录每次评分时的可选位置,以及记录该数组最后一个有效元素下标的变量,用于更方便存入取出。另外,诸如初始化棋盘,打印棋盘,初始化位置结构体数组等自定义函数,也一并贴在此处。

#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("%2d", (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');
	}
}
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, '*');
}

        贴一张实际效果图:
C语言——五子棋人机对战
(2)感知:
        这一步中,将遍历棋盘中所有可落子位置,从横,纵,右斜和左斜四个方向,以棋子状态进行评分,总后将有效位置的总分记录在数组中。
         下面先贴出的是评分函数,其接受一个大小为5的字符数组(记录在选定位置周围截取的包含该位置的”一行“棋子状态),一个描述选定位置在该行中的位置的参数x。内部由若干分支构成,对应每种棋子状态,返回对该状态的评分。

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

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

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)思考与行动:
        思考便是在感知并评分后记录下来的位置数组中,寻找最高分位置。行动紧跟其后,在最高分位置落子。博主在实现过程中,遇到有几个相同最高分时,只取第一个最高分,读者可添加随机功能,任选其一落子。代码如下。
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)主函数:
        
void main(void){	initialize_board();	print_board();	while (!is_full()) {		scan();		sense();		think_act();		system("CLS");		print_board();	}	system("pause");}


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


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号