#!/usr/bin/env ruby # coding: utf-8 #=============================================================================== # # どうぶつしょうぎ # #--------------------------------------------------------------- # # 将棋盤クラス # #   ABC #  ########## # 1##麒獅象## # 2## 雛 ## # 3## 雛 ## # 4##象獅麒## #  ########## # class Board < Array def initialize(objs) @objs = objs wall = @objs[Wall]; empt = @objs[Empt] self.clear self << wall << wall << wall << wall << wall self << wall << empt << empt << empt << wall self << wall << empt << empt << empt << wall self << wall << empt << empt << empt << wall self << wall << empt << empt << empt << wall self << wall << wall << wall << wall << wall end def [](x, y) super(5 * y + x) end def []=(x, y, obj) super(5 * y + x, obj) end def show(indent = '') puts(indent + ' ABC') axis_y = [ ' ', '1', '2', '3', '4', ' ' ] (0..5).each {|y| print(indent + axis_y.shift) (0..4).each {|x| print(self[x, y].char) } puts } end def each_place (1..4).each {|y| (1..3).each {|x| yield(self[x, y], x, y) } } end def each_piece(side) each_place {|obj, x, y| obj.side == side and yield(obj, x, y) } end def each_empty_place each_place {|obj, x, y| obj.class == Empt and yield(x, y) } end def pick(x, y) piece = self[x, y] self[x, y] = @objs[Empt] piece end def tried?(side) y = @objs[side][Lion].op_area (1..3).each {|x| (piece = self[x, y]).side == side and piece.class == Lion and return(true) } false end def caught?(side) each_place {|obj, x, y| obj.side == side and obj.class == Lion and return(false) } true end end #--------------------------------------------------------------- # # 持ち駒クラス # class Standby < Hash def initialize(objs) @objs = objs self[:A] = [] self[:B] = [] end def dup dupe = super dupe[:A] = self[:A].dup dupe[:B] = self[:B].dup dupe end def each_piece(side) self[side].each {|piece| yield(piece) } end def show(side, indent = '') pieces_h = ''; each_piece(side) {|piece| pieces_h << piece.char } puts(indent + '持ち駒: [%s]' % pieces_h) end def pick(piece) (pieces = self[piece.side]).each_index {|n| pieces[n].class == piece.class and return(pieces.delete_at(n)) } raise('you do not have it.') end end #--------------------------------------------------------------- # # 指し手クラス # class Hand < Array def piece self[0] end def to self[1, 2] end def from self.size == 3 ? false : self[3, 2] end def view from ? '[%s][%d, %d]=>[%d, %d]' % [piece.char, *from, *to] : '[%s]=>[%d, %d]' % [piece.char, *to] end end #--------------------------------------------------------------- # # 勝負クラス # class Game attr_reader :board, :standby def initialize(objs, game = nil) @objs = objs if(game) # 状況のコピーを返す @board = game.board.dup @standby = game.standby.dup else # 初期の状況を返す @board = Board.new(objs) @standby = Standby.new(objs) end end def show(indent = '') puts @standby.show(:B, indent) @board.show(indent) @standby.show(:A, indent) puts end #----------------------------------------------------------- # # 駒を取る # def pick(hand) hand.from ? @board.pick(*hand.from) : @standby.pick(hand.piece) # 自分の駒/持ち駒を手に取った end #----------------------------------------------------------- # # 駒を置く # def put(hand) # 対象の場所のオブジェクト/駒を得る piece = hand.piece; x, y = *hand.to obj = @board[x, y] # 空き地に置けた if(obj.class == Empt) @board[x, y] = put_with_evol(hand) # 壁があった elsif(obj.class == Wall) false # 自分の駒があった elsif(obj.side == piece.side) false # 相手の駒を取った elsif(obj.side == piece.op_side) @standby[piece.side] << @objs[piece.side][(it = obj.class) == Cock ? Baby : it] # (雛に戻して)持ち駒に加える @board[x, y] = put_with_evol(hand) end end #----------------------------------------------------------- # # 駒が成るか? # def put_with_evol(hand) piece = hand.piece; x, y = *hand.to (hand.from and piece.class == Baby and y == piece.op_area) ? @objs[piece.side][Cock] : piece end end #--------------------------------------------------------------- # # プレーヤクラス # class Player attr_reader :side, :op_side, :name def initialize(objs, side) @objs = objs @op_side = (@side = side) == :A ? :B : :A # :A 自分は手前、奥に攻める, :B 自分は奥、手前に攻める end #----------------------------------------------------------- # # 初期状態に駒を並べる # def setup(game, test = false) if(@side == :A) x = 1; y1 = 3; y2 = 4 elsif(@side == :B) x = -1; y1 = 2; y2 = 1 end game.put(Hand.new([@objs[@side][Baby], 2, y1])) game.put(Hand.new([@objs[@side][Elep], 2 - x, y2])) game.put(Hand.new([@objs[@side][Lion], 2, y2])) game.put(Hand.new([@objs[@side][Graf], 2 + x, y2])) end end #--------------------------------------------------------------- # # 人間プレーヤクラス # class HumanPlayer < Player def initialize(objs, side) super @name = 'あなた' @inputs = { 'a' => 1, 'b' => 2, 'c' => 3, 'g' => objs[@side][Graf], 'e' => objs[@side][Elep], 'y' => objs[@side][Baby], } end def think(game) print('どれをどこに[ a1b2 | yc3 ]移動する?'); move = $stdin.readline if((it = @inputs[move[0]]).is_a?(Numeric)) # anxn / bnxn / cnxn ? piece = game.board[it, move[1].to_i] hand = Hand.new([piece, @inputs[move[2]], move[3].to_i, it, move[1].to_i]) else hand = Hand.new([it, @inputs[move[1]], move[2].to_i]) end end end #--------------------------------------------------------------- # # コンピュータプレーヤクラス # class ComputerPlayerX < Player def initialize(objs, side, think_depth) super(objs, side) @name = 'ワタシ' @think_depth = think_depth @weight = { Lion => 1000, Graf => 60, Elep => 50, Baby => 30, Cock => 70, } end #----------------------------------------------------------- # # 再帰的に思考し、最善の手/状況の評価点を返す # def think(game, depth = 0, turn = 1) # turn 1:自分, -1:相手 debug = depth < 0 ? '> ' * (depth + 1) : false #------------------------------------------------------- # # 読みの深さが一定に達したら、その状況の評価点を返す # if(depth > @think_depth) return(evaluate(game) * turn) #------------------------------------------------------- # # 打てる手について順に評価し、最高の評価点を返す # else side, op_side, turn_h = turn > 0 ? [@side, @op_side, '自分'] : [@op_side, @side, '相手'] maxpoint = -999999; besthand = nil tried = game.board.tried?(op_side) # 相手がトライ状態か? game.board.each_piece(side) {|piece, x, y| piece.each_move(x, y) {|tx, ty| hand = Hand.new([piece, tx, ty, x, y]) virt_game = Game.new(@objs, game) # 状況を複製 virt_game.pick(hand) virt_game.put(hand) or next # 手を打ってみて… puts("\n" + debug + '[%s]の手%s...' % [turn_h, hand.view]) if(debug) virt_game.show(debug) if(debug) if(virt_game.board.caught?(op_side)) # 相手をキャッチしたか? → 勝ち point = 999900 - depth elsif(tried) # 相手がトライ状態か? → 負け point = -999900 + depth else point = -think(virt_game, depth + 1, -turn) # 再帰的に思考する end point > maxpoint and maxpoint = point and besthand = hand puts(debug + '[%s]の手%s...評価点[%d]' % [turn_h, hand.view, point]) if(debug) } } tried or game.standby.each_piece(side) {|piece| # 相手がトライ状態なら持ち駒は考えない game.board.each_empty_place {|tx, ty| hand = Hand.new([piece, tx, ty]) virt_game = Game.new(@objs, game) # 状況を複製 virt_game.pick(hand) virt_game.put(hand) or next # 手を打ってみて… puts("\n" + debug + '[%s]の手%s...' % [turn_h, hand.view]) if(debug) virt_game.show(debug) if(debug) point = -think(virt_game, depth + 1, -turn) # 再帰的に思考する point > maxpoint and maxpoint = point and besthand = hand puts(debug + '[%s]の手%s...評価点[%d]' % [turn_h, hand.view, point]) if(debug) } } depth == 0 and maxpoint.abs > 990000 and puts('読み切りました[%s]の勝ちです。' % (maxpoint > 0 ? 'ワタシ' : 'あなた')) puts(debug + '最善の手を選択、[%s]の手%s...評価点[%d]' % [turn_h, besthand.view, maxpoint]) if(debug) return(depth == 0 ? besthand : maxpoint) # 最後は評価点でなく、最善の手を返す end end #----------------------------------------------------------- # # 手番後の状況の評価点を返す # def evaluate(game) point = 0 game.board.each {|obj| # 将棋盤の駒の評価点 point += ((it = @weight[obj.class]) ? it : 0) * (obj.side == @side ? 1 : -1) } game.standby.each {|side, pieces| # 持ち駒の評価点 pieces.each {|piece| point += ((it = @weight[piece.class]) ? it : 0) * (piece.side == @side ? 1 : -1) } } point end end #--------------------------------------------------------------- # # 各オブジェクトの定義 # class Obj attr_reader :side, :op_side def initialize @side = @op_side = :N end end class Empt < Obj def initialize super end def char ' ' end end class Wall < Obj def initialize super end def char '##' end end class Piece < Obj attr_reader :moves, :op_area def initialize(side) @op_side, @op_area = (@side = side) == :A ? [:B, 1] : [:A, 4] # :A 自分は手前、奥に攻める @side == :B and @moves.each {|xy| xy[1] = -xy[1] } # :B 自分は奥、手前に攻める end def color(char) @side == :A and return("\e[34m\e[1;1m%s\e[0m" % char) # 青色 @side == :B and return("\e[31m\e[1;1m%s\e[0m" % char) # 赤色 return(char) end def each_move(x, y) @moves.each {|dx, dy| yield(x + dx, y + dy) } end end class Lion < Piece def initialize(side) @moves = [ [ 0, -1], [-1, -1], [-1, 0], [-1, 1], [ 0, 1], [ 1, 1], [ 1, 0], [ 1, -1] ] super(side) end def char color('獅') end end class Graf < Piece def initialize(side) @moves = [ [ 0, -1], [-1, 0], [ 0, 1], [ 1, 0] ] super(side) end def char color('麒') end end class Elep < Piece def initialize(side) @moves = [ [-1, -1], [-1, 1], [ 1, 1], [ 1, -1] ] super(side) end def char color('象') end end class Baby < Piece def initialize(side) @moves = [ [ 0, -1], ] super(side) end def char color('雛') end end class Cock < Piece def initialize(side) @moves = [ [ 0, -1], [-1, -1], [-1, 0], [ 0, 1], [ 1, 0], [ 1, -1] ] super(side) end def char color('鶏') end end #--------------------------------------------------------------- # # 各オブジェクトの準備 # objs = { Empt => Empt.new, Wall => Wall.new, :A => { Lion => Lion.new(:A), Graf => Graf.new(:A), Elep => Elep.new(:A), Baby => Baby.new(:A), Cock => Cock.new(:A), }, :B => { Lion => Lion.new(:B), Graf => Graf.new(:B), Elep => Elep.new(:B), Baby => Baby.new(:B), Cock => Cock.new(:B), }, } #--------------------------------------------------------------- # # メイン # think_depth = 4 # 0: 自分の手、1: 自分、相手の手、2: 自分、相手、自分の手… game = Game.new(objs) playerA = HumanPlayer.new(objs, :A ); playerA.setup(game) playerB = ComputerPlayerX.new(objs, :B, think_depth ); playerB.setup(game) game.show round = 0; loop { [playerA, playerB].each {|player| hand = player.think(game) puts('%d: %sの手%s' % [round += 1, player.name, hand.view]) game.pick(hand) game.put(hand) or raise('unexpected error') game.show } } __END__