鳩でもわかるXORパズルをつくる(1)の続きです。今回はゲーム開始以降の処理を実装します。

ゲーム開始の処理
ゲームを開始するための処理を示します。
最初の問題を生成・表示させて各フラグをリセットしています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function gameStart(is_easy){ is_easy_mode = is_easy; showQuestion(); $start_buttons.style.display = 'none'; $ctrl_buttons.style.display = 'block'; playing = true; ignore_click = false; timer_moving = true; remaining_time = REMAINING_TIME_MAX; score = 0; clear_count = 0; select_sound.currentTime = 0; select_sound.play(); } |
移動の処理
移動させる処理を示します。引数は移動先のセルのindexです。移動できない場所が引数として渡された場合は何もしません。
移動可能であれば移動処理をおこない、xorの値を更新して現在位置に応じて各セルの背景色を変えます。右下のセルに到達したらxorの値をチェックして 0 であればステージクリアです。
ステージクリアのときはステージ数と残り時間から追加する点数を計算して加点処理をおこないます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
const sleep = async(ms) => await new Promise(_ => setTimeout(_, ms)); async function move(idx){ if(!playing || ignore_click) return; ignore_click = true; // 移動中に新たな移動処理が発生しないようにする const nexts = getNextIndexes(cur_index); let find = false; for (let i = 0; i < nexts.length; i++) { // 引数は移動可能な場所か? if(nexts[i] == idx){ find = true; break; } } if(find){ // 移動可能であれば移動 cur_index = idx; changeCellsColor(cur_index); xor ^= cur_question.values[cur_index]; showXOR(xor); select_sound.currentTime = 0; select_sound.play(); } else { ng_sound.currentTime = 0; ng_sound.play(); } // 右下に到達したらxorが0か確認する。0ならステージクリア if(cur_index == ROW_COUNT * COL_COUNT - 1 && xor == 0){ clear_count++; timer_moving = false; // 2秒後に次ステージに遷移して再開。それまでタイマーを止める const add = Math.floor(remaining_time / 10) + clear_count * 10000; if(!is_easy_mode) score += add; else score += Math.floor(add / 8); clear_sound.currentTime = 0; clear_sound.play(); await sleep(2000); showQuestion(); timer_moving = true; } if(remaining_time > 0) // 残り時間が0になっていない場合だけ再度クリックに反応するようにする ignore_click = false; } |
キー操作でも移動処理がおこなえるようにしておきます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function onKeyDown(ev){ if(ev.key != 'ArrowUp' && ev.key != 'ArrowDown' && ev.key != 'ArrowLeft' && ev.key != 'ArrowRight') return; if(!playing) return; ev.preventDefault(); if(ev.key == 'ArrowUp' && Math.floor(cur_index / COL_COUNT) > 0) move(cur_index - COL_COUNT); if(ev.key == 'ArrowDown' && Math.floor(cur_index / COL_COUNT) < ROW_COUNT - 1) move(cur_index + COL_COUNT); if(ev.key == 'ArrowLeft' && cur_index % COL_COUNT > 0) move(cur_index - 1); if(ev.key == 'ArrowRight' && cur_index % COL_COUNT < COL_COUNT - 1) move(cur_index + 1); } |
リセットの処理
[リセット]が押下されたら現在位置を左上のマスに戻します。
|
1 2 3 4 5 6 7 8 9 |
function reset(){ if(!playing || ignore_click) return; cur_index = 0; changeCellsColor(cur_index); xor = cur_question.values[0]; showXOR(xor); } |
ギブアップの処理
[ギブアップ]が押下されたら解を表示して次の問題を出題します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
async function giveup(){ if(!playing || ignore_click) return; const ans = cur_question.ans; ignore_click = true; // クリックに反応しないようにして cur_index = 0; // 現在位置をスタート地点に戻す xor = 0; for(let i = 0; i < ans.length; i++){ // 0.5秒おきに正しい手順でゴールまで移動させる await sleep(500); for(let k = 0; k < ROW_COUNT * COL_COUNT; k++) getElementFromCellIndex(k).style.backgroundColor = '#000'; cur_index = ans[i]; const cur_elm = getElementFromCellIndex(cur_index); cur_elm.style.backgroundColor = '#f00'; xor ^= cur_question.values[cur_index]; showXOR(xor); } await sleep(2000); // 解を示し終わったら次の問題を表示 showQuestion(); if(remaining_time > 0) ignore_click = false; } |
スコアランキングへの登録
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function saveScore(player_name, score){ if(player_name == '') player_name = '名無しのゴンベ'; // JSON形式でPOST fetch('./ranking.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: player_name, score: score, }) }); } |
PHP側では以下のような処理がおこなわれます。上位20件のスコアと各ユーザーの最高スコアが100人分保存されます。
ranking.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
<?php if($_SERVER["REQUEST_METHOD"] == "POST") saveData(); if($_SERVER["REQUEST_METHOD"] == "GET") echo getData(); function GetFileName(){ return "../xor-puzzle-highscore.json"; } function getData(){ $file_path = GetFileName(); if(file_exists($file_path)) return file_get_contents($file_path); //JSON形式 } function saveData(){ //return; // POSTされたJSON文字列を取り出し $json = file_get_contents("php://input"); // JSON文字列をobjectに変換(第2引数をtrueにしないとハマるので注意) $contents = json_decode($json, true); if(!isset($contents['name'])) return; if(!isset($contents['score'])) return; $name = $contents['name']; $score = $contents['score']; $name = htmlspecialchars($name, ENT_QUOTES); $now = date('Y-m-d H:i:s'); if($name == '' || mb_strlen($name) > 32 || !is_numeric($score)) return; $time = (int)$time; $score = (int)$score; $file_path = GetFileName(); if(file_exists($file_path)){ $ranking = json_decode(file_get_contents($file_path), true); //JSON形式を元に戻す } else { $ranking = array(); $ranking = [ 'scores' => array(), 'users' => array(), ]; } $data = [ 'name' => $name, 'score' => $score, 'date' => $now, ]; $ranking['scores'][] = $data; $ranking['scores'] = sortByScore($ranking['scores']); $done = false; foreach ($ranking['users'] as &$value) { if($value['name'] == $name){ if($value['score'] < $score){ $value['score'] = $score; $value['date'] = $now; } $done = true; break; } } unset($value); // 最後の要素への参照を解除します if(!$done) $ranking['users'][] = $data; $ranking['users'] = sortByScore($ranking['users']); $scores_max_count = 20; $users_max_count = 100; $ranking['scores'] = array_slice($ranking['scores'], 0, $scores_max_count); $ranking['users'] = array_slice($ranking['users'], 0, $users_max_count); $json = json_encode($ranking, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT); file_put_contents($file_path, $json, LOCK_EX); } function sortByScore($array) { $scores_array = createArrayForSort('score', $array); $name_array = createArrayForSort('name', $array); array_multisort($scores_array, SORT_DESC, $name_array, SORT_ASC, $array); return $array; } function createArrayForSort($key_name, $array) { foreach ($array as $key => $value) $key_array[$key] = $value[$key_name]; return $key_array; } |
スコアランキングの表示
スコアランキングを表示させます。
HTML部分
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>鳩でもわかるXORパズル ランキング</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel="stylesheet" href="./ranking.css"> </head> <body> <div id = "container"> <h1>鳩でもわかるXORパズル ランキング</h1> <p><a href="./">ゲームのページへ</a></p> <h2>スコアランキング</h2> <div id = "scores"></div> <h2>ユーザーランキング</h2> <div id = "users"></div> </div> <script src = "./ranking.js"></script> </body> </html> |
ranking.css
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
body { color: white; background-color: black; } #container { width: 560px; margin-left: auto; margin-right: auto; margin-bottom: 80px; } h1 { font-size: 20px; color: magenta; } h2 { font-size: 18px; color: yellow; margin-top: 40px; } a { color: aqua; font-weight: bold; } a:hover { color: red; } table { border-collapse: collapse; } td { border: 1px #fff solid; padding: 10px 10px 10px 10px; text-align: center; } .players { width: 250px; } .scores { width: 110px; } .dates { width: 250px; } |
ranking.js
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
window.addEventListener('load', () => { fetch('./ranking.php', { method: 'GET', }).then(async(res) => { const obj = await res.json(); const aa = createTableHTML(obj); document.getElementById('scores').innerHTML = aa.html1; document.getElementById('users').innerHTML = aa.html2; }); }); function createTableHTML(obj){ const scores = obj.scores; const users = obj.users; let html1 = ''; html1 += '<table>'; html1 += `<tr><td></td><td class = "players">Player Name</td><td class = "scores">Score</td><td class = "dates">Date</td></tr>`; for(let i = 0; i < scores.length; i++) html1 += `<tr><td>${i + 1}</td><td>${scores[i].name}</td><td>${Number(scores[i].score).toLocaleString()}</td><td>${scores[i].date}</td></tr>`; html1 += '</table>'; let html2 = ''; html2 += '<table>'; html2 += `<tr><td></td><td class = "players">Player Name</td><td class = "scores">Score</td><td class = "dates">Date</td></tr>`; for(let i = 0; i < users.length; i++) html2 += `<tr><td>${i + 1}</td><td>${users[i].name}</td><td>${Number(users[i].score).toLocaleString()}</td><td>${users[i].date}</td></tr>`; html2 += '</table>'; return {html1, html2}; } |
