13. 関数

このレッスンでは、同じ処理をまとめて再利用できるようにする関数について学びます。

同じ処理が2回ある

前のレッスンで書いたコードを見てみましょう。

<!-- 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:13-16
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;

let damage = Math.floor((attack1 - defense2) / 2);
if (damage < 0) {
    damage = 0;
}
hp2 = hp2 - damage;
if (hp2 < 0) {
    hp2 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name2}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name2}${damage}のダメージ。`;
}

damage = Math.floor((attack2 - defense1) / 2);
if (damage < 0) {
    damage = 0;
}
hp1 = hp1 - damage;
if (hp1 < 0) {
    hp1 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name1}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name1}${damage}のダメージ。`;
}
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;
// battle.js selection:27-30
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;

let damage = Math.floor((attack1 - defense2) / 2);
if (damage < 0) {
    damage = 0;
}
hp2 = hp2 - damage;
if (hp2 < 0) {
    hp2 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name2}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name2}${damage}のダメージ。`;
}

damage = Math.floor((attack2 - defense1) / 2);
if (damage < 0) {
    damage = 0;
}
hp1 = hp1 - damage;
if (hp1 < 0) {
    hp1 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name1}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name1}${damage}のダメージ。`;
}
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

ダメージを計算する処理が2回書かれています。

違いは、攻撃する側と防御する側のステータスだけです。このように同じ処理を何度も書くのは大変ですし、間違いも起きやすくなります。関数を使えば、同じ処理を1回だけ書いて、何度でも使い回すことができます。

関数とは

関数は、処理をまとめて名前を付けたものです。関数を作ることを「定義する」と言います。

function 関数名() {
    // 処理
}

定義した関数は、名前を使って呼び出すことができます。

関数名();

calculateDamage関数

ダメージ計算を関数にしてみましょう。攻撃力と防御力を受け取って、ダメージを計算して返す関数を作ります。

引数

関数に値を渡すには、引数(ひきすう)を使います。関数に「引き渡す数(値)」なので引数と呼ばれます。引数は関数名の後の( )の中に書きます。

// highlight:1
function calculateDamage(attack, defense) {
    // attackとdefenseを使った処理
}

この関数を呼び出すときは、( )の中に値を入れます。

calculateDamage(186, 97);

こうすると、attack186が、defense97が入った状態で関数の中の処理が実行されます。

戻り値

関数から値を返すには、returnを使います。returnの後に書いた値が、関数を呼び出した場所に返されます。呼び出し元に値が「戻る」ので、この値を戻り値(もどりち)と呼びます。

// highlight:6
function calculateDamage(attack, defense) {
    let damage = Math.floor((attack - defense) / 2);
    if (damage < 0) {
        damage = 0;
    }
    return damage;
}

この関数を呼び出すと、計算されたダメージが返ってきます。

let damage = calculateDamage(186, 97);

コードに適用する

では、この関数を使ってコードを書き換えてみましょう。

// battle.js highlight:1-7,21,32
function calculateDamage(attack, defense) {
    let damage = Math.floor((attack - defense) / 2);
    if (damage < 0) {
        damage = 0;
    }
    return damage;
}

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;

let damage = calculateDamage(attack1, defense2);
hp2 = hp2 - damage;
if (hp2 < 0) {
    hp2 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name2}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name2}${damage}のダメージ。`;
}

damage = calculateDamage(attack2, defense1);
hp1 = hp1 - damage;
if (hp1 < 0) {
    hp1 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name1}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name1}${damage}のダメージ。`;
}
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

ダメージ計算の処理がcalculateDamage関数にまとまり、それを2回呼び出すだけで済むようになりました。

calculateHp関数

同じようにもう1つ関数を作ってみましょう。HPを計算する処理も2回書かれています。

// battle.js selection:22-25
function calculateDamage(attack, defense) {
    let damage = Math.floor((attack - defense) / 2);
    if (damage < 0) {
        damage = 0;
    }
    return damage;
}

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;

let damage = calculateDamage(attack1, defense2);
hp2 = hp2 - damage;
if (hp2 < 0) {
    hp2 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name2}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name2}${damage}のダメージ。`;
}

damage = calculateDamage(attack2, defense1);
hp1 = hp1 - damage;
if (hp1 < 0) {
    hp1 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name1}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name1}${damage}のダメージ。`;
}
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;
// battle.js selection:33-36
function calculateDamage(attack, defense) {
    let damage = Math.floor((attack - defense) / 2);
    if (damage < 0) {
        damage = 0;
    }
    return damage;
}

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;

let damage = calculateDamage(attack1, defense2);
hp2 = hp2 - damage;
if (hp2 < 0) {
    hp2 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name2}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name2}${damage}のダメージ。`;
}

damage = calculateDamage(attack2, defense1);
hp1 = hp1 - damage;
if (hp1 < 0) {
    hp1 = 0;
}
if (damage === 0) {
    document.getElementById("message").textContent = `${name1}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name1}${damage}のダメージ。`;
}
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

これを関数にするとどうなるでしょうか?calculateDamageを参考に、calculateHp関数を作り、コードを書き換えてみてください。

// battle.js highlight:9-15,30,39 folded
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;
}

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;

let damage = calculateDamage(attack1, defense2);
hp2 = calculateHp(hp2, damage);
if (damage === 0) {
    document.getElementById("message").textContent = `${name2}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name2}${damage}のダメージ。`;
}

damage = calculateDamage(attack2, defense1);
hp1 = calculateHp(hp1, damage);
if (damage === 0) {
    document.getElementById("message").textContent = `${name1}にダメージをあたえられない。`;
} else {
    document.getElementById("message").textContent = `${name1}${damage}のダメージ。`;
}
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

displayMessage関数

関数には、引数がないものや、戻り値がないものもあります。calculateDamage関数は引数も戻り値もありましたが、今度は戻り値がない関数を作ってみましょう。

メッセージを表示する処理を関数にします。

function displayMessage(message) {
    document.getElementById("message").textContent = message;
}

この関数は引数としてmessageを受け取りますが、何もreturnしていません。このような関数は、呼び出しても値が返ってきません。値を返す必要がない処理を関数にするときに使います。

displayMessage("まおうがあらわれた。");

コードに適用する

displayMessage関数を使ってコードを書き換えてみましょう。

// battle.js selection:17-48 highlight:17-19,36,38,44,46
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;
}

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;

let damage = calculateDamage(attack1, defense2);
hp2 = calculateHp(hp2, damage);
if (damage === 0) {
    displayMessage(`${name2}にダメージをあたえられない。`);
} else {
    displayMessage(`${name2}${damage}のダメージ。`);
}

damage = calculateDamage(attack2, defense1);
hp1 = calculateHp(hp1, damage);
if (damage === 0) {
    displayMessage(`${name1}にダメージをあたえられない。`);
} else {
    displayMessage(`${name1}${damage}のダメージ。`);
}
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

displayMessage関数は短い関数ですが、document.getElementById("message").textContent = ...という長いコードがdisplayMessage(...)に置き換わることで、何をしているのかがわかりやすくなりました。

displayDamageMessage関数

関数の中から別の関数を呼び出すこともできます。ダメージのメッセージを表示する関数を作り、その中でdisplayMessageを呼び出してみましょう。

function displayDamageMessage(name, damage) {
    if (damage === 0) {
        displayMessage(`${name}にダメージをあたえられない。`);
    } else {
        displayMessage(`${name}${damage}のダメージ。`);
    }
}

この関数は、ダメージが0かどうかで表示するメッセージを変え、displayMessage関数を呼び出しています。

コードに適用する

displayDamageMessage関数を使ってコードを書き換えてみましょう。

// battle.js selection:17-48 highlight:21-27,43,47
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}のダメージ。`);
    }
}

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;

let damage = calculateDamage(attack1, defense2);
hp2 = calculateHp(hp2, damage);
displayDamageMessage(name2, damage);

damage = calculateDamage(attack2, defense1);
hp1 = calculateHp(hp1, damage);
displayDamageMessage(name1, damage);
document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;

if文を使ったメッセージの分岐も関数にまとまり、コードがさらにすっきりしました。

画面へのHP表示について

HPを画面に反映する処理も同じように関数にしたいところですが、HPの表示蘭は「ゆうしゃ」の他に「せんし」「そうりょ」「まほうつかい」もあり、誰のHPを更新するかを指定する必要があります。他のパーティメンバーのHPも扱うようになったら、誰のHPを画面に反映するかを指定できる関数を作ることにしましょう。

main関数

プログラムでは、全体の処理をmainという名前の関数にまとめることがよくあります。

今のコードを見ると、関数の定義とメインの処理が混ざっています。関数を定義している部分と、実際に処理を行っている部分が分かれていると、コードの構造が把握しやすくなります。

main関数は引数も戻り値もない関数です。全体の処理をmain関数にまとめ、最後に呼び出すようにしましょう。

// battle.js selection:21-58 highlight:29-50,52
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 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;

    let damage = calculateDamage(attack1, defense2);
    hp2 = calculateHp(hp2, damage);
    displayDamageMessage(name2, damage);

    damage = calculateDamage(attack2, defense1);
    hp1 = calculateHp(hp1, damage);
    displayDamageMessage(name1, damage);
    document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;
}

main();

これで、関数の定義部分とメインの処理部分が明確に分かれ、プログラムの構造がより整理されました。

試してみよう

これまでの成果

<!-- 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 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 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;

    let damage = calculateDamage(attack1, defense2);
    hp2 = calculateHp(hp2, damage);
    displayDamageMessage(name2, damage);

    damage = calculateDamage(attack2, defense1);
    hp1 = calculateHp(hp1, damage);
    displayDamageMessage(name1, damage);
    document.querySelector("#status tr:nth-child(2) td:nth-child(1)").textContent = `HP ${hp1}`;
}

main();