前回の記事で、セガが提供している「ぷよぷよプログラミング」を紹介しました。
公式のソースコードでは、左回転はできますが右回転ができません。右回転できるようソースコードを改変します。
左回転の処理を理解する
右回転できるようにするためには、すでに実装されている左回転のコードをコピーして修正すれば実現できるはずです。まずは、ソースコードを読んで左回転の処理を理解します。
前の記事でも説明していますが、game.js の loop() の中で、mode が”playing”のとき(プレイヤーの操作を判定している箇所)と、 “rotating”のとき(ぷよを回転している箇所)のコードを調べます。Player.playing() が “rotating” を返した場合に、Player.rotating() で回転していることがわかります。Player.playing() と Player.rotating() の中を見てみましょう。
function loop() {
switch (mode) {
...中略...
case "playing":
// プレイヤーが操作する
const action = Player.playing(frame);
mode = action; // 'playing' 'moving' 'rotating' 'fix' のどれかが帰ってくる
break;
...中略...
case "rotating":
if (!Player.rotating(frame)) {
// 回転が終わったので操作可能にする
mode = "playing";
}
break;
Player.playing() の中を見てみると、左回転を行う↑キーの入力判定があります。そして、操作するぷよの現在の回転角度に応じて cx, cy, canRotate をセットしています。cx は回転後の横移動の量を、cy は縦移動の量を表します。例えば、操作するぷよが左端にあるときに左回転を行うと、操作するぷよ全体を右に1つずらす必要があり、このときに cx が 1 になります。操作するぷよの左右が壁や別のぷよで囲まれている場合は、回転できないと判断し、canRotate を false にしています。canRotate が true の場合のみ、”rotating” を呼び出し元に返しています。
static playing(frame) {
...中略...
} else if (this.keyStatus.up) {
...中略...
const rotation = this.puyoStatus.rotation;
let canRotate = true;
let cx = 0;
let cy = 0;
if (rotation === 0) {
...中略...
}
if (canRotate) {
...中略...
return "rotating";
}
}
Player.rotating() の中を見ると、ぷよの現在の回転角度である this.puyoStatus.rotation に 90° を足していることがわかります。ratio が 1 になるまでは回転の途中のため false を返しており、ratio が 1 になり 90° 回転し終わったときに true を返しています。
static rotating(frame) {
// 回転中も自然落下はさせる
this.falling();
const ratio = Math.min(1, (frame - this.actionStartFrame) / Config.playerRotateFrame);
this.puyoStatus.left = (this.rotateAfterLeft - this.rotateBeforeLeft) * ratio + this.rotateBeforeLeft;
this.puyoStatus.rotation = this.rotateFromRotation + ratio * 90;
this.setPuyoPosition();
if (ratio === 1) {
this.puyoStatus.rotation = (this.rotateFromRotation + 90) % 360;
return false;
}
return true;
}
ここまでで、左回転を行っている処理をざっくり理解できました。ここからは、右回転の処理を実装していきます。必要な処理を一度に実装するとミスに気づきにくいため、以下の手順で段階的に実装していきます。
- 左回転を↑キーからzキーに変更する
- xキーでも左回転できるようにする
- xキーで右回転できるようにする
- 単純に右回転できない場合の処理を追加する
左回転を↑キーからzキーに変更する
元のソースコードでは、↑キーを押すとぷよが左回転します。これをzキーで左回転、xキーで右回転するよう実装します。まずは↑キーの入力判定しているところを、zキーに置き換えます。zキーのキーコードは仕様によると 90 です。keyStatusオブジェクトのキーであるupをrotateLeftに名称変更し、e.keyCodeの条件を 38 から 90 に変更します。下記のソースコード以外の箇所もすべて変更します。
static initialize() {
// キーボードの入力を確認する
this.keyStatus = {
right: false,
left: false,
rotateLeft: false,
down: false,
};
// ブラウザのキーボードの入力を取得するイベントリスナを登録する
document.addEventListener("keydown", (e) => {
// キーボードが押された場合
switch (e.keyCode) {
...中略...
case 90: // zキー
this.keyStatus.rotateLeft = true;
e.preventDefault();
return false;
...中略...
変更したソースコードを動かしてみましょう。↑キーは反応しなくなり、zキーで左回転できれば成功です。
xキーでも左回転できるようにする
左回転の既存の処理をそのまま利用して、xキーでも左回転できるようにします。this.keyStatus.rotateLeft に関するコードをコピーして、その直後に貼り付けます。xキーのキーコードは 88 です。下記のソースコード以外の箇所もすべて変更します。
static initialize() {
// キーボードの入力を確認する
this.keyStatus = {
right: false,
left: false,
rotateLeft: false,
rotateRight: false,
down: false,
};
// ブラウザのキーボードの入力を取得するイベントリスナを登録する
document.addEventListener("keydown", (e) => {
// キーボードが押された場合
switch (e.keyCode) {
...中略...
case 90: // zキー
this.keyStatus.rotateLeft = true;
e.preventDefault();
return false;
case 88: // xキー
this.keyStatus.rotateRight = true;
e.preventDefault();
return false;
case 39: // 右向きキー
...中略...
以下の this.keyStatus.rotateLeft で条件分岐している箇所も、まずはコピーして貼り付け、rotateRight に書き換えます。
static playing(frame) {
...中略...
} else if (this.keyStatus.rotateLeft) {
...中略...
} else if (this.keyStatus.rotateRight) {
...中略...
}
...中略...
変更したソースコードを動かしてみましょう。xキーを入力しても、zキーと同じように左回転すれば成功です。
xキーで右回転できるようにする
左回転は 90° ですが、右回転は -90° です。回転する角度を変えられるよう、Player.rotating() で 90° と記載されている箇所を変数 this.rotateAngle に置き換えます。以下のコードで this.puyoStatus.rotation に360の余りを代入していることから、this.puyoStatus.rotation は 0 ~ 360° である必要があるようです。this.rotateAngle が -90°のときに this.puyoStatus.rotation がマイナスにならないように、360を足す処理を加えています。
static playing(frame) {
...中略...
} else if (this.keyStatus.rotateLeft) {
...中略...
this.puyoStatus.x += cx;
this.rotateAngle = 90;
const distRotation = (this.puyoStatus.rotation + this.rotateAngle) % 360;
// 変更前:const distRotation = (this.puyoStatus.rotation + 90) % 360;
...中略...
}
static rotating(frame) {
...中略...
this.puyoStatus.rotation = this.rotateFromRotation + ratio * this.rotateAngle;
// 変更前:this.puyoStatus.rotation = this.rotateFromRotation + ratio * 90;
this.setPuyoPosition();
if (ratio === 1) {
this.puyoStatus.rotation = (this.rotateFromRotation + this.rotateAngle + 360) % 360;
// 変更前:this.puyoStatus.rotation = (this.rotateFromRotation + 90) % 360;
return false;
}
return true;
}
変更したソースコードを動かしてみましょう。zキーで左回転、xキーで右回転すれば成功です。
これで完成かと思いきや、この状態だと問題があります。ぷよを一番右に動かしてから、xキーを教えてみましょう。なんと、右回転したぷよが隠れてしまいます。期待する動きとしては、右回転時にぷよ全体が左に動くべきです。他にも、ぷよが一番左にあるときに右回転すると、ぷよ全体が右に移動してしまうといった問題もあります。次はこれらの問題を修正します。
xキー入力
→
単純に右回転できない場合の処理を追加する
ぷよの現在の回転角度に応じて、ぷよを左右や上下にずらす処理が必要です。以下のように、もともと左回転の処理であるコードを変更します。右回転の処理は、左回転の処理を左右反対にすればいいので、角度は左右反対に(0°→180°、180°→0°)、x軸はプラスマイナス反対に(x-1→x+1、x+1→x-1、cx=1→cx=-1)変更します。
static playing(frame) {
...中略...
} else if (this.keyStatus.rotateRight) {
...中略...
if (rotation === 180) {
// 右から上には100% 確実に回せる。何もしない
} else if (rotation === 90) {
// 上から右に回すときに、右にブロックがあれば左に移動する必要があるのでまず確認する
if (y + 1 < 0 || x + 1 < 0 || x + 1 >= Config.stageCols || Stage.board[y + 1][x + 1]) {
if (y + 1 >= 0) {
// ブロックがある。右に1個ずれる
cx = -1;
}
}
// 左にずれる必要がある時、左にもブロックがあれば回転出来ないので確認する
if (cx === -1) {
if (y + 1 < 0 || x - 1 < 0 || y + 1 >= Config.stageRows || x - 1 >= Config.stageCols || Stage.board[y + 1][x - 1]) {
if (y + 1 >= 0) {
// ブロックがある。回転出来なかった
canRotate = false;
}
}
}
} else if (rotation === 0) {
// 右から下に回す時には、自分の下か右下にブロックがあれば1個上に引き上げる。まず下を確認する
if (y + 2 < 0 || y + 2 >= Config.stageRows || Stage.board[y + 2][x]) {
if (y + 2 >= 0) {
// ブロックがある。上に引き上げる
cy = -1;
}
}
// 右下も確認する
if (y + 2 < 0 || y + 2 >= Config.stageRows || x + 1 < 0 || Stage.board[y + 2][x + 1]) {
if (y + 2 >= 0) {
// ブロックがある。上に引き上げる
cy = -1;
}
}
} else if (rotation === 270) {
// 下から左に回すときは、左にブロックがあれば右に移動する必要があるのでまず確認する
if (y + 1 < 0 || x - 1 < 0 || x - 1 >= Config.stageCols || Stage.board[y + 1][x - 1]) {
if (y + 1 >= 0) {
// ブロックがある。左に1個ずれる
cx = 1;
}
}
// 右にずれる必要がある時、右にもブロックがあれば回転出来ないので確認する
if (cx === 1) {
if (y + 1 < 0 || x + 1 < 0 || x + 1 >= Config.stageCols || Stage.board[y + 1][x + 1]) {
if (y + 1 >= 0) {
// ブロックがある。回転出来なかった
canRotate = false;
}
}
}
}
if (canRotate) {
...中略...
これで右回転に必要な処理はすべて実装できました。ぷよが縦の状態で一番右にあるとき、一番左にある時、ぷよが下に回転する状態で別のぷよが右下にある時、などおかしな挙動にならないか確認しましょう。
おわりに
実装済みの左回転を参考にすれば、右回転も簡単に実装できるかと思っていましたが、意外と必要な処理が多く、理解することや変更する箇所が多くて大変でした。今回の変更内容は以下のGitHubのコミット履歴で確認できます。
https://github.com/shoarai/puyopuyo-programing/commit/1a1484d7f56bdaa1ad2c917320817d84a92436df
おまけ:VSCode・フォーマッタ・Webサーバ設定
公式の開発環境ではMonacaが利用できます。自分は使い慣れたVSCodeを使いたかったため、右回転の処理を追加する前に以下の設定を行いました。
以下のVSCodeのワークスペースファイルを、ルートフォルダに作成します。ファイル保存時に自動フォーマットできるよう、フォーマッタであるPrettierを使用します。このファイルをVSCodeで開いて実装します。
{
"folders": [
{
"path": "."
}
],
"settings": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"extensions": {
"recommendations": ["esbenp.prettier-vscode"]
}
}
以下のPrettierの設定ファイルもルートフォルダに作成します。自分のPCのディスプレイ幅に合わせて一行の文字数上限を設定し、元のソースコードと同じタブ幅を設定します。
printWidth: 150
tabWidth: 4
アプリの起動のために、簡易的なWebサーバであるhttp-serverを使います。以下のファイルに”start”コマンドとして追加します。
{
...中略...
"scripts": {
"monaca:preview": "npm run dev",
"dev": "browser-sync start -s www/ --watch --port 8080 --ui-port 8081",
"start": "npx http-server www"
},
...中略...
}
ターミナルで以下のコマンドを実行すると、アプリが起動します。
npm start