Source: myGameCharacter.js

/**
 * @fileOverview RPG のキャラクタ等を表現するクラス例。
 * @author K.Miyawaki
 */

/**
 * RPG キャラクタが持つ武器を表現するクラス例。
 */
class MyGameItem {
    /**
     * @constructor
     * @param {string} name 名前。 
     * @param {number} attackMin ダメージ最小値。 
     * @param {number} attackMax ダメージ最大値。 
     * @param {number} attackCount 攻撃回数。 
     * @param {number} stock 所持数。 null で無限に使用できる。
     */
    constructor(name, attackMin, attackMax, attackCount, stock) {
        this.name = name;
        this.attackMin = attackMin;
        this.attackMax = attackMax;
        this.attackCount = attackCount;
        this.stock = stock;
    }

    /**
     * この武器がまだ使えるかどうか。
     * @returns {boolean} 使えるなら true 。
     * @memberof MyGameItem
     */
    checkCount() {
        if (this.stock == null) {
            return true;
        }
        return this.stock > 0;
    }

    /**
     * この武器を使い、所持数を減らす。0 未満にはしない。 this.stock が null なら何もしない。
     * @memberof MyGameItem
     */
    use() {
        if (this.stock) {
            this.stock = Math.max(0, this.stock - 1);
        }
    }

    /**
     * この武器で攻撃した場合の 1 回あたりのダメージを計算する。
     * @returns {number}
     * @memberof MyGameItem
     */
    calcAttackBonus() {
        return Math.floor(Math.random() * (this.attackMax + 1 - this.attackMin)) + this.attackMin;
    }

    /**
     * この武器の文字列での表現を得る。
     * @returns {string}
     * @memberof MyGameItem
     */
    getString() {
        let text = this.name + ":";
        if (this.stock == null) {
            text += "無制限";
        } else {
            text += this.stock;
        }
        return text;
    }
}

/**
 * RPG のキャラクタを表現するクラス例。
 */
class MyGameCharacter {
    /**
     * @constructor
     * @param {string} name キャラクタの名前。
     * @param {string} [imageURL=null] 画像の URL 。null なら画像表示しない。
     * @param {number} [hp=1] 体力。
     * @param {number} [power=1] 腕力。
     * @param {number} [defense=1] 防御力。
     * @param {number} [speed=1] 素早さ。
     */
    constructor(name, imageURL = null, hp = 1, power = 1, defense = 1, speed = 1) {
        this.name = name;
        this.hp = hp;
        this.power = power;
        this.defense = defense;
        this.speed = speed;
        this.wepon = new MyGameItem("素手", 0, 5, 1, null);
        this.items = [this.wepon];
        this.imageURL = imageURL;
        this.image = null;
    }

    /**
     * キャラクタの画像をロードして、指定された HTML 要素に表示する。
     * @param {HTMLDocument} targetElement
     * @memberof MyGameCharacter
     */
    show(targetElement) {
        if (this.imageURL) {
            this.image = document.createElement("img");
            let obj = this;
            this.image.onload = function () {
                obj.onLoad();
                obj.addImage(targetElement);
            }
            this.image.src = this.imageURL;
        }
    }

    onLoad() {
        this.image.style.position = "absolute";
        this.image.style.left = "0px";
        this.image.style.top = "0px";
        this.image.style.width = "auto";
        this.image.style.height = "80%";
    }

    addImage(targetElement) {
        if (this.image) {
            targetElement.appendChild(this.image);
            const targetRect = targetElement.getBoundingClientRect();
            const imageRect = this.image.getBoundingClientRect();
            const left = (targetRect.width - imageRect.width) / 2;
            const top = (targetRect.height - imageRect.height) / 2;
            this.image.style.left = left + "px";
            this.image.style.top = top + "px";
        }
    }

    /**
     * キャラクタ画像を指定した HTML 要素から取り除く。
     * @param {HTMLElement} targetElement
     * @memberof MyGameCharacter
     */
    remove(targetElement) {
        if (this.image) {
            targetElement.removeChild(this.image);
        }
    }

    /**
     * キャラクタが生存しているか否か。 true: 生存している。
     * @returns {boolean}
     * @memberof MyGameCharacter
     */
    isAlive() {
        return this.hp > 0;
    }

    /**
     * 他のキャラクタに攻撃が命中するか否か。 true: 命中する。
     * @param {MyGameCharacter} targetCharacter 攻撃対象。
     * @returns {boolean}
     * @memberof MyGameCharacter
     */
    checkHit(targetCharacter) {
        return (Math.random() * this.speed) > (Math.random() * targetCharacter.speed);
    }

    /**
     * targetCharacter に攻撃し、ダメージを与える。
     * @param {MyGameCharacter} targetCharacter
     * @param {MyGameItem} wepon
     * @returns {Object} 以下のキーを持つ Object 。
     * <ul>
     *   <li>hitCount: 攻撃命中回数</li>
     *   <li>damage: 与えたダメージ</li>
     *   <li>message: 攻撃結果を表現する文字列</li>
     * </ul>
     * @memberof MyGameCharacter
     */
    attackTo(targetCharacter, wepon) {
        let damage = 0;
        let hitCount = 0;
        let message = "";
        if (wepon.checkCount()) {
            for (let i = 0; i < wepon.attackCount; i++) {
                let attack = wepon.calcAttackBonus() + this.power;
                if (this.checkHit(targetCharacter)) {
                    hitCount++;
                    damage += Math.max(0, attack - targetCharacter.defense);
                }
            }
            wepon.use();
            message = this.name + "は" + wepon.name + "で攻撃した。" + targetCharacter.name + "に";
            if (damage > 0) {
                message += (hitCount + " 回命中し、" + damage + " のダメージを与えた。");
            } else {
                message += "ダメージはない。";
            }
        } else {
            message = this.name + "は" + wepon.name + "で攻撃しようとしたが弾切れだった。";
        }
        targetCharacter.hp -= damage;
        return { hitCount: hitCount, damage: damage, message: message };
    }

    /**
     * このキャラクタのステータスを表示する。
     * @param {HTMLElement} htmlElement 表示対象の HTML 要素。
     * @memberof MyGameCharacter
     */
    print(htmlElement) {
        htmlElement.innerHTML = "";
        htmlElement.innerHTML += ("<b>" + this.name + "</b><br />");
        htmlElement.innerHTML += ("体力:" + this.hp + "<br />");
        htmlElement.innerHTML += ("腕力:" + this.power + "<br />");
        htmlElement.innerHTML += ("防御:" + this.defense + "<br />");
        htmlElement.innerHTML += ("素早さ:" + this.speed + "<br />");
        htmlElement.innerHTML += ("<b>持ち物</b><br />");
        for (let item of this.items) {
            htmlElement.innerHTML += (item.getString() + "<br />");
        }
    }

    useItem(item) {
        if (item.checkCount() && this[item.targetParam]) {
            this[item.targetParam] += item.bonusValue;
            item.use();
        }
    }
}