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 const Figure[7] FIGURES = [ 73 // FIGURE1 =========================================== 74 // ## #### 75 // 00## 00## 76 // ## 77 Figure([FigureShape([1, 0], [ 1, 1], [0,-1]), 78 FigureShape([0, 1], [-1, 1], [1, 0]), 79 FigureShape([1, 0], [ 1, 1], [0,-1]), 80 FigureShape([0, 1], [-1, 1], [1, 0])]), 81 // FIGURE2 =========================================== 82 // ## #### 83 // 00## ##00 84 // ## 85 Figure([FigureShape([1, 0], [0, 1], [ 1,-1]), 86 FigureShape([0, 1], [1, 1], [-1, 0]), 87 FigureShape([1, 0], [0, 1], [ 1,-1]), 88 FigureShape([0, 1], [1, 1], [-1, 0])]), 89 // FIGURE3 =========================================== 90 // ## ## #### 91 // ##00## 00 ##00## 00 92 // ## #### ## 93 Figure([FigureShape([1, 0], [-1, 0], [-1,-1]), 94 FigureShape([0, 1], [ 0,-1], [ 1,-1]), 95 FigureShape([1, 0], [-1, 0], [ 1, 1]), 96 FigureShape([0, 1], [-1, 1], [ 0,-1])]), 97 // FIGURE4 =========================================== 98 // #### ## ## 99 // ##00## 00 ##00## 00 100 // ## ## #### 101 Figure([FigureShape([1, 0], [-1, 0], [ 1,-1]), 102 FigureShape([0, 1], [ 0,-1], [ 1, 1]), 103 FigureShape([1, 0], [-1, 0], [-1, 1]), 104 FigureShape([0, 1], [-1,-1], [ 0,-1])]), 105 // FIGURE5 =========================================== 106 // #### 107 // 00## 108 // 109 Figure([FigureShape([1, 0], [0, 1], [ 1, 1]), 110 FigureShape([1, 0], [0, 1], [ 1, 1]), 111 FigureShape([1, 0], [0, 1], [ 1, 1]), 112 FigureShape([1, 0], [0, 1], [ 1, 1])]), 113 // FIGURE6 =========================================== 114 // ## 115 // ## 116 // 00 ##00#### 117 // ## 118 Figure([FigureShape([0, 1], [0, 2], [ 0,-1]), 119 FigureShape([1, 0], [2, 0], [-1, 0]), 120 FigureShape([0, 1], [0, 2], [ 0,-1]), 121 FigureShape([1, 0], [2, 0], [-1, 0])]), 122 // FIGURE7 =========================================== 123 // ## ## ## 124 // ##00## 00## ##00## ##00 125 // ## ## ## 126 Figure([FigureShape([1, 0], [-1,0], [ 0,-1]), 127 FigureShape([0, 1], [0,-1], [ 1, 0]), 128 FigureShape([1, 0], [-1,0], [ 0, 1]), 129 FigureShape([0, 1], [0,-1], [-1, 0])]), 130 ]; 131 132 /// colors for different figure types 133 const uint[7] _figureColors = [0xC00000, 0x80A000, 0xA00080, 0x0000C0, 0x800020, 0x408000, 0x204000]; 134 135 /// Figure type, orientation and position container 136 struct FigurePosition { 137 int index; 138 int orientation; 139 int x; 140 int y; 141 this(int index, int orientation, int x, int y) { 142 this.index = index; 143 this.orientation = orientation; 144 this.x = x; 145 this.y = y; 146 } 147 /// return rotated position CCW for angle=1, CW for angle=-1 148 FigurePosition rotate(int angle) { 149 int newOrientation = (orientation + 4 + angle) & 3; 150 return FigurePosition(index, newOrientation, x, y); 151 } 152 /// return moved position 153 FigurePosition move(int dx, int dy = 0) { 154 return FigurePosition(index, orientation, x + dx, y + dy); 155 } 156 /// return shape for figure orientation 157 @property FigureShape shape() const { 158 return FIGURES[index - 1].shapes[orientation]; 159 } 160 /// return color for figure 161 @property uint color() const { 162 return _figureColors[index - 1]; 163 } 164 /// return true if figure index is not initialized 165 @property empty() const { 166 return index == 0; 167 } 168 /// clears content 169 void reset() { 170 index = 0; 171 } 172 } 173 174 /** 175 Cup content 176 177 Coordinates are relative to bottom left corner. 178 */ 179 struct Cup { 180 private int[] _cup; 181 private int _cols; 182 private int _rows; 183 private bool[] _destroyedFullRows; 184 private int[] _cellGroups; 185 186 private FigurePosition _currentFigure; 187 /// current figure index, orientation, position 188 @property ref FigurePosition currentFigure() { return _currentFigure; } 189 190 private FigurePosition _nextFigure; 191 /// next figure 192 @property ref FigurePosition nextFigure() { return _nextFigure; } 193 194 /// returns number of columns 195 @property int cols() { 196 return _cols; 197 } 198 /// returns number of columns 199 @property int rows() { 200 return _rows; 201 } 202 /// inits empty cup of specified size 203 void init(int cols, int rows) { 204 _cols = cols; 205 _rows = rows; 206 _cup = new int[_cols * _rows]; 207 _destroyedFullRows = new bool[_rows]; 208 _cellGroups = new int[_cols * _rows]; 209 } 210 /// returns cell content at specified position 211 int opIndex(int col, int row) { 212 if (col < 0 || row < 0 || col >= _cols || row >= _rows) 213 return WALL; 214 return _cup[row * _cols + col]; 215 } 216 /// set cell value 217 void opIndexAssign(int value, int col, int row) { 218 if (col < 0 || row < 0 || col >= _cols || row >= _rows) 219 return; // ignore modification of cells outside cup 220 _cup[row * _cols + col] = value; 221 } 222 /// put current figure into cup at current position and orientation 223 void putFigure() { 224 FigureShape shape = _currentFigure.shape; 225 foreach(cell; shape.cells) { 226 this[_currentFigure.x + cell.dx, _currentFigure.y + cell.dy] = _currentFigure.index; 227 } 228 } 229 230 /// check if all cells where specified figure is located are free 231 bool isPositionFree(in FigurePosition pos) { 232 FigureShape shape = pos.shape; 233 foreach(cell; shape.cells) { 234 int value = this[pos.x + cell.dx, pos.y + cell.dy]; 235 if (value != 0) // occupied 236 return false; 237 } 238 return true; 239 } 240 /// returns true if specified row is full 241 bool isRowFull(int row) { 242 for (int i = 0; i < _cols; i++) 243 if (this[i, row] == EMPTY) 244 return false; 245 return true; 246 } 247 /// returns true if at least one row is full 248 @property bool hasFullRows() { 249 for (int i = 0; i < _rows; i++) 250 if (isRowFull(i)) 251 return true; 252 return false; 253 } 254 /// destroy all full rows, saving flags for destroyed rows; returns count of destroyed rows, 0 if no rows destroyed 255 int destroyFullRows() { 256 int res = 0; 257 for (int i = 0; i < _rows; i++) { 258 if (isRowFull(i)) { 259 _destroyedFullRows[i] = true; 260 res++; 261 for (int col = 0; col < _cols; col++) 262 this[col, i] = EMPTY; 263 } else { 264 _destroyedFullRows[i] = false; 265 } 266 } 267 return res; 268 } 269 270 /// check if all cells where current figire is located are free 271 bool isPositionFree() { 272 return isPositionFree(_currentFigure); 273 } 274 275 /// check if all cells where current figire is located are free 276 bool isPositionFreeBelow() { 277 return isPositionFree(_currentFigure.move(0, -1)); 278 } 279 280 /// try to rotate current figure, returns true if figure rotated 281 bool rotate(int angle, bool falling) { 282 FigurePosition newpos = _currentFigure.rotate(angle); 283 if (isPositionFree(newpos)) { 284 if (falling) { 285 // special handling for fall animation 286 if (!isPositionFree(newpos.move(0, -1))) { 287 if (isPositionFreeBelow()) 288 return false; 289 } 290 } 291 _currentFigure = newpos; 292 return true; 293 } else if (isPositionFree(newpos.move(0, -1))) { 294 _currentFigure = newpos.move(0, -1); 295 return true; 296 } 297 return false; 298 } 299 300 /// try to move current figure, returns true if figure rotated 301 bool move(int deltaX, int deltaY, bool falling) { 302 FigurePosition newpos = _currentFigure.move(deltaX, deltaY); 303 if (isPositionFree(newpos)) { 304 if (falling && !isPositionFree(newpos.move(0, -1))) { 305 if (isPositionFreeBelow()) 306 return false; 307 } 308 _currentFigure = newpos; 309 return true; 310 } 311 return false; 312 } 313 314 /// random next figure 315 void genNextFigure() { 316 _nextFigure.index = uniform(FIGURE1, FIGURE7 + 1); 317 _nextFigure.orientation = ORIENTATION0; 318 _nextFigure.x = _cols / 2; 319 _nextFigure.y = _rows - _nextFigure.shape.extent + 1; 320 } 321 322 /// New figure: put it on top of cup 323 bool dropNextFigure() { 324 if (_nextFigure.empty) 325 genNextFigure(); 326 _currentFigure = _nextFigure; 327 _currentFigure.x = _cols / 2; 328 _currentFigure.y = _rows - 1 - _currentFigure.shape.y0; 329 return isPositionFree(); 330 } 331 332 /// get cell group / falling cell value 333 private int cellGroup(int col, int row) { 334 if (col < 0 || row < 0 || col >= _cols || row >= _rows) 335 return 0; 336 return _cellGroups[col + row * _cols]; 337 } 338 339 /// set cell group / falling cells value 340 private void setCellGroup(int value, int col, int row) { 341 _cellGroups[col + row * _cols] = value; 342 } 343 344 /// recursive fill occupied area of cells with group id 345 private void fillCellGroup(int x, int y, int value) { 346 if (x < 0 || y < 0 || x >= _cols || y >= _rows) 347 return; 348 if (this[x, y] != EMPTY && cellGroup(x, y) == 0) { 349 setCellGroup(value, x, y); 350 fillCellGroup(x + 1, y, value); 351 fillCellGroup(x - 1, y, value); 352 fillCellGroup(x, y + 1, value); 353 fillCellGroup(x, y - 1, value); 354 } 355 } 356 357 /// 1 == next cell below is occupied, 2 == one empty cell 358 private int distanceToOccupiedCellBelow(int col, int row) { 359 for (int y = row - 1; y >= -1; y--) { 360 if (this[col, y] != EMPTY) 361 return row - y; 362 } 363 return 1; 364 } 365 366 /// mark cells in _cellGroups[] matrix which can fall down (value > 0 is distance to fall) 367 bool markFallingCells() { 368 _cellGroups = new int[_cols * _rows]; 369 int groupId = 1; 370 for (int y = 0; y < _rows; y++) { 371 for (int x = 0; x < _cols; x++) { 372 if (this[x, y] != EMPTY && cellGroup(x, y) == 0) { 373 fillCellGroup(x, y, groupId); 374 groupId++; 375 } 376 } 377 } 378 // check space below each group - can it fall down? 379 int[] spaceBelowGroup = new int[groupId]; 380 for (int y = 0; y < _rows; y++) { 381 for (int x = 0; x < _cols; x++) { 382 int group = cellGroup(x, y); 383 if (group > 0) { 384 if (y == 0) 385 spaceBelowGroup[group] = 1; 386 else if (this[x, y - 1] != EMPTY && cellGroup(x, y - 1) != group) 387 spaceBelowGroup[group] = 1; 388 else if (this[x, y - 1] == EMPTY) { 389 int dist = distanceToOccupiedCellBelow(x, y); 390 if (spaceBelowGroup[group] == 0 || spaceBelowGroup[group] > dist) 391 spaceBelowGroup[group] = dist; 392 } 393 } 394 } 395 } 396 // replace group IDs with distance to fall (0 == cell cannot fall) 397 for (int y = 0; y < _rows; y++) { 398 for (int x = 0; x < _cols; x++) { 399 int group = cellGroup(x, y); 400 if (group > 0) { 401 // distance to fall 402 setCellGroup(spaceBelowGroup[group] - 1, x, y); 403 } 404 } 405 } 406 bool canFall = false; 407 for (int i = 1; i < groupId; i++) 408 if (spaceBelowGroup[i] > 1) 409 canFall = true; 410 return canFall; 411 } 412 413 /// moves all falling cells one cell down 414 /// returns true if there are more cells to fall 415 bool moveFallingCells() { 416 bool res = false; 417 for (int y = 0; y < _rows - 1; y++) { 418 for (int x = 0; x < _cols; x++) { 419 int dist = cellGroup(x, y + 1); 420 if (dist > 0) { 421 // move cell down, decreasing distance 422 setCellGroup(dist - 1, x, y); 423 this[x, y] = this[x, y + 1]; 424 setCellGroup(0, x, y + 1); 425 this[x, y + 1] = EMPTY; 426 if (dist > 1) 427 res = true; 428 } 429 } 430 } 431 return res; 432 } 433 434 /// return true if cell is currently falling 435 bool isCellFalling(int col, int row) { 436 return cellGroup(col, row) > 0; 437 } 438 439 /// returns true if next figure is generated 440 @property bool hasNextFigure() { 441 return !_nextFigure.empty; 442 } 443 } 444