数组的应用——三子棋、五子棋

来源:互联网 发布:excel 一列数据递增 编辑:程序博客网 时间:2024/06/05 20:44

  • 三子棋
    • gameh
    • mainc
    • gamec
  • 五子棋
    • gameh
    • mainc
    • gamec

三子棋

1.在game.h中进行宏定义,头文件引入以及函数声明
2.在main.c中实现主函数
3.在game.c中实现功能函数

game.h

#ifndef __GAME_H_#define __GAME_H_#define _CRT_SECURE_NO_WARNINGS 1#define ROW 3#define COL 3#define CHESS 3 //三子棋#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>typedef struct Location{    int x;    int y;}Location;void Menu();void Play();void InitBoard(char board[ROW][COL], int row,int col);void PrintBoard(char board[ROW][COL], int row, int col);Location PlayerMove(char board[ROW][COL], int row, int col);Location ComputerMove(char board[ROW][COL], int row, int col);char CheckWin(char board[ROW][COL], int row, int col);#endif // !__GAME_H_

main.c

#include"game.h"int main(){    Menu();    return 0;}

接下来是最关键的部分

game.c

#include"game.h"void PrintComputer();void PrintPeople();void Welcome();void Menu(){    int n = 0;    srand((unsigned int)time(NULL));    do {        Welcome();        printf("请选择:>");        scanf("%d", &n);        switch (n)        {        case 1:Play(); break;        case 0:printf("\n退出游戏\n\n"); break;        default:            printf("\n选项不存在,请重新选择:>");            break;        }    } while (n);}//欢迎界面void Welcome(){    printf("\n                        三  子  棋  游  戏 \n");    printf("             /———————————————————\\\n");    printf("            |         1.play          0.exit         |\n");    printf("             \\______________________________________/\n");}void Play(){    char Board[ROW][COL] = { 0 };    char ret = 0;    Location loc = { -1,-1 };    InitBoard(Board, ROW, COL);    while (1)    {        loc = PlayerMove(Board, ROW, COL);        PrintBoard(Board, ROW, COL);        ret = CheckWin(Board, ROW, COL);        if (ret != ' ')            break;        loc = ComputerMove(Board, ROW, COL);        PrintBoard(Board, ROW, COL);        ret = CheckWin(Board, ROW, COL);        if (ret != ' ')            break;    }    if (ret == 'X')        printf("\n       恭喜你,你赢啦!\n");    else if(ret == '0') printf("\n       很遗憾,你输了!\n");    else printf("\n       平局啦!\n");}//可优化char CheckWin(char board[ROW][COL], int row, int col){    int i = 0, j = 0;    for (i = 0; i < row; i++) {        if (board[i][1] != ' '&&board[i][0] == board[i][1] && board[i][1] == board[i][2])            return board[i][1];    }    for (i = 0; i < col; i++) {        if (board[1][i] != ' '&&board[1][i] == board[0][i] && board[1][i] == board[2][i])            return board[1][i];    }    if (board[1][1] != ' ' &&board[0][0] == board[1][1] && board[1][1] == board[2][2])        return board[1][1];    if (board[1][1] != ' '&&board[1][1] == board[0][2] && board[1][1] == board[2][0])        return board[1][1];    //判断是否平局    for (i = 0; i < row; i++) {        for (int j = 0; j < col; j++) {            if(board[i][j] == ' ')                return ' ';        }    }    return 'E';}void InitBoard(char board[ROW][COL], int row, int col) {    memset(&board[0][0], ' ', row*col);}void PrintBoard(char board[ROW][COL], int row, int col){    int i, j;    printf("                            ___ ___ ___\n");    for (i = 0; i < row; i++) {        printf("                           |");        for (j = 0; j < col; j++) {            printf(" %c |", board[i][j]);        }        printf("\n");        printf("                           |___|___|___|\n");    }}void PrintPeople(){    printf("\n   ○\n");    printf("  /|\\   Your Round\n");    printf("  / \\\n");}void PrintComputer(){    printf("                                                     ┌ —┐\n");    printf("                                                     └— ┘\n");    printf("                                          Computer Round  __|__ \n");}Location PlayerMove(char board[ROW][COL], int row, int col){    int x = 0;    int y = 0;    Location loc = { 0,0 };    PrintPeople();    while (1) {        printf("\n       请输入坐标:>");        scanf("%d%d", &x, &y);        if (x > 0 && x <= row&&y > 0 && y <= col) {            if (board[x - 1][y - 1] == ' ')            {                board[x - 1][y - 1] = 'X';                break;            }        }        printf("\n       坐标不合法!\n");    }    loc.x = x - 1;    loc.y = y - 1;    return loc;}//可优化Location ComputerMove(char board[ROW][COL], int row, int col){    int i = 0, j = 0;    Location loc = { 0,0 };    PrintComputer();    while (1)    {        loc.x = rand() % row;        loc.y = rand() % col;        if (board[loc.x][loc.y] == ' ') {            board[loc.x][loc.y] = '0';            break;        }    }    return loc;}

功能函数的实现如上。

运行效果:

哈哈 我超喜欢这个小人和电脑,可爱可爱,非常可爱,感谢输入法

虽然这个电脑落子的算法过于简单,对弈起来有点弱智,但它还是能赢的,不信你瞧
我输了。。

有两个点需要重点优化:
1.判断是否有人胜出的函数CheckWin需要优化,这次实现是为三子棋量身打造的,单纯的判断是否有三个棋在一条线,并不能做到通用。
2.电脑落子函数ComputerMove需要优化,在这个项目中是随机落子,这让游戏丧失了趣味性,谁会愿意和一个不思考的对手下棋。

考虑到以上两点,我没有在三子棋上继续优化,因为三子棋棋子过少的原因,许多投机取巧的实现方式会比通用的方式更高效,因此我在这基础上扩展到了五子棋,两者本质上是没什么区别的。

由于对三子棋的函数介绍过于简单,我会在五子棋上分析的细致些,不要嫌我啰嗦哦~

五子棋

1.在game.h中进行宏定义,头文件引入以及函数声明
2.在main.c中实现主函数
3.在game.c中实现功能函数

game.h

#ifndef __GAME_H_#define __GAME_H_#define _CRT_SECURE_NO_WARNINGS 1//棋盘大小#define ROW 10#define COL 10#define CHESS 5 //五子棋#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>#include<windows.h>//坐标typedef struct Location{    int x;    int y;}Location;void Menu();void Play();void InitBoard(char board[ROW][COL], int row, int col);//五子棋棋盘打印void PrintBigBoard(char board[ROW][COL], int row, int col);Location PlayerMove(char board[ROW][COL], int row, int col);Location ComputerMove(char board[ROW][COL], int row, int col);char CheckWin(char board[ROW][COL], int row, int col, Location loc);//Check1是电脑下棋的算法1,叫Check是因为我觉得功能是查找落子的位置Location Check1(char board[ROW][COL], int row, int col);//Check2是电脑下棋的算法2Location Check2(char board[ROW][COL], int row, int col);#endif // !__GAME_H_

是不是发现头文件和三子棋的惊奇的相似,其实整个文件都是从三子棋复制过来的啦,只做了一些微小的改动

main.c

#include"game.h"int main(){    Menu();    return 0;}

哈哈 这次和三子棋一模一样,没错我就是代码的搬运工,其实这些都不重要啦,关键点在下边 ↓

game.c

和三子棋相同的代码我就不搬运了,我分析几个重要的功能函数

/**   函数名  :CheckWin*   函数介绍:判断棋盘当前状态,是否有人获胜*   输入参数:  board 棋盘数组,记录棋子              row   棋盘行数              col   棋盘列数              loc   当前落子坐标*   返回值  :若玩家获胜返回'X',电脑获胜返回'0',棋盘没有空位则 为平局,返回'E',否则返回‘ ’表示游戏继续*/char CheckWin(char board[ROW][COL], int row, int col, Location loc){    int i = 0, j = 0;    int x = 0, y = 0;//移动坐标     int flag = 0;    //共有4种情况 水平,垂直,斜杠,反斜杠,每种情况分两个部分判断    for (i = 1; i <= 8; i++) {        if (i % 2 == 1)            flag = 0;//两次循环为一种情况,在该情况最开始标志位清零        switch (i) {            //水平向右查看        case 1:            x = loc.x;            y = loc.y + 1;            break;            //水平向左        case 2:            x = loc.x;            y = loc.y - 1;            break;            //垂直向上        case 3:            x = loc.x - 1;            y = loc.y;            break;            //垂直向下        case 4:            x = loc.x + 1;            y = loc.y;            break;            //向右下        case 5:            x = loc.x + 1;            y = loc.y + 1;            break;            //向左上        case 6:            x = loc.x - 1;            y = loc.y - 1;            break;            //向右上        case 7:            x = loc.x - 1;            y = loc.y + 1;            break;            //向左下        case 8:            x = loc.x + 1;            y = loc.y - 1;            break;        default:            break;        }        while(x >= 0 && x < row&&y >= 0 && y < row) {            if (board[x][y] == board[loc.x][loc.y])                flag++;            else break;//如果不符合,说明至少已经不连贯了            switch (i) {                //8种具体情况的循环变量控制            case 1:y++; break;            case 2:y--; break;            case 3:x--; break;            case 4:x++; break;            case 5:x++; y++; break;            case 6:x--; y--; break;            case 7:x--; y++; break;            case 8:x++; y--; break;            }        }        if (i % 2 == 0) {//2种情况在一条线上,每条线进行判断            if (flag >= CHESS - 1)                return board[loc.x][loc.y];        }    }    //判断是否平局    for (i = 0; i < row; i++) {        for (int j = 0; j < col; j++) {            if (board[i][j] == ' ')                return ' ';        }    }    return 'E';}

这里的判断输赢函数比三子棋多了一个参数loc,它表示最后一个人落子的坐标,判断输赢刚开始我准备用比较暴力的方法,从第一个位置开始判断下,右,右下,左下四个方向有没有符合条件的子,直到最后一个有效的位置,但这样未免太耽误时间了。

因为每次落子都会判断一次输赢,所以我想能改变输赢状态的是最后一个落子的位置,所以对最后一个落子的位置进行8个方向判断(其实是4条直线),满足连续有5个子就胜出。

假设棋盘是这个样子的,大红色是己方最后一次落子的位置,粉色是之前己方已经下过的位置
这里写图片描述
红色棋子的坐标是(5,5),这里就不考虑是从0开始还是从1开始啦,我只是想讲清楚这个函数的实现,就假设进入CheckWin函数的坐标是(5,5),flag是记录有多少个棋子和最后一个棋子是连续且在一条直线上的。
首先进入第一层循环,由循环变量i控制,第一层共循环8次,分别为8种情况——水平向右,水平向左,竖直向下,竖直向上,右上,左下,左上、右下,其中每2种情况在一条直线上,作为一组,所以flag在每种情况的开始要清零。
因为每种情况判断的第一个棋子都是不一样的,所以用switch case分支来实现,刚进来i=1,所以进入第一种情况,查看水平向右方向的棋子,坐标(5,6),然后进入第二层循环,和红色棋子位置的符号进行比较,(5,6)没有己方棋子,所以不会相等,flag不改变,并且跳出循环,(为啥要跳出循环,因为即便再下一个棋子是己方棋子,可已经不连续了,不满足胜出条件了),然后i++,判断水平向左,没有己方棋子,flag仍然不改变,还是0,第一条直线判断结束,然后判断flag的值,是否大于等于CHESS-1,CHESS是一个宏定义,五子棋CHESS定义为5,三子棋就定义为3,为啥是CHESS-1呢,因为最后一次下的棋子没有加在flag里呀。
然后i++,判断第二条直线,肯定不满足,跳过,不看了,直接到i=7,flag=0,x=4,y=6,再次进入内部循环,判断发现board[x][y]==board[loc.x][loc.y],flag++,由于每种情况循环变量的控制也不相同,所以再次使用switch case语句,x- -,y++,现在查看(3,7),然后发现board[3][7]==board[loc.x][loc.y],flag++,现在flag=2了,然后重复刚才的步骤,但这一次board[2][8]不等于board[loc.x][loc.y],所以跳出内部循环,i++,此时i=8,x=5,y=4,和刚才一样,flag又加了两次,最后flag=4,满足flag>=CHESS-1,有人胜出了,把胜出棋子的符号返回。

突然觉得自己变成了一个絮叨的老太婆~

如果路过的大牛有更好的方法或者指导意见,可以留言哦,小生不怕别人指出错误~

Location ComputerMove(char board[ROW][COL], int row, int col){    Location loc = { -1,-1 };    PrintComputer();    loc = Check1(board, row, col);    if (loc.x != -1 && loc.y != -1)        return loc;    loc = Check2(board, row, col);    return loc;}

电脑下棋,先使用第一种方法,因为第一种方法不是所有情况都会给出最好的位置,所以再使用第二种下棋方法,这样所有情况电脑都会正确落子。

/** 函数名  :Check1* 函数介绍: 电脑落子算法1,己方有三个一排时会续,对方有三个一排时会堵* 返回值  :落子坐标*/Location Check1(char board[ROW][COL], int row, int col){    int x = 0, y = 0;    int i = 0, j = 0, t = 0;    Location loc = { -1,-1 }, loc2 = { -1,-1 };    int flag = 0;    for (i = 0; i < ROW; i++) {//四种直线情况,右,右下,下,左下        for (j = 0; j < COL; j++) {            if (board[i][j] != ' ') {                for (t = 1; t <= 4; t++) {                    flag = 0;                    switch (t)                    {                    case 1://右                        x = i;                        y = j + 1;                        break;                    case 2://下                        x = i + 1;                        y = j;                        break;                    case 3://右下                        x = i + 1;                        y = j + 1;                        break;                    case 4://左下                        x = i + 1;                        y = j - 1;                        break;                    default:                        break;                    }                    while (x >= 0 && x < row&&y >= 0 && y < row) {                        if (board[x][y] == board[i][j])                            flag++;                        else break;                        if (flag >= CHESS - 3)                        {                            switch (t) {                            case 1:                                if (board[x][y + 1] == ' ') {                                    if (y + 1 < row) {                                        loc.x = x;                                        loc.y = y + 1;                                    }                                }                                else if (board[x][y - flag - 1] == ' ')                                {                                    if (y - flag - 1 >= 0) {                                        loc.x = x;                                        loc.y = y - flag - 1;                                    }                                }                                break;                            case 2:                                if (board[x + 1][y] == ' ') {                                    if (x + 1 < row) {                                        loc.x = x + 1;                                        loc.y = y;                                    }                                }                                else if (board[x - flag - 1][y] == ' ')                                {                                    if (y - flag - 1 >= 0) {                                        loc.x = x - flag - 1;                                        loc.y = y;                                    }                                }                                break;                            case 3:                                if (board[x + 1][y + 1] == ' ') {                                    if ((y + 1 < row) && (x + 1 < col)) {                                        loc.x = x + 1;                                        loc.y = y + 1;                                    }                                }                                else if (board[x - flag - 1][y - flag - 1] == ' ')                                {                                    if ((y - flag - 1 >= 0) && (x - flag - 1 >= 0)) {                                        loc.x = x - flag - 1;                                        loc.y = y - flag - 1;                                    }                                }                                break;                            case 4:                                if (board[x + 1][y - 1] == ' ') {                                    if ((x + 1 < row) && (y - 1 >= 0)) {                                        loc.x = x + 1;                                        loc.y = y - 1;                                    }                                }                                else if (board[x - flag - 1][y + flag + 1] == ' ')                                {                                    if ((y + flag + 1 < col) && (x - flag - 1 >= 0)) {                                        loc.x = x - flag - 1;                                        loc.y = y + flag + 1;                                    }                                }                                break;                            }                            break;                        }                        switch (t) {                        case 1:y++; break;                        case 2:x++; break;                        case 3:x++; y++; break;                        case 4:x++; y--; break;                        }                    }                }            }        }    }    if ((loc.x != -1 && loc.y != -1) && board[loc.x][loc.y] == ' ')    {        board[loc.x][loc.y] = '0';        return loc;    }    return loc2;}

这里用到的就是刚刚我提到的比较暴力的方法,从头扫到尾,查看对方或者己方有没有3个或者3个以上的棋子在同一条直线上的,如果对方有的话,就堵上,己方有的话,就续接上

我这个方法做的不是很好,有个缺点:
电脑发现有三个棋连在一起,它会先堵一头,下一回合会堵另外一头。其实开始它是只能堵一头,然后我做了修改,我的本意是发现4个棋连在一起再堵另外一头,这个有待优化。

运行效果:
这里写图片描述

/** 函数名  :Check2* 函数介绍: 电脑落子算法2,找出周围棋子最多的能落子的坐标* 返回值  :落子坐标*/Location Check2(char board[ROW][COL], int row, int col){    int i = 0, j = 0;    Location loc = { -1,-1 };    int max = -1;    int weight[ROW][COL] = { 0 };//权重,每个棋子附近的棋子数    int flag = 0;    //随机放在棋子最多的位置    //不能放的位置 权值为-1,边界太麻烦,不讨论,直接用初始化的值0,与不能下的位置做区别    for (i = 0; i < row; i++) {        for (j = 0; j < col; j++) {            if (board[i][j] != ' ')                weight[i][j] = -1;        }    }    for (i = 1; i < row - 1; i++) {//计算每个子的权重,己方和对手方的棋子都算        for (j = 1; j < col - 1; j++) {            if (weight[i][j]>-1)            {                //这里可以用两层循环,但是我不想让循环套太多层,所以用if语句                if (board[i - 1][j - 1] != ' ')                    weight[i][j]++;                if (board[i - 1][j] != ' ')                    weight[i][j]++;                if (board[i + 1][j + 1] != ' ')                    weight[i][j]++;                if (board[i][j + 1] != ' ')                    weight[i][j]++;                if (board[i - 1][j + 1] != ' ')                    weight[i][j]++;                if (board[i + 1][j] != ' ')                    weight[i][j]++;                if (board[i + 1][j - 1] != ' ')                    weight[i][j]++;                if (board[i][j - 1] != ' ')                    weight[i][j]++;            }        }    }    for (i = 0; i < row; i++) {//找出权重最大的位置        for (j = 0; j < col; j++) {            if (weight[i][j]>max) {                max = weight[i][j];                loc.x = i;                loc.y = j;            }        }    }    board[loc.x][loc.y] = '0';    return loc;}

这个方法是给每个位置都赋一个权重,权重的值表示周围8个位置已经下过棋子的个数,因为边边还需要特殊考虑,所以我直接把权重设成0了,下过棋子的位置权重是-1,两者作区别,剩下的位置就是用很笨的循环动态的判断,当所有位置的权重都确定了之后,找出权重最大的那个返回。

这样下只是为了让电脑不胡乱放棋子,起码看起来不那么蠢

运行一下,电脑一下子聪明多了
这里写图片描述

原创粉丝点击