This repository has been archived by the owner on Dec 12, 2021. It is now read-only.
/
game_engine.rb
134 lines (118 loc) · 3.42 KB
/
game_engine.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
class GameEngine
class Error < StandardError; end
class IllegalMove < Error; end
class OutOfTurn < Error; end
def self.run(options = {})
Go::GTP.run_gnugo do |gtp|
gtp.boardsize(options[:board_size]) unless options[:board_size].to_s.empty?
gtp.fixed_handicap(options[:handicap]) if options[:handicap].to_i.nonzero?
gtp.komi(options[:komi]) unless options[:komi].to_s.empty?
yield GameEngine.new(gtp, options)
end
end
def self.update_game_attributes_with_move(game, move = nil)
update = {}
run(game) do |engine|
update.merge!(engine.update_game_attributes_with_move(game, move))
end
update
end
def initialize(gtp, options = {})
@resigned = nil
@gtp = gtp
@board_size = options[:board_size] || 19
@handicap = options[:handicap].to_i
@current_color = @handicap > 0 ? :white : :black
end
def replay(moves)
moves.to_s.split("-").each do |move|
play(move =~ /[A-Z]/ ? move : move[0..1])
end
end
# Play at the given position, nil for computer play
def play(position = nil)
if position.nil?
position = sgf_point(@gtp.genmove(@current_color))
else
@gtp.play(@current_color, gnugo_point(position)) unless position == "RESIGN"
raise IllegalMove unless @gtp.success?
end
if position == "RESIGN"
@resigned = @current_color
end
@current_color = opposite_color
position
end
# Play the move and include the captured stones afterwards
def move(position = nil)
raise IllegalMove if over?
if %w[PASS RESIGN].include? position
play(position)
else
other_color = opposite_color
other_stones = @gtp.list_stones(other_color)
position = play(position)
captured = other_stones - @gtp.list_stones(other_color)
position + captured.map { |v| sgf_point(v) }.join
end
end
def update_game_attributes_with_move(game, move = nil)
update = {}
replay(game[:moves])
update[:moves] = [game[:moves].to_s, move(move)].reject(&:empty?).join("-")
update[:last_move_at] = Time.now
update[:black_positions] = positions(:black)
update[:white_positions] = positions(:white)
update[:current_player_id] = game["#{@current_color}_player_id".to_sym]
if over?
update[:finished_at] = Time.now
update[:black_score] = score(:black)
update[:white_score] = score(:white)
else
update[:black_score] = captures(:black)
update[:white_score] = captures(:white)
end
update
end
def positions(color)
@gtp.list_stones(color).map { |v| sgf_point(v) }.join
end
def captures(color)
@gtp.captures(color)
end
def over?
@resigned || @gtp.over?
end
def score(color)
if @resigned
@resigned.to_sym == color.to_sym ? 0 : 1
else
@gtp.final_score[/^#{color.to_s[0].upcase}\+([\d\.]+)$/, 1].to_f
end
end
private
def opposite_color
@current_color == :black ? :white : :black
end
def point(position)
args = [position]
if position =~ /\A[A-HJ-T](?:1\d|[1-9])\z/
args << {:board_size => @board_size}
end
Go::GTP::Point.new(*args)
end
def gnugo_point(position)
if %w[PASS RESIGN].include? position.to_s.upcase
position.to_s.upcase
else
point(position).to_gnugo(@board_size)
end
end
def sgf_point(position)
if %w[PASS RESIGN].include? position.to_s.upcase
position.to_s.upcase
else
point(position).to_sgf
end
end
end