ジャンケンゲームの実装
オンラインゲームでは、ゲームのクライアントとサーバーがデータの送受信を行いながらゲームを構成します。 まずははじめに、クライアントとサーバーがそれぞれ手を選択して勝敗を決めるようなジャンケンゲームを作ってみましょう。 以下のような通信が行われます。
| 通信方向 | 送信内容 | 
|---|---|
| クライアント→サーバー | プレイヤーが選択した手 | 
| クライアント←サーバー | 相手が選択した手 | 
ここでは単純化のために、サーバー側はユーザーが操作せず、ランダムに手を選択するようにします。
サーバープログラム
サーバープログラムは非常にシンプルです。
TCPのコネクションを待ち受け、何らかのデータを受け取ったら、サーバー側が選択した手を表す0から2の整数を返します。 グー(G)、チョキ(C)、パー(P)をそれぞれ0、1、2とすることにします。
サーバープログラムの全体は以下の通りです。
import processing.net.*;
Server server;
void setup() {
  server = new Server(this, 5204);
}
void draw() {
}
void clientEvent(Client client) {
  client.read();
  server.write(int(random(3)));
}
クライアントプログラム
クライアントプログラムは、ゲーム画面の描画やユーザー入力の受け取り、サーバーとの通信など様々な処理を行う必要があります。
画面に3つのボタンを表示し、それぞれグー、チョキ、パーを表すことにします。
まずは、ジャンケンの手の処理を考えます。
ジャンケンの手を表すために以下のような列挙型 Hand を導入します。
Hand 型は、手を表す0から2の数値(id)や、文字列への変換処理を持たせます。
また、ジャンケンの勝敗のロジックもここに含めます。
勝ち、負け、あいこの結果は 列挙型 Result で表すことにします。
enum Result {
  WIN,
  DRAW,
  LOOSE
}
enum Hand {
  G(0),
  C(1),
  P(2);
  private int id;
  private Hand(final int id) {
    this.id = id;
  }
  static Hand valueOf(int id) {
    if (id == 0) {
      return Hand.G;
    } else if (id == 1) {
      return Hand.C;
    } else {
      return Hand.P;
    }
  }
  int getId() {
    return this.id;
  }
  String toString() {
    if (this.id == 0) {
      return "G";
    } else if (this.id == 1) {
      return "C";
    } else {
      return "P";
    }
  }
  Result battle(Hand hand) {
    if (this == Hand.G) {
      if (hand == Hand.G) {
        return Result.DRAW;
      } else if (hand == Hand.C) {
        return Result.WIN;
      } else {
        return Result.LOOSE;
      }
    } else if (this == Hand.C) {
      if (hand == Hand.G) {
        return Result.LOOSE;
      } else if (hand == Hand.C) {
        return Result.DRAW;
      } else {
        return Result.WIN;
      }
    } else {
      if (hand == Hand.G) {
        return Result.WIN;
      } else if (hand == Hand.C) {
        return Result.LOOSE;
      } else {
        return Result.DRAW;
      }
    }
  }
}
手の選択のボタンを以下のように Button クラスとして実装します。
ボタンに関する情報の保持や、クリックの判定、描画の処理等を含みます。
class Button {
  Hand hand;
  int cx, cy, size;
  color fillColor;
  boolean selected = false;
  Button(Hand hand, int cx, int cy, int size, color fillColor) {
    this.hand = hand;
    this.cx = cx;
    this.cy = cy;
    this.size = size;
    this.fillColor = fillColor;
  }
  boolean clicked() {
    return dist(mouseX, mouseY, cx, cy) <= size / 2;
  }
  void draw() {
    noStroke();
    if (this.selected) {
      fill(this.fillColor);
    } else {
      fill(this.fillColor, 100);
    }
    ellipse(this.cx, this.cy, this.size, this.size);
    fill(0);
    textAlign(CENTER, CENTER);
    text(this.hand.toString(), this.cx, this.cy);
  }
}
ゲームの進行状況を列挙型 Status で表すことにします。
INPUT はユーザーの入力待ち、WAIT はサーバーからの結果待ち、RESULT は結果画面の表示を表します。
INPUT 状態から、ボタンが選択されると WAIT 状態になり、サーバーから相手の手を受け取ると RESULT 状態に遷移します。
RESULT 状態から画面がクリックされるとまた INPUT 状態に戻るようにします。
enum Status {
  INPUT,
  WAIT,
  RESULT
}
ゲームの初期化及び描画処理は以下のようになります。
Client client;
Status currentStatus = Status.INPUT;
Button[] buttons;
Hand enemy = null;
void setup() {
  size(400, 400);
  client = new Client(this, "127.0.0.1", 5204);
  int size = 100;
  buttons = new Button[3];
  buttons[0] = new Button(Hand.G, size / 2, height / 2, size, color(255, 0, 0));
  buttons[1] = new Button(Hand.C, width / 2, height / 2, size, color(0, 255, 0));
  buttons[2] = new Button(Hand.P, height - size / 2, height / 2, size, color(0, 0, 255));
}
void draw() {
  background(255);
  Hand hand = null;
  for (Button button : buttons) {
    button.draw();
    if (button.selected) {
      hand = button.hand;
    }
  }
  if (currentStatus == Status.RESULT && hand != null) {
    fill(0);
    textAlign(CENTER, CENTER);
    text("enemy : " + enemy.toString(), width / 2, 60);
    Result result = hand.battle(enemy);
    if (result == Result.WIN) {
      text("win", width / 2, 100);
    } else if (result == Result.DRAW) {
      text("draw", width / 2, 100);
    } else {
      text("lose", width / 2, 100);
    }
  }
}
ユーザーからの入力の処理と通信の処理を以下のようにイベントハンドラーとして実装します。
それぞれの処理に応じて、currentStatus を変更し、ゲームの状態を更新します。
グー、チョキ、パーのボタンが押されたらその内容をサーバーに送信し、サーバーからの応答を受け取ると結果画面へと遷移させます。
void mouseClicked() {
  if (currentStatus == Status.INPUT) {
    int selected = -1;
    for (Button button : buttons) {
      if (button.clicked()) {
        selected = button.hand.getId();
        button.selected = true;
      }
    }
    if (selected != -1) {
      client.write(selected);
      currentStatus = Status.WAIT;
    }
  } else if (currentStatus == Status.RESULT) {
    for (Button button : buttons) {
      button.selected = false;
    }
    enemy = null;
    currentStatus = Status.INPUT;
  }
}
void clientEvent(Client client) {
  enemy = Hand.valueOf(client.read());
  currentStatus = Status.RESULT;
}
クライアントプログラムの全体は以下の通りです。
import processing.net.*;
enum Result {
  WIN,
  DRAW,
  LOOSE
}
enum Hand {
  G(0),
  C(1),
  P(2);
  private int id;
  private Hand(final int id) {
    this.id = id;
  }
  static Hand valueOf(int id) {
    if (id == 0) {
      return Hand.G;
    } else if (id == 1) {
      return Hand.C;
    } else {
      return Hand.P;
    }
  }
  int getId() {
    return this.id;
  }
  String toString() {
    if (this.id == 0) {
      return "G";
    } else if (this.id == 1) {
      return "C";
    } else {
      return "P";
    }
  }
  Result battle(Hand hand) {
    if (this == Hand.G) {
      if (hand == Hand.G) {
        return Result.DRAW;
      } else if (hand == Hand.C) {
        return Result.WIN;
      } else {
        return Result.LOOSE;
      }
    } else if (this == Hand.C) {
      if (hand == Hand.G) {
        return Result.LOOSE;
      } else if (hand == Hand.C) {
        return Result.DRAW;
      } else {
        return Result.WIN;
      }
    } else {
      if (hand == Hand.G) {
        return Result.WIN;
      } else if (hand == Hand.C) {
        return Result.LOOSE;
      } else {
        return Result.DRAW;
      }
    }
  }
}
enum Status {
  INPUT,
  WAIT,
  RESULT
}
class Button {
  Hand hand;
  int cx, cy, size;
  color fillColor;
  boolean selected = false;
  Button(Hand hand, int cx, int cy, int size, color fillColor) {
    this.hand = hand;
    this.cx = cx;
    this.cy = cy;
    this.size = size;
    this.fillColor = fillColor;
  }
  boolean clicked() {
    return dist(mouseX, mouseY, cx, cy) <= size / 2;
  }
  void draw() {
    noStroke();
    if (this.selected) {
      fill(this.fillColor);
    } else {
      fill(this.fillColor, 100);
    }
    ellipse(this.cx, this.cy, this.size, this.size);
    fill(0);
    textAlign(CENTER, CENTER);
    text(this.hand.toString(), this.cx, this.cy);
  }
}
Client client;
Status currentStatus = Status.INPUT;
Button[] buttons;
Hand enemy = null;
void setup() {
  size(400, 400);
  client = new Client(this, "127.0.0.1", 5204);
  int size = 100;
  buttons = new Button[3];
  buttons[0] = new Button(Hand.G, size / 2, height / 2, size, color(255, 0, 0));
  buttons[1] = new Button(Hand.C, width / 2, height / 2, size, color(0, 255, 0));
  buttons[2] = new Button(Hand.P, height - size / 2, height / 2, size, color(0, 0, 255));
}
void draw() {
  background(255);
  Hand hand = null;
  for (Button button : buttons) {
    button.draw();
    if (button.selected) {
      hand = button.hand;
    }
  }
  if (currentStatus == Status.RESULT && hand != null) {
    fill(0);
    textAlign(CENTER, CENTER);
    text("enemy : " + enemy.toString(), width / 2, 60);
    Result result = hand.battle(enemy);
    if (result == Result.WIN) {
      text("win", width / 2, 100);
    } else if (result == Result.DRAW) {
      text("draw", width / 2, 100);
    } else {
      text("lose", width / 2, 100);
    }
  }
}
void mouseClicked() {
  if (currentStatus == Status.INPUT) {
    int selected = -1;
    for (Button button : buttons) {
      if (button.clicked()) {
        selected = button.hand.getId();
        button.selected = true;
      }
    }
    if (selected != -1) {
      client.write(selected);
      currentStatus = Status.WAIT;
    }
  } else if (currentStatus == Status.RESULT) {
    for (Button button : buttons) {
      button.selected = false;
    }
    enemy = null;
    currentStatus = Status.INPUT;
  }
}
void clientEvent(Client client) {
  enemy = Hand.valueOf(client.read());
  currentStatus = Status.RESULT;
}
動作確認
サーバープログラム、クライアントプログラムの順に実行し、動作確認してみましょう。
入力待ち状態

結果表示状態
