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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b0c6f51cb33c58227a82783ff663d546397ef750
4
+ data.tar.gz: 2764e68e3b5a9ecc0ed8b1e62273201bf8748962
5
+ SHA512:
6
+ metadata.gz: 8d3d477881d123c94556c396885002ff3e01708f6f147c8aa1ceb7b625189de8d6f59202393d8f9806e75ef7743ccbd204c6f37e136b5dbe26b7f631eb5067f3
7
+ data.tar.gz: e5b6acffc005f3e3ad2098cb7c3efc37b95253e8d58288a486d46f43b7c12dd047e80b528dd95ad47fd7d5ad4b510bcea5db48b6da333d1767dd9b810c2b4993
@@ -0,0 +1,5 @@
1
+ Gemfile.lock
2
+ profiling.html
3
+ .bundle
4
+ .jruby+truffle_bundle
5
+ pkg/
@@ -0,0 +1 @@
1
+ rubykon
@@ -0,0 +1 @@
1
+ ruby-2.2.3
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - jruby-19mode
4
+ - rbx-19mode
5
+ - 2.2
6
+ - jruby-9.0.3.0
7
+ script: bundle exec rspec spec
8
+
9
+ addons:
10
+ code_climate:
11
+ repo_token: fab8afb587984cc2f6100be9c660e966ac3fb5e113458fb381267bfffcef15fe
@@ -0,0 +1,32 @@
1
+ ## 0.3 (2015-11-12)
2
+ Implement full bot together with Monte Carlo Tree Search, as well as a more coarse grained benchmarking tool to benchmark full MCTS runs. Also add a CLI. Mostly a feature release.
3
+
4
+ ### Performance
5
+ * Faster and more reliable move selection
6
+ * optimize neighbours_of by enumerating possibilities, raw but effective
7
+
8
+ ### Features
9
+ * Full Monte Carlo Tree Search implementation
10
+ * Basic CLI implementation
11
+ * benchmark/avg to do more coarse grained benchmarking
12
+ * More readable string board representation
13
+ * Added License (oops)
14
+ * Added CoC
15
+
16
+ ### Bugfixes
17
+ * correctly count captures for score
18
+
19
+ ## 0.2 (2015-10-03)
20
+ Rewrote data representation to be smaller and do way less allocations.
21
+
22
+ ### Performance
23
+ * board is now a one dimensional array with each element corresponding to one cutting point (e.g. 1-1 is 0, 3-1 is 2, 1-2 is 19 (on 19x19).
24
+ * no more stone class, the board just stores the color of the stone there. Instead of `Stone` objects, an identifier (see above) and its color are passed around.
25
+ * what would be a ko move is now stored on the game, making checking faster and easier
26
+ * dupping games is easier thanks to simpler data structures
27
+
28
+ ### Bugfixes
29
+ * captures are correctly included when scoring a game
30
+
31
+ ## 0.1 (2015-09-25)
32
+ Basic game version, able to perform random playouts and benchmark those.
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # all dev dependencies
4
+ gem 'rspec', '~> 3.3'
5
+ gem 'guard-rspec'
6
+ gem "codeclimate-test-reporter", group: :test, require: nil
7
+ gem "benchmark-ips"
@@ -0,0 +1,12 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ rspec = dsl.rspec
6
+ watch(rspec.spec_helper) { rspec.spec_dir }
7
+ watch(rspec.spec_support) { rspec.spec_dir }
8
+ watch(rspec.spec_files)
9
+
10
+ ruby = dsl.ruby
11
+ dsl.watch_spec_files_for(ruby.lib_files)
12
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (C) 2012-2015 Tobias Pfeiffer
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without restriction,
6
+ including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software,
8
+ and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
15
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
16
+ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
17
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18
+ SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
20
+ OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,25 @@
1
+ # Possible Improvements
2
+
3
+ Possible improvements to try out in the implementation :)
4
+
5
+ ## Playout speed
6
+
7
+ ### Board representation
8
+ * Reuse stones instead of creating new ones (would probably require a separate move class)
9
+ * just use symbols on the board instead of full fledged objects
10
+ * use a one dimensional array as the board (can map back to original using modulo etc.)
11
+ * use multiple bitmasks (or a bigger one) to represent board state (we have 3 states so a simple mask won't do)
12
+ * neighbour_colors methods
13
+
14
+ ### Group
15
+ * remove references to stones/liberties from obsolete groups (when it is merged in another group or taken off the board)
16
+ * in liberties, don't point to stones but point to groups (be careful as no group is nil... and that then is not good with hash lookups)
17
+
18
+ ### Move gen
19
+ * check self atari?
20
+
21
+ ### Scoring
22
+ * use more efficient scoring algorithm (michi floodfill like?)
23
+
24
+ ### Move generation
25
+ * sensibly choose moves and not just that random barrage... possiby using an Enumerator
@@ -0,0 +1,36 @@
1
+ # Rubykon [![Build Status](https://secure.travis-ci.org/PragTob/rubykon.png?branch=master)](https://travis-ci.org/PragTob/rubykon)[![Code Climate](https://codeclimate.com/github/PragTob/Rubykon.png)](https://codeclimate.com/github/PragTob/Rubykon)[![Test Coverage](https://codeclimate.com/github/PragTob/Rubykon/badges/coverage.svg)](https://codeclimate.com/github/PragTob/Rubykon/coverage)
2
+ A Go-Engine being built in Ruby.
3
+
4
+ ### Status?
5
+ A naive very slow implementation of random playouts. Next up making it faster... or implementing the mcts bits. We'll see. Don't use and judge yet, some pretty inefficient stuff in here :D
6
+
7
+ ### Why would you build a Go-Bot in Ruby?
8
+ Cause it's fun.
9
+
10
+ ### Running truffle
11
+
12
+ Go ahead and [install from source](https://github.com/jruby/jruby/wiki/Truffle#from-source). Then you have to specify the graal VM when you execute something like this:
13
+
14
+ JAVACMD=~/dev/graalvm-jdk1.8.0/bin/java ../jruby/bin/jruby -X+T -e 'puts Truffle.graal?'
15
+
16
+ If this (adjusted to your paths) prints `true` then the setup is good so far.
17
+
18
+ Next up, install the jruby+truffle tool. Go into the jruby directory you checked out and make sure you use the same ruby version/gemset you want to use (this installs a gem). Then do:
19
+
20
+ tool/jt.rb install-tool
21
+
22
+ With this installed you can then setup graal in your repository (i.e. rubykon), this install gems etc.:
23
+
24
+ jruby+truffle setup
25
+
26
+ This should now still print true:
27
+
28
+ jruby+truffle --graal-path ../graalvm-jdk1.8.0/bin/java run --graal -- -e 'p Truffle.graal?'
29
+
30
+ You can then use it like this to run benchmarks et. al.:
31
+
32
+ jruby+truffle --graal-path ../graalvm-jdk1.8.0/bin/java run --graal -J-Xmx1500m benchmark/mcts.rb
33
+
34
+ The `-J-Xmx1500m` is important as truffle needs more heap space.
35
+
36
+ You can also run the specs via `jruby+truffle run -S rspec spec/`
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,22 @@
1
+ #! /bin/bash --login
2
+
3
+ script_name=$1
4
+
5
+ declare -A RUBY_TO_ARG=( ["2.2"]="" [jruby]="" [jruby-9]="--server -Xcompile.invokedynamic=true -J-Xmx1500m" ["rbx-2.5.8"]="" [jruby-1]="" [1.9.3]="" )
6
+
7
+ for ruby in "${!RUBY_TO_ARG[@]}"
8
+ do
9
+ echo Running $ruby with ${RUBY_TO_ARG[$ruby]}
10
+ rvm use $ruby
11
+ ruby -v
12
+ ruby ${RUBY_TO_ARG[$ruby]} $script_name
13
+ echo
14
+ echo
15
+ done
16
+
17
+ rvm use 2.2@rubykon
18
+ echo Running truffle graal with enough heap space
19
+ jruby+truffle run --graal -- -e "puts RUBY_DESCRIPTION"
20
+ jruby+truffle run --graal -J-Xmx1500m $script_name
21
+ echo
22
+ echo
@@ -0,0 +1,17 @@
1
+ require_relative '../lib/rubykon'
2
+ require_relative 'support/playout_help'
3
+ require_relative 'support/benchmark-ips'
4
+
5
+ Benchmark.ips do |benchmark|
6
+ benchmark.config time: 30, warmup: 60
7
+
8
+ benchmark.report '9x9 full playout (+ score)' do
9
+ full_playout_for 9
10
+ end
11
+ benchmark.report '13x13 full playout (+ score)' do
12
+ full_playout_for 13
13
+ end
14
+ benchmark.report '19x19 full playout (+ score)' do
15
+ full_playout_for 19
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ require_relative '../lib/rubykon'
2
+ require_relative '../lib/benchmark/avg'
3
+
4
+ Benchmark.avg do |benchmark|
5
+ game_state_9 = Rubykon::GameState.new Rubykon::Game.new(9)
6
+ game_state_13 = Rubykon::GameState.new Rubykon::Game.new(13)
7
+ game_state_19 = Rubykon::GameState.new Rubykon::Game.new(19)
8
+ mcts = MCTS::MCTS.new
9
+
10
+ benchmark.config warmup: 180, time: 120
11
+
12
+ benchmark.report "9x9 10_000 iterations" do
13
+ mcts.start game_state_9, 10_000
14
+ end
15
+
16
+ benchmark.report "13x13 2_000 iterations" do
17
+ mcts.start game_state_13, 2_000
18
+ end
19
+
20
+ benchmark.report "19x19 1_000 iterations" do
21
+ mcts.start game_state_19, 1_000
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../lib/rubykon'
2
+ require_relative 'support/playout_help'
3
+ require_relative 'support/benchmark-ips'
4
+
5
+ Benchmark.ips do |benchmark|
6
+ benchmark.report '9x9 playout' do
7
+ playout_for 9
8
+ end
9
+ benchmark.report '13x13 playout' do
10
+ playout_for 13
11
+ end
12
+ benchmark.report '19x19 playout' do
13
+ playout_for 19
14
+ end
15
+ end
@@ -0,0 +1,188 @@
1
+ require_relative '../lib/rubykon'
2
+ require_relative 'support/playout_help'
3
+ require_relative 'support/benchmark-ips'
4
+
5
+ class Rubykon::GameState
6
+ public :plausible_move?
7
+ public :next_turn_color
8
+ end
9
+
10
+ class Rubykon::EyeDetector
11
+ public :candidate_eye_color
12
+ public :is_real_eye?
13
+ end
14
+
15
+ class Rubykon::MoveValidator
16
+ public :no_suicide_move?
17
+ end
18
+
19
+ class Rubykon::GroupTracker
20
+ public :color_to_neighbour
21
+ public :create_own_group
22
+ public :take_liberties_of_enemies
23
+ public :join_group_of_friendly_stones
24
+ public :add_liberties
25
+ end
26
+
27
+
28
+ Benchmark.ips do |benchmark|
29
+ empty_game = Rubykon::GameState.new Rubykon::Game.new 19
30
+ mid_game = empty_game.dup
31
+ 200.times do
32
+ mid_game.set_move mid_game.generate_move
33
+ end
34
+ finished_game = playout_for(19).game_state
35
+
36
+ games = {
37
+ empty_game => 'empty game',
38
+ mid_game => 'mid game (200 moves played)',
39
+ finished_game => 'finished game'
40
+ }
41
+
42
+ games.each do |game_state, description|
43
+
44
+ game = game_state.game
45
+ board = game.board
46
+
47
+ benchmark.report "#{description}: finished?" do
48
+ game_state.finished?
49
+ end
50
+
51
+ benchmark.report "#{description}: generate_move" do
52
+ game_state.generate_move
53
+ end
54
+
55
+ color = game_state.next_turn_color
56
+
57
+ benchmark.report "#{description}: plausible_move?" do
58
+ identifier = rand(361)
59
+ game_state.plausible_move?(identifier, color)
60
+ end
61
+
62
+ validator = Rubykon::MoveValidator.new
63
+
64
+ benchmark.report "#{description}: valid?" do
65
+ identifier = rand(361)
66
+ validator.valid?(identifier, color, game)
67
+ end
68
+
69
+ benchmark.report "#{description}: no_suicide_move?" do
70
+ identifier = rand(361)
71
+ validator.no_suicide_move?(identifier, color, game)
72
+ end
73
+
74
+ eye_detector = Rubykon::EyeDetector.new
75
+
76
+ benchmark.report "#{description}: is_eye?" do
77
+ identifier = rand(361)
78
+ eye_detector.is_eye?(identifier, board)
79
+ end
80
+
81
+ benchmark.report "#{description}: candidate_eye_color" do
82
+ identifier = rand(361)
83
+ eye_detector.candidate_eye_color(identifier, board)
84
+ end
85
+
86
+ candidate_identifier = rand(361)
87
+ candidate_eye_color = eye_detector.candidate_eye_color(candidate_identifier, board)
88
+
89
+ benchmark.report "#{description}: is_real_eye?" do
90
+ eye_detector.is_real_eye?(candidate_identifier, board, candidate_eye_color)
91
+ end
92
+
93
+ benchmark.report "#{description}: diagonal_colors_of" do
94
+ identifier = rand(361)
95
+ board.diagonal_colors_of(identifier)
96
+ end
97
+
98
+ benchmark.report "#{description}: dup" do
99
+ game_state.dup
100
+ end
101
+
102
+ benchmark.report "#{description}: set_valid_move" do
103
+ game.dup.set_valid_move rand(361), color
104
+ end
105
+
106
+ benchmark.report "#{description}: assign" do
107
+ group_tracker = game.group_tracker.dup
108
+ group_tracker.assign(rand(361), color, board)
109
+ end
110
+
111
+ group_tracker = game.group_tracker.dup
112
+
113
+ benchmark.report "#{description}: color_to_neighbour" do
114
+ group_tracker.color_to_neighbour(board, rand(361))
115
+ end
116
+ end
117
+
118
+
119
+ # more rigorous setup, values gotta be right so we can't just take
120
+ # our randomly played out boards. Also this doesn't make much sense
121
+ # on the empty or finished board.
122
+
123
+ game = Rubykon::Game.new(19)
124
+ group_tracker = game.group_tracker
125
+
126
+ stone1 = 55
127
+ liberties_1 = {
128
+ 54 => Rubykon::Board::EMPTY,
129
+ 56 => 56,
130
+ 36 => Rubykon::Board::EMPTY,
131
+ 74 => Rubykon::Board::EMPTY,
132
+ 57 => Rubykon::Board::EMPTY
133
+ }
134
+ group_1 = Rubykon::Group.new(stone1, [stone1], liberties_1, 4)
135
+ group_tracker.stone_to_group[55] = 1
136
+ group_tracker.groups[1] = group_1
137
+ group_tracker.create_own_group(56)
138
+
139
+ stone2 = 33
140
+ liberties_2 = {
141
+ 32 => Rubykon::Board::EMPTY,
142
+ 34 => 34,
143
+ 24 => Rubykon::Board::EMPTY,
144
+ 54 => Rubykon::Board::EMPTY,
145
+ 55 => Rubykon::Board::EMPTY
146
+ }
147
+ group_2 = Rubykon::Group.new(stone2, [stone2], liberties_2, 4)
148
+ group_tracker.stone_to_group[33] = 2
149
+ group_tracker.groups[2] = group_2
150
+ group_tracker.create_own_group(34)
151
+
152
+ # "small groups" as they have few liberties and stones assigned to them,
153
+ # groups can easily have 20+ stones and even more liberties, but that'd
154
+ # be even more setup :)
155
+ benchmark.report 'connecting two small groups' do
156
+ stones = [stone1, stone2]
157
+ my_stone = 44
158
+ group_tracker.dup.join_group_of_friendly_stones(stones, my_stone)
159
+ end
160
+
161
+ benchmark.report 'add_liberties' do
162
+ liberties = [24, 78, 36, 79]
163
+ group_tracker.add_liberties(liberties, stone1)
164
+ end
165
+
166
+ enemy_stones = [56, 34]
167
+ liberties = {
168
+ stone1 => stone1,
169
+ 23 => Rubykon::Board::EMPTY,
170
+ 73 => Rubykon::Board::EMPTY
171
+ }
172
+ enemy_group_1 = Rubykon::Group.new(enemy_stones[0], [enemy_stones[0]], liberties, 2)
173
+ enemy_group_2 = Rubykon::Group.new(enemy_stones[1], [enemy_stones[1]], liberties.dup, 2)
174
+ group_tracker.stone_to_group[enemy_stones[0]] = 3
175
+ group_tracker.groups[3] = enemy_group_1
176
+
177
+ group_tracker.stone_to_group[enemy_stones[1]] = 4
178
+ group_tracker.groups[4] = enemy_group_2
179
+
180
+
181
+ # Does not trigger enemy_group.caught? and removing the group.
182
+ # That doesn't happen THAT often and it'd require to setup an according
183
+ # board (with each test run)
184
+ benchmark.report 'remove liberties of enemies' do
185
+
186
+ group_tracker.dup.take_liberties_of_enemies(enemy_stones, stone1, game.board, :black)
187
+ end
188
+ end