当前位置:   article > 正文

人机对战-黑白棋_黑白棋人机

黑白棋人机

先大致了解一下黑白棋:

规则
如果玩家在棋盘上没有地方可以下子,则该玩家对手可以连下。双方都没有棋子可以下时棋局结束,以棋子数目来计算胜负,棋子多的一方获胜。
在棋盘还没有下满时,如果一方的棋子已经被对方吃光,则棋局也结束。将对手棋子吃光的一方获胜。
翻转棋类似于棋盘游戏“奥赛罗 (Othello)”,是一种得分会戏剧性变化并且需要长时间思考的策略性游戏。
翻转棋的棋盘上有 64 个可以放置黑白棋子的方格(类似于国际象棋和跳棋)。游戏的目标是使棋盘上自己颜色的棋子数超过对手的棋子数。
该游戏非常复杂,其名称就暗示着结果的好坏可能会迅速变化。
当游戏双方都不能再按规则落子时,游戏就结束了。通常,游戏结束时棋盘上会摆满了棋子。结束时谁的棋子最多谁就是赢家。
玩法
每个“翻转棋”游戏开始时,棋盘上已经交叉放好了四颗棋子。其中两颗是黑棋,另两颗是白棋。黑棋总是先走。
当您的棋子在某一直线方向包围了对手的棋子时,就可以翻转这些棋子的颜色,使它们成为您方的颜色。例如,如果您执黑棋,并且看到在一排白棋的某一端是一颗黑棋,那么当您将一颗黑棋放在这一排的另一端时,所有的白棋都将翻转并变为黑棋!
所有的直线方向均有效:水平、垂直和斜线方向。
走棋的唯一规则是只能走包围并翻转对手的棋子。每一回合都必须至少翻转一颗对手的棋子。
按规则不能再走棋时,这一回合弃权。这一步的行棋权将被交给对方。

由以上可知,在做黑白棋人机对战时,AI要遵守游戏规则。根据

感知(Sense)→思考(Think)→行动(Act)

这个基本架构去设计AI
感知玩家下的棋子位置;
思考我方下子后,增加多少分,玩家接下来走的位置,会减少我方多少分,以相差最高为标准,确定下棋位置;
行动落子;

根据以上分析,编写程序,代码如下:

#include <stdio.h>
//显示棋盘上棋子的状态
void Output(char chessboard[][8]) 
{
    int row, col;
    printf("\n   ");
    //输出列标号 
    for (col = 0; col < 8; col++)   
    {
        printf("  %c ", 'A' + col);
    }
    printf("\n");
    //输出项部横线 
    printf("  ┌");  
    //输出一行 
    for (col = 0; col < 7; col++)   
    {
        printf("─┬");
    }
    printf("─┐\n");
    for (row = 0; row < 8; row++)
    {
        //输出行号
        printf("%2d│", row + 1);    
        //输出棋盘各单元格中棋子的状态 
        for (col = 0; col < 8; col++)
        {
            if (chessboard[row][col] == 1)//白棋 
            {
                printf("○│");
            }
            else if (chessboard[row][col] == -1)//黑棋 
            {
                printf("●│");
            }
            else//未下子处 
            {
                printf("  │");
            }
        }
        printf("\n");
        if (row < 8 - 1)
        {
            printf("  ├");  //输出交叉线 
            //输出一行 
            for (col = 0; col < 8 - 1; col++)
            {
                printf("─┼");
            }
            printf("─┤\n");
        }
    }
    printf("  └");
    //最后一行的横线
    for (col = 0; col < 8 - 1; col++)    
    {
        printf("─┴");
    }
    printf("─┘\n");
}
//检查某一方是否还有下子的地方
int Check(char chessboard[][8], int isDown[][8], char player)   
{
    int rowdelta, coldelta, row, col, x, y = 0;
    int iStep = 0;
    char opponent = (player == 1) ? -1 : 1; //对方棋子 
    char myplayer = -1 * opponent;  //我方棋子 
    //将isDown数组全部清0 
    for (row = 0; row < 8; row++)   
    {
        for (col = 0; col < 8; col++)
        {
            isDown[row][col] = 0;
        }
    }
    //循环判断棋盘中哪些单元格可以下子 
    for (row = 0; row < 8; row++)   
    {
        for (col = 0; col < 8; col++)
        {
            //若棋盘上对应位置不为空(表示已经有子)
            if (chessboard[row][col] != 0)   
            {
                continue;//继续处理下一个单元格 
            }
            //循环检查上下行
            for (rowdelta = -1; rowdelta <= 1; rowdelta++)   
            {
                //循环检查左右列
                for (coldelta = -1; coldelta <= 1; coldelta++)   
                {
                    //检查若坐标超过棋盘 或为当前单元格
                    if (row + rowdelta < 0 || row + rowdelta >= 8
                        || col + coldelta < 0 || col + coldelta >= 8
                        || (rowdelta == 0 && coldelta == 0))     
                    {
                        continue;   //继续循环 
                    }
                    //若(row,col)四周有对手下的子 
                    if (chessboard[row + rowdelta][col + coldelta] == opponent) 
                    {
                        //以对手下子位置为坐标
                        x = row + rowdelta;  
                        y = col + coldelta;
                        //对对手下子为起始点,向四周查找自己方的棋子,以攻击对方棋子 
                        while(1)    
                        {
                            //对手下子的四周坐标
                            x += rowdelta;   
                            y += coldelta;
                            //超过棋盘
                            if (x < 0 || x >= 8 || y < 0 || y >= 8)  
                            {
                                break;  //退出循环 
                            }
                            //若对应位置为空
                            if (chessboard[x][y] == 0)   
                            {
                                break;
                            }
                            //若对应位置下的子是当前棋手的
                            if (chessboard[x][y] == myplayer)    
                            {
                                //设置移动数组中对应位置为1 (该位置可下子,形成向对手进攻的棋形)
                                isDown[row][col] = 1;   
                                iStep++;    //累加可下子的位置数量 
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
    //返回可下的位置数量(若返回值为0,表示没地方可下)
    return iStep; 
}
//在指定位置下子 
void PlayStep(char chessboard[][8], int row, int col, char player)  
{
    int rowdelta = 0;
    int coldelta = 0;
    int x = 0;
    int y = 0;
    char opponent = (player == 1) ? -1 : 1; //对方棋子
    char myplayer = -1 * opponent;  //我方棋子 
    chessboard[row][col] = myplayer;    //保存所下的棋子
    //检查所下子四周的棋子
    for (rowdelta = -1; rowdelta <= 1; rowdelta++)  
    {
        for (coldelta = -1; coldelta <= 1; coldelta++)
        {
            //若坐标超过棋盘界限
            if (row + rowdelta < 0 || row + rowdelta >= 8 || col + coldelta < 0
                || col + coldelta >= 8 || (rowdelta == 0 && coldelta == 0)) 
            {
                continue;   //继续下一位置 
            }
            //若该位置是对手的棋子
            if (chessboard[row + rowdelta][col + coldelta] == opponent)  
            {
                //以对手棋为坐标
                x = row + rowdelta;  
                y = col + coldelta;
                //在对手棋子四周寻找我方棋子 
                while(1)    
                {
                    x += rowdelta;
                    y += coldelta;
                    //若坐标超过棋盘
                    if (x < 0 || x >= 8 || y < 0 || y >= 8)  
                    {
                        break;  //退出循环
                    }
                    //若对应位置为空 
                    if (chessboard[x][y] == 0)  
                    {
                        break;  //退出循环 
                    }
                    //若对应位置是我方模子
                    if (chessboard[x][y] == myplayer)    
                    {
                        //循环处理 
                        while (chessboard[x -= rowdelta][y -= coldelta] == opponent)    
                        {
                            //将中间的棋子都变成我方棋子
                            chessboard[x][y] = myplayer;     
                        }
                        break;  //退出循环 
                    }
                }
            }
        }
    }
}
//获取分数
int GetMaxScore(char chessboard[][8], char player)   
{
    int Score, row, col;
    char opponent = (player == 1) ? -1 : 1; //对方棋子 
    char myplayer=-1*opponent;
    for (row = 0; row < 8; row++)   //循环 
    {
        for (col = 0; col < 8; col++)
        {
            //若棋盘对应位置是对手下的棋子,从总分中减1
            Score -= chessboard[row][col] == opponent;  
            //若棋盘对应位置是我方的棋子,总分中加1分
            Score += chessboard[row][col] == myplayer;   
        }
    }
    return Score;//返回分数 
}
//获取最佳下子位置
int BestPlay(char chessboard[][8], int isDown[][8], char player)     
{
    int row, col, i, j;
    //定义一个临时数组
    char chessboard1[8][8] = { 0 };  
    int MaxScore = 0;   //保存最高分 
    int Score = 0;
    char opponent = (player == 1) ? -1 : 1; //对手下的棋子 
    //循环检查每个单元格
    for (row = 0; row < 8; row++)    
    {
        for (col = 0; col < 8; col++)
        {
            //若该位置不可下子
            if (!isDown[row][col])  
            {
                continue;   //继续 
            }
            //复制棋盘各单元格下子的状态到临时数组
            for (i = 0; i < 8; i++)  
            {
                for (j = 0; j < 8; j++)
                {
                    chessboard1[i][j] = chessboard[i][j];
                }
            }
            //在临时数组中的指定行列下子
            PlayStep(chessboard1, row, col, player);
            //获取下子后可得到的分数
            Score = GetMaxScore(chessboard1, player);
            //若原方案得到的分数小于本次下子的分数 
            if (MaxScore < Score)   
            {
                MaxScore = Score;   //保存最高分 
            }
        }
    }
    return MaxScore;//返回得到的最高分 
}
//AI自动下子
void AutoPlayStep(char chessboard[][8], int isDown[][8], char player)    
{
    int row, col, row1, col1, i, j;
    //对方可下子提到的分数和最小分数
    int Score = 0, MinScore = 100;   
    //临时数组,保存棋盘下子位置 
    char chessboard1[8][8]; 
    //临时数组,保存可下子位置 
    int isDown1[8][8];      
    char opponent = (player == 1) ? -1 : 1; //对手下的棋子    
    for (row = 0; row < 8; row++)   //循环检查棋盘每个单元格 
    {
        for (col = 0; col < 8; col++)
        {   
            //若不可下子
            if (isDown[row][col] == 0)   
            {
                continue;//继续下一个位置 
            }
            //将棋盘原来的棋子复制到临时数组中
            for (i = 0; i < 8; i++)  
            {
                for (j = 0; j < 8; j++)
                {
                    chessboard1[i][j] = chessboard[i][j];
                }
            }
            //试着在临时棋盘中的一个位子下子
            PlayStep(chessboard1, row, col, player);     
            //检查对手是否有地方可下子
            Check(chessboard1, isDown1, opponent);   
            //获得临时棋盘中对方下子的得分情况
            Score = BestPlay(chessboard1, isDown1, opponent);   
            //保存对方得分最低的下法 
            if (Score < MinScore)   
            {
                MinScore = Score;
                row1 = row;
                col1 = col;
            }
        }
    }
    //AI按最优下法下子 
    PlayStep(chessboard, row1, col1, player);   
} 
int main()
{
    //保存棋盘中各单元格下子的状态
    char chessboard[8][8];  
    //保存棋盘中各位置是否可以下子,可下子的位置为1,其余位置为0 
    int isDown[8][8] = { 0 };       
    int row, col, x, y;
    //已下棋子数量 
    int iCount = 0; 
    int player = 0; //下棋方
    //跳过下子的次数,若为2,表示双方都不能下子
    int SkipPlay = 0;    
    //保存AI和游戏者的得分
    int Score[2];    
    char select;
    printf("黑白棋\n\n");
    printf("游戏者执黑先下,AI执白,按回车键开始:\n");
    scanf("%c", &select);
    do
    {
        //计算下棋方(0表示游戏者,1表示AI)
        if (player == 0) 
        {
            player = 1;
        }
        else
        {
            player = 0;
        }
        iCount = 4; //累计下子数 
        //棋盘各位置清空 
        for (row = 0; row < 8; row++)   
        {
            for (col = 0; col < 8; col++)
            {
                chessboard[row][col] = 0;
            }
        }
        //在棋盘中间位置放置白棋 
        chessboard[3][3] = chessboard[4][4] = 1;    
        //在棋盘中间位置放置黑棋 
        chessboard[3][4] = chessboard[4][3] = -1;   
        printf("\n棋盘初始状态:\n");
        //显示初始棋盘下子的状况 
        Output(chessboard);
        do
        {
            //若是游戏者下棋(下白子) 
            if (player == 1)    
            {
                player = 0;
                //判断是否可下黑子 
                if (Check(chessboard, isDown, 2))   
                {
                    //死循环,直到用户输入正确的坐标为止
                    while(1) 
                    {
                        fflush(stdin);
                        printf("输入下子的位置(行 列):");
                        scanf("%d%c", &x, &y);
                        x--;    //计算行坐标位置 
                        if(y >= 'a')
                        {
                            y = y - 'a' + 1;
                        }
                        else
                        {
                            y = y - 'A' + 1;
                        }
                        y--;    //计算列位置 
                        //若行列坐标输入有效
                        if (x >= 0 && y >= 0 && x < 8 && y < 8 && isDown[x][y])  
                        {
                            //在指定坐标位置下黑子
                            PlayStep(chessboard, x, y, 2);  
                            iCount++;   //累加下子数 
                            break;
                        }
                        else
                        {
                            printf("坐标输入错误,请重新输入。\n");
                        }
                    }
                    printf("\n你下子后的状态:\n");
                    Output(chessboard); //显示棋子状态
                    printf("按任意键AI下子。\n");
                    getch();
                }
                //若无效下子的次数小于2
                else if (++SkipPlay < 2)     
                {
                    fflush(stdin);  //清除输入缓冲区 
                    printf("你没位置可下,按回车键让对方下子。");
                    scanf("%c", &select);
                } else
                {
                    printf("双方都没地方下子,游戏结束!\n");
                }
            }
            //若是AI下棋(下黑子) 
            else    
            {
                player = 1;
                //检查是否可下白子
                if (Check(chessboard, isDown, 1))    
                {
                    SkipPlay = 0;   //清除无效下子次数 
                    //AI下一个白子 
                    AutoPlayStep(chessboard, isDown, 1);    
                    iCount++;   //累加下子数
                    printf("\nAI下子后的状态:\n");
                    Output(chessboard); //显示棋子状态
                }
                else
                {
                    //若无效下子次数小于2
                    if (++SkipPlay < 2)  
                    {
                        printf("我没位置可走,请你走。\n");
                    }
                    else
                    {
                        printf("双方都没地方下子,游戏结束!");
                    }
                }
            }
        }
        //下子数量小于64 且无效下子的次数小于2
        while (iCount < 64 && SkipPlay < 2);
        //显示各双方棋子的状况
        Output(chessboard);  
        Score[0] = Score[1] = 0;//清空计分变量 
        //循环统计各单元格黑白棋子的数量
        for (row = 0; row < 8; row++)
        {
            for (col = 0; col < 8; col++)
            {
                //统计黑子数 
                Score[0] += chessboard[row][col] == -1; 
                //统计白子数
                Score[1] += chessboard[row][col] == 1;               
            }
        }
        printf("最终成绩:\n");
        printf("AI:%d\n游戏者:%d\n", Score[0], Score[1]);
        fflush(stdin);  //清空输入缓冲区 
        printf("继续下一局(y/n)?:");
        scanf("%c", &select);
    }while (select == 'y' || select == 'Y');
    printf("Game Over!\n");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451

运行演示:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

参考资源:

《零基础学算法》 第三版 戴艳等编 机械工业出版社


代码下载地址

http://download.csdn.net/download/u013553804/9500500
http://pan.baidu.com/s/1o8nbtFG


欢迎关注我的微信个人订阅号
这里写图片描述
每天多学一点0.0

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/135257?site
推荐阅读
相关标签
  

闽ICP备14008679号