用Vue.js寫一個命令列貪吃蛇遊戲
前言
大家好,我是webfansplz.本文要分享的是如何使用Vue.js實現一個命令列貪吃蛇遊戲(temir-snake-game).對於貪吃蛇遊戲想必大家都不陌生了,使用Vue.js實現一個Web版的貪吃蛇遊戲似乎沒什麼難度,那如果是命令列版的呢?是不是你會對它的實現原理感興趣呢?讓我們開始吧!
安裝
npm install temir-snake-game -g
開始遊戲
在終端視窗執行 temir-sg
.
對於Windows系統,推薦使用hyper終端進行體驗.
將Vue渲染到命令列介面
使用Vue.js實現命令列貪吃蛇遊戲,首先意味著我們要將Vue.js渲染到命令列介面,才能開始具體的遊戲實現.我們經常用Vue.js來編寫Web應用,但是Vue的能力卻不僅僅侷限於此,它的舞臺也不只有瀏覽器.Vue3擁有出色的跨平臺能力,我們可以通過createRenderer API建立一個自定義渲染器,通過建立宿主環境中對應的Node和Element,並對元素進行增刪改查操作.
得益於Vue3出色的跨平臺能力,我實現了Temir,一個用Vue元件來編寫命令列介面應用的工具.開發者只需要使用Vue就可以編寫命令列應用,不需要任何額外的學習成本.順便值得一提的是,它還支援HMR~
關於Temir就不在這裡進行詳細的介紹了,有興趣的童鞋可以上Github檢視介紹或者看 使用Vue.js編寫命令列介面 這篇文章.
貪吃蛇遊戲實現
有了Temir,我們就具備了使用Vue.js編寫命令行遊戲的條件,接下來我們來看看遊戲的具體實現:
實現拆解
首先我們對遊戲實現進行一下簡單的拆解,從元素+邏輯的維度來看,可以簡單分為幾部分:
元素初始化
競技臺
蛇的爬行與食物的生成都需要依賴座標,最簡單的座標其實只需要一個索引值.因此競技臺的組成也很簡單,就是由很多個小盒子(這裡以:black_large_square:表示)組成,每一個盒子對應一個座標(索引),我們要做的是一個28*28的競技臺,因此它的索引集合就是(0~783).
const basic = 28 const backgroundIcon = ':black_large_square:' const arena = ref<string[]>([]) function initArena() { arena.value = Array.from({ length: basic * basic }, () => backgroundIcon) }
蛇
前面我們提到了座標的概念,蛇身的組成就是一串有規律的座標.
const snakeIcon = ' ' // 座標(索引)30,29 長度為2的蛇身 const snakeBody = ref([30, 29])
食物
食物的生成其實也就是隨機一個座標(索引),只不過要注意的是,我們需要避開蛇身本身的座標.
const foodIcon = ':poultry_leg:' // 食物座標 const foodCoord = ref(77) // 生成食物 function generateFood() { const food = Math.floor(Math.random() * basic * basic) // 與蛇身衝突,重新生成 if (snakeBody.value.includes(food)) { generateFood() return } foodCoord.value = food }
初始化後的元素長這樣 :point_down: :
蛇的爬行
蛇的爬行邏輯有兩個基礎元素,方向 + 步數.前面我們提到了競技臺的組成是一個28*28的行列式結構,那麼關於方向和步數的對映,就比較清晰了:
const map = { left: -1, right: 1, top: -28, bottom: 28 }
有了兩個基本元素,我們就可以得出我們每一次爬行的下一個座標.我們只需要在每次爬行的時候往蛇頭新增對應的座標,並移除蛇尾所在的座標就可以達到蛇爬行的效果.
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() } }
越界邏輯
貪吃蛇的遊戲結束規則判斷就是爬行時蛇頭越界(這裡的界限指的是超出競技臺的範圍)或者碰到蛇身.
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) }
方向控制
貪吃蛇遊戲核心的操作邏輯在於操縱蛇的方向進行食物的捕捉.所以我們需要做的就是捕捉使用者方向鍵的輸入進行方向的切換.Temir提供了useInput函式監聽使用者的輸入.
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繪製
關於UI的繪製與呈現Temir提供了一些Vue元件,我們只需要像構建Flexbox佈局那樣構建終端UI:
<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>
到這裡,貪吃蛇的實現就結束了,對具體實現感興趣的可以戳原始碼檢視.
演示
結語
如果我的文章和專案對你有所啟發和幫助,請給一個star支援作者 ✌!
- SegmentFault 2022 年社群週報 Vol.9
- 社群精選 | 不容錯過的9個冷門css屬性
- 2022最新版 Redis大廠面試題總結(附答案)
- 手寫一個mini版本的React狀態管理工具
- 【vue3原始碼】十三、認識Block
- 天翼雲全場景業務無縫替換至國產原生作業系統CTyunOS!
- JavaScript 設計模式 —— 代理模式
- MobTech簡訊驗證ApiCloud端SDK
- 以羊了個羊為例,淺談小程式抓包與響應報文修改
- 這幾種常見的 JVM 調優場景,你知道嗎?
- 聊聊如何利用管道模式來進行業務編排(下篇)
- 通用ORM的設計與實現
- 如此狂妄,自稱高效能佇列的Disruptor有啥來頭?
- 為什麼要學習GoF設計模式?
- 827. 最大人工島 : 簡單「並查集 列舉」運用題
- 介紹 Preact Signals
- 手把手教你如何使用 Timestream 實現物聯網時序資料儲存和分析
- 850. 矩形面積 II : 掃描線模板題
- Java 併發程式設計解析 | 基於JDK原始碼解析Java領域中的併發鎖,我們可以從中學習到什麼內容?
- 令人困惑的 Go time.AddDate