c913a5bbb95946174b09b349bf48267fd5a456c0
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
77)   if (colorUpdate(m_fileStoneColor, m_stoneColor) ||
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

78)       valueUpdate(m_fileDelay, c_delayDescr, m_delay) ||
Stefan Schuermans tetris: add blink delay

Stefan Schuermans authored 5 years ago

79)       valueUpdate(m_fileDropDelay, c_dropDelayDescr, m_dropDelay) ||
80)       valueUpdate(m_fileBlinkDelay, c_blinkDelayDescr, m_blinkDelay)) {
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

147)   /** normal keys for controlling game,
148)       deactivated if dropping stone */
149)   if (m_dropping) {
150)     return;
151)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

152) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

153)   // move left
154)   if (key == '4') {
155)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX - 1)) {
156)       wipeStone(m_stone, m_rot, m_posY, m_posX);
157)       m_posX -= 1;
158)       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

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

Stefan Schuermans authored 5 years ago

160)     }
161)     return;
162)   }
163) 
164)   // move right
165)   if (key == '6') {
166)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX + 1)) {
167)       wipeStone(m_stone, m_rot, m_posY, m_posX);
168)       m_posX += 1;
169)       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

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

Stefan Schuermans authored 5 years ago

171)     }
172)     return;
173)   }
174) 
175)   // rotate left
176)   if (key == '1') {
177)     int new_rot = m_rot - 1;
178)     if (new_rot < 0) {
179)       new_rot = c_rotCnt - 1;
180)     }
181)     if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) {
182)       wipeStone(m_stone, m_rot, m_posY, m_posX);
183)       m_rot = new_rot;
184)       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

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

Stefan Schuermans authored 5 years ago

186)     }
187)     return;
188)   }
189) 
190)   // rotate right
191)   if (key == '2' || key == '3') {
192)     int new_rot = m_rot + 1;
193)     if (new_rot >= c_rotCnt) {
194)       new_rot = 0;
195)     }
196)     if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) {
197)       wipeStone(m_stone, m_rot, m_posY, m_posX);
198)       m_rot = new_rot;
199)       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

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

Stefan Schuermans authored 5 years ago

201)     }
202)     return;
203)   }
204) 
205)   // drop stone
206)   if (key == '8') {
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

209)     return;
210)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

323) 
324)   // stone is not being dropped yet
325)   m_dropping = false;
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

326) }
327) 
328) /// set time for next time step of game - or unset if not needed
329) void Tetris::planTimeStep()
330) {
331)   // no time call needed if not active
332)   if (! isActive()) {
333)     unsetTimeStep();
334)     return;
335)   }
336) 
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

338)   int interval_ms = m_delay;
339)   if (m_dropping) {
340)     interval_ms = m_dropDelay;
341)   } else if (m_blinking > 0) {
342)     interval_ms = m_blinkDelay;
343)   }
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

345) 
346)   // request next time call
347)   Time stepTime;
348)   stepTime.fromFloatSec(interval);
349)   setTimeStep(Time::now() + stepTime);
350) }
351) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

362) /// check if stone fits at position
363) bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const
364) {
365)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

368)     return false; // invalid stone or rotation -> does not fit
369)   }
370) 
371)   // check pixels
372)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

378)     if (py >= 0) { // do not check above top
379)       int pi = py * m_width + px;
380)       if (m_field.at(pi) >= 0) {
381)         return false; // occupixed pixel -> does not fit
382)       }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

383)     }
384)   }
385) 
386)   // all checks passed -> stone fits
387)   return true;
388) }
389) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

430)     return; // invalid stone or rotation -> nothing to do
431)   }
432) 
433)   // color pixels
434)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

504) 
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

508) /// descriptor for delay value during blinking of disappearing rows
509) Tetris::ValueDescr const Tetris::c_blinkDelayDescr = { 50, 50, 250 };
510)