釘を飛び越えて消していくパズルをつくる(1)の続きです。今回はGameクラスを実装します。
Contents
Gameクラスの定義
Gameクラスを定義します。
コンストラクタ
最初にコンストラクタを示します。ここでやっているのはCellオブジェクトとNailオブジェクトの生成、フラグ等の初期化、描画に必要なDOMオブジェクトの取得、更新処理の開始です。
|
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 |
class Game { constructor(){ this.Cells = this.InitCells(); this.Nails = this.InitNails(); this.IsPlaying = false; this.IgcoreClick = true; this.SelectedCellIndex = -1; // 移動元としてどれが選択されているか?(-1なら非選択) this.Question = ''; // 問題と答え this.Answer = []; this.Histories = []; // 元に戻せるように移動履歴を取る this.Time = 0; // 消費時間 // 釘をマスの中央に描画するために座標を設定する for(let i = 0; i < this.Cells.length; i++){ const cx = this.Cells[i].CX; const cy = this.Cells[i].CY; this.SetNailPosition(this.Nails[i], cx, cy); } // 描画処理に必要なDOM要素 this.$time = document.getElementById('time'); this.$navi = document.getElementById('navi'); this.$how_to_play_inner = document.getElementById('how-to-play-inner'); // 効果音 this.NgSound = new Audio('./sounds/ng.mp3'); this.SelectSound = new Audio('./sounds/select.mp3'); this.GameClearSound = new Audio('./sounds/clear.mp3'); this.GameOverSound = new Audio('./sounds/gameover.mp3'); this.Sounds = [this.SelectSound, this.NgSound, this.GameClearSound, this.GameOverSound]; this.HowToPlayInnerLeft = 400; // 更新処理の開始 this.PrevUpdateTime = Date.now(); this.Update(); } } |
CellオブジェクトとNailオブジェクトの生成
CellオブジェクトとNailオブジェクトを生成する処理を示します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Game { InitCells(){ const cells = []; for(let r = 0; r < 5; r++){ for(let c = 0; c <= r; c++) cells.push(new Cell(r, c)); // コンストラクタの引数でマスの中心座標が決まる(前述) } return cells; } // 釘を各色5本、全部で15本生成する InitNails(){ const nails = []; nails.length = 15; for(let i=0; i < 5; i++) nails[i] = new Nail('1'); for(let i = 5; i < 10; i++) nails[i] = new Nail('2'); for(let i = 10; i < 15; i++) nails[i] = new Nail('3'); return nails; } } |
釘の表示位置を変更する
釘の表示位置を変更する処理を示します。第一引数で渡されたNailオブジェクトの表示位置を(cx, cy)に変更します。
|
1 2 3 4 5 6 7 |
class Game { SetNailPosition(nail, cx, cy){ nail.Exist = true; nail.CX = cx; nail.CY = cy; } } |
効果音の再生
効果音を再生する処理を示します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Game { PlaySelectSound(){ this.SelectSound.currentTime = 0; this.SelectSound.play(); } PlayNgSound(){ this.NgSound.currentTime = 0; this.NgSound.play(); } PlayGameClearSound(){ this.GameClearSound.currentTime = 0; this.GameClearSound.play(); } PlayGameOverSound(){ this.GameOverSound.currentTime = 0; this.GameOverSound.play(); } } |
更新と描画
更新処理と描画処理を示します。
更新処理は描画と消費時間の計測、HowToPlayの文字列を移動をおこないます。
|
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 |
class Game { Update(){ requestAnimationFrame(() => this.Update()); // 描画処理 ctx.fillStyle = '#000' ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); for(let i = 0; i < this.Cells.length; i++) this.Cells[i].Draw(this.SelectedCellIndex == i); this.Nails.forEach(cell => cell.Draw()); // 前回の更新時刻と現在時刻の差を加算することで消費時間を計測する const now = Date.now(); const diff = now - this.PrevUpdateTime; this.PrevUpdateTime = now; // プレイ中なら差分を加算する。 if(this.IsPlaying) this.Time += diff; // 時間の表示(後述) this.ShowTime(this.Time); // HowToPlayの文字列の表示位置の変更(左に少しずつ移動させる) this.HowToPlayInnerLeft -= 1; if(this.HowToPlayInnerLeft < -2350) this.HowToPlayInnerLeft = 400; this.$how_to_play_inner.style.marginLeft = `${this.HowToPlayInnerLeft}px`; } } |
消費時間を表示する処理を示します。
|
1 2 3 4 5 6 7 8 9 10 |
class Game { ShowTime(t){ const ms = t % 1000; const s = Math.floor(t / 1000); const min = Math.floor(s / 60); const sec = s % 60; const text =`${min} : ${sec.toString().padStart(2, '0')}. ${ms.toString().padStart(3, '0')}`; this.$time.innerHTML = `Time <span style = "margin-left: 20px">${text}</span>`; } } |
ゲーム開始時の処理
ゲーム開始時におこなわれる処理を示します。
|
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 |
class Game { GameStart(){ this.SelectedCellIndex = -1; // どれも選択されていない状態にする this.Histories = []; // 移動履歴クリア this.Time = 0; this.IsPlaying = true; this.IgcoreClick = false; const gene = new QuestionGenerator(this.Cells); // 問題の生成(前述) const qa = gene.Generate(); this.Question = qa.q; this.Answer = qa.ans; this.SetNailsToCells(qa.q); // 問題として生成された文字列にあわせて釘を配置する this.ShowStartButtons(false); this.PlaySelectSound(); this.$navi.innerHTML = '最初に取り除く釘を指定してください'; } // スタートボタンの表示・非表示を切り替える ShowStartButtons(show) { document.getElementById('start-buttons').style.display = show ? 'block' : 'none'; } } |
QuestionGeneratorクラスのGenerate関数を実行すると問題となる文字列(+答え)が返されます。この文字列にあわせて釘を配置する処理を示します。
|
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 |
class Game { SetNailsToCells(text){ const chars = text.split(''); // '1','2','3'で構成される長さ15の文字列を配列に変換 // Nailオブジェクトを3色にわける(各色5本ずつ生成したのでindexをみればどの色かわかる) const red = []; const green = []; const blue = []; for(let i = 0; i < this.Cells.length; i++){ if(i <= 4) red.push(this.Nails[i]); else if(i <= 9) green.push(this.Nails[i]); else blue.push(this.Nails[i]); } // 文字を数値に変換して対応するNailを配置していく for(let i = 0; i < this.Cells.length; i++){ const num = Number(chars[i]) let nail; if(num == 1) nail = red.pop(); if(num == 2) nail = green.pop(); if(num == 3) nail = blue.pop(); this.SetNailPosition(nail, this.Cells[i].CX, this.Cells[i].CY); } } } |
釘を移動させる処理
釘を移動させる処理をするうえで必要になる関数を先に示します。
|
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 |
class Game { // 座標から対応するCellオブジェクトを取得する GetCellFromPosition(x, y){ for(let i = 0; i < this.Cells.length; i++){ const d2 = Math.pow(this.Cells[i].CX - x, 2) + Math.pow(this.Cells[i].CY - y, 2); if(Math.sqrt(d2) < CELL_RADIUS) return this.Cells[i]; } return null; } // CellオブジェクトのindexからそこにあるNailオブジェクトを取得する(すでに取り除かれているならnull) GetNailByCellIndex(cellIndex){ const cx = this.Cells[cellIndex].CX; const cy = this.Cells[cellIndex].CY; for(let i = 0; i < this.Nails.length; i++){ const nail = this.Nails[i]; if(nail.Exist){ if(Math.abs(nail.CX - cx) < 4 && Math.abs(nail.CY - cy) < 4) return nail; } } return null; } // 待機する async Sleep(ms) { await new Promise(resolve => setTimeout(resolve, ms)); } } |
釘を指定した座標に移動させる処理を示します。移動は瞬時に移動するのではなく滑るように移動させます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Game { async MoveNail(nail, tx, ty){ const sx = nail.CX; const sy = nail.CY; const dx = tx - sx; const dy = ty - sy; const cnt = 16; for(let i = 0; i < cnt; i++){ nail.CX = sx + dx / cnt * i; nail.CY = sy + dy / cnt * i; await this.Sleep(1000 / 60); } nail.CX = tx; nail.CY = ty; } } |
クリア判定の処理を示します。存在する釘が1本だけになり、その位置が最下段中央(12番マス)であればクリアです。クリアしたらスコアランキングにタイムとプレイヤー名を登録します。
サーバー側の処理とスコアランキングを表示させる処理は後述します。
|
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 |
class Game { CheckGameClear(){ if(!this.IsPlaying) return; let cnt = 0; this.Nails.forEach(nail => { if(nail.Exist) cnt++; }); const nail_12 = this.GetNailByCellIndex(12); if(cnt == 1 && nail_12 != null){ this.IsPlaying = false; this.IgcoreClick = true; this.$navi.innerHTML = 'ゲームクリア!!'; const $player_name = document.getElementById('player-name'); // JSON形式でPOST fetch('./ranking.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ time: this.Time, name: $player_name.value, }) }); this.PlayGameClearSound(); setTimeout(() => this.ShowStartButtons(true), 3000); } } } |
マスがクリックされたときの処理
マスがクリックされたときにおこなわれる処理を示します。
マスがクリックされたときはどのマスも選択されていない場合はクリックされたマスを選択状態とします。すでに選択されている場合は選択されている位置にある釘をクリックされた位置に移動させるとともに飛び越えられた釘を消去します。そしてこれを移動履歴に追加します。
もし適切ではない動作(釘がないマスを移動元として選択しようとした、釘があるマスを移動先として指定しようとした、移動する釘と飛び越えられる釘が色違いになっていない、など)がおこなわれようとしたら警告音をならすとともに、なぜその操作は不可なのか理由を表示させます。
|
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 |
class Game { async OnCanvasClick(x, y){ if(!this.IsPlaying || this.IgcoreClick){ this.PlayNgSound(); return; } const cell = this.GetCellFromPosition(x, y); if(cell == null){ this.PlayNgSound(); this.SelectedCellIndex = -1; if(this.Histories.length > 0) this.$navi.innerHTML = '<span class = "aqua">移動元</span> を指定してください'; return; } this.IgcoreClick = true; // 処理が完了するまではクリックしてもなにもしない if(this.SelectedCellIndex == -1){ if(this.Histories.length == 0){ const deleted = this.GetNailByCellIndex(cell.Index); deleted.Exist = false; this.Histories.push(new MoveHistory(null, -1, deleted, cell.Index)); this.$navi.innerHTML = '<span class = "aqua">移動元</span> を指定してください'; this.PlaySelectSound(); } else { if(this.GetNailByCellIndex(cell.Index) != null){ this.SelectedCellIndex = cell.Index; this.$navi.innerHTML = '<span class = "red">移動先</span> を指定してください'; this.PlaySelectSound(); } else { this.$navi.innerHTML = '釘がない場所を移動元として指定することはできません'; setTimeout(() => { if(this.SelectedCellIndex == -1 && this.Histories.length > 0) this.$navi.innerHTML = '<span class = "aqua">移動元</span> を指定してください'; }, 1500); this.PlayNgSound(); } } } else { const selectedIndex = this.SelectedCellIndex; this.SelectedCellIndex = -1; const selectedNail = this.GetNailByCellIndex(selectedIndex); const pairs = this.Cells[selectedIndex].MovablePairs; let done = false; let mes = ''; for(let i =0; i < pairs.length; i++){ if(pairs[i][1] == cell.Index){ const nextNail = this.GetNailByCellIndex(pairs[i][0]); const nextNextNail = this.GetNailByCellIndex(pairs[i][1]); if(nextNextNail == null && nextNail != null && nextNail.Color != selectedNail?.Color){ this.Histories.push(new MoveHistory(selectedNail, selectedIndex, nextNail, pairs[i][0])); done = true; this.PlaySelectSound(); const tx = this.Cells[pairs[i][1]].CX; const ty = this.Cells[pairs[i][1]].CY; await this.MoveNail(selectedNail, tx, ty) nextNail.Exist = false; this.$navi.innerHTML = '<span class = "aqua">移動元</span> を指定してください'; this.CheckGameClear(); } else { if(nextNextNail != null) mes = '移動先はなにもない場所を選んでください'; else if(nextNail == null || nextNail.Color == selectedNail?.Color) mes = '移動元と移動先の間には <span class = "red">色違いの釘</span> がなければなりません'; } break; } } if(!done){ if(mes == '') mes = '移動先は選択されている釘の <span class = "red">隣の隣</span> でなければなりません'; this.$navi.innerHTML = mes; this.PlayNgSound(); setTimeout(() => { if(this.SelectedCellIndex == -1 && this.Histories.length > 0) this.$navi.innerHTML = '<span class = "aqua">移動元</span> を指定してください'; }, 1500); } } this.IgcoreClick = false; } } |
ひとつ前に戻す処理
ひとつ前の状態に戻す処理を示します。
移動元が選択されている状態であれば非選択の状態に戻します。非選択であれば移動履歴が保存されている配列からpopされたデータから前の状態に戻します。
|
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 |
class Game { GoBack(){ if(this.IgcoreClick || !this.IsPlaying || this.Histories.length == 0){ this.PlayNgSound(); return; } if(this.SelectedCellIndex == -1){ // 移動元が選択されていない状態 -> ひとつ前の状態に戻す const his = this.Histories.pop(); // 移動させた釘を元の位置に戻し、取り除かされた釘を復活させる if(his.Nail != null){ his.Nail.CX = this.Cells[his.OldCellIndex].CX; his.Nail.CY = this.Cells[his.OldCellIndex].CY; } his.DeletedNail.CX = this.Cells[his.DeletedOldCellIndex].CX; his.DeletedNail.CY = this.Cells[his.DeletedOldCellIndex].CY; his.DeletedNail.Exist = true; if(this.Histories.length == 0) this.$navi.innerHTML = '最初に取り除く釘を指定してください'; } else { // 移動元が選択されている状態 -> 非選択の状態に戻す this.SelectedCellIndex = -1; this.$navi.innerHTML = '<span class = "aqua">移動元</span> を指定してください'; } this.PlaySelectSound(); } } |
ギブアップ時の処理
ギブアップ時の処理を示します。
問題が生成されたときに答えをGame.Answerに保存しているので、これを元に解を表示したあとゲームオーバー処理をおこないます。
|
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 |
class Game { async GiveUp(){ if(this.IgcoreClick || !this.IsPlaying){ this.PlayNgSound(); return; } this.PlaySelectSound(); this.$navi.innerHTML = '答えを表示しています'; this.IsPlaying = false; this.IgcoreClick = true; this.SelectedCellIndex = -1; this.Histories = []; this.SetNailsToCells(this.Question); const ans = this.Answer; for(let i = 0; i< ans.length; i++){ await this.Sleep(500); if(i == 0){ console.log(ans[i]) this.GetNailByCellIndex(ans[i].deleted).Exist = false; } else { const nail = this.GetNailByCellIndex(ans[i].from); this.MoveNail() const tx = this.Cells[ans[i].to].CX; const ty = this.Cells[ans[i].to].CY; await this.MoveNail(nail, tx, ty); this.GetNailByCellIndex(ans[i].deleted).Exist = false; } } this.PlayGameOverSound(); setTimeout(() => this.ShowStartButtons(true), 3000); } } |
ページが読み込まれたときの処理
ページが読み込まれたときの処理を示します。
Gameオブジェクトを生成してイベントリスナを追加します。
|
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 |
window.addEventListener('load', () => { // テキストボックスにかつて入力したプレイヤー名を自動で設定する処理 const $player_name = document.getElementById('player-name'); const savedName = localStorage.getItem('hatodemowakaru-player-name'); if(savedName && $player_name != null) $player_name.value = savedName; $player_name?.addEventListener('change', () => { localStorage.setItem('hatodemowakaru-player-name', $player_name.value ); }); const game = new Game(); // イベントリスナの追加 document.getElementById('start')?.addEventListener('click', () => game.GameStart()); $canvas.addEventListener('click', async(ev) => { const x = ev.clientX - $canvas.getBoundingClientRect().x; const y = ev.clientY - $canvas.getBoundingClientRect().y; game.OnCanvasClick(x, y); // 引数はcanvas上の座標 }); document.getElementById('go-back')?.addEventListener('click', () => game.GoBack()); document.getElementById('give-up')?.addEventListener('click', () => game.GiveUp()); initVolume('volume', game.Sounds); // 定番の処理 }); |
レンジスライダーでボリューム調整をするための処理を示します。
|
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 |
function initVolume(elementId, sounds){ let volume = 0.3; const savedVolume = localStorage.getItem('hatodemowakaru-volume'); if(savedVolume) volume = Number(savedVolume); const $element = document.getElementById(elementId); const $div = document.createElement('div'); const $span1 = document.createElement('span'); $span1.innerHTML = '音量'; $div.appendChild($span1); const $range = document.createElement('input'); $range.type = 'range'; $div.appendChild($range); const $span2 = document.createElement('span'); $div.appendChild($span2); $range.addEventListener('input', () => { const value = $range.value; $span2.innerText = value; volume = Number(value) / 100; setVolume(); }); $range.addEventListener('change', () => localStorage.setItem('hatodemowakaru-volume', volume.toString())); setVolume(); $span2.innerText = Math.round(volume * 100).toString(); $span2.style.marginLeft = '16px'; $range.value = Math.round(volume * 100).toString(); $range.style.width = '230px'; $range.style.verticalAlign = 'middle'; $element.appendChild($div); const $button = document.createElement('button'); $button.innerHTML = '音量テスト'; $button.style.width = '120px'; $button.style.height = '45px'; $button.style.marginTop = '12px'; $button.style.marginLeft = '32px'; $button.addEventListener('click', () => { sounds[0].currentTime = 0; sounds[0].play(); }); $element.appendChild($button); function setVolume(){ for(let i = 0; i < sounds.length; i++) sounds[i].volume = volume; } } |
スコアランキングの表示
ゲームクリア時の処理でjsonがサーバー側に送信されますが、サーバー側でおこなわれる処理を示します。
サーバー側の処理
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 104 |
<?php if($_SERVER["REQUEST_METHOD"] == "POST") saveData(); if($_SERVER["REQUEST_METHOD"] == "GET") echo getData(); function GetFileName(){ return "../nail-highscore.json"; } function getData(){ $file_path = GetFileName(); if(file_exists($file_path)) return file_get_contents($file_path); //JSON形式 } function saveData(){ // 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['time'])) return; $name = $contents['name']; $time = $contents['time']; $name = htmlspecialchars($name, ENT_QUOTES); $now = date('Y-m-d H:i:s'); if($name == '' || mb_strlen($name) > 32 || !is_numeric($time)) return; $time = (int)$time; $file_path = GetFileName(); if(file_exists($file_path)){ $ranking = json_decode(file_get_contents($file_path), true); //JSON形式を元に戻す } else { $ranking = array(); $ranking = [ 'times' => array(), // 時間のランキングとユーザーランキングの2つをつくる 'users' => array(), ]; } $data = [ 'name' => $name, 'time' => $time, 'date' => $now, ]; $ranking['times'][] = $data; $ranking['times'] = sortByTime($ranking['times']); // ユーザーランキングは同じユーザーが登録されていた場合、 // 送信されたデータのほうが成績がよい場合は上書き更新、そうでないならなにもしない $done = false; foreach ($ranking['users'] as &$value) { if($value['name'] == $name){ if($value['time'] > $time){ $value['time'] = $time; $value['date'] = $now; } $done = true; break; } } unset($value); // 最後の要素への参照を解除 if(!$done) $ranking['users'][] = $data; $ranking['users'] = sortByTime($ranking['users']); $times_max_count = 20; $users_max_count = 100; $ranking['times'] = array_slice($ranking['times'], 0, $times_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); } // 各キーを基準にソートできるように、対応する値の配列を作成 SORT_ASC function sortByTime($array) { $times_array = createArrayForSort('time', $array); array_multisort($times_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部分
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>釘を飛び越して消していくパズル ランキング</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel="stylesheet" href="./ranking.css"> </head> <body> <div id = "container"> <h1>釘を飛び越して消していくパズル ランキング</h1> <p><a href="./">ゲームのページへ</a></p> <h2>速解きランキング</h2> <div id = "times"></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 44 45 46 47 48 49 50 51 52 53 |
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; } .times { 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
window.addEventListener('load', () => { fetch('./ranking.php', { method: 'GET', }).then(async(res) => { const obj = await res.json(); const htmls = createTableHTML(obj); document.getElementById('times').innerHTML = htmls.html1; document.getElementById('users').innerHTML = htmls.html2; }); }); function createTableHTML(obj){ const times = obj.times; const users = obj.users; let html1 = ''; html1 += '<table>'; html1 += `<tr><td>Rank</td><td class = "players">Player Name</td><td class = "times">Time</td><td class = "dates">Date</td></tr>`; for(let i = 0; i < times.length; i++){ const data = times[i]; let time = Number(data.time); time = Math.round(time / 1000); const sec = time % 60; const min = Math.floor(time / 60); const text = `${min} 分 ${sec} 秒`; html1 += `<tr><td>${i + 1}</td><td>${data.name}</td><td>${text}</td><td>${data.date}</td></tr>`; } html1 += '</table>'; let html2 = ''; html2 += '<table>'; html2 += `<tr><td>Rank</td><td class = "players">Player Name</td><td class = "times">Time</td><td class = "dates">Date</td></tr>`; for(let i = 0; i < users.length; i++){ const data = users[i]; let time = Number(data.time); time = Math.round(time / 1000); const sec = time % 60; const min = Math.floor(time / 60); const text = `${min} 分 ${sec} 秒`; html2 += `<tr><td>${i + 1}</td><td>${data.name}</td><td>${text}</td><td>${data.date}</td></tr>`; } html2 += '</table>'; return {html1, html2}; } |
