OSDN Git Service

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