16. boolean
このレッスンでは、条件式の結果として得られるboolean(論理値)について学びます。
hp > 0 の重複
前回のレッスンで、ゆうしゃとまおうをオブジェクトにまとめました。コードを見返すと、hp > 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:50-76 highlight:50,58,72
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();
- 50行目:
while (hero.hp > 0 && darkLord.hp > 0)で両者が生きているか判定 - 58行目:
if (darkLord.hp <= 0)でまおうが倒れたか判定(hp > 0の否定) - 71行目:
if (hero.hp <= 0)でゆうしゃが倒れたか判定(hp > 0の否定)
「HPが0より大きいかどうか」=「生きているかどうか」という判定が繰り返し出てきます。この判定を関数にまとめられないでしょうか?
isAlive関数
「生きているかどうか」を判定するisAlive関数を作ってみましょう。
// battle.js selection:33-35 highlight:33-35
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;
}
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();
この関数はcharacter(キャラクターのオブジェクト)を受け取り、character.hp > 0の結果を返します。
is命名規則
関数名をisAlive(「生きている」)としました。これは”The character is alive.”(そのキャラクターは生きている)という英文に由来します。
このように、「〇〇は△△である」という意味を持つ関数は、慣例的にisで始める命名が使われます。
boolean(論理値)
isAlive関数が返すcharacter.hp > 0の結果は、どのような値でしょうか?
hp > 0のような比較式は、四則演算と同じように計算できます。四則演算では数値の結果が得られますが、比較式ではtrueまたはfalseという値が得られます。
console.log(10 > 5); // true
console.log(3 > 7); // false
console.log(5 === 5); // true
console.log(5 === 3); // false
このtrue(真)とfalse(偽)の2つの値をboolean(論理値)と呼びます。
比較演算子
四則演算に使う+や-を演算子と呼ぶように、比較に使う===や>も比較演算子と呼びます。
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
=== |
等しい | 5 === 5 |
true |
!== |
等しくない | 5 !== 3 |
true |
> |
より大きい | 10 > 5 |
true |
< |
より小さい | 3 < 7 |
true |
>= |
以上 | 5 >= 5 |
true |
<= |
以下 | 3 <= 5 |
true |
booleanリテラル
trueとfalseは、booleanの値をそのまま書いたリテラルです。数値の10や文字列の"hello"と同じように、コードに直接書くことができます。
booleanを変数に格納する
booleanも他の値と同じように、変数に格納できます。
const a = 10 > 5; // 比較式の結果を格納
console.log(a); // true
const b = true; // リテラルを直接格納
const c = false;
const isPlaying = true;
const isGameOver = false;
比較式の結果を変数に格納しておけば、後で何度も使い回すことができます。
ifとwhileの条件
ここで、if文とwhile文を振り返ってみましょう。これまで「条件」と呼んでいた( )の中身は、実はboolean値です。
if (hp > 0) {
// hp > 0 が true のときに実行される
}
while (hp > 0) {
// hp > 0 が true の間、繰り返される
}
hp > 0という比較式が計算されてtrueまたはfalseになり、その結果に応じて処理が分岐したりループしたりします。
つまり、boolean値を返す関数や変数をそのまま条件として使えます。
if (isAlive(hero)) {
// isAlive(hero) が true のときに実行される
}
const isHeroAlive = isAlive(hero);
if (isHeroAlive) {
// isHeroAlive が true のときに実行される
}
論理演算子
booleanには、専用の演算子があります。
&&(AND演算子)
「かつ」を意味し、両方がtrueのときだけtrueになります。
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false
||(OR演算子)
「または」を意味し、どちらかがtrueならtrueになります。
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false
!(NOT演算子)
「否定」を意味し、trueとfalseを反転させます。
console.log(!true); // false
console.log(!false); // true
演算子の優先順位
論理演算子には優先順位があります。! > && > || の順に優先されます。
// !が最優先
console.log(!true && false); // false(!true が先に計算され false && false)
console.log(!false || true); // true(!false が先に計算され true || true)
// &&が||より優先
console.log(true || false && false); // true(false && false が先に計算され true || false)
四則演算と同様に、()で優先順位を変えられます。
console.log((true || false) && false); // false(true || false が先に計算され true && false)
コードの書き換え
isAlive関数を使って、バトルのコードを書き換えましょう。
hero.hp > 0→isAlive(hero)darkLord.hp <= 0→!isAlive(darkLord)(生きていない=倒れた)hero.hp <= 0→!isAlive(hero)
// battle.js selection:54-79 highlight:54,62,76
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;
}
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 (isAlive(hero) && isAlive(darkLord)) {
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 (!isAlive(darkLord)) {
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 (!isAlive(hero)) {
displayMessage(`${hero.name}はしんでしまった。`);
}
}
}
main();
isAlive(hero)は「ゆうしゃが生きている」、!isAlive(darkLord)は「まおうが生きていない」と読め、コードの意図が分かりやすくなりました。
displayDeadMessage関数
次に、死亡メッセージの表示を関数にまとめてみましょう。
現在、まおうが倒れたときは「〇〇をやっつけた。」、ゆうしゃが倒れたときは「〇〇はしんでしまった。」と表示しています。どちらも「キャラクターが倒れた」という状況ですが、味方か敵かでメッセージが異なります。
booleanを引数に取る関数
「味方かどうか」をbooleanで受け取る関数を作ります。
// battle.js selection:37-43 highlight:37-43
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 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 (isAlive(hero) && isAlive(darkLord)) {
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 (!isAlive(darkLord)) {
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 (!isAlive(hero)) {
displayMessage(`${hero.name}はしんでしまった。`);
}
}
}
main();
isFriendは「味方かどうか」を表すbooleanの引数です。trueなら味方、falseなら敵です。
booleanを入れる変数や引数も、isAliveと同様にisで始める命名がよく使われます。
コードの書き換え
displayDeadMessage関数を使って、コードを書き換えましょう。
- まおうが倒れた:
displayDeadMessage(darkLord.name, false)(敵なのでfalse) - ゆうしゃが倒れた:
displayDeadMessage(hero.name, true)(味方なのでtrue)
// battle.js selection:62-87 highlight:72,85
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 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 (isAlive(hero) && isAlive(darkLord)) {
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 (!isAlive(darkLord)) {
document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
displayDeadMessage(darkLord.name, false);
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 (!isAlive(hero)) {
displayDeadMessage(hero.name, true);
}
}
}
main();
while (true) と無限ループ
while文の条件にtrueを書くと、条件が常にtrueのため、永遠にループし続けます。これを無限ループと呼びます。
while (true) {
// このブロックは永遠に繰り返される
}
無限ループを終了するには、break文を使います。
コードの書き換え
現在のコードでは、whileの条件で「両者が生きている」をチェックし、ループ内でも「倒れたか」をチェックしています。これをwhile (true)とbreakを使って整理しましょう。
まおうの攻撃後もゆうしゃが倒れたらbreakでループを抜けるようにします。
// battle.js selection:62-88 highlight:62,86
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 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 (true) {
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 (!isAlive(darkLord)) {
document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
displayDeadMessage(darkLord.name, false);
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 (!isAlive(hero)) {
displayDeadMessage(hero.name, true);
break;
}
}
}
main();
while (true)で無限ループを作り、どちらかが倒れたタイミングでbreakしてループを抜けます。これにより、勝敗が決まったら確実にループが終了します。
試してみよう
isAlive関数を参考に、HPが0以下かどうかを判定するisDead関数を作ってみましょう
これまでの成果
<!-- 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 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 (true) {
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 (!isAlive(darkLord)) {
document.querySelector("#monster img:nth-child(2)").style.visibility = "hidden";
displayDeadMessage(darkLord.name, false);
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 (!isAlive(hero)) {
displayDeadMessage(hero.name, true);
break;
}
}
}
main();