tetris: complete game logic
Stefan Schuermans

Stefan Schuermans commited on 2019-07-15 19:04:46
Showing 5 changed files, with 131 additions and 16 deletions.

... ...
@@ -40,14 +40,18 @@ Tetris::Tetris(const std::string &name, Mgrs &mgrs, const Directory &dirBase):
40 40
   m_fileDelay(dirBase.getFile("delay")),
41 41
   m_fileDropDelay(dirBase.getFile("dropDelay")),
42 42
   m_fileBlinkDelay(dirBase.getFile("blinkDelay")),
43
+  m_fileGameOverDelay(dirBase.getFile("gameOverDelay")),
43 44
   m_fileStartSound(dirBase.getFile("startSound")),
45
+  m_fileRowCompleteSound(dirBase.getFile("rowCompleteSound")),
46
+  m_fileGameOverSound(dirBase.getFile("gameOverSound")),
44 47
   m_stoneColor(),
45 48
   m_delay(c_delayDescr.default_),
46 49
   m_dropDelay(c_dropDelayDescr.default_),
47 50
   m_blinkDelay(c_blinkDelayDescr.default_),
51
+  m_gameOverDelay(c_gameOverDelayDescr.default_),
48 52
   m_pConn(NULL),
49 53
   m_stone(-1), m_rot(-1), m_posX(-1), m_posY(-1),
50
-  m_dropping(false), m_blinking(0),
54
+  m_dropping(false), m_blinking(0), m_completed(0), m_gameOver(0),
51 55
   m_field(), m_rowsBlink()
52 56
 {
53 57
   // open operator connection interfaces for player
... ...
@@ -86,9 +90,15 @@ bool Tetris::updateConfigGame()
86 90
   if (valueUpdate(m_fileBlinkDelay, c_blinkDelayDescr, m_blinkDelay)) {
87 91
     ret = true;
88 92
   }
93
+  if (valueUpdate(m_fileGameOverDelay, c_gameOverDelayDescr,
94
+                  m_gameOverDelay)) {
95
+    ret = true;
96
+  }
89 97
 
90 98
   // sound name file was modified -> re-read sound name, no other update needed
91 99
   soundUpdate(m_fileStartSound);
100
+  soundUpdate(m_fileRowCompleteSound);
101
+  soundUpdate(m_fileGameOverSound);
92 102
 
93 103
   return ret;
94 104
 }
... ...
@@ -151,8 +161,8 @@ void Tetris::opConnRecvKey(OpConn *pConn, char key)
151 161
   }
152 162
 
153 163
   /** normal keys for controlling game,
154
-      deactivated if dropping stone or rows blinking */
155
-  if (m_dropping || m_blinking > 0) {
164
+      deactivated if dropping stone, rows blinking or end of game */
165
+  if (m_dropping || m_blinking > 0 || m_gameOver > 0) {
156 166
     return;
157 167
   }
158 168
 
... ...
@@ -254,6 +264,7 @@ void Tetris::reinitialize()
254 264
   valueFromFile(m_fileDelay, c_delayDescr, m_delay);
255 265
   valueFromFile(m_fileDropDelay, c_dropDelayDescr, m_dropDelay);
256 266
   valueFromFile(m_fileBlinkDelay, c_blinkDelayDescr, m_blinkDelay);
267
+  valueFromFile(m_fileGameOverDelay, c_gameOverDelayDescr, m_gameOverDelay);
257 268
 
258 269
   // initialize field: empty
259 270
   m_field.clear();
... ...
@@ -263,6 +274,11 @@ void Tetris::reinitialize()
263 274
   m_rowsBlink.clear();
264 275
   m_rowsBlink.resize(m_height, false);
265 276
 
277
+  // no rows blinking or completed, game not over yet
278
+  m_blinking = 0;
279
+  m_completed = 0;
280
+  m_gameOver = 0;
281
+
266 282
   // start with new stone
267 283
   newStone();
268 284
 
... ...
@@ -300,9 +316,43 @@ void Tetris::redraw()
300 316
 /// process next time step of game
301 317
 void Tetris::timeStep()
302 318
 {
319
+  // count time at end of game
320
+  if (m_gameOver > 0) {
321
+    timeGameOver();
303 322
   // blinking of completed rows
304
-  if (m_blinking > 0) {
323
+  } else if (m_blinking > 0) {
324
+    timeBlinkRows();
325
+  // falling stone
326
+  } else {
327
+    timeStone();
328
+  }
329
+
330
+  // request next time step
331
+  planTimeStep();
332
+}
333
+
334
+/// count time at end of game
335
+void Tetris::timeGameOver()
336
+{
337
+  // count time
338
+  ++m_gameOver;
305 339
 
340
+  // terminate game after some time
341
+  if (m_gameOver >= 8) {
342
+    // close operator connection
343
+    if (m_pConn) {
344
+      forgetOpConn(m_pConn); // remove from requests (if it was in)
345
+      m_pConn->close();
346
+      m_pConn = NULL;
347
+    }
348
+    // deactivate game
349
+    deactivate();
350
+  }
351
+}
352
+
353
+/// blink completed rows
354
+void Tetris::timeBlinkRows()
355
+{
306 356
   // blink rows
307 357
   ++m_blinking;
308 358
 
... ...
@@ -333,10 +383,11 @@ void Tetris::timeStep()
333 383
 
334 384
   // redraw image and send frame
335 385
   redraw();
386
+}
336 387
 
337
-  // falling stone
338
-  } else {
339
-
388
+/// falling stone
389
+void Tetris::timeStone()
390
+{
340 391
   // stone can move down by one pixel
341 392
   if (checkStoneFit(m_stone, m_rot, m_posY + 1, m_posX)) {
342 393
     // move stone down by one pixel
... ...
@@ -350,19 +401,27 @@ void Tetris::timeStep()
350 401
     // add stone permanently to field at current position
351 402
     freezeStone(m_stone, m_rot, m_posY, m_posX);
352 403
     drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: frozen color
404
+
405
+    // overflow of game field -> game over
406
+    if (checkStoneOverflow(m_stone, m_rot, m_posY, m_posX)) {
407
+      // unset stone
408
+      m_stone = -1;
409
+      // game over
410
+      m_gameOver = 1;
411
+      playOpConnSound(m_pConn, m_fileGameOverSound);
412
+    }
413
+
414
+    // no overflow
415
+    else {
353 416
       // no current stone any more
354 417
       m_stone = -1;
355 418
       // check for completed rows, (afterwards: new stone)
356 419
       checkComplete();
357 420
     }
421
+  }
358 422
 
359 423
   // send updated image buffer as frame
360 424
   sendFrame();
361
-
362
-  } // falling stone
363
-
364
-  // request next time step
365
-  planTimeStep();
366 425
 }
367 426
 
368 427
 /// check for completed rows to disappear (new stone afterwards)
... ...
@@ -391,6 +450,7 @@ void Tetris::checkComplete()
391 450
   // start blinking (start with rows visible, last bit == 0)
392 451
   else {
393 452
     m_blinking = 2;
453
+    playOpConnSound(m_pConn, m_fileRowCompleteSound);
394 454
   }
395 455
 }
396 456
 
... ...
@@ -420,12 +480,17 @@ void Tetris::planTimeStep()
420 480
 
421 481
   // compute interval based on game state
422 482
   int interval_ms = m_delay;
423
-  if (m_dropping) {
424
-    interval_ms = m_dropDelay;
483
+  int speedup = m_completed;
484
+  if (m_gameOver > 0) {
485
+    interval_ms = m_gameOverDelay;
486
+    speedup = 0;
425 487
   } else if (m_blinking > 0) {
426 488
     interval_ms = m_blinkDelay;
489
+  } else if (m_dropping) {
490
+    interval_ms = m_dropDelay;
427 491
   }
428
-  float interval = 1e-3f * interval_ms;
492
+  float scale = 0.3f + 0.7f * expf(-0.3f * speedup);
493
+  float interval = 1e-3f * interval_ms * scale;
429 494
 
430 495
   // request next time call
431 496
   Time stepTime;
... ...
@@ -471,6 +536,29 @@ bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const
471 536
   return true;
472 537
 }
473 538
 
539
+/// check if stone overflow game field
540
+bool Tetris::checkStoneOverflow(int stone, int rot, int y, int x) const
541
+{
542
+  // get rotation of stone
543
+  RotStone const *rotStone = getRotStone(stone, rot);
544
+  if (! rotStone) {
545
+    return true; // invalid stone or rotation -> overflow
546
+  }
547
+
548
+  // check pixels for overflow
549
+  for (int p = 0; p < c_pixelCnt; ++p) {
550
+    int py = y + rotStone->pixels[p].y;
551
+    int px = x + rotStone->pixels[p].x;
552
+    if (! checkLimitInt(py, 0, m_height -1) ||
553
+        ! checkLimitInt(px, 0, m_width - 1)) {
554
+      return true; // outside field (including top) -> overflow
555
+    }
556
+  }
557
+
558
+  // all checks passed -> no overflow
559
+  return false;
560
+}
561
+
474 562
 /// freeze stone to field at position
475 563
 void Tetris::freezeStone(int stone, int rot, int y, int x)
476 564
 {
... ...
@@ -592,5 +680,8 @@ Tetris::ValueDescr const Tetris::c_dropDelayDescr = { 100, 50, 250 };
592 680
 /// descriptor for delay value during blinking of disappearing rows
593 681
 Tetris::ValueDescr const Tetris::c_blinkDelayDescr = { 50, 50, 250 };
594 682
 
683
+/// descriptor for delay value at end of game
684
+Tetris::ValueDescr const Tetris::c_gameOverDelayDescr = { 2000, 100, 5000 };
685
+
595 686
 } // namespace Blinker
596 687
 
... ...
@@ -121,6 +121,15 @@ protected:
121 121
   /// process next time step of game
122 122
   virtual void timeStep();
123 123
 
124
+  /// count time at end of game
125
+  void timeGameOver();
126
+
127
+  /// blink completed rows
128
+  void timeBlinkRows();
129
+
130
+  /// falling stone
131
+  void timeStone();
132
+
124 133
   /// check for completed rows to disappear (new stone afterwards)
125 134
   void checkComplete();
126 135
 
... ...
@@ -136,6 +145,9 @@ protected:
136 145
   /// check if stone fits at position
137 146
   bool checkStoneFit(int stone, int rot, int y, int x) const;
138 147
 
148
+  /// check if stone overflow game field
149
+  bool checkStoneOverflow(int stone, int rot, int y, int x) const;
150
+
139 151
   /// freeze stone to field at position
140 152
   void freezeStone(int stone, int rot, int y, int x);
141 153
 
... ...
@@ -165,17 +177,24 @@ protected:
165 177
   /// descriptor for delay value during blinking of disappearing rows
166 178
   static ValueDescr const c_blinkDelayDescr;
167 179
 
180
+  /// descriptor for delay value at end of game
181
+  static ValueDescr const c_gameOverDelayDescr;
182
+
168 183
   ColorFile m_fileStoneColor;    ///< color file for stone color
169 184
   UIntFile  m_fileDelay;         ///< file for initial delay in ms per frame
170 185
   UIntFile  m_fileDropDelay;     ///< file for delay while dropping (ms/frame)
171 186
   UIntFile  m_fileBlinkDelay;    ///< file for delay while blinking (ms/frame)
187
+  UIntFile  m_fileGameOverDelay; ///< file for delay at end (ms/frame)
172 188
 
173 189
   NameFile m_fileStartSound;       ///< "start game" sound name file
190
+  NameFile m_fileRowCompleteSound; ///< "row complete" sound name file
191
+  NameFile m_fileGameOverSound;    ///< "game over" sound name file
174 192
 
175 193
   ColorData    m_stoneColor;    ///< stone color
176 194
   unsigned int m_delay;         ///< initial delay in ms per frame
177 195
   unsigned int m_dropDelay;     ///< delay while dropping in ms per frame
178
-  unsigned int m_blinkDelay; ///< delay while rwos blinking in ms per frame
196
+  unsigned int m_blinkDelay;    ///< delay while rows blinking in ms per frame
197
+  unsigned int m_gameOverDelay; ///< delay at end of game in ms per frame
179 198
 
180 199
   OpConn *m_pConn; ///< operator connection of player (or NULL)
181 200
 
... ...
@@ -185,6 +204,8 @@ protected:
185 204
   int   m_posY;      ///< y position of current stone
186 205
   bool  m_dropping;  ///< whether currently dropping a stone
187 206
   int   m_blinking;  ///< step of blinking: 0 not blinking, lsb == 0 -> visible
207
+  int   m_completed; ///< number of overall completed rows
208
+  int   m_gameOver;  ///< counter at end of game: 0 means game is running
188 209
 
189 210
   /// tetris field (y * m_width + x), -1 for free, >= 0 for pixel from stone
190 211
   std::vector<int> m_field;
191 212