ぷよぷよプログラミング の改造版。 左右の回転、ネクスト、おじゃま、影の表示、他、のサンプルコード。 2020.07.10
回転操作用に、rollLeft と rollRight を用意する。また、押し戻し判定が 左回転 専用なので、右回転の判定も作る。
playing() の if (this.keyStatus.up) {} を
if (this.keyStatus.rollLeft) {} ~ if (this.keyStatus.rollRight) {} とに分岐。
const angle = this.keyStatus.rollLeft ? 90 : -90 ; // 回転方向をangleとして、角度をもたせる。 // let distRotation = (this.puyoStatus.rotation + 90) % 360; → let distRotation = (this.puyoStatus.rotation + angle) % 360; // angleは、回転後の角度の計算に使う
if (this.keyStatus.rollLeft) { // この左回転を複製する
if (rotation === 0) {
→
if (this.keyStatus.rollRight) {
if (rotation === 180) { // rotationは回転前の座標なので注意
左右の移動と回転操作、それぞれで ぷよの座標を計算しているので、これを1つの計算式につなぎあわせる。
this.puyoStatus.left = ratioMove * (this.moveDestination - this.moveSource) + this.moveSource // 移動によるx軸を含む位置を求める + (this.rotateAfterLeft - this.rotateBeforeLeft) * ratioRotate + this.rotateBeforeLeft; // 回転による相対的な位置の変化を求める
移動と回転 を単純に足し合わせてもうまくいかない。どちらの計算にも x軸をベースとした結果が返るため、 回転操作からは x軸の計算を除外するとよい。
移動によるx軸の結果 this.moveSource = x * Config.puyoImgWidth; this.moveDestination = (x + cx) * Config.puyoImgWidth; 回転によるx軸の結果 // this.rotateBeforeLeft = x * Config.puyoImgHeight; // this.rotateAfterLeft = (x + cx) * Config.puyoImgHeight; → this.rotateBeforeLeft = 0; this.rotateAfterLeft = cx * Config.puyoImgHeight;
また、キー入力がなくても上の計算に関する”変化がない”という計算を行っておく必要がある。
if (this.keyStatus.right || this.keyStatus.left) {
..
} else {
// 左右の移動がなくても計算する
const x = this.puyoStatus.x;
this.moveSource = x * Config.puyoImgWidth;
this.moveDestination = x * Config.puyoImgWidth;
}
if (this.keyStatus.rollLeft || this.keyStatus.rollRight) {
..
} else {
// 回転しなくても計算する
this.rotateBeforeLeft = 0;
this.rotateAfterLeft = 0;
this.rotateFromRotation = this.puyoStatus.rotation;
}
※移動しながら回転させると y軸が 許容を超えて大きく変化する場合がある。(たとえば、右から下への回転でy+1、下入力でy+1 あわせて飛び出しが発生する。床だけではなく天井に対しても同様。) 。これを制御しておく。
if(this.puyoStatus.y >= Config.stageRows - 2) { this.puyoStatus.y = Config.stageRows - 2 }; // > 行き過ぎたら戻す
if(this.puyoStatus.y < -1) { this.puyoStatus.y = -1 };
座標計算だけでは、キー入力の受付が単発なので同時操作できない。そこで、複数のキーを同時に認識できるようにする。 配列のkeyCodesに、入力したキーをキャッシュしていく。PCで見ている場合は、試しにキーボードで何か複数同時入力してみて。操作結果→ *
function handleMultiple(event) {
var keyCode = event.keyCode,
keyCodes = this.keyCodes,
i = keyCodes.indexOf(keyCode);
switch (event.type) {
case "keydown":
if (i === -1) {
keyCodes.push(keyCode);
}
if (keyCodes.indexOf(37) !== -1) { // 左向きキー
Player.keyStatus.left = true;
}
if (keyCodes.indexOf(38) !== -1) { // 上向きキー
Player.keyStatus.rollLeft = true;
}
if (keyCodes.indexOf(39) !== -1) { // 右向きキー
Player.keyStatus.right = true;
}
if (keyCodes.indexOf(40) !== -1) { // 下向きキー
Player.keyStatus.down = true;
}
break;
case "keyup":
keyCodes.splice(i, 1);
if (keyCodes.indexOf(37) === -1) {
Player.keyStatus.left = false;
}
if (keyCodes.indexOf(38) === -1) {
Player.keyStatus.rollLeft = false;
}
if (keyCodes.indexOf(39) === -1) {
Player.keyStatus.right = false;
}
if (keyCodes.indexOf(40) === -1) {
Player.keyStatus.down = false;
}
break;
}
}
var listener = { keyCodes: [], handleEvent: handleMultiple };
document.addEventListener("keydown", listener, false);
document.addEventListener("keyup", listener, false);
スマホを下へ大きくスワイプすると、画面更新が発生して、操作の邪魔になる。これを停止する。
function handleVoid(event) {
// preventDefaultが無視されるのでaddEventListenerの第三引数をpassive:falseにする
event.preventDefault();
}
// スクロールによる画面更新を禁止
document.addEventListener('touchmove', handleVoid, { passive: false });
createNewPuyo()の中で、新規ぷよを作っているので、ネクストの一覧から取り出す仕組みに変更する。 仮に、128個分のネクストを用意する。getNextPuyo(0)で1手目、getNextPuyo(1)で2手目が得られる。
static createNextTable() {
const puyoColors = Math.max(1, Math.min(5, Config.puyoColors));
// let nextTable = Array(127);
let tmpCenterPuyo;
let tmpMovablePuyo;
// テーブルサイズ
for (let i = 0; i < Config.nextTableSize; i++) { // Config.nextTableSize = 128
// 新しいぷよの色を決める
tmpCenterPuyo = Math.floor(Math.random() * puyoColors) + 1;
tmpMovablePuyo = Math.floor(Math.random() * puyoColors) + 1;
this.nextTable.push({'centerPuyo':tmpCenterPuyo, 'movablePuyo':tmpMovablePuyo});
}
}
// index: 0-127
static getNextPuyo(index) {
index = index % this.nextTable.length;
return this.nextTable[index];
}
ネクストを準備できたら、画面に表示する。ステージの作成と同じように配置する。 本来HTMLで生成するコードをjsからコントロールしている。
<div id="next"></div>を、HTML側へ。
for (let i = 0; i < Config.nextVolume; i++) { // Config.nextVolume は ネクストの表示数 = 2
let nextPuyo = this.getNextPuyo(index + (i + 1));
Stage.setNextPuyo(i, 1, nextPuyo['centerPuyo']);
Stage.setNextPuyo(i, 0, nextPuyo['movablePuyo']);
}
const nextElement = document.getElementById("next");
this.nextElement = nextElement;
static setNextPuyo(x, y, puyo) {
// 画像を作成し配置する
const puyoImage = PuyoImage.getPuyo(puyo);
puyoImage.style.left = (x * Config.puyoImgWidth + (x * Config.puyoImgWidth / 2)) + "px"; // xでネクネクの位置をズラす
puyoImage.style.top = (y * Config.puyoImgHeight + (x * Config.puyoImgHeight / 4)) + "px"; // xでネクネクの位置をズラす
this.nextElement.appendChild(puyoImage);
}
URLパラメータを使って、牌譜を指定できるようにする。1手ごとにURLパラメータを変更するには、window.history.pushState() または window.history.replaceState() を使う。
// Stage.boardの中身はそのままだと長い
?q=[[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,{%22puyo%22:2,%22element%22:{}},null,null,null],[null,null,{%22puyo%22:3,%22element%22:{}},null,null,null]]
→
// 短くまとめる
?q=[[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]]
// 牌譜を人がわかる程度に短くする
static encodeBoard(board) {
let ret = [];
if(!Array.isArray(board)) return null;
for(var y in board) {
let line = [];
for(var x in board[y]) {
line.push(board[y][x] && board[y][x].puyo || 0); // 0~4
}
ret.push(line);
}
return ret;
}
牌譜をサーバーへ送って、保存や復元ができる。サーバー側では、GETまたはPOSTデータを受け取り、データベースへ保存する。*リクエストを受け取るサーバー側のプログラムと状態を保持するデータベースが必要
var xhr = new XMLHttpRequest();
xhr.open('GET', server+'/add?q='+query, true);
xhr.send(null);

CSSアニメーションを使う。以下のような ぷるぷるアニメーションを用意し、タイミングをあわせて class を与える。
.jiggly {
animation: jiggly ease 0.7s 0s 1;
}
@keyframes jiggly {
0% { transform: scale(1.0, 1.0) translate(0%, 0%); }
15% { transform: scale(0.8, 0.8) translate(0%, 5%); }
30% { transform: scale(1.4, 0.8) translate(0%, 10%); }
50% { transform: scale(0.8, 1.4) translate(0%, -10%); }
70% { transform: scale(1.2, 0.8) translate(0%, 5%); }
100% { transform: scale(1.0, 1.0) translate(0%, 0%); }
}
puyoImage.classList.add('jiggly');
ブロックの存在チェックは1つのメソッドにまとめるとよい。コードの可読性が改善される。
// 指定した座標にブロックがあるかどうか
static isBlocked(x, y) {
if (
y < 0 ||
x < 0 ||
y >= Config.stageRows ||
x >= Config.stageCols ||
Stage.board[y][x]
) return true;
return false;
}
以下のような判定が10か所以上あるが、すべて1行に置き換えることができる
if (
my < 0 ||
mx + cx < 0 ||
mx + cx >= Config.stageCols ||
Stage.board[my][mx + cx]
) { ..
→
if (this.isBlocked(mx + cx, my)) { .. // この1行で済む
また、回転方向は4方向に限定しておく。マイナスは扱わず、0°, 90°, 180°, 270°の4つにする。
var distRotation = (this.puyoStatus.rotation + angle) % 360;
if (distRotation < 0) distRotation += 360; // > マイナス時は一回転
if (rotation === 0) { // 反時計回りで0°→90°の分岐 であることがわかりにくい(0°→270°の処理かもしれないし、0°に対する処理かもしれない。この1行では判断できない)
→ if (distRotation === 90) { // 90°の方向に対する処理であることが明確
// 他の角度も同様
if (rotation === 90) { → if (distRotation === 180) {
if (rotation === 180) { → if (distRotation === 270) {
if (rotation === 270) { → if (distRotation === 0) {
※ここまで対応すると、270°以外の処理は 左回転と右回転の処理は共通化できる。さらに、残った270°の処理も、キー入力(this.keyStatus)の値に置き換えると同一のコードとなり、複製した右回転の処理は一切不要になる。
const _rx = this.keyStatus.rollRight ? 1 : -1;
if (this.isBlocked(x + _rx, y + 2)) { // 左回転のときは x - 1, 右回転のときは x + 1 なので、(this.keyStatus.rollRight ? 1 : -1)に置き換えることができる。
if (y + 2 >= 0) {
// ブロックがある。上に引き上げる
cy = -1;
}
}
このプログラムはES6サポートのブラウザに限り動作する。IE11では動作しない。
(対応状況)
なお、クラスの意義を考えさせられるが、クラスメソッドがすべてstaticなので どこからでもどの値でも書き換えや参照ができてしまう。
this.ojamaImage = document.getElementById(`ojama`);
// 隣接しているojamaも消す
for (const info of this.erasingPuyoInfoList) {
this.ojamaErase(info.x, info.y, info.cell); // この周囲四方向のおじゃまをチェック
}
static ojamaErase(x, y, cell) {
// 自身がojamaの場合はerase対象外
if(cell && cell.puyo == 9) return;
// 四方向の周囲ぷよを確認する ・・checkErase()に似たような処理があるので参考に
(省略)
// indexが9だったら削除対象にする
this.erasingPuyoInfoList.push({
x: dx,
y: dy,
cell: this.board[dy][dx],
});
this.board[dy][dx] = null;
}
// 座標を特定する var clickX = event.pageX; var clickY = event.pageY; var stageRect = Stage.stageElement.getBoundingClientRect() ; var positionX = (stageRect.left + window.pageXOffset); var positionY = (stageRect.top + window.pageYOffset); var ex = (clickX - positionX) ; var ey = (clickY - positionY) ; const x = parseInt(ex / Config.puyoImgWidth) ; const y = parseInt(ey / Config.puyoImgHeight) ;
// ぷよを配置する Stage.setPuyo(x, y, puyoColor);
Puyo Puyo Champions. By Joro.