846b225579f6552a55c826265240793e6f42bbb0
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
Stefan Schuermans tetris: output frame on sto...

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

148)     }
149)     return;
150)   }
151) 
152)   // move right
153)   if (key == '6') {
154)     if (checkStoneFit(m_stone, m_rot, m_posY, m_posX + 1)) {
155)       wipeStone(m_stone, m_rot, m_posY, m_posX);
156)       m_posX += 1;
157)       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

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

Stefan Schuermans authored 5 years ago

159)     }
160)     return;
161)   }
162) 
163)   // rotate left
164)   if (key == '1') {
165)     int new_rot = m_rot - 1;
166)     if (new_rot < 0) {
167)       new_rot = c_rotCnt - 1;
168)     }
169)     if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) {
170)       wipeStone(m_stone, m_rot, m_posY, m_posX);
171)       m_rot = new_rot;
172)       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

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

Stefan Schuermans authored 5 years ago

174)     }
175)     return;
176)   }
177) 
178)   // rotate right
179)   if (key == '2' || key == '3') {
180)     int new_rot = m_rot + 1;
181)     if (new_rot >= c_rotCnt) {
182)       new_rot = 0;
183)     }
184)     if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) {
185)       wipeStone(m_stone, m_rot, m_posY, m_posX);
186)       m_rot = new_rot;
187)       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

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

Stefan Schuermans authored 5 years ago

189)     }
190)     return;
191)   }
192) 
193)   // drop stone
194)   if (key == '8') {
195)     // TODO
196)     return;
197)   }
Stefan Schuermans tetris game WIP

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

340) /// check if stone fits at position
341) bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const
342) {
343)   // get rotation of stone
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

361)     }
362)   }
363) 
364)   // all checks passed -> stone fits
365)   return true;
366) }
367) 
Stefan Schuermans tetris WIP: move/freeze stones

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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