golang 实现华容道

游戏介绍

华容道游戏共有10个棋子, 在5*4的棋盘上. 其中曹操占2*2格, 张飞,关羽, 赵云, 黄忠, 马超 占2*1格, 这些棋子有横向和竖向的区别. 4个卒各占1格. 目标: 将曹操这枚棋子移动到棋盘正下方. 棋子不能横跨棋子,每次只移动一格.

设计思路

因为没有图形界面, 于是将每个棋子和移动方向编号.
* 棋子编号: 0.曹操 1.张飞 2.关羽 3.赵云 4.黄忠 5.马超 6.兵 7.卒 8.勇 9.士
* 移动方向编号: 1.上 2.下 3.左 4.右
用户输入两个整数, 第一个选择棋子, 第二个选择方向.
如: 0, 1 将曹操向上移动一格.

将棋盘看作一个对象, 它有5*4的一个二维数组当作棋盘.
有10个棋子组成的map.
棋子结构体定义如下:

// 棋子
type Chessman struct {
    Name       string   `desc:"棋子的名字"`
    Code       string   `desc:"棋子在棋盘上的标志符"`
    StartPoint [2]int   `desc:"起点"`
    GridNums   int      `desc:"格子数"`
    Direction  int      `desc:"方向, 0 代表竖着, 1 代表横着"`
}

棋盘的定义如下:

// 华容道
type Klotski struct {
    Array    *[5][4]string  `desc:"5*4的地图"`
    Chessmen map[int]*Chessman  `desc:"存放棋子的字典"`
}

因为棋盘的摆放位置多种多样, 本程序没有模拟这个随机算法.只选取其中一种摆放位置.

// 用于初始化华容道地图
// 初始地图有很多种,所以将这部分提取出来,以后可扩展
func initKlotski() *Klotski {
    return &Klotski{
        Array: &[5][4]string{},
        Chessmen: map[int]*Chessman{
            0: {Name: "曹操", Code: "曹", StartPoint: [2]int{0, 1}, GridNums: 4},
            1: {Name: "张飞", Code: "张", StartPoint: [2]int{0, 0}, GridNums: 2, Direction: 0},
            2: {Name: "关羽", Code: "关", StartPoint: [2]int{2, 1}, GridNums: 2, Direction: 1},
            3: {Name: "赵云", Code: "赵", StartPoint: [2]int{0, 3}, GridNums: 2, Direction: 0},
            4: {Name: "黄忠", Code: "黄", StartPoint: [2]int{2, 0}, GridNums: 2, Direction: 0},
            5: {Name: "马超", Code: "马", StartPoint: [2]int{2, 3}, GridNums: 2, Direction: 0},
            6: {Name: "兵", Code: "兵", StartPoint: [2]int{3, 1}, GridNums: 1},
            7: {Name: "卒", Code: "卒", StartPoint: [2]int{3, 2}, GridNums: 1},
            8: {Name: "勇", Code: "勇", StartPoint: [2]int{4, 0}, GridNums: 1},
            9: {Name: "士", Code: "士", StartPoint: [2]int{4, 3}, GridNums: 1},
        },
    }
}

摆放完棋子后, 要初始化二维数组,一则方便呈现给用户看, 二则可以用来判断棋子的位置, 周围的情况.

const BLANK = "  " // 空白
// 根据棋子的位置,初始化华容道对象中的数组
// 将棋子的code填入棋盘上对应的位置中
func (this *Klotski) initArray() {
    arr := this.Array
    // 0-9 棋子
    for i := 0; i < 10; i++ {
        chessman := this.Chessmen[i]
        // 占据2个格子的棋子,根据棋子方向,填入code
        if chessman.GridNums == 2 {
            arr[chessman.StartPoint[0]][chessman.StartPoint[1]] = chessman.Code
            if chessman.Direction == 0 {
                // 总坐标 + 1
                arr[chessman.StartPoint[0] + 1][chessman.StartPoint[1]] = chessman.Code
            }else {
                // 横坐标 + 1
                arr[chessman.StartPoint[0]][chessman.StartPoint[1] + 1] = chessman.Code
            }
        }
        // 占据1个格子的棋子,在起点位置填入code
        if chessman.GridNums == 1 {
            arr[chessman.StartPoint[0]][chessman.StartPoint[1]] = chessman.Code
        }
        // 占据4个格子的棋子,填入code
        if chessman.GridNums == 4 {
            arr[chessman.StartPoint[0]][chessman.StartPoint[1]] = chessman.Code
            arr[chessman.StartPoint[0] + 1][chessman.StartPoint[1]] = chessman.Code
            arr[chessman.StartPoint[0]][chessman.StartPoint[1] + 1] = chessman.Code
            arr[chessman.StartPoint[0] + 1][chessman.StartPoint[1] + 1] = chessman.Code
        }
    }

    // 填入空白
    for i := 0; i < len(arr); i++ {
        for j := 0; j < len(arr[0]); j++ {
            if arr[i][j] == "" {
                arr[i][j] = BLANK
            }
        }
    }
}

常量BLANK的目的是代表2个空格, 让棋盘显示的好看一点.

开始游戏前要初始化,返回一个棋盘对象

// 返回一个华容道
func NewKlotski() *Klotski {
    // 1. 初始化棋子的位置
    klotski := initKlotski()
    // 2. 初始化数组
    klotski.initArray()
    return klotski
}

接收到用户的输入后,要移动棋子.
移动棋子时要注意会不会越界, 可不可以移动, 移动后的二维数组又会变成什么样.
move方法没有分解, 代码很长,这里就只给出函数签名, 最后会附上源码.
move时华容道对象的一个方法. 接收棋子的ID和移动方向.返回值是是否移动了棋子.

// 移动棋子的方法
func (this *Klotski) move(chessID, direct int) bool

源码

package main

import (
    "fmt"
    "bytes"
    "os"
)

const BLANK = "  " // 空白

// 棋子
type Chessman struct {
    Name       string   `desc:"棋子的名字"`
    Code       string   `desc:"棋子在棋盘上的标志符"`
    StartPoint [2]int   `desc:"起点"`
    GridNums   int      `desc:"格子数"`
    Direction  int      `desc:"方向, 0 代表竖着, 1 代表横着"`
}


// 华容道
type Klotski struct {
    Array    *[5][4]string  `desc:"5*4的地图"`
    Chessmen map[int]*Chessman  `desc:"存放棋子的字典"`
}

// 移动棋子的方法
func (this *Klotski) move(chessID, direct int) bool {
    arr := this.Array
    chessman := this.Chessmen[chessID]
    x, y := chessman.StartPoint[0], chessman.StartPoint[1]
    // 1. 输入校验
    if direct == 1 && x == 0 {
        // 向上移动时,x坐标要大于0
        return false
    }
    if direct == 2 && x == 4 {
        // 向下移动时,x坐标要小于4
        return false
    }
    if direct == 3 && y == 0 {
        // 向左移动时, y的坐标要大于0
        return false
    }
    if direct == 4 && y == 3 {
        // 向左移动时, y的坐标要小于3
        return false
    }

    // 2. 判断是否可以移动, 可以就移动

    // 占据2个格子的棋子,根据棋子方向, 移动
    isMove := false
    if chessman.GridNums == 2 {
        switch direct {
        case 1:  // 上
            if chessman.Direction == 0 {  // 竖着
                if arr[x-1][y] == BLANK {
                    this.switchGrid(x, y, x-1, y)
                    this.switchGrid(x+1, y, x, y)
                    isMove = true
                }
            }else {  // 横着
                if arr[x-1][y] == BLANK && arr[x-1][y+1] == BLANK {
                    this.switchGrid(x, y, x-1, y)
                    this.switchGrid(x, y+1, x-1, y+1)
                    isMove = true
                }
            }
        case 2:  // 下
            if chessman.Direction == 0 {  // 竖着
                if arr[x+2][y] == BLANK {
                    this.switchGrid(x+1, y, x+2, y)
                    this.switchGrid(x, y, x+1, y)
                    isMove = true
                }
            }else {  // 横着
                if arr[x+1][y] == BLANK && arr[x+1][y+1] == BLANK {
                    this.switchGrid(x, y, x+1, y)
                    this.switchGrid(x, y+1, x+1, y+1)
                    isMove = true
                }
            }
        case 3:  // 左
            if chessman.Direction == 0 {  // 竖着
                if arr[x][y-1] == BLANK && arr[x+1][y-1] == BLANK {
                    this.switchGrid(x, y, x, y-1)
                    this.switchGrid(x+1, y, x+1, y-1)
                    isMove = true
                }
            }else {  // 横着
                if arr[x][y-1] == BLANK {
                    this.switchGrid(x, y, x, y-1)
                    this.switchGrid(x, y+1, x, y)
                    isMove = true
                }
            }
        case 4:  // 右
            if chessman.Direction == 0 {  // 竖着
                if arr[x][y+1] == BLANK && arr[x+1][y+1] == BLANK {
                    this.switchGrid(x, y, x, y+1)
                    this.switchGrid(x+1, y, x+1, y+1)
                    isMove = true
                }
            }else {  // 横着
                if arr[x][y+2] == BLANK {
                    this.switchGrid(x, y+1, x, y+2)
                    this.switchGrid(x, y, x, y+1)
                    isMove = true
                }
            }
        }
    }

    // 占据1个格子的棋子, 移动
    if chessman.GridNums == 1 {
        switch direct {
        case 1:  // 上
            if arr[x-1][y] == BLANK {
                arr[x][y], arr[x-1][y] = arr[x-1][y], arr[x][y]
                isMove = true
            }
        case 2:  // 下
            if arr[x+1][y] == BLANK {
                arr[x][y], arr[x+1][y] = arr[x+1][y], arr[x][y]
                isMove = true
            }
        case 3:  // 左
            if arr[x][y-1] == BLANK {
                arr[x][y], arr[x][y-1] = arr[x][y-1], arr[x][y]
                isMove = true
            }
        case 4:  // 右
            if arr[x][y+1] == BLANK {
                arr[x][y], arr[x][y+1] = arr[x][y+1], arr[x][y]
                isMove = true
            }
        }
    }

    // 占据4个格子的棋子, 移动
    if chessman.GridNums == 4 {
        switch direct {
        case 1:  // 上
            if arr[x-1][y] == BLANK && arr[x-1][y+1] == BLANK {
                this.switchGrid(x, y, x-1, y)
                this.switchGrid(x, y+1, x-1, y+1)
                this.switchGrid(x+1, y, x, y)
                this.switchGrid(x+1, y+1, x, y+1)
                isMove = true
            }
        case 2:  // 下
            if arr[x+2][y] == BLANK && arr[x+2][y+1] == BLANK {
                this.switchGrid(x+1, y, x+2, y)
                this.switchGrid(x+1, y+1, x+2, y+1)
                this.switchGrid(x, y, x+1, y)
                this.switchGrid(x, y+1, x+1, y+1)
                isMove = true
            }
        case 3:  // 左
            if arr[x][y-1] == BLANK && arr[x+1][y] == BLANK {
                this.switchGrid(x, y, x, y-1)
                this.switchGrid(x+1, y, x+1, y-1)
                this.switchGrid(x, y+1, x, y)
                this.switchGrid(x+1, y+1, x+1, y)
                isMove = true
            }
        case 4:  // 右
            if arr[x][y+2] == BLANK && arr[x+1][y+2] == BLANK {
                this.switchGrid(x, y+1, x, y+2)
                this.switchGrid(x+1, y+1, x+1, y+2)
                this.switchGrid(x, y, x, y+1)
                this.switchGrid(x+1, y, x+1, y+1)
                isMove = true
            }
        }
    }

    // 3. 如果移动,修改棋子起点位置
    if isMove {
        switch direct {
        case 1:  // 上
            chessman.StartPoint[0] = x - 1
        case 2:  // 下
            chessman.StartPoint[0] = x + 1
        case 3:  // 左
            chessman.StartPoint[1] = y - 1
        case 4:  // 右
            chessman.StartPoint[1] = y + 1
        }
    }

    // 4. 判断用户是否胜利
    if this.Array[4][1] == "曹" && this.Array[4][2] == "曹" {
        fmt.Println("恭喜您胜利了!")
        fmt.Println("游戏退出.")
        os.Exit(0)
    }

    return isMove
}

// 交换数组中的(a, b) 和 (c, d)
func (this *Klotski) switchGrid(a, b, c, d int) {
    this.Array[a][b], this.Array[c][d] = this.Array[c][d], this.Array[a][b]
}

// 将华容道地图变成可打印的字符串
func (this *Klotski) String() string {
    var buffer bytes.Buffer
    for i := 0; i < 5; i++ {
        for j := 0; j < 4; j++ {
            buffer.WriteString(this.Array[i][j])
            buffer.WriteString(" ")
        }
        buffer.WriteString("\n")
    }
    return buffer.String()
}

// 返回一个华容道
func NewKlotski() *Klotski {
    // 1. 初始化棋子的位置
    klotski := initKlotski()
    // 2. 初始化数组
    klotski.initArray()
    return klotski
}

// 用于初始化华容道地图
// 初始地图有很多种,所以将这部分提取出来,以后可扩展
func initKlotski() *Klotski {
    return &Klotski{
        Array: &[5][4]string{},
        Chessmen: map[int]*Chessman{
            0: {Name: "曹操", Code: "曹", StartPoint: [2]int{0, 1}, GridNums: 4},
            1: {Name: "张飞", Code: "张", StartPoint: [2]int{0, 0}, GridNums: 2, Direction: 0},
            2: {Name: "关羽", Code: "关", StartPoint: [2]int{2, 1}, GridNums: 2, Direction: 1},
            3: {Name: "赵云", Code: "赵", StartPoint: [2]int{0, 3}, GridNums: 2, Direction: 0},
            4: {Name: "黄忠", Code: "黄", StartPoint: [2]int{2, 0}, GridNums: 2, Direction: 0},
            5: {Name: "马超", Code: "马", StartPoint: [2]int{2, 3}, GridNums: 2, Direction: 0},
            6: {Name: "兵", Code: "兵", StartPoint: [2]int{3, 1}, GridNums: 1},
            7: {Name: "卒", Code: "卒", StartPoint: [2]int{3, 2}, GridNums: 1},
            8: {Name: "勇", Code: "勇", StartPoint: [2]int{4, 0}, GridNums: 1},
            9: {Name: "士", Code: "士", StartPoint: [2]int{4, 3}, GridNums: 1},
        },
    }
}

// 根据棋子的位置,初始化华容道对象中的数组
// 将棋子的code填入棋盘上对应的位置中
func (this *Klotski) initArray() {
    arr := this.Array
    // 0-9 棋子
    for i := 0; i < 10; i++ {
        chessman := this.Chessmen[i]
        // 占据2个格子的棋子,根据棋子方向,填入code
        if chessman.GridNums == 2 {
            arr[chessman.StartPoint[0]][chessman.StartPoint[1]] = chessman.Code
            if chessman.Direction == 0 {
                // 总坐标 + 1
                arr[chessman.StartPoint[0] + 1][chessman.StartPoint[1]] = chessman.Code
            }else {
                // 横坐标 + 1
                arr[chessman.StartPoint[0]][chessman.StartPoint[1] + 1] = chessman.Code
            }
        }
        // 占据1个格子的棋子,在起点位置填入code
        if chessman.GridNums == 1 {
            arr[chessman.StartPoint[0]][chessman.StartPoint[1]] = chessman.Code
        }
        // 占据4个格子的棋子,填入code
        if chessman.GridNums == 4 {
            arr[chessman.StartPoint[0]][chessman.StartPoint[1]] = chessman.Code
            arr[chessman.StartPoint[0] + 1][chessman.StartPoint[1]] = chessman.Code
            arr[chessman.StartPoint[0]][chessman.StartPoint[1] + 1] = chessman.Code
            arr[chessman.StartPoint[0] + 1][chessman.StartPoint[1] + 1] = chessman.Code
        }
    }

    // 填入空白
    for i := 0; i < len(arr); i++ {
        for j := 0; j < len(arr[0]); j++ {
            if arr[i][j] == "" {
                arr[i][j] = BLANK
            }
        }
    }
}

// 游戏开始提示
func init() {
    fmt.Println("华容道游戏开始...")
    fmt.Println("操作提示: 输入棋子的序号和移动方向,然后回车. 如 0 1 代表将曹操向上移动一格. 输入0 0或其他字符退出游戏.")
    fmt.Println("0.曹操 1.张飞 2.关羽 3.赵云 4.黄忠 5.马超 6.兵 7.卒 8.勇 9.士")
    fmt.Println("1.上 2.下 3.左 4.右")
    fmt.Println()
}

// 用户输入
func input() (int, int) {

    fmt.Print("(棋子序号, 移动方向): ")
    chessID, direct := 0, 0
    fmt.Scan(&chessID, &direct)

    // 判断游戏是否结束
    if chessID == 0 && direct == 0 {
        fmt.Println("游戏退出.")
        os.Exit(0)
    }

    fmt.Println(chessID, direct)
    return chessID, direct
}

func main() {

    klotski := NewKlotski()
    fmt.Println(klotski.String())

    fmt.Println()

    for {
        chessID, direct := input()
        isMove := klotski.move(chessID, direct)
        if isMove {
            fmt.Println(klotski.String())
        }else {
            fmt.Println("被包围了,不能移动")
        }
    }
}

设计一个文件格式保存华容道?

  1. 使用json, 将华容道klotski这个对象进行序列化.
    golang的json模块可以做到.
  2. txt文件, 保存棋子的ID和起点位置. 注: 起点位置是棋子最左上角的坐标.

如何自动完成处于某个状态的华容道游戏?

华容道游戏的下棋步骤分支很多, 有点像树的结构. 因为游戏是要尽可能找到最少步骤, 要求出最优解.所以分支限界算法可以处理这个问题.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值