LoginSignup
8
0

More than 5 years have passed since last update.

Board Game on PostgreSQL!

Posted at

この記事はPostgreSQL Advent Calendar 2018の16日目の記事です。

はじめに

誰かさんの影響で、私も PostgreSQL でクダラナイことをやってみたくなりました。

冬の風物詩

冬といえば、やっぱり 軍人将棋 ですよね!

軍人将棋(ぐんじんしょうぎ)とは、通常2人で行うボードゲームの一種。軍事将棋(ぐんじしょうぎ)、または行軍将棋(こうぐんしょうぎ)とも呼ばれる。軍隊の階級や兵種を元にした駒を用いて盤上にて競う。欧米圏ではストラテゴ(Stratego)という名称の同種のボードゲームがプレイされている。
Wikipediaより

小さい頃、父を審判役に姉と競い合ったのを思い出します。
ということで、PostgreSQLを使って 軍人将棋 を実現してみます。
(審判役もPostgreSQLにやらせれば不要になるので一石二鳥だし!)

ルール

軍人将棋を知らない方のためにサクッとルールをまとめると、、、
- 16種類23個の駒を盤面に置く(相手に見えないように裏返して)
- 16種類の駒は各々動き方が決まってるので、それにしたがって動かす
- 相手の駒の位置に動かしたらバトル開始!
- 審判は公平に判断する(16種類の駒の勝ち負けは下図のように決まってる)
- 相手の動ける駒を全て負かすor偉い駒で総司令部を占拠したらゲーム終了
って感じです。

勝敗表(0は勝ち、1は引き分け、2は負け。9は条件次第)
  koma  | taisho | cyusho | shosho | plane | tank | taisa | cyusa | shosa | taii | cyui | shoi | kihei | kohei | spy | mine | flag 
--------+--------+--------+--------+-------+------+-------+-------+-------+------+------+------+-------+-------+-----+------+------
 大将   |      1 |      0 |      0 |     0 |    0 |     0 |     0 |     0 |    0 |    0 |    0 |     0 |     0 |   2 |    2 |    9
 中将   |      2 |      1 |      0 |     0 |    0 |     0 |     0 |     0 |    0 |    0 |    0 |     0 |     0 |   0 |    2 |    9
 少将   |      2 |      2 |      1 |     0 |    0 |     0 |     0 |     0 |    0 |    0 |    0 |     0 |     0 |   0 |    2 |    9
 飛行機 |      2 |      2 |      2 |     1 |    0 |     0 |     0 |     0 |    0 |    0 |    0 |     0 |     0 |   0 |    0 |    9
 タンク |      2 |      2 |      2 |     2 |    1 |     0 |     0 |     0 |    0 |    0 |    0 |     0 |     0 |   0 |    2 |    9
 大佐   |      2 |      2 |      2 |     2 |    2 |     1 |     0 |     0 |    0 |    0 |    0 |     0 |     0 |   0 |    2 |    9
 中佐   |      2 |      2 |      2 |     2 |    2 |     2 |     1 |     0 |    0 |    0 |    0 |     0 |     0 |   0 |    2 |    9
 少佐   |      2 |      2 |      2 |     2 |    2 |     2 |     2 |     1 |    0 |    0 |    0 |     0 |     0 |   0 |    2 |    9
 大尉   |      2 |      2 |      2 |     2 |    2 |     2 |     2 |     2 |    1 |    0 |    0 |     0 |     0 |   0 |    2 |    9
 中尉   |      2 |      2 |      2 |     2 |    2 |     2 |     2 |     2 |    2 |    1 |    0 |     0 |     0 |   0 |    2 |    9
 少尉   |      2 |      2 |      2 |     2 |    2 |     2 |     2 |     2 |    2 |    2 |    1 |     0 |     0 |   0 |    2 |    9
 騎兵   |      2 |      2 |      2 |     2 |    2 |     2 |     2 |     2 |    2 |    2 |    2 |     1 |     0 |   0 |    2 |    9
 工兵   |      2 |      2 |      2 |     2 |    2 |     2 |     2 |     2 |    2 |    2 |    2 |     2 |     1 |   0 |    0 |    9
 スパイ |      0 |      2 |      2 |     2 |    2 |     2 |     2 |     2 |    2 |    2 |    2 |     2 |     2 |   1 |    2 |    9
 地雷   |      0 |      0 |      0 |     2 |    0 |     0 |     0 |     0 |    0 |    0 |    0 |     0 |     2 |   0 |    1 |    9
 軍旗   |      9 |      9 |      9 |     9 |    9 |     9 |     9 |     9 |    9 |    9 |    9 |     9 |     9 |   9 |    9 |    9

軍旗は後ろの駒と同じ強さになるので、ちょっと面倒くさい。

実現方法

相手の駒が見えてしまうと、ゲームが成立しないのでユーザ分けたり、権限を整理したりする必要があります。
board.png

ちなみに、パーティションで盤面テーブルを作成してますが、PostgreSQL10だとパーティションキー(player)をまたぐ更新ができません。PostgreSQL11からはOKなので、持っててよかったPostgreSQL11です。

実装例

細々とルールを盛り込んでいったら、コードもそこそこの量になってしまったので、ポイントのみ解説します。

judge.sql
CREATE OR REPLACE FUNCTION update_internal(target_player_id int, target_koma_id int, target_pos_x int, target_pos_y int) RETURNS VOID AS $$
BEGIN
    DELETE FROM all_koma WHERE pos_x = target_pos_x AND pos_y = target_pos_y;
    INSERT INTO all_koma VALUES (target_player_id, target_koma_id, target_pos_x, target_pos_y);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE OR REPLACE FUNCTION judge() RETURNS trigger AS $$
DECLARE
    col_name    TEXT;
    row_num     INT;
    judge_num   INT;
    back_pos_y  INT;
    back_koma_id    INT;
BEGIN
    -- NEW:チャレンジャー、OLD:元の駒
    SELECT koma_col INTO col_name FROM vs_rule WHERE koma_id = NEW.koma_id;
    row_num  = OLD.koma_id;
    EXECUTE 'SELECT ' || col_name || ' FROM vs_rule WHERE koma_id = ' || row_num INTO judge_num;
    IF judge_num = 0 THEN
        -- 元の勝ち:何もしない
        return NULL;
    ELSIF judge_num = 1 THEN
        -- 相打ち:空にする
        PERFORM update_internal(0, 98, OLD.pos_x, OLD.pos_y);
    ELSIF judge_num = 2 THEN
        -- チャレンジャーの勝ち:NEWで上書き
        PERFORM update_internal(NEW.player_id, NEW.koma_id, NEW.pos_x, NEW.pos_y);
    ELSIF judge_num = 9 THEN
        -- 軍旗駒に対する条件チェック
        IF OLD.player_id = 1 THEN
            back_pos_y = OLD.pos_y - 1;
        ELSIF OLD.player_id = 2 THEN
            back_pos_y = OLD.pos_y + 1;
        END IF;
        IF back_pos_y < 0 OR back_pos_y > 8 THEN
            PERFORM update_internal(NEW.player_id, NEW.koma_id, NEW.pos_x, NEW.pos_y);
        ELSE
            SELECT koma_id INTO back_koma_id FROM all_koma WHERE pos_x = OLD.pos_x AND pos_y = back_pos_y;
            row_num  = back_koma_id;
            EXECUTE 'SELECT ' || col_name || ' FROM vs_rule WHERE koma_id = ' || row_num INTO judge_num;
            IF judge_num = 0 THEN
                return NULL;
            ELSIF judge_num = 1 THEN
                PERFORM update_internal(0, 98, OLD.pos_x, OLD.pos_y);
            ELSIF judge_num = 2 THEN
                PERFORM update_internal(NEW.player_id, NEW.koma_id, NEW.pos_x, NEW.pos_y);
            END IF;
        END IF;
    END IF;
    return NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER koma_judge_p0 BEFORE UPDATE ON p0_koma
FOR EACH ROW EXECUTE PROCEDURE judge();
CREATE TRIGGER koma_judge_p1 BEFORE UPDATE ON p1_koma
FOR EACH ROW EXECUTE PROCEDURE judge();
CREATE TRIGGER koma_judge_p2 BEFORE UPDATE ON p2_koma
FOR EACH ROW EXECUTE PROCEDURE judge();

update_internal関数で盤上の駒を更新します。UPDATEだと無限ループしちゃいそうなので、実際はDELETE+INSERTで更新してます。
また、この関数は各プレイヤーが直接呼び出すこともある(移動後のマスを空にする。ちなみに空は駒ID=98です)ので、「SECURITY DEFINER」をつけてスーパーユーザ権限で実行されるようになってます。

judgeトリガ関数で勝敗表を参照してどっちの駒を残すか決定してます。この関数を各パーティションテーブル(p0_koma, p1_koma, p2_koma)に仕掛けることで、審判不要の世界が形成されてます。

Let's play!

本当は駒の移動についてもきちんとルールに則って実装したかったけど、面倒なので、そこは紳士的にゲームを楽しむべし!という方針にしました。
盤面の表示は、みんな大好き¥crosstabviewで実現してます。ちゃんと相手の駒が見えない(?になってる)ので、軍人将棋に独特の 心理戦 を再現できました!
play.png

実際の駒は2から3文字(大将とか飛行機とか)になり表示が崩れてしまうので、盤上の駒は全角英字で表現してるのが切ないですが、まぁなんとなくそれっぽい動きをするし、まぁいっかって感じです。

おわりに

ということで16日目でした。明日17日目はホヤホヤのsawada_masahikoさんの登場です!

8
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0