OSDN Git Service

1fe8ee9a00b3f3f1c0a50a811383e1fad18ec671
[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 set_from_str(strs)
74     strs.split(/\n/).each do |str|
75       if (str =~ /^P\d/)
76         str.sub!(/^P(.)/, '')
77         y = $1.to_i
78         x = 9
79         while (str.length > 2)
80           str.sub!(/^(...?)/, '')
81           one = $1
82           if (one =~ /^([\+\-])(..)/)
83             sg = $1
84             name = $2
85             if (sg == "+")
86               sente = true
87             else
88               sente = false
89             end
90             if ((x < 1) || (9 < x) || (y < 1) || (9 < y))
91               raise "bad position #{x} #{y}"
92             end
93             case (name)
94             when "FU"
95               PieceFU::new(self, x, y, sente)
96             when "KY"
97               PieceKY::new(self, x, y, sente)
98             when "KE"
99               PieceKE::new(self, x, y, sente)
100             when "GI"
101               PieceGI::new(self, x, y, sente)
102             when "KI"
103               PieceKI::new(self, x, y, sente)
104             when "OU"
105               PieceOU::new(self, x, y, sente)
106             when "KA"
107               PieceKA::new(self, x, y, sente)
108             when "HI"
109               PieceHI::new(self, x, y, sente)
110             when "TO"
111               PieceFU::new(self, x, y, sente, true)
112             when "NY"
113               PieceKY::new(self, x, y, sente, true)
114             when "NK"
115               PieceKE::new(self, x, y, sente, true)
116             when "NG"
117               PieceGI::new(self, x, y, sente, true)
118             when "UM"
119               PieceKA::new(self, x, y, sente, true)
120             when "RY"
121               PieceHI::new(self, x, y, sente, true)
122             else
123               raise "unkown piece #{name}"
124             end
125           end
126           x = x - 1
127         end
128       elsif (str =~ /^P([\+\-])/)
129         sg = $1
130         if (sg == "+")
131           sente = true
132         else
133           sente = false
134         end
135         str.sub!(/^../, '')
136         while (str.length > 3)
137           str.sub!(/^..(..)/, '')
138           name = $1
139           case (name)
140           when "FU"
141             PieceFU::new(self, 0, 0, sente)
142           when "KY"
143             PieceKY::new(self, 0, 0, sente)
144           when "KE"
145             PieceKE::new(self, 0, 0, sente)
146           when "GI"
147             PieceGI::new(self, 0, 0, sente)
148           when "KI"
149             PieceKI::new(self, 0, 0, sente)
150           when "KA"
151             PieceKA::new(self, 0, 0, sente)
152           when "HI"
153             PieceHI::new(self, 0, 0, sente)
154           else
155             raise "unkown piece #{name}"
156           end
157         end # while
158       end # if
159     end # do
160   end
161
162   def have_piece?(hands, name)
163     piece = hands.find { |i|
164       i.name == name
165     }
166     return piece
167   end
168
169   def move_to(x0, y0, x1, y1, name, sente)
170     if (sente)
171       hands = @sente_hands
172     else
173       hands = @gote_hands
174     end
175
176     if ((x0 == 0) || (y0 == 0))
177       piece = have_piece?(hands, name)
178       return :illegal if (piece == nil || ! piece.move_to?(x1, y1, name))
179       piece.move_to(x1, y1)
180     else
181       if (@array[x0][y0] == nil || !@array[x0][y0].move_to?(x1, y1, name))
182         return :illegal
183       end
184       if (@array[x0][y0].name != name) # promoted ?
185         @array[x0][y0].promoted = true
186       end
187       if (@array[x1][y1]) # capture
188         if (@array[x1][y1].name == "OU")
189           return :outori        # return board update
190         end
191         @array[x1][y1].sente = @array[x0][y0].sente
192         @array[x1][y1].move_to(0, 0)
193         hands.sort! {|a, b| # TODO refactor. Move to Piece class
194           a.name <=> b.name
195         }
196       end
197       @array[x0][y0].move_to(x1, y1)
198     end
199     @move_count += 1
200     @teban = @teban ? false : true
201     return true
202   end
203
204   def look_for_ou(sente)
205     x = 1
206     while (x <= 9)
207       y = 1
208       while (y <= 9)
209         if (@array[x][y] &&
210             (@array[x][y].name == "OU") &&
211             (@array[x][y].sente == sente))
212           return @array[x][y]
213         end
214         y = y + 1
215       end
216       x = x + 1
217     end
218     raise "can't find ou"
219   end
220
221   # not checkmate, but check. sente is checked.
222   def checkmated?(sente)        # sente is loosing
223     ou = look_for_ou(sente)
224     x = 1
225     while (x <= 9)
226       y = 1
227       while (y <= 9)
228         if (@array[x][y] &&
229             (@array[x][y].sente != sente))
230           if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
231             return true
232           end
233         end
234         y = y + 1
235       end
236       x = x + 1
237     end
238     return false
239   end
240
241   def uchifuzume?(sente)
242     rival_ou = look_for_ou(! sente)   # rival's ou
243     if (sente)                  # rival is gote
244       if ((rival_ou.y != 9) &&
245           (@array[rival_ou.x][rival_ou.y + 1]) &&
246           (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
247           (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
248         fu_x = rival_ou.x
249         fu_y = rival_ou.y + 1
250       else
251         return false
252       end
253     else                        # gote
254       if ((rival_ou.y != 1) &&
255           (@array[rival_ou.x][rival_ou.y - 1]) &&
256           (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
257           (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
258         fu_x = rival_ou.x
259         fu_y = rival_ou.y - 1
260       else
261         return false
262       end
263     end
264
265     ## case: rival_ou is moving
266     rival_ou.movable_grids.each do |(cand_x, cand_y)|
267       tmp_board = deep_copy
268       s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
269       raise "internal error" if (s != true)
270       if (! tmp_board.checkmated?(! sente)) # good move
271         return false
272       end
273     end
274
275     ## case: rival is capturing fu
276     x = 1
277     while (x <= 9)
278       y = 1
279       while (y <= 9)
280         if (@array[x][y] &&
281             (@array[x][y].sente != sente) &&
282             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
283           
284           names = []
285           if (@array[x][y].promoted)
286             names << @array[x][y].promoted_name
287           else
288             names << @array[x][y].name
289             if @array[x][y].promoted_name && 
290                @array[x][y].move_to?(fu_x, fu_y, @array[x][y].promoted_name)
291               names << @array[x][y].promoted_name 
292             end
293           end
294           names.map! do |name|
295             tmp_board = deep_copy
296             s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
297             if s == :illegal
298               s # result
299             else
300               tmp_board.checkmated?(! sente) # result
301             end
302           end
303           all_illegal = names.find {|a| a != :illegal}
304           raise "internal error: legal move not found" if all_illegal == nil
305           r = names.find {|a| a == false} # good move
306           return false if r == false # found good move
307         end
308         y = y + 1
309       end
310       x = x + 1
311     end
312     return true
313   end
314
315   # @[sente|gote]_history has at least one item while the player is checking the other or 
316   # the other escapes.
317   def update_sennichite(player)
318     str = to_s
319     @history[str] += 1
320     if checkmated?(!player)
321       if (player)
322         @sente_history["dummy"] = 1  # flag to see Sente player is checking Gote player
323       else
324         @gote_history["dummy"]  = 1  # flag to see Gote player is checking Sente player
325       end
326     else
327       if (player)
328         @sente_history.clear # no more continuous check
329       else
330         @gote_history.clear  # no more continuous check
331       end
332     end
333     if @sente_history.size > 0  # possible for Sente's or Gote's turn
334       @sente_history[str] += 1
335     end
336     if @gote_history.size > 0   # possible for Sente's or Gote's turn
337       @gote_history[str] += 1
338     end
339   end
340
341   def oute_sennichite?(player)
342     return nil unless sennichite?
343
344     if player
345       # sente's turn
346       if (@sente_history[to_s] >= 4)   # sente is checking gote
347         return :oute_sennichite_sente_lose
348       elsif (@gote_history[to_s] >= 3) # sente is escaping
349         return :oute_sennichite_gote_lose
350       else
351         return nil # Not oute_sennichite, but sennichite
352       end
353     else
354       # gote's turn
355       if (@gote_history[to_s] >= 4)     # gote is checking sente
356         return :oute_sennichite_gote_lose
357       elsif (@sente_history[to_s] >= 3) # gote is escaping
358         return :oute_sennichite_sente_lose
359       else
360         return nil # Not oute_sennichite, but sennichite
361       end
362     end
363   end
364
365   def sennichite?
366     if (@history[to_s] >= 4) # already 3 times
367       return true
368     end
369     return false
370   end
371
372   def good_kachi?(sente)
373     if (checkmated?(sente))
374       puts "'NG: Checkmating." if $DEBUG
375       return false 
376     end
377     
378     ou = look_for_ou(sente)
379     if (sente && (ou.y >= 4))
380       puts "'NG: Black's OU does not enter yet." if $DEBUG
381       return false     
382     end  
383     if (! sente && (ou.y <= 6))
384       puts "'NG: White's OU does not enter yet." if $DEBUG
385       return false 
386     end
387       
388     number = 0
389     point = 0
390
391     if (sente)
392       hands = @sente_hands
393       r = [1, 2, 3]
394     else
395       hands = @gote_hands
396       r = [7, 8, 9]
397     end
398     r.each do |y|
399       x = 1
400       while (x <= 9)
401         if (@array[x][y] &&
402             (@array[x][y].sente == sente) &&
403             (@array[x][y].point > 0))
404           point = point + @array[x][y].point
405           number = number + 1
406         end
407         x = x + 1
408       end
409     end
410     hands.each do |piece|
411       point = point + piece.point
412     end
413
414     if (number < 10)
415       puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
416       return false     
417     end  
418     if (sente)
419       if (point < 28)
420         puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
421         return false 
422       end  
423     else
424       if (point < 27)
425         puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
426         return false 
427       end
428     end
429
430     puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
431     return true
432   end
433
434   # sente is nil only if tests in test_board run
435   def handle_one_move(str, sente=nil)
436     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
437       sg = $1
438       x0 = $2.to_i
439       y0 = $3.to_i
440       x1 = $4.to_i
441       y1 = $5.to_i
442       name = $6
443     elsif (str =~ /^%KACHI/)
444       raise ArgumentError, "sente is null", caller if sente == nil
445       if (good_kachi?(sente))
446         return :kachi_win
447       else
448         return :kachi_lose
449       end
450     elsif (str =~ /^%TORYO/)
451       return :toryo
452     else
453       return :illegal
454     end
455     
456     if (((x0 == 0) || (y0 == 0)) && # source is not from hand
457         ((x0 != 0) || (y0 != 0)))
458       return :illegal
459     elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
460       return :illegal
461     end
462     
463     if (sg == "+")
464       sente = true if sente == nil           # deprecated
465       return :illegal unless sente == true   # black player's move must be black
466       hands = @sente_hands
467     else
468       sente = false if sente == nil          # deprecated
469       return :illegal unless sente == false  # white player's move must be white
470       hands = @gote_hands
471     end
472     
473     ## source check
474     if ((x0 == 0) && (y0 == 0))
475       return :illegal if (! have_piece?(hands, name))
476     elsif (! @array[x0][y0])
477       return :illegal           # no piece
478     elsif (@array[x0][y0].sente != sente)
479       return :illegal           # this is not mine
480     elsif (@array[x0][y0].name != name)
481       return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
482     end
483
484     ## destination check
485     if (@array[x1][y1] &&
486         (@array[x1][y1].sente == sente)) # can't capture mine
487       return :illegal
488     elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
489       return :illegal           # can't put on existing piece
490     end
491
492     tmp_board = deep_copy
493     return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
494     return :oute_kaihimore if (tmp_board.checkmated?(sente))
495     tmp_board.update_sennichite(sente)
496     os_result = tmp_board.oute_sennichite?(sente)
497     return os_result if os_result # :oute_sennichite_sente_lose or :oute_sennichite_gote_lose
498     return :sennichite if tmp_board.sennichite?
499
500     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
501       return :uchifuzume
502     end
503
504     move_to(x0, y0, x1, y1, name, sente)
505
506     update_sennichite(sente)
507     return :normal
508   end
509
510   def to_s
511     a = Array::new
512     y = 1
513     while (y <= 9)
514       a.push(sprintf("P%d", y))
515       x = 9
516       while (x >= 1)
517         piece = @array[x][y]
518         if (piece)
519           s = piece.to_s
520         else
521           s = " * "
522         end
523         a.push(s)
524         x = x - 1
525       end
526       a.push(sprintf("\n"))
527       y = y + 1
528     end
529     if (! sente_hands.empty?)
530       a.push("P+")
531       sente_hands.each do |p|
532         a.push("00" + p.name)
533       end
534       a.push("\n")
535     end
536     if (! gote_hands.empty?)
537       a.push("P-")
538       gote_hands.each do |p|
539         a.push("00" + p.name)
540       end
541       a.push("\n")
542     end
543     a.push("%s\n" % [@teban ? "+" : "-"])
544     return a.join
545   end
546 end
547
548 end # ShogiServer