我用Go写了个华容道游戏,曹操终于不用再求关羽了!

缘起

最近闲着无聊,突然想起小时候玩的华容道游戏。那个时候玩得可起劲了,经常为了把曹操从困境中解救出来,绞尽脑汁好几个小时。现在我可是程序员了,不如用Go语言把这个经典游戏复刻出来?说干就干!毕竟之前我用易语言和快手语言都开发过这个游戏。

开发思路:从棋盘到代码

核心数据结构:棋盘是怎么表示的?

首先得想清楚,怎么在代码里表示这个棋盘。华容道是一个5x4的棋盘,上面有不同形状的棋子。我灵机一动,用一个二维数组就能搞定!

//TZHF 阵法的二维数组
type TZHF [5][4]int8

//ZhFaPz 阵法数据结构
type ZhFaPz struct {
    Name string //阵法名称
    ZhFa TZHF   //阵法二维数组
}

这个设计我可是相当满意!数组里每个数字都有特殊含义:

  • 1代表曹操(2x2大胖子)
  • 2-10代表关羽等历史人物(分横条和竖条两种)
  • 11代表小兵(1x1灵活的小瘦子)
  • 0代表空格(大家都想抢占的位置)
  • -1代表占位符(比如曹操这么大,得占四个格子不是?)

这样设计的好处是一目了然,代码里判断起来特别方便。

阵法系统:让游戏更有挑战性

华容道好玩就好玩在有各种不同的阵法布局。我可不想写死一个布局,那样多没创意!于是我设计了一个配置文件系统:

func init() {
    //从配置txt读取阵法
    bb, _ := ioutil.ReadFile("config.txt")
    strsz := strings.Split(string(bb), "\r\n")
    for _, str := range strsz {
        if z, err := strToZhenFa(str); err == nil {
            zhenFaList = append(zhenFaList, z)
        }
    }
    //加载背景图片和角色图片
    //...
}

看,这样我只要在config.txt里加一行,就能添加一个新的阵法!比如:

横刀立马=5,1,-1,4,-1,-1,-1,-1,3,2,-1,6,-1,11,11,-1,11,0,0,11
层拦叠障=4,1,-1,11,-1,-1,-1,11,2,-1,11,11,6,7,-1,5,-1,0,0,-1

用户想玩什么难度,自己选!我还特意收集了好几种经典阵法,够玩家折腾一阵子了。

技术细节:如何让棋子听话地移动?

移动算法:看似简单实则复杂

移动棋子是游戏的核心。刚开始我以为很简单,后来发现不同形状的棋子移动规则完全不一样!曹操那么大一个,移动起来得考虑四个格子;而小兵就灵活多了。

我写了一个yd函数("移动"的拼音缩写)来处理所有移动逻辑:

func yd(yx, yy, nx, ny int32, TempZF TZHF) (TZHF, bool) {
    // yx,yy: 原位置坐标
    // nx,ny: 目标位置坐标
    // 返回: 新的棋盘状态和是否移动成功
    
    if yx == nx && yy == ny || TempZF[nx][ny] != 0 {
        return TempZF, false
    }
    
    switch TempZF[yx][yy] {
    case 1: // 曹操的移动逻辑
        // 各种复杂的条件判断...
    case 2,7,8,9,10: // 横条角色
        // 横条移动规则...
    case 3,4,5,6: // 竖条角色
        // 竖条移动规则...
    case 11: // 小兵
        // 小兵移动规则...
    }
    return TempZF, false
}

这里我耍了个小技巧:棋子不仅可以移动一格,还可以一次性移动多格!只要路径上都是空的,想滑多远滑多远。这大大提高了游戏的流畅度,用起来特别爽!

电脑求解:让AI当你的救星

这绝对是我最得意的部分!当初我被华容道卡关时,要是有这功能就好了。我实现了一个自动求解的AI,用的是广度优先搜索(BFS)算法。

func (f *TForm1) OnButton1Click(sender vcl.IObject) {
    hashjg := make(map[int]TZHF) // 存储已走过的状态,避免重复
    sz := []TZHF{}               // 待尝试的状态列表
    sz = append(sz, zhenFa)      // 从当前状态开始
    
    // 广度优先搜索
    for {
        newsz := []TZHF{}
        for _, sj := range sz {
            ydhsz := getYdsz(sj) // 获取所有可能的下一步状态
            for _, ydh := range ydhsz {
                key := getKey(ydh)
                
                // 检查是否已经达到目标状态(曹操到达出口)
                if ydh[3][1] == 1 {
                    // 找到解了!
                    // ...
                }
                
                // 如果这个状态之前没出现过,就加入待尝试列表
                if _, ok := hashjg[key]; !ok {
                    hashjg[key] = sj
                    newsz = append(newsz, ydh)
                }
            }
        }
        // ...
    }
    
    // 回溯并展示求解过程
    // ...
}

为了让搜索更高效,我还发明了一个状态压缩的方法:

func getKey(sj TZHF) int {
    jg := 1
    for i := 0; i < 5; i++ {
        for j := 0; j < 4; j++ {
            sj := int(sj[i][j])
            // 将相似的棋子类型进行归类,减少状态空间
            if sj == 4 || sj == 5 || sj == 6 { sj = 3 }  // 竖条统一
            else if sj == 7 || sj == 8 || sj == 9 || sj == 10 { sj = 2 }  // 横条统一
            else if sj == 11 { sj = 4 }  // 小兵转成4
            else if sj == 0 { sj = 5 }  // 空格转成5
            if sj > -1 {
                jg = jg*10 + sj // 合并数据得到一个int型的key
            }
        }
    }
    return jg
}

这招可管用了!把相似类型的棋子归类后,大大减少了需要存储的状态数量,搜索速度提升了不少。找到解后,我还加了个动画效果,一步步展示如何移动,特别直观!

项目结构

这个项目的文件组织还是比较清晰的,主要分为几个部分:

├── Form1.go            # 界面定义文件
├── Form1Impl.go        # 界面实现和核心游戏逻辑
├── Hrd.exe             # 编译后的可执行文件
├── README.md           # 项目说明文档(就是你现在看的这个)
├── config.txt          # 阵法配置文件
├── go.mod              # Go模块定义
├── go.sum              # 依赖校验文件
├── h.go                # 核心数据结构和工具函数
├── main.go             # 程序入口
├── png/                # 图片资源文件夹
│   ├── 0.png           # 空格图片
│   ├── 1.png           # 曹操图片
│   ├── 2.png-11.png    # 其他角色图片
│   ├── 原.jpg          # 原始图片素材
│   └── 背景原.png      # 背景图片
└── ui/                 # 界面设计相关文件
    └── backup/         # 备份文件

主要代码都在Form1Impl.goh.gomain.go这几个文件里。如果你想修改游戏逻辑,这几个文件是重点。config.txt用来配置新的阵法,png文件夹里则存放了所有的游戏角色图片。

用户体验:让游戏更好玩

界面设计

我用govcl库做了个简洁的界面。左边是游戏棋盘,右边是阵法列表。用户可以:

  1. 点击选择棋子,再点击空格移动
  2. 双击右边的阵法列表切换不同的布局
  3. 实在解不出来时,点"电脑求解"按钮,让AI来救场

小彩蛋

游戏成功时,我特意加了一句古诗:

曹瞒兵败走华容,正与关公狭路逢,
只为当初恩义重,放开金锁走蛟龙。

有没有瞬间回到三国时代的感觉?这可是当初我翻了好久的资料找出来的,很应景吧!

上个图

在这里插入图片描述

总结

这个华容道游戏虽然不大,但让我学到了很多。从数据结构设计到算法实现,再到用户界面,每一步都充满了挑战和乐趣。最让我满足的是,现在我可以随时打开游戏,重温小时候的快乐时光了。

如果你也想挑战一下,不妨试试"横刀立马"这个经典阵法。要是实在解不出来,记得找AI帮忙哦!


PS:如果你有什么好的阵法创意,欢迎在config.txt里添加,分享你的智慧!

下载链接

想亲自体验这个华容道游戏吗?快来下载试试吧!

点击这里下载完整源码

往期部分文章列表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值