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 }