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)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/374989.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法学习——华为机考题库8(HJ46 - HJ55)

算法学习——华为机考题库8&#xff08;HJ46 - HJ50&#xff09; HJ46 截取字符串 描述 输入一个字符串和一个整数 k &#xff0c;截取字符串的前k个字符并输出 数据范围&#xff1a; 字符串长度满足 1≤n≤1000 &#xff0c; 1≤k≤n 输入描述&#xff1a; 1.输入待截取的…

spring boot学习第九篇:操作mongo的集合和集合中的数据

1、安装好了Mongodb 参考&#xff1a;ubuntu安装mongod、配置用户访问、添删改查-CSDN博客 2、pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns…

洛谷_P1014 [NOIP1999 普及组] Cantor 表_python写法

这道题其实没什么特别的&#xff0c;最重要就是仔细分析找到其中的数学规律。 以斜着为行&#xff0c;每一行的数值就是与第几行有关。 那对于Z字形而言就是行数的奇偶判断。 n int(input()) ans 0 flag 0 l [0] while ans < n:flag 1ans flag ans - flag n - ans j …

VPP学习-VPP初始化流程

概念 VPP作为一个开源的、高性能的用户态网络协议栈&#xff0c;以进程的形式运行于Linux或&#xff08;类unix&#xff09;系统下&#xff0c;即VPP实际是一个用户进程&#xff0c;VPP启动后可通过"ps -ef | grep vpp"命令查看。 VPP启动 用户态进程启动都有一个ma…

如何选择旅游路线,使得假期旅游路费最少?

旅行是许多人的热爱&#xff0c;但是在规划一个完美的假期时&#xff0c;找到最经济的路线常常是一个挑战。这里就需要引入一个著名的优化问题——旅行商问题。本文将介绍TSP的基础知识&#xff0c;并使用MTZ消除子环方法优化一个简单的TSP问题的示例。 旅行商问题简介 TSP&a…

springboot war包部署 和jar包部署

文章目录 war包部署设置打包方式为war排除内嵌的tomcat在插件中指定入口类打包测试 jar包部署设置打包方式执行打包测试访问修改插件版本指定jsp打包配置 重新打包测试 war包部署 设置打包方式为war 执行项目打包的方式为 "war" 默认创建springboot项目打包都是ja…

2024.2.4 awd总结

学习一下awd的靶机信息 防御阶段 感觉打了几次awd&#xff0c;前面阶段还算比较熟练 1.ssh连接 靶机登录 修改密码 [root8 ~]# passwd Changing password for user root. New password: Retype new password: 2.xftp连接 备份网站源码 xftp可以直接拖过来 我觉得这步还…

【详解】斗地主随机发牌项目

目录 前言&#xff1a; 1.初始化牌 2.洗牌 3.揭牌 总代码&#xff1a; Card类&#xff1a; CardGame类&#xff1a; Main类&#xff1a; 结语&#xff1a; 前言&#xff1a; 斗地主是全国范围内的一种桌面游戏&#xff0c;本节我们来实现一下斗地主中的简单初始化牌、…

Day 38 | 动态规划 理论基础 、 509. 斐波那契数 、 70. 爬楼梯 、746. 使用最小花费爬楼梯

理论基础 文章讲解 视频讲解 动态规划五部曲 509. 斐波那契数 题目 文章讲解 视频讲解 思路&#xff1a; class Solution {public int fib(int n) {if (n < 2)return n;int a 0, b 1, c 0;for (int i 1; i < n; i) {c a b;a b;b c;}return c;} }70. 爬楼梯…

深度解析源码,Spring 如何使用三级缓存解决循环依赖

目录 一. 前言 二. 基础知识 2.1. 什么是循环依赖&#xff1f; 2.2. 三级缓存 2.3. 原理执行流程 三. 源码解读 3.1. 代码入口 3.2. 第一层 3.3. 第二层 3.4. 第三层 3.5. 返回第二层 3.6. 返回第一层 四. 原理深度解读 4.1. 什么要有三级缓存&#xff1f; 4.2.…

百面嵌入式专栏(面试题)驱动开发面试题汇总1.0

沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将介绍驱动开发面试题 。 1、Linux驱动程序的功能是什么? 对设备初始化和释放。进行内核与硬件的数据交互。检测和处理设备出现的错误。2、内核程序中申请内存使用什么函数? 答案:kmalloc()、kzalloc()、vm…

Camunda流程引擎数据库架构

&#x1f496;专栏简介 ✔️本专栏将从Camunda(卡蒙达) 7中的关键概念到实现中国式工作流相关功能。 ✔️文章中只包含演示核心代码及测试数据&#xff0c;完整代码可查看作者的开源项目snail-camunda ✔️请给snail-camunda 点颗星吧&#x1f618; &#x1f496;数据库架构…

vue3 mathjax2.7.7 数学公式

1. index.html代码部分 <script type"text/x-mathjax-config">MathJax.Hub.Config({extensions: ["tex2jax.js"],jax: ["input/TeX","output/HTML-CSS"],tex2jax: {inlineMath: [["$","$"],["\\(&quo…

软件测试学习笔记-使用jmeter进行性能测试

性能测试&#xff1a;使用自动化工具&#xff0c;模拟不同的场景&#xff0c;对软件各项性能指标进行测试和评估的过程。 性能测试的目的&#xff1a; 评估当前系统的能力寻找性能瓶颈&#xff0c;优化性能评估软件是否能够满足未来的需要 性能测试和功能测试对比 焦点不同&…

沁恒微WCH32v003驱动ST7735S硬件spi+DMA调试小坑(2)

上一篇文章解决了spidma传输数据时DC线操作时序不匹配的问题&#xff0c;但是屏幕依旧没有点亮&#xff0c;所以这一篇文章继续找还存在的问题。上一篇文章&#xff1a;沁恒微WCH32v003驱动ST7735S硬件spiDMA调试小坑-CSDN博客 老规矩&#xff0c;先用逻辑分析仪抓取一下波形。…

幻兽帕鲁怎么样?好玩? Mac版的玩《幻兽帕鲁》也很简单,只需三个步骤

幻兽帕鲁怎么样 幻兽帕鲁是一款集合了多种游戏元素的游戏&#xff0c;它巧妙地融合了《方舟:生存进化》的野外生存挑战、《荒野之息》的开放世界探索、《魔兽世界》的多元角色互动以及宝可梦的精灵捕捉与培养等经典游戏元素。游戏的核心系统是「帕鲁」捕获&#xff0c;你可以让…

Redis -- zset有序集合

聪明在于勤奋&#xff0c;天才在于积累。 目录 zset 有序集合 zset相关命令 zadd zcard zcount zrange zrevrange zrangebyscore zpopmax bzpopmax zpopmin bzpopmin zrank zscore zrem zRemRangeByRank zRemRangeByScore zincrby 集合间操作 zinte…

光学PCIe 6.0技术引领AI时代超大规模集群

随着云计算、大数据和人工智能技术的快速发展&#xff0c;超大规模数据中心正经历一场前所未有的变革。传统的集中式架构逐渐转变为解聚式&#xff08;disaggregated&#xff09;架构&#xff0c;这种架构将计算、存储和网络资源从单一的物理服务器中分离出来&#xff0c;形成独…

【Vue3】解决路由缓存问题(响应路由参数的变化)

官方文档解释&#xff1a; 解决问题的思路: 让组件实例不复用,强制销毁重建监听路由变化,变化之后执行数据更新操作 方案一&#xff1a;给router-view添加key 以当前路由完整路径为key 的值&#xff0c;给router-view组件绑定 <RouterView :key"$route.fullPath&qu…

C++-模板基础

1. 泛型编程 大家在学习过程中一定写过swap函数吧&#xff0c;那么swap函数的可以写成很多种形式&#xff0c;因为形参的类型可以是任意类型&#xff0c;那么我们如果想用多种swap函数的话&#xff0c;就意味着我们必须写多个swap函数吗&#xff1f;不是的&#xff0c;C为了解…