3 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
4 ## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ## GNU General Public License for more details.
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, write to the Free Software
18 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 module ShogiServer # for a namespace
23 def initialize(move_count=0)
24 @sente_hands = Array::new
25 @gote_hands = Array::new
26 @history = Hash::new(0)
27 @sente_history = Hash::new(0)
28 @gote_history = Hash::new(0)
29 @array = [[], [], [], [], [], [], [], [], [], []]
30 @move_count = move_count
31 @teban = nil # black => true, white => false
33 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history, :teban
34 attr_reader :move_count
37 # return Marshal.load(Marshal.dump(self))
38 board = Board.new(self.move_count)
39 board.sente_hands = self.sente_hands.clone
40 board.gote_hands = self.gote_hands.clone
41 board.history = self.history.clone
42 board.history.default = 0
43 board.sente_history = self.sente_history.clone
44 board.sente_history.default = 0
45 board.gote_history = self.gote_history.clone
46 board.gote_history.default = 0
48 self.array.each {|a| board.array.push(a.clone)}
49 board.teban = self.teban
54 PieceKY::new(self, 1, 1, false)
55 PieceKE::new(self, 2, 1, false)
56 PieceGI::new(self, 3, 1, false)
57 PieceKI::new(self, 4, 1, false)
58 PieceOU::new(self, 5, 1, false)
59 PieceKI::new(self, 6, 1, false)
60 PieceGI::new(self, 7, 1, false)
61 PieceKE::new(self, 8, 1, false)
62 PieceKY::new(self, 9, 1, false)
63 PieceKA::new(self, 2, 2, false)
64 PieceHI::new(self, 8, 2, false)
66 PieceFU::new(self, i, 3, false)
69 PieceKY::new(self, 1, 9, true)
70 PieceKE::new(self, 2, 9, true)
71 PieceGI::new(self, 3, 9, true)
72 PieceKI::new(self, 4, 9, true)
73 PieceOU::new(self, 5, 9, true)
74 PieceKI::new(self, 6, 9, true)
75 PieceGI::new(self, 7, 9, true)
76 PieceKE::new(self, 8, 9, true)
77 PieceKY::new(self, 9, 9, true)
78 PieceKA::new(self, 8, 8, true)
79 PieceHI::new(self, 2, 8, true)
81 PieceFU::new(self, i, 7, true)
86 def have_piece?(hands, name)
87 piece = hands.find { |i|
93 def move_to(x0, y0, x1, y1, name, sente)
100 if ((x0 == 0) || (y0 == 0))
101 piece = have_piece?(hands, name)
102 return :illegal if (piece == nil || ! piece.move_to?(x1, y1, name))
103 piece.move_to(x1, y1)
105 if (@array[x0][y0] == nil || !@array[x0][y0].move_to?(x1, y1, name))
108 if (@array[x0][y0].name != name) # promoted ?
109 @array[x0][y0].promoted = true
111 if (@array[x1][y1]) # capture
112 if (@array[x1][y1].name == "OU")
113 return :outori # return board update
115 @array[x1][y1].sente = @array[x0][y0].sente
116 @array[x1][y1].move_to(0, 0)
117 hands.sort! {|a, b| # TODO refactor. Move to Piece class
121 @array[x0][y0].move_to(x1, y1)
124 @teban = @teban ? false : true
128 def look_for_ou(sente)
134 (@array[x][y].name == "OU") &&
135 (@array[x][y].sente == sente))
142 raise "can't find ou"
145 # note checkmate, but check. sente is checked.
146 def checkmated?(sente) # sente is loosing
147 ou = look_for_ou(sente)
153 (@array[x][y].sente != sente))
154 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
165 def uchifuzume?(sente)
166 rival_ou = look_for_ou(! sente) # rival's ou
167 if (sente) # rival is gote
168 if ((rival_ou.y != 9) &&
169 (@array[rival_ou.x][rival_ou.y + 1]) &&
170 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
171 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
173 fu_y = rival_ou.y + 1
178 if ((rival_ou.y != 1) &&
179 (@array[rival_ou.x][rival_ou.y - 1]) &&
180 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
181 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
183 fu_y = rival_ou.y - 1
189 ## case: rival_ou is moving
190 rival_ou.movable_grids.each do |(cand_x, cand_y)|
191 tmp_board = deep_copy
192 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
193 raise "internal error" if (s != true)
194 if (! tmp_board.checkmated?(! sente)) # good move
199 ## case: rival is capturing fu
205 (@array[x][y].sente != sente) &&
206 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
209 if (@array[x][y].promoted)
210 names << @array[x][y].promoted_name
212 names << @array[x][y].name
213 if @array[x][y].promoted_name &&
214 @array[x][y].move_to?(fu_x, fu_y, @array[x][y].promoted_name)
215 names << @array[x][y].promoted_name
219 tmp_board = deep_copy
220 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
224 tmp_board.checkmated?(! sente) # result
227 all_illegal = names.find {|a| a != :illegal}
228 raise "internal error: legal move not found" if all_illegal == nil
229 r = names.find {|a| a == false} # good move
230 return false if r == false # found good move
239 # @[sente|gote]_history has at least one item while the player is checking the other or
241 def update_sennichite(player)
244 if checkmated?(!player)
246 @sente_history["dummy"] = 1 # flag to see Sente player is checking Gote player
248 @gote_history["dummy"] = 1 # flag to see Gote player is checking Sente player
252 @sente_history.clear # no more continuous check
254 @gote_history.clear # no more continuous check
257 if @sente_history.size > 0 # possible for Sente's or Gote's turn
258 @sente_history[str] += 1
260 if @gote_history.size > 0 # possible for Sente's or Gote's turn
261 @gote_history[str] += 1
265 def oute_sennichite?(player)
266 if (@sente_history[to_s] >= 4)
267 return :oute_sennichite_sente_lose
268 elsif (@gote_history[to_s] >= 4)
269 return :oute_sennichite_gote_lose
275 def sennichite?(sente)
276 if (@history[to_s] >= 4) # already 3 times
282 def good_kachi?(sente)
283 if (checkmated?(sente))
284 puts "'NG: Checkmating." if $DEBUG
288 ou = look_for_ou(sente)
289 if (sente && (ou.y >= 4))
290 puts "'NG: Black's OU does not enter yet." if $DEBUG
293 if (! sente && (ou.y <= 6))
294 puts "'NG: White's OU does not enter yet." if $DEBUG
312 (@array[x][y].sente == sente) &&
313 (@array[x][y].point > 0))
314 point = point + @array[x][y].point
320 hands.each do |piece|
321 point = point + piece.point
325 puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
330 puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
335 puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
340 puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
344 # sente is nil only if tests in test_board run
345 def handle_one_move(str, sente=nil)
346 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
353 elsif (str =~ /^%KACHI/)
354 raise ArgumentError, "sente is null", caller if sente == nil
355 if (good_kachi?(sente))
360 elsif (str =~ /^%TORYO/)
366 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
367 ((x0 != 0) || (y0 != 0)))
369 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
374 sente = true if sente == nil # deprecated
375 return :illegal unless sente == true # black player's move must be black
378 sente = false if sente == nil # deprecated
379 return :illegal unless sente == false # white player's move must be white
384 if ((x0 == 0) && (y0 == 0))
385 return :illegal if (! have_piece?(hands, name))
386 elsif (! @array[x0][y0])
387 return :illegal # no piece
388 elsif (@array[x0][y0].sente != sente)
389 return :illegal # this is not mine
390 elsif (@array[x0][y0].name != name)
391 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
395 if (@array[x1][y1] &&
396 (@array[x1][y1].sente == sente)) # can't capture mine
398 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
399 return :illegal # can't put on existing piece
402 tmp_board = deep_copy
403 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
404 return :oute_kaihimore if (tmp_board.checkmated?(sente))
405 tmp_board.update_sennichite(sente)
406 os_result = tmp_board.oute_sennichite?(sente)
407 return os_result if os_result # :oute_sennichite_sente_lose or :oute_sennichite_gote_lose
408 return :sennichite if tmp_board.sennichite?(sente)
410 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
414 move_to(x0, y0, x1, y1, name, sente)
416 update_sennichite(sente)
424 a.push(sprintf("P%d", y))
436 a.push(sprintf("\n"))
439 if (! sente_hands.empty?)
441 sente_hands.each do |p|
442 a.push("00" + p.name)
446 if (! gote_hands.empty?)
448 gote_hands.each do |p|
449 a.push("00" + p.name)
453 a.push("%s\n" % [@teban ? "+" : "-"])