STEP06.ゲーム結果を表示する
前回はゲームの開始と終了の処理を作りました。 今回は終了処理を行うときに、ゲームの結果を表示するようにしましょう。
今回の記事で作成する最終的なコードは次のリンクから確認してください。
結果表示用の関数を作る
最初に結果表示用の関数の枠組みだけを作成しておきます。
呼び出す場所は、修了処理の中からです。 プレイヤーの状態を初期化すると結果を計算できないので、タイトル表示と初期化の間に追加します。
function exitGame() {
isPlaying = false;
overworld.runCommand(`title @a title Game Over!`);
// 結果を表示する(記述する場所に注意)
showResult();
for (const player of overworld.getPlayers()) {
initializePlayer(player);
}
}
function showResult() {
// ここに結果表示の処理を書いていく
}
全員の得点を表示する
最終結果の表示方法は色々と工夫できそうですが、まずは簡単に全プレイヤーの結果だけを表示してみましょう。
各プレイヤーのアクションバーに表示していた内容を、名前を加えて/say
コマンドでチャット画面に表示します。
function showResult() {
overworld.runCommand(`say --- 結果表示 ---`);
// プレイヤーごとに繰り返し
for (const player of overworld.getPlayers()) {
const height = getHeightProperty(player);
const score = height.max - height.min;
const text = `${score}pt (${height.min} ~ ${height.max})`;
// /sayでチャット画面に表示
overworld.runCommand(`say ${player.nameTag}: ${text}`);
}
overworld.runCommand(`say --------------`);
}
順位をつけて表示したい
得点を表示をするだけなら、上の処理だけでも構いません。 しかしゲームである以上、勝者や順位を適切に表示してあげるべきでしょう。
ここからの処理は少し難しくなります。 わからなくなったら、最終結果をコピペして次に進んでください。
最初に全員の得点を計算する
今回は単純に得点順にプレイヤーを並べ替えて、その並び順を、そのまま順位とします。
順位は厳密に判定しようとすると、同じ点数などを考えないといけません。 この処理はずっと複雑な処理になってしまうので、今回は簡単な処理で済ませます。
先に全体の処理を載せておきます。
function getScore(max: number, min: number) {
return max - min;
}
function getScoreText(max: number, min: number) {
const score = getScore(max, min);
return `${score}pt (${min} ~ ${max})`;
}
function getResult(playerIterator: mc.PlayerIterator) {
// イテレーターをスプレッド構文を使って配列化する(次のように.map()と.sort()を使いたい)
const players = [...playerIterator];
// 結果をJSON構造で返す
const result = players.map((player) => {
const name = player.nameTag;
const height = getHeightProperty(player);
const score = getScore(height.max, height.min);
const text = getScoreText(height.max, height.min);
return { name, score, text };
});
// テスト用に結果情報を追加
result.push({
name: 'Steve',
score: getScore(60, 57),
text: getScoreText(60, 57),
});
result.push({
name: 'Alex',
score: getScore(80, 62),
text: getScoreText(80, 62),
});
return result
.sort((a, b) => {
// スコアで並び替えて順位を決める
return b.score - a.score;
})
.map((obj, index) => {
// スコアが高い順に並び変えているので、indexを+1した値がそのまま順位になる(indexは0から)
const ranking = index + 1;
return { ranking, ...obj };
});
}
function showResult() {
const players = overworld.getPlayers();
const result = getResult(players);
try {
overworld.runCommand(`say --- 結果表示 ---`);
result.forEach((obj) => {
overworld.runCommand(`say ${obj.ranking}. ${obj.name} ${obj.text}`);
});
overworld.runCommand(`say --------------`);
} catch (error) {
overworld.runCommand(`say ${error}`);
}
}
最終的に、showResult()
は次のように、大きく3つに分かれます。
- 計算対象のプレイヤーを決定する
- そのプレイヤーの結果を取得する
- 取得した結果を順番に表示する
function showResult() {
// 1. 計算対象のプレイヤーを決定する
const players = overworld.getPlayers();
// 2. そのプレイヤーの結果を取得する
const result = getResult(players);
// 3. 取得した結果を順番に表示する
try {
overworld.runCommand(`say --- 結果表示 ---`);
result.forEach((obj) => {
overworld.runCommand(`say ${obj.ranking}. ${obj.name} ${obj.text}`);
});
overworld.runCommand(`say --------------`);
} catch (error) {
overworld.runCommand(`say ${error}`);
}
}
計算対象のプレイヤーを決定する
今回の計算対象はプレイヤー全員です。
今後開発が進むと、ゲームに参加している人だけに絞り込んだりする必要があります。 そのため、計算対象は次の結果計算関数から分離しておきます。
const players = overworld.getPlayers();
そのプレイヤーの結果を取得する
結果計算はgetResult()
として専用の関数にまとめます。
function getResult(playerIterator: mc.PlayerIterator) {
// イテレーターをスプレッド構文を使って配列化する(次のように.map()と.sort()を使いたい)
const players = [...playerIterator];
// 結果をJSON構造で返す
const result = players.map((player) => {
const name = player.nameTag;
const height = getHeightProperty(player);
const score = getScore(height.max, height.min);
const text = getScoreText(height.max, height.min);
return { name, score, text };
});
return result
.sort((a, b) => {
// スコアで並び替えて順位を決める
return b.score - a.score;
})
.map((obj, index) => {
// スコアが高い順に並び変えているので、indexを+1した値がそのまま順位になる(indexは0から)
const ranking = index + 1;
return { ranking, ...obj };
});
}
overworld.getPlayers()
で取得されるデータはイテレーター という少し特殊な形式になっているので注意してください。
イテレーターは次のようにスプレッド構文 を使って、簡単に配列に変換できるので、今の段階では正確に理解する必要はありません。
const players = [...playerIterator];
配列に変換したらmap()
を使って、順番に処理します。
map()
は配列の内容ごとに繰り返し、それぞれの戻り値で新しい配列を作ります。
新しい配列には、後の結果表示処理するときの内容を格納しておきます。
const result = players.map((player) => {
const name = player.nameTag;
const height = getHeightProperty(player);
const score = getScore(height.max, height.min);
const text = getScoreText(height.max, height.min);
return { name, score, text };
});
処理内容は、次のコードとまったく同じですが、この場合はmap()
を使った方が少し綺麗に書けます。
// map()をfor文で書き直した場合
const result = [];
for (const player of players) {
const name = player.nameTag;
const height = getHeightProperty(player);
const score = getScore(height.max, height.min);
const text = getScoreText(height.max, height.min);
result.push({ name, score, text });
};
map()
は頻繁に出現する処理方法なので、少しずつ慣れていってください。
細かい違いを言うとmap()
は「式」で、for文は「文」という違いがあります。
そのため、すべての場合で、相互に置き換えられるわけではありません。
また、最後の戻り値のオブジェクトは省略表記になっています。
// 本来のオブジェクトの記入の仕方
{
name: name,
score: score,
text: text,
}
// キーと変数が同じ場合には、記述を省略できる
{
name,
score,
text,
}
// 1行に直した場合
{ name, score, text }
テスト用のデータを追加する
開発環境では基本的に自分1人だけなので、順位として並べ替える対象が存在しません。 これではテストができないので、結果配列にデータをpush() で追加しておきます。
// テスト用に結果情報を追加
result.push({
name: 'Steve',
score: getScore(60, 57),
text: getScoreText(60, 57),
});
result.push({
name: 'Alex',
score: getScore(80, 62),
text: getScoreText(80, 62),
});
データを並べ替える
配列の並べ替えは、次にsort()
を使います。
今回の配列内容はオブジェクトなので、どのように並べ替えるのかを明示するアロー関数を書きます。 この部分は少し難しいので、とりあえず次のように書いておいてください。 これで得点が高い方から順に並びます。(降順)
result.sort((a, b) => {
// スコアで並び替えて順位を決める
return b.score - a.score;
})
続けて、再びmap()
を使い、並び替えた後の配列内オブジェクトに順位を追加します。
result.map((obj, index) => {
// スコアが高い順に並び変えているので、indexを+1した値がそのまま順位になる(indexは0から)
const ranking = index + 1;
return { ranking, ...obj };
});
上の二つのメソッドを連続で実行し、return
で返すまでの処理を一気に書くと、次のようになります。
return result
.sort((a, b) => {
return b.score - a.score;
})
.map((obj, index) => {
const ranking = index + 1;
return { ranking, ...obj };
});
ここは改行で少し伝わりづらいですが、result.sort().map()
という形で、メソッドが繋がっています。
このようにメソッドを繋げる方法をメソッドチェーン と呼びます。
このメソッドチェーンを使う場合はmap()
部分をfor文におきかえられません。
結果を表示する
showResult()
に戻って、結果を表示する処理を行います。
表示する内容は、結果計算関数内で終えているので、ここでは単純な繰り返しから、その内容を呼び出すだけです。
予期しない値でエラーになった場合に備えて、try...catch
文で囲んでおきます。
try {
overworld.runCommand(`say --- 結果表示 ---`);
result.forEach((obj) => {
overworld.runCommand(`say ${obj.ranking}. ${obj.name} ${obj.text}`);
});
overworld.runCommand(`say --------------`);
} catch (error) {
overworld.runCommand(`say ${error}`);
}
ここの処理はとても簡潔になっていますが、これを最初から書くことは難しいです。 実際は、どのように前処理を行なっておけば、ここでのコードが綺麗になるかを考えて、何度も修正を繰り返しています。