reversi 1.0.1 → 1.0.2
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 +80 -6
- data/ext/reversi/reversi.c +98 -1
- data/ext/reversi/reversi.h +1 -15
- data/lib/reversi/board.rb +1 -1
- data/lib/reversi/configuration.rb +1 -1
- data/lib/reversi/player.rb +1 -0
- data/lib/reversi/player/alpha_beta_ai.rb +70 -0
- data/lib/reversi/player/min_max_ai.rb +7 -8
- data/lib/reversi/player/nega_max_ai.rb +6 -8
- data/lib/reversi/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f21066f809daeaccbfdfbc19f61dc5ac79b0dbca
|
|
4
|
+
data.tar.gz: dca98c6b7c7f64ae612e35dd3a218a9993b35434
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b6798e66984df642eb6fec17b0bf88a6d37d21c10b909558d6ca069a0fab63e347a76fc01106cf7993bf23f468c62da060a296341792b1058bedaa682d4eaee
|
|
7
|
+
data.tar.gz: ae1a412572613d73f47b75aed52c63211f50b90d2a3a0f22d7a8d86a7b9dcf586553d25bf2792b708eb4215c864513c102febc43519eae4ce241c194fdd529e1
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reversi
|
|
2
2
|
|
|
3
|
-
[](http://badge.fury.io/rb/reversi) [](https://travis-ci.org/seinosuke/reversi)
|
|
3
|
+
[](http://badge.fury.io/rb/reversi) [](https://travis-ci.org/seinosuke/reversi) [](http://inch-ci.org/github/seinosuke/reversi)
|
|
4
4
|
A Ruby Gem to play reversi game. You can enjoy a game on the command line or easily make your original reversi game programs.
|
|
5
5
|
|
|
6
6
|

|
|
@@ -53,19 +53,22 @@ Use `Reversi.configure` to configure setting for a reversi game.
|
|
|
53
53
|
* `disk_w` A string of the black disks. ( 'w' )
|
|
54
54
|
* `disk_color_b` A color of the black disks. ( 0 )
|
|
55
55
|
* `disk_color_w` A color of the black disks. ( 0 )
|
|
56
|
-
* `initial_position` The initial positions of each disk on the board.
|
|
56
|
+
* `initial_position` The initial positions of each disk on the board.
|
|
57
57
|
* `progress` Whether or not the progress of the game is displayed. ( false )
|
|
58
58
|
* `stack_limit` The upper limit number of times of use repeatedly `Reversi::Board#undo!` . ( 3 )
|
|
59
59
|
|
|
60
60
|
A string and a color of the disks are reflected on `game.board.to_s` .
|
|
61
61
|
You can choose from 9 colors, black, red, green, yellow, blue, magenda, cyan, white and gray.
|
|
62
62
|
|
|
63
|
+
The default of `initial_position` is `{:black => [[4, 5], [5, 4]], :white => [[4, 4], [5, 5]]}` .
|
|
64
|
+
|
|
63
65
|
Using `Reversi.reset` method, you can reset all options to the default values.
|
|
64
66
|
|
|
65
67
|
### Human vs Computer
|
|
66
68
|
|
|
67
69
|
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`.
|
|
68
70
|
|
|
71
|
+
|
|
69
72
|
```ruby
|
|
70
73
|
Reversi.configure do |config|
|
|
71
74
|
config.player_b = Reversi::Player::Human
|
|
@@ -75,13 +78,14 @@ game = Reversi::Game.new
|
|
|
75
78
|
game.start
|
|
76
79
|
```
|
|
77
80
|
|
|
81
|
+
|
|
78
82
|
### Your Original Player
|
|
79
83
|
|
|
80
84
|
You can make your original player class by inheriting `Reversi::Player::BasePlayer` and defining `move` method.
|
|
81
85
|
|
|
82
|
-
`next_moves` method returns an array of the next moves information. A player places a supplied color's disk on specified position, and flips the opponent's disks by using `put_disk` method. You can get the current game board state from a `board` variable.
|
|
86
|
+
`next_moves` method returns an array of the next moves information. A player places a supplied color's disk on specified position, and flips the opponent's disks by using `put_disk` method. You can get the current game board state from a `board` variable or `status` method.
|
|
83
87
|
|
|
84
|
-
* Example of Random
|
|
88
|
+
* **Example of Random Player**
|
|
85
89
|
|
|
86
90
|
```ruby
|
|
87
91
|
class MyAI < Reversi::Player::BasePlayer
|
|
@@ -99,8 +103,78 @@ game = Reversi::Game.new
|
|
|
99
103
|
game.start
|
|
100
104
|
```
|
|
101
105
|
|
|
102
|
-
*
|
|
103
|
-
|
|
106
|
+
* **Example of MinMax algorithm**
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
class MyAI < Reversi::Player::BasePlayer
|
|
110
|
+
|
|
111
|
+
def initialize(_color, _board)
|
|
112
|
+
super
|
|
113
|
+
|
|
114
|
+
# The evaluation value at each position.
|
|
115
|
+
points = [
|
|
116
|
+
100, -10, 0, -1, -1, 0, -10, 100,
|
|
117
|
+
-10, -30, -5, -5, -5, -5, -30, -10,
|
|
118
|
+
0, -5, 0, -1, -1, 0, -5, 0,
|
|
119
|
+
-1, -5, -1, -1, -1, -1, -5, -1,
|
|
120
|
+
-1, -5, -1, -1, -1, -1, -5, -1,
|
|
121
|
+
0, -5, 0, -1, -1, 0, -5, 0,
|
|
122
|
+
-10, -30, -5, -5, -5, -5, -30, -10,
|
|
123
|
+
100, -10, 0, -1, -1, 0, -10, 100
|
|
124
|
+
]
|
|
125
|
+
@evaluation_value =
|
|
126
|
+
Hash[(1..8).map{ |x| (1..8).map{ |y| [[x, y], points.shift] } }.flatten(1) ]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def move(board)
|
|
130
|
+
moves = next_moves.map{ |v| v[:move] }
|
|
131
|
+
return if moves.empty?
|
|
132
|
+
next_move = moves.map do |move|
|
|
133
|
+
{ :move => move, :point => evaluate(move, board, 3, true) }
|
|
134
|
+
end.max_by{ |v| v[:point] }[:move]
|
|
135
|
+
put_disk(*next_move)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def evaluate(move, board, depth, color)
|
|
139
|
+
# Move to the child node.
|
|
140
|
+
put_disk(*move, color)
|
|
141
|
+
|
|
142
|
+
# Acquire the branches of this node.
|
|
143
|
+
moves = next_moves(!color).map{ |v| v[:move] }
|
|
144
|
+
|
|
145
|
+
# Evaluate a current game-state when a search reaches the leaf node.
|
|
146
|
+
if depth == 1
|
|
147
|
+
status[:mine].inject(0){ |sum, xy| sum + @evaluation_value[xy] }
|
|
148
|
+
elsif moves.empty?
|
|
149
|
+
-100
|
|
150
|
+
|
|
151
|
+
# Select one of the child nodes
|
|
152
|
+
# when the current node is not the leaf node.
|
|
153
|
+
# Return a minimum value if it is a my move, or
|
|
154
|
+
# return a maximum value if it is an opponent's move.
|
|
155
|
+
else
|
|
156
|
+
values = moves.map{ |move| evaluate(move, board, depth - 1, !color) }
|
|
157
|
+
case depth
|
|
158
|
+
when ->(n){ n.odd? } then values.min
|
|
159
|
+
when ->(n){ n.even? } then values.max end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Return to a parent node.
|
|
163
|
+
ensure
|
|
164
|
+
board.undo!
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
Reversi.configure do |config|
|
|
169
|
+
config.player_b = MyAI
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
game = Reversi::Game.new
|
|
173
|
+
game.start
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
* **Other examples of algorithm**
|
|
177
|
+
Please see [Reversi::Player::NegaMaxAI](https://github.com/seinosuke/reversi/blob/master/lib/reversi/player/nega_max_ai.rb) or [Reversi::Player::AlphaBetaAI](https://github.com/seinosuke/reversi/blob/master/lib/reversi/player/alpha_beta_ai.rb) . These are examples of the player that can read the next three moves and evaluate a game state based on the positions of the disks. If you want to read the next more than three moves, set `stack_limit` to the number.
|
|
104
178
|
|
|
105
179
|
## Contributing
|
|
106
180
|
|
data/ext/reversi/reversi.c
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
#include "reversi.h"
|
|
2
2
|
|
|
3
|
+
struct stack{
|
|
4
|
+
int stack_columns[10][10];
|
|
5
|
+
struct stack *next;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
struct stack *head = NULL;
|
|
9
|
+
|
|
10
|
+
struct board {
|
|
11
|
+
int columns[10][10];
|
|
12
|
+
};
|
|
13
|
+
|
|
3
14
|
void Init_reversi(void) {
|
|
4
15
|
VALUE reversi = rb_define_module("Reversi");
|
|
5
16
|
VALUE reversi_board = rb_define_class_under(reversi, "Board", rb_cObject);
|
|
6
17
|
|
|
7
18
|
rb_define_alloc_func(reversi_board, board_alloc);
|
|
8
19
|
|
|
9
|
-
|
|
20
|
+
/* The getter method for a Reversi::Board object. */
|
|
10
21
|
rb_define_method(reversi_board, "columns", board_columns_getter, 0);
|
|
11
22
|
rb_define_method(reversi_board, "stack", board_stack_getter, 0);
|
|
23
|
+
|
|
24
|
+
/* The instance method for a Reversi::Board object. */
|
|
12
25
|
rb_define_method(reversi_board, "undo!", board_undo, 0);
|
|
13
26
|
|
|
27
|
+
/* These private methods are used in the board.rb file. */
|
|
28
|
+
rb_define_private_method(reversi_board, "board_initialize", board_initialize, 0);
|
|
14
29
|
rb_define_private_method(reversi_board, "board_push_stack", board_push_stack, 1);
|
|
15
30
|
rb_define_private_method(reversi_board, "board_status", board_status, 0);
|
|
16
31
|
rb_define_private_method(reversi_board, "board_openness", board_openness, 2);
|
|
@@ -21,6 +36,11 @@ void Init_reversi(void) {
|
|
|
21
36
|
rb_define_private_method(reversi_board, "board_flip_disks", board_flip_disks, 3);
|
|
22
37
|
}
|
|
23
38
|
|
|
39
|
+
static VALUE board_alloc(VALUE class) {
|
|
40
|
+
struct board *ptr = ALLOC(struct board);
|
|
41
|
+
return Data_Wrap_Struct(class, 0, -1, ptr);
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
static VALUE board_initialize(VALUE self) {
|
|
25
45
|
int x, y;
|
|
26
46
|
struct board *ptr;
|
|
@@ -39,6 +59,11 @@ static VALUE board_initialize(VALUE self) {
|
|
|
39
59
|
return Qnil;
|
|
40
60
|
}
|
|
41
61
|
|
|
62
|
+
/*
|
|
63
|
+
* The getter method for the instance variable `columns`.
|
|
64
|
+
*
|
|
65
|
+
* @return [Array]
|
|
66
|
+
*/
|
|
42
67
|
static VALUE board_columns_getter(VALUE self) {
|
|
43
68
|
VALUE column = rb_ary_new();
|
|
44
69
|
VALUE columns = rb_ary_new();
|
|
@@ -54,6 +79,11 @@ static VALUE board_columns_getter(VALUE self) {
|
|
|
54
79
|
return columns;
|
|
55
80
|
}
|
|
56
81
|
|
|
82
|
+
/*
|
|
83
|
+
* The getter method for the instance variable `stack`.
|
|
84
|
+
*
|
|
85
|
+
* @return [Array]
|
|
86
|
+
*/
|
|
57
87
|
static VALUE board_stack_getter(VALUE self) {
|
|
58
88
|
VALUE stack = rb_ary_new();
|
|
59
89
|
VALUE column = rb_ary_new();
|
|
@@ -73,6 +103,10 @@ static VALUE board_stack_getter(VALUE self) {
|
|
|
73
103
|
return stack;
|
|
74
104
|
}
|
|
75
105
|
|
|
106
|
+
/*
|
|
107
|
+
* Pops an array of the game board off of the stack,
|
|
108
|
+
* and that is stored in the instance variable `columns`.
|
|
109
|
+
*/
|
|
76
110
|
static VALUE board_undo(VALUE self) {
|
|
77
111
|
struct board *ptr;
|
|
78
112
|
Data_Get_Struct(self, struct board, ptr);
|
|
@@ -80,11 +114,22 @@ static VALUE board_undo(VALUE self) {
|
|
|
80
114
|
return Qnil;
|
|
81
115
|
}
|
|
82
116
|
|
|
117
|
+
/*
|
|
118
|
+
* Pushes an array of the game board onto a stack.
|
|
119
|
+
*
|
|
120
|
+
* @param limit [Integer] An upper limit size of the stack.
|
|
121
|
+
*/
|
|
83
122
|
static VALUE board_push_stack(VALUE self, VALUE limit) {
|
|
84
123
|
push_stack(self, FIX2INT(limit));
|
|
85
124
|
return Qnil;
|
|
86
125
|
}
|
|
87
126
|
|
|
127
|
+
/*
|
|
128
|
+
* Returns an array containing the coordinates of each color.
|
|
129
|
+
* The arrays are stored `black`, `while`, and `none` in that order.
|
|
130
|
+
*
|
|
131
|
+
* @return [Array]
|
|
132
|
+
*/
|
|
88
133
|
static VALUE board_status(VALUE self) {
|
|
89
134
|
VALUE black = rb_ary_new();
|
|
90
135
|
VALUE white = rb_ary_new();
|
|
@@ -123,6 +168,13 @@ static VALUE board_status(VALUE self) {
|
|
|
123
168
|
return status;
|
|
124
169
|
}
|
|
125
170
|
|
|
171
|
+
/*
|
|
172
|
+
* Returns the openness of the coordinates.
|
|
173
|
+
*
|
|
174
|
+
* @param x [Integer] the column number
|
|
175
|
+
* @param y [Integer] the row number
|
|
176
|
+
* @return [Integer] the openness
|
|
177
|
+
*/
|
|
126
178
|
static VALUE board_openness(VALUE self, VALUE x, VALUE y) {
|
|
127
179
|
int dx, dy, sum = 0;
|
|
128
180
|
struct board *ptr;
|
|
@@ -137,12 +189,25 @@ static VALUE board_openness(VALUE self, VALUE x, VALUE y) {
|
|
|
137
189
|
return INT2FIX(sum);
|
|
138
190
|
}
|
|
139
191
|
|
|
192
|
+
/*
|
|
193
|
+
* Returns the disk color of supplied coordinates.
|
|
194
|
+
*
|
|
195
|
+
* @param x [Integer] the column number
|
|
196
|
+
* @param y [Integer] the row number
|
|
197
|
+
* @return [Integer] `black`: -1, `white`: 1, `none`: 0
|
|
198
|
+
*/
|
|
140
199
|
static VALUE board_at(VALUE self, VALUE x, VALUE y) {
|
|
141
200
|
struct board *ptr;
|
|
142
201
|
Data_Get_Struct(self, struct board, ptr);
|
|
143
202
|
return INT2FIX(ptr->columns[FIX2INT(x)][FIX2INT(y)]);
|
|
144
203
|
}
|
|
145
204
|
|
|
205
|
+
/*
|
|
206
|
+
* Counts the number of the supplied color's disks.
|
|
207
|
+
*
|
|
208
|
+
* @param color [Integer] `black`: -1, `white`: 1, `none`: 0
|
|
209
|
+
* @return [Integer] the sum of the counted disks
|
|
210
|
+
*/
|
|
146
211
|
static VALUE board_count_disks(VALUE self, VALUE color) {
|
|
147
212
|
int x, y, count = 0;
|
|
148
213
|
struct board *ptr;
|
|
@@ -156,6 +221,12 @@ static VALUE board_count_disks(VALUE self, VALUE color) {
|
|
|
156
221
|
return INT2FIX(count);
|
|
157
222
|
}
|
|
158
223
|
|
|
224
|
+
/*
|
|
225
|
+
* Returns an array of the next moves.
|
|
226
|
+
*
|
|
227
|
+
* @param color [Integer] `black`: -1, `white`: 1, `none`: 0
|
|
228
|
+
* @return [Array] the next moves
|
|
229
|
+
*/
|
|
159
230
|
static VALUE board_next_moves(VALUE self, VALUE color) {
|
|
160
231
|
int x, y;
|
|
161
232
|
VALUE move = rb_ary_new();
|
|
@@ -174,6 +245,13 @@ static VALUE board_next_moves(VALUE self, VALUE color) {
|
|
|
174
245
|
return moves;
|
|
175
246
|
}
|
|
176
247
|
|
|
248
|
+
/*
|
|
249
|
+
* Places a supplied color's disk on specified position.
|
|
250
|
+
*
|
|
251
|
+
* @param x [Integer] the column number
|
|
252
|
+
* @param y [Integer] the row number
|
|
253
|
+
* @param color [Integer] `black`: -1, `white`: 1, `none`: 0
|
|
254
|
+
*/
|
|
177
255
|
static VALUE board_put_disk(VALUE self, VALUE x, VALUE y, VALUE color) {
|
|
178
256
|
struct board *ptr;
|
|
179
257
|
Data_Get_Struct(self, struct board, ptr);
|
|
@@ -182,6 +260,14 @@ static VALUE board_put_disk(VALUE self, VALUE x, VALUE y, VALUE color) {
|
|
|
182
260
|
return Qnil;
|
|
183
261
|
}
|
|
184
262
|
|
|
263
|
+
/*
|
|
264
|
+
* Flips the opponent's disks between a new disk and another disk of my color.
|
|
265
|
+
* The invalid move has no effect.
|
|
266
|
+
*
|
|
267
|
+
* @param x [Integer] the column number
|
|
268
|
+
* @param y [Integer] the row number
|
|
269
|
+
* @param color [Integer] `black`: -1, `white`: 1, `none`: 0
|
|
270
|
+
*/
|
|
185
271
|
static VALUE board_flip_disks(VALUE self, VALUE x, VALUE y, VALUE color) {
|
|
186
272
|
int dx, dy;
|
|
187
273
|
struct board *ptr;
|
|
@@ -201,6 +287,10 @@ static VALUE board_flip_disks(VALUE self, VALUE x, VALUE y, VALUE color) {
|
|
|
201
287
|
return Qnil;
|
|
202
288
|
}
|
|
203
289
|
|
|
290
|
+
/*
|
|
291
|
+
* Flips the opponent's disks on one of these straight lines
|
|
292
|
+
* between a new disk and another disk of my color.
|
|
293
|
+
*/
|
|
204
294
|
void flip_disk(VALUE self, int x, int y, int dx, int dy, int color){
|
|
205
295
|
struct board *ptr;
|
|
206
296
|
Data_Get_Struct(self, struct board, ptr);
|
|
@@ -214,6 +304,10 @@ void flip_disk(VALUE self, int x, int y, int dx, int dy, int color){
|
|
|
214
304
|
return;
|
|
215
305
|
}
|
|
216
306
|
|
|
307
|
+
/*
|
|
308
|
+
* Whether or not a player can place a new disk on specified position.
|
|
309
|
+
* Returns `1` if the move is valid.
|
|
310
|
+
*/
|
|
217
311
|
int can_put(VALUE self, int x, int y, int color){
|
|
218
312
|
int dx, dy;
|
|
219
313
|
struct board *ptr;
|
|
@@ -232,6 +326,9 @@ int can_put(VALUE self, int x, int y, int color){
|
|
|
232
326
|
return 0;
|
|
233
327
|
}
|
|
234
328
|
|
|
329
|
+
/*
|
|
330
|
+
* Whether or not a player can flip the opponent's disks.
|
|
331
|
+
*/
|
|
235
332
|
int can_flip(VALUE self, int x, int y, int dx, int dy, int color){
|
|
236
333
|
struct board *ptr;
|
|
237
334
|
Data_Get_Struct(self, struct board, ptr);
|
data/ext/reversi/reversi.h
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include <memory.h>
|
|
4
4
|
|
|
5
5
|
void Init_board(void);
|
|
6
|
+
|
|
6
7
|
static VALUE board_alloc(VALUE class);
|
|
7
8
|
static VALUE board_initialize(VALUE self);
|
|
8
9
|
static VALUE board_columns_getter(VALUE self);
|
|
@@ -26,18 +27,3 @@ void pop_stack(int ary[10][10]);
|
|
|
26
27
|
int stack_size(void);
|
|
27
28
|
void delete_old(void);
|
|
28
29
|
void reset_stack(void);
|
|
29
|
-
|
|
30
|
-
struct stack{
|
|
31
|
-
int stack_columns[10][10];
|
|
32
|
-
struct stack *next;
|
|
33
|
-
};
|
|
34
|
-
struct stack *head = NULL;
|
|
35
|
-
|
|
36
|
-
struct board {
|
|
37
|
-
int columns[10][10];
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
static VALUE board_alloc(VALUE class) {
|
|
41
|
-
struct board *ptr = ALLOC(struct board);
|
|
42
|
-
return Data_Wrap_Struct(class, 0, -1, ptr);
|
|
43
|
-
}
|
data/lib/reversi/board.rb
CHANGED
|
@@ -120,7 +120,7 @@ module Reversi
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# Flips the opponent's disks between a new disk and another disk of my color.
|
|
123
|
-
#
|
|
123
|
+
# The invalid move has no effect.
|
|
124
124
|
#
|
|
125
125
|
# @param x [Symbol, Integer] the column number
|
|
126
126
|
# @param y [Integer] the row number
|
|
@@ -20,7 +20,7 @@ module Reversi
|
|
|
20
20
|
:disk_w => 'w',
|
|
21
21
|
:disk_color_b => 0,
|
|
22
22
|
:disk_color_w => 0,
|
|
23
|
-
:initial_position => {:black => [[
|
|
23
|
+
:initial_position => {:black => [[4, 5], [5, 4]], :white => [[4, 4], [5, 5]]},
|
|
24
24
|
:progress => false,
|
|
25
25
|
:stack_limit => 3
|
|
26
26
|
}
|
data/lib/reversi/player.rb
CHANGED
|
@@ -3,6 +3,7 @@ module Reversi
|
|
|
3
3
|
autoload :RandomAI, "reversi/player/random_ai"
|
|
4
4
|
autoload :NegaMaxAI, "reversi/player/nega_max_ai"
|
|
5
5
|
autoload :MinMaxAI, "reversi/player/min_max_ai"
|
|
6
|
+
autoload :AlphaBetaAI, "reversi/player/alpha_beta_ai"
|
|
6
7
|
autoload :Human, "reversi/player/human"
|
|
7
8
|
end
|
|
8
9
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Reversi::Player
|
|
2
|
+
class AlphaBetaAI < BasePlayer
|
|
3
|
+
|
|
4
|
+
N = 10000.freeze
|
|
5
|
+
|
|
6
|
+
def initialize(_color, _board)
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
points = [
|
|
10
|
+
100, -10, 0, -1, -1, 0, -10, 100,
|
|
11
|
+
-10, -30, -5, -5, -5, -5, -30, -10,
|
|
12
|
+
0, -5, 0, -1, -1, 0, -5, 0,
|
|
13
|
+
-1, -5, -1, -1, -1, -1, -5, -1,
|
|
14
|
+
-1, -5, -1, -1, -1, -1, -5, -1,
|
|
15
|
+
0, -5, 0, -1, -1, 0, -5, 0,
|
|
16
|
+
-10, -30, -5, -5, -5, -5, -30, -10,
|
|
17
|
+
100, -10, 0, -1, -1, 0, -10, 100
|
|
18
|
+
]
|
|
19
|
+
@evaluation_value =
|
|
20
|
+
Hash[(1..8).map{ |x| (1..8).map{ |y| [[x, y], points.shift] } }.flatten(1) ]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def move(board)
|
|
24
|
+
moves = next_moves.sort_by{ |v| v[:openness] }.map{ |v| v[:move] }
|
|
25
|
+
return if moves.empty?
|
|
26
|
+
alpha = -N; beta = N
|
|
27
|
+
next_move = moves.map do |move|
|
|
28
|
+
{ :move => move, :point => evaluate(move, board, alpha, beta, 3, true) }
|
|
29
|
+
end.max_by{ |v| v[:point] }[:move]
|
|
30
|
+
put_disk(*next_move)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def evaluate(move, board, alpha, beta, depth, color)
|
|
34
|
+
put_disk(*move, color)
|
|
35
|
+
moves = next_moves(!color).sort_by{ |v| v[:openness] }.map{ |v| v[:move] }
|
|
36
|
+
|
|
37
|
+
if depth == 1
|
|
38
|
+
status[:mine].inject(0){ |sum, xy| sum + @evaluation_value[xy] }
|
|
39
|
+
|
|
40
|
+
elsif moves.empty?
|
|
41
|
+
case depth
|
|
42
|
+
when ->(n){ n.odd? } then alpha
|
|
43
|
+
when ->(n){ n.even? } then beta end
|
|
44
|
+
|
|
45
|
+
else
|
|
46
|
+
case depth
|
|
47
|
+
when ->(n){ n.odd? }
|
|
48
|
+
beta = N
|
|
49
|
+
moves.each do |move|
|
|
50
|
+
val = evaluate(move, board, alpha, beta, depth - 1, !color)
|
|
51
|
+
beta = val if val < beta
|
|
52
|
+
return alpha if alpha > beta
|
|
53
|
+
end
|
|
54
|
+
beta
|
|
55
|
+
when ->(n){ n.even? }
|
|
56
|
+
alpha = -N
|
|
57
|
+
moves.each do |move|
|
|
58
|
+
val = evaluate(move, board, alpha, beta, depth - 1, !color)
|
|
59
|
+
alpha = val if val > alpha
|
|
60
|
+
return beta if alpha > beta
|
|
61
|
+
end
|
|
62
|
+
alpha
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
ensure
|
|
67
|
+
board.undo!
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -4,7 +4,7 @@ module Reversi::Player
|
|
|
4
4
|
def initialize(_color, _board)
|
|
5
5
|
super
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
points = [
|
|
8
8
|
100, -10, 0, -1, -1, 0, -10, 100,
|
|
9
9
|
-10, -30, -5, -5, -5, -5, -30, -10,
|
|
10
10
|
0, -5, 0, -1, -1, 0, -5, 0,
|
|
@@ -15,17 +15,15 @@ module Reversi::Player
|
|
|
15
15
|
100, -10, 0, -1, -1, 0, -10, 100
|
|
16
16
|
]
|
|
17
17
|
@evaluation_value =
|
|
18
|
-
Hash[(1..8).map{ |x| (1..8).map{ |y| [[x, y],
|
|
18
|
+
Hash[(1..8).map{ |x| (1..8).map{ |y| [[x, y], points.shift] } }.flatten(1) ]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def move(board)
|
|
22
22
|
moves = next_moves.map{ |v| v[:move] }
|
|
23
23
|
return if moves.empty?
|
|
24
|
-
|
|
25
24
|
next_move = moves.map do |move|
|
|
26
|
-
{ :move => move, :point => evaluate(move, board,
|
|
27
|
-
end
|
|
28
|
-
.max_by{ |v| v[:point] }[:move]
|
|
25
|
+
{ :move => move, :point => evaluate(move, board, 3, true) }
|
|
26
|
+
end.max_by{ |v| v[:point] }[:move]
|
|
29
27
|
put_disk(*next_move)
|
|
30
28
|
end
|
|
31
29
|
|
|
@@ -33,12 +31,13 @@ module Reversi::Player
|
|
|
33
31
|
put_disk(*move, color)
|
|
34
32
|
moves = next_moves(!color).map{ |v| v[:move] }
|
|
35
33
|
|
|
36
|
-
if depth ==
|
|
34
|
+
if depth == 1
|
|
37
35
|
status[:mine].inject(0){ |sum, xy| sum + @evaluation_value[xy] }
|
|
38
36
|
elsif moves.empty?
|
|
39
37
|
-100
|
|
38
|
+
|
|
40
39
|
else
|
|
41
|
-
values = moves.map{ |move| evaluate(move, board, depth
|
|
40
|
+
values = moves.map{ |move| evaluate(move, board, depth - 1, !color) }
|
|
42
41
|
case depth
|
|
43
42
|
when ->(n){ n.odd? } then values.min
|
|
44
43
|
when ->(n){ n.even? } then values.max end
|
|
@@ -4,7 +4,7 @@ module Reversi::Player
|
|
|
4
4
|
def initialize(_color, _board)
|
|
5
5
|
super
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
points = [
|
|
8
8
|
100, -10, 0, -1, -1, 0, -10, 100,
|
|
9
9
|
-10, -30, -5, -5, -5, -5, -30, -10,
|
|
10
10
|
0, -5, 0, -1, -1, 0, -5, 0,
|
|
@@ -15,17 +15,15 @@ module Reversi::Player
|
|
|
15
15
|
100, -10, 0, -1, -1, 0, -10, 100
|
|
16
16
|
]
|
|
17
17
|
@evaluation_value =
|
|
18
|
-
Hash[(1..8).map{ |x| (1..8).map{ |y| [[x, y],
|
|
18
|
+
Hash[(1..8).map{ |x| (1..8).map{ |y| [[x, y], points.shift] } }.flatten(1) ]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def move(board)
|
|
22
22
|
moves = next_moves.map{ |v| v[:move] }
|
|
23
23
|
return if moves.empty?
|
|
24
|
-
|
|
25
24
|
next_move = moves.map do |move|
|
|
26
|
-
{ :move => move, :point => evaluate(move, board,
|
|
27
|
-
end
|
|
28
|
-
.max_by{ |v| v[:point] }[:move]
|
|
25
|
+
{ :move => move, :point => evaluate(move, board, 3, true) }
|
|
26
|
+
end.max_by{ |v| v[:point] }[:move]
|
|
29
27
|
put_disk(*next_move)
|
|
30
28
|
end
|
|
31
29
|
|
|
@@ -33,12 +31,12 @@ module Reversi::Player
|
|
|
33
31
|
put_disk(*move, color)
|
|
34
32
|
moves = next_moves(!color).map{ |v| v[:move] }
|
|
35
33
|
|
|
36
|
-
if depth ==
|
|
34
|
+
if depth == 1
|
|
37
35
|
status[:mine].inject(0){ |sum, xy| sum + @evaluation_value[xy] }
|
|
38
36
|
elsif moves.empty?
|
|
39
37
|
-100
|
|
40
38
|
else
|
|
41
|
-
-( moves.map{ |move| evaluate(move, board, depth
|
|
39
|
+
-( moves.map{ |move| evaluate(move, board, depth - 1, !color) }.max )
|
|
42
40
|
end
|
|
43
41
|
|
|
44
42
|
ensure
|
data/lib/reversi/version.rb
CHANGED
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: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
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-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- lib/reversi/configuration.rb
|
|
51
51
|
- lib/reversi/game.rb
|
|
52
52
|
- lib/reversi/player.rb
|
|
53
|
+
- lib/reversi/player/alpha_beta_ai.rb
|
|
53
54
|
- lib/reversi/player/base_player.rb
|
|
54
55
|
- lib/reversi/player/human.rb
|
|
55
56
|
- lib/reversi/player/min_max_ai.rb
|