[Scala] 2048小游戏

[Scala] 2048小游戏

很长一段时间没有用过Scala,重新学习了下顺便写个小游戏练练手

在这里插入图片描述

规则

  • 相同数字的两个格子,相撞时数字会相加。
  • 每次滑动时,空白处会随机刷新出一个数字的格子。
  • 当界面全部被数字填满时游戏结束;当界面中最大数字是2048时,游戏胜利

代码

object Game2048 {
  val target = 2048
  var highest = 0

  def main(args: Array[String]): Unit = {
    SwingUtilities.invokeLater(() => {
      val f = new JFrame
      f.setDefaultCloseOperation(3)
      f.setTitle("2048")
      f.add(new Game, BorderLayout.CENTER)
      f.pack()
      f.setLocationRelativeTo(null)
      f.setVisible(true)
    })
  }

  class Game extends JPanel {
    private val (rand, side) = (new Random, 4)
    private var (tiles, game) = (Array.ofDim[Tile](side, side), State.start)

    final private val colorTable =
      Seq(new Color(0x701710), new Color(0xFFE4C3), new Color(0xfff4d3), new Color(0xffdac3), new Color(0xe7b08e), new Color(0xe7bf8e),
        new Color(0xffc4c3), new Color(0xE7948e), new Color(0xbe7e56), new Color(0xbe5e56), new Color(0x9c3931), new Color(0x701710))

    setPreferredSize(new Dimension(900, 700))
    setBackground(new Color(0xFAF8EF))
    setFont(new Font("SansSerif", Font.BOLD, 48))
    setFocusable(true)
    addMouseListener(new MouseAdapter() {
      override def mousePressed(e: MouseEvent): Unit = {
        startGame()
        repaint()
      }
    })
    addKeyListener(new KeyAdapter() {
      override def keyPressed(e: KeyEvent): Unit = {
        e.getKeyCode match {
          case KeyEvent.VK_UP => moveUp()
          case KeyEvent.VK_DOWN => moveDown()
          case KeyEvent.VK_LEFT => moveLeft()
          case KeyEvent.VK_RIGHT => moveRight()
          case _ =>
        }
        repaint()
      }
    })

    override def paintComponent(gg: Graphics): Unit = {
      super.paintComponent(gg)
      val g = gg.asInstanceOf[Graphics2D]
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
      drawGrid(g)
    }

    private def drawGrid(g: Graphics2D): Unit = {
      val (gridColor, emptyColor, startColor) = (new Color(0xBBADA0), new Color(0xCDC1B4), new Color(0xFFEBCD))

      if (game == State.running) {
        g.setColor(gridColor)
        g.fillRoundRect(200, 100, 499, 499, 15, 15)
        for (
          r <- 0 until side;
          c <- 0 until side
        ) if (Option(tiles(r)(c)).isEmpty) {
          g.setColor(emptyColor)
          g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7)
        }
        else drawTile(g, r, c)
      } else {
        g.setColor(startColor)
        g.fillRoundRect(215, 115, 469, 469, 7, 7)
        g.setColor(gridColor.darker)
        g.setFont(new Font("SansSerif", Font.BOLD, 128))
        g.drawString("2048", 310, 270)
        g.setFont(new Font("SansSerif", Font.BOLD, 20))
        if (game == State.won) g.drawString("you made it!", 390, 350)
        else if (game == State.over) g.drawString("game over", 400, 350)
        g.setColor(gridColor)
        g.drawString("click to start a new game", 330, 470)
        g.drawString("(use arrow keys to move tiles)", 310, 530)
      }
    }

    private def drawTile(g: Graphics2D, r: Int, c: Int): Unit = {
      val value = tiles(r)(c).value
      g.setColor(colorTable((math.log(value) / math.log(2)).toInt + 1))
      g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7)
      g.setColor(if (value < 128) colorTable.head else colorTable(1))
      val (s, fm) = (value.toString, g.getFontMetrics)
      val asc = fm.getAscent
      val (x, y) = (215 + c * 121 + (106 - fm.stringWidth(s)) / 2, 115 + r * 121 + (asc + (106 - (asc + fm.getDescent)) / 2))
      g.drawString(s, x, y)
    }

    private def moveUp(checkingAvailableMoves: Boolean = false) = move(-1, 0, checkingAvailableMoves)

    private def moveDown(checkingAvailableMoves: Boolean = false) = move(1, 0, checkingAvailableMoves)

    private def moveLeft(checkingAvailableMoves: Boolean = false) = move(0, -1, checkingAvailableMoves)

    private def moveRight(checkingAvailableMoves: Boolean = false) = move(0, 1, checkingAvailableMoves)

    private def clearMerged(): Unit = for (row <- tiles; tile <- row) if (Option(tile).isDefined) tile.setMerged()

    private def movesAvailable() = moveUp(true) || moveDown(true) || moveLeft(true) || moveRight(true)

    def move(yIncr: Int, xIncr: Int, checkingAvailableMoves: Boolean): Boolean = {
      var moved = false
      for (i <- 0 until side;
           j <- 0 until side) if (Option(tiles(i)(j)).isDefined) {
        var (r, c) = (i, j)
        var (nextR, nextC, breek) = (i + yIncr, j + xIncr, false)
        while ((nextR >= 0 && nextR < side && nextC >= 0 && nextC < side) && !breek) {
          val (next, curr) = (tiles(nextR)(nextC), tiles(r)(c))

          if (Option(next).isEmpty)
            if (checkingAvailableMoves) return true
            else {
              tiles(nextR)(nextC) = curr
              tiles(r)(c) = null
              r = nextR
              c = nextC
              nextR += yIncr
              nextC += xIncr
              moved = true
            }
          else if (next.canMergeWith(curr)) {
            if (checkingAvailableMoves) return true
            highest = math.max(next.mergeWith(curr), highest)
            tiles(r)(c) = null
            breek = true
            moved = true
          } else breek = true
        }
      }
      updateState(moved)
      moved
    }

    private def updateState(moved: Boolean): Unit = {
      if (moved && highest < target) {
        clearMerged()
        addRandomTile()
        if (!movesAvailable) game = State.over
      }
      else if (highest == target) game = State.won
    }

    private def startGame(): Unit = if (game ne State.running) {
      highest = 0
      game = State.running
      tiles = Array.ofDim[Tile](side, side)
      addRandomTile()
      addRandomTile()
    }

    private def addRandomTile(): Unit = {
      var pos = rand.nextInt(side * side)
      var (row, col) = (0, 0)
      do {
        pos = (pos + 1) % (side * side)
        row = pos / side
        col = pos % side
      } while (Option(tiles(row)(col)).isDefined)
      tiles(row)(col) = Tile(if (rand.nextInt(10) == 0) 4 else 2)
    }
  }
}

完整代码

后面可以考虑加上AI,尝试基于规则或强化学习的方法

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值