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.
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: []