gorb 0.0.1

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.
data/LICENSE ADDED
File without changes
data/README ADDED
@@ -0,0 +1,6 @@
1
+ gorb is a go (board game) library written in pure Ruby.
2
+
3
+ To use it, do the following:
4
+
5
+ gem build gorb.gemspec
6
+ gem install gorb-0.0.1.gem
@@ -0,0 +1,11 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test]
5
+
6
+ desc "Run tests"
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.pattern = 'test/test_*.rb'
9
+ t.verbose = false
10
+ t.warning = true
11
+ end
@@ -0,0 +1,4 @@
1
+ require 'gorb/board'
2
+ require 'gorb/group'
3
+ require 'gorb/player'
4
+ require 'gorb/stone'
@@ -0,0 +1,147 @@
1
+ require 'gorb/stone'
2
+
3
+ class Board
4
+
5
+ attr_accessor :groups, :turn
6
+ attr_reader :black, :white, :handicap, :komi, :size
7
+
8
+ # Initialize a new Board instance. Requires two Player objects and a
9
+ # handicap as arguments. The handicap should be an integer from 0 to 9.
10
+ def initialize(black=nil, white=nil, handicap=0, komi=6.5, size="19x19")
11
+ @black = black ||= Player.new("Black")
12
+ @white = white ||= Player.new("White")
13
+ @komi = komi
14
+ @size = size
15
+ @groups = []
16
+ @hashes = []
17
+ @turn = black
18
+
19
+ raise ArgumentError, "Incorrect handicap" if handicap < 0 or handicap > 9
20
+ @handicap = handicap
21
+ @komi = 0.5 if handicap > 0
22
+ @turn = white if handicap > 1
23
+
24
+ if size == "9x9"
25
+ handicap_stones = %w{G7 C3 G3 C7 E5 C5 G5 E7 E3}
26
+ elsif size == "13x13"
27
+ handicap_stones = %w{K10 D4 K4 D10 G7 D7 K7 G10 G4}
28
+ elsif size == "19x19"
29
+ handicap_stones = %w{Q16 D4 Q4 D16 K10 D10 Q10 K16 K4}
30
+ else
31
+ raise ArgumentError, "Incorrect board size"
32
+ end
33
+
34
+ case @handicap
35
+ when 2..5, 7, 9
36
+ handicap_stones[0..(@handicap-1)].each {|s| self.add_stone(s, :black)}
37
+ when 6, 8
38
+ handicap_stones[0..@handicap].each {|s| self.add_stone(s, :black)}
39
+ self.remove_stone(handicap_stones[4]) # Middle stone
40
+ end
41
+ end
42
+
43
+ # Add a Stone to the board if the move is legal. This function will also
44
+ # keep track of the turn. You can force the color with additional color
45
+ # argument -- in this case the turn is not changing.
46
+ def add_stone(point, color=nil)
47
+ # Guess the color based on turn, unless color was forced.
48
+ unless color
49
+ if @turn == black
50
+ color = :black
51
+ else
52
+ color = :white
53
+ end
54
+ advance = true
55
+ end
56
+
57
+ # Check the legality of the move and play it if legal.
58
+ raise ArgumentError, "Illegal move" unless legal?(point, color)
59
+ stone = Stone.new(self, point, color)
60
+ resolve!(stone)
61
+
62
+ # If the color was not explicitly set, advance the turn.
63
+ turn_over if advance
64
+ return stone
65
+ end
66
+
67
+ def remove_stone(point)
68
+ stone = self.search(point).first
69
+ raise ArgumentError, "No such stone" unless stone
70
+ stone.group.delete(stone)
71
+ stone.board.groups.delete(stone.group) if stone.group.size == 0
72
+ end
73
+
74
+ # Search the Board for stones in given points.
75
+ def search(points)
76
+ stones = []
77
+ @groups.each do |group|
78
+ group.each do |stone|
79
+ stones << stone if points.include? stone.point
80
+ end
81
+ end
82
+ return stones
83
+ end
84
+
85
+ def stone_at?(point)
86
+ @groups.any? {|group| group.include? point}
87
+ end
88
+
89
+ def stones_at?(points)
90
+ points.all? {|point| self.stone_at? point}
91
+ end
92
+
93
+ # Recalculate all liberties. Removes dead groups from the table.
94
+ def resolve!(added_stone)
95
+ @groups.each do |group|
96
+ group.liberties! if not group.include? added_stone
97
+ end
98
+ # The group of last added stone is checked after others to make kills by
99
+ # 'suicide' (filling dame) work.
100
+ added_stone.group.liberties!
101
+ end
102
+
103
+ # Generate a hash of a board situation. Used to enforce ko rule.
104
+ def generate_hash
105
+ @groups.flatten.inject([]) {|hash, stone| hash << stone.to_s}.sort.hash
106
+ end
107
+
108
+ def legal?(point, color)
109
+ # Check if the point if already occupied.
110
+ return false if self.stone_at? point
111
+
112
+ # The method for checking legality requires placing a test stone to the
113
+ # point and seeing what happens. This is done by doing a deep copy of the
114
+ # board and playing the move there.
115
+ dummy_board = Marshal.load(Marshal.dump(self))
116
+
117
+ # Check for suicide.
118
+ stone = Stone.new(dummy_board, point, color)
119
+ legal = true
120
+ if stone.group.liberties == 0
121
+ # Normally suicide is not ok...
122
+ legal = false
123
+ # ...but killing with 'suicide' (filling dame) is ok.
124
+ opposing = dummy_board.search(stone.neighbors)
125
+ opposing.each do |opp_stone|
126
+ if opp_stone.color != color and opp_stone.group.liberties == 0
127
+ legal = true
128
+ end
129
+ end
130
+ end
131
+
132
+ # Check for ko.
133
+ dummy_board.resolve!(stone)
134
+ legal = false if @hashes.include? dummy_board.generate_hash
135
+ return legal
136
+ end
137
+
138
+ def turn_over
139
+ if @turn == @black
140
+ @turn = @white
141
+ else
142
+ @turn = @black
143
+ end
144
+ @hashes << generate_hash
145
+ end
146
+
147
+ end
@@ -0,0 +1,42 @@
1
+ class Group < Array
2
+
3
+ def initialize(board, stone)
4
+ @board = board
5
+ @board.groups << self
6
+ self << stone
7
+ end
8
+
9
+ def merge(groups)
10
+ groups.each do |group|
11
+ group.each {|stone| self << stone}
12
+ @board.groups.delete group
13
+ end
14
+ end
15
+
16
+ def include?(point)
17
+ self.any? {|stone| stone.point == point}
18
+ end
19
+
20
+ # Check the liberties of the Group.
21
+ def liberties
22
+ libs = []
23
+ self.each do |stone|
24
+ stone.liberties.each do |liberty|
25
+ libs << liberty
26
+ end
27
+ end
28
+ libs.uniq!
29
+ return libs.size
30
+ end
31
+
32
+ # Destructive version of the former. Note that this will remove the Group
33
+ # from the board if it has no liberties, so all the existing groups should
34
+ # have their liberties checked before checking the liberties of a new group
35
+ # in order to allow kills by filling dame.
36
+ def liberties!
37
+ libs = self.liberties
38
+ @board.groups.delete(self) if libs == 0
39
+ return libs
40
+ end
41
+
42
+ end
@@ -0,0 +1,12 @@
1
+ class Player
2
+
3
+ def initialize(name="Anonymous", rank=nil)
4
+ @name = name
5
+ @rank = rank
6
+ end
7
+
8
+ def to_str
9
+ @name
10
+ end
11
+
12
+ end
@@ -0,0 +1,80 @@
1
+ require 'gorb/group'
2
+
3
+ class Stone
4
+
5
+ attr_accessor :group
6
+ attr_reader :board, :point, :color
7
+
8
+ def initialize(board, point, color)
9
+ @board = board
10
+ @point = point
11
+ @color = color
12
+
13
+ if @board.size == "9x9"
14
+ @letters = %w{A B C D E F G H J}
15
+ elsif @board.size == "13x13"
16
+ @letters = %w{A B C D E F G H J K L M N}
17
+ elsif @board.size == "19x19"
18
+ @letters = %w{A B C D E F G H J K L M N O P Q R S T}
19
+ end
20
+
21
+ if (@point[1, 2].to_i > @board.size.split('x')[0].to_i or
22
+ not @letters.index(@point[0]))
23
+ raise ArgumentError, "Invalid point"
24
+ end
25
+
26
+ @group = self.find_group
27
+ end
28
+
29
+ # Return the neighboring points of the Stone.
30
+ def neighbors
31
+ x, y = @letters.index(@point[0]), @point[1, 2].to_i
32
+ neighbors = []
33
+ unless y == 1
34
+ neighbors << @letters[x] + (y - 1).to_s
35
+ end
36
+ unless y == @board.size.split('x')[0].to_i
37
+ neighbors << @letters[x] + (y + 1).to_s
38
+ end
39
+ unless @letters[x] == @letters.first
40
+ neighbors << @letters[x-1] + y.to_s
41
+ end
42
+ unless @letters[x] == @letters.last
43
+ neighbors << @letters[x+1] + y.to_s
44
+ end
45
+ return neighbors
46
+ end
47
+
48
+ # Return the liberties of the Stone.
49
+ def liberties
50
+ liberties = neighbors
51
+ stones = @board.search(neighbors)
52
+ stones.each {|stone| liberties.delete(stone.point)}
53
+ return liberties
54
+ end
55
+
56
+ # Find the Group of the Stone or create a new one. If this Stone connects
57
+ # one or more groups, merge them together to a single Group.
58
+ def find_group
59
+ groups = []
60
+ stones = @board.search(neighbors)
61
+ stones.each do |stone|
62
+ if stone.color == @color and not groups.include? stone.group
63
+ groups << stone.group
64
+ end
65
+ end
66
+ if groups.empty?
67
+ return Group.new(@board, self)
68
+ else
69
+ group = groups.pop
70
+ group.merge(groups)
71
+ group << self
72
+ return group
73
+ end
74
+ end
75
+
76
+ def to_s
77
+ @point
78
+ end
79
+
80
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gorb
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Aku Kotkavuo
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-06 00:00:00 +03:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: gorb is a go (board game) library written in pure Ruby.
22
+ email: aku@hibana.net
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - Rakefile
31
+ - lib/gorb/board.rb
32
+ - lib/gorb/group.rb
33
+ - lib/gorb/player.rb
34
+ - lib/gorb/stone.rb
35
+ - lib/gorb.rb
36
+ - README
37
+ - LICENSE
38
+ has_rdoc: true
39
+ homepage: http://github.com/arkx/gorb
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.7
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Ruby go library
70
+ test_files: []
71
+