Add shogi PSN load/save support.
[kaya.git] / lib / games / chess / pgn.rb
blobfb13ecb7497cb8e4ef05fe6edb3ad6e1f541b4b8
1 require 'strscan'
2 require 'history'
3 require 'qtutils'
5 module Chess
7 class PGN
8   ParseError = Class.new(ParseException)
9   
10   def initialize(serializer, state_factory)
11     @serializer = serializer
12     @state_factory = state_factory
13   end
14   
15   def write(info, history)
16     date = if info[:date].respond_to? :strftime
17       info[:date].strftime('%Y.%m.%d')
18     else
19       info[:date]
20     end
21     tag(:event, info[:event]) +
22     tag(:site, info[:site]) +
23     tag(:date, date) +
24     tag(:round, info[:round]) +
25     player_tags(info[:players] || { }) +
26     tag(:result, result(info[:result])) +
27     "\n" +
28     game(history) + " " +
29     result(info[:result]) + "\n"
30   end
31   
32   def read(text, info)
33     # read tags
34     scanner = StringScanner.new(text)
35     while scanner.scan(/\[(.*) "(.*)"\]\n/)
36       info[scanner[1].downcase.to_sym] = scanner[2]
37     end
38     scanner.scan(/\n*/)
39     
40     # insert players into info[:players]
41     info[:players] = read_players(info)
42     
43     state = @state_factory.new
44     state.setup
45     history = History.new(state)
46     index = 1
47     
48     while scanner.scan(/(\d+)\.\s*/)
49       if index != scanner[1].to_i
50         raise ParseError.new("Unexpected index #{index}")
51       end
52       
53       wmove = @serializer.deserialize(scanner, state)
54       raise ParseError.new("Expected move at index #{index}") unless wmove
55       state = state.dup
56       state.perform! wmove
57       history.add_move(state, wmove)
58             
59       scanner.scan(/\s+/)
60       bmove = @serializer.deserialize(scanner, state)
61       break unless bmove
62       state = state.dup
63       state.perform! bmove
64       history.add_move(state, bmove)
65       
66       scanner.scan(/\s+/)
67       
68       index += 1
69     end
70     
71     result = scanner.scan(/1-0|0-1|1\/2-1\/2|\*/)
72     info[:result] = result if result
73     history
74   end
75   
76   def tag(key, value)
77     if value
78       %{[#{key.to_s.capitalize} "#{value}"]\n}
79     else
80       ""
81     end
82   end
83   
84   def read_players(info)
85     result = {
86       :white => info[:white],
87       :black => info[:black] }
88     info.delete(:white)
89     info.delete(:black)
90     result
91   end
92   
93   def player_tags(players)
94     tag(:white, players[:white]) +
95     tag(:black, players[:black])
96   end
97   
98   def result(value)
99     case value
100     when String
101       value
102     when :white
103       "1-0"
104     when :black
105       "0-1"
106     when :draw
107       "1/2-1/2"
108     else
109       "*"
110     end
111   end
112   
113   def game(history)
114     1.to_enum(:step, history.size - 1, 2).map do |i|
115       wmove = @serializer.serialize(history[i].move, history[i - 1].state)
116       bmove = if i + 1 < history.size
117         @serializer.serialize(history[i + 1].move, history[i].state)
118       end
119       index = (i + 1) / 2
120       result = "#{index}.#{wmove}"
121       result += " #{bmove}" if bmove
122       result
123     end.join(' ')
124   end