15. オブジェクト

このレッスンでは、関連するデータをまとめて管理するオブジェクトについて学びます。

変数が多すぎる

前回のレッスンで、バトルを繰り返すことができるようになりました。しかし、コードを見返すと変数がたくさんあることに気づきます。

<!-- 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:34-44
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}のダメージ。`);
    }
}

async function main() {
    const name1 = "ゆうしゃ";
    const maxHp1 = 153;
    let hp1 = maxHp1;
    const attack1 = 162;
    const defense1 = 97;

    const name2 = "まおう";
    const maxHp2 = 999;
    let hp2 = maxHp2;
    const attack2 = 186;
    const defense2 = 58;

    while (hp1 > 0 && hp2 > 0) {
        displayMessage(`${name1}のこうげき。`);
        await sleep();
        let damage = calculateDamage(attack1, defense2);
        hp2 = calculateHp(hp2, damage);
        displayDamageMessage(name2, damage);
        await sleep();

        if (hp2 <= 0) {
            document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
            displayMessage(`${name2}をやっつけた。`);
            break;
        }

        displayMessage(`${name2}のこうげき。`);
        await sleep();
        damage = calculateDamage(attack2, defense1);
        hp1 = calculateHp(hp1, damage);
        displayDamageMessage(name1, damage);
        await sleep();
        document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

        if (hp1 <= 0) {
            displayMessage(`${name1}はしんでしまった。`);
        }
    }
}

main();

「ゆうしゃ」に関する変数が5つ(name1maxHp1hp1attack1defense1)、「まおう」に関する変数も5つ(name2maxHp2hp2attack2defense2)あります。さらに、今後他にもMPや素早さなどの変数が必要になりそうです。

「ゆうしゃ」に関する変数をばらばらに扱うより、1つにまとめて扱えるとわかりやすそうです。

オブジェクト

JavaScriptには、関連するデータをひとまとめにするオブジェクトという仕組みがあります。オブジェクトは{ }で囲んで作ります。

const hero = {
    name: "ゆうしゃ",
    maxHp: 153
};

↑のように、オブジェクトも変数に代入することができます。

namemaxHpのことをオブジェクトのプロパティと呼びます。プロパティはプロパティ名: 値の形式で書き、複数のプロパティはカンマ(,)で区切ります。

オブジェクトのプロパティには.(ドット)を使ってアクセスできます。

console.log(hero.name);   // "ゆうしゃ"
console.log(hero.maxHp);  // 153

また、新しいプロパティを後から追加することもできます。

hero.hp = hero.maxHp;

ゆうしゃのオブジェクトを作る

「ゆうしゃ」に関する5つの変数をオブジェクトにまとめてみましょう。

// battle.js selection:34-40 highlight:34-40
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}のダメージ。`);
    }
}

async function main() {
    const hero = {
        name: "ゆうしゃ",
        maxHp: 153,
        attack: 162,
        defense: 97
    };
    hero.hp = hero.maxHp;

    const name2 = "まおう";
    const maxHp2 = 999;
    let hp2 = maxHp2;
    const attack2 = 186;
    const defense2 = 58;

    while (hp1 > 0 && hp2 > 0) {
        displayMessage(`${name1}のこうげき。`);
        await sleep();
        let damage = calculateDamage(attack1, defense2);
        hp2 = calculateHp(hp2, damage);
        displayDamageMessage(name2, damage);
        await sleep();

        if (hp2 <= 0) {
            document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
            displayMessage(`${name2}をやっつけた。`);
            break;
        }

        displayMessage(`${name2}のこうげき。`);
        await sleep();
        damage = calculateDamage(attack2, defense1);
        hp1 = calculateHp(hp1, damage);
        displayDamageMessage(name1, damage);
        await sleep();
        document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

        if (hp1 <= 0) {
            displayMessage(`${name1}はしんでしまった。`);
        }
    }
}

main();

5つの変数が1つのオブジェクトにまとまりました。先ほど学んだように、hphero.hp = hero.maxHp;で後から追加しています。

まおうのオブジェクトを作る

同様に「まおう」もオブジェクトにまとめましょう。

// battle.js selection:34-48 highlight:42-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}のダメージ。`);
    }
}

async function main() {
    const hero = {
        name: "ゆうしゃ",
        maxHp: 153,
        attack: 162,
        defense: 97
    };
    hero.hp = hero.maxHp;

    const darkLord = {
        name: "まおう",
        maxHp: 999,
        attack: 186,
        defense: 58
    };
    darkLord.hp = darkLord.maxHp;

    while (hp1 > 0 && hp2 > 0) {
        displayMessage(`${name1}のこうげき。`);
        await sleep();
        let damage = calculateDamage(attack1, defense2);
        hp2 = calculateHp(hp2, damage);
        displayDamageMessage(name2, damage);
        await sleep();

        if (hp2 <= 0) {
            document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
            displayMessage(`${name2}をやっつけた。`);
            break;
        }

        displayMessage(`${name2}のこうげき。`);
        await sleep();
        damage = calculateDamage(attack2, defense1);
        hp1 = calculateHp(hp1, damage);
        displayDamageMessage(name1, damage);
        await sleep();
        document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

        if (hp1 <= 0) {
            displayMessage(`${name1}はしんでしまった。`);
        }
    }
}

main();

オブジェクトを使うように書き換える

オブジェクトを作りましたが、まだwhile文の中では古い変数名(hp1hp2など)を使っています。これをオブジェクトのプロパティを使うように書き換えましょう。

古い変数 新しい書き方
name1 hero.name
hp1 hero.hp
attack1 hero.attack
defense1 hero.defense
name2 darkLord.name
hp2 darkLord.hp
attack2 darkLord.attack
defense2 darkLord.defense

書き換えると次のようになります。

// battle.js selection:50-76 highlight:50-51,53-55,58,60,64,66-68,70,72-73
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}のダメージ。`);
    }
}

async function main() {
    const hero = {
        name: "ゆうしゃ",
        maxHp: 153,
        attack: 162,
        defense: 97
    };
    hero.hp = hero.maxHp;

    const darkLord = {
        name: "まおう",
        maxHp: 999,
        attack: 186,
        defense: 58
    };
    darkLord.hp = darkLord.maxHp;

    while (hero.hp > 0 && darkLord.hp > 0) {
        displayMessage(`${hero.name}のこうげき。`);
        await sleep();
        let damage = calculateDamage(hero.attack, darkLord.defense);
        darkLord.hp = calculateHp(darkLord.hp, damage);
        displayDamageMessage(darkLord.name, damage);
        await sleep();

        if (darkLord.hp <= 0) {
            document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
            displayMessage(`${darkLord.name}をやっつけた。`);
            break;
        }

        displayMessage(`${darkLord.name}のこうげき。`);
        await sleep();
        damage = calculateDamage(darkLord.attack, hero.defense);
        hero.hp = calculateHp(hero.hp, damage);
        displayDamageMessage(hero.name, damage);
        await sleep();
        document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hero.hp}`;

        if (hero.hp <= 0) {
            displayMessage(`${hero.name}はしんでしまった。`);
        }
    }
}

main();

hp1attack2のような番号付きの変数名がなくなり、hero.hpdarkLord.attackのように「誰の何か」が分かりやすい書き方になりました。

試してみよう

これまでの成果

<!-- 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}のダメージ。`);
    }
}

async function main() {
    const hero = {
        name: "ゆうしゃ",
        maxHp: 153,
        attack: 162,
        defense: 97
    };
    hero.hp = hero.maxHp;

    const darkLord = {
        name: "まおう",
        maxHp: 999,
        attack: 186,
        defense: 58
    };
    darkLord.hp = darkLord.maxHp;

    while (hero.hp > 0 && darkLord.hp > 0) {
        displayMessage(`${hero.name}のこうげき。`);
        await sleep();
        let damage = calculateDamage(hero.attack, darkLord.defense);
        darkLord.hp = calculateHp(darkLord.hp, damage);
        displayDamageMessage(darkLord.name, damage);
        await sleep();

        if (darkLord.hp <= 0) {
            document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
            displayMessage(`${darkLord.name}をやっつけた。`);
            break;
        }

        displayMessage(`${darkLord.name}のこうげき。`);
        await sleep();
        damage = calculateDamage(darkLord.attack, hero.defense);
        hero.hp = calculateHp(hero.hp, damage);
        displayDamageMessage(hero.name, damage);
        await sleep();
        document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hero.hp}`;

        if (hero.hp <= 0) {
            displayMessage(`${hero.name}はしんでしまった。`);
        }
    }
}

main();