7ab9f1ddf8dc59d06efe69fcd5aebc467a6059dd
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")),
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) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

141)   // move left
142)   if (key == '4') {
143)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX - 1)) {
144)       wipeStone(m_stone, m_rot, m_posY, m_posX);
145)       m_posX -= 1;
146)       drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
147)     }
148)     return;
149)   }
150) 
151)   // move right
152)   if (key == '6') {
153)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX + 1)) {
154)       wipeStone(m_stone, m_rot, m_posY, m_posX);
155)       m_posX += 1;
156)       drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
157)     }
158)     return;
159)   }
160) 
161)   // rotate left
162)   if (key == '1') {
163)     int new_rot = m_rot - 1;
164)     if (new_rot < 0) {
165)       new_rot = c_rotCnt - 1;
166)     }
167)     if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) {
168)       wipeStone(m_stone, m_rot, m_posY, m_posX);
169)       m_rot = new_rot;
170)       drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
171)     }
172)     return;
173)   }
174) 
175)   // rotate right
176)   if (key == '2' || key == '3') {
177)     int new_rot = m_rot + 1;
178)     if (new_rot >= c_rotCnt) {
179)       new_rot = 0;
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
185)     }
186)     return;
187)   }
188) 
189)   // drop stone
190)   if (key == '8') {
191)     // TODO
192)     return;
193)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

272)   // stone can move down by one pixel
273)   if (checkStoneFit(m_stone, m_rot, m_posY + 1, m_posX)) {
274)     // move stone down by one pixel
275)     wipeStone(m_stone, m_rot, m_posY, m_posX);
276)     m_posY += 1;
277)     drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color
278)   }
279) 
280)   // stone cannot move down by one pixel
281)   else {
282)     // add stone permanently to field at current position
283)     freezeStone(m_stone, m_rot, m_posY, m_posX);
284)     drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: frozen color
285)     // prepare new stone
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

286)     newStone();
287)   }
288) 
289)   // send updated image buffer as frame
290)   sendFrame();
291) 
292)   // request next time step
293)   planTimeStep();
294) }
295) 
296) /// set up a new stone
297) void Tetris::newStone()
298) {
299)   // random stone, random rotation
300)   m_stone = rand() % c_stoneCnt;
301)   m_rot = rand() % c_rotCnt;
302) 
303)   // postion: two pixels above top middle
304)   m_posX = (m_width - 1) / 2;
305)   m_posY = -2;
306) }
307) 
308) /// set time for next time step of game - or unset if not needed
309) void Tetris::planTimeStep()
310) {
311)   // no time call needed if not active
312)   if (! isActive()) {
313)     unsetTimeStep();
314)     return;
315)   }
316) 
317)   // compute interval based on score and bounce count
318)   float interval = 1e-3f * m_delay;
319) 
320)   // request next time call
321)   Time stepTime;
322)   stepTime.fromFloatSec(interval);
323)   setTimeStep(Time::now() + stepTime);
324) }
325) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

326) /// get rotatation of stone from stone/rotation index (or NULL in invalid)
327) Tetris::RotStone const * Tetris::getRotStone(int stone, int rot)
328) {
329)   if (! checkLimitInt(stone, 0, c_stoneCnt -1) ||
330)       ! checkLimitInt(rot, 0, c_rotCnt - 1)) {
331)     return NULL; // invalid stone or rotation
332)   }
333)   return &c_stones[stone].rot[rot];
334) }
335) 
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

336) /// check if stone fits at position
337) bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const
338) {
339)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

342)     return false; // invalid stone or rotation -> does not fit
343)   }
344) 
345)   // check pixels
346)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

352)     if (py >= 0) { // do not check above top
353)       int pi = py * m_width + px;
354)       if (m_field.at(pi) >= 0) {
355)         return false; // occupixed pixel -> does not fit
356)       }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

357)     }
358)   }
359) 
360)   // all checks passed -> stone fits
361)   return true;
362) }
363) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

364) /// freeze stone to field at position
365) void Tetris::freezeStone(int stone, int rot, int y, int x)
366) {
367)   // get rotation of stone
368)   RotStone const *rotStone = getRotStone(stone, rot);
369)   if (! rotStone) {
370)     return; // invalid stone or rotation -> nothing to do
371)   }
372) 
373)   // add pixels to field
374)   for (int p = 0; p < c_pixelCnt; ++p) {
375)     int py = y + rotStone->pixels[p].y;
376)     int px = x + rotStone->pixels[p].x;
377)     if (checkLimitInt(py, 0, m_height - 1) &&
378)         checkLimitInt(px, 0, m_width - 1)) {
379)       int pi = py * m_width + px;
380)       m_field.at(pi) = stone; // mark pixel in field with stone index
381)     }
382)   }
383) }
384) 
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

385) /// draw a stone to image buffer
386) void Tetris::drawStone(int stone, int rot, int y, int x)
387) {
388)   colorStone(stone, rot, y, x, m_stoneColor);
389) }
390) 
391) /// wipe a stone from image buffer (i.e. replace it with background color)
392) void Tetris::wipeStone(int stone, int rot, int y, int x)
393) {
394)   colorStone(stone, rot, y, x, m_backgroundColor);
395) }
396) 
397) /// set shape of stone to color in image buffer
398) void Tetris::colorStone(int stone, int rot, int y, int x,
399)                         ColorData const &color)
400) {
401)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

404)     return; // invalid stone or rotation -> nothing to do
405)   }
406) 
407)   // color pixels
408)   for (int p = 0; p < c_pixelCnt; ++p) {
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

410)   }
411) }
412) 
413) /// stone data
414) Tetris::Stone const Tetris::c_stones[7] = {
415)   // the I
416)   { {
417)       { { { -2,  0 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
418)       { { {  0, -2 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
419)       { { { -2,  0 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
420)       { { {  0, -2 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
421)   } },
422)   // the L
423)   { {
424)       { { {  1, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
425)       { { {  0, -1 }, {  0,  0 }, {  0,  1 }, {  1,  1 } } },
426)       { { { -1,  0 }, {  0,  0 }, {  1,  0 }, { -1,  1 } } },
427)       { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  0,  1 } } },
428)   } },
429)   // the J
430)   { {
431)       { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
432)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  0,  1 } } },
433)       { { { -1,  0 }, {  0,  0 }, {  1,  0 }, {  1,  1 } } },
434)       { { {  0, -1 }, {  0,  0 }, { -1,  1 }, {  0,  1 } } },
435)   } },
436)   // the T
437)   { {
438)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, {  1,  0 } } },
439)       { { {  0, -1 }, {  0,  0 }, {  1,  0 }, {  0,  1 } } },
440)       { { { -1,  0 }, {  0,  0 }, {  1,  0 }, {  0,  1 } } },
441)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
442)   } },
443)   // the O
444)   { {
445)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
446)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
447)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
448)       { { {  0, -1 }, {  1, -1 }, {  0,  0 }, {  1,  0 } } },
449)   } },
450)   // the Z
451)   { {
452)       { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  1,  0 } } },
453)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, { -1,  1 } } },
454)       { { { -1, -1 }, {  0, -1 }, {  0,  0 }, {  1,  0 } } },
455)       { { {  0, -1 }, { -1,  0 }, {  0,  0 }, { -1,  1 } } },
456)   } },
457)   // the S
458)   { {
459)       { { {  0, -1 }, {  1, -1 }, { -1,  0 }, {  0,  0 } } },
460)       { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
461)       { { {  0, -1 }, {  1, -1 }, { -1,  0 }, {  0,  0 } } },
462)       { { { -1, -1 }, { -1,  0 }, {  0,  0 }, {  0,  1 } } },
463)   } },
464) };
465) 
466) /// number of stones
467) int const Tetris::c_stoneCnt = sizeof(Tetris::c_stones) /
468)                                sizeof(Tetris::c_stones[0]);
469) /// number of rotations per stone
470) int const Tetris::c_rotCnt = sizeof(Tetris::Stone::rot) /
471)                              sizeof(Tetris::Stone::rot[0]);
472) /// number of pixels per stone
473) int const Tetris::c_pixelCnt = sizeof(Tetris::RotStone::pixels) /
474)                                sizeof(Tetris::RotStone::pixels[0]);
475) 
476) /// descriptor for delay value
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

477) Tetris::ValueDescr const Tetris::c_delayDescr = { 400, 200, 1000 };