18. for文

このレッスンでは、ループをより簡潔に書けるfor文について学びます。

whileループの煩わしさ

前回のレッスンでは、Arrayの要素を順番に処理するためにwhileループを使いました。

<!-- 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:52-56
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 }
    ];
    let i = 0;
    while (i < friendParty.length) {
        friendParty[i].hp = friendParty[i].maxHp;
        i++;
    }

    const enemyParty = [
        { name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
        { name: "まおう", maxHp: 999, attack: 186, defense: 58 },
        { name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
    ];
    i = 0;
    while (i < enemyParty.length) {
        enemyParty[i].hp = enemyParty[i].maxHp;
        i++;
    }

    while (true) {
        i = 0;
        while (i < friendParty.length) {
            if (!isAlive(friendParty[i])) {
                i++;
                continue;
            }
            displayMessage(`${friendParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(enemyParty[0])) {
                displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
                await sleep();
                i++;
                continue;
            }
            let damage = calculateDamage(friendParty[i].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();
            }
            i++;
        }

        i = 0;
        while (i < enemyParty.length) {
            if (!isAlive(enemyParty[i])) {
                i++;
                continue;
            }
            displayMessage(`${enemyParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(friendParty[0])) {
                displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
                await sleep();
                i++;
                continue;
            }
            let damage = calculateDamage(enemyParty[i].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();
            }
            i++;
        }
    }
}

main();

このコードには3つの部分があります。

  1. 初期化: let i = 0; — ループの前に変数を初期化
  2. 条件: i < friendParty.length — ループを続ける条件
  3. 更新: i++; — ループの末尾で変数を更新

whileループでは、これら3つの部分が離れた場所に書かれています。特にi++;はループの末尾に書く必要があり、忘れると無限ループになってしまいます。

さらに、continueを使う場合は問題が複雑になります。

while (i < friendParty.length) {
    if (!isAlive(friendParty[i])) {
        i++;      // continueの前にもi++が必要!
        continue;
    }
    // ...攻撃処理...
    i++;
}

continueはループの残りをスキップするので、末尾のi++も実行されません。そのため、continueの前にもi++を書く必要があります。これを忘れると無限ループになります。

for文

JavaScriptには、この3つの部分をまとめて書けるfor文があります。

for (let i = 0; i < friendParty.length; i++) {
    friendParty[i].hp = friendParty[i].maxHp;
}

for文は次の構文で書きます。

for (初期化; 条件; 更新) {
    // 繰り返す処理
}

( )の中に、初期化・条件・更新の3つをセミコロン(;)で区切って書きます。

whileループと比較してみましょう。

// while
let i = 0;
while (i < friendParty.length) {
    friendParty[i].hp = friendParty[i].maxHp;
    i++;
}

// for
for (let i = 0; i < friendParty.length; i++) {
    friendParty[i].hp = friendParty[i].maxHp;
}

for文では、ループに関する3つの部分が1行にまとまっています。コードが短くなるだけでなく、i++を書き忘れる心配もありません。

for文の各部分

for文の3つの部分について、詳しく見てみましょう。

for (let i = 0; i < array.length; i++) {
//   ^^^^^^^^^  ^^^^^^^^^^^^^^^^  ^^^
//   初期化     条件              更新
}

初期化let i = 0

条件i < array.length

更新i++

HP初期化をforで書き換える

それでは、味方パーティのHP初期化をfor文で書き換えてみましょう。

// battle.js selection:45-56 highlight:52-54
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 (let i = 0; i < friendParty.length; i++) {
        friendParty[i].hp = friendParty[i].maxHp;
    }

    const enemyParty = [
        { name: "あんこくきし", maxHp: 250, attack: 181, defense: 93 },
        { name: "まおう", maxHp: 999, attack: 186, defense: 58 },
        { name: "デモンプリースト", maxHp: 180, attack: 121, defense: 55 }
    ];
    i = 0;
    while (i < enemyParty.length) {
        enemyParty[i].hp = enemyParty[i].maxHp;
        i++;
    }

    while (true) {
        i = 0;
        while (i < friendParty.length) {
            if (!isAlive(friendParty[i])) {
                i++;
                continue;
            }
            displayMessage(`${friendParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(enemyParty[0])) {
                displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
                await sleep();
                i++;
                continue;
            }
            let damage = calculateDamage(friendParty[i].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();
            }
            i++;
        }

        i = 0;
        while (i < enemyParty.length) {
            if (!isAlive(enemyParty[i])) {
                i++;
                continue;
            }
            displayMessage(`${enemyParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(friendParty[0])) {
                displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
                await sleep();
                i++;
                continue;
            }
            let damage = calculateDamage(enemyParty[i].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();
            }
            i++;
        }
    }
}

main();

whileでは4行だったコードが、forでは3行になりました。

敵パーティのHP初期化も同様にfor文で書き換えてみてください。

// battle.js highlight:61-63 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 (let i = 0; i < friendParty.length; i++) {
        friendParty[i].hp = friendParty[i].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 (let i = 0; i < enemyParty.length; i++) {
        enemyParty[i].hp = enemyParty[i].maxHp;
    }

    while (true) {
        let i = 0;
        while (i < friendParty.length) {
            if (!isAlive(friendParty[i])) {
                i++;
                continue;
            }
            displayMessage(`${friendParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(enemyParty[0])) {
                displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
                await sleep();
                i++;
                continue;
            }
            let damage = calculateDamage(friendParty[i].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();
            }
            i++;
        }

        i = 0;
        while (i < enemyParty.length) {
            if (!isAlive(enemyParty[i])) {
                i++;
                continue;
            }
            displayMessage(`${enemyParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(friendParty[0])) {
                displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
                await sleep();
                i++;
                continue;
            }
            let damage = calculateDamage(enemyParty[i].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();
            }
            i++;
        }
    }
}

main();

for文では初期化式でlet i = 0と宣言しているので、2つのforループで同じ変数名iを使っても問題ありません。それぞれのforループで独立した変数iが作られます。

continueとfor文

先に述べたように、for文の大きな利点はcontinueを使う場合に発揮されます。

while文では、continueの前にi++を書き忘れると無限ループになってしまいました。

// while: continueの前にもi++が必要
while (i < friendParty.length) {
    if (!isAlive(friendParty[i])) {
        i++;      // これを忘れると無限ループ!
        continue;
    }
    // ...攻撃処理...
    i++;
}

for文では、continueでスキップされても更新の式が実行されます。そのため、continueの前にi++を書く必要がありません。

// for: continueの前のi++は不要
for (let i = 0; i < friendParty.length; i++) {
    if (!isAlive(friendParty[i])) {
        continue;  // i++は実行される
    }
    // ...攻撃処理...
}

味方の攻撃ループをfor文で書き換えてみましょう。

// battle.js selection:66-87 highlight:66,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}をやっつけた。`);
    }
}

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 (let i = 0; i < friendParty.length; i++) {
        friendParty[i].hp = friendParty[i].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 (let i = 0; i < enemyParty.length; i++) {
        enemyParty[i].hp = enemyParty[i].maxHp;
    }

    while (true) {
        for (let i = 0; i < friendParty.length; i++) {
            if (!isAlive(friendParty[i])) {
                continue;
            }
            displayMessage(`${friendParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(enemyParty[0])) {
                displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
                await sleep();
                continue;
            }
            let damage = calculateDamage(friendParty[i].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();
            }
        }

        let i = 0;
        while (i < enemyParty.length) {
            if (!isAlive(enemyParty[i])) {
                i++;
                continue;
            }
            displayMessage(`${enemyParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(friendParty[0])) {
                displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
                await sleep();
                i++;
                continue;
            }
            let damage = calculateDamage(enemyParty[i].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();
            }
            i++;
        }
    }
}

main();

continueの前にあったi++;がなくなり、コードがすっきりしました。

敵の攻撃ループも同様にfor文で書き換えてみてください。

// battle.js highlight:89,91,98 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 (let i = 0; i < friendParty.length; i++) {
        friendParty[i].hp = friendParty[i].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 (let i = 0; i < enemyParty.length; i++) {
        enemyParty[i].hp = enemyParty[i].maxHp;
    }

    while (true) {
        for (let i = 0; i < friendParty.length; i++) {
            if (!isAlive(friendParty[i])) {
                continue;
            }
            displayMessage(`${friendParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(enemyParty[0])) {
                displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
                await sleep();
                continue;
            }
            let damage = calculateDamage(friendParty[i].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 (let i = 0; i < enemyParty.length; i++) {
            if (!isAlive(enemyParty[i])) {
                continue;
            }
            displayMessage(`${enemyParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(friendParty[0])) {
                displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
                await sleep();
                continue;
            }
            let damage = calculateDamage(enemyParty[i].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();

while(true)は残す

バトルのメインループはwhile (true)のままです。このループはfor文には向いていません。

for文は「決まった回数繰り返す」または「配列の要素を順番に処理する」ときに便利です。一方、バトルのメインループは「勝敗が決まるまで繰り返す」ので、繰り返し回数が事前にわかりません。

このような「条件を満たすまで繰り返す」ループには、while文が適しています。

試してみよう

これまでの成果

<!-- 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}をやっつけた。`);
    }
}

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 (let i = 0; i < friendParty.length; i++) {
        friendParty[i].hp = friendParty[i].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 (let i = 0; i < enemyParty.length; i++) {
        enemyParty[i].hp = enemyParty[i].maxHp;
    }

    while (true) {
        for (let i = 0; i < friendParty.length; i++) {
            if (!isAlive(friendParty[i])) {
                continue;
            }
            displayMessage(`${friendParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(enemyParty[0])) {
                displayMessage(`${enemyParty[0].name}はすでにしんでいる。`);
                await sleep();
                continue;
            }
            let damage = calculateDamage(friendParty[i].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 (let i = 0; i < enemyParty.length; i++) {
            if (!isAlive(enemyParty[i])) {
                continue;
            }
            displayMessage(`${enemyParty[i].name}のこうげき。`);
            await sleep();
            if (!isAlive(friendParty[0])) {
                displayMessage(`${friendParty[0].name}はすでにしんでいる。`);
                await sleep();
                continue;
            }
            let damage = calculateDamage(enemyParty[i].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();