【九转宫格】相关

· · 个人记录

背景

九转宫格是我的同学因为考试复习太无聊发明出来的,一局十分钟左右的,尚未被找到规律的,每一局大同小异手感却大相径庭的自认为很有趣味的小游戏。于是我将其改编为电脑程序,并打算添加bot、online等功能。

概括

本文章包括四部分,分别为资源查找,开发过程,bot算法所提供的接口,背景

对于想要开发游戏的同学,可以积攒一些API的用法,移步开发过程,对于想要写bot的犇犇,可以直接移步bot算法所提供的接口

游戏规则详见游戏!

游戏规则详见游戏!

游戏规则详见游戏!

资源查找

游戏下载https://www.luogu.com.cn/problem/U284285

源码https://www.luogu.com.cn/paste/nx4bol1c

团队https://www.luogu.com.cn/team/56396

开发过程

1.rgb配色

首先,我费尽心思地找到了实现rgb配色的函数,并添加了游戏中使用的颜色

/*=============================RGB调色====================================*/
//https://blog.csdn.net/wangxun20081008/article/details/115046817
//https://www.bilibili.com/read/cv18885696(为主) 
HANDLE hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
COLORREF colors[16]={RGB(233,212,166),RGB(0,0,0),RGB(84,76,65),RGB(150,105,44),RGB(255,0,0),RGB(0,0,255),RGB(158,70,33),RGB(58,100,133),RGB(64,0,64),RGB(255,0,0)};
void RgbInit(){
    CONSOLE_SCREEN_BUFFER_INFOEX csbi = {sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };
    GetConsoleScreenBufferInfoEx(hOutput, &csbi);
    memcpy(csbi.ColorTable, colors, sizeof(colors)); //把颜色配置拷进去
    SetConsoleScreenBufferInfoEx(hOutput, &csbi); //应用设置 
}
void RgbSet(int front,int back=0){//设置RGB,此游戏的默认背景色已经设定为colors[0] 
    SetConsoleTextAttribute(hOutput,(back<<4)|front);
}

2.模板搭建

游戏使用鼠标操作,所以有一套鼠标系统

void GPT(POINT &pt) {//返回相对窗口的坐标,还不算框框 
    //pt.x是横,.y是竖 
    GetCursorPos(&pt);
    ScreenToClient(FindWindow("ConsoleWindowClass", NULL), &pt);
}
int ConPosx,ConPosy;//字体大小,分别是长,高 
RECT Rect;//存储窗口坐标
HWND hwnd=GetForegroundWindow();
void GetCon(){
    HANDLE hOutput;
    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_FONT_INFO consoleCurrentFont;
    GetCurrentConsoleFont(hOutput, FALSE, &consoleCurrentFont);
    //printf("通过GetCurrentConsoleFont()函数获取到的控制台当前字体长度是%d, 高度是%d.\n",consoleCurrentFont.dwFontSize.X,consoleCurrentFont.dwFontSize.Y);
    ConPosx=consoleCurrentFont.dwFontSize.X;
    ConPosy=consoleCurrentFont.dwFontSize.Y;
    /*GT(9,10);
    printf("%d %d",ConPosx,ConPosy);*/
}
POINT PT;//全局变量 
void PtToNum(int& x,int& y){//返回x行,y列 
//将鼠标坐标转换成控制台坐标 
    GetCon();
    GPT(PT);
    x=PT.x/ConPosx;//两格作为一格 
    y=PT.y/ConPosy;
    swap(x,y);//要置换过来 
}
class MESSAGE{//鼠标类 
public:
    int x,y;//储存第一的字符的控制台坐标 
    int length,height;//储存被判区域的长度和宽度“都是多少字符” 
    bool Is_Touching;//(1是0否)被鼠标触碰 
    POINT pt;//存储鼠标 
    short Left,Right,Top,Bottom;//存储字符串的可点击范围 
    MESSAGE(int x=0,int y=0,int length=0,int height=0){ 
        this->x=x;
        this->y=y;
        this->length=length;
        this->height=height;
    }
    void Army(short x,short y,short length,short height){
        this->x=x;
        this->y=y;
        this->length=length;
        this->height=height;
    }
    bool If_Touching(){
        Left=y*ConPosx;
        Right=Left+length*ConPosx;
        Top=x*ConPosy;
        Bottom=Top+height*ConPosy; 
        GPT(pt);
        return pt.x>=Left&&pt.x<Right&&pt.y>=Top&&pt.y<Bottom;
    }
    short Be_Clicked(){
        if(If_Touching()){
            if(GetAsyncKeyState(1))//左键 
                return 1;
            else if(GetAsyncKeyState(2))//右键 
                return 2;
            else if(GetAsyncKeyState(4))//中键 
                return 3;   
            else//没按键 
                return 0;
        }
        return 0;
    }
};
MESSAGE cur;

MESSAGE类提供对控制台的某一块范围进行触碰探测和点击探测的功能,例如,对(3,2)开始,向右向下分别延伸4和5个坐标的区间,探测方式如下

cur.Army(3 2,4,5);
if(cur.If_Touching())
    ...
//或者
if(cur.Be_Clicked()==1)//1左键2右键3中键
    ...

游戏准备了一套控制台属性初始化的代码,解决设计者们的燃眉之急

    RgbInit();//rgb配色初始化 
    system("mode con cols=90 lines=32");//屏幕大小设置 
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    DWORD mode;
    GetConsoleMode(hStdin, &mode);  
    mode &= ~ENABLE_QUICK_EDIT_MODE;  //移除快速编辑模式
    mode &= ~ENABLE_INSERT_MODE;      //移除插入模式
    mode &= ~ENABLE_MOUSE_INPUT;
    SetConsoleMode(hStdin, mode);
    srand((unsigned)time(0));//随机数设置 
    GetCon();//字体设置 
    GetWindowRect(hwnd,&Rect);//字体设置 
    HideCursor();//光标初始化 

其中RgbInit会影响控制台属性,所以需要最先执行,并只能执行一次

void RgbInit(){
    CONSOLE_SCREEN_BUFFER_INFOEX csbi = {sizeof(CONSOLE_SCREEN_BUFFER_INFOEX) };
    GetConsoleScreenBufferInfoEx(hOutput, &csbi);
    memcpy(csbi.ColorTable, colors, sizeof(colors)); //把颜色配置拷进去
    SetConsoleScreenBufferInfoEx(hOutput, &csbi); //应用设置 
}

各类开发游戏的思想请移步源码

bot算法所提供的接口

高潮部分来了,我想征集一份关于游戏的bot

1.Transform();

游戏为了设计方便,使用了两种不同的坐标模式,我起名为矩阵坐标井坐标,两种坐标的转换是有非常美的对称的。具体探究过程可以借鉴这篇讨论

void Transform(int i,int j,int& trani,int& tranj){//变换坐标,超级美的对称公式 
    //更绝妙的是,对一组(x,y)运算两次该变换,会得到原数对 
    trani=((i-1)/3)*3+((j-1)/3)+1;//Map下标从1开始,有着上述良好的性质 
    tranj=((i-1)%3)*3+((j-1)%3)+1;
}

这种运算两次坐标不变的性质确实是开发中的意外之喜。

少了个定义: 对于矩阵坐标(x,y),起名为 (矩阵坐标)横坐标(矩阵坐标)纵坐标

对于矩阵坐标(a,b),起名为 (井坐标)第一序数(井坐标)第二序数

2.CheckFull(int trani);

我提供了检测落子后下一个棋子应下的格子是否满格的函数

bool CheckFull(int trani){//检测满格,返回0 
    if(trani>9||trani<1)//悔棋的特判 
        return 0;
    int x,y;
    for(int j=1;j<=9;++j){
        Transform(trani,j,x,y);
        if(Map[x][y]==-1)//ok
            return 1;
    }
    return 0;//没格子,任意下
}

提供trani,即当前落子的井坐标第一序数,给出是否满格

restrict=CheckFull(trani)?tranj:0;

这条代码通过这个函数设置了下一个落子的范围(井坐标第一序数),这在悔棋部分也有应用。详见游戏规则和源码

CheckWin(int trani,int now);

检测是否产生胜局,暴力枚举法

int Check[3][3];
bool CheckWin(int trani,int now){//检查胜利(1出现胜利,0未出现) 
    //由于是井字棋的检测方式,我们可以枚举八种结果
    //首先将转换下来的坐标存储方便使用 
    int x,y;
    for(int j=1;j<=9;++j){
        Transform(trani,j,x,y);
        Check[(j-1)/3][(j-1)%3]=Map[x][y];
    }
    return (Check[0][0]==Check[0][1]&&Check[0][1]==Check[0][2]&&Check[0][2]==now)||
    (Check[1][0]==Check[1][1]&&Check[1][1]==Check[1][2]&&Check[1][2]==now)||
    (Check[2][0]==Check[2][1]&&Check[2][1]==Check[2][2]&&Check[2][2]==now)||
    (Check[0][0]==Check[1][0]&&Check[1][0]==Check[2][0]&&Check[2][0]==now)||
    (Check[0][1]==Check[1][1]&&Check[1][1]==Check[2][1]&&Check[2][1]==now)||
    (Check[0][2]==Check[1][2]&&Check[1][2]==Check[2][2]&&Check[2][2]==now)||
    (Check[0][0]==Check[1][1]&&Check[1][1]==Check[2][2]&&Check[2][2]==now)||
    (Check[0][2]==Check[1][1]&&Check[1][1]==Check[2][0]&&Check[2][0]==now);
}

这个没啥好说的

现在,征求一份bot,满足对于当前局面可以提供较优(或者最优)解,时间限制在5s内,有志愿者可以考虑提供源码或加入团队(见资源查找

最后,我认为我关注的增加数可以反映这个项目在洛谷的影响力,所以这篇blog如果帮助了你,不要忘记斟酌之后的关注,谢谢大家!