忍者ブログ
趣味と実益を兼ねて将棋のプログラムを作ってみたいなと思っている私の試行錯誤や勉強したことを綴ってゆく予定です。 一番の目的はソフトウェア設計やオブジェクト指向に慣れること ・・・だったのですが、元々の興味や電王戦に触発されたこともあり、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ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

そろそろAIっぽいこともしてみようと思った次第。


拍手[0回]

まあ着手可能な手からランダムにピックアップするだけのものですが。

ゲーム部分の実装が簡単であろうという理由から、今回の題材は将棋ではなくオセロです。ゲーム木うんぬんとか、その辺の基礎理論的なことの勉強も必要ですし、実装していく上でのノウハウとかも吸収していきたい。で、これらを同時に効率よく身につけていこうと考えた時に、将棋とかいう複雑なゲームを題材にするのはまだ早いでしょうよ?と思いました。まぁAIに限らず、って気はしますが。

で、以下がソースコードです。所要時間は2~3時間ほどでした。プレイヤーは存在せず、黒と白に同一の思考ルーチンを使って終局まで打たせるようにしています。完全にランダムに置くだけなので、AIっぽい処理はほぼないです。BoardをSubjectとしたObserverを作ることで棋譜の記録、及びそのエクスポート(Loggerクラス)をサポートしてます。この辺は以前の記事でやってたことがちょろっと活きてます。

今回オブジェクト指向っぽいことはObserverくらいです。カプセル化とかは一切考慮してないので、このコードを基に色々やろうとするといずれスパゲティになると思います。なのでこのコードは今回でほぼ廃棄。対局の終了条件とかも雑に判定してて、多分(空白のマスがある&両方これ以上置けないのケースとか)漏れてますし。まぁでも、こうしてちゃちゃっと書いてみたコードが実際に対局を行っている様子を見るのはとても満足です。こういうのがモチベーションて奴なんでしょうね。

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

import os,sys
import random
import string,re

class CannotPut(Exception):
	pass

class Cell:
	def __str__(self):
		return "  "
	def value(self):
		return 0
class Empty(Cell):
	pass
class Black(Cell):
	def __str__(self):
		return " o"
	def value(self):
		return 1
class White(Cell):
	def __str__(self):
		return " x"
	def value(self):
		return -1

class Board:
	def __init__(self):
		self.obs = []
		self.b = []
		for i in range(0,8):
			self.b.insert(len(self.b), [Empty()]*8)
	
	def initBoard(self):
		self.b[3][3] = Black()
		self.b[4][4] = Black()
		self.b[3][4] = White()
		self.b[4][3] = White()

	def __str__(self):
		s = ""
		for i in range(0,8):
			s += "---"*8 + "\n"
			s += "|" + "|".join([c.__str__() for c in self.b[i]]) + "|\n"
		s += "---"*8 + "\n"
		return s
	
	def notify(self,cmd):
		for o in self.obs:
			o.update(cmd)

	def registObs(self, o):
		if isinstance(o, BoardObserver):
			self.obs.insert(len(self.obs), o)
		return 1

class Put:
	def __init__(self, board, point, turn):
		self.board = board
		self.point = point # (x,y)
		self.reverses = []
		self.turn = turn
		if not isinstance(board.b[point[0]][point[1]], Empty):
			raise CannotPut, "Already putting"
		
		rel = [(i,j) for i in range(-1,2) for j in range(-1,2)]
		for i,elm in enumerate(rel):
			if(rel[i]==(0,0)):
				del rel[i]
				break
		org = self.point
		
		for v in rel:
			for i in range(1,8):
				tgtx = org[0]+v[0]*i
				tgty = org[1]+v[1]*i
				try:
					if(self.board.b[tgtx][tgty].value() == self.turn):
						if(i==1):
							break
						self.reverses.insert(len(self.reverses), (tgtx,tgty))
						break
					elif(self.board.b[tgtx][tgty].value() == 0):
						break
					else:
						continue
				except IndexError,e:
					break
		
		if(self.reverses == []):
			raise CannotPut, "Exception: Cannot put on given position"
			print self.board
			return 
	def execute(self):
		c = Empty()
		if self.turn==1:
			c = Black()
		else:
			c = White()
		self.board.b[self.point[0]][self.point[1]] = c
		
		for rel in self.reverses:
			v = (rel[0] - self.point[0], rel[1] - self.point[1])
			print "Vector: "+ v.__str__()
			getbase = lambda x: 0 if x==0 else x/abs(x)
			base = (getbase(v[0]), getbase(v[1]))
			border = max( abs(v[0]), abs(v[1]) )
			for i in range(1, border):
				p = (base[0]*i, base[1]*i)
				p = (self.point[0]+p[0], self.point[1]+p[1])
				self.board.b[p[0]][p[1]] = c

		#print self.reverses
		#print self.board
		self.board.notify(self)
	def undo(self):
		pass

class BoardObserver:
	def update(self, cmd):
		pass
class Logger(BoardObserver):
	def __init__(self):
		self.log = []
	def update(self, cmd):
		self.log.insert(len(self.log), cmd)
	def export(self):
		s = ""
		for i,cmd in enumerate(self.log):
			s += str(i) + ": "+str(cmd.point) + "\n"
		return s

def think(board,turn):
	nonfill = False
	for i in range(0,8):
		for j in range(0,8):
			if isinstance(board.b[i][j], Empty):
				nonfill = True
				break
	if not nonfill:
		print "Game End."
		return -1

	available = []
	for i in range(0,8):
		for j in range(0,8):
			try:
				cmd = Put(board, (i,j), turn)
				available.insert(len(available), cmd)
			except CannotPut,e:
				continue
	if(available==[]):
		print "Game end."
		return -1

	idx = random.randint(0, len(available)-1)
	available[idx].execute()
	print board
	t = "Black's" if turn==-1 else "White's"
	print t+" turn.\n"
	return 1

def main():
	board = Board()
	board.initBoard()
	logger = Logger()
	board.registObs(logger)

	print board
	print "\n"
	"""
	cmd = Put(board, (5,3), 1)
	cmd.execute()
	cmd = Put(board, (5,2), -1)
	cmd.execute()
	"""
	#print "You are Black"
	#cmd = Put(board, (5,3), 1)
	#cmd.execute()
	#print board
	
	turn = 1
	while(True):
		ret = think(board,turn)
		if ret==-1: break
		turn *= -1
	
	b=0
	w=0
	for i in range(0,8):
		for j in range(0,8):
			if   isinstance(board.b[i][j], Black): b+=1
			elif isinstance(board.b[i][j], White): w+=1
	print "Black: "+str(b)
	print "White: "+str(w)
	if b>w:
		print "Black Win."
	elif w>b:
		print "White Win."
	else:
		print "Draw Game."
	
	print "\n*** logger ***"
	print logger.export()

if __name__=="__main__":
	main()

実行結果は以下。対局中の出力(現在の局面)と、終局後の棋譜の出力とで2つあるので別々に出します。

------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  | o| x|  |  |  |
------------------------
|  |  |  | x| o|  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------



Vector: (2, 0)
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  | o|  |  |  |
------------------------
|  |  |  | o| o|  |  |  |
------------------------
|  |  |  | x| o|  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------
|  |  |  |  |  |  |  |  |
------------------------

White's turn.

... 中略 ... 
------------------------
| o| o| o| o| x| x| o| o|
------------------------
| o| o| x| o| o| x| o| o|
------------------------
| o| o| x| o| o| o| x| x|
------------------------
| o| o| x| x| x| x| x| x|
------------------------
| o| x| x| o| o| o| x| x|
------------------------
| o| x| x| x| o| x| o| x|
------------------------
| o| o| o| o| x| o| o| o|
------------------------
| o| o| o| x| x| x| x| o|
------------------------

Black's turn.

Game End.
Black: 37
White: 27
Black Win.

そして棋譜部分。

*** logger ***
0: (2, 4)
1: (2, 5)
2: (3, 5)
3: (4, 5)
... 中略 ...
57: (5, 7)
58: (6, 5)
59: (3, 7)

※座標にはタテヨコの区別がありますが、棋譜出力の際とかにその辺のことは考慮してません。将棋の表記である筋=>段と同じ順でオセロも表記しますが、もし局面の出力形式と整合をとるのであれば(最終手を例とすると)本当は(3,7)でなく(7,3)となります。もっとも、配列の添字である0~7番をそのまま出してるだけなのでそこからさらに+1する必要もありますし、「筋」にあたる所は正しくはアルファベット表記しなくてはなりません。ま、こまけぇことはいいんだよということです。

実際にコードを書いてみて思ったことは、Model部分と思考部分が独立になればいいなってことです。強いAIを作るには思考部分の高速化が必要なので、内部表現が変更される可能性は大いにあります。で、そういう高速化に重点を置いた内部表現とUI関係のコードはあまり相性がよくない気がします。Modelの表現を「自分」が扱える形式に変換するAdapterを書くことになるのでしょうが、「自分」に当てはまる要素はView側なのか、思考ルーチン側なのか。はたまた両方なのか。その辺は考える必要があるかもしれません。言い換えると、Modelの内部表現はUIとAIどっち寄りにすべきか?それともどっちにも寄らないようにすべきか?という所でしょうか。

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