ジャンケンゲームの実装

オンラインゲームでは、ゲームのクライアントとサーバーがデータの送受信を行いながらゲームを構成します。 まずははじめに、クライアントとサーバーがそれぞれ手を選択して勝敗を決めるようなジャンケンゲームを作ってみましょう。 以下のような通信が行われます。

通信方向 送信内容
クライアント→サーバー プレイヤーが選択した手
クライアント←サーバー 相手が選択した手

ここでは単純化のために、サーバー側はユーザーが操作せず、ランダムに手を選択するようにします。

サーバープログラム

サーバープログラムは非常にシンプルです。

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

動作確認

サーバープログラム、クライアントプログラムの順に実行し、動作確認してみましょう。

入力待ち状態

01

結果表示状態

02

results matching ""

    No results matching ""