6
头图

foreword

Hello everyone, I'm webfansplz . This article will share how to use Vue.js to implement a command-line snake game ( temir-snake-game ). You must be familiar with the snake game, using Vue.js to achieve A web version of Snake game seems to be easy, but what if it is a command line version? Are you interested in how it works? Let's get started!

Install

 npm install temir-snake-game -g

start the game

In a terminal window run temir-sg .

For Windows systems, it is recommended to use the hyper terminal for experience.

Render Vue to a command line interface

Using Vue.js to implement the command-line snake game first means that we need to render Vue.js to the command-line interface before we can start the specific game implementation. We often use Vue.js to write web applications, but Vue's capabilities are not It is not limited to this, its stage is not only the browser. Vue3 has excellent cross-platform capabilities, we can create a custom renderer through the createRenderer API , create the corresponding Node and Element in the host environment, and perform operations on the elements. Add, delete, modify and check operations.

Thanks to the excellent cross-platform capabilities of Vue3, I implemented Temir , a tool for writing command-line interface applications with Vue components. Developers only need to use Vue to write command-line applications without any additional learning costs. By the way It is worth mentioning that it also supports HMR~

About Temir will not be introduced in detail here. Interested children's shoes can go to Github to view the introduction or read the article on writing command line interface using Vue.js.

Snake game implementation

With Temir , we have the conditions to use Vue.js to write command line games. Next, let's take a look at the specific implementation of the game:

Implement disassembly

First of all, we have a simple dismantling of the game implementation. From the perspective of elements + logic, it can be simply divided into several parts:

element initialization

Arena

The crawling of snakes and the generation of food all need to rely on coordinates. The simplest coordinates actually only need an index value. Therefore, the composition of the arena is also very simple, that is, it is composed of many small boxes (represented by ⬛ here), and each box corresponds to A coordinate (index), what we want to do is a 28*28 arena, so its index set is (0~783).

 const basic = 28
const backgroundIcon = '⬛'
const arena = ref<string[]>([])

function initArena() {
 arena.value = Array.from({ length: basic * basic }, () => backgroundIcon)
}
snake

Earlier we mentioned the concept of coordinates, the composition of the snake body is a series of regular coordinates.

 const snakeIcon = '🟧'
// 坐标(索引)30,29 长度为2的蛇身
const snakeBody = ref([30, 29])
food

The generation of food is actually a random coordinate (index), but it should be noted that we need to avoid the coordinates of the snake body itself.

 const foodIcon = '🍗'
// 食物坐标
const foodCoord = ref(77)

// 生成食物
function generateFood() {
  const food = Math.floor(Math.random() * basic * basic)
  // 与蛇身冲突,重新生成
  if (snakeBody.value.includes(food)) {
    generateFood()
    return
  }
  foodCoord.value = food
}

The initialized element looks like this👇 :

snake crawling

The snake's crawling logic has two basic elements, the direction + the number of steps. We mentioned earlier that the composition of the arena is a 28*28 determinant structure, so the mapping of the direction and the number of steps is relatively clear:

 const map = {
 left: -1,
 right: 1,
 top: -28,
 bottom: 28
}

With two basic elements, we can get the next coordinate of each crawling. We only need to add the corresponding coordinates to the snake head and remove the coordinates of the snake's tail during each crawling to achieve the snake crawling. Effect.

 function move() {
  const h = snakeBody.value[0]
  // 计算下一次爬行坐标,并添加至蛇头
  head.value = h + direction.value
  snakeBody.value.unshift(head.value)

  // 吃到食物,重新生成
  if (head.value === foodCoord.value) {
    generateFood()
  }
  // 只有在未吃到食物的时候,才需要移除蛇尾
  else { snakeBody.value.pop() }
}

out-of-bounds logic

The game-ending rule for greedy snakes is that the head of the snake crosses the bounds when crawling (the limit here refers to beyond the scope of the arena) or touches the body of the snake.

 function isOutOfRange(h: number) {
    // 1. 蛇头碰到蛇身
  return snakeBody.value.indexOf(h, 1) > 0
    // 2. 蛇头超出竞技台上方
    || h < 0
    // 3. 蛇头超出竞技台下方
    || h > basic * basic - 1
    // 4. 蛇头超出竞技台右方
    || (direction.value === 1 && h % basic === 0)
    // 5. 蛇头超出竞技台左方
    || (direction.value === -1 && h % basic === basic - 1)
}

direction control

The core operation logic of the Snake game is to manipulate the direction of the snake to capture food. So what we need to do is to capture the input of the user's arrow keys to switch the direction. Temir provides the useInput function to monitor the user's input.

 import { useInput } from '@temir/core'
useInput(onKeyBoard, { isActive: true })

function onKeyBoard(_, keys) {
  const { upArrow, downArrow, leftArrow, rightArrow } = keys
  const d = {
    [+leftArrow]: -1,
    [+rightArrow]: 1,
    [+upArrow]: -basic,
    [+downArrow]: basic,
  }[1] ?? direction.value
  direction.value = (snakeBody.value[1] - snakeBody.value[0] === d) ? direction.value : d
}

UI drawing

About UI drawing and rendering Temir provides some Vue components, we just need to build the terminal UI like a Flexbox layout:

 <script lang="ts" setup>
import { computed } from 'vue'
import { TBox, TText } from '@temir/core'
import { useGame } from './composables'
import Header from './components/Header.vue'
import Home from './components/Home.vue'
import Game from './components/Game.vue'
import GameOver from './components/GameOver.vue'
import Exit from './components/Exit.vue'
const { playStatus } = useGame()
const activeComponent = computed(() => {
  return {
    unplayed: Home,
    playing: Game,
    over: GameOver,
    exit: Exit,
  }[playStatus.value]
})
</script>

<template>
  <TBox
    :width="100"
    justify-content="center"
    align-items="center"
    flex-direction="column"
    border-style="double"
  >
    <Header />
    <component :is="activeComponent" />
  </TBox>
</template>

At this point, the implementation of greedy snake is over, and those interested in the specific implementation can poke the source code to view.

demo

temir-snake-game.gif

Epilogue

If my articles and projects inspire and help you, please give a star to support the author ✌!


null仔
4.9k 声望3.7k 粉丝

总是有人要赢的,那为什么不能是我呢