2007年 05月 06日

Ragnarok Online のホムンクルス AI

Lua 云々はこれのためにやっていたやつです。
公式配布のもの (ROのインストールディレクトリの AI 以下) は最小限の機能が最小限の実装で書かれていて、そのままだと読みにくく、書きかえにくい。

なので、とりあえずデフォルトのAIの機能を移植するかたちで、書きやすように OOP 化しはじめてみた ( http://lab.lowreal.net/trac/browser/c/ro/AI/ )

デフォルトの Util.lua にある List テーブルがなぜかクラスではないので List.lua として分離し、ruby っぽいメソッドをいくつか追加した。

l = List.new(1, 2, 3)
l:join() --> "1, 2, 3"

l1 = List.new(4, 5, 6)
l2 = l + l1
l2:map(function (i)
    return i * i
end) --> 1, 4, 9, 16, 25, 36

クラスの実装は http://subtech.g.hatena.ne.jp/cho45/20070502/1178092846 のまま。


GetDistance 系はダサいので、Actor クラスを作ってラップしてある (Actor.lua)。Actor クラスはモンスターやホムやプレイヤーに対して使えるクラス。

a1 = Actor.new(id)
a2 = Actor.new(other_id)

a1:distanceFrom(a2) > 3
a1:isInSight(a2) --> a2 が 20 セル以内にいるとき true
a1:isInAttackSight(a2) --> a2 が攻撃可能な範囲にいるとき true (今は単に 1 セル以内)

a1:isStand() --> a1 が立っているとき true

これによって GetV を使わなくてすむはず。定数を何回も書くのがめんどいのでちょっと楽かなぁと思う。


Homun クラス (Homun.lua) は飼っているホムに使うクラス。Actor のサブクラスになっていて、Actor のメソッドはよべる。

h = Homun.new(myid)
h:owner() --> オーナーキャラ (つまり操作している自分のキャラ) を Actor インスタンスで取得
h:move(x, y) --> x, y に移動
h:attack(target) --> target を攻撃 (attack できる範囲にはいっていないと何もおきないと思う)

スキル使うのはこれから実装する (AIばっかり書いててまだスキルがとれてない)


RO からは AI.lua が呼びだされる。こいつにはデバッグメッセージの出力と、本体でエラーをはいたときに RO のほうに伝達しないようにしてる (RO に伝達すると、ダイアログが表示されまくって Alt-F4 するしかない。さすが! 重力クオリティ!) AI.lua は Main.lua をロードして、Main 関数を実行する。

Main.lua には Main 関数と HogeAI クラスと reload 関数が定義されてる。

Main 関数では HogeAI クラスをインスタンス化して、RO から呼びだしがくるたびに、インスタンス化した AI クラスの act メソッドを呼びだしている。CurrentAI とかいうグローバル変数に突っ込んでいるのは、そのうち切りかえとかできたらなぁとか考えているからだけど、実装できるか謎い。

HogeAI は今つくってる劣化デフォルトAI で、やってることはデフォルト AI とそんな違いはないはず (実装してないのがいくつかある)。デフォルト AI の処理をとりあえず移植したら DefaultAI クラスにでもして、それのサブクラスを作ったらいいかなぁとか思ったけど、まだやってなくてわからない。

Try と xpcall

xpcall とかいう関数ふたつとってエラーキャッチする関数をみのがしてた!!
Try をつかいつづけるか悩む

RO AI

2007年 05月 04日

Lua try catch

pcall とかいう安全な関数呼出なんていう関数があるんだけど、つかいにくい。

ok, err = pcall(function ()
end)
if not ok then exit end

みたいな。

function try(tryFun)
	local suc, err = pcall(tryFun)
	return {
		catch = function (catchFun)
			if not suc then
				catchFun(err)
			end
		end
	}
end


try(function ()
	print "hoge"
	error("error")
end).catch(function (e)
	print(e)
	print "Catched"
end)

とかやってみる。Io の try catch に似てる構造

2007年 05月 02日

Lua のオブジェクト指向

できるだけ同じ名前を出さないという方向で書きかたを考えてみる。

#!/usr/bin/lua

List = {
	new = function ()
		return setmetatable({
			-- instance variables
			firstIndex = 0,
			lastIndex  = -1,
		}, { __index = List.prototype })
	end,

	prototype = {
		unshift = function (self, value)
			self.firstIndex  = self.firstIndex - 1
			self[self.firstIndex] = value
			return self
		end,

		push = function (self, value)
			self.lastIndex = self.lastIndex + 1
			self[self.lastIndex] = value
			return self
		end,

		shift = function (self)
			local first = self.firstIndex
			if first > self.lastIndex then
				return nil
			end
			local value = self[first]
			self[first] = nil
			self.firstIndex = first + 1
			return value
		end,

		pop = function (self)
			local last = self.lastIndex
			if self.firstIndex > last then
				return nil
			end
			local value = self[last]
			self[last] = nil
			self.lastIndex = last - 1
			return value
		end,

		clear = function (self)
			for i,v in ipairs(self) do
				self[i] = nil
			end
			self.firstIndex = 0
			self.lastIndex  = -1
		end,

		size  = function (self)
			return self.lastIndex - self.firstIndex + 1
		end,

		first = function (self)
			return self[self.firstIndex]
		end,

		last = function (self)
			return self[self.lastIndex]
		end,
	},
}


l = List.new()
print(l)

-- basic tests
l:push(1)
l:push(2)
assert(l:pop() == 2)
assert(l:pop() == 1)

l:push(1)
l:unshift(2)
assert(l:size()  == 2)
assert(l:first() == 2)
assert(l:last()  == 1)
assert(l:shift() == 2)
assert(l:shift() == 1)

assert(l:size() == 0)
l:push(1)
assert(l:size() == 1)
l:clear()
assert(l:size() == 0)

-- multi-obj tests
l1 = List.new()
l1:push(1)
l:push(1)
l1:push(2)
l:push(2)
assert(l1:pop() == 2)
assert(l:pop()  == 2)
assert(l1:pop() == 1)
assert(l:pop()  == 1)

-- prototype 書きかえてみる
List.prototype.hoge = function ()
	return "hoge"
end
assert(l:hoge() == "hoge")

-- プロトタイプとってきたいとき
assert(getmetatable(l).__index == List.prototype)
print "Tests are completed"

new の中ででてきてしまう。うーん。


Lua は基本的にオブジェクト指向がどうこうっていう話はあんまりなくて、テーブルとメタテーブルをごにょって自分でやるらしい。
あるテーブルはメタテーブルを持てて (そしてメタテーブルもまたテーブルなので、さらにメタテーブルを持てるはず?)、そのメタテーブルに定義された __hoge なキーの値が特別な意味をもってたりする (演算子のオーバーロードもこれでやるみたい。ためしてない)。__index キーには元のテーブルでキーが見付からないときに使われるテーブルをセットする。

self を暗黙的に使うシンタックスもあるけど、名前が何回もでてきてうざくなる。

Lua の OOP , クラスベース

function Class(obj)
	-- metatable も __index も同じにする (再帰参照)
	-- 演算子オーバーロードも直感的に書けるように
	obj.__index = obj
	-- superclass の設定
	setmetatable(obj, { __index = obj.super})
	return setmetatable({
		new = function(...)
			local newObj = {}
			setmetatable(newObj, obj)
			newObj:initialize(unpack(arg))
			return newObj
		end
	}, obj)
end

List = Class { super = nil,
	__tostring = function ()
		return "#<Class List>"
	end,
	
	initialize = function (self, ...)
		self.firstIndex = 0
		self.lastIndex  = -1
		for i,v in ipairs(arg) do self:push(v) end
	end,

	unshift = function (self, value)
		self.firstIndex  = self.firstIndex - 1
		self[self.firstIndex] = value
		return self
	end,

	push = function (self, value)
		self.lastIndex = self.lastIndex + 1
		self[self.lastIndex] = value
		return self
	end,

	shift = function (self)
		local first = self.firstIndex
		if first > self.lastIndex then
			return nil
		end
		local value = self[first]
		self[first] = nil
		self.firstIndex = first + 1
		return value
	end,

	pop = function (self)
		local last = self.lastIndex
		if self.firstIndex > last then
			return nil
		end
		local value = self[last]
		self[last] = nil
		self.lastIndex = last - 1
		return value
	end,

	clear = function (self)
		for i,v in ipairs(self) do
			self[i] = nil
		end
		self.firstIndex = 0
		self.lastIndex  = -1
	end,

	size  = function (self)
		return self.lastIndex - self.firstIndex + 1
	end,

	first = function (self)
		return self[self.firstIndex]
	end,

	last = function (self)
		return self[self.lastIndex]
	end,
}

l = List.new()
print(l)

-- basic tests
l:push(1)
l:push(2)
assert(l:pop() == 2)
assert(l:pop() == 1)

l:push(1)
l:unshift(2)
assert(l:size()  == 2)
assert(l:first() == 2)
assert(l:last()  == 1)
assert(l:shift() == 2)
assert(l:shift() == 1)

assert(l:size() == 0)
l:push(1)
assert(l:size() == 1)
l:clear()
assert(l:size() == 0)

-- multi-obj tests
l1 = List.new()
l1:push(1)
l:push(1)
l1:push(2)
l:push(2)
assert(l1:pop() == 2)
assert(l:pop()  == 2)
assert(l1:pop() == 1)
assert(l:pop()  == 1)

l2 = List.new(1, 2, 3, 4, 5)
assert(l2:pop() == 5)
assert(l2:pop() == 4)
assert(l2:shift() == 1)
assert(l2:shift() == 2)
assert(l2:pop() == 3)

-- prototype 書きかえてみる
getmetatable(l).hoge = function ()
	return "hoge"
end
assert(l:hoge() == "hoge")

List2 = Class { super = List,
	__tostring = function ()
		return "#<Class List2>"
	end,

	initialize = function (self, values)
		self.super.initialize(self, values)
	end,

	test = function (self, str) 
		print(str)
		self:push(str)
	end
}

l2 = List2.new()
print(l2)
l2:test("hoe")
l2:push(1)
l2:push(2)
assert(l2:pop() == 2)
assert(l2:pop() == 1)

print "Tests are completed"

どうだろ

2007年 05月 01日

参照渡しを誤解してた

2007年 04月 30日

Test

#!/usr/bin/ruby

require "rubygems"
require "exifr"
require "pathname"

Pathname.glob("*.{jpg,JPG}") do |f|
	date = EXIFR::JPEG.new(f.to_s).date_time_original
	path = Pathname.new(date.strftime("%Y/%m%d"))
	path.mkpath
	f.rename(path + f)
	puts "#{f} -> #{path}"
	raw =  Pathname.new(f.to_s.sub(/jpg$/i, "CR2"))
	if raw.exist?
		puts "#{raw} -> #{path}"
		raw.rename(path + raw)
	end
end
2007年 04月 27日

カレントディレクトリにある jpg を exif の日付にしたがいフォルダにふりわける

~/pictures/ 以下にてきとーにほうりこんだ jpg を 2007/0427 とかにふりわけるだけ

#!ruby

require "rubygems"
require "exifr"
require "pathname"

Pathname.glob("*.jpg") do |f|
	date = EXIFR::JPEG.new(f.to_s).date_time_original
	path = Pathname.new(date.strftime("%Y/%m%d"))
	path.mkpath
	f.rename(path + f)
end
2007年 04月 25日

Mac の Address Book が utf-8 な vcf を読めない

utf-16 だといける。めんどす