1 module model; 2 3 import std.random : uniform; 4 5 /// Cell codes 6 enum : int { 7 WALL = -1, 8 EMPTY = 0, 9 FIGURE1, 10 FIGURE2, 11 FIGURE3, 12 FIGURE4, 13 FIGURE5, 14 FIGURE6, 15 FIGURE7, 16 } 17 18 /// Orientations 19 enum : int { 20 ORIENTATION0, 21 ORIENTATION90, 22 ORIENTATION180, 23 ORIENTATION270 24 } 25 26 27 /// Cell offset 28 struct FigureCell { 29 // horizontal offset 30 int dx; 31 // vertical offset 32 int dy; 33 this(int[2] v) { 34 dx = v[0]; 35 dy = v[1]; 36 } 37 } 38 39 /// Single figure shape for some particular orientation - 4 cells 40 struct FigureShape { 41 /// by cell index 0..3 42 FigureCell[4] cells; 43 /// lowest y coordinate - to show next figure above cup 44 int extent; 45 /// upper y coordinate - initial Y offset to place figure to cup 46 int y0; 47 /// Init cells (cell 0 is [0,0]) 48 this(int[2] c2, int[2] c3, int[2] c4) { 49 cells[0] = FigureCell([0, 0]); 50 cells[1] = FigureCell(c2); 51 cells[2] = FigureCell(c3); 52 cells[3] = FigureCell(c4); 53 extent = y0 = 0; 54 foreach (cell; cells) { 55 if (extent > cell.dy) 56 extent = cell.dy; 57 if (y0 < cell.dy) 58 y0 = cell.dy; 59 } 60 } 61 } 62 63 /// Figure data - shapes for 4 orientations 64 struct Figure { 65 FigureShape[4] shapes; // by orientation 66 this(FigureShape[4] v) { 67 shapes = v; 68 } 69 } 70 71 /// All shapes 72 __gshared const Figure[7] FIGURES; 73 74 // workaround for dmd 2.0.67 - move initialization to static this 75 __gshared static this() { 76 FIGURES = [ 77 // FIGURE1 =========================================== 78 // ## #### 79 // 00## 00## 80 // ## 81 Figure([FigureShape([1, 0], [ 1, 1], [0,-1]), 82 FigureShape([0, 1], [-1, 1], [1, 0]), 83 FigureShape([1, 0], [ 1, 1], [0,-1]), 84 FigureShape([0, 1], [-1, 1], [1, 0])]), 85 // FIGURE2 =========================================== 86 // ## #### 87 // 00## ##00 88 // ## 89 Figure([FigureShape([1, 0], [0, 1], [ 1,-1]), 90 FigureShape([0, 1], [1, 1], [-1, 0]), 91 FigureShape([1, 0], [0, 1], [ 1,-1]), 92 FigureShape([0, 1], [1, 1], [-1, 0])]), 93 // FIGURE3 =========================================== 94 // ## ## #### 95 // ##00## 00 ##00## 00 96 // ## #### ## 97 Figure([FigureShape([1, 0], [-1, 0], [-1,-1]), 98 FigureShape([0, 1], [ 0,-1], [ 1,-1]), 99 FigureShape([1, 0], [-1, 0], [ 1, 1]), 100 FigureShape([0, 1], [-1, 1], [ 0,-1])]), 101 // FIGURE4 =========================================== 102 // #### ## ## 103 // ##00## 00 ##00## 00 104 // ## ## #### 105 Figure([FigureShape([1, 0], [-1, 0], [ 1,-1]), 106 FigureShape([0, 1], [ 0,-1], [ 1, 1]), 107 FigureShape([1, 0], [-1, 0], [-1, 1]), 108 FigureShape([0, 1], [-1,-1], [ 0,-1])]), 109 // FIGURE5 =========================================== 110 // #### 111 // 00## 112 // 113 Figure([FigureShape([1, 0], [0, 1], [ 1, 1]), 114 FigureShape([1, 0], [0, 1], [ 1, 1]), 115 FigureShape([1, 0], [0, 1], [ 1, 1]), 116 FigureShape([1, 0], [0, 1], [ 1, 1])]), 117 // FIGURE6 =========================================== 118 // ## 119 // ## 120 // 00 ##00#### 121 // ## 122 Figure([FigureShape([0, 1], [0, 2], [ 0,-1]), 123 FigureShape([1, 0], [2, 0], [-1, 0]), 124 FigureShape([0, 1], [0, 2], [ 0,-1]), 125 FigureShape([1, 0], [2, 0], [-1, 0])]), 126 // FIGURE7 =========================================== 127 // ## ## ## 128 // ##00## 00## ##00## ##00 129 // ## ## ## 130 Figure([FigureShape([1, 0], [-1,0], [ 0,-1]), 131 FigureShape([0, 1], [0,-1], [ 1, 0]), 132 FigureShape([1, 0], [-1,0], [ 0, 1]), 133 FigureShape([0, 1], [0,-1], [-1, 0])]), 134 ]; 135 } 136 137 /// colors for different figure types 138 const uint[7] _figureColors = [0xC00000, 0x80A000, 0xA00080, 0x0000C0, 0x800020, 0x408000, 0x204000]; 139 140 /// Figure type, orientation and position container 141 struct FigurePosition { 142 int index; 143 int orientation; 144 int x; 145 int y; 146 this(int index, int orientation, int x, int y) { 147 this.index = index; 148 this.orientation = orientation; 149 this.x = x; 150 this.y = y; 151 } 152 /// return rotated position CCW for angle=1, CW for angle=-1 153 FigurePosition rotate(int angle) { 154 int newOrientation = (orientation + 4 + angle) & 3; 155 return FigurePosition(index, newOrientation, x, y); 156 } 157 /// return moved position 158 FigurePosition move(int dx, int dy = 0) { 159 return FigurePosition(index, orientation, x + dx, y + dy); 160 } 161 /// return shape for figure orientation 162 @property FigureShape shape() const { 163 return FIGURES[index - 1].shapes[orientation]; 164 } 165 /// return color for figure 166 @property uint color() const { 167 return _figureColors[index - 1]; 168 } 169 /// return true if figure index is not initialized 170 @property empty() const { 171 return index == 0; 172 } 173 /// clears content 174 void reset() { 175 index = 0; 176 } 177 } 178 179 /** 180 Cup content 181 182 Coordinates are relative to bottom left corner. 183 */ 184 struct Cup { 185 private int[] _cup; 186 private int _cols; 187 private int _rows; 188 private bool[] _destroyedFullRows; 189 private int[] _cellGroups; 190 191 private FigurePosition _currentFigure; 192 /// current figure index, orientation, position 193 @property ref FigurePosition currentFigure() return { return _currentFigure; } 194 195 private FigurePosition _nextFigure; 196 /// next figure 197 @property ref FigurePosition nextFigure() return { return _nextFigure; } 198 199 /// returns number of columns 200 @property int cols() { 201 return _cols; 202 } 203 /// returns number of columns 204 @property int rows() { 205 return _rows; 206 } 207 /// inits empty cup of specified size 208 void initialize(int cols, int rows) { 209 _cols = cols; 210 _rows = rows; 211 _cup = new int[_cols * _rows]; 212 _destroyedFullRows = new bool[_rows]; 213 _cellGroups = new int[_cols * _rows]; 214 } 215 /// returns cell content at specified position 216 int opIndex(int col, int row) { 217 if (col < 0 || row < 0 || col >= _cols || row >= _rows) 218 return WALL; 219 return _cup[row * _cols + col]; 220 } 221 /// set cell value 222 void opIndexAssign(int value, int col, int row) { 223 if (col < 0 || row < 0 || col >= _cols || row >= _rows) 224 return; // ignore modification of cells outside cup 225 _cup[row * _cols + col] = value; 226 } 227 /// put current figure into cup at current position and orientation 228 void putFigure() { 229 FigureShape shape = _currentFigure.shape; 230 foreach(cell; shape.cells) { 231 this[_currentFigure.x + cell.dx, _currentFigure.y + cell.dy] = _currentFigure.index; 232 } 233 } 234 235 /// check if all cells where specified figure is located are free 236 bool isPositionFree(in FigurePosition pos) { 237 FigureShape shape = pos.shape; 238 foreach(cell; shape.cells) { 239 int value = this[pos.x + cell.dx, pos.y + cell.dy]; 240 if (value != 0) // occupied 241 return false; 242 } 243 return true; 244 } 245 /// returns true if specified row is full 246 bool isRowFull(int row) { 247 for (int i = 0; i < _cols; i++) 248 if (this[i, row] == EMPTY) 249 return false; 250 return true; 251 } 252 /// returns true if at least one row is full 253 @property bool hasFullRows() { 254 for (int i = 0; i < _rows; i++) 255 if (isRowFull(i)) 256 return true; 257 return false; 258 } 259 /// destroy all full rows, saving flags for destroyed rows; returns count of destroyed rows, 0 if no rows destroyed 260 int destroyFullRows() { 261 int res = 0; 262 for (int i = 0; i < _rows; i++) { 263 if (isRowFull(i)) { 264 _destroyedFullRows[i] = true; 265 res++; 266 for (int col = 0; col < _cols; col++) 267 this[col, i] = EMPTY; 268 } else { 269 _destroyedFullRows[i] = false; 270 } 271 } 272 return res; 273 } 274 275 /// check if all cells where current figire is located are free 276 bool isPositionFree() { 277 return isPositionFree(_currentFigure); 278 } 279 280 /// check if all cells where current figire is located are free 281 bool isPositionFreeBelow() { 282 return isPositionFree(_currentFigure.move(0, -1)); 283 } 284 285 /// try to rotate current figure, returns true if figure rotated 286 bool rotate(int angle, bool falling) { 287 FigurePosition newpos = _currentFigure.rotate(angle); 288 if (isPositionFree(newpos)) { 289 if (falling) { 290 // special handling for fall animation 291 if (!isPositionFree(newpos.move(0, -1))) { 292 if (isPositionFreeBelow()) 293 return false; 294 } 295 } 296 _currentFigure = newpos; 297 return true; 298 } else if (isPositionFree(newpos.move(0, -1))) { 299 _currentFigure = newpos.move(0, -1); 300 return true; 301 } 302 return false; 303 } 304 305 /// try to move current figure, returns true if figure rotated 306 bool move(int deltaX, int deltaY, bool falling) { 307 FigurePosition newpos = _currentFigure.move(deltaX, deltaY); 308 if (isPositionFree(newpos)) { 309 if (falling && !isPositionFree(newpos.move(0, -1))) { 310 if (isPositionFreeBelow()) 311 return false; 312 } 313 _currentFigure = newpos; 314 return true; 315 } 316 return false; 317 } 318 319 /// random next figure 320 void genNextFigure() { 321 _nextFigure.index = uniform(FIGURE1, FIGURE7 + 1); 322 _nextFigure.orientation = ORIENTATION0; 323 _nextFigure.x = _cols / 2; 324 _nextFigure.y = _rows - _nextFigure.shape.extent + 1; 325 } 326 327 /// New figure: put it on top of cup 328 bool dropNextFigure() { 329 if (_nextFigure.empty) 330 genNextFigure(); 331 _currentFigure = _nextFigure; 332 _currentFigure.x = _cols / 2; 333 _currentFigure.y = _rows - 1 - _currentFigure.shape.y0; 334 return isPositionFree(); 335 } 336 337 /// get cell group / falling cell value 338 private int cellGroup(int col, int row) { 339 if (col < 0 || row < 0 || col >= _cols || row >= _rows) 340 return 0; 341 return _cellGroups[col + row * _cols]; 342 } 343 344 /// set cell group / falling cells value 345 private void setCellGroup(int value, int col, int row) { 346 _cellGroups[col + row * _cols] = value; 347 } 348 349 /// recursive fill occupied area of cells with group id 350 private void fillCellGroup(int x, int y, int value) { 351 if (x < 0 || y < 0 || x >= _cols || y >= _rows) 352 return; 353 if (this[x, y] != EMPTY && cellGroup(x, y) == 0) { 354 setCellGroup(value, x, y); 355 fillCellGroup(x + 1, y, value); 356 fillCellGroup(x - 1, y, value); 357 fillCellGroup(x, y + 1, value); 358 fillCellGroup(x, y - 1, value); 359 } 360 } 361 362 /// 1 == next cell below is occupied, 2 == one empty cell 363 private int distanceToOccupiedCellBelow(int col, int row) { 364 for (int y = row - 1; y >= -1; y--) { 365 if (this[col, y] != EMPTY) 366 return row - y; 367 } 368 return 1; 369 } 370 371 /// 1 == next cell below is occupied, 2 == one empty cell 372 private int distanceToOccupiedCellBelowForGroup(int group) { 373 int minDistanceFound = 0; 374 for (int y = 0; y < _rows; y++) { 375 for (int x = 0; x < _cols; x++) { 376 if (cellGroup(x, y) != group) 377 continue; 378 if (y == 0) 379 return 1; // right below 380 if (this[x, y - 1] != EMPTY) // check only lowest cell of group 381 continue; 382 int dist = 0; 383 for (int d = 1; y - d >= 0; d++) { 384 if (this[x, y - d] == EMPTY) { 385 dist = d + 1; 386 } else { 387 // reached non-empty cell 388 if (cellGroup(x, y - d) == group) { 389 // non-empty cell of the same group after empty space - ignore 390 dist = 0; 391 } 392 break; 393 } 394 } 395 if (dist > 0) { 396 // found some empty space below 397 if (minDistanceFound == 0 || minDistanceFound > dist) 398 minDistanceFound = dist; 399 } 400 } 401 } 402 if (minDistanceFound == 0) 403 return 1; 404 return minDistanceFound; 405 } 406 407 /// mark cells in _cellGroups[] matrix which can fall down (value > 0 is distance to fall) 408 bool markFallingCells() { 409 // clear cellGroups matrix 410 if (_cellGroups.length != _cols * _rows) { 411 _cellGroups = new int[_cols * _rows]; 412 } else { 413 foreach(ref cell; _cellGroups) 414 cell = 0; 415 } 416 // find and mark all groups 417 int groupId = 1; 418 for (int y = 0; y < _rows; y++) { 419 for (int x = 0; x < _cols; x++) { 420 if (this[x, y] != EMPTY && cellGroup(x, y) == 0) { 421 fillCellGroup(x, y, groupId); 422 groupId++; 423 } 424 } 425 } 426 // check space below each group - can it fall down? 427 int[] spaceBelowGroup = new int[groupId]; 428 static if (true) { 429 for (int i = 1; i < groupId; i++) 430 spaceBelowGroup[i] = distanceToOccupiedCellBelowForGroup(i); 431 } else { 432 for (int y = 0; y < _rows; y++) { 433 for (int x = 0; x < _cols; x++) { 434 int group = cellGroup(x, y); 435 if (group > 0) { 436 if (y == 0) 437 spaceBelowGroup[group] = 1; 438 else if (this[x, y - 1] != EMPTY && cellGroup(x, y - 1) != group) 439 spaceBelowGroup[group] = 1; 440 else if (this[x, y - 1] == EMPTY) { 441 int dist = distanceToOccupiedCellBelow(x, y); 442 if (spaceBelowGroup[group] == 0 || spaceBelowGroup[group] > dist) 443 spaceBelowGroup[group] = dist; 444 } 445 } 446 } 447 } 448 } 449 // replace group IDs with distance to fall (0 == cell cannot fall) 450 for (int y = 0; y < _rows; y++) { 451 for (int x = 0; x < _cols; x++) { 452 int group = cellGroup(x, y); 453 if (group > 0) { 454 // distance to fall 455 setCellGroup(spaceBelowGroup[group] - 1, x, y); 456 } 457 } 458 } 459 bool canFall = false; 460 for (int i = 1; i < groupId; i++) 461 if (spaceBelowGroup[i] > 1) 462 canFall = true; 463 return canFall; 464 } 465 466 /// moves all falling cells one cell down 467 /// returns true if there are more cells to fall 468 bool moveFallingCells() { 469 bool res = false; 470 for (int y = 0; y < _rows - 1; y++) { 471 for (int x = 0; x < _cols; x++) { 472 int dist = cellGroup(x, y + 1); 473 if (dist > 0) { 474 // move cell down, decreasing distance 475 setCellGroup(dist - 1, x, y); 476 this[x, y] = this[x, y + 1]; 477 setCellGroup(0, x, y + 1); 478 this[x, y + 1] = EMPTY; 479 if (dist > 1) 480 res = true; 481 } 482 } 483 } 484 return res; 485 } 486 487 /// return true if cell is currently falling 488 bool isCellFalling(int col, int row) { 489 return cellGroup(col, row) > 0; 490 } 491 492 /// returns true if next figure is generated 493 @property bool hasNextFigure() { 494 return !_nextFigure.empty; 495 } 496 } 497