大家好,又见面了,我是你们的朋友全栈君。
先说下背景吧,写下这篇博客时,博主大一在读,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。内部由若干分支构成,对应每种棋子状态,返回对该状态的评分。
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复制
接下来呢,就是感知中的另一个主要部分,其功能是对某一位置,从四个方向,截取评分函数所需的一行五位置数组。
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