gorb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +0 -0
- data/README +6 -0
- data/Rakefile +11 -0
- data/lib/gorb.rb +4 -0
- data/lib/gorb/board.rb +147 -0
- data/lib/gorb/group.rb +42 -0
- data/lib/gorb/player.rb +12 -0
- data/lib/gorb/stone.rb +80 -0
- metadata +71 -0
data/LICENSE
ADDED
File without changes
|
data/README
ADDED
data/Rakefile
ADDED
data/lib/gorb.rb
ADDED
data/lib/gorb/board.rb
ADDED
@@ -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
|
data/lib/gorb/group.rb
ADDED
@@ -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
|
data/lib/gorb/player.rb
ADDED
data/lib/gorb/stone.rb
ADDED
@@ -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
|
+
|