技術とか戦略とか

証券レガシーシステムを8年いじってから転職した普通の文系SEによるブログ。技術のみではなく趣味の戦略考察についても。

javaでのCompositeパターン

Compositeパターンは、入れ物のクラスと中身のクラスを1つの抽象クラスでまとめ、同一視できるようにするパターンです。
このパターンを取り入れることで、クラスを使う側から見て入れ物のクラスなのか中身のクラスなのかを意識する必要がなくなります。
 
ファイルシステムの例が多いですが、理解を深めるために今回はあえて別の例を出して説明します。
袋と本・ゲームソフトの例を出して説明します。
単に袋の中に本やゲームソフトを入れるだけでなく、袋が破れないように袋を二重にする(袋の中に袋を入れる)こともありますし、袋の中に本用の袋とゲームソフト用の袋を入れて分類することもあります。
このような場合、Compositeパターンを用いることで、袋でも本・ゲームソフトでも同じように処理することができるようになります。
 
【サンプルコード】
・Entry.java
public abstract class Entry {

    public abstract void descript();

}
 
・Book.java
public class Book extends Entry {

    private String name;

    public Book(String name) {
        this.name = name;
    }

    public void descript() {
        System.out.println("これは" + name + "という本です。");
    }
}
 
・GameSoft.java
public class GameSoft extends Entry {

    private String name;

    public GameSoft(String name) {
        this.name = name;
    }

    public void descript() {
        System.out.println("これは" + name + "というソフトです。");
    }
}
 
・Bag.java
import java.util.ArrayList;
import java.util.Iterator;

public class Bag extends Entry {

    private String name;
    private ArrayList<Entry> item = new ArrayList<Entry>();

    public Bag(String name) {
        this.name = name;
    }

    public Entry add(Entry entry) {
        item.add(entry);
        return this;
    }

    public void descript() {
        System.out.println("これは" + name + "という袋です。");
        Iterator<Entry> iterator = item.iterator();
        while (iterator.hasNext()) {
            Entry entry = (Entry) iterator.next();
            System.out.print("袋「" + name + "」の中身:");
            entry.descript();
        }
    }
}
 
・EntryMain.java
public class EntryMain {

    public static void main(String[] args) {

        // 袋、本・ゲームソフトを作成
        Bag bigBag = new Bag("BigBag");
        Bag emptyBag = new Bag("EmptyBag");
        Bag bagOfBook = new Bag("BagOfBook");
        Bag bagOfSoft = new Bag("BagOfSoft");
        Book bigBook = new Book("BigBook");
        Book book1 = new Book("Book1");
        Book book2 = new Book("Book2");
        GameSoft soft1 = new GameSoft("Soft1");
        GameSoft soft2 = new GameSoft("Soft2");

        // 袋の中に詰める
        // 袋の中に詰めるのは袋でも本・ゲームソフトでも良い
        // (入れ物のクラスなのか中身のクラスなのか意識しなくて良い)
        bagOfBook.add(book1);
        bagOfBook.add(book2);
        bagOfSoft.add(soft1);
        bagOfSoft.add(soft2);
        bigBag.add(emptyBag);
        bigBag.add(bagOfBook);
        bigBag.add(bagOfSoft);
        bigBag.add(bigBook);

        // 説明を見る
        // 袋でも本・ゲームソフトでも同じように説明を見ることができる
        // (入れ物のクラスなのか中身のクラスなのか意識しなくて良い)
        System.out.println("[BigBag(入れ物クラス)の説明]");
        bigBag.descript();
        System.out.println();
        System.out.println("[Book1(中身クラス)の説明]");
        book1.descript();

    }
}
 
【実行結果】
[BigBag(入れ物クラス)の説明]
これはBigBagという袋です。
袋「BigBag」の中身:これはEmptyBagという袋です。
袋「BigBag」の中身:これはBagOfBookという袋です。
袋「BagOfBook」の中身:これはBook1という本です。
袋「BagOfBook」の中身:これはBook2という本です。
袋「BigBag」の中身:これはBagOfSoftという袋です。
袋「BagOfSoft」の中身:これはSoft1というソフトです。
袋「BagOfSoft」の中身:これはSoft2というソフトです。
袋「BigBag」の中身:これはBigBookという本です。

[Book1(中身クラス)の説明]
これはBook1という本です。

javaでのBridgeパターン

Bridgeパターンは、機能のクラス階層と実装のクラス階層を橋渡しするパターンです。
機能のクラス階層と実装のクラス階層を分けることで、実装毎に機能を書かなくて良くなりますし、その逆に機能毎に実装を書かなくて良くなります。
結果として、重複した記述を減らすことができ、保守性を向上させることができます。
 
今回は、RPGのダメージ計算を模したサンプルコードを作成してみました。
機能のクラス階層ではダメージの与え方を定義し、実装のクラス階層では具体的なダメージ計算式を定義しています。
Bridgeパターンの適用により、ダメージの与え方毎にダメージ計算式を書かなくて良くなっている点、逆にダメージ計算式毎にダメージの与え方を書かなくて良くなっている点に注目です。
 
【サンプルコード】
・DamageCalc.java
// ダメージ計算機能
public class DamageCalc {

    protected DamageCalcImpl damageCalcImpl;
    protected int damage = 0;

    public DamageCalc (DamageCalcImpl damageCalcImpl) {
        this.damageCalcImpl = damageCalcImpl;
    }

    protected void clearDamage() {
        damage = 0;
    }

    public int getDamage() {
        return this.damage;
    }

    public void damageCalc(int offenceValue, int defenceValue) {
        clearDamage();
        damage = damageCalcImpl.damageCalc(offenceValue,defenceValue);
    }

}
 
・MultipleDamageCalc.java
// ダメージ計算機能(連続攻撃に対応するための拡張)
public class MultipleDamageCalc extends DamageCalc {

    public MultipleDamageCalc(DamageCalcImpl damageCalcImpl) {
        super(damageCalcImpl);
    }

    public void multipleDamageCalc
        (int offenceValue, int defenceValue, int damageTimes) {
        clearDamage();
        for(int i = 0;i < damageTimes;i++) {
            damage += damageCalcImpl.damageCalc(offenceValue,defenceValue);
        }
    }

}
 
・DamageCalcImpl.java
// ダメージ計算の実装(抽象クラス)
public abstract class DamageCalcImpl {

    public abstract int damageCalc(int offenceValue, int defenceValue);

}
 
・NormalDamageCalcImpl.java
// ダメージ計算の実装(通常のダメージ計算)
public class NormalDamageCalcImpl extends DamageCalcImpl {

    public int damageCalc(int offenceValue, int defenceValue) {
        int damage = 0;
        damage = offenceValue - (defenceValue/2);
        if (damage < 0) {
            damage = 0;
        }
        return damage;
    }

}
 
・IgnoreDefenceDamageCalcImpl.java
// ダメージ計算の実装(防御無視のダメージ計算)
public class IgnoreDefenceDamageCalcImpl extends DamageCalcImpl {

    public int damageCalc(int offenceValue, int defenceValue) {
        int damage = 0;
        damage = offenceValue;
        if (damage < 0) {
            damage = 0;
        }
        return damage;
    }

}
 
・DamageCalcMain.java
// ダメージ計算のメインクラス
public class DamageCalcMain {
    public static void main(String[] args){

        int offenceValue = 100;
        int defenceValue = 50;
        int multipleDamageTimes = 4; // 連続攻撃の場合のみ使用

        NormalDamageCalcImpl normalDamageCalcImpl =
            new NormalDamageCalcImpl();
        IgnoreDefenceDamageCalcImpl ignoreDefenceDamageCalcImpl =
            new IgnoreDefenceDamageCalcImpl();

        System.out.println("■通常のダメージ計算(1回攻撃)");
        DamageCalc damageCalc1 = new DamageCalc(normalDamageCalcImpl);
        damageCalc1.damageCalc(offenceValue, defenceValue);
        System.out.println(" ダメージ:" + damageCalc1.getDamage());

        // 実装を独立させることで、実装毎に機能を書かなくて良い
        // (通常の1回攻撃メソッドと
        //   防御無視の1回攻撃メソッドを用意しなくて良い)
        System.out.println("■防御無視のダメージ計算(1回攻撃)");
        DamageCalc damageCalc2 =
            new DamageCalc(ignoreDefenceDamageCalcImpl);
        damageCalc2.damageCalc(offenceValue, defenceValue);
        System.out.println(" ダメージ:" + damageCalc2.getDamage());

        // 機能を独立させることで、機能毎に実装を書かなくて良い
        // (1回攻撃の防御無視メソッドと
        //   複数攻撃の防御無視メソッドを用意しなくて良い)
        System.out.println("■防御無視のダメージ計算(4回攻撃)");
        MultipleDamageCalc multipleDamageCalc1 =
            new MultipleDamageCalc(ignoreDefenceDamageCalcImpl);
        multipleDamageCalc1.multipleDamageCalc
            (offenceValue, defenceValue, multipleDamageTimes);
        System.out.println(" ダメージ:" + multipleDamageCalc1.getDamage());

    }
}
 
【実行結果】
■通常のダメージ計算(1回攻撃)
 ダメージ:75
■防御無視のダメージ計算(1回攻撃)
 ダメージ:100
■防御無視のダメージ計算(4回攻撃)
 ダメージ:400

javaでのBuilderパターン

Builderパターンは、処理内容を定義するBuilderクラスと、処理順番を定義するDirectorクラスの2つに分けることで、柔軟に処理を変更できるようにするパターンです。
Builderクラスの定義により処理内容が変わっても都度処理内容を記述する必要が無くなり、Directorクラスの定義により処理順番が変わっても都度処理順番を記述する必要が無くなります。
 
今回は、RPGのダメージ計算を模したサンプルコードを作成してみました。
ゲームが変わると処理内容が変わるのですが、Builderクラスを定義することで処理内容が変更されても利用者側で処理内容を都度記述する必要が無くなります。
また、ダメージを与える手段によってはダメージが可変になったり固定になったりするのですが、Directorクラスを定義することで利用者側で都度ダメージを可変にしたり固定にしたりするための具体的な記述をする必要が無くなります。
 
【サンプルコード】
・DamageBuilder.java
// ダメージ計算で使用する共通のメソッドを定義
public interface DamageBuilder {

    public void baseDamageCalc(int offenceValue,int defenceValue);
    public void damageRandomise();
    public Object getResult();

}
 
・DamageDto.java
public class DamageDto {

    private int DamageValue;

    public int getDamageValue() {
        return DamageValue;
    }

    public void setDamageValue(int damageValue) {
        DamageValue = damageValue;
    }

}
 
・DamageBuilderGameA.java
import java.util.Random;

// ゲームA用の計算式を記述
public class DamageBuilderGameA implements DamageBuilder {

    private DamageDto damageDto;

    public DamageBuilderGameA(){
        this.damageDto = new DamageDto();
    }

    public void baseDamageCalc(int offenceValue, int defenceValue) {
        int baseDamageValue = offenceValue - (defenceValue / 2);
        if (baseDamageValue < 0) {
            baseDamageValue = 0;
        }
        damageDto.setDamageValue(baseDamageValue);
    }

    public void damageRandomise() {
        // 4/5、5/5、6/5の何れかの値をかけてランダム化する
        damageDto.setDamageValue(damageDto.getDamageValue() *
                ( (new Random()).nextInt(3) + 4) / 5);
    }

    public Object getResult() {
        return this.damageDto;
    }

}
 
・DamageBuilderGameB.java
import java.util.Random;

// ゲームB用の計算式を記述
public class DamageBuilderGameB implements DamageBuilder {

    private DamageDto damageDto;

    public DamageBuilderGameB(){
        this.damageDto = new DamageDto();
    }

    public void baseDamageCalc(int offenceValue, int defenceValue) {
        int baseDamageValue = offenceValue / (defenceValue / 10);
        if (baseDamageValue < 0) {
            baseDamageValue = 0;
        }
        damageDto.setDamageValue(baseDamageValue);
    }

    public void damageRandomise() {
        // 9/10、10/10、11/10の何れかの値をかけてランダム化する
        damageDto.setDamageValue(damageDto.getDamageValue() *
                ( (new Random()).nextInt(3) + 9) / 10);
    }

    public Object getResult() {
        return this.damageDto;
    }

}
 
・DamageDirectorVariable.java
// ダメージがランダムで変化するケースで使用するDirector
public class DamageDirectorVariable {

    private DamageBuilder damageBuilder;
    public DamageDirectorVariable (DamageBuilder damageBuilder){
        this.damageBuilder = damageBuilder;
    }

    public void constract(int offenceValue,int defenceValue){
        damageBuilder.baseDamageCalc(offenceValue,defenceValue);
        damageBuilder.damageRandomise();
    }

}
 
・DamageDirectorFixed.java
// ダメージが固定のケースで使用するDirector
public class DamageDirectorFixed {

    private DamageBuilder damageBuilder;
    public DamageDirectorFixed (DamageBuilder damageBuilder){
        this.damageBuilder = damageBuilder;
    }

    public void constract(int offenceValue,int defenceValue){
        damageBuilder.baseDamageCalc(offenceValue,defenceValue);
    }

}
 
・DamageCalcMain.java
public class DamageCalcMain {
    public static void main(String[] args){

        int offenceValue = 100;
        int defenceValue = 100;
        int damageValue = 0;

        DamageBuilderGameA damageBuilderGameA = new DamageBuilderGameA();
        DamageBuilderGameB damageBuilderGameB = new DamageBuilderGameB();
        DamageDirectorVariable damageDirectorValiableGameA =
                new DamageDirectorVariable(damageBuilderGameA);
        DamageDirectorFixed damageDirectorFixedGameA =
                new DamageDirectorFixed(damageBuilderGameA);
        DamageDirectorVariable damageDirectorValiableGameB =
                new DamageDirectorVariable(damageBuilderGameB);

        System.out.println
                ("■ゲームA用のダメージ計算(ダメージ可変ケース)");
        damageDirectorValiableGameA.constract(offenceValue, defenceValue);
        damageValue =
                ( (DamageDto) damageBuilderGameA.getResult()).getDamageValue();
        System.out.println(" ダメージ:" + damageValue);

        // Directorを定義することで利用者側で処理の順番を都度定義しなくて良い
        System.out.println
                ("■ゲームA用のダメージ計算(ダメージ固定ケース)");
        damageDirectorFixedGameA.constract(offenceValue, defenceValue);
        damageValue =
                ( (DamageDto) damageBuilderGameA.getResult()).getDamageValue();
        System.out.println(" ダメージ:" + damageValue);

        // Builderを定義することで利用者側で処理の内容を都度定義しなくて良い
        System.out.println
                ("■ゲームB用のダメージ計算(ダメージ可変ケース)");
        damageDirectorValiableGameB.constract(offenceValue, defenceValue);
        damageValue =
                ( (DamageDto) damageBuilderGameB.getResult()).getDamageValue();
        System.out.println(" ダメージ:" + damageValue);

    }
}
 
【実行結果】
■ゲームA用のダメージ計算(ダメージ可変ケース)
 ダメージ:40
■ゲームA用のダメージ計算(ダメージ固定ケース)
 ダメージ:50
■ゲームB用のダメージ計算(ダメージ可変ケース)
 ダメージ:9

java:オブジェクトの中身をコピーする方法(cloneメソッド実装)

参照型変数(主に、自分で作成したクラスのオブジェクト)をコピーする場合、単純に「=」で代入するだけでは不十分な場合があります。
参照型変数の中身は参照先(オブジェクトのメモリ領域を示すポインタ)です。
「=」で代入するだけでは、参照先だけがコピーされて、参照しているものは同じという状態になるので、コピー先の変更がコピー元に影響してしまいますし、その逆にコピー元の変更がコピー先に影響してしまいます。
 
これを避けたい場合は、cloneメソッドを用いて中身を丸ごとコピー(新たにメモリ領域を確保し書き込み)する必要があります。
以下の記述を行うことで、cloneメソッドを使用することができるようになります。
・cloneしたいクラスでCloneableインターフェースを実装する
・Cloneableインターフェースのclone()メソッドをオーバーライドする
・clone()メソッド内で、super.clone()メソッドを使用する
 (super=Objectクラス)
・super.clone()メソッドはObject型で返ってくるので自身の型でキャストを行う
・CloneNotSupportedExceptionが返ってくる可能性があるので例外処理する
 
以下、サンプルコードです。
参照先のみコピーした場合とcloneで中身をコピーした場合を比較しています。
参照先のみコピーした場合は、コピー後にコピー先を変更した際にコピー元が影響を受けていますが、cloneで中身をコピーした場合は影響を受けていません。
 
【サンプルコード】
・CloneableItem.java
// cloneしたいクラスはCloneableインターフェースを実装する必要がある
public class CloneableItem implements Cloneable {

  // イミュータブルではない参照型変数を含む場合はそれも一緒にcloneが必要
  // (今回は割愛)
  private int itemId;
  private String itemName;

  public int getItemId() {
    return itemId;
  }
  public void setItemId(int itemId) {
    this.itemId = itemId;
  }
  public String getItemName() {
    return itemName;
  }
  public void setItemName(String itemName) {
    this.itemName = itemName;
  }

  // Cloneableインターフェースのclone()メソッドをオーバーライド
  @Override
  public CloneableItem clone() {
    CloneableItem clonedItem = null;
    // CloneNotSupportedExceptionを返す可能性があるので例外処理が必要
    try {
      // Object型で返ってくるのでキャストが必要
      clonedItem = (CloneableItem)super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return clonedItem;
  }

}

・ItemCloneMain.java
public class ItemCloneMain {
  public static void main(String[] args){

    System.out.println("[参照先のみコピーした場合]");
    CloneableItem cloneableItem1 = new CloneableItem();
    cloneableItem1.setItemId(1);
    cloneableItem1.setItemName("sword");
    System.out.println
      ("コピー前のコピー元オブジェクト:ID=" +
       cloneableItem1.getItemId() +
       " NAME=" +
       cloneableItem1.getItemName());
    CloneableItem cloneableItem2 = cloneableItem1;
    System.out.println
      ("コピー後のコピー元オブジェクト:ID=" +
       cloneableItem1.getItemId() +
       " NAME=" +
       cloneableItem1.getItemName());
    System.out.println
      ("コピー後のコピー先オブジェクト:ID=" +
       cloneableItem2.getItemId() +
       " NAME=" +
       cloneableItem2.getItemName());
    cloneableItem2.setItemId(2);
    cloneableItem2.setItemName("shield");
    System.out.println
      ("編集後のコピー元オブジェクト :ID=" +
       cloneableItem1.getItemId() +
       " NAME=" +
       cloneableItem1.getItemName());
    System.out.println
      ("編集後のコピー先オブジェクト :ID=" +
       cloneableItem2.getItemId() +
       " NAME=" +
       cloneableItem2.getItemName());

    System.out.println("");
    System.out.println("[cloneで中身をコピーした場合]");
    CloneableItem cloneableItem3 = new CloneableItem();
    cloneableItem3.setItemId(3);
    cloneableItem3.setItemName("armor");
    System.out.println
      ("コピー前のコピー元オブジェクト:ID=" +
       cloneableItem3.getItemId() +
       " NAME=" +
       cloneableItem3.getItemName());
    CloneableItem cloneableItem4 = cloneableItem3.clone();
    System.out.println
      ("コピー後のコピー元オブジェクト:ID=" +
       cloneableItem3.getItemId() +
       " NAME=" +
       cloneableItem3.getItemName());
    System.out.println
      ("コピー後のコピー先オブジェクト:ID=" +
       cloneableItem4.getItemId() +
       " NAME=" +
       cloneableItem4.getItemName());
    cloneableItem4.setItemId(4);
    cloneableItem4.setItemName("helm");
    System.out.println
      ("編集後のコピー元オブジェクト :ID=" +
       cloneableItem3.getItemId() +
       " NAME=" +
       cloneableItem3.getItemName());
    System.out.println
      ("編集後のコピー先オブジェクト :ID=" +
       cloneableItem4.getItemId() +
       " NAME=" +
       cloneableItem4.getItemName());

  }
}
 
【実行結果】
[参照先のみコピーした場合]
コピー前のコピー元オブジェクト:ID=1 NAME=sword
コピー後のコピー元オブジェクト:ID=1 NAME=sword
コピー後のコピー先オブジェクト:ID=1 NAME=sword
編集後のコピー元オブジェクト :ID=2 NAME=shield
編集後のコピー先オブジェクト :ID=2 NAME=shield

[cloneで中身をコピーした場合]
コピー前のコピー元オブジェクト:ID=3 NAME=armor
コピー後のコピー元オブジェクト:ID=3 NAME=armor
コピー後のコピー先オブジェクト:ID=3 NAME=armor
編集後のコピー元オブジェクト :ID=3 NAME=armor
編集後のコピー先オブジェクト :ID=4 NAME=helm

unix/linux:いつものログイン後画面と違う/いつもは使えるコマンドが使えない場合の対処→ホームディレクトリをls -la で確認する

サーバ管理者から提供されたLinuxの環境で開発している時に、もしかしたら
 
・ログインした後の左側の表示(プロンプト)が違う
 (いつもは [hoge]$ なのに bash4.1$ になってしまった、等)
 
・いつもは使えている(環境独自の)コマンドが使えなくなった
 
といったことがあるかもしれません。
 
その場合は、ログイン直後のディレクトリ(ホームディレクトリ)の下で「ls -la」のコマンドを発行し、他の開発者のコマンド発行結果と見比べてください。
「.bash_profile(.bash_login、.profile)」「.bashrc」等のファイルを誤って消してしまっている可能性が高いので、もし消してしまっていたら、他の開発者のファイルをコピーして配置してください。
(ユーザ毎に記述が異なる可能性があるので、念のためviコマンドを発行して中身は確認してください)
 
-------------------
 
以下、背景の説明です。
 
プロンプトの表示やコマンドの設定は、「.bash_profile(.bash_login、.profile)」「.bashrc」等のファイルで行います。
これらのファイルは、ホームディレクトリ直下に配置することで、自動的に読み込まれます。
 
頭に「.」がついているファイルは「ドットファイル」と呼ばれ、通常はlsコマンドを発行しても存在を確認することができません。
Windowsで言う隠しファイルのようなものです)
-aオプションをつけてlsコマンドを発行することで、存在を確認することができます。

人的ミスを考慮した手順の作成

システムを運用する上で、手作業での運用作業は避けられないものです。
例を挙げると、プログラムの不具合の対応でデータを補正する、自動的にデプロイできない特殊なプログラムをデプロイする、といった運用作業が発生します。
 
手作業での運用作業では、コマンドの打ち間違いによる人的ミスがつきものです。
人的ミスを減らすためには手順を作り込むことが大事ですが、その手順に従っていても単純な入力ミスは防ぎきれません。
そこで、入力ミスしたとしても、ミスによる影響をなるべく限定できるような手順にすることが大事です。
フールプルーフ的な考え方です)
 
-----------------------------
 
データベースに登録している商品について補正をかける例を挙げて具体的に見ていきます。
 
商品テーブルのレコードとして、主キーは「商品コード」、従属項目として「メーカーコード」「商品種別」があるとします。
ここで、「メーカー側の情報公開の誤りにより、メーカーコード"100"の一部商品について、商品種別が"e"から"c"に変えなければならない」という要件が急遽出てきて、手作業によるデータベースの補正が必要になったとします。
また、対象の商品コードは"100"と"101"であると補正手順上で事前に特定できたとします。
 
ここで、主キーである「商品コード」を特定したからといって、
 update 商品テーブル set 商品種別 = "c" where 商品コード in ("100","101");
というSQL文を発行するのは危険です。
このSQL文だと、商品コードの特定の誤りや商品コードを打ち間違いが発生した場合に、他メーカーの商品や他の商品種別の商品に影響を与えてしまう恐れがあります。
 
ここは、
 update 商品テーブル set 商品種別 = "c" where 商品コード in ("100","101") and メーカーコード = "100" and 商品種別 = "e";
というSQL文を発行する手順にした方が安全です。
対象商品は「商品コード in ("100","101")」で一意に特定しているので「メーカーコード = "100"」「商品種別 = "e"」という条件はあっても無くても更新結果は変わらないのですが、前述のような誤りが発生した時に「メーカーコード = "100"」かつ「商品種別 = "e"」の商品に影響を限定することができます。
 
-----------------------------
 
これは一例ですが、システム障害を減らす上では、「人間はどんなに気を付けていてもミスをするものだ」という前提に立った上で、ミスによる影響をなるべく限定するような策を講じることが大事です。

ターン制・COMと1対1で対戦するブラウザゲームのサンプル

Javascriptゲームエンジンである「enchant.js」で簡単なブラウザゲームをサンプルで作ってみましたので、ソースコードを公開します。
ターン制で、COMと1対1で対戦するタイプのゲームのサンプルです。
新しいゲームを作る時の雛形にする等の形でお役に立てれば幸いです。
 
【ゲームの説明】
ダイスを2個振って、出た目の合計が偶数か奇数かを予想するゲームです。
COMと予想し合うことで勝ち負けを競います。
COMは弱いものと強いものが設定可能で、弱いものは予想を外す確率が高いです。
 
ソースコード
こちらに公開しました。

https://1drv.ms/u/s!AivF3bzWXOzuh1xXk5hscKYqkLkM

 
【遊び方】
1.index.html をChrome等のブラウザで開く。
2.ボタンを押してCOMの難易度設定を行う。

f:id:akira2kun:20200119210228j:plain

3.ボタンを押して偶数か奇数かを予想する。

f:id:akira2kun:20200119210241j:plain

4.ダイス振る→結果判定→COMの予想→ダイス振る→結果判定、と自動で遷移する。

f:id:akira2kun:20200119210259j:plain

5.対戦結果が表示される。ボタンを押すと2に戻る。

f:id:akira2kun:20200119210314j:plain