Snake: MoonBit版贪吃蛇来了!

什么是贪吃蛇?

贪吃蛇(Snake)是起源于1976年的街机游戏 Blockade。此类游戏在1990年代由于一些具有小型屏幕的移动电话的引入而再度流行起来,在现在的手机上基本都可安装此小游戏。版本亦有所不同。

在游戏中,玩家操控一条细长的直线(俗称蛇或虫),它会不停前进,玩家只能操控蛇的头部朝向(上下左右),一路拾起触碰到之物(或称作“豆”),并要避免触碰到自身或者其他障碍物。每次贪吃蛇吃掉一个食物,它的身体便增长一些。吃掉一些食物后会使蛇的移动速度逐渐加快,让游戏的难度渐渐变大。游戏设计大致分为四面都有墙(都不可穿越)以及某部分的墙可以穿越,以及四面墙都可以穿越的模式。有些游戏碰到自己身体也不会死掉,例如蛇蛇食颜色、贪吃蛇进化论等等。本文将介绍如何用MoonBit实现贪吃蛇,完整的代码:https://github.com/moonbitlang/moonbit-docs/tree/main/examples/snake

如果你想尝试一下,可以点击此处进行尝试:https://www.moonbitlang.cn/gallery

如何用MoonBit实现贪吃蛇

struct GameState来创建整个游戏:

struct GameState{
   
  mut grid:  Array[Int]
  mut body:  List[Position]
  mut dir:   Direction
}

grid来初始化每个格子的颜色:

0 0 0 0 0 0
0 2 0 1 0 0
0 0 0 0 0 0

0代表普通格子,1代表蛇的身体,2代表食物

初始化后的游戏界面:

在这里插入图片描述

生成食物

fn random() -> Double = "Math" "random"
fn floor(i: Double) -> Int = "Math" "floor"

fn generate_Food(self: GameState){
   
  while true {
   
    let i : Int = floor(random() * 20.0)
    let j : Int = floor(random() * 20.0)

    if(self.grid[j * grid_col_count + i] == grid_num(Default)){
   
      self.setGridType({
   x: i, y: j}, Food)
      return
    }
  }
}

首先通过两个外部引用函数随机生成新食物的横坐标和纵坐标,然后通过grid_num来判断新生成的坐标是否可以放置,最后通过setGridType方法来设置为食物。

控制蛇行进

pub fn tran_step(self : GameState, a : Int){
   
  let mut action : Direction = Default
  match a {
   
    1 => action = Up
    2 => action = Down
    3 => action = Left
    4 => action = Right
    _ => action = Default
  }

  self.step(action)
}

pub fn step(self : GameState, action : Direction) {
   

  match action {
   
    // move up
    Up =>{
   
      if length(self.body) == 1{
   
        self.dir = Up
      }else{
   
        if self.dir == Left || self.dir == Right || self.dir == Up{
   
          self.dir = Up
        }else{
   
          self.dir = self.dir
        }
      }

      }

    // move down
    Down =>{
   
      if length(self.body) == 1{
   
        self.dir = Down
      }else{
   
        if self.dir == Left || self.dir == Right || self.dir == Down{
   
          self.dir = Down
        }else{
   
          self.dir = self.dir
        }
      }

      }

    // move left
    Left =>{
   
      if length(self.body) == 1{
   
        self.dir = Left
      }else{
   
        if self.dir == Up || self.dir == Left || self.dir == Down{
   
          self.dir = Left
        }else{
   
          self.dir = self.dir
        }
      }

      }

    // move right
    Right =>{
   
      if length(self.body) == 1{
   
        self.dir = Right
      }else{
   
        if self.dir == Up || self.dir == Right || self.dir == Down{
   
          self.dir = Right
        }else{
   
          self.dir = self.dir
        }
      }
      }

    _ =>{
   
      self.dir = self.dir
      }

  }

  self.go_step()
}

首先,通过tran_step方法识别外部键盘的响应并对应不同的输入方向指令。

其次,通过step方法来过滤输入方向是否合法。游戏规定蛇无法转向180度,但在蛇身长度为1时,蛇可以上下左右自由移动。

最后,调用go_step方法完成蛇的移动。

fn go_step(self: GameState){
   
  let head : Position = get_head(self.body)
  let newHead : Position = {
   x: head.x , y: head.y }

  newHead.x = dir_posi(self.dir).x + newHead.x
  newHead.y = dir_posi(self.dir).y + newHead.y

  newHead.x = (newHead.x + grid_col_count) % grid_col_count
  newHead.y = (newHead.y + grid_col_count) % grid_col_count

  if self.grid[newHead.y * grid_col_count + newHead.x] == 1{
   

    initialize(self)
    return
  }else if self.grid[newHead.y * grid_col_count + newHead.x] == 2{
   

    self.setGridType(newHead, Body)
    self.body = Cons(newHead, self.body)
    generate_Food(self)
  }else {
   

    self.setGridType(newHead, Body)
    self.body = Cons(newHead, self.body)
    self.setGridType(get_tail(self.body), Default)
    self.body = delete_tail(self.body)
  }

}

go_step方法中,首先通过self.dirPosition获得新头的位置,其次判断新头的位置是普通格子,蛇的身体还是食物。

  • 如果为普通格子,则将新头设置为蛇身,删除蛇原本的尾巴
  • 如果为蛇的身体,本轮游戏结束,重新initialize整场游戏
  • 如果为食物,则吃掉这个食物,并把食物的位置设置成蛇身

通过外部引用画图

声明外部函数引用

type Canvas_ctx

fn set_stroke_color(self : Canvas_ctx, color : Int) = "canvas" "set_stroke_color"

fn set_line_width(self : Canvas_ctx, width : Double) = "canvas" "set_line_width"

fn stroke_rect(self : Canvas_ctx, x : Int, y : Int, width : Int, height : Int) = "canvas" "stroke_rect"

fn fill_rect(self : Canvas_ctx, x : Int, y : Int, width : Int, height : Int) = "canvas" "fill_rect"

fn set_fill_style(self : Canvas_ctx, color : Int) = "canvas" "set_fill_style"

然后进行画图

pub fn draw(canvas : Canvas_ctx, snake : GameState) {
   
  let mut c = 0

  // draw backgroud
  while c < grid_col_count {
   
    canvas.set_fill_style(0)
    canvas.fill_rect(c, 0, 1, grid_row_count)
    c = c + 1
  }

  draw_piece(canvas, snake.grid, (0, 0))
}

pub fn draw_piece(canvas : Canvas_ctx, matrix : Array[Int],
        offset : (Int, Int)) {
   

  let mut r = 0
  let mut c = 0
  let mut c0 = 0
  while c < matrix.length() {
   
    if matrix[c] == 0 {
   
      c = c + 1
      continue
    }
    c0 = c % grid_col_count
    r = c / grid_col_count
    canvas.set_fill_style(matrix[c] + 1)
    canvas.fill_rect( offset.0 + c0, r, 1, 1)
    canvas.set_stroke_color(1)
    canvas.set_line_width(0.1)
    canvas.stroke_rect( c0, r, 1, 1)
    c = c + 1
  }
}

JavaScript 键盘监听与画面更新

window.addEventListener("keydown", (e) => {
   
  if (!requestAnimationFrameId) return
  switch (e.key) {
   
    case "ArrowLeft": {
   
        snake_step(snake, 3)
        snake_draw(context, snake)
        break
    }
    case "ArrowRight": {
   

        snake_step(snake, 4)
        snake_draw(context, snake)
        break
    }
    case "ArrowDown": {
   

        snake_step(snake, 2)
        snake_draw(context, snake)
        break
    }
    case "ArrowUp": {
   

        snake_step(snake, 1)
        snake_draw(context, snake)
        break
    }

  }
})

update画面,调用snake_stepsnake_draw方法

function update(time = 0) {
   
  const deltaTime = time - lastTime
  dropCounter += deltaTime
  if (dropCounter > dropInterval) {
   
    snake_step(snake, 5);
    dropCounter = 0
  }
  lastTime = time
  snake_draw(context, snake)
  requestAnimationFrameId = requestAnimationFrame(update)
}

相关推荐

  1. Python小游戏 贪吃(完整) Pygame sys time

    2024-02-06 21:08:03       22 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-06 21:08:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-06 21:08:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-06 21:08:03       82 阅读
  4. Python语言-面向对象

    2024-02-06 21:08:03       91 阅读

热门阅读

  1. 单片机和 ARM 的区别

    2024-02-06 21:08:03       40 阅读
  2. LeetCode解法汇总292. Nim 游戏

    2024-02-06 21:08:03       57 阅读
  3. 计算机网络的形成和发展

    2024-02-06 21:08:03       47 阅读
  4. 【数据提取Xpath/BeautifulSoup4】

    2024-02-06 21:08:03       49 阅读
  5. Python f-strings - PEP 498 - 字面字符串插值

    2024-02-06 21:08:03       47 阅读
  6. SQL--DML

    SQL--DML

    2024-02-06 21:08:03      44 阅读
  7. PostgreSQL导出导入

    2024-02-06 21:08:03       55 阅读
  8. 抖音ip地址可以改?抖音如何改ip地址

    2024-02-06 21:08:03       48 阅读
  9. 重学 VUE —— 一、创建一个应用

    2024-02-06 21:08:03       51 阅读
  10. SpringBoot 动态加载jar包,动态配置

    2024-02-06 21:08:03       44 阅读
  11. 什么是边缘计算?

    2024-02-06 21:08:03       52 阅读
  12. Spring Boot项目整合Seata AT模式

    2024-02-06 21:08:03       53 阅读
  13. c#directory 和directoryinfo的使用

    2024-02-06 21:08:03       54 阅读