PL/pgSQLサンプルプログラム
PL/pgSQL入門用のサンプルプログラムとして、○×ゲームを作成しました。
○×ゲームの仕組み
まず○×を書くマスを用意する必要がありますが、今回は○×をテーブルに記録します。
テーブルは行番号・列番号・マークの3列用意します。
行番号と列番号でマスを表現し、そのマスに記入されたマークを表現します。
レコードそのまま表示してしまうと○×ゲームになりませんが、表示するときにマス目状に表示すればOKです。
○×ゲームのテーブル作成
CREATE TABLE marubatsu_field (
row_id integer NOT NULL CHECK (1 <= row_id AND row_id <= 3),
column_id integer NOT NULL CHECK (1 <= column_id AND column_id <= 3),
mark char(1),
PRIMARY KEY (row_id, column_id)
);
○×ゲームの関数
CREATE OR REPLACE FUNCTION marubatsu(rownum integer, columnnum integer) RETURNS text AS $$
DECLARE
row_count integer;
init_row_num CONSTANT integer := 3;
init_column_num CONSTANT integer := 3;
sente_mark marubatsu_field.mark%TYPE;
gote_rec RECORD;
display_cur CURSOR FOR
SELECT row_id, column_id, COALESCE(mark, ' ') AS mark
FROM marubatsu_field ORDER BY row_id, column_id;
display_text text := '';
BEGIN -- 実行部の開始
-- 引数チェック
IF rownum < 1 OR rownum > 3 THEN
RAISE EXCEPTION '行指定の数字が有効範囲外を指定しています。';
ELSIF columnnum < 1 OR columnnum > 3 THEN
RAISE EXCEPTION '列指定の数字が有効範囲外を指定しています。';
END IF;
-- 行が初期化行数じゃなければ初期化
SELECT COUNT(*) INTO row_count FROM marubatsu_field;
IF row_count != (init_row_num * init_column_num) THEN
DELETE FROM marubatsu_field;
FOR i IN 1 .. init_row_num LOOP
FOR j IN 1 .. init_column_num LOOP
INSERT INTO marubatsu_field (row_id, column_id) VALUES (i, j);
END LOOP;
END LOOP;
END IF;
-- 先手が選んだ位置に○を入れる
SELECT mark INTO sente_mark FROM marubatsu_field WHERE row_id = rownum AND column_id = columnnum;
IF sente_mark IS NOT NULL THEN
-- すでにマークが入っていたらメッセージ
RAISE EXCEPTION '指定された位置はすでにマークされています。';
ELSE
UPDATE marubatsu_field SET mark = '○' WHERE row_id = rownum AND column_id = columnnum;
END IF;
-- 後手がマークされていないところをランダムで選択して×を入れる
BEGIN -- サブブロック開始
SELECT * INTO gote_rec FROM marubatsu_field WHERE mark is null ORDER BY random() limit 1;
IF NOT FOUND THEN
-- 全部埋まっていたら例外を発生させる
RAISE EXCEPTION SQLSTATE 'ABCDE' USING MESSAGE = 'もう書き込める場所がありません。';
ELSE
UPDATE marubatsu_field SET mark = '×' WHERE row_id = gote_rec.row_id AND column_id = gote_rec.column_id;
END IF;
EXCEPTION
-- 全部埋まっていた場合に発生した例外をキャッチしてメッセージを表示
WHEN SQLSTATE 'ABCDE' THEN
RAISE NOTICE '%', SQLERRM;
END; -- サブブロック終了
-- 全てのレコードを取得して、表示用のテキストを作成する
FOR rec IN display_cur LOOP
CASE rec.column_id
WHEN 1 THEN display_text := display_text || ' ' || rec.mark;
WHEN 2 THEN display_text := display_text || ' | ' || rec.mark;
WHEN 3 THEN display_text := display_text || ' | ' || rec.mark || ' ' || chr(10);
END CASE;
END LOOP;
-- 表示用のテキストを返す
RETURN display_text;
END; -- 実行部の終了
$$ LANGUAGE plpgsql;
おまけ
実は上記のソースには、勝利判定処理がありません。
修正して組み込んでみてもいいかもしれません。
また、あくまでもPL/pgSQLのサンプルなので、機能を説明するために冗長なソースになっているところもあります。もっと簡潔にできるようトライしてもいいでしょう。