golang 实现华容道

来源:互联网 发布:淘宝网买的学车模拟器 编辑:程序博客网 时间:2024/05/08 13:50

游戏介绍

华容道游戏共有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 mainimport (    "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和起点位置. 注: 起点位置是棋子最左上角的坐标.

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

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

原创粉丝点击