75fe083f8e81e21ac074a83178e7bd0144c421ea
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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")),
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

41)   m_fileDropDelay(dirBase.getFile("dropDelay")),
Stefan Schuermans tetris: add blink delay

Stefan Schuermans authored 5 years ago

42)   m_fileBlinkDelay(dirBase.getFile("blinkDelay")),
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

43)   m_fileStartSound(dirBase.getFile("startSound")),
44)   m_stoneColor(),
45)   m_delay(c_delayDescr.default_),
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

46)   m_dropDelay(c_dropDelayDescr.default_),
Stefan Schuermans tetris: add blink delay

Stefan Schuermans authored 5 years ago

47)   m_blinkDelay(c_blinkDelayDescr.default_),
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

48)   m_pConn(NULL),
Stefan Schuermans tetris: add blink delay

Stefan Schuermans authored 5 years ago

49)   m_stone(-1), m_rot(-1), m_posX(-1), m_posY(-1),
50)   m_dropping(false), m_blinking(0),
51)   m_field(), m_rowsBlink()
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

52) {
53)   // open operator connection interfaces for player
54)   m_mgrs.m_opMgr.open(m_name, this);
55) }
56) 
57) /// virtual destructor
58) Tetris::~Tetris()
59) {
60)   // close operator connection interface
61)   m_mgrs.m_opMgr.close(m_name);
62) 
63)   // close open operator connection
64)   if (m_pConn) {
65)     m_pConn->close();
66)     m_pConn = NULL;
67)   }
68) }
69) 
70) /// check for update of configuration (derived game), return true on update
71) bool Tetris::updateConfigGame()
72) {
73)   bool ret = false;
74) 
75)   // color file was modified -> convert color, return true for update
76)   // cfg value file was updated -> read new cfg value, return true for update
Stefan Schuermans tetris: always check all co...

Stefan Schuermans authored 5 years ago

77)   if (colorUpdate(m_fileStoneColor, m_stoneColor)) {
78)     ret = true;
79)   }
80)   if (valueUpdate(m_fileDelay, c_delayDescr, m_delay)) {
81)     ret = true;
82)   }
83)   if (valueUpdate(m_fileDropDelay, c_dropDelayDescr, m_dropDelay)) {
84)     ret = true;
85)   }
86)   if (valueUpdate(m_fileBlinkDelay, c_blinkDelayDescr, m_blinkDelay)) {
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

87)     ret = true;
88)   }
89) 
90)   // sound name file was modified -> re-read sound name, no other update needed
91)   soundUpdate(m_fileStartSound);
92) 
93)   return ret;
94) }
95) 
96) /**
97)  * @brief check if accepting new operator connection is possible
98)  * @param[in] name operator interface name
99)  * @return if accepting new connection is possible
100)  */
101) bool Tetris::acceptNewOpConn(const std::string &name)
102) {
103)   (void)name;
104) 
105)   // accept player if no one there yet
106)   return ! m_pConn;
107) }
108) 
109) /**
110)  * @brief new operator connection
111)  * @param[in] name operator interface name
112)  * @param[in] pConn operator connection object
113)  *
114)  * The new connection may not yet be used for sending inside this callback.
115)  */
116) void Tetris::newOpConn(const std::string &name, OpConn *pConn)
117) {
118)   (void)name;
119) 
120)   // player arrives and starts game
121)   if (! m_pConn) {
122)     m_pConn = pConn;
123)     requestOpConnSound(m_pConn, m_fileStartSound);
124)     activate();
125)   }
126)   // close imcoming connection as soon as possible, nothing else happens
127)   else {
128)     requestOpConnClose(pConn);
129)     return;
130)   }
131) }
132) 
133) /**
134)  * @brief key command received on operator connection
135)  * @param[in] pConn operator connection object
136)  * @param[in] key key that was pressed
137)  */
138) void Tetris::opConnRecvKey(OpConn *pConn, char key)
139) {
140)   // hash -> hang up
141)   if (key == '#') {
142)     opConnClose(pConn);
143)     pConn->close();
144)     return;
145)   }
146) 
147)   // star -> inform player about game
148)   if (key == '*') {
149)     playOpConnSound(pConn, m_fileStartSound);
150)     return;
151)   }
152) 
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

153)   /** normal keys for controlling game,
154)       deactivated if dropping stone */
155)   if (m_dropping) {
156)     return;
157)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

158) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

159)   // move left
160)   if (key == '4') {
161)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX - 1)) {
162)       wipeStone(m_stone, m_rot, m_posY, m_posX);
163)       m_posX -= 1;
164)       drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
Stefan Schuermans tetris: output frame on sto...

Stefan Schuermans authored 5 years ago

165)       sendFrame();
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

166)     }
167)     return;
168)   }
169) 
170)   // move right
171)   if (key == '6') {
172)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX + 1)) {
173)       wipeStone(m_stone, m_rot, m_posY, m_posX);
174)       m_posX += 1;
175)       drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
Stefan Schuermans tetris: output frame on sto...

Stefan Schuermans authored 5 years ago

176)       sendFrame();
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

177)     }
178)     return;
179)   }
180) 
181)   // rotate left
182)   if (key == '1') {
183)     int new_rot = m_rot - 1;
184)     if (new_rot < 0) {
185)       new_rot = c_rotCnt - 1;
186)     }
187)     if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) {
188)       wipeStone(m_stone, m_rot, m_posY, m_posX);
189)       m_rot = new_rot;
190)       drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
Stefan Schuermans tetris: output frame on sto...

Stefan Schuermans authored 5 years ago

191)       sendFrame();
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

192)     }
193)     return;
194)   }
195) 
196)   // rotate right
197)   if (key == '2' || key == '3') {
198)     int new_rot = m_rot + 1;
199)     if (new_rot >= c_rotCnt) {
200)       new_rot = 0;
201)     }
202)     if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) {
203)       wipeStone(m_stone, m_rot, m_posY, m_posX);
204)       m_rot = new_rot;
205)       drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
Stefan Schuermans tetris: output frame on sto...

Stefan Schuermans authored 5 years ago

206)       sendFrame();
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

207)     }
208)     return;
209)   }
210) 
211)   // drop stone
212)   if (key == '8') {
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

213)     m_dropping = true;
214)     planTimeStep(); // stone falls fater now -> update time callback
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

215)     return;
216)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

217) }
218) 
219) /**
220)  * @brief play command received on operator connection
221)  * @param[in] pConn operator connection object
222)  * @param[in] sound name of sound to play
223)  */
224) void Tetris::opConnRecvPlay(OpConn *pConn, const std::string &sound)
225) {
226)   (void)pConn;
227)   (void)sound;
228) }
229) 
230) /**
231)  * @brief operator connection is closed
232)  * @param[in] pConn operator connection object
233)  *
234)  * The connection may not be used for sending any more in this callback.
235)  */
236) void Tetris::opConnClose(OpConn *pConn)
237) {
238)   // remove coperator connection from requests (if it was in)
239)   forgetOpConn(pConn);
240) 
241)   // player leaves -> deactivate game
242)   if (pConn == m_pConn) {
243)     m_pConn = NULL;
244)     deactivate();
245)   }
246) }
247) 
248) /// re-initialize game (e.g. due to config change)
249) void Tetris::reinitialize()
250) {
251)   // convert colors
252)   color2data(m_fileStoneColor, m_stoneColor);
253)   // get values
254)   valueFromFile(m_fileDelay, c_delayDescr, m_delay);
255) 
256)   // initialize field: empty
257)   m_field.clear();
258)   m_field.resize(m_height * m_width, -1);
259) 
260)   // start with new stone
261)   newStone();
262) 
263)   // redraw image and send frame
264)   redraw();
265) 
266)   // request first time step if needed
267)   planTimeStep();
268) }
269) 
270) /// redraw current game image, expected to call sendFrame() at end
271) void Tetris::redraw()
272) {
273)   // draw background
274)   rectFill(0, m_height, 0, m_width, m_backgroundColor);
275) 
276)   // draw fixed pixels
277)   for (int y = 0, i = 0; y < m_height; ++y) {
278)     for (int x = 0; x < m_width; ++x, ++i) {
279)       if (m_field.at(i) >= 0) {
280)         pixel(y, x, m_stoneColor);
281)       }
282)     }
283)   }
284) 
285)   // draw current stone
286)   drawStone(m_stone, m_rot, m_posY, m_posX);
287) 
288)   // send updated image buffer as frame
289)   sendFrame();
290) }
291) 
292) /// process next time step of game
293) void Tetris::timeStep()
294) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

295)   // stone can move down by one pixel
296)   if (checkStoneFit(m_stone, m_rot, m_posY + 1, m_posX)) {
297)     // move stone down by one pixel
298)     wipeStone(m_stone, m_rot, m_posY, m_posX);
299)     m_posY += 1;
300)     drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
301)   }
302) 
303)   // stone cannot move down by one pixel
304)   else {
305)     // add stone permanently to field at current position
306)     freezeStone(m_stone, m_rot, m_posY, m_posX);
307)     drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: frozen color
308)     // prepare new stone
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

309)     newStone();
310)   }
311) 
312)   // send updated image buffer as frame
313)   sendFrame();
314) 
315)   // request next time step
316)   planTimeStep();
317) }
318) 
319) /// set up a new stone
320) void Tetris::newStone()
321) {
322)   // random stone, random rotation
323)   m_stone = rand() % c_stoneCnt;
324)   m_rot = rand() % c_rotCnt;
325) 
326)   // postion: two pixels above top middle
327)   m_posX = (m_width - 1) / 2;
328)   m_posY = -2;
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

329) 
330)   // stone is not being dropped yet
331)   m_dropping = false;
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

332) }
333) 
334) /// set time for next time step of game - or unset if not needed
335) void Tetris::planTimeStep()
336) {
337)   // no time call needed if not active
338)   if (! isActive()) {
339)     unsetTimeStep();
340)     return;
341)   }
342) 
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

343)   // compute interval based on game state
Stefan Schuermans tetris: add blink delay

Stefan Schuermans authored 5 years ago

344)   int interval_ms = m_delay;
345)   if (m_dropping) {
346)     interval_ms = m_dropDelay;
347)   } else if (m_blinking > 0) {
348)     interval_ms = m_blinkDelay;
349)   }
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

350)   float interval = 1e-3f * interval_ms;
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

351) 
352)   // request next time call
353)   Time stepTime;
354)   stepTime.fromFloatSec(interval);
355)   setTimeStep(Time::now() + stepTime);
356) }
357) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

358) /// get rotatation of stone from stone/rotation index (or NULL in invalid)
359) Tetris::RotStone const * Tetris::getRotStone(int stone, int rot)
360) {
361)   if (! checkLimitInt(stone, 0, c_stoneCnt -1) ||
362)       ! checkLimitInt(rot, 0, c_rotCnt - 1)) {
363)     return NULL; // invalid stone or rotation
364)   }
365)   return &c_stones[stone].rot[rot];
366) }
367) 
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

368) /// check if stone fits at position
369) bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const
370) {
371)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

372)   RotStone const *rotStone = getRotStone(stone, rot);
373)   if (! rotStone) {
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

374)     return false; // invalid stone or rotation -> does not fit
375)   }
376) 
377)   // check pixels
378)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

379)     int py = y + rotStone->pixels[p].y;
380)     int px = x + rotStone->pixels[p].x;
381)     if (py > m_height - 1 || ! checkLimitInt(px, 0, m_width - 1)) {
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

382)       return false; // outside field (except at top) -> does not fit
383)     }
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

384)     if (py >= 0) { // do not check above top
385)       int pi = py * m_width + px;
386)       if (m_field.at(pi) >= 0) {
387)         return false; // occupixed pixel -> does not fit
388)       }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

389)     }
390)   }
391) 
392)   // all checks passed -> stone fits
393)   return true;
394) }
395) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

396) /// freeze stone to field at position
397) void Tetris::freezeStone(int stone, int rot, int y, int x)
398) {
399)   // get rotation of stone
400)   RotStone const *rotStone = getRotStone(stone, rot);
401)   if (! rotStone) {
402)     return; // invalid stone or rotation -> nothing to do
403)   }
404) 
405)   // add pixels to field
406)   for (int p = 0; p < c_pixelCnt; ++p) {
407)     int py = y + rotStone->pixels[p].y;
408)     int px = x + rotStone->pixels[p].x;
409)     if (checkLimitInt(py, 0, m_height - 1) &&
410)         checkLimitInt(px, 0, m_width - 1)) {
411)       int pi = py * m_width + px;
412)       m_field.at(pi) = stone; // mark pixel in field with stone index
413)     }
414)   }
415) }
416) 
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

417) /// draw a stone to image buffer
418) void Tetris::drawStone(int stone, int rot, int y, int x)
419) {
420)   colorStone(stone, rot, y, x, m_stoneColor);
421) }
422) 
423) /// wipe a stone from image buffer (i.e. replace it with background color)
424) void Tetris::wipeStone(int stone, int rot, int y, int x)
425) {
426)   colorStone(stone, rot, y, x, m_backgroundColor);
427) }
428) 
429) /// set shape of stone to color in image buffer
430) void Tetris::colorStone(int stone, int rot, int y, int x,
431)                         ColorData const &color)
432) {
433)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

434)   RotStone const *rotStone = getRotStone(stone, rot);
435)   if (! rotStone) {
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

436)     return; // invalid stone or rotation -> nothing to do
437)   }
438) 
439)   // color pixels
440)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

441)     pixel(y + rotStone->pixels[p].y, x + rotStone->pixels[p].x, color);
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

442)   }
443) }
444) 
445) /// stone data
446) Tetris::Stone const Tetris::c_stones[7] = {
447)   // the I
448)   { {
449)       { { { -2,  0 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
450)       { { {  0, -2 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
451)       { { { -2,  0 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
452)       { { {  0, -2 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
453)   } },
454)   // the L
455)   { {
456)       { { {  1, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
457)       { { {  0, -1 }, {  0,  0 }, {  0,  1 }, {  1,  1 } } },
458)       { { { -1,  0 }, {  0,  0 }, {  1,  0 }, { -1,  1 } } },
459)       { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
460)   } },
461)   // the J
462)   { {
463)       { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
464)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  0,  1 } } },
465)       { { { -1,  0 }, {  0,  0 }, {  1,  0 }, {  1,  1 } } },
466)       { { {  0, -1 }, {  0,  0 }, { -1,  1 }, {  0,  1 } } },
467)   } },
468)   // the T
469)   { {
470)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
471)       { { {  0, -1 }, {  0,  0 }, {  1,  0 }, {  0,  1 } } },
472)       { { { -1,  0 }, {  0,  0 }, {  1,  0 }, {  0,  1 } } },
473)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
474)   } },
475)   // the O
476)   { {
477)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
478)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
479)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
480)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
481)   } },
482)   // the Z
483)   { {
484)       { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  1,  0 } } },
485)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, { -1,  1 } } },
486)       { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  1,  0 } } },
487)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, { -1,  1 } } },
488)   } },
489)   // the S
490)   { {
491)       { { {  0, -1 }, {  1, -1 }, { -1,  0 }, {  0,  0 } } },
492)       { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
493)       { { {  0, -1 }, {  1, -1 }, { -1,  0 }, {  0,  0 } } },
494)       { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
495)   } },
496) };
497) 
498) /// number of stones
499) int const Tetris::c_stoneCnt = sizeof(Tetris::c_stones) /
500)                                sizeof(Tetris::c_stones[0]);
501) /// number of rotations per stone
502) int const Tetris::c_rotCnt = sizeof(Tetris::Stone::rot) /
503)                              sizeof(Tetris::Stone::rot[0]);
504) /// number of pixels per stone
505) int const Tetris::c_pixelCnt = sizeof(Tetris::RotStone::pixels) /
506)                                sizeof(Tetris::RotStone::pixels[0]);
507) 
508) /// descriptor for delay value
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

509) Tetris::ValueDescr const Tetris::c_delayDescr = { 400, 200, 1000 };
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

510) 
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

511) /// descriptor for delay value during dropping a stone
512) Tetris::ValueDescr const Tetris::c_dropDelayDescr = { 100, 50, 250 };
513) 
Stefan Schuermans tetris: add blink delay

Stefan Schuermans authored 5 years ago

514) /// descriptor for delay value during blinking of disappearing rows
515) Tetris::ValueDescr const Tetris::c_blinkDelayDescr = { 50, 50, 250 };
516)