OSDN Git Service

[shogi-server]
[shogi-server/shogi-server.git] / shogi_server / board.rb
1 ## $Id$
2
3 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
4 ## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
5 ##
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.
10 ##
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.
15 ##
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
19
20 require 'shogi_server/piece'
21
22 module ShogiServer # for a namespace
23
24 class Board
25   def initialize
26     @sente_hands = Array::new
27     @gote_hands  = Array::new
28     @history       = Hash::new(0)
29     @sente_history = Hash::new(0)
30     @gote_history  = Hash::new(0)
31     @array = [[], [], [], [], [], [], [], [], [], []]
32     @move_count = 0
33     @teban = nil # black => true, white => false
34   end
35   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
36   attr_reader :move_count
37
38   def initial
39     PieceKY::new(self, 1, 1, false)
40     PieceKE::new(self, 2, 1, false)
41     PieceGI::new(self, 3, 1, false)
42     PieceKI::new(self, 4, 1, false)
43     PieceOU::new(self, 5, 1, false)
44     PieceKI::new(self, 6, 1, false)
45     PieceGI::new(self, 7, 1, false)
46     PieceKE::new(self, 8, 1, false)
47     PieceKY::new(self, 9, 1, false)
48     PieceKA::new(self, 2, 2, false)
49     PieceHI::new(self, 8, 2, false)
50     (1..9).each do |i|
51       PieceFU::new(self, i, 3, false)
52     end
53
54     PieceKY::new(self, 1, 9, true)
55     PieceKE::new(self, 2, 9, true)
56     PieceGI::new(self, 3, 9, true)
57     PieceKI::new(self, 4, 9, true)
58     PieceOU::new(self, 5, 9, true)
59     PieceKI::new(self, 6, 9, true)
60     PieceGI::new(self, 7, 9, true)
61     PieceKE::new(self, 8, 9, true)
62     PieceKY::new(self, 9, 9, true)
63     PieceKA::new(self, 8, 8, true)
64     PieceHI::new(self, 2, 8, true)
65     (1..9).each do |i|
66       PieceFU::new(self, i, 7, true)
67     end
68     @teban = true
69   end
70
71   def have_piece?(hands, name)
72     piece = hands.find { |i|
73       i.name == name
74     }
75     return piece
76   end
77
78   def move_to(x0, y0, x1, y1, name, sente)
79     if (sente)
80       hands = @sente_hands
81     else
82       hands = @gote_hands
83     end
84
85     if ((x0 == 0) || (y0 == 0))
86       piece = have_piece?(hands, name)
87       return :illegal if (! piece.move_to?(x1, y1, name)) # TODO null check for the piece?
88       piece.move_to(x1, y1)
89     else
90       return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))  # TODO null check?
91       if (@array[x0][y0].name != name) # promoted ?
92         @array[x0][y0].promoted = true
93       end
94       if (@array[x1][y1]) # capture
95         if (@array[x1][y1].name == "OU")
96           return :outori        # return board update
97         end
98         @array[x1][y1].sente = @array[x0][y0].sente
99         @array[x1][y1].move_to(0, 0)
100         hands.sort! {|a, b| # TODO refactor. Move to Piece class
101           a.name <=> b.name
102         }
103       end
104       @array[x0][y0].move_to(x1, y1)
105     end
106     @move_count += 1
107     @teban = @teban ? false : true
108     return true
109   end
110
111   def look_for_ou(sente)
112     x = 1
113     while (x <= 9)
114       y = 1
115       while (y <= 9)
116         if (@array[x][y] &&
117             (@array[x][y].name == "OU") &&
118             (@array[x][y].sente == sente))
119           return @array[x][y]
120         end
121         y = y + 1
122       end
123       x = x + 1
124     end
125     raise "can't find ou"
126   end
127
128   # note checkmate, but check. sente is checked.
129   def checkmated?(sente)        # sente is loosing
130     ou = look_for_ou(sente)
131     x = 1
132     while (x <= 9)
133       y = 1
134       while (y <= 9)
135         if (@array[x][y] &&
136             (@array[x][y].sente != sente))
137           if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
138             return true
139           end
140         end
141         y = y + 1
142       end
143       x = x + 1
144     end
145     return false
146   end
147
148   def uchifuzume?(sente)
149     rival_ou = look_for_ou(! sente)   # rival's ou
150     if (sente)                  # rival is gote
151       if ((rival_ou.y != 9) &&
152           (@array[rival_ou.x][rival_ou.y + 1]) &&
153           (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
154           (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
155         fu_x = rival_ou.x
156         fu_y = rival_ou.y + 1
157       else
158         return false
159       end
160     else                        # gote
161       if ((rival_ou.y != 1) &&
162           (@array[rival_ou.x][rival_ou.y - 1]) &&
163           (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
164           (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
165         fu_x = rival_ou.x
166         fu_y = rival_ou.y - 1
167       else
168         return false
169       end
170     end
171
172     ## case: rival_ou is moving
173     rival_ou.movable_grids.each do |(cand_x, cand_y)|
174       tmp_board = Marshal.load(Marshal.dump(self))
175       s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
176       raise "internal error" if (s != true)
177       if (! tmp_board.checkmated?(! sente)) # good move
178         return false
179       end
180     end
181
182     ## case: rival is capturing fu
183     x = 1
184     while (x <= 9)
185       y = 1
186       while (y <= 9)
187         if (@array[x][y] &&
188             (@array[x][y].sente != sente) &&
189             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
190           
191           names = []
192           if (@array[x][y].promoted)
193             names << @array[x][y].promoted_name
194           else
195             names << @array[x][y].name
196             if @array[x][y].promoted_name && 
197                @array[x][y].move_to?(fu_x, fu_y, @array[x][y].promoted_name)
198               names << @array[x][y].promoted_name 
199             end
200           end
201           names.map! do |name|
202             tmp_board = Marshal.load(Marshal.dump(self))
203             s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
204             if s == :illegal
205               s # result
206             else
207               tmp_board.checkmated?(! sente) # result
208             end
209           end
210           all_illegal = names.find {|a| a != :illegal}
211           raise "internal error: legal move not found" if all_illegal == nil
212           r = names.find {|a| a == false} # good move
213           return false if r == false # found good move
214         end
215         y = y + 1
216       end
217       x = x + 1
218     end
219     return true
220   end
221
222   # @[sente|gote]_history has at least one item while the player is checking the other or 
223   # the other escapes.
224   def update_sennichite(player)
225     str = to_s
226     @history[str] += 1
227     if checkmated?(!player)
228       if (player)
229         @sente_history["dummy"] = 1  # flag to see Sente player is checking Gote player
230       else
231         @gote_history["dummy"]  = 1  # flag to see Gote player is checking Sente player
232       end
233     else
234       if (player)
235         @sente_history.clear # no more continuous check
236       else
237         @gote_history.clear  # no more continuous check
238       end
239     end
240     if @sente_history.size > 0  # possible for Sente's or Gote's turn
241       @sente_history[str] += 1
242     end
243     if @gote_history.size > 0   # possible for Sente's or Gote's turn
244       @gote_history[str] += 1
245     end
246   end
247
248   def oute_sennichite?(player)
249     if (@sente_history[to_s] >= 4)
250       return :oute_sennichite_sente_lose
251     elsif (@gote_history[to_s] >= 4)
252       return :oute_sennichite_gote_lose
253     else
254       return nil
255     end
256   end
257
258   def sennichite?(sente)
259     if (@history[to_s] >= 4) # already 3 times
260       return true
261     end
262     return false
263   end
264
265   def good_kachi?(sente)
266     if (checkmated?(sente))
267       puts "'NG: Checkmating." if $DEBUG
268       return false 
269     end
270     
271     ou = look_for_ou(sente)
272     if (sente && (ou.y >= 4))
273       puts "'NG: Black's OU does not enter yet." if $DEBUG
274       return false     
275     end  
276     if (! sente && (ou.y <= 6))
277       puts "'NG: White's OU does not enter yet." if $DEBUG
278       return false 
279     end
280       
281     number = 0
282     point = 0
283
284     if (sente)
285       hands = @sente_hands
286       r = [1, 2, 3]
287     else
288       hands = @gote_hands
289       r = [7, 8, 9]
290     end
291     r.each do |y|
292       x = 1
293       while (x <= 9)
294         if (@array[x][y] &&
295             (@array[x][y].sente == sente) &&
296             (@array[x][y].point > 0))
297           point = point + @array[x][y].point
298           number = number + 1
299         end
300         x = x + 1
301       end
302     end
303     hands.each do |piece|
304       point = point + piece.point
305     end
306
307     if (number < 10)
308       puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
309       return false     
310     end  
311     if (sente)
312       if (point < 28)
313         puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
314         return false 
315       end  
316     else
317       if (point < 27)
318         puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
319         return false 
320       end
321     end
322
323     puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
324     return true
325   end
326
327   # sente is nil only if tests in test_board run
328   def handle_one_move(str, sente=nil)
329     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
330       sg = $1
331       x0 = $2.to_i
332       y0 = $3.to_i
333       x1 = $4.to_i
334       y1 = $5.to_i
335       name = $6
336     elsif (str =~ /^%KACHI/)
337       raise ArgumentError, "sente is null", caller if sente == nil
338       if (good_kachi?(sente))
339         return :kachi_win
340       else
341         return :kachi_lose
342       end
343     elsif (str =~ /^%TORYO/)
344       return :toryo
345     else
346       return :illegal
347     end
348     
349     if (((x0 == 0) || (y0 == 0)) && # source is not from hand
350         ((x0 != 0) || (y0 != 0)))
351       return :illegal
352     elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
353       return :illegal
354     end
355     
356     if (sg == "+")
357       sente = true if sente == nil           # deprecated
358       return :illegal unless sente == true   # black player's move must be black
359       hands = @sente_hands
360     else
361       sente = false if sente == nil          # deprecated
362       return :illegal unless sente == false  # white player's move must be white
363       hands = @gote_hands
364     end
365     
366     ## source check
367     if ((x0 == 0) && (y0 == 0))
368       return :illegal if (! have_piece?(hands, name))
369     elsif (! @array[x0][y0])
370       return :illegal           # no piece
371     elsif (@array[x0][y0].sente != sente)
372       return :illegal           # this is not mine
373     elsif (@array[x0][y0].name != name)
374       return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
375     end
376
377     ## destination check
378     if (@array[x1][y1] &&
379         (@array[x1][y1].sente == sente)) # can't capture mine
380       return :illegal
381     elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
382       return :illegal           # can't put on existing piece
383     end
384
385     tmp_board = Marshal.load(Marshal.dump(self))
386     return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
387     return :oute_kaihimore if (tmp_board.checkmated?(sente))
388     tmp_board.update_sennichite(sente)
389     os_result = tmp_board.oute_sennichite?(sente)
390     return os_result if os_result # :oute_sennichite_sente_lose or :oute_sennichite_gote_lose
391     return :sennichite if tmp_board.sennichite?(sente)
392
393     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
394       return :uchifuzume
395     end
396
397     move_to(x0, y0, x1, y1, name, sente)
398
399     update_sennichite(sente)
400     return :normal
401   end
402
403   def to_s
404     a = Array::new
405     y = 1
406     while (y <= 9)
407       a.push(sprintf("P%d", y))
408       x = 9
409       while (x >= 1)
410         piece = @array[x][y]
411         if (piece)
412           s = piece.to_s
413         else
414           s = " * "
415         end
416         a.push(s)
417         x = x - 1
418       end
419       a.push(sprintf("\n"))
420       y = y + 1
421     end
422     if (! sente_hands.empty?)
423       a.push("P+")
424       sente_hands.each do |p|
425         a.push("00" + p.name)
426       end
427       a.push("\n")
428     end
429     if (! gote_hands.empty?)
430       a.push("P-")
431       gote_hands.each do |p|
432         a.push("00" + p.name)
433       end
434       a.push("\n")
435     end
436     a.push("%s\n" % [@teban ? "+" : "-"])
437     return a.join
438   end
439 end
440
441 end # ShogiServer