b91f9323746cf5d5c626a83c1e12cd8226c01cce
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 game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

45)   m_dropDelay(c_dropDelayDescr.default_),
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

46)   m_pConn(NULL),
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

47)   m_stone(-1), m_rot(-1), m_posX(-1), m_posY(-1), m_dropping(false),
48)   m_field()
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

75)       valueUpdate(m_fileDelay, c_delayDescr, m_delay) ||
76)       valueUpdate(m_fileDropDelay, c_dropDelayDescr, m_dropDelay)) {
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

143)   /** normal keys for controlling game,
144)       deactivated if dropping stone */
145)   if (m_dropping) {
146)     return;
147)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

148) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

149)   // move left
150)   if (key == '4') {
151)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX - 1)) {
152)       wipeStone(m_stone, m_rot, m_posY, m_posX);
153)       m_posX -= 1;
154)       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

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

Stefan Schuermans authored 5 years ago

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

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

Stefan Schuermans authored 5 years ago

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

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

Stefan Schuermans authored 5 years ago

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

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

Stefan Schuermans authored 5 years ago

197)     }
198)     return;
199)   }
200) 
201)   // drop stone
202)   if (key == '8') {
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

205)     return;
206)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

319) 
320)   // stone is not being dropped yet
321)   m_dropping = false;
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

333)   // compute interval based on game state
334)   int interval_ms = m_dropping ? m_dropDelay : m_delay;
335)   float interval = 1e-3f * interval_ms;
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

336) 
337)   // request next time call
338)   Time stepTime;
339)   stepTime.fromFloatSec(interval);
340)   setTimeStep(Time::now() + stepTime);
341) }
342) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

343) /// get rotatation of stone from stone/rotation index (or NULL in invalid)
344) Tetris::RotStone const * Tetris::getRotStone(int stone, int rot)
345) {
346)   if (! checkLimitInt(stone, 0, c_stoneCnt -1) ||
347)       ! checkLimitInt(rot, 0, c_rotCnt - 1)) {
348)     return NULL; // invalid stone or rotation
349)   }
350)   return &c_stones[stone].rot[rot];
351) }
352) 
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

353) /// check if stone fits at position
354) bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const
355) {
356)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

359)     return false; // invalid stone or rotation -> does not fit
360)   }
361) 
362)   // check pixels
363)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

369)     if (py >= 0) { // do not check above top
370)       int pi = py * m_width + px;
371)       if (m_field.at(pi) >= 0) {
372)         return false; // occupixed pixel -> does not fit
373)       }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

374)     }
375)   }
376) 
377)   // all checks passed -> stone fits
378)   return true;
379) }
380) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

402) /// draw a stone to image buffer
403) void Tetris::drawStone(int stone, int rot, int y, int x)
404) {
405)   colorStone(stone, rot, y, x, m_stoneColor);
406) }
407) 
408) /// wipe a stone from image buffer (i.e. replace it with background color)
409) void Tetris::wipeStone(int stone, int rot, int y, int x)
410) {
411)   colorStone(stone, rot, y, x, m_backgroundColor);
412) }
413) 
414) /// set shape of stone to color in image buffer
415) void Tetris::colorStone(int stone, int rot, int y, int x,
416)                         ColorData const &color)
417) {
418)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

421)     return; // invalid stone or rotation -> nothing to do
422)   }
423) 
424)   // color pixels
425)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

495) 
Stefan Schuermans tetris: dropping stones

Stefan Schuermans authored 5 years ago

496) /// descriptor for delay value during dropping a stone
497) Tetris::ValueDescr const Tetris::c_dropDelayDescr = { 100, 50, 250 };
498)