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