ぷよぷよプログラミング の改造版。 左右の回転、ネクスト、おじゃま、影の表示、他、のサンプルコード。 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.