1 module dreversi.env;
2 
3 import std.stdio;
4 import mir.ndslice;
5 
6 // states of reversi points
7 enum Point {
8     empty = 0,
9     black = 1,
10     white = 2
11 }
12 
13 /// text representaion of Point
14 enum pointString = [Point.empty: " ",
15                     Point.black: "x",
16                     Point.white: "o"];
17 
18 /// board data of reversi
19 struct Board {
20     Slice!(Contiguous, [2LU], Point*) data;
21     bool valid = true;
22     alias data this;
23 
24     @property @safe
25     pure toString() const {
26         import std..string;
27         string horizontal = "   ";
28         foreach (j; 0 .. this.length!1) {
29             horizontal ~= format!"%3d"(j);
30         }
31         horizontal ~= "\n";
32         string ret = horizontal;
33         foreach (i; 0 .. this.length!0) {
34             ret ~= format!"%3d"(i);
35             foreach (j; 0..this.length!1) {
36                 ret ~= format!"[%s]"(pointString[this.slice[i, j]]);
37             }
38             ret ~= format!"%3d\n"(i);
39         }
40         ret ~= horizontal;
41         return ret;
42     }
43 }
44 
45 pure @safe:
46 
47 /// returns next step by the action
48 auto put(in Board b, bool isBlack, size_t row, size_t col) {
49     import std.algorithm : min;
50 
51     immutable p = isBlack ? Point.black : Point.white;
52     if (row >= b.length!0 || col >= b.length!1 || b[row, col] != Point.empty) {
53         return Board(b.data, false);
54     }
55 
56     auto ret = b.data.slice; // dup
57     // upper
58     foreach_reverse (r; 0 .. row) {
59         if (ret[r, col] == Point.empty) {
60             break;
61         }
62         if (ret[r, col] == p) {
63             ret[r..row, col] = p;
64             break;
65         }
66     }
67     // lower
68     foreach (r; row+1 .. ret.length!0) {
69         if (ret[r, col] == Point.empty) {
70             break;
71         }
72         if (ret[r, col] == p) {
73             ret[row+1..r, col] = p;
74             break;
75         }
76     }
77     // left
78     foreach_reverse (c; 0 .. col) {
79         if (ret[row, c] == Point.empty) {
80             break;
81         }
82         if (ret[row, c] == p) {
83             ret[row, c..col] = p;
84             break;
85         }
86     }
87     // right
88     foreach (c; col+1..ret.length!1) {
89         if (ret[row, c] == Point.empty) {
90             break;
91         }
92         if (ret[row, c] == p) {
93             ret[row, col+1..c] = p;
94             break;
95         }
96     }
97     // left-upper
98     foreach (n; 1..min(row, col) + 1) {
99         if (ret[row-n, col-n] == Point.empty) {
100             break;
101         }
102         if (ret[row-n, col-n] == p) {
103             foreach (m; 1 .. n+1) {
104                 ret[row-m, col-m] = p;
105             }
106             break;
107         }
108     }
109     // left-lower
110     foreach (n; 1..min(row, ret.length!1 - col - 1) + 1) {
111         if (ret[row-n, col+n] == Point.empty) {
112             break;
113         }
114         if (ret[row-n, col+n] == p) {
115             foreach (m; 1 .. n+1) {
116                 ret[row-m, col+m] = p;
117             }
118             break;
119         }
120     }
121     // right-upper
122     foreach (n; 1..min(ret.length!0 - row - 1, col) + 1) {
123         if (ret[row+n, col-n] == Point.empty) {
124             break;
125         }
126         if (ret[row+n, col-n] == p) {
127             foreach (m; 1 .. n+1) {
128                 ret[row+m, col-m] = p;
129             }
130             break;
131         }
132     }
133     // right-lower
134     foreach (n; 1..min(ret.length!0 - row - 1, ret.length!1 - col - 1) + 1) {
135         if (ret[row+n, col+n] == Point.empty) {
136             break;
137         }
138         if (ret[row+n, col+n] == p) {
139             foreach (m; 1 .. n+1) {
140                 ret[row+m, col+m] = p;
141             }
142             break;
143         }
144     }
145     // if nothing is changed, the action is invalid
146     immutable valid = ret != b.data;
147     ret[row, col] = p;
148     return Board(ret, valid);
149 }
150 
151 /// counts the number of empty/black/white positions
152 auto count(in Board b) {
153     auto ret =  [Point.empty: 0, Point.black: 0, Point.white: 0];
154     b.each!((p) { ++ret[p]; });
155     return ret;
156 }
157 
158 /// returns true if no next move for the color
159 auto pass(in Board b, bool isBlack) {
160     import std.algorithm : any, map;
161     auto stat = b.count;
162     if (stat.byValue.map!"a == 0".any) return true;
163     foreach (i; 0 .. b.length!0) {
164         foreach (j; 0 .. b.length!1) {
165             if (b.put(isBlack, i, j).valid) {
166                 return false;
167             }
168         }
169     }
170     return true;
171 }
172 
173 /// test rules
174 unittest {
175     immutable b0 = Board([Point.empty, Point.empty, Point.empty, Point.empty,
176                           Point.empty, Point.black, Point.white, Point.empty,
177                           Point.empty, Point.empty, Point.black, Point.empty,
178                           Point.empty, Point.empty, Point.empty, Point.empty].sliced(4, 4));
179     assert(!b0.put(true, 0, 4).valid); // out-of-bounds
180     assert(!b0.put(true, 1, 1).valid); // non empty
181     assert(!b0.put(true, 0, 0).valid); // begin
182     assert(!b0.put(true, 3, 3).valid); // end
183     assert(!b0.put(true, 0, 1).valid); // no reverse neighbor
184 
185     immutable b1 = b0.put(true, 1, 3);
186     assert(b1 == [Point.empty, Point.empty, Point.empty, Point.empty,
187                   Point.empty, Point.black, Point.black, Point.black,
188                   Point.empty, Point.empty, Point.black, Point.empty,
189                   Point.empty, Point.empty, Point.empty, Point.empty].sliced(4, 4));
190     assert(b1.valid);
191     assert(b1.pass(true));
192     assert(b1.pass(false));
193 
194     immutable b5x5 = Board([Point.white, Point.empty, Point.white, Point.white, Point.white,
195                             Point.empty, Point.black, Point.black, Point.black, Point.empty,
196                             Point.black, Point.black, Point.empty, Point.black, Point.white,
197                             Point.empty, Point.black, Point.black, Point.black, Point.empty,
198                             Point.white, Point.empty, Point.white, Point.white, Point.white].sliced(5, 5));
199     auto b5x5_ = b5x5.put(false, 2, 2);
200     assert(b5x5_.toString ==
201 `     0  1  2  3  4
202   0[o][ ][o][o][o]  0
203   1[ ][o][o][o][ ]  1
204   2[x][x][o][o][o]  2
205   3[ ][o][o][o][ ]  3
206   4[o][ ][o][o][o]  4
207      0  1  2  3  4
208 `);
209     assert(b5x5_.count == [Point.empty: 6, Point.white: 17, Point.black: 2]);
210     assert(!b5x5_.pass(true));
211     assert(!b5x5_.pass(false));
212 
213     immutable b2 = Board([Point.empty, Point.empty, Point.empty, Point.empty,
214                           Point.empty, Point.black, Point.black, Point.black,
215                           Point.empty, Point.empty, Point.black, Point.empty,
216                           Point.empty, Point.empty, Point.white, Point.empty].sliced(4, 4));
217     assert(b2.pass(true));
218     assert(!b2.pass(false));
219 
220     /*
221          0  1  2  3        
222       0[x][ ][ ][ ]  0     
223       1[x][o][o][ ]  1     
224       2[x][o][o][ ]  2     
225       3[o][o][o][ ]  3     
226          0  1  2  3
227       >>> 3 0
228       >>> 3 2
229       >>> 1 0
230      */
231     immutable b3 = Board([Point.black, Point.empty, Point.empty, Point.empty,
232                           Point.black, Point.white, Point.white, Point.empty,
233                           Point.black, Point.white, Point.white, Point.empty,
234                           Point.white, Point.white, Point.white, Point.empty].sliced(4, 4));
235     assert(!b3.pass(true));
236     assert(b3.pass(false));
237 
238     /*
239      0  1  2  3  4  5  6  7
240   0[ ][ ][ ][ ][ ][ ][ ][ ]  0
241   1[ ][ ][ ][ ][ ][ ][ ][ ]  1
242   2[ ][ ][ ][ ][ ][ ][ ][ ]  2
243   3[ ][ ][o][o][o][o][o][ ]  3
244   4[x][x][x][x][o][ ][ ][ ]  4
245   5[x][x][x][x][x][o][x][x]  5
246   6[x][x][x][x][x][x][o][ ]  6
247   7[x][x][x][x][x][x][x][x]  7
248      0  1  2  3  4  5  6  7
249      */
250     enum _ = Point.empty;
251     enum x = Point.black;
252     enum o = Point.white;
253     immutable b4 = Board([_, _, _, _, _, _, _, _,
254                           _, _, _, _, _, _, _, _,
255                           _, _, _, _, _, _, _, _,
256                           _, _, o, o, o, o, o, _,
257                           x, x, x, x, o, _, _, _,
258                           x, x, x, x, x, o, x, x,
259                           x, x, x, x, x, x, o, _,
260                           x, x, x, x, x, x, x, x].sliced(8, 8));
261     assert(!b4.put(false, 0, 0).valid);
262 }
263 
264 /// returns initial board
265 auto reset(size_t rows = 8, size_t cols = 8) {
266     auto s = uninitSlice!Point(rows, cols);
267     s[] = Point.empty;
268     s[rows / 2, cols / 2] = Point.black;
269     s[rows / 2, cols / 2 - 1] = Point.white;
270     s[rows / 2 - 1, cols / 2] = Point.white;
271     s[rows / 2 - 1, cols / 2 - 1] = Point.black;
272     return Board(s);
273 }
274 
275 auto finished(in Board board) {
276     return board.pass(true) && board.pass(false);
277 }
278 
279 auto score(in Board board, bool isBlack) {
280     immutable stat = board.count;
281     return stat[isBlack ? Point.black : Point.white] - stat[isBlack ? Point.white : Point.black];
282 }