忍者ブログ
趣味と実益を兼ねて将棋のプログラムを作ってみたいなと思っている私の試行錯誤や勉強したことを綴ってゆく予定です。 一番の目的はソフトウェア設計やオブジェクト指向に慣れること ・・・だったのですが、元々の興味や電王戦に触発されたこともあり、AI製作も目指してみたいと今は考えています。 ※はてなに移転しました。
カレンダー
04 2024/05 06
S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
プロフィール
HN:
cwron
性別:
男性
自己紹介:
将棋の腕前はムラがあるのでなんとも言えませんが、将棋ウォーズや81Dojo基準だと約三段てことになってます。リアルで指す機会が希少です。
Share
ブログ内検索
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

一応、棋譜のメタ情報(プレイヤ名のみ)と棋譜本体部分を抽出することには成功しました。動いたことによって駒を取ったのか?王手か?みたいな部分は全くなのですが、一応棋譜学習を行う上での必要最低限にはかなり近づいたんじゃないかと思います。αを名乗れるクオリティかどうかは疑問ですけども。


拍手[0回]

フォーマットについて。内部形式には特にデザインパターンとかを使わないことにします。せっかくpythonには便利な辞書型があることですし。簡単なラッパーくらいは作るかもしれませんが。以下(クリックで表示)のような感じを考えています。

隠す

"""
kifudocument = {
	"header":
		"player":
			"+": "*****"
			"-": "*****"
		"期戦とか":
			****
	"body":
		[{
				"type"   : "regular"/"special"
				"command": (RESIGN|CHUDAN|...)/""  # Follow the CSA Specification
				"turn"   : "+"/"-"
				"from"   : (0,0)/ ([1-9],[1-9])
				"to"     : ([1-9], [1-9])
				"piece"  : (FU|KY|KE|GI|KI|KA|HI|OU|TO|NY|NK|NG|UM|RY|NONE)
		}]

基本はCSA仕様に倣うつもり。
"""

class TE:
	class TURN:	# turn
		BLACK="+"
		WHITE="-"
		B=BLACK
		W=WHITE
		N=""
	class PT:	# pieceType
		FU="FU"
		KY="KY"
		KE="KE"
		GI="GI"
		KI="KI"
		KA="KA"
		HI="HI"
		OU="OU"
		TO="TO"
		NY="NY"
		NK="NK"
		NG="NG"
		UM="UM"
		RY="RY"
		NONE=""
	class TYPE:
		REGULAR = "REGULAR"
		SPECIAL = "SPECIAL"
	class COMMAND:
		RESIGN = "RESIGN"
		MATTA  = "MATTA"
		CHUDAN = "CHUDAN"
		NONE = ""

	"""def __init__(self):
		self.type = 
		self.turn = TE.TURN.N
		self.fr = (0,0)
		self.to = (0,0)
		self.pi = PT.NONE
	"""
	@classmethod
	def Create(self, mtype=TYPE.REGULAR, command=COMMAND.NONE, turn=TURN.N, fr=(0,0),to=(0,0),piece=PT.NONE):
		return {
			"type"    : mtype,
			"command" : command,
			"turn"    : turn,
			"from"    : fr,
			"to"      : to,
			"piece"   : piece }

隠す

多分内部表現はこれでほぼ間に合うと思う。この後はこの構造を前提にコードを組んでいきたいので、この表現が変更されないようにしたい。おそらく変更はないと思うが、拡張としてありそうな可能性を挙げるなら

  • 消費時間
  • (コンピュータの場合)読み筋コメント

あたりではなかろうか。消費時間は普通にありうるのであらかじめ追加しとく必要がありそう。コンピュータ将棋作る気であれば読み筋情報はかなりおいしいので、できれば採取したい。flodgateの棋譜限定でいいから何とか格納可能なようにはしときいです。が、「拡張」であればまだ対応は容易なはずなので後回しに。

検討は必要ですけど、実装もさっさと進めたいし、手を動かしたことで得られる考察とか設計の反省点とか、そういうものも大事にしたいので、そのあたりは平行作業ですかね。

実装したコードは以下。かなりゴリゴリやっていますが、棋譜情報の大部分をカットの上無視するようになっているのでその分かなりマイルドではあります。

コードの表示

コードを隠す

本体部分。冒頭でインポートしてるkifudocumentは自分でつくったもの。で表示してるのと同一なので省きます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import string,re,os
import kifudocument as kd

"""
kifudocument = {
	"header":
		"player":
			"black": "*****"
			"white": "*****"
		"期戦とか":
			****
	"body":
		[{
				"type" : "regular"/"special"
				"turn" : "+"/"-"
				"from" : (0,0)/ ([1-9],[1-9])
				"to"   : ([1-9], [1-9])
				"piece": (FU|KY|KE|GI|KI|KA|HI|OU|TO|NY|NK|NG|UM|RY)
		}]
"""

class CSADocumentBuilder(kd.DocumentBuilder):
	def __init__(self, obj):
		self.obj = obj
		self.product = None
		self.meta = None
		self.kifu = None
		self.body = None
		for s in obj:
			if(isinstance(s, SMeta)):
				self.meta = s
			if(isinstance(s, SMove)):
				self.kifu = s.moves
	def buildHeader(self):
		if self.meta is None:
			self.body = {}
			return
		self.body = {"player": self.meta.player}
	def buildBody(self):
		pass
	def getProduct(self):
		return {"header":self.body, "body":self.kifu}

class CSADocumentBuilderFactory(kd.DocumentBuilderFactory):
	@classmethod
	def Create(cls, obj):
		return CSADocumentBuilder(obj)

class Ut:
	mver		= re.compile("^V[1-9](\.[0-9])?")
	mmeta		= re.compile("^($.*|N[+-])")
	mstart		= re.compile("^([+\-]$|P)")
	mmoves		= re.compile("^[+\-%T]")
	mcomment	= re.compile("^'")
	
	isVer 		= staticmethod(lambda l: True if not Ut.mver.match(l) is None else False)
	isMeta		= staticmethod(lambda l: True if not Ut.mmeta.match(l) is None else False)
	isStart		= staticmethod(lambda l: True if not Ut.mstart.match(l) is None else False)
	isMove		= staticmethod(lambda l: True if not Ut.mmoves.match(l) is None else False)
	isComment   = staticmethod(lambda l: True if not Ut.mmoves.match(l) is None else False)
	isValid		= staticmethod(lambda l: True if (Ut.isVer(l) or Ut.isMeta(l) or Ut.isStart(l) or Ut.isMove(l) or Ut.isComment(l)) else False)
	
class SimpleCsaInterpreter:
	def __init__(self, f):
		self.context = f
		self.lines = f.readlines()
		f.seek(0)
	
	def prepare(self):
		self.lines = filter(lambda l: l if not l=="" else None, reduce(lambda a,b: a+b, map(lambda l: re.split("[,\r\n]",l), self.lines)))
		#self.lines = filter(lambda l: l if not re.match("^$",l) else None, self.lines)
	def interpret(self):
		self.prepare()
		stat = SIni()
		for n,l in enumerate(self.lines):
			if re.match("^[\$'T]",l): continue
			try:
				stat = stat.setCurrentLine(l).interpret()
			except Exception,e:
				print "line "+str(n)+": "+e.__str__()
		return stat
	def isSelfState(self):
		pass

class CsaSyntaxError(Exception):
	pass

class CsaState:
	def __init__(self,l="",pre=None):
		self.line = l
		self.prestate = pre
	def setPrestate(self, pre):
		self.prestate = pre
		return self
	def setCurrentLine(self, line):
		self.line = line
		return self
	def delegate(self):
		if(not Ut.isValid(self.line)):
			raise CsaSyntaxError, line
		s = SIni()
		if(Ut.isVer(self.line)):
			s = SVer()
		elif(Ut.isMeta(self.line)):
			s = SMeta()
		elif(Ut.isStart(self.line)):	
			s = SStart()
		elif(Ut.isMove(self.line)):
			s = SMove()
		elif(Ut.isComment(self.line)):
			s = SComment()
		s = s.setCurrentLine(self.line).setPrestate(self)
		return s.interpret()
	def interpret(self):
		if self.isSelfState():
			return self.interpretline()
		else:
			return self.delegate()
	def isSelfState(self):
		return False
	def interpretline(self):
		return SIni().setCurrentLine(self.line).interpret()
	
	def tolist(self):
		ret = []
		cur = self
		while not cur.prestate is None:
			ret.insert(0, cur)
			cur = cur.prestate
		return ret
	def __iter__(self):
		for elm in self.tolist():
			yield elm

class SIni(CsaState):
	def __init__(self,l="",pre=None):
		self.line = l
		self.prestate = None
	def isSelfState(self): return False
	def interpret(self):
		return self.delegate()
	#def _interpretline(self): pass

class ConcleteState(CsaState):
	def __init__(self,l="",pre=SIni()):
		self.line = l
		self.prestate = pre
		self.buf = []
	def pushBuf(self,l):
		self.buf.insert(len(self.buf), l)
	def getLatestBuf(self):
		try:
			return self.buf[-1]
		except Exception:
			return None
	#def __iter__(self):
	#	for l in self.buf:
	#		yield l


class SVer(ConcleteState):
	def isSelfState(self):
		if(Ut.isVer(self.line)):return True
		return False
	def interpretline(self):
		#print "SVer	: "+self.line
		self.pushBuf(self.line)
		return self
class SMeta(ConcleteState):
	BLACK=kd.TE.TURN.BLACK
	WHITE=kd.TE.TURN.WHITE
	def __init__(self,l="",pre=SIni()):
		ConcleteState.__init__(self,l,pre)
		self.player = {SMeta.BLACK: "", SMeta.WHITE: ""}
	def isSelfState(self):
		if(Ut.isMeta(self.line)):return True
		return False
	def interpretline(self):
		#print "SMeta	: "+self.line
		self.pushBuf(self.line)
		if(re.match("^N[+-]",self.line)):
			if(self.line[1]=="+"):
				self.player[SMeta.BLACK]=self.line[2:]
			if(self.line[1]=="-"):
				self.player[SMeta.WHITE]=self.line[2:]
		return self
class SStart(ConcleteState):
	"""
	実装の手間を省くため、内容に関係なく全部平手と解釈させる。
	本来なら、クライアント側にはそれがわからないような感じのコードにならないとおかしいが、
	多分そこすら省くかも
	"""
	def __init__(self, l="",pre=SIni()):
		#ConcleteState.__init__(l,pre)
		ConcleteState.__init__(self,l,pre)
	def isSelfState(self):
		if(Ut.isStart(self.line)):return True
		return False
	def interpretline(self):
		#print "SStart	: "+self.line
		preline = self.getLatestBuf()
		self.pushBuf(self.line)
		return self
class SMove(ConcleteState):
	"""
	一番避けて通れない部分の実装
	データ構造は辞書で簡易的に対応
	"""
	def __init__(self, l="",pre=SIni()):
		ConcleteState.__init__(self,l,pre)
		self.moves = []
	def isSelfState(self):
		if(Ut.isMove(self.line)):return True
		return False
	def interpretline(self):
		#print "SMove	: "+self.line
		self.pushBuf(self.line)
		
		move = {
			"type": "REGULAR",
			"command": "",
			"turn":"",
			"from":(0,0),
			"to":(0,0),
			"piece": "N" }
		if(self.line[0]=="+" or self.line[0]=="-"):
			"""
			Regular move
			"""
			if(self.line[0]=="+"):	move["turn"] = "+"
			if(self.line[0]=="-"):	move["turn"] = "-"	
			
			if(not re.match("^(00[1-9]{2}|[1-9]{4})$",self.line[1:5])): raise CsaSyntaxError, self.line
			move["from"] = (int(self.line[1]), int(self.line[2]))
			move["to"]   = (int(self.line[3]), int(self.line[4]))
			
			if(not re.match("(FU|KY|KE|GI|KI|KA|HI|OU|TO|NY|NK|NG|UM|RY)$", self.line[5:7])):
				print "raise CsaSyntaxError: pi "+self.line[5:7]
				raise CsaSyntaxError, self.line
			move["piece"] = self.line[5:7]
			self.moves.insert(len(self.moves), move)
		elif(self.line[0]=="%"):
			"""
			Special move
			"""
			move["type"]    = "SPECIAL"
			move["command"] = self.line[1:]
			self.moves.insert(len(self.moves), move)
		else:
			print "raise CsaSyntaxError"
			raise CsaSyntaxError, self.line
		return self
class SComment(ConcleteState):
	def isSelfState(self):
		if(Ut.isComment(self.line)):return True
		return False
	def interpretline(self):
		#print "SComment	: "+self.line
		self.pushBuf(self.line)
		return self

#class AbstractDocumentBuilder:
	"""
	Builderの目的は「同じ作成手順」で「中身が異なるもの」を作れること。
	今回適用するのであれば、使い方は割とクライアントに近い側で、コードは
		def constract(self)
			builder = Factory.CreateConcleteDocumentBuilder()
			for s in result:  # iterate stat(s) chain list
				builder.add(s)
			builder.buildMetaInfo()
			builder.buildKifuList()
			document = builder.getProduct()
			return document
	のようになると思われる
	
	Builderを使う動機として、
	Documentを構成する方法とか、表現形式とか、そういう部分の変更に対して
	上記のコードが独立であり、再利用できるということが重要。
	
	"""
#	pass

def test():
	itp = SimpleCsaInterpreter(file("sample.csa"))
	res = itp.interpret()
	"""
	print "=============="
	for elm in res:
		#if(elm.__class__ == SMove):
		#	for l in elm.buf: print l
		#else: 
		print elm.__class__.__name__
		print elm.buf
	
	for elm in res:
		if(isinstance(elm, SMove)):
			for m in elm.moves:
				print m
	"""

	# 棋譜ディレクトリ以下の全てのcsaファイルについて適用
	builder = CSADocumentBuilderFactory.Create(res)
	builder.buildHeader()
	builder.buildBody()
	doc = builder.getProduct()
	print "================================================="
	print doc["header"]
	#print doc["body"]
	

	p = os.popen("ls kifus/*.csa", "r")
	for fn in p.readlines():
		itp = SimpleCsaInterpreter(file(fn[:-1]))
		res = itp.interpret()
		b = CSADocumentBuilderFactory.Create(res)
		b.buildHeader()
		b.buildBody()
		ret = b.getProduct()
		print ret["body"]
		print "========================"

if __name__=="__main__":
	test()

コードを隠す

上記のコードの実行結果は以下。長いので中略してます。

実行結果の表示

実行結果を隠す

=================================================
# サンプル用の棋譜。ヘッダ部とボディ部を表示
{'player': {'+': 'Bonanza', '-': 'YSS'}}
[{'from': (2, 7), 'turn': '+', 'to': (2, 6), 'command': '', 'piece': 'FU', 'type': 'REGULAR'}, ...略... , {'from': (0, 0), 'turn': '+', 'to': (7, 7), 'command': '', 'piece': 'GI', 'type': 'REGULAR'}, {'from': (5, 8), 'turn': '-', 'to': (6, 9), 'command': '', 'piece': 'TO', 'type': 'REGULAR'}]
========================
#... 棋譜リスト全てについて表示 ...
[{'from': (2, 7), 'turn': '+', 'to': (2, 6), 'command': '', 'piece': 'FU', 'type': 'REGULAR'}, ...略... , {'from': (5, 9), 'turn': '-', 'to': (5, 8), 'command': '', 'piece': 'UM', 'type': 'REGULAR'}, {'from': (0, 0), 'turn': '', 'to': (0, 0), 'command': 'TORYO', 'piece': 'N', 'type': 'SPECIAL'}]
========================

実行結果を隠す

...なんかサンプル用の棋譜は投了が反映されてない。なんかおかしいです。

ちなみにサンプル用の棋譜だけ表示させるようにする(test()の最初の方のコード)と以下のようになります。こっちは正常なのでなんかどこかでつまらないミスをしてると思われます。

{'from': (7, 7), 'turn': '+', 'to': (7, 6), 'command': '', 'piece': 'FU', 'type': 'REGULAR'}
{'from': (8, 3), 'turn': '-', 'to': (8, 4), 'command': '', 'piece': 'FU', 'type': 'REGULAR'}
{'from': (7, 9), 'turn': '+', 'to': (6, 8), 'command': '', 'piece': 'GI', 'type': 'REGULAR'}
{'from': (3, 3), 'turn': '-', 'to': (3, 4), 'command': '', 'piece': 'FU', 'type': 'REGULAR'}
{'from': (6, 7), 'turn': '+', 'to': (6, 6), 'command': '', 'piece': 'FU', 'type': 'REGULAR'}
(中略)
{'from': (0, 0), 'turn': '+', 'to': (2, 2), 'command': '', 'piece': 'KI', 'type': 'REGULAR'}
{'from': (0, 0), 'turn': '', 'to': (0, 0), 'command': 'TORYO', 'piece': 'N', 'type': 'SPECIAL'}

実行結果を隠す

これを元に盤上の動きをシミュレートし、意味的な誤りをチェックしていく感じになります。「取った」とか「避けた」「王手」「合駒」などなど、手の性質のようなものを検出することも必要になってくるでしょう。盤と駒については以前のプロトタイプ実装があるのでその辺も活用していけたらいいなあ、と。

これからは既存コードとの絡み合いも増えてくるでしょうし、テストのノウハウもそろそろ勉強が必要ですねぇ...。あとはデザインパターンに固執せず、手抜ける所では辞書型をはじめとする組み込み型を使っていくようにした方がいい。下手にパターンを使うと混乱するからやめとけ、という教えもありますし、組み込み型は自前のクラスと違って十分テストされてて信頼できますので。バグを発見しやすくするための実装、というのも研究していく必要がありそうです。

コードの解説とかした方がいいのでしょうが、自分で見返してみても汚すぎることとと、気力の問題により記事には盛り込みません。解釈部分についてはStateパターンを使っています。この辺の記事も合わせて参照していただけるとうれしいです。では本日はここまで。

PR
お名前
タイトル
文字色
URL
コメント
パスワード
Vodafone絵文字 i-mode絵文字 Ezweb絵文字
Copyright © nounai.output(spaghetiThinking); All Rights Reserved
Powered by ニンジャブログ  Designed by ピンキー・ローン・ピッグ
忍者ブログ / [PR]