20. 多重ループ
このレッスンでは、多重ループとその解消方法について学びます。
生きているキャラクターを選択する
現在のコードでは、攻撃対象がenemyParty[0]やfriendParty[0]に固定されています。そのため、0番目のキャラクターが倒れても攻撃し続けてしまい、戦闘が終わりません。
<!-- battle.html hidden -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>バトル</title>
<link rel="stylesheet" href="css/battle.css">
<script src="js/battle.js" defer></script>
</head>
<body>
<table id="status">
<tr>
<th>ゆうしゃ</th><th>せんし</th><th>そうりょ</th><th>まほうつかい</th>
</tr>
<tr><td>HP 153</td><td>HP 198</td><td>HP 101</td><td>HP 77</td></tr>
<tr><td>MP 25</td><td>MP 0</td><td>MP 35</td><td>MP 58</td></tr>
</table>
<div id="monster">
<img class="dark-knight" src="img/dark-knight.png">
<img class="dark-lord" src="img/dark-lord.png">
<img class="demon-priest" src="img/demon-priest.png">
</div>
<div id="message">
まおうがあらわれた。
</div>
</body>
</html>
/* battle.css hidden */
body {
background-color: rgb(34, 34, 34);
color: white;
font-family: sans-serif;
}
table#status {
border: solid 2px white;
border-collapse: collapse;
margin: 10px auto 0 auto;
width: 640px;
}
table#status tr:first-child {
border-bottom: solid 1px white;
}
table#status td {
text-align: center;
}
#monster {
text-align: center;
margin-top: 40px;
}
#monster .dark-lord {
width: 400px;
}
#monster .dark-knight {
width: 150px;
}
#monster .demon-priest {
width: 150px;
}
#message {
border: solid 2px white;
border-radius: 4px;
padding: 10px;
width: 720px;
margin: 30px auto;
}
// battle.js selection:66-87 highlight:72-73,77-79,82-84
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[0])) {
displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[0].defense);
enemyParty[0].hp = calculateHp(enemyParty[0].hp, damage);
displayDamageMessage(enemyParty[0].name, damage);
await sleep();
if (!isAlive(enemyParty[0])) {
document.querySelector("#monster img:nth-child(1)").style.visibility = "hidden";
displayDeadMessage(enemyParty[0].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[0])) {
displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[0].defense);
friendParty[0].hp = calculateHp(friendParty[0].hp, damage);
displayDamageMessage(friendParty[0].name, damage);
await sleep();
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${friendParty[0].hp}`;
if (!isAlive(friendParty[0])) {
displayDeadMessage(friendParty[0].name, true);
await sleep();
}
}
}
}
main();
生きているキャラクターを攻撃するように修正しましょう。まず、生きている敵キャラクターを探して、そのインデックスを変数target(ターゲット)に代入します。
// battle.js selection:71-77
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
let target = 0;
for (let i = 0; i < enemyParty.length; i++) {
if (isAlive(enemyParty[i])) {
target = i;
break;
}
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[0])) {
displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[0].defense);
enemyParty[0].hp = calculateHp(enemyParty[0].hp, damage);
displayDamageMessage(enemyParty[0].name, damage);
await sleep();
if (!isAlive(enemyParty[0])) {
document.querySelector("#monster img:nth-child(1)").style.visibility = "hidden";
displayDeadMessage(enemyParty[0].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[0])) {
displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[0].defense);
friendParty[0].hp = calculateHp(friendParty[0].hp, damage);
displayDamageMessage(friendParty[0].name, damage);
await sleep();
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${friendParty[0].hp}`;
if (!isAlive(friendParty[0])) {
displayDeadMessage(friendParty[0].name, true);
await sleep();
}
}
}
}
main();
このtargetを使って、これまでenemyParty[0]としていた箇所をenemyParty[target]に書き換えれば、選択された敵キャラクターを攻撃するようになります。
// battle.js selection:79-95 highlight:81-82,86-88,91-93
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
let target = 0;
for (let i = 0; i < enemyParty.length; i++) {
if (isAlive(enemyParty[i])) {
target = i;
break;
}
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[0])) {
displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[0].defense);
friendParty[0].hp = calculateHp(friendParty[0].hp, damage);
displayDamageMessage(friendParty[0].name, damage);
await sleep();
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${friendParty[0].hp}`;
if (!isAlive(friendParty[0])) {
displayDeadMessage(friendParty[0].name, true);
await sleep();
}
}
}
}
main();
敵の画像を非表示にする処理では、CSSセレクタのnth-childは1から始まるので、target + 1としています。
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
敵の攻撃コードを修正する
味方の攻撃コードと同様に、敵の攻撃コードも修正してみてください。friendPartyの中から生きているキャラクターを探し、攻撃対象にします。
// battle.js highlight:103-109,113-114,118-120,122,124-125 folded
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
let target = 0;
for (let i = 0; i < enemyParty.length; i++) {
if (isAlive(enemyParty[i])) {
target = i;
break;
}
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
let target = 0;
for (let i = 0; i < friendParty.length; i++) {
if (isAlive(friendParty[i])) {
target = i;
break;
}
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
document.querySelector(`#status tr:nth-child(2) td:nth-child(${target + 1})`).textContent = `HP ${friendParty[target].hp}`;
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
多重ループの問題
コードは正しく動作するようになりましたが、構造を見てみると、ループの中にループがあり、さらにその中にループがある状態になっています。
while (true) { // 1段目: ターン全体のループ
for (const character of friendParty) { // 2段目: 味方全員の攻撃ループ
// ...
for (let i = 0; i < enemyParty.length; i++) { // 3段目: 攻撃対象を探すループ
// ...
}
// ...
}
// ...
}
このように、ループの中にループがある構造を多重ループと呼びます。
多重ループ自体は必ずしも悪いものではありません。しかし、ネストが深くなるとコードが読みにくくなり、何をしているのかを把握しづらくなります。
このような場合、内側のループを関数に切り出すことで、コードを読みやすくすることができます。
selectTarget関数
攻撃対象を探す処理をselectTarget関数として切り出しましょう。
// battle.js selection:45-52
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
let target = 0;
for (let i = 0; i < enemyParty.length; i++) {
if (isAlive(enemyParty[i])) {
target = i;
break;
}
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
let target = 0;
for (let i = 0; i < friendParty.length; i++) {
if (isAlive(friendParty[i])) {
target = i;
break;
}
}
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
document.querySelector(`#status tr:nth-child(2) td:nth-child(${target + 1})`).textContent = `HP ${friendParty[target].hp}`;
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
この関数は、引数で受け取ったpartyの中から生きているキャラクターを探し、見つかったらそのインデックスを返します。見つからなかった場合は0を返します。
コードに適用する
selectTarget関数を使ってコードを書き換えてみましょう。
// battle.js selection:74-122 highlight:79,103
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
document.querySelector(`#status tr:nth-child(2) td:nth-child(${target + 1})`).textContent = `HP ${friendParty[target].hp}`;
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
3重ループが2重ループに戻り、コードが読みやすくなりました。
HP表現の更新
レッスン13で関数について学んだとき、次のように説明しました。
HPを画面に反映する処理も同じように関数にしたいところですが、HPの表示蘭は「ゆうしゃ」の他に「せんし」「そうりょ」「まほうつかい」もあり、誰のHPを更新するかを指定する必要があります。他のパーティメンバーのHPも扱うようになったら、誰のHPを画面に反映するかを指定できる関数を作ることにしましょう。
今こそ、この関数を作るときです。パーティメンバーのインデックスを使って、画面上のHP表現を更新するdisplayHp関数を作りましょう。
味方のHP更新
現在のコードでは、味方のキャラクターが攻撃を受けた後にHP表示を更新しています。
// battle.js selection:115-115
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
document.querySelector(`#status tr:nth-child(2) td:nth-child(${target + 1})`).textContent = `HP ${friendParty[target].hp}`;
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
これを関数にします。
// battle.js selection:45-48
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function displayHp(party, index) {
const selector = `#status tr:nth-child(2) td:nth-child(${index + 1})`;
document.querySelector(selector).textContent = `HP ${party[index].hp}`;
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
document.querySelector(`#status tr:nth-child(2) td:nth-child(${target + 1})`).textContent = `HP ${friendParty[target].hp}`;
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
指定されたインデックスのキャラクターのHPを画面に反映します。
敵のHP更新
敵の場合は、HPの数値を表示する代わりに、倒れたら画像を非表示にしています。
// battle.js selection:98-98
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function displayHp(party, index) {
const selector = `#status tr:nth-child(2) td:nth-child(${index + 1})`;
document.querySelector(selector).textContent = `HP ${party[index].hp}`;
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
document.querySelector(`#status tr:nth-child(2) td:nth-child(${target + 1})`).textContent = `HP ${friendParty[target].hp}`;
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
これも一種のHPの表現です。同じ関数に統合しましょう。
引数にisFriendを追加し、trueのとき(味方の場合)はHPを更新、falseのとき(敵の場合)は倒されたら画像を非表示にします。
// battle.js selection:45-57
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function displayHp(party, index, isFriend) {
if (isFriend) {
const selector = `#status tr:nth-child(2) td:nth-child(${index + 1})`;
document.querySelector(selector).textContent = `HP ${party[index].hp}`;
} else {
const selector = `#monster img:nth-child(${index + 1})`;
if (isAlive(party[index])) {
document.querySelector(selector).style.visibility = "visible";
} else {
document.querySelector(selector).style.visibility = "hidden";
}
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
if (!isAlive(enemyParty[target])) {
document.querySelector(`#monster img:nth-child(${target + 1})`).style.visibility = "hidden";
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
document.querySelector(`#status tr:nth-child(2) td:nth-child(${target + 1})`).textContent = `HP ${friendParty[target].hp}`;
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
敵の場合は、isAliveで生存判定をして、生きている場合は画像を表示し、死んでいる場合は非表示にします。
コードに適用する
displayHp関数を使ってコードを書き換えてみましょう。
// battle.js selection:88-136 highlight:105,129
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function displayHp(party, index, isFriend) {
if (isFriend) {
const selector = `#status tr:nth-child(2) td:nth-child(${index + 1})`;
document.querySelector(selector).textContent = `HP ${party[index].hp}`;
} else {
const selector = `#monster img:nth-child(${index + 1})`;
if (isAlive(party[index])) {
document.querySelector(selector).style.visibility = "visible";
} else {
document.querySelector(selector).style.visibility = "hidden";
}
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
displayHp(enemyParty, target, false);
if (!isAlive(enemyParty[target])) {
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
displayHp(friendParty, target, true);
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
味方への攻撃では第3引数にtrueを、敵への攻撃ではfalseを渡しています。
// 敵のHP表現を更新(画像の非表示)
displayHp(enemyParty, target, false);
// 味方のHP表現を更新(HP数値の表示)
displayHp(friendParty, target, true);
これまで、敵の画像の非表示化はif (!isAlive(enemyParty[target]))の{ }の中で行ってきました。しかし、displayHpの呼び出しはそのif文の前に行っていることに注目してください。
displayHpは内部でisAliveを判定するので、if (!isAlive(enemyParty[target]))の外側で呼び出しても問題ありません。これで、味方の攻撃と敵の攻撃で同じタイミングでHP表示を更新するようになりました。
戦闘の終了判定
ここまでのコードには終了条件がなく、while (true)による無限ループになっています。敵を全員倒しても、味方が全滅しても、戦闘がずっと続いてしまいます。
戦闘を終了させるには、「敵が全滅したか」「味方が全滅したか」を判定する必要があります。
isWipedOut関数
パーティが全滅したかどうかを判定するisWipedOut関数を作りましょう。
// selection:68-75
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function displayHp(party, index, isFriend) {
if (isFriend) {
const selector = `#status tr:nth-child(2) td:nth-child(${index + 1})`;
document.querySelector(selector).textContent = `HP ${party[index].hp}`;
} else {
const selector = `#monster img:nth-child(${index + 1})`;
if (isAlive(party[index])) {
document.querySelector(selector).style.visibility = "visible";
} else {
document.querySelector(selector).style.visibility = "hidden";
}
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
function isWipedOut(party) {
for (const character of party) {
if (isAlive(character)) {
return false;
}
}
return true;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
displayHp(enemyParty, target, false);
if (!isAlive(enemyParty[target])) {
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
displayHp(friendParty, target, true);
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
}
}
}
main();
この関数は、パーティの中に1人でも生きているキャラクターがいればfalseを返し、全員が倒れていればtrueを返します。
ラベル付きbreak
isWipedOut関数を使って戦闘を終了させたいのですが、ここで問題があります。
今のコードはwhileループの中にforループがあります。forループの中で敵を倒したとき、breakを使うとforループだけを抜け、外側のwhileループは続いてしまいます。
while (true) { // ← ここを抜けたい
for (const character of friendParty) {
// ...
if (isWipedOut(enemyParty)) {
break; // ← for...ofループしか抜けない
}
}
// ...
}
この問題を解決するために、JavaScriptにはラベル付きbreakという構文があります。
ループにラベルを付けると、break ラベル名;でそのラベルのループを直接抜けることができます。
// highlight:1,5
battle: while (true) {
for (const character of friendParty) {
// ...
if (isWipedOut(enemyParty)) {
break battle; // ← whileループを抜ける
}
}
// ...
}
battle:というラベルをwhileループに付け、break battle;で直接whileループを抜けています。
コードに適用する
isWipedOut関数とラベル付きbreakを使って、戦闘を終了できるようにしましょう。
// battle.js selection:97-151 highlight:97,120-122,147-149
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function displayHp(party, index, isFriend) {
if (isFriend) {
const selector = `#status tr:nth-child(2) td:nth-child(${index + 1})`;
document.querySelector(selector).textContent = `HP ${party[index].hp}`;
} else {
const selector = `#monster img:nth-child(${index + 1})`;
if (isAlive(party[index])) {
document.querySelector(selector).style.visibility = "visible";
} else {
document.querySelector(selector).style.visibility = "hidden";
}
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
function isWipedOut(party) {
for (const character of party) {
if (isAlive(character)) {
return false;
}
}
return true;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
battle: while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
displayHp(enemyParty, target, false);
if (!isAlive(enemyParty[target])) {
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
if (isWipedOut(enemyParty)) {
break battle;
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
displayHp(friendParty, target, true);
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
if (isWipedOut(friendParty)) {
break battle;
}
}
}
}
main();
displayDeadMessageのif文の直後に、パーティが全滅したかを判定するif文を追加しています。全滅していたらbreak battle;でwhileループを抜けて戦闘を終了します。
プレビューで確認すると、敵または味方が全滅したときに戦闘が終了するようになりました。
試してみよう
enemyPartyに敵を4体追加して、全員を倒せるか確認してみましょう- 味方のHPが全員分更新されることを確認してみましょう
これまでの成果
<!-- battle.html folded -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>バトル</title>
<link rel="stylesheet" href="css/battle.css">
<script src="js/battle.js" defer></script>
</head>
<body>
<table id="status">
<tr>
<th>ゆうしゃ</th><th>せんし</th><th>そうりょ</th><th>まほうつかい</th>
</tr>
<tr><td>HP 153</td><td>HP 198</td><td>HP 101</td><td>HP 77</td></tr>
<tr><td>MP 25</td><td>MP 0</td><td>MP 35</td><td>MP 58</td></tr>
</table>
<div id="monster">
<img class="dark-knight" src="img/dark-knight.png">
<img class="dark-lord" src="img/dark-lord.png">
<img class="demon-priest" src="img/demon-priest.png">
</div>
<div id="message">
まおうがあらわれた。
</div>
</body>
</html>
/* battle.css folded */
body {
background-color: rgb(34, 34, 34);
color: white;
font-family: sans-serif;
}
table#status {
border: solid 2px white;
border-collapse: collapse;
margin: 10px auto 0 auto;
width: 640px;
}
table#status tr:first-child {
border-bottom: solid 1px white;
}
table#status td {
text-align: center;
}
#monster {
text-align: center;
margin-top: 40px;
}
#monster .dark-lord {
width: 400px;
}
#monster .dark-knight {
width: 150px;
}
#monster .demon-priest {
width: 150px;
}
#message {
border: solid 2px white;
border-radius: 4px;
padding: 10px;
width: 720px;
margin: 30px auto;
}
// battle.js
function sleep() {
return new Promise(resolve => setTimeout(resolve, 1000));
}
function calculateDamage(attack, defense) {
let damage = Math.floor((attack - defense) / 2);
if (damage < 0) {
damage = 0;
}
return damage;
}
function calculateHp(hp, damage) {
hp = hp - damage;
if (hp < 0) {
hp = 0;
}
return hp;
}
function displayMessage(message) {
document.getElementById("message").textContent = message;
}
function displayDamageMessage(name, damage) {
if (damage === 0) {
displayMessage(`${name}にダメージをあたえられない。`);
} else {
displayMessage(`${name}に${damage}のダメージ。`);
}
}
function isAlive(character) {
return character.hp > 0;
}
function displayDeadMessage(name, isFriend) {
if (isFriend) {
displayMessage(`${name}はしんでしまった。`);
} else {
displayMessage(`${name}をやっつけた。`);
}
}
function displayHp(party, index, isFriend) {
if (isFriend) {
const selector = `#status tr:nth-child(2) td:nth-child(${index + 1})`;
document.querySelector(selector).textContent = `HP ${party[index].hp}`;
} else {
const selector = `#monster img:nth-child(${index + 1})`;
if (isAlive(party[index])) {
document.querySelector(selector).style.visibility = "visible";
} else {
document.querySelector(selector).style.visibility = "hidden";
}
}
}
function selectTarget(party) {
for (let i = 0; i < party.length; i++) {
if (isAlive(party[i])) {
return i;
}
}
return 0;
}
function isWipedOut(party) {
for (const character of party) {
if (isAlive(character)) {
return false;
}
}
return true;
}
async function main() {
const friendParty = [
{ name: "ゆうしゃ", maxHp: 153, attack: 162, defense: 97 },
{ name: "せんし", maxHp: 198, attack: 178, defense: 111 },
{ name: "そうりょ", maxHp: 101, attack: 76, defense: 55 },
{ name: "まほうつかい", maxHp: 77, attack: 60, defense: 57 }
];
for (const character of friendParty) {
character.hp = character.maxHp;
}
const enemyParty = [
{ name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
{ name: "まおう", maxHp: 999, attack: 186, defense: 58 },
{ name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
];
for (const character of enemyParty) {
character.hp = character.maxHp;
}
battle: while (true) {
for (const character of friendParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(enemyParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(enemyParty[target])) {
displayMessage(`${enemyParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, enemyParty[target].defense);
enemyParty[target].hp = calculateHp(enemyParty[target].hp, damage);
displayDamageMessage(enemyParty[target].name, damage);
await sleep();
displayHp(enemyParty, target, false);
if (!isAlive(enemyParty[target])) {
displayDeadMessage(enemyParty[target].name, false);
await sleep();
}
if (isWipedOut(enemyParty)) {
break battle;
}
}
for (const character of enemyParty) {
if (!isAlive(character)) {
continue;
}
const target = selectTarget(friendParty);
displayMessage(`${character.name}のこうげき。`);
await sleep();
if (!isAlive(friendParty[target])) {
displayMessage(`${friendParty[target].name}はすでにしんでいる。`);
await sleep();
continue;
}
let damage = calculateDamage(character.attack, friendParty[target].defense);
friendParty[target].hp = calculateHp(friendParty[target].hp, damage);
displayDamageMessage(friendParty[target].name, damage);
await sleep();
displayHp(friendParty, target, true);
if (!isAlive(friendParty[target])) {
displayDeadMessage(friendParty[target].name, true);
await sleep();
}
if (isWipedOut(friendParty)) {
break battle;
}
}
}
}
main();