こんにちは、猫チーズです。
社内のVue.js勉強用プログラムとして1時間ほどで簡単なスネークゲームを作ったところ、メンバー各々が魔改造などして、一時的に社内でスネークゲームが流行りました。
(2019/09/29 追記) 第2弾も作りました → 『250行のVue.jsで陣取りゲームを作った』
デモページ
GitHub ソースコード
ゲームルール
スネークゲームは、シンプルで古典的なゲームです。
- 何も操作しないとヘビはまっすぐ進む
- 壁か自分自身にぶつかるとゲームオーバー
- 矢印キーでヘビを方向転換できる
- リンゴを食べると体が伸びる&スコアUP
これらのルールをVue.jsで作りました。
200行のプログラム
以下の200行のhtmlファイルに全ての機能が纏まっています。
snake.htmlなどの名前で保存して、ブラウザでそのファイルを開くと遊べます。
速さや色、新しいアイテムの追加などをして遊んでみてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Snake</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<style>
/* グリッドレイアウト */
#map {
--grid-size: 10; /* 10 x 10 マス(CSS変数) */
display: grid;
grid-template-columns: repeat(var(--grid-size), 30px); /* 10列 幅30px */
grid-template-rows: repeat(var(--grid-size), 30px); /* 10行 高さ30px */
}
/* セルの色 */
.cell {
border: 1px solid white;
background: whitesmoke;
}
/* ヘビの体の色 */
.cell.body {
background: darkgray;
}
/* フルーツの色 */
.cell.fruit {
background: orangered;
}
/* ヘビの頭の色 */
.cell.head {
background: dimgray;
}
</style>
</head>
<body>
<div id='app'>
<p>SCORE: {{ snake.body_indexes.length - 1 }}</p>
<div id='map'>
<!-- セルを100個生成して、必要に応じてhead, body, fruitクラスを付ける -->
<!-- (注意:Vueは v-for="i in 数値" としたとき、iが1から始まる) -->
<div v-for="i in grid_size * grid_size"
:class="{
cell: true,
head: snake_head_index === i - 1,
body: snake.body_indexes.includes(i - 1),
fruit: fruit_index === i - 1,
}"
><!-- {{ i - 1 }} --></div>
</div>
<p v-if='is_gameover'>
GAME OVER<br>
<button onclick="location.reload()">RETRY</button>
</p>
</div>
<script>
new Vue({
el: '#app',
data: {
grid_size: 10, // 10 x 10 マス
fruit_index: 0, // フルーツの位置インデックス
// ヘビに関するデータ
snake: {
// 頭の座標(初期値)
head_pos: {
x: 1,
y: 3,
},
body_indexes: [0], // 体の位置インデックスたち
direction: '→', // 進行方向
speed: 400, // 1マス進むのにかかる時間[ms]
},
},
// 初期化
created() {
// フルーツの位置をランダムに移動
this.randomize_fruit_index()
// キーボード入力のイベントをon_keydownメソッドに投げる
document.onkeydown = () => {
this.on_keydown(event.keyCode)
}
// 時間を動かし始める
this.time_goes()
},
watch: {
// フルーツを食べているか監視
is_eating_fruit(newValue) {
if(!newValue) return // 食べていなかったら何もしない
this.grow_up_snake()
this.randomize_fruit_index()
},
},
computed: {
// ヘビの頭の座標をインデックスに変換
snake_head_index() {
if(this.is_frameout) return null
return this.snake.head_pos.y * this.grid_size + this.snake.head_pos.x
},
// フルーツ食べてる?
is_eating_fruit() {
return this.snake_head_index === this.fruit_index
},
// 自己衝突してる?
is_suicided() {
return this.snake.body_indexes.includes(this.snake_head_index)
},
// ヘビの頭は画面外?
is_frameout() {
const head = this.snake.head_pos
return head.x < 0 || this.grid_size <= head.x || head.y < 0 || this.grid_size <= head.y
},
// ゲームオーバー?
is_gameover() {
return this.is_suicided || this.is_frameout
},
},
methods: {
// 時間を進める
time_goes() {
if(this.is_gameover) return
this.forward_snake()
// speedミリ秒後に自分自身を呼び出す
setTimeout(this.time_goes.bind(this), this.snake.speed)
},
// ヘビを進める
forward_snake() {
// 体の最後尾を頭に持ってくる
this.snake.body_indexes.shift()
this.snake.body_indexes.push(this.snake_head_index)
// 頭を1マス移動
switch(this.snake.direction) {
case '←': this.snake.head_pos.x--; break
case '↑': this.snake.head_pos.y--; break
case '→': this.snake.head_pos.x++; break
case '↓': this.snake.head_pos.y++; break
}
},
// ヘビの体を伸ばす
grow_up_snake() {
this.snake.body_indexes.unshift(this.snake.body_indexes[0])
},
// フルーツの位置をランダムに移動
randomize_fruit_index() {
this.fruit_index = Math.floor(Math.random() * this.grid_size * this.grid_size) // 0 〜 99 の乱数
},
// キー入力を受け取ってヘビの進行方向を変える(逆方向は不可)
on_keydown(keyCode) {
switch(keyCode) {
case 37: // 「←」キーが押された
if(this.snake.direction !== '→') { this.snake.direction = '←'; }
break
case 38: // 「↑」キーが押された
if(this.snake.direction !== '↓') { this.snake.direction = '↑'; }
break
case 39: // 「→」キーが押された
if(this.snake.direction !== '←') { this.snake.direction = '→'; }
break
case 40: // 「↓」キーが押された
if(this.snake.direction !== '↑') { this.snake.direction = '↓'; }
break
}
},
},
})
</script>
</body>
</html>
位置インデックスについて補足
プログラム内に出てくる **位置インデックス** というのは、各セルに割り振った番号のことです。左上の0から始まり、右下まで行くと99です。上の画像の時点では、
ヘビの頭 snake_head_index === 58
ヘビの体 snake.body_indexes === [44, 54, 55, 56, 57]
フルーツ fruit_index === 99
になっています。
猫チーズ
高校一年の頃からアプリ作りに没頭していて、今はアプリクリエイターとして生きるために奮闘中です。
今年の4月からサイバーブレイン株式会社でデザイナーを始めました。
Twitter | 猫チーズ
https://twitter.com/miyauchoi
Hello 猫チーズ | 猫チーズと擬似会話ができるブログ
https://blog.miyauchi-akira.app/post/20190927/
ポートフォリオ | ミヤウチアキラ
https://miyauchi-akira.app
会社
サイバーブレイン株式会社では、AI Academyというサービスを作っています。
AI Academy
「AIを作りながらAIプログラミングを学ぼう」
https://aiacademy.jp/
Pythonを使った機械学習の教材が無料でたくさんあります。
AIに興味がある方はぜひ。
AI Academyサービス自体のフロントエンド側がVue.jsで作られています。
そのため今回は、社内メンバー(and いずれ来る後輩)のためにVue.jsの教材を作りました。