プログラミングの魔物

エラー、バグ、仕様変更と戦うブログ

アドベンチャーゲームのアルゴリズム

遊びのレシピの2章を読みながら適当にメモ

アドベンチャーゲームとは?

小説のようなゲームから育成ゲームなど幅広い種類のゲームがある。
システムに手を加えることで別の種類のゲームを作ることが出来る。
他のゲームの基礎となる仕組みを持ったゲーム。

選択の結果により物語が進んでいく

選択肢をプレイヤーが選んで進める。
選択肢は直接的な設問だけではなく、様々な形を持つ。

  • 入手したアイテム
  • イベント
  • ミニゲーム、その他の要因

プレイヤーの意志で物語を変えられる、主人公と一緒に疑似体験できる。
インタラクティブな要素も面白さに繋がる。

アドベンチャーゲームのサンプルプログラム

Luaで簡単にコードを書くとこんな感じ。
本の内容とは別物なのであまり参考にならないかも知れない。

local step = 1
first = {
	"貴方は怪しい館に閉じ込められてしまいました",
	"部屋を移動して脱出しましょう",
	"最初の部屋です",
	"1:右の部屋へ行く 2:左の部屋へ行く",
	{function () return {"ドアの向こうには人喰い虎が居た。貴方は死んでしまった"} end,
	function ()
		return {
		"1:正面の部屋へ行く 2:右の部屋へ行く 3:元の部屋に戻る",
		{function () return {"ドアを開けた途端、灼熱のマグマが流れ込んできた", "貴方は死んでしまった"} end,
		function () return {"貴方は脱出に成功した"} end,
		function () step = 4 return first end}} end}}
local events = first
while step <= #events do
	if type(events[step]) == "string" then
		print(events[step])
	end
	local input = io.read()
	if type(events[step + 1]) == "table" then
		local func = events[step + 1][tonumber(input)]
		if type(func) == "function" then
			step = 1
			events = func()
		end
	else
		step = step + 1
	end
end

Luaで組む場合は、シナリオを上のようなテーブルに変換するプログラムを用意すると良いと思う。
もちろん他のアプローチもあるので、この方法が最善とは限らない。

サンプルプログラムの適当解説

eventsテーブルは逐次処理される。
stepの指す要素が文字列なら表示し、その次の要素がテーブルであれば選択肢として処理する。
選択肢の処理はユーザーから入力されたインデックスの要素が関数かどうかを判断し、関数であれば実行する。関数は戻り値としてeventsに代入可能なテーブルを返す。
上記の内容をeventsテーブルの最後まで行う。

この記事で読み進めている本

ゲームプログラミング遊びのレシピ―アルゴリズムとデータ構造 (C magazine)

ゲームプログラミング遊びのレシピ―アルゴリズムとデータ構造 (C magazine)

オブジェクト間での特性の移動

リファクタリング 7章

オブジェクト指向の設計では責任をどこに置くかで悩む。
リファクタリングによって以前の決定を変えられるので、悩みを軽減できる。

メソッドの移動

クラスの内部に他のクラスへの依存性が高いメソッドが存在する → メソッドを他のクラスに移動して、元のメソッドは委譲・または取り除く

  • 責任の集合を整理する
  • メソッドの参照している情報の割合が別のクラスに寄っていないか調べる。特にフィールドを移動した後
  • メソッドとのやり取りが多いオブジェクトを探す
  • 判断しにくい場合は直感で。後から変更できる。

フィールドの移動

あるクラスのフィールドが、現在または将来に渡って他のクラスで使われることのほうが多い → 他のクラスに移動

  • 他のクラスのほうが多くアクセスするなら移動する

クラスの抽出

2つのクラスでなされるべき作業を1つのクラスで行なっている → 分割して新しいクラスを作る

  • クラスは成長する。成長によって責任が増えると複雑になり手がつけにくくなる
  • 大きすぎて簡単に理解できないクラスは一部のデータとメソッドを新しいクラスへ切り分ける

クラスのインライン化

そのクラスは大したことをしていない → 別のクラスに特性を移動して削除

  • 「クラスの抽出」とは逆
  • リファクタリングで責任を移動した結果小さくなったクラスなどに適用する

委譲の隠蔽

クライアントがあるオブジェクトの委譲クラスをコールしている → サーバにメソッドを作って委譲を隠す

  • カプセル化によって情報を隠蔽することで変更を伝えるオブジェクトが少なくなり手を加えやすくなる

仲介人の除去

クラスがやっていることは単純な委譲 → クライアントに委譲オブジェクトを直接コールさせる

  • 「委譲の隠蔽」の逆。カプセル化はたしかに有効だが、新たな特性を使う度に委譲メソッドを追加する必要がある
  • 隠蔽のバランスをコントロールする

外部メソッドの導入

利用中のサーバクラスにメソッドを追加する必要があるが、そのクラスを変更できない → クライアントクラスにサーバクラスのインスタンスを第1引数にとるメソッドを作る

  • 別のクラスにメソッドを加えたいけど変更できない場合に、そのクラスを操作するメソッドを用意する
  • 大量の外部メソッドが作られたり、多くのクラスが同じ外部メソッドを必要とする場合は「局所的拡張の導入」を適用する

局所的拡張の導入

利用中のサーバクラスにメソッドを幾つか追加する必要があるが、クラスを変更できない → 追加されるメソッドを備えた新たなクラスを作成する。そのクラスは元のクラスのサブクラスまたはラッパー

  • 追加するメソッドが増えると「外部メソッドの導入」では対処しきれなくなる
  • サブタイプにすることで元クラスの特性を生かしつつ拡張できる

この記事で読み進めている本

リファクタリング―プログラムの体質改善テクニック (Object Technology Series)

リファクタリング―プログラムの体質改善テクニック (Object Technology Series)

  • 作者: マーチンファウラー,Martin Fowler,児玉公信,平澤章,友野晶夫,梅沢真史
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/05
  • メディア: 単行本
  • 購入: 93人 クリック: 3,054回
  • この商品を含むブログ (295件) を見る

WindowsでのLua開発(Macでも可)

Windows上でLuaの開発環境をいくつか使ってみた感想。
それぞれの開発環境のメリットデメリット。

よく考えてみたら全部Macでも使えるツールだった。

VimにTrinityプラグインとlua-supportプラグインを導入

  • メリット
    • ctagsが使える(Macしか試してない)
    • Vimのプラグインによる拡張性が高い(カラーテーマやneocomplcache、neosnippet)
    • Vimmerバンザイ
  • デメリット
    • Trinityは他のプラグインとの相性が悪かったり不安定だったりするので設定に手間がかかる
    • 保存時の文法エラーチェックがスムーズではない(感覚的なもの)

LuaDevelopmentTools(LDT)

  • メリット
  • デメリット
    • 基本的に英語。SJISには自力で対応する必要がある

ZeroBraneStudio

ちょっとしか触ってないけど・・・
メリット・デメリットはLDTと似たような感じだが、細かい機能では一歩及ばない印象を受けた。
デバッグ機能はLDTより強力で、スタックに加えてウォッチが使える。また、Analyzeによってwarningを表示できる。
UTF8オンリー。
アプリケーションの構成を見たところLuaで作成されている様子。ソースを書き換えることで拡張できそう。


ZeroBraneStudioのデバッグ機能はウォッチが使える分LDTより強力だが、強調表示やリアルタイムの文法チェックなど細かい機能はLDTに劣る。
とりあえずLDTがオススメ。

本読み

遊びのレシピの1章を読んだ。

10年以上前の本で内容も古いけど、アルゴリズムは(あまり)変化しないので、参考になる部分もあるだろうと思い中古で購入。
1章の内容はC++なのに何故かポリモーフィズムの代わりに関数ポインタを使っていたりする。

アルゴリズムとは?

  • 問題を解決する時の考え方。言語が変わっても考え方は変わらない

テーブル参照

  • 計算省略のため、式を予めテーブル化すること

連鎖するデータ

セーブ/ロード

1章は最近の開発から見てあまり参考にならない情報も多いが、次章以降は様々なゲームのアルゴリズムが載っているので参考にして行きたい。
おそらく次からは実機で組むのでノートを取るのはこれが最後。

この記事で読み進めている本

ゲームプログラミング遊びのレシピ―アルゴリズムとデータ構造 (C magazine)

ゲームプログラミング遊びのレシピ―アルゴリズムとデータ構造 (C magazine)

メソッドの構成

リファクタリング 6章

5章は6章以降にあるリファクタリングカタログの導入部なのでノートを取らず読むだけ。

メソッドの抽出

ひとまとめにできるコードの断片に、その目的を表すような名前をつける

  • うまく命名されたメソッドは再利用しやすくなる
  • 上位のメソッドが読みやすくなる
  • メソッドの抽出を行うことで行数は増えるが、抽出元のメソッドを短くし、コメントを除去できる
  • 小さなメソッドは良い名前が付けられなければ無効。抽出することで明快さが向上するならそうする

メソッドのインライン化

メソッドの本体が名前をつけて呼ぶまでもなく単純明快 → メソッド本体をコール元にインライン化してメソッドを除去する。

  • メソッドの中身が単純で、敢えて分ける必要性を感じなければインライン化する
  • メソッドの整理の前段階として、まとめ直すために使われることもある。(「メソッドオブジェクトによるメソッドの置き換え」の前など)
  • 間接化しすぎた場合に無用なメソッドを除去する目的で適用する

一時変数のインライン化

簡単な式によって1度だけ代入される一時変数があり、それが他のリファクタリングの障害となっている → 一時変数への参照をすべて式で置き換える

  • 「問い合わせによる一時変数の置き換え」の一部として使われることが多い

問い合わせによる一時変数の置き換え

一時変数を使って式の結果を保持している → メソッド化してすべての変数を置き換える

  • 一時変数はメソッドを長くする要因となる。メソッドに置き換えてしまえば他のメソッドからも参照できる
  • 行いやすい状況:一時変数が1度だけ代入される場合や、代入される値を作る式が副作用を起こさない場合
  • パフォーマンスは問題になるまで気にしない

説明用変数の導入

複雑な式 → その式の結果または部分的な結果をその目的を説明する名前を付けた一時変数に代入する

  • 読みにくい式は分解してわかりやすくする
  • 複雑な条件、長いアルゴリズムなど
  • 通常はメソッドの抽出を行い、それが導入しにくい場合に使う

一時変数の分離

複数回代入される一時変数があるが、それはループ変数でも一時変数を集める変数でもない → 代入ごとに別の一時変数に分ける

  • 同じ一時変数を使いまわしている場合、他のリファクタリングがしにくくなる
  • 特殊な場合を除き一時変数への代入は1度だけにする

パラメータへの代入の除去

引数への代入が行われている → 一時変数を使う

  • パラメータを一時変数の代わりに利用しているようなケースに適用する

メソッドオブジェクトによるメソッドの置き換え

長いメソッドで「メソッドの抽出」を適用できないようなローカル変数の使い方をしている → メソッド自身をオブジェクトとし、すべてのローカル変数をそのオブジェクトのフィールドとする。そうすればそのメソッドを同じオブジェクト内のメソッド群に分解できる

  • 問い合わせによる一時変数の置き換えをしても、ローカル変数がメソッドの分解の障害になる場合に適用する

アルゴリズムの置き換え

アルゴリズムをより分かりやすいものに置き換えたい → メソッドの本体を新たなアルゴリズムで置き換える

  • より分かりやすい方法、より良い方法が見つかったら置き換えるべき
  • 問題がはっきりしないうちに書いたコードの改善、重複した機能を持つライブラリの使用など

この記事で読み進めている本

リファクタリング―プログラムの体質改善テクニック (Object Technology Series)

リファクタリング―プログラムの体質改善テクニック (Object Technology Series)

  • 作者: マーチンファウラー,Martin Fowler,児玉公信,平澤章,友野晶夫,梅沢真史
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/05
  • メディア: 単行本
  • 購入: 93人 クリック: 3,054回
  • この商品を含むブログ (295件) を見る

日経ソフトウェア4月号の記事とスクリプト言語による効率的ゲーム開発7章を読んで

ゲームシステムを内部DSLで記述したら面白そうだと思いました。


DSLというのはドメイン特化言語といって、SQLのように特定の分野に特化した言語のこと。
また、SQLのように独立した言語を外部DSL、言語内において新たに定義された文法を内部DSLと呼ぶ。

内部DSLはメソッドチェーンとか、先日作ったリファレンスチェーンなんかが含まれると思う。
少し変わった文法だけど、特定の操作は簡潔に記述できるようになる・・・というのがDSL。


Luaの場合は主にメタテーブルを使うと内部DSLの拡張が出来そうなので、ここ数日メタテーブルでリファレンスチェーンを作って遊んでいた。
まあリファレンスチェーンは効率悪いので普通に算術演算子等を置き換えるほうがいいだろう。

しかしあまりDSLを多用すると他人が見た時や後から読み返した時に混乱するかもしれない。

Luaの文法で遊ぶ2「リファレンスチェーン」

リファレンスチェーンを簡単に利用できるように分解してみた。

サンプルプログラム

--リファレンスチェーン生成関数
local function ref(t, call)
	return setmetatable({t}, {
		__index = function (self, k)
			table.insert(self, k)
			return self
		end,
		__call = call})
end

Class = {}

--リファレンスチェーンを処理するメソッド。ローカル関数でも問題はない。
function Class:apply()
	local u = unpack or table.unpack  --5.1と5.2の互換用
	self[1][self[#self]](u(self, 1, #self - 1))
end

--サンプルメソッド
function Class:show(...)
	for i, v in ipairs{...} do
		print(v)
	end
end

--関数風呼び出し
function Class:__call()
	return ref(Class, Class.apply)
end

setmetatable(Class, Class)

Class().hoge.hage.hige.show()

出力結果

hoge
hage
hige

applyでリファレンスチェーンを分解し、showメソッドに"hoge","hage","hige"を渡している。
・・・遊んでないで本読もう。