rubykon 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +11 -0
  6. data/CHANGELOG.md +32 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +7 -0
  9. data/Guardfile +12 -0
  10. data/LICENSE +22 -0
  11. data/POSSIBLE_IMPROVEMENTS.md +25 -0
  12. data/README.md +36 -0
  13. data/Rakefile +6 -0
  14. data/benchmark/benchmark.sh +22 -0
  15. data/benchmark/full_playout.rb +17 -0
  16. data/benchmark/mcts_avg.rb +23 -0
  17. data/benchmark/playout.rb +15 -0
  18. data/benchmark/playout_micros.rb +188 -0
  19. data/benchmark/profiling/full_playout.rb +7 -0
  20. data/benchmark/profiling/mcts.rb +6 -0
  21. data/benchmark/results/HISTORY.md +541 -0
  22. data/benchmark/scoring.rb +20 -0
  23. data/benchmark/scoring_micros.rb +60 -0
  24. data/benchmark/support/benchmark-ips.rb +11 -0
  25. data/benchmark/support/benchmark-ips_shim.rb +143 -0
  26. data/benchmark/support/playout_help.rb +13 -0
  27. data/examples/mcts_laziness.rb +22 -0
  28. data/exe/rubykon +5 -0
  29. data/lib/benchmark/avg.rb +14 -0
  30. data/lib/benchmark/avg/benchmark_suite.rb +59 -0
  31. data/lib/benchmark/avg/job.rb +92 -0
  32. data/lib/mcts.rb +11 -0
  33. data/lib/mcts/examples/double_step.rb +68 -0
  34. data/lib/mcts/mcts.rb +13 -0
  35. data/lib/mcts/node.rb +88 -0
  36. data/lib/mcts/playout.rb +22 -0
  37. data/lib/mcts/root.rb +49 -0
  38. data/lib/rubykon.rb +13 -0
  39. data/lib/rubykon/board.rb +188 -0
  40. data/lib/rubykon/cli.rb +122 -0
  41. data/lib/rubykon/exceptions/exceptions.rb +1 -0
  42. data/lib/rubykon/exceptions/illegal_move_exception.rb +4 -0
  43. data/lib/rubykon/eye_detector.rb +27 -0
  44. data/lib/rubykon/game.rb +115 -0
  45. data/lib/rubykon/game_scorer.rb +62 -0
  46. data/lib/rubykon/game_state.rb +93 -0
  47. data/lib/rubykon/group.rb +99 -0
  48. data/lib/rubykon/group_tracker.rb +144 -0
  49. data/lib/rubykon/gtp_coordinate_converter.rb +25 -0
  50. data/lib/rubykon/move_validator.rb +55 -0
  51. data/lib/rubykon/version.rb +3 -0
  52. data/rubykon.gemspec +21 -0
  53. 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
@@ -0,0 +1,3 @@
1
+ module Rubykon
2
+ VERSION = "0.3.0"
3
+ end
@@ -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: []