reversi 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +12 -9
- data/lib/reversi/board.rb +13 -12
- data/lib/reversi/configuration.rb +24 -8
- data/lib/reversi/player/base_player.rb +1 -4
- data/lib/reversi/player/negamax_ai.rb +1 -1
- data/lib/reversi/version.rb +1 -1
- data/spec/base_player_spec.rb +0 -6
- data/spec/board_spec.rb +31 -0
- data/spec/game_spec.rb +29 -0
- data/spec/player_spec.rb +70 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 651aae750b3b767bc9375470adff0bb5414f0663
|
|
4
|
+
data.tar.gz: b90881770c721b08e945d7f20b3433bf97437115
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85398b003393d63e49e37bbdf9e59958d2e3cdf49d9d787b4bd95dfd1261d2498b7b1f217a21909ddd283ab59f12893d5f909294dda5887bf5d5ba375ec5067e
|
|
7
|
+
data.tar.gz: 81821d25c301e6e18a0bda8088a730558ec96a00e0317eb104e03fbd83f2de2b8b4601d7bd5a0629c254d52c583b493cc21b8b57bca6f36c916b8a5a8056952c
|
data/README.md
CHANGED
|
@@ -47,18 +47,21 @@ Use `Reversi.configure` to configure setting for a reversi game.
|
|
|
47
47
|
|
|
48
48
|
`name` description... (default value)
|
|
49
49
|
|
|
50
|
-
* `player_b` A player having the first move uses this class object. (Reversi::Player::RandomAI)
|
|
51
|
-
* `player_w` A player having the passive move uses this class object. (Reversi::Player::RandomAI)
|
|
52
|
-
* `disk_b` A string of the black disks. ('b')
|
|
53
|
-
* `disk_w` A string of the black disks. ('w')
|
|
54
|
-
* `disk_color_b` A color of the black disks. (0)
|
|
55
|
-
* `disk_color_w` A color of the black disks. (0)
|
|
56
|
-
* `
|
|
57
|
-
* `
|
|
50
|
+
* `player_b` A player having the first move uses this class object. ( Reversi::Player::RandomAI )
|
|
51
|
+
* `player_w` A player having the passive move uses this class object. ( Reversi::Player::RandomAI )
|
|
52
|
+
* `disk_b` A string of the black disks. ( 'b' )
|
|
53
|
+
* `disk_w` A string of the black disks. ( 'w' )
|
|
54
|
+
* `disk_color_b` A color of the black disks. ( 0 )
|
|
55
|
+
* `disk_color_w` A color of the black disks. ( 0 )
|
|
56
|
+
* `initial_position` The initial positions of each disk on the board. ( {:black => [[:d, 5], [:e, 4]], :white => [[:d, 4], [:e, 5]]} )
|
|
57
|
+
* `progress` Whether or not the progress of the game is displayed. ( false )
|
|
58
|
+
* `stack_limit` The upper limit number of times of use `Reversi::Board#undo!` . ( 3 )
|
|
58
59
|
|
|
59
60
|
A string and a color of the disks are reflected on `game.board.to_s` .
|
|
60
61
|
You can choose from 9 colors, black, red, green, yellow, blue, magenda, cyan, white and gray.
|
|
61
62
|
|
|
63
|
+
Using `Reversi.reset` method, you can reset all options to the default values.
|
|
64
|
+
|
|
62
65
|
### Human vs Computer
|
|
63
66
|
|
|
64
67
|
Set `Reversi::Player::Human` to player_b or player_w, and run. Please input your move (for example: d3). This program is terminated when this game is over or when you input `q` or `exit`.
|
|
@@ -72,7 +75,7 @@ game = Reversi::Game.new
|
|
|
72
75
|
game.start
|
|
73
76
|
```
|
|
74
77
|
|
|
75
|
-
###
|
|
78
|
+
### Your Original Player
|
|
76
79
|
|
|
77
80
|
You can make your original player class by inheriting `Reversi::Player::BasePlayer` and defining `move` method.
|
|
78
81
|
|
data/lib/reversi/board.rb
CHANGED
|
@@ -32,13 +32,14 @@ module Reversi
|
|
|
32
32
|
@options = options
|
|
33
33
|
@stack = []
|
|
34
34
|
[:disk_color_b, :disk_color_w].each do |color|
|
|
35
|
-
if options[color].is_a?(Symbol) || options[color].is_a?(String)
|
|
36
|
-
options[color] = DISK_COLOR[options[color].to_sym].to_i
|
|
35
|
+
if @options[color].is_a?(Symbol) || @options[color].is_a?(String)
|
|
36
|
+
@options[color] = DISK_COLOR[@options[color].to_sym].to_i
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
@columns = (0..9).map{ (0..9).map{ |_| DISK[:none] } }
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
@options[:initial_position].each do |color, positions|
|
|
41
|
+
positions.each{ |position| put_disk(*position, color) }
|
|
42
|
+
end
|
|
42
43
|
@columns.each do |col|
|
|
43
44
|
col[0] = 2; col[-1] = 2
|
|
44
45
|
end.tap do |cols|
|
|
@@ -50,7 +51,7 @@ module Reversi
|
|
|
50
51
|
#
|
|
51
52
|
# @return [String]
|
|
52
53
|
def to_s
|
|
53
|
-
" #{
|
|
54
|
+
" #{[*'a'..'h'].join(" ")}\n" <<
|
|
54
55
|
" #{"+---"*8}+\n" <<
|
|
55
56
|
@columns[1][1..-2].zip(*@columns[2..8].map{ |col| col[1..-2] })
|
|
56
57
|
.map{ |row| row.map do |e|
|
|
@@ -61,7 +62,7 @@ module Reversi
|
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
.map{ |e| "| #{e} |" }.join }.map{ |e| e.gsub(/\|\|/, "|") }
|
|
64
|
-
.tap{ |rows| break
|
|
65
|
+
.tap{ |rows| break [*0..7].map{ |i| " #{i+1} " << rows[i] } }
|
|
65
66
|
.join("\n #{"+---"*8}+\n") <<
|
|
66
67
|
"\n #{"+---"*8}+\n"
|
|
67
68
|
end
|
|
@@ -69,6 +70,7 @@ module Reversi
|
|
|
69
70
|
# Pushes an array of the game board onto a stack.
|
|
70
71
|
# The stack size limit is 3(default).
|
|
71
72
|
def push_stack
|
|
73
|
+
x = [*:a..:h].index(x) + 1 if x.is_a? Symbol
|
|
72
74
|
@stack.push(Marshal.load(Marshal.dump(@columns)))
|
|
73
75
|
@stack.shift if @stack.size > @options[:stack_limit]
|
|
74
76
|
end
|
|
@@ -96,7 +98,7 @@ module Reversi
|
|
|
96
98
|
# @param y [Integer] the row number
|
|
97
99
|
# @return [Integer] the openness
|
|
98
100
|
def openness(x, y)
|
|
99
|
-
x =
|
|
101
|
+
x = [*:a..:h].index(x) + 1 if x.is_a? Symbol
|
|
100
102
|
([-1,0,1].product([-1,0,1]) - [[0, 0]]).inject(0) do |sum, (dx, dy)|
|
|
101
103
|
sum + (@columns[x + dx][y + dy] == 0 ? 1 : 0)
|
|
102
104
|
end
|
|
@@ -108,7 +110,7 @@ module Reversi
|
|
|
108
110
|
# @param y [Integer] the row number
|
|
109
111
|
# @return [Symbol] the color or `:none`
|
|
110
112
|
def at(x, y)
|
|
111
|
-
x =
|
|
113
|
+
x = [*:a..:h].index(x) + 1 if x.is_a? Symbol
|
|
112
114
|
DISK.key(@columns[x][y])
|
|
113
115
|
end
|
|
114
116
|
|
|
@@ -139,7 +141,7 @@ module Reversi
|
|
|
139
141
|
# @param y [Integer] the row number
|
|
140
142
|
# @param color [Symbol]
|
|
141
143
|
def put_disk(x, y, color)
|
|
142
|
-
x =
|
|
144
|
+
x = [*:a..:h].index(x) + 1 if x.is_a? Symbol
|
|
143
145
|
@columns[x][y] = DISK[color.to_sym]
|
|
144
146
|
end
|
|
145
147
|
|
|
@@ -150,10 +152,9 @@ module Reversi
|
|
|
150
152
|
# @param y [Integer] the row number
|
|
151
153
|
# @param color [Symbol]
|
|
152
154
|
def flip_disks(x, y, color)
|
|
153
|
-
x =
|
|
155
|
+
x = [*:a..:h].index(x) + 1 if x.is_a? Symbol
|
|
154
156
|
[-1,0,1].product([-1,0,1]).each do |dx, dy|
|
|
155
157
|
next if dx == 0 && dy == 0
|
|
156
|
-
# 隣接石が異色であったらflippable?でひっくり返せるか(挟まれているか)確認
|
|
157
158
|
if @columns[x + dx][y + dy] == DISK[color]*(-1)
|
|
158
159
|
flip_disk(x, y, dx, dy, color) if flippable?(x, y, dx, dy, color)
|
|
159
160
|
end
|
|
@@ -184,7 +185,7 @@ module Reversi
|
|
|
184
185
|
# @param color [Symbol]
|
|
185
186
|
# @return [Boolean]
|
|
186
187
|
def puttable?(x, y, color)
|
|
187
|
-
x =
|
|
188
|
+
x = [*:a..:h].index(x) + 1 if x.is_a? Symbol
|
|
188
189
|
return false if @columns[x][y] != 0
|
|
189
190
|
[-1,0,1].product([-1,0,1]).each do |dx, dy|
|
|
190
191
|
next if dx == 0 && dy == 0
|
|
@@ -8,10 +8,23 @@ module Reversi
|
|
|
8
8
|
:disk_w,
|
|
9
9
|
:disk_color_b,
|
|
10
10
|
:disk_color_w,
|
|
11
|
+
:initial_position,
|
|
11
12
|
:progress,
|
|
12
13
|
:stack_limit
|
|
13
14
|
].freeze
|
|
14
15
|
|
|
16
|
+
DEFAULTS = {
|
|
17
|
+
:player_b => Reversi::Player::RandomAI,
|
|
18
|
+
:player_w => Reversi::Player::RandomAI,
|
|
19
|
+
:disk_b => 'b',
|
|
20
|
+
:disk_w => 'w',
|
|
21
|
+
:disk_color_b => 0,
|
|
22
|
+
:disk_color_w => 0,
|
|
23
|
+
:initial_position => {:black => [[:d, 5], [:e, 4]], :white => [[:d, 4], [:e, 5]]},
|
|
24
|
+
:progress => false,
|
|
25
|
+
:stack_limit => 3
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
attr_accessor *OPTIONS_KEYS
|
|
16
29
|
|
|
17
30
|
def configure
|
|
@@ -22,15 +35,18 @@ module Reversi
|
|
|
22
35
|
Hash[*OPTIONS_KEYS.map{|key| [key, send(key)]}.flatten]
|
|
23
36
|
end
|
|
24
37
|
|
|
38
|
+
def reset
|
|
39
|
+
DEFAULTS.each do |option, default|
|
|
40
|
+
self.send("#{option}=".to_sym, default)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
25
44
|
def set_defaults
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
self.disk_color_w ||= 0
|
|
32
|
-
self.progress ||= false
|
|
33
|
-
self.stack_limit ||= 3
|
|
45
|
+
DEFAULTS.each do |option, default|
|
|
46
|
+
default.class == String ?
|
|
47
|
+
eval("self.#{option} ||= \'#{default}\'") :
|
|
48
|
+
eval("self.#{option} ||= #{default}")
|
|
49
|
+
end
|
|
34
50
|
end
|
|
35
51
|
end
|
|
36
52
|
end
|
|
@@ -23,10 +23,7 @@ module Reversi::Player
|
|
|
23
23
|
@board.push_stack
|
|
24
24
|
color = my_color ? @my_color : @opponent_color
|
|
25
25
|
x = (:a..:h).to_a.index(x) + 1 if x.is_a? Symbol
|
|
26
|
-
|
|
27
|
-
if diff.empty?
|
|
28
|
-
raise Reversi::MoveError, "A player must flip at least one or more opponent's disks."
|
|
29
|
-
end
|
|
26
|
+
flip_disks(x, y, color)
|
|
30
27
|
@board.put_disk(x, y, color)
|
|
31
28
|
end
|
|
32
29
|
|
data/lib/reversi/version.rb
CHANGED
data/spec/base_player_spec.rb
CHANGED
|
@@ -12,12 +12,6 @@ describe Reversi::Player::BasePlayer do
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
context "when a player make an invalid move" do
|
|
16
|
-
it "Reversi::MoveError raised" do
|
|
17
|
-
expect{ player.put_disk(:a, 1) }.to raise_error Reversi::MoveError
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
15
|
context "when the third argument is `false`" do
|
|
22
16
|
it "makes a opponent's move" do
|
|
23
17
|
expect{ player.put_disk(:e, 3, false) }.to change{ board.status[:white].size }.by(2)
|
data/spec/board_spec.rb
CHANGED
|
@@ -8,6 +8,8 @@ describe Reversi::Board do
|
|
|
8
8
|
:disk_w => "w",
|
|
9
9
|
:disk_color_b => disk_color_b,
|
|
10
10
|
:disk_color_w => disk_color_w,
|
|
11
|
+
:initial_position => {:black => [[:d, 5], [:e, 4]],
|
|
12
|
+
:white => [[:d, 4], [:e, 5]]},
|
|
11
13
|
:progress => false,
|
|
12
14
|
:stack_limit => 3}
|
|
13
15
|
end
|
|
@@ -33,6 +35,35 @@ describe Reversi::Board do
|
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
describe "#to_s" do
|
|
39
|
+
let(:disk_color_b) { :black }
|
|
40
|
+
let(:disk_color_w) { 'white' }
|
|
41
|
+
|
|
42
|
+
it do
|
|
43
|
+
str = <<-END.gsub(/ {6}/,"")
|
|
44
|
+
a b c d e f g h
|
|
45
|
+
+---+---+---+---+---+---+---+---+
|
|
46
|
+
1 | | | | | | | | |
|
|
47
|
+
+---+---+---+---+---+---+---+---+
|
|
48
|
+
2 | | | | | | | | |
|
|
49
|
+
+---+---+---+---+---+---+---+---+
|
|
50
|
+
3 | | | | | | | | |
|
|
51
|
+
+---+---+---+---+---+---+---+---+
|
|
52
|
+
4 | | | | \e[37mw\e[0m | \e[30mb\e[0m | | | |
|
|
53
|
+
+---+---+---+---+---+---+---+---+
|
|
54
|
+
5 | | | | \e[30mb\e[0m | \e[37mw\e[0m | | | |
|
|
55
|
+
+---+---+---+---+---+---+---+---+
|
|
56
|
+
6 | | | | | | | | |
|
|
57
|
+
+---+---+---+---+---+---+---+---+
|
|
58
|
+
7 | | | | | | | | |
|
|
59
|
+
+---+---+---+---+---+---+---+---+
|
|
60
|
+
8 | | | | | | | | |
|
|
61
|
+
+---+---+---+---+---+---+---+---+
|
|
62
|
+
END
|
|
63
|
+
expect(board.to_s).to eq str
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
36
67
|
describe "#push_stack" do
|
|
37
68
|
let(:disk_color_b) { 0 }
|
|
38
69
|
let(:disk_color_w) { 0 }
|
data/spec/game_spec.rb
CHANGED
|
@@ -34,6 +34,35 @@ describe Reversi::Game do
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
describe "initial position" do
|
|
38
|
+
it "default" do
|
|
39
|
+
game = Reversi::Game.new
|
|
40
|
+
expect(game.board.at(:e, 4)).to eq :black
|
|
41
|
+
expect(game.board.at(:d, 4)).to eq :white
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "original position" do
|
|
45
|
+
initial = {:black => [[:a, 1]], :white => [[:h, 8]]}
|
|
46
|
+
options = {:initial_position => initial}
|
|
47
|
+
game = Reversi::Game.new(options)
|
|
48
|
+
expect(game.board.at(:d, 4)).to eq :none
|
|
49
|
+
expect(game.board.at(:a, 1)).to eq :black
|
|
50
|
+
expect(game.board.at(:h, 8)).to eq :white
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "Reversi.reset" do
|
|
55
|
+
it "reset all options to the default values" do
|
|
56
|
+
Reversi.configure{ |config| config.progress = true }
|
|
57
|
+
game = Reversi::Game.new
|
|
58
|
+
expect(game.options[:progress]).to eq true
|
|
59
|
+
|
|
60
|
+
Reversi.reset
|
|
61
|
+
game = Reversi::Game.new
|
|
62
|
+
expect(game.options[:progress]).to eq false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
37
66
|
describe "from a record of a reversi game" do
|
|
38
67
|
game = Reversi::Game.new
|
|
39
68
|
game.player_b.put_disk(:c, 4); game.player_w.put_disk(:e, 3)
|
data/spec/player_spec.rb
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
class ValidPlayer < Reversi::Player::BasePlayer
|
|
4
|
+
def move(board)
|
|
5
|
+
moves = next_moves.map{ |v| v[:move] }
|
|
6
|
+
put_disk(*moves.sample) unless moves.empty?
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class InvalidPlayer1 < Reversi::Player::BasePlayer
|
|
11
|
+
def move(board)
|
|
12
|
+
put_disk(:a, 1)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class InvalidPlayer2 < Reversi::Player::BasePlayer
|
|
17
|
+
def move(board)
|
|
18
|
+
put_disk(:d, 4)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class InvalidPlayer3 < Reversi::Player::BasePlayer
|
|
23
|
+
def move(board)
|
|
24
|
+
put_disk(:a, 1)
|
|
25
|
+
put_disk(:a, 2)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe Reversi::Player do
|
|
30
|
+
let(:game) { Reversi::Game.new }
|
|
31
|
+
|
|
32
|
+
describe ValidPlayer do
|
|
33
|
+
it "exit successfully" do
|
|
34
|
+
Reversi.configure do |config|
|
|
35
|
+
config.player_b = ValidPlayer
|
|
36
|
+
end
|
|
37
|
+
expect(game.start).to eq nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe InvalidPlayer1 do
|
|
42
|
+
it "should raise Reversi::MoveError" do
|
|
43
|
+
Reversi.configure do |config|
|
|
44
|
+
config.player_b = InvalidPlayer1
|
|
45
|
+
end
|
|
46
|
+
message = "A player must flip at least one or more opponent's disks."
|
|
47
|
+
expect{ game.start }.to raise_error(Reversi::MoveError, message)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe InvalidPlayer2 do
|
|
52
|
+
it "should raise Reversi::MoveError" do
|
|
53
|
+
Reversi.configure do |config|
|
|
54
|
+
config.player_b = InvalidPlayer2
|
|
55
|
+
end
|
|
56
|
+
message = "When a player can't make a valid move, you must not place a new disk."
|
|
57
|
+
expect{ game.start }.to raise_error(Reversi::MoveError, message)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe InvalidPlayer3 do
|
|
62
|
+
it "should raise Reversi::MoveError" do
|
|
63
|
+
Reversi.configure do |config|
|
|
64
|
+
config.player_b = InvalidPlayer3
|
|
65
|
+
end
|
|
66
|
+
message = "A player must place a new disk on the board."
|
|
67
|
+
expect{ game.start }.to raise_error(Reversi::MoveError, message)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reversi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- seinosuke
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-03-
|
|
11
|
+
date: 2015-03-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -54,6 +54,7 @@ files:
|
|
|
54
54
|
- spec/base_player_spec.rb
|
|
55
55
|
- spec/board_spec.rb
|
|
56
56
|
- spec/game_spec.rb
|
|
57
|
+
- spec/player_spec.rb
|
|
57
58
|
- spec/spec_helper.rb
|
|
58
59
|
homepage: https://github.com/seinosuke/reversi
|
|
59
60
|
licenses:
|
|
@@ -83,5 +84,6 @@ test_files:
|
|
|
83
84
|
- spec/base_player_spec.rb
|
|
84
85
|
- spec/board_spec.rb
|
|
85
86
|
- spec/game_spec.rb
|
|
87
|
+
- spec/player_spec.rb
|
|
86
88
|
- spec/spec_helper.rb
|
|
87
89
|
has_rdoc:
|