OSDN Git Service

efc1e36ef666a69c1b3ca21792bc1e96c84c0423
[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     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
47     board.array = []
48     self.array.each {|a| board.array.push(a.clone)}
49     board.teban         = self.teban
50     return board
51   end
52
53   def initial
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)
65     (1..9).each do |i|
66       PieceFU::new(self, i, 3, false)
67     end
68
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)
80     (1..9).each do |i|
81       PieceFU::new(self, i, 7, true)
82     end
83     @teban = true
84   end
85
86   def have_piece?(hands, name)
87     piece = hands.find { |i|
88       i.name == name
89     }
90     return piece
91   end
92
93   def move_to(x0, y0, x1, y1, name, sente)
94     if (sente)
95       hands = @sente_hands
96     else
97       hands = @gote_hands
98     end
99
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)
104     else
105       if (@array[x0][y0] == nil || !@array[x0][y0].move_to?(x1, y1, name))
106         return :illegal
107       end
108       if (@array[x0][y0].name != name) # promoted ?
109         @array[x0][y0].promoted = true
110       end
111       if (@array[x1][y1]) # capture
112         if (@array[x1][y1].name == "OU")
113           return :outori        # return board update
114         end
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
118           a.name <=> b.name
119         }
120       end
121       @array[x0][y0].move_to(x1, y1)
122     end
123     @move_count += 1
124     @teban = @teban ? false : true
125     return true
126   end
127
128   def 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].name == "OU") &&
135             (@array[x][y].sente == sente))
136           return @array[x][y]
137         end
138         y = y + 1
139       end
140       x = x + 1
141     end
142     raise "can't find ou"
143   end
144
145   # note checkmate, but check. sente is checked.
146   def checkmated?(sente)        # sente is loosing
147     ou = look_for_ou(sente)
148     x = 1
149     while (x <= 9)
150       y = 1
151       while (y <= 9)
152         if (@array[x][y] &&
153             (@array[x][y].sente != sente))
154           if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
155             return true
156           end
157         end
158         y = y + 1
159       end
160       x = x + 1
161     end
162     return false
163   end
164
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
172         fu_x = rival_ou.x
173         fu_y = rival_ou.y + 1
174       else
175         return false
176       end
177     else                        # gote
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
182         fu_x = rival_ou.x
183         fu_y = rival_ou.y - 1
184       else
185         return false
186       end
187     end
188
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
195         return false
196       end
197     end
198
199     ## case: rival is capturing fu
200     x = 1
201     while (x <= 9)
202       y = 1
203       while (y <= 9)
204         if (@array[x][y] &&
205             (@array[x][y].sente != sente) &&
206             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
207           
208           names = []
209           if (@array[x][y].promoted)
210             names << @array[x][y].promoted_name
211           else
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 
216             end
217           end
218           names.map! do |name|
219             tmp_board = deep_copy
220             s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
221             if s == :illegal
222               s # result
223             else
224               tmp_board.checkmated?(! sente) # result
225             end
226           end
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
231         end
232         y = y + 1
233       end
234       x = x + 1
235     end
236     return true
237   end
238
239   # @[sente|gote]_history has at least one item while the player is checking the other or 
240   # the other escapes.
241   def update_sennichite(player)
242     str = to_s
243     @history[str] += 1
244     if checkmated?(!player)
245       if (player)
246         @sente_history["dummy"] = 1  # flag to see Sente player is checking Gote player
247       else
248         @gote_history["dummy"]  = 1  # flag to see Gote player is checking Sente player
249       end
250     else
251       if (player)
252         @sente_history.clear # no more continuous check
253       else
254         @gote_history.clear  # no more continuous check
255       end
256     end
257     if @sente_history.size > 0  # possible for Sente's or Gote's turn
258       @sente_history[str] += 1
259     end
260     if @gote_history.size > 0   # possible for Sente's or Gote's turn
261       @gote_history[str] += 1
262     end
263   end
264
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
270     else
271       return nil
272     end
273   end
274
275   def sennichite?(sente)
276     if (@history[to_s] >= 4) # already 3 times
277       return true
278     end
279     return false
280   end
281
282   def good_kachi?(sente)
283     if (checkmated?(sente))
284       puts "'NG: Checkmating." if $DEBUG
285       return false 
286     end
287     
288     ou = look_for_ou(sente)
289     if (sente && (ou.y >= 4))
290       puts "'NG: Black's OU does not enter yet." if $DEBUG
291       return false     
292     end  
293     if (! sente && (ou.y <= 6))
294       puts "'NG: White's OU does not enter yet." if $DEBUG
295       return false 
296     end
297       
298     number = 0
299     point = 0
300
301     if (sente)
302       hands = @sente_hands
303       r = [1, 2, 3]
304     else
305       hands = @gote_hands
306       r = [7, 8, 9]
307     end
308     r.each do |y|
309       x = 1
310       while (x <= 9)
311         if (@array[x][y] &&
312             (@array[x][y].sente == sente) &&
313             (@array[x][y].point > 0))
314           point = point + @array[x][y].point
315           number = number + 1
316         end
317         x = x + 1
318       end
319     end
320     hands.each do |piece|
321       point = point + piece.point
322     end
323
324     if (number < 10)
325       puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
326       return false     
327     end  
328     if (sente)
329       if (point < 28)
330         puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
331         return false 
332       end  
333     else
334       if (point < 27)
335         puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
336         return false 
337       end
338     end
339
340     puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
341     return true
342   end
343
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})/)
347       sg = $1
348       x0 = $2.to_i
349       y0 = $3.to_i
350       x1 = $4.to_i
351       y1 = $5.to_i
352       name = $6
353     elsif (str =~ /^%KACHI/)
354       raise ArgumentError, "sente is null", caller if sente == nil
355       if (good_kachi?(sente))
356         return :kachi_win
357       else
358         return :kachi_lose
359       end
360     elsif (str =~ /^%TORYO/)
361       return :toryo
362     else
363       return :illegal
364     end
365     
366     if (((x0 == 0) || (y0 == 0)) && # source is not from hand
367         ((x0 != 0) || (y0 != 0)))
368       return :illegal
369     elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
370       return :illegal
371     end
372     
373     if (sg == "+")
374       sente = true if sente == nil           # deprecated
375       return :illegal unless sente == true   # black player's move must be black
376       hands = @sente_hands
377     else
378       sente = false if sente == nil          # deprecated
379       return :illegal unless sente == false  # white player's move must be white
380       hands = @gote_hands
381     end
382     
383     ## source check
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
392     end
393
394     ## destination check
395     if (@array[x1][y1] &&
396         (@array[x1][y1].sente == sente)) # can't capture mine
397       return :illegal
398     elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
399       return :illegal           # can't put on existing piece
400     end
401
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)
409
410     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
411       return :uchifuzume
412     end
413
414     move_to(x0, y0, x1, y1, name, sente)
415
416     update_sennichite(sente)
417     return :normal
418   end
419
420   def to_s
421     a = Array::new
422     y = 1
423     while (y <= 9)
424       a.push(sprintf("P%d", y))
425       x = 9
426       while (x >= 1)
427         piece = @array[x][y]
428         if (piece)
429           s = piece.to_s
430         else
431           s = " * "
432         end
433         a.push(s)
434         x = x - 1
435       end
436       a.push(sprintf("\n"))
437       y = y + 1
438     end
439     if (! sente_hands.empty?)
440       a.push("P+")
441       sente_hands.each do |p|
442         a.push("00" + p.name)
443       end
444       a.push("\n")
445     end
446     if (! gote_hands.empty?)
447       a.push("P-")
448       gote_hands.each do |p|
449         a.push("00" + p.name)
450       end
451       a.push("\n")
452     end
453     a.push("%s\n" % [@teban ? "+" : "-"])
454     return a.join
455   end
456 end
457
458 end # ShogiServer