tetris game WIP
Stefan Schuermans

Stefan Schuermans commited on 2019-07-14 17:51:57
Showing 3 changed files, with 565 additions and 0 deletions.

... ...
@@ -0,0 +1,388 @@
1
+/* Blinker
2
+   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#include <cmath>
7
+#include <stdlib.h>
8
+#include <string>
9
+#include <vector>
10
+
11
+#include <BlinkenLib/BlinkenFrame.h>
12
+
13
+#include "File.h"
14
+#include "Format.h"
15
+#include "FormatFile.h"
16
+#include "Game.h"
17
+#include "Mgrs.h"
18
+#include "Module.h"
19
+#include "NameFile.h"
20
+#include "OpConn.h"
21
+#include "OpConnIf.h"
22
+#include "OpReqIf.h"
23
+#include "OutStreamFile.h"
24
+#include "Tetris.h"
25
+#include "Time.h"
26
+#include "TimeCallee.h"
27
+#include "UIntFile.h"
28
+
29
+namespace Blinker {
30
+
31
+/**
32
+ * @brief constructor
33
+ * @param[in] name module name
34
+ * @param[in] mgrs managers
35
+ * @param[in] dirBase base directory
36
+ */
37
+Tetris::Tetris(const std::string &name, Mgrs &mgrs, const Directory &dirBase):
38
+  Game(name, mgrs, dirBase),
39
+  m_fileStoneColor(dirBase.getFile("stoneColor")),
40
+  m_fileDelay(dirBase.getFile("delay")),
41
+  m_fileStartSound(dirBase.getFile("startSound")),
42
+  m_stoneColor(),
43
+  m_delay(c_delayDescr.default_),
44
+  m_pConn(NULL),
45
+  m_stone(-1), m_rot(-1), m_posX(-1), m_posY(-1), m_field()
46
+{
47
+  // open operator connection interfaces for player
48
+  m_mgrs.m_opMgr.open(m_name, this);
49
+}
50
+
51
+/// virtual destructor
52
+Tetris::~Tetris()
53
+{
54
+  // close operator connection interface
55
+  m_mgrs.m_opMgr.close(m_name);
56
+
57
+  // close open operator connection
58
+  if (m_pConn) {
59
+    m_pConn->close();
60
+    m_pConn = NULL;
61
+  }
62
+}
63
+
64
+/// check for update of configuration (derived game), return true on update
65
+bool Tetris::updateConfigGame()
66
+{
67
+  bool ret = false;
68
+
69
+  // color file was modified -> convert color, return true for update
70
+  // cfg value file was updated -> read new cfg value, return true for update
71
+  if (colorUpdate(m_fileStoneColor, m_stoneColor) ||
72
+      valueUpdate(m_fileDelay, c_delayDescr, m_delay)) {
73
+    ret = true;
74
+  }
75
+
76
+  // sound name file was modified -> re-read sound name, no other update needed
77
+  soundUpdate(m_fileStartSound);
78
+
79
+  return ret;
80
+}
81
+
82
+/**
83
+ * @brief check if accepting new operator connection is possible
84
+ * @param[in] name operator interface name
85
+ * @return if accepting new connection is possible
86
+ */
87
+bool Tetris::acceptNewOpConn(const std::string &name)
88
+{
89
+  (void)name;
90
+
91
+  // accept player if no one there yet
92
+  return ! m_pConn;
93
+}
94
+
95
+/**
96
+ * @brief new operator connection
97
+ * @param[in] name operator interface name
98
+ * @param[in] pConn operator connection object
99
+ *
100
+ * The new connection may not yet be used for sending inside this callback.
101
+ */
102
+void Tetris::newOpConn(const std::string &name, OpConn *pConn)
103
+{
104
+  (void)name;
105
+
106
+  // player arrives and starts game
107
+  if (! m_pConn) {
108
+    m_pConn = pConn;
109
+    requestOpConnSound(m_pConn, m_fileStartSound);
110
+    activate();
111
+  }
112
+  // close imcoming connection as soon as possible, nothing else happens
113
+  else {
114
+    requestOpConnClose(pConn);
115
+    return;
116
+  }
117
+}
118
+
119
+/**
120
+ * @brief key command received on operator connection
121
+ * @param[in] pConn operator connection object
122
+ * @param[in] key key that was pressed
123
+ */
124
+void Tetris::opConnRecvKey(OpConn *pConn, char key)
125
+{
126
+  // hash -> hang up
127
+  if (key == '#') {
128
+    opConnClose(pConn);
129
+    pConn->close();
130
+    return;
131
+  }
132
+
133
+  // star -> inform player about game
134
+  if (key == '*') {
135
+    playOpConnSound(pConn, m_fileStartSound);
136
+    return;
137
+  }
138
+
139
+  // normal keys for controlling game
140
+
141
+  // TODO
142
+}
143
+
144
+/**
145
+ * @brief play command received on operator connection
146
+ * @param[in] pConn operator connection object
147
+ * @param[in] sound name of sound to play
148
+ */
149
+void Tetris::opConnRecvPlay(OpConn *pConn, const std::string &sound)
150
+{
151
+  (void)pConn;
152
+  (void)sound;
153
+}
154
+
155
+/**
156
+ * @brief operator connection is closed
157
+ * @param[in] pConn operator connection object
158
+ *
159
+ * The connection may not be used for sending any more in this callback.
160
+ */
161
+void Tetris::opConnClose(OpConn *pConn)
162
+{
163
+  // remove coperator connection from requests (if it was in)
164
+  forgetOpConn(pConn);
165
+
166
+  // player leaves -> deactivate game
167
+  if (pConn == m_pConn) {
168
+    m_pConn = NULL;
169
+    deactivate();
170
+  }
171
+}
172
+
173
+/// re-initialize game (e.g. due to config change)
174
+void Tetris::reinitialize()
175
+{
176
+  // convert colors
177
+  color2data(m_fileStoneColor, m_stoneColor);
178
+  // get values
179
+  valueFromFile(m_fileDelay, c_delayDescr, m_delay);
180
+
181
+  // initialize field: empty
182
+  m_field.clear();
183
+  m_field.resize(m_height * m_width, -1);
184
+
185
+  // start with new stone
186
+  newStone();
187
+
188
+  // redraw image and send frame
189
+  redraw();
190
+
191
+  // request first time step if needed
192
+  planTimeStep();
193
+}
194
+
195
+/// redraw current game image, expected to call sendFrame() at end
196
+void Tetris::redraw()
197
+{
198
+  // draw background
199
+  rectFill(0, m_height, 0, m_width, m_backgroundColor);
200
+
201
+  // draw fixed pixels
202
+  for (int y = 0, i = 0; y < m_height; ++y) {
203
+    for (int x = 0; x < m_width; ++x, ++i) {
204
+      if (m_field.at(i) >= 0) {
205
+        pixel(y, x, m_stoneColor);
206
+      }
207
+    }
208
+  }
209
+
210
+  // draw current stone
211
+  drawStone(m_stone, m_rot, m_posY, m_posX);
212
+
213
+  // send updated image buffer as frame
214
+  sendFrame();
215
+}
216
+
217
+/// process next time step of game
218
+void Tetris::timeStep()
219
+{
220
+  // FIXME
221
+  if (m_posY >= m_height + 2) {
222
+    newStone();
223
+  }
224
+  wipeStone(m_stone, m_rot, m_posY, m_posX);
225
+  m_posY += 1;
226
+  drawStone(m_stone, m_rot, m_posY, m_posX);
227
+
228
+  // send updated image buffer as frame
229
+  sendFrame();
230
+
231
+  // request next time step
232
+  planTimeStep();
233
+}
234
+
235
+/// set up a new stone
236
+void Tetris::newStone()
237
+{
238
+  // random stone, random rotation
239
+  m_stone = rand() % c_stoneCnt;
240
+  m_rot = rand() % c_rotCnt;
241
+
242
+  // postion: two pixels above top middle
243
+  m_posX = (m_width - 1) / 2;
244
+  m_posY = -2;
245
+}
246
+
247
+/// set time for next time step of game - or unset if not needed
248
+void Tetris::planTimeStep()
249
+{
250
+  // no time call needed if not active
251
+  if (! isActive()) {
252
+    unsetTimeStep();
253
+    return;
254
+  }
255
+
256
+  // compute interval based on score and bounce count
257
+  float interval = 1e-3f * m_delay;
258
+
259
+  // request next time call
260
+  Time stepTime;
261
+  stepTime.fromFloatSec(interval);
262
+  setTimeStep(Time::now() + stepTime);
263
+}
264
+
265
+/// check if stone fits at position
266
+bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const
267
+{
268
+  // get rotation of stone
269
+  if (! checkLimitInt(stone, 0, c_stoneCnt -1) ||
270
+      ! checkLimitInt(rot, 0, c_rotCnt - 1)) {
271
+    return false; // invalid stone or rotation -> does not fit
272
+  }
273
+  RotStone const &rotStone = c_stones[stone].rot[rot];
274
+
275
+  // check pixels
276
+  for (int p = 0; p < c_pixelCnt; ++p) {
277
+    int py = y + rotStone.pixels[p].y;
278
+    int px = x + rotStone.pixels[p].x;
279
+    if (py >= m_height - 1 || ! checkLimitInt(px, 0, m_width - 1)) {
280
+      return false; // outside field (except at top) -> does not fit
281
+    }
282
+    int pi = py *m_width + px;
283
+    if (m_field.at(pi) >= 0) {
284
+      return false; // occupixed pixel -> does not fit
285
+    }
286
+  }
287
+
288
+  // all checks passed -> stone fits
289
+  return true;
290
+}
291
+
292
+/// draw a stone to image buffer
293
+void Tetris::drawStone(int stone, int rot, int y, int x)
294
+{
295
+  colorStone(stone, rot, y, x, m_stoneColor);
296
+}
297
+
298
+/// wipe a stone from image buffer (i.e. replace it with background color)
299
+void Tetris::wipeStone(int stone, int rot, int y, int x)
300
+{
301
+  colorStone(stone, rot, y, x, m_backgroundColor);
302
+}
303
+
304
+/// set shape of stone to color in image buffer
305
+void Tetris::colorStone(int stone, int rot, int y, int x,
306
+                        ColorData const &color)
307
+{
308
+  // get rotation of stone
309
+  if (! checkLimitInt(stone, 0, c_stoneCnt -1) ||
310
+      ! checkLimitInt(rot, 0, c_rotCnt - 1)) {
311
+    return; // invalid stone or rotation -> nothing to do
312
+  }
313
+  RotStone const &rotStone = c_stones[stone].rot[rot];
314
+
315
+  // color pixels
316
+  for (int p = 0; p < c_pixelCnt; ++p) {
317
+    pixel(y + rotStone.pixels[p].y, x + rotStone.pixels[p].x, color);
318
+  }
319
+}
320
+
321
+/// stone data
322
+Tetris::Stone const Tetris::c_stones[7] = {
323
+  // the I
324
+  { {
325
+      { { { -2,  0 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
326
+      { { {  0, -2 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
327
+      { { { -2,  0 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
328
+      { { {  0, -2 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
329
+  } },
330
+  // the L
331
+  { {
332
+      { { {  1, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
333
+      { { {  0, -1 }, {  0,  0 }, {  0,  1 }, {  1,  1 } } },
334
+      { { { -1,  0 }, {  0,  0 }, {  1,  0 }, { -1,  1 } } },
335
+      { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
336
+  } },
337
+  // the J
338
+  { {
339
+      { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
340
+      { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  0,  1 } } },
341
+      { { { -1,  0 }, {  0,  0 }, {  1,  0 }, {  1,  1 } } },
342
+      { { {  0, -1 }, {  0,  0 }, { -1,  1 }, {  0,  1 } } },
343
+  } },
344
+  // the T
345
+  { {
346
+      { { {  0, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
347
+      { { {  0, -1 }, {  0,  0 }, {  1,  0 }, {  0,  1 } } },
348
+      { { { -1,  0 }, {  0,  0 }, {  1,  0 }, {  0,  1 } } },
349
+      { { {  0, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
350
+  } },
351
+  // the O
352
+  { {
353
+      { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
354
+      { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
355
+      { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
356
+      { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
357
+  } },
358
+  // the Z
359
+  { {
360
+      { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  1,  0 } } },
361
+      { { {  0, -1 }, { -1,  0 }, {  0,  0 }, { -1,  1 } } },
362
+      { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  1,  0 } } },
363
+      { { {  0, -1 }, { -1,  0 }, {  0,  0 }, { -1,  1 } } },
364
+  } },
365
+  // the S
366
+  { {
367
+      { { {  0, -1 }, {  1, -1 }, { -1,  0 }, {  0,  0 } } },
368
+      { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
369
+      { { {  0, -1 }, {  1, -1 }, { -1,  0 }, {  0,  0 } } },
370
+      { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
371
+  } },
372
+};
373
+
374
+/// number of stones
375
+int const Tetris::c_stoneCnt = sizeof(Tetris::c_stones) /
376
+                               sizeof(Tetris::c_stones[0]);
377
+/// number of rotations per stone
378
+int const Tetris::c_rotCnt = sizeof(Tetris::Stone::rot) /
379
+                             sizeof(Tetris::Stone::rot[0]);
380
+/// number of pixels per stone
381
+int const Tetris::c_pixelCnt = sizeof(Tetris::RotStone::pixels) /
382
+                               sizeof(Tetris::RotStone::pixels[0]);
383
+
384
+/// descriptor for delay value
385
+Tetris::ValueDescr const Tetris::c_delayDescr = { 200, 100, 500 };
386
+
387
+} // namespace Blinker
388
+
... ...
@@ -0,0 +1,175 @@
1
+/* Blinker
2
+   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#ifndef BLINKER_TETRIS_H
7
+#define BLINKER_TETRIS_H
8
+
9
+#include <string>
10
+#include <vector>
11
+
12
+#include <BlinkenLib/BlinkenFrame.h>
13
+
14
+#include "Color.h"
15
+#include "ColorFile.h"
16
+#include "File.h"
17
+#include "Format.h"
18
+#include "FormatFile.h"
19
+#include "Game.h"
20
+#include "Mgrs.h"
21
+#include "Module.h"
22
+#include "NameFile.h"
23
+#include "OpConn.h"
24
+#include "OpConnIf.h"
25
+#include "OpReqIf.h"
26
+#include "OutStreamFile.h"
27
+#include "Time.h"
28
+#include "TimeCallee.h"
29
+#include "UIntFile.h"
30
+
31
+namespace Blinker {
32
+
33
+/// tetris game
34
+class Tetris: public Game, public OpReqIf
35
+{
36
+protected:
37
+  /// coordinates of a pixel part of a stone
38
+  struct Coord {
39
+    int x;
40
+    int y;
41
+  };
42
+
43
+  /// descriptor of a certain rotation of a stone
44
+  struct RotStone {
45
+    Coord pixels[4]; ///< coordinates of the 4 pixels
46
+  };
47
+
48
+  /// descriptor of a stone
49
+  struct Stone {
50
+    RotStone rot[4]; ///< rotations of the stone
51
+  };
52
+
53
+public:
54
+  /**
55
+   * @brief constructor
56
+   * @param[in] name module name
57
+   * @param[in] mgrs managers
58
+   * @param[in] dirBase base directory
59
+   */
60
+  Tetris(const std::string &name, Mgrs &mgrs, const Directory &dirBase);
61
+
62
+  /// virtual destructor
63
+  virtual ~Tetris();
64
+
65
+private:
66
+  /// copy constructor disabled
67
+  Tetris(const Tetris &that);
68
+
69
+  /// assignment operator disabled
70
+  const Tetris & operator=(const Tetris &that);
71
+
72
+public:
73
+  /// check for update of configuration (derived game), return true on update
74
+  virtual bool updateConfigGame();
75
+
76
+  /**
77
+   * @brief check if accepting new operator connection is possible
78
+   * @param[in] name operator interface name
79
+   * @return if accepting new connection is possible
80
+   */
81
+  virtual bool acceptNewOpConn(const std::string &name);
82
+
83
+  /**
84
+   * @brief new operator connection
85
+   * @param[in] name operator interface name
86
+   * @param[in] pConn operator connection object
87
+   *
88
+   * The new connection may not yet be used for sending inside this callback.
89
+   */
90
+  virtual void newOpConn(const std::string &name, OpConn *pConn);
91
+
92
+  /**
93
+   * @brief key command received on operator connection
94
+   * @param[in] pConn operator connection object
95
+   * @param[in] key key that was pressed
96
+   */
97
+  virtual void opConnRecvKey(OpConn *pConn, char key);
98
+
99
+  /**
100
+   * @brief play command received on operator connection
101
+   * @param[in] pConn operator connection object
102
+   * @param[in] sound name of sound to play
103
+   */
104
+  virtual void opConnRecvPlay(OpConn *pConn, const std::string &sound);
105
+
106
+  /**
107
+   * @brief operator connection is closed
108
+   * @param[in] pConn operator connection object
109
+   *
110
+   * The connection may not be used for sending any more in this callback.
111
+   */
112
+  virtual void opConnClose(OpConn *pConn);
113
+
114
+protected:
115
+  /// re-initialize game (e.g. due to config change)
116
+  virtual void reinitialize();
117
+
118
+  /// redraw current game image, expected to call sendFrame() at end
119
+  virtual void redraw();
120
+
121
+  /// process next time step of game
122
+  virtual void timeStep();
123
+
124
+  /// set up a new stone
125
+  void newStone();
126
+
127
+  /// set time for next time step of game - or unset if not needed
128
+  void planTimeStep();
129
+
130
+  /// check if stone fits at position
131
+  bool checkStoneFit(int stone, int rot, int y, int x) const;
132
+
133
+  /// draw a stone to image buffer
134
+  void drawStone(int stone, int rot, int y, int x);
135
+
136
+  /// wipe a stone from image buffer (i.e. replace it with background color)
137
+  void wipeStone(int stone, int rot, int y, int x);
138
+
139
+  /// set shape of stone to color in image buffer
140
+  void colorStone(int stone, int rot, int y, int x, ColorData const &color);
141
+
142
+protected:
143
+  /// stone data
144
+  static Stone const c_stones[7];
145
+
146
+  static int const c_stoneCnt; ///< number of stones
147
+  static int const c_rotCnt;   ///< number of rotations per stone
148
+  static int const c_pixelCnt; ///< number of pixels per stone
149
+
150
+  /// descriptor for delay value
151
+  static ValueDescr const c_delayDescr;
152
+
153
+  ColorFile m_fileStoneColor; ///< color file for stone color
154
+  UIntFile  m_fileDelay;      ///< file for initial delay in ms per frame
155
+
156
+  NameFile m_fileStartSound; ///< "start game" sound name file
157
+
158
+  ColorData    m_stoneColor; ///< stone color
159
+  unsigned int m_delay;      ///< initial delay in ms per frame (ball speed)
160
+
161
+  OpConn *m_pConn; ///< operator connection of player (or NULL)
162
+
163
+  int   m_stone; ///< index of current stone
164
+  int   m_rot;   ///< rotation of current stone
165
+  int   m_posX;  ///< x position of current stone
166
+  int   m_posY;  ///< y position of current stone
167
+
168
+  /// tetris field (y * m_width + x), -1 for free, >= 0 for pixel from stone
169
+  std::vector<int> m_field;
170
+}; // class Tetris
171
+
172
+} // namespace Blinker
173
+
174
+#endif // #ifndef BLINKER_TETRIS_H
175
+
... ...
@@ -30,6 +30,7 @@
30 30
 #include "SipPhone.h"
31 31
 #include "SyncNameSplitter.h"
32 32
 #include "SyncPrinter.h"
33
+#include "Tetris.h"
33 34
 #include "Transformer.h"
34 35
 #include "Udp4Phone.h"
35 36
 #include "Udp4Receiver.h"
... ...
@@ -70,6 +71,7 @@ void run(const std::string &dirConfig)
70 71
   MODULEMGR(SipPhone,         sipphones);
71 72
   MODULEMGR(SyncNameSplitter, syncnamesplitters);
72 73
   MODULEMGR(SyncPrinter,      syncprinters);
74
+  MODULEMGR(Tetris,           tetrises);
73 75
   MODULEMGR(Transformer,      transformers);
74 76
   MODULEMGR(Udp4Phone,        udp4phones);
75 77
   MODULEMGR(Udp4Receiver,     udp4receivers);
76 78