利用遗传算法演化一个棋类游戏的人工智能

人工智能设计是大多数计算机游戏设计的一个重要组成部分,其最为主要的作用是塑造一个虚拟的玩家形象与游戏之中的真实玩家竞技或交流。目前在技术上说,大部分游戏之中的人工智能设计工作可以归结为有限状态机的设计。本文之中提到的这种棋类游戏,其状态机结构清晰简单,固可以方便的构建出仿真环境,接着用遗传算法推演出人工智能就比较简单了。

    其实这个东西目前还没有完成,但是我约摸着其输出的几个样本已经可以作为这个游戏的人工智能使用了,并且根据在演化过程之中得分等级,可以设计出不同难度的人工智能,之前说的那么正经,唉,其实就是发出来装一下逼,而且我现在不想理这烂摊子了。


 
 
//避免C标准库函数在Visual C++之中可忽略的警告
//请尽量使用DEV C++编译 
#define _CRT_SECURE_NO_WARNINGS
 
 
//头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <conio.h>
 
 
//猿人DNA序列,对棋局所有情况的列表
#define APE_DNA_LEN (9 * 8 * 7 * 6 * 5 * 4)
//基因突变宽度
#define APE_DNA_MUTATION_WIDTH 4000
//每一次棋局允许的步数
#define STEP_NUMBER 1000
//人口,或者说猴口
#define POPULATION_NUMBER 200
//下一代遗传样本对数
#define SEED_NUMBER 20
//繁殖能力
#define REPRODUCTION_ABILITY ( POPULATION_NUMBER / SEED_NUMBER )
//分数标志,用于初始化分数表,表示该位置分数没有登记
#define SCORE_FLAG (STEP_NUMBER * -5.0 * 2.0)
//失效分数,对弈之中若对方胜利的话,己方分数则失效
#define SCORE_FAIL (STEP_NUMBER * -5.0 * 3.0)
 
 
//猿猴类型
typedef uint8_t * ape;
 
//种群类型
typedef ape population[POPULATION_NUMBER];
 
//棋盘上的坐标类型
typedef struct coordinate coordinate;
struct coordinate{
 
uint8_t x;
uint8_t y;
};
 
//棋盘信息描述类型,六个坐标分别指明了上下方玩家的棋子坐标
typedef coordinate checkerboard_inf[6];
 
//分数项目类型,包含猿猴ID以及对弈分数
typedef struct score_item score_item;
struct score_item{
 
int id;
double score;
};
 
 
 
 
//根据棋盘信息显示详细的棋盘情况
void display_checkerboard(const checkerboard_inf cinf){
 
 
char checkerboard[3][3][3];
 
int x, y;
 
//棋盘准备
for (y = 0; y < 3; y++){
 
for (x = 0; x < 3; x++){
 
strcpy(checkerboard[y][x], "  ");
}
}
 
//绘制上方玩家棋子
strcpy(checkerboard[cinf[0].y][cinf[0].x], "德");
strcpy(checkerboard[cinf[1].y][cinf[1].x], "意");
strcpy(checkerboard[cinf[2].y][cinf[2].x], "日");
 
//绘制下方玩家棋子
strcpy(checkerboard[cinf[3].y][cinf[3].x], "美");
strcpy(checkerboard[cinf[4].y][cinf[4].x], "苏");
strcpy(checkerboard[cinf[5].y][cinf[5].x], "中");
 
//绘制到屏幕
for (y = 0; y < 3; y++){
 
for (x = 0; x < 3; x++){
 
printf("%s", checkerboard[y][x]);
}
 
printf("\n");
}
 
printf("\n");
}
 
 
//新建一个毫无智商可言的猿猴
ape new_ape(){
 
ape nape = (ape)malloc(APE_DNA_LEN);
 
int i;
 
for (i = 0; i < APE_DNA_LEN; i++){
 
//毫无智商可言的猿猴其行为方式是随机的
nape[i] = rand() % 24;
}
 
return nape;
}
 
 
//杀死一个猿猴
void kill_ape(ape kape){
 
free(kape);
}
 
 
//初始化种群
void population_init(population pltn){
 
int i;
 
for (i = 0; i < POPULATION_NUMBER; i++){
 
pltn[i] = new_ape();
}
}
 
 
//灭绝种群
void population_kill(population pltn){
 
int i;
 
for (i = 0; i < POPULATION_NUMBER; i++){
 
kill_ape(pltn[i]);
}
}
 
 
 
 
 
 
//让猿猴根据当前棋盘情况回答如何移动自己的棋子
uint8_t ape_answer(const ape iape, const checkerboard_inf inf){
 
uint32_t id = 0;
 
id += (inf[0].y * 3 + inf[0].x) * (APE_DNA_LEN / 9);
id += (inf[1].y * 3 + inf[1].x) * (APE_DNA_LEN / 9 / 8);
id += (inf[2].y * 3 + inf[2].x) * (APE_DNA_LEN / 9 / 8 / 7);
id += (inf[3].y * 3 + inf[3].x) * (APE_DNA_LEN / 9 / 8 / 7 / 6);
id += (inf[4].y * 3 + inf[4].x) * (APE_DNA_LEN / 9 / 8 / 7 / 6 / 5);
id += (inf[5].y * 3 + inf[5].x) * (APE_DNA_LEN / 9 / 8 / 7 / 6 / 5 / 4);
 
return iape[id];
}
 
 
//移动棋子
int8_t moving_pawn(checkerboard_inf cinf, uint8_t code){
 
//被选中准备移动的棋子ID
uint8_t pawn_id = code / 8;
//棋盘视角总在下方玩家,固为3 + pawn_id
coordinate crdnt = cinf[3 + pawn_id];
 
//计算出行为值
uint8_t action = code % 8;
 
//区间平移,0~7 转换为 1~8
action++;
 
int8_t xoft, yoft;
 
xoft = action % 3 - 1;
yoft = action / 3 - 1;
 
//置换行为项目,确保禁止弃权移动
if (xoft + yoft == 0){
 
xoft = -1;
yoft = -1;
}
 
 
int8_t x, y;
 
//计算出目标坐标
x = crdnt.x + xoft;
y = crdnt.y + yoft;
 
 
//如果目标坐标出界,扣5分
if ((x < 0 || x > 2) ||
(y < 0 || y > 2)){
 
return -5;
}
 
 
//如果发生了斜向运动
if (xoft != 0 && yoft != 0){
 
//如果斜向运动并没有经过棋盘中心,那么这是不合法的,扣5分
if ( !((x == 1 && y == 1) || (crdnt.x == 1 && crdnt.y ==1)) ){
 
return -5;
}
}
 
 
int i;
 
for (i = 0; i < 6; i++){
 
//如果目标坐标上有棋子,扣5分
if (cinf[i].x == x &&
cinf[i].y == y){
 
return -5;
}
}
 
 
//棋子移动合法,应用目标坐标
cinf[3 + pawn_id].x = x;
cinf[3 + pawn_id].y = y;
 
 
//棋子移动合法,不扣分
return 0;
}
 
 
//反转棋盘视角
void change_view(checkerboard_inf scinf, const checkerboard_inf dcinf){
 
scinf[0] = dcinf[3];
scinf[1] = dcinf[4];
scinf[2] = dcinf[5];
 
scinf[3] = dcinf[0];
scinf[4] = dcinf[1];
scinf[5] = dcinf[2];
 
scinf[0].y = 2 - scinf[0].y;
scinf[1].y = 2 - scinf[1].y;
scinf[2].y = 2 - scinf[2].y;
scinf[3].y = 2 - scinf[3].y;
scinf[4].y = 2 - scinf[4].y;
scinf[5].y = 2 - scinf[5].y;
}
 
 
//根据当前棋盘信息判断是否获胜
int is_win(checkerboard_inf cinf){
 
coordinate crdnt1, crdnt2, crdnt3;
 
crdnt1 = cinf[3];
crdnt2 = cinf[4];
crdnt3 = cinf[5];
 
 
if (crdnt1.x + crdnt2.x + crdnt3.x == 3 &&
crdnt1.y + crdnt2.y + crdnt3.y == 3){
 
return 1;
}
else{
 
return 0;
}
}
 
 
 
//两个猿猴下棋,基本调用
void ape_pk_base(const ape ape1, const ape ape2, double * pscore1, double * pscore2){
 
//两个猿猴的分数
double score1 = 0.0, score2 = 0.0;
 
//开局棋盘状况
checkerboard_inf cinf1 = {
 
{ 0, 0 },
{ 1, 0 },
{ 2, 0 },
{ 0, 2 },
{ 1, 2 },
{ 2, 2 }
};
 
//视角置换棋盘
checkerboard_inf cinf2;
 
 
int i;
 
for (i = 0; i < STEP_NUMBER; i++){
 
uint8_t code;
int8_t num1, num2;
 
//获得先手行为代码
code = ape_answer(ape1, cinf1);
 
num1 = moving_pawn(cinf1, code);
 
score1 += num1;
 
//如果先手获得胜利
if (is_win(cinf1)){
 
score1 += ( STEP_NUMBER * 5.0 * 4.0 ) / (i + 1);
score2 = SCORE_FAIL;
 
break;
}
 
//display_checkerboard(cinf1);
 
 
//改变棋局视角为后手
change_view(cinf2, cinf1);
 
 
//获取后手行为代码
code = ape_answer(ape2, cinf2);
 
num2 = moving_pawn(cinf2, code);
 
score2 += num2;
 
//如果后手获得胜利
if (is_win(cinf2)){
 
score2 += (STEP_NUMBER * 5.0 * 4.0) / (i + 1);
score1 = SCORE_FAIL;
 
break;
}
 
//display_checkerboard(cinf2);
 
 
//如果已经陷入愚蠢的死水境地,那么则没必要继续进行下去
if (num1 == -5 && num2 == -5){
 
//预算分数
score1 += -5.0 * (STEP_NUMBER - i - 1);
score2 += -5.0 * (STEP_NUMBER - i - 1);
 
break;
}
 
 
//改变棋局视角为先手
change_view(cinf1, cinf2);
}
 
 
*pscore1 = score1;
*pscore2 = score2;
 
//printf("%lf %lf\n", *pscore1, *pscore2);
}
 
 
//两个猿猴下棋,相互先手,上层调用
void ape_pk(const ape ape1, const ape ape2, double * pscore1, double * pscore2){
 
double score1, score2, score3, score4;
 
//猿猴1先手
ape_pk_base(ape1, ape2, &score1, &score2);
//猿猴2先手
ape_pk_base(ape2, ape1, &score4, &score3);
 
 
//无论先后手,只要有任意一局分数失效,分数平均数也就失效
if (score1 == SCORE_FAIL || score3 == SCORE_FAIL){
 
*pscore1 = SCORE_FAIL;
}
else{
 
*pscore1 = (score1 + score3) / 2;
}
 
if (score2 == SCORE_FAIL || score4 == SCORE_FAIL){
 
*pscore2 = SCORE_FAIL;
}
else{
 
*pscore2 = (score2 + score4) / 2;
}
 
//printf("%lf %lf\n", *pscore1, *pscore2);
}
 
 
 
//优选随机产生器
int population_rand(){
 
int n = 0;
 
while (1){
 
//概率分母为递增数列
if (rand() % 2 == 0){
 
break;
}
else{
 
n++;
}
}
 
return n;
}
 
 
//32位的随机数产生函数
uint32_t rand_32(){
 
//基因分裂点
uint32_t num;
uint8_t a, b, c, d;
 
//用于构成32位的随机数
a = rand() % 256;
b = rand() % 256;
c = rand() % 256;
d = rand() % 256;
 
num = 0;
num |= a;
num <<= 8;
num |= b;
num <<= 8;
num |= c;
num <<= 8;
num |= d;
 
return num;
}
 
 
//两个猿猴交配,产生下一代
void ape_childbirth(const ape father, const ape mother, ape children){
 
//基因分裂点
uint32_t cutpt;
 
cutpt = rand_32();
 
//切割点偏移范围为 1~APE_DNA_LEN-1
cutpt %= (APE_DNA_LEN - 1);
cutpt++;
 
 
uint32_t i;
 
for (i = 0; i < cutpt; i++){
 
children[i] = father[i];
}
 
for (i = cutpt; i < APE_DNA_LEN; i++){
 
children[i] = mother[i];
}
 
 
//根据基因突变宽度进行基因突变
for (i = 0; i < APE_DNA_MUTATION_WIDTH; i++){
 
children[rand_32() % APE_DNA_LEN] = rand() % 24;
}
}
 
 
//依据当前种群诞生下一代种群
void population_birth(const population pltn, score_item score_list[], population npltn){
 
int i, j, p = 0;
 
for (i = 0; i < SEED_NUMBER; i++){
 
ape father, mother;
 
//优选随机选中父母
father = pltn[score_list[population_rand()].id];
mother = pltn[score_list[population_rand()].id];
 
 
//根据繁殖能力生育
for (j = 0; j < REPRODUCTION_ABILITY; j++){
 
if (j % 2 == 0){
 
ape_childbirth(father, mother, npltn[p++]);
}
else{
 
ape_childbirth(mother, father, npltn[p++]);
}
}
}
}
 
 
double max_score = -10000000.0; 
 
 
//种群竞争
void population_competition(const population pltn, population npltn){
 
//分数表
double * * score_table;
 
//分配一级索引
score_table = (double * *)malloc( sizeof(double *) * POPULATION_NUMBER);
 
int i, j;
 
//分配二级索引
for (i = 0; i < POPULATION_NUMBER; i++){
 
score_table[i] = (double *)malloc(sizeof(double) * POPULATION_NUMBER);
 
//分数表默认全部设置为分数标志,表示分数表目前全部为空
for (j = 0; j < POPULATION_NUMBER; j++){
 
score_table[i][j] = SCORE_FLAG;
}
}
 
 
int x, y;
 
for (y = 0; y < POPULATION_NUMBER; y++){
 
for (x = 0; x < POPULATION_NUMBER; x++){
 
//自己不需要和自己对弈
if (x != y){
 
//如果该项目并未登记
if (score_table[y][x] == SCORE_FLAG){
 
double score1, score2;
 
//让选中的两个猿猴进行对弈,产生分数
ape_pk(pltn[y], pltn[x], &score1, &score2);
 
score_table[y][x] = score1;
score_table[x][y] = score2;
}
}
}
}
 
 
//分数列表
score_item score_list[POPULATION_NUMBER];
 
for (y = 0; y < POPULATION_NUMBER; y++){
 
score_list[y].id = y;
 
//分数和
double score_sum = 0.0;
int count = 0;
 
for (x = 0; x < POPULATION_NUMBER; x++){
 
//如果是有效分数则累加,每一行有一个非有效分数
if (score_table[y][x] != SCORE_FLAG &&
score_table[y][x] != SCORE_FAIL){
 
score_sum += score_table[y][x];
 
//记录累加了多少个得分
count++;
}
}
 
//依次释放二级索引
free(score_table[y]);
 
if (count == 0){
 
count = 1;
}
 
score_list[y].score = score_sum / count;
}
 
//释放一级索引
free(score_table);
 
 
//对分数项目表进行选择排序,分数降序排列
for (y = 0; y < POPULATION_NUMBER - 1; y++){
 
int max = y;
 
for (x = y + 1; x < POPULATION_NUMBER; x++){
 
if (score_list[max].score < score_list[x].score){
 
max = x;
}
}
 
if (max != y){
 
score_item tmp_item;
 
tmp_item = score_list[max];
score_list[max] = score_list[y];
score_list[y] = tmp_item;
}
}
 
 
 
if( score_list[0].score > max_score ){
 
FILE * fp; 
char file_name[100];
unsigned long int n;
 
max_score = score_list[0].score;
 
n = (unsigned long int)max_score;
 
sprintf(file_name,"%ld.dat",n);
 
fp = fopen(file_name, "wb");
 
fwrite(pltn[score_list[0].id], APE_DNA_LEN, 1, fp );
 
fclose(fp);
}
 
printf( "%lf\t%lf\n", max_score, score_list[0].score );
 
 
population_birth(pltn, score_list, npltn);
}
 
 
 
int main(int argc, char * argv[]){
 
//初始化伪随机数种子
srand((unsigned int)time(NULL));
 
population pltn;
population npltn;
 
population_init(pltn);
population_init(npltn);
 
 
while (1){
 
population_competition(pltn, npltn);
 
population_competition(npltn, pltn);
}
 
 
population_kill(npltn);
population_kill(pltn);
 
return 0;
}
posted @ 2016-03-02 13:41  鸡毛巾  阅读(732)  评论(0编辑  收藏  举报