OSDN Git Service

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