简介

2048是一款简单但具有挑战性的益智游戏,玩家通过滑动数字方块,使相同数字的方块合并,以达到2048为目标。本文将介绍如何使用Go语言实现2048游戏,涵盖主要功能模块的代码实现和解释。

游戏规则

  1. 合并规则:当两个相同数字的方块相撞时,它们会合并成一个新的方块,其值为两个方块的和。

  2. 移动方向:玩家可以选择向上、下、左、右四个方向滑动方块。

  3. 胜利条件:当一个方块的值达到2048时,玩家获胜。

  4. 失败条件:当所有格子都被填满且无法进行任何合并时,游戏结束。

功能模块

  1. 游戏初始化:创建一个4x4的棋盘,并随机生成两个初始数字(2或4)。

  2. 玩家输入:捕获玩家的滑动方向(上、下、左、右)。

  3. 方块移动与合并:根据玩家输入的方向移动并合并方块。

  4. 新方块生成:每次玩家移动后,随机生成一个新的数字方块。

  5. 游戏结束检测:检查当前棋盘状态是否达到胜利条件或失败条件。

代码实现

初始化棋盘

func initArr() {
    clearCells()
    fillEmptyCell()
}

func clearCells() {
    Cells = [4][4]int{
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0},
    }
}

func fillEmptyCell() {
    type cell [2]int
    var list [16]cell
    var count = 0
    for i := 0; i < 4; i++ {
        for j := 0; j < 4; j++ {
            if Cells[i][j] == 0 {
                list[count][0] = i
                list[count][1] = j
                count++
            }
        }
    }

    if count > 0 {
        l := list[rand.Intn(count)]
        Cells[l[0]][l[1]] = getValue()
    }
}

func getValue() int {
    r := rand.Intn(10)
    var value = 2
    if r >= 9 {
        value = 4
    }
    return value
}

游戏结束检测

func checkGameOver() bool {
    var f bool
    for i := 0; i < 4; i++ {
        for j := 0; j < 4; j++ {
            if Cells[j][i] == 0 {
                f = true
                goto Goon
            }
            if j < 3 && Cells[i][j] == Cells[i][j+1] {
                f = true
                goto Goon
            }
            if i < 3 && Cells[i][j] == Cells[i+1][j] {
                f = true
                goto Goon
            }
        }
    }
Goon:
    if f {
        fillEmptyCell()
    }

    return f
}

方块移动与合并

func horizontal(toLeft bool) {
    for i := 0; i < 4; i++ {
        newArr := []int{0, 0, 0, 0}
        for j := 0; j < 4; j++ {
            newArr[j] = Cells[i][j]
        }
        if toLeft == false {
            tmp := []int{0, 0, 0, 0}
            for k, v := range newArr {
                tmp[4-k-1] = v
            }
            newArr = tmp
            resultArr := resize(newArr)
            for j := 0; j < 4; j++ {
                Cells[i][j] = resultArr[4-j-1]
            }
        } else {
            resultArr := resize(newArr)
            for j := 0; j < 4; j++ {
                Cells[i][j] = resultArr[j]
            }
        }
    }
}

func vertical(toTop bool) {
    for i := 0; i < 4; i++ {
        newArr := []int{0, 0, 0, 0}
        for j := 0; j < 4; j++ {
            newArr[j] = Cells[j][i]
        }
        if toTop == false {
            tmp := []int{0, 0, 0, 0}
            for k, v := range newArr {
                tmp[4-k-1] = v
            }
            newArr = tmp
            resultArr := resize(newArr)
            for j := 0; j < 4; j++ {
                Cells[j][i] = resultArr[4-j-1]
            }
        } else {
            resultArr := resize(newArr)
            for j := 0; j < 4; j++ {
                Cells[j][i] = resultArr[j]
            }
        }
    }
}

func resize(arr []int) []int {
    l := len(arr)
    newArr := make([]int, 0)
    for _, k := range arr {
        if k == 0 {
            continue
        }
        newArr = append(newArr, k)
    }

    for {
        tmp := make([]int, 0)
        f := true

        for k, v := range newArr {
            if v == 0 {
                continue
            }

            if k == len(newArr)-1 || v != newArr[k+1] {
                tmp = append(tmp, v)
                continue
            }

            newArr[k] = v + newArr[k+1]
            newArr[k+1] = 0
            tmp = append(tmp, newArr[k])
            f = false
        }

        newArr = tmp

        if f {
            break
        }
    }

    ret := make([]int, l)
    for k, v := range newArr {
        ret[k] = v
    }

    return ret
}

构建和显示游戏界面

var numMap = map[int]string{
    2:    "[2](bg:red)",
    4:    "[4](bg:blue)",
    8:    "[8](bg:green)",
    16:   "[16](bg:yellow)",
    32:   "[32](bg:blue)",
    64:   "[64](bg:red)",
    128:  "[128](bg:yellow)",
    256:  "[256](bg:red)",
    512:  "[512](bg:blue)",
    1024: "[1024](bg:green)",
}

func getNum(n int) string {
    if v, ok := numMap[n]; ok {
        return v
    }
    return fmt.Sprint(n)
}

func buildTerm() string {
    var s string
    s = "------------------\n"
    for i := range Cells {
        s = s + "|"
        for j := range Cells[i] {
            s = s + getNum(Cells[i][j]) + "|"
        }
        s = s + "\n"
        s = s + "------------------\n"
    }

    return s
}

func game() {
    p1 := widgets.NewParagraph()
    p1.TitleStyle.Fg = termui.ColorBlack
    p1.TitleStyle.Fg = termui.ColorBlue
    p1.BorderStyle.Fg = termui.ColorBlue
    p1.Title = "2048 开始"
    if checkGameOver() == false {
        p1.TitleStyle.Fg = termui.ColorRed
        p1.BorderStyle.Fg = termui.ColorRed
        p1.Title = "Game Over!"
    }
    p1.Text = buildTerm()
    p1.SetRect(0, 5, 30, 20)
    termui.Render(p1)
}

func title() {
    p := widgets.NewParagraph()
    p.Title = "2048"
    p.Text = "上下左右移动方块\n[ESC](fg:green):退出游戏\n[DELETE](fg:green):重新开始"
    p.SetRect(0, 0, 30, 5)
    termui.Render(p)
}

初始化终端界面

func Start() {
    if err := termui.Init(); err != nil {
        log.Fatalf("failed to initialize termui: %v", err)
    }
    defer termui.Close()

    playGame()
}

玩家输入

func playGame() {
    initArr()
    title()
    game()
    listenAction()
}

func listenAction() {
    for e := range termui.PollEvents() {
        if e.Type != termui.KeyboardEvent {
            continue
        }

        switch e.ID {
        case "<Escape>":
            return
        case "<Delete>":
            initArr()
            break
        case "<Up>":
            vertical(true)
            break
        case "<Down>":
            vertical(false)
            break
        case "<Left>":
            horizontal(true)
            break
        case "<Right>":
            horizontal(false)
            break
        default:
        }

        game()
    }
}

主函数

package main

import (
   "2048/V0"
)

func main() {
   V0.Start()
}

关键概念解释

  1. 随机数生成通过 math/rand 包,在空位置生成新的方块(2 或 4)

  2. 状态存储游戏的状态存储在一个 4x4 的二维数组 Cells 中,每个元素代表棋盘上的一个位置,存储该位置的数字。

  3. 游戏结束判断:checkGameOver函数用于检测棋盘是否已无可移动的空间或可合并的相邻数字。

  4. 合并逻辑:在resize函数中实现方块合并的逻辑。