rubykon 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +32 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +7 -0
- data/Guardfile +12 -0
- data/LICENSE +22 -0
- data/POSSIBLE_IMPROVEMENTS.md +25 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/benchmark/benchmark.sh +22 -0
- data/benchmark/full_playout.rb +17 -0
- data/benchmark/mcts_avg.rb +23 -0
- data/benchmark/playout.rb +15 -0
- data/benchmark/playout_micros.rb +188 -0
- data/benchmark/profiling/full_playout.rb +7 -0
- data/benchmark/profiling/mcts.rb +6 -0
- data/benchmark/results/HISTORY.md +541 -0
- data/benchmark/scoring.rb +20 -0
- data/benchmark/scoring_micros.rb +60 -0
- data/benchmark/support/benchmark-ips.rb +11 -0
- data/benchmark/support/benchmark-ips_shim.rb +143 -0
- data/benchmark/support/playout_help.rb +13 -0
- data/examples/mcts_laziness.rb +22 -0
- data/exe/rubykon +5 -0
- data/lib/benchmark/avg.rb +14 -0
- data/lib/benchmark/avg/benchmark_suite.rb +59 -0
- data/lib/benchmark/avg/job.rb +92 -0
- data/lib/mcts.rb +11 -0
- data/lib/mcts/examples/double_step.rb +68 -0
- data/lib/mcts/mcts.rb +13 -0
- data/lib/mcts/node.rb +88 -0
- data/lib/mcts/playout.rb +22 -0
- data/lib/mcts/root.rb +49 -0
- data/lib/rubykon.rb +13 -0
- data/lib/rubykon/board.rb +188 -0
- data/lib/rubykon/cli.rb +122 -0
- data/lib/rubykon/exceptions/exceptions.rb +1 -0
- data/lib/rubykon/exceptions/illegal_move_exception.rb +4 -0
- data/lib/rubykon/eye_detector.rb +27 -0
- data/lib/rubykon/game.rb +115 -0
- data/lib/rubykon/game_scorer.rb +62 -0
- data/lib/rubykon/game_state.rb +93 -0
- data/lib/rubykon/group.rb +99 -0
- data/lib/rubykon/group_tracker.rb +144 -0
- data/lib/rubykon/gtp_coordinate_converter.rb +25 -0
- data/lib/rubykon/move_validator.rb +55 -0
- data/lib/rubykon/version.rb +3 -0
- data/rubykon.gemspec +21 -0
- metadata +97 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
module Rubykon
|
2
|
+
class GroupTracker
|
3
|
+
|
4
|
+
attr_reader :groups, :stone_to_group
|
5
|
+
|
6
|
+
def initialize(groups = {}, stone_to_group = {})
|
7
|
+
@groups = groups
|
8
|
+
@stone_to_group = stone_to_group
|
9
|
+
end
|
10
|
+
|
11
|
+
def assign(identifier, color, board)
|
12
|
+
neighbours_by_color = color_to_neighbour(board, identifier)
|
13
|
+
join_group_of_friendly_stones(neighbours_by_color[color], identifier)
|
14
|
+
create_own_group(identifier) unless group_id_of(identifier)
|
15
|
+
add_liberties(neighbours_by_color[Board::EMPTY], identifier)
|
16
|
+
take_liberties_of_enemies(neighbours_by_color[Game.other_color(color)], identifier, board, color)
|
17
|
+
end
|
18
|
+
|
19
|
+
def liberty_count_at(identifier)
|
20
|
+
group_of(identifier).liberty_count
|
21
|
+
end
|
22
|
+
|
23
|
+
def group_id_of(identifier)
|
24
|
+
@stone_to_group[identifier]
|
25
|
+
end
|
26
|
+
|
27
|
+
def group_of(identifier)
|
28
|
+
group(group_id_of(identifier))
|
29
|
+
end
|
30
|
+
|
31
|
+
def group(id)
|
32
|
+
@groups[id]
|
33
|
+
end
|
34
|
+
|
35
|
+
def stone_joins_group(stone_identifier, group_identifier)
|
36
|
+
@stone_to_group[stone_identifier] = group_identifier
|
37
|
+
end
|
38
|
+
|
39
|
+
def dup
|
40
|
+
self.class.new(dup_groups, @stone_to_group.dup)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def color_to_neighbour(board, identifier)
|
45
|
+
neighbors = board.neighbours_of(identifier)
|
46
|
+
hash = neighbors.inject({}) do |hash, (n_identifier, color)|
|
47
|
+
(hash[color] ||= []) << n_identifier
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
hash.default = []
|
51
|
+
hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def take_liberties_of_enemies(enemy_neighbours, identifier, board, capturer_color)
|
55
|
+
my_group = group_of(identifier)
|
56
|
+
captures = enemy_neighbours.inject([]) do |caught, enemy_identifier|
|
57
|
+
enemy_group = group_of(enemy_identifier)
|
58
|
+
remove_liberties(enemy_identifier, enemy_group, identifier, my_group)
|
59
|
+
collect_captured_groups(caught, enemy_group)
|
60
|
+
end
|
61
|
+
remove_caught_groups(board, capturer_color, captures)
|
62
|
+
end
|
63
|
+
|
64
|
+
def remove_liberties(enemy_identifier, enemy_group, identifier, my_group)
|
65
|
+
enemy_group.remove_liberty(identifier)
|
66
|
+
# this needs to be the identifier and not the group, as groups
|
67
|
+
# might get merged
|
68
|
+
my_group.add_enemy_group_at(enemy_identifier)
|
69
|
+
end
|
70
|
+
|
71
|
+
def collect_captured_groups(caught, enemy_group)
|
72
|
+
if enemy_group.caught? && !caught.include?(enemy_group)
|
73
|
+
caught << enemy_group
|
74
|
+
end
|
75
|
+
caught
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_caught_groups(board, capturer_color, caught)
|
79
|
+
captures = caught.inject([]) do |captures, enemy_group|
|
80
|
+
captures + remove(enemy_group, board)
|
81
|
+
end
|
82
|
+
captures
|
83
|
+
end
|
84
|
+
|
85
|
+
def remove(enemy_group, board)
|
86
|
+
regain_liberties_from_capture(enemy_group)
|
87
|
+
delete_group(enemy_group)
|
88
|
+
remove_captured_stones(board, enemy_group)
|
89
|
+
end
|
90
|
+
|
91
|
+
def remove_captured_stones(board, enemy_group)
|
92
|
+
captured_stones = enemy_group.stones
|
93
|
+
captured_stones.each do |identifier|
|
94
|
+
@stone_to_group.delete identifier
|
95
|
+
board[identifier] = Board::EMPTY
|
96
|
+
end
|
97
|
+
captured_stones
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete_group(enemy_group)
|
101
|
+
@groups.delete(enemy_group.identifier)
|
102
|
+
end
|
103
|
+
|
104
|
+
def regain_liberties_from_capture(enemy_group)
|
105
|
+
neighboring_groups_of(enemy_group).each do |neighbor_group|
|
106
|
+
neighbor_group.gain_liberties_from_capture_of(enemy_group, self)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def neighboring_groups_of(enemy_group)
|
111
|
+
enemy_group.liberties.map do |identifier, _|
|
112
|
+
group_of(identifier)
|
113
|
+
end.compact.uniq
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_liberties(liberties, identifier)
|
117
|
+
liberties.each do |liberty_identifier|
|
118
|
+
group_of(identifier).add_liberty(liberty_identifier)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def join_group_of_friendly_stones(friendly_stones, identifier)
|
123
|
+
friendly_stones.each do |f_identifier, _color|
|
124
|
+
friendly_group = group_of(f_identifier)
|
125
|
+
friendly_group.connect identifier, group_of(identifier), self
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_own_group(identifier)
|
130
|
+
# we can use the identifier of the stone, as it should not be taken
|
131
|
+
# (it may have been taken before, but for that stone to be played the
|
132
|
+
# other group would have had to be captured before)
|
133
|
+
@groups[identifier] = Group.new(identifier)
|
134
|
+
@stone_to_group[identifier] = identifier
|
135
|
+
end
|
136
|
+
|
137
|
+
def dup_groups
|
138
|
+
@groups.inject({}) do |dupped, (key, group)|
|
139
|
+
dupped[key] = group.dup
|
140
|
+
dupped
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rubykon
|
2
|
+
class GTPCoordinateConverter
|
3
|
+
|
4
|
+
X_CHARS = ('A'..'Z').reject { |c| c == 'I'.freeze }
|
5
|
+
|
6
|
+
def initialize(board)
|
7
|
+
@board = board
|
8
|
+
end
|
9
|
+
|
10
|
+
def from(string)
|
11
|
+
x = string[0]
|
12
|
+
y = string[1..-1]
|
13
|
+
x_index = X_CHARS.index(x) + 1
|
14
|
+
y_index = @board.size - y.to_i + 1
|
15
|
+
@board.identifier_for(x_index, y_index)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to(index)
|
19
|
+
x, y = @board.x_y_from(index)
|
20
|
+
x_char = X_CHARS[x - 1]
|
21
|
+
y_index = @board.size - y + 1
|
22
|
+
"#{x_char}#{y_index}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Rubykon
|
2
|
+
class MoveValidator
|
3
|
+
|
4
|
+
def valid?(identifier, color, game)
|
5
|
+
board = game.board
|
6
|
+
no_double_move?(color, game) &&
|
7
|
+
(Game.pass?(identifier) ||
|
8
|
+
(move_on_board?(identifier, board) &&
|
9
|
+
spot_unoccupied?(identifier, board) &&
|
10
|
+
no_suicide_move?(identifier, color, game) &&
|
11
|
+
no_ko_move?(identifier, game)))
|
12
|
+
end
|
13
|
+
|
14
|
+
def trusted_valid?(identifier, color, game)
|
15
|
+
board = game.board
|
16
|
+
spot_unoccupied?(identifier, board) &&
|
17
|
+
no_ko_move?(identifier, game) &&
|
18
|
+
no_suicide_move?(identifier, color, game)
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def no_double_move?(color, game)
|
24
|
+
color == game.next_turn_color
|
25
|
+
end
|
26
|
+
|
27
|
+
def move_on_board?(identifier, board)
|
28
|
+
board.on_board?(identifier)
|
29
|
+
end
|
30
|
+
|
31
|
+
def spot_unoccupied?(identifier, board)
|
32
|
+
board[identifier] == Board::EMPTY
|
33
|
+
end
|
34
|
+
|
35
|
+
def no_suicide_move?(identifier, color, game)
|
36
|
+
enemy_color = Game.other_color(color)
|
37
|
+
board = game.board
|
38
|
+
board_neighbours_of = board.neighbours_of(identifier)
|
39
|
+
p identifier if board_neighbours_of.nil?
|
40
|
+
board_neighbours_of.any? do |n_identifier, n_color|
|
41
|
+
(n_color == Board::EMPTY) ||
|
42
|
+
(n_color == color) && (liberties_at(n_identifier, game) > 1) ||
|
43
|
+
(n_color == enemy_color) && (liberties_at(n_identifier, game) <= 1)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def liberties_at(identifier, game)
|
48
|
+
game.group_tracker.liberty_count_at(identifier)
|
49
|
+
end
|
50
|
+
|
51
|
+
def no_ko_move?(identifier, game)
|
52
|
+
identifier != game.ko
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/rubykon.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rubykon/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rubykon"
|
8
|
+
spec.version = Rubykon::VERSION
|
9
|
+
spec.authors = ["Tobias Pfeiffer"]
|
10
|
+
spec.email = ["pragtob@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{An AI to play Go using Monte Carlo Tree Search.}
|
13
|
+
spec.description = %q{An AI to play Go using Monte Carlo Tree Search. Currently includes the mcts gem and benchmark/avg. Works on all major ruby versions.}
|
14
|
+
spec.homepage = "https://github.com/PragTob/rubykon"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubykon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tobias Pfeiffer
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-16 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: An AI to play Go using Monte Carlo Tree Search. Currently includes the
|
14
|
+
mcts gem and benchmark/avg. Works on all major ruby versions.
|
15
|
+
email:
|
16
|
+
- pragtob@gmail.com
|
17
|
+
executables:
|
18
|
+
- rubykon
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- ".gitignore"
|
23
|
+
- ".ruby-gemset"
|
24
|
+
- ".ruby-version"
|
25
|
+
- ".travis.yml"
|
26
|
+
- CHANGELOG.md
|
27
|
+
- CODE_OF_CONDUCT.md
|
28
|
+
- Gemfile
|
29
|
+
- Guardfile
|
30
|
+
- LICENSE
|
31
|
+
- POSSIBLE_IMPROVEMENTS.md
|
32
|
+
- README.md
|
33
|
+
- Rakefile
|
34
|
+
- benchmark/benchmark.sh
|
35
|
+
- benchmark/full_playout.rb
|
36
|
+
- benchmark/mcts_avg.rb
|
37
|
+
- benchmark/playout.rb
|
38
|
+
- benchmark/playout_micros.rb
|
39
|
+
- benchmark/profiling/full_playout.rb
|
40
|
+
- benchmark/profiling/mcts.rb
|
41
|
+
- benchmark/results/HISTORY.md
|
42
|
+
- benchmark/scoring.rb
|
43
|
+
- benchmark/scoring_micros.rb
|
44
|
+
- benchmark/support/benchmark-ips.rb
|
45
|
+
- benchmark/support/benchmark-ips_shim.rb
|
46
|
+
- benchmark/support/playout_help.rb
|
47
|
+
- examples/mcts_laziness.rb
|
48
|
+
- exe/rubykon
|
49
|
+
- lib/benchmark/avg.rb
|
50
|
+
- lib/benchmark/avg/benchmark_suite.rb
|
51
|
+
- lib/benchmark/avg/job.rb
|
52
|
+
- lib/mcts.rb
|
53
|
+
- lib/mcts/examples/double_step.rb
|
54
|
+
- lib/mcts/mcts.rb
|
55
|
+
- lib/mcts/node.rb
|
56
|
+
- lib/mcts/playout.rb
|
57
|
+
- lib/mcts/root.rb
|
58
|
+
- lib/rubykon.rb
|
59
|
+
- lib/rubykon/board.rb
|
60
|
+
- lib/rubykon/cli.rb
|
61
|
+
- lib/rubykon/exceptions/exceptions.rb
|
62
|
+
- lib/rubykon/exceptions/illegal_move_exception.rb
|
63
|
+
- lib/rubykon/eye_detector.rb
|
64
|
+
- lib/rubykon/game.rb
|
65
|
+
- lib/rubykon/game_scorer.rb
|
66
|
+
- lib/rubykon/game_state.rb
|
67
|
+
- lib/rubykon/group.rb
|
68
|
+
- lib/rubykon/group_tracker.rb
|
69
|
+
- lib/rubykon/gtp_coordinate_converter.rb
|
70
|
+
- lib/rubykon/move_validator.rb
|
71
|
+
- lib/rubykon/version.rb
|
72
|
+
- rubykon.gemspec
|
73
|
+
homepage: https://github.com/PragTob/rubykon
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.4.8
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: An AI to play Go using Monte Carlo Tree Search.
|
97
|
+
test_files: []
|