e897205a2f2e4ef710f8b48d0a3b8c7e93cd8632
Stefan Schuermans base class for games

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) 
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

6) #include <map>
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

7) #include <set>
Stefan Schuermans pong: display score

Stefan Schuermans authored 5 years ago

8) #include <sstream>
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

9) #include <string>
10) #include <vector>
11) 
12) #include <BlinkenLib/BlinkenFrame.h>
13) 
14) #include "File.h"
15) #include "Format.h"
16) #include "FormatFile.h"
17) #include "Game.h"
18) #include "Mgrs.h"
19) #include "Module.h"
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

20) #include "NameFile.h"
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

21) #include "OutStreamFile.h"
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

22) #include "UIntFile.h"
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

23) 
24) namespace Blinker {
25) 
26) /**
27)  * @brief constructor
28)  * @param[in] name module name
29)  * @param[in] mgrs managers
30)  * @param[in] dirBase base directory
31)  */
32) Game::Game(const std::string &name, Mgrs &mgrs, const Directory &dirBase):
33)   Module(name, mgrs, dirBase),
34)   m_fileFormat(dirBase.getFile("format")),
35)   m_fileBackgroundColor(dirBase.getFile("backgroundColor")),
36)   m_fileOutStream(dirBase.getFile("outstream"), mgrs.m_streamMgr),
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

37)   m_height(0), m_width(0), m_channels(0), m_imgBuf(), m_backgroundColor(),
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

38)   m_haveTimeStep(false), m_timeStepTime(), m_opConnSounds(), m_opConnsClose()
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

39) {
40) }
41) 
42) /// virtual destructor
43) Game::~Game()
44) {
45)   // clean up
46)   deactivate();
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

47) 
48)   // cancel time callback request
49)   m_mgrs.m_callMgr.cancelTimeCall(this);
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

50) }
51) 
52) /// check for update of configuration
53) void Game::updateConfig()
54) {
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

55)   bool doReinit = false;
56)   bool doRedraw = false;
57) 
58)   // format file was modified -> re-create canvas and schedule redraw
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

59)   if (m_fileFormat.checkModified()) {
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

60)     m_fileFormat.update();
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

61)     createImgBuf();
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

62)     doReinit = true;
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

63)   }
64) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

65)   // color file was modified -> convert color, schedule redraw
66)   if (colorUpdate(m_fileBackgroundColor, m_backgroundColor)) {
67)     doRedraw = true;
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

68)   }
69) 
70)   // output stream name file was modified -> re-get output stream
71)   if (m_fileOutStream.checkModified()) {
72)     m_fileOutStream.update();
73)   }
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

74) 
75)   // check config update of derived game
Stefan Schuermans change of some game params...

Stefan Schuermans authored 5 years ago

76)   updateConfigGame(doReinit, doRedraw);
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

77) 
78)   // re-initialize / redraw
79)   if (doReinit) {
80)     reinitialize();
81)     doRedraw = true;
82)   }
83)   if (doRedraw) {
84)     redraw();
85)   }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

86) }
87) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

88) /// callback when requested time reached
89) void Game::timeCall()
90) {
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

91)   // play sounds on operator connections
92)   for (auto & opConnSound : m_opConnSounds) {
93)     playOpConnSound(opConnSound.first, *opConnSound.second);
94)   }
95)   m_opConnSounds.clear();
96) 
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

97)   // close operator connections
98)   for (OpConn *pConn : m_opConnsClose) {
99)     pConn->close();
100)   }
101)   m_opConnsClose.clear();
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

102) 
103)   // time step of game has been reached -> call game
104)   if (m_haveTimeStep && Time::now() >= m_timeStepTime) {
105)     m_haveTimeStep = false;
106)     timeStep();
107)   }
108) 
109)   // plan next time call - if any
110)   planTimeCall();
111) }
112) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

113) /// activate game: set up image buffer, call redraw()
114) void Game::activate()
115) {
116)   createImgBuf();
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

117)   reinitialize();
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

118)   redraw();
119) }
120) 
121) /// deactivate game: tear down image buffer, deactivate output
122) void Game::deactivate()
123) {
124)   destroyImgBuf();
125)   sendFrame();
126) }
127) 
Stefan Schuermans pong: activate when player...

Stefan Schuermans authored 5 years ago

128) /// check if game is active
129) bool Game::isActive() const
130) {
131)   return ! m_imgBuf.empty(); // game is active if there is an image buffer
132) }
133) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

134) /// set pixel in image buffer
135) void Game::pixel(int y, int x, ColorData const &cd)
136) {
137)   if (m_imgBuf.empty() ||
138)       ! checkLimitInt(y, 0, m_height - 1) ||
139)       ! checkLimitInt(x, 0, m_width - 1)) {
140)     return;
141)   }
142)   int pos = (y * m_width + x) * m_channels;
143)   std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
144) }
145) 
146) /// draw horizontal line to image buffer
147) void Game::lineHor(int y, int x1, int x2, ColorData const &cd)
148) {
149)   if (m_imgBuf.empty() ||
150)       ! checkLimitInt(y, 0, m_height - 1) ||
151)       ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) {
152)     return;
153)   }
154)   int pos = (y * m_width + x1) * m_channels;
155)   int dx = m_channels;
156)   for (int x = x1; x <= x2; ++x) {
157)     std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
158)     pos += dx;
159)   }
160) }
161) 
162) /// draw vertical line to image buffer
163) void Game::lineVert(int y1, int y2, int x, ColorData const &cd)
164) {
165)   if (m_imgBuf.empty() ||
166)       ! checkIntRangeLimit(y1, y2, 0, m_height - 1) ||
167)       ! checkLimitInt(x, 0, m_width - 1)) {
168)     return;
169)   }
170)   int pos = (y1 * m_width + x) * m_channels;
171)   int dy = m_width * m_channels;
172)   for (int y = y1; y <= y2; ++y) {
173)     std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
174)     pos += dy;
175)   }
176) }
177) 
178) /// draw non-filled rectangle to image buffer
179) void Game::rect(int y1, int y2, int x1, int x2, ColorData const &cd)
180) {
181)   lineHor(y1, x1, x2, cd);
182)   lineHor(y2, x1, x2, cd);
183)   lineVert(y1, y2, x1, cd);
184)   lineVert(y1, y2, x2, cd);
185) }
186) 
187) /// draw filled rectangle to image buffer
188) void Game::rectFill(int y1, int y2, int x1, int x2, ColorData const &cd)
189) {
190)   if (m_imgBuf.empty() ||
191)       ! checkIntRangeLimit(y1, y2, 0, m_height - 1) ||
192)       ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) {
193)     return;
194)   }
195)   int pos = (y1 * m_width + x1) * m_channels;
196)   int dx = m_channels;
197)   int dy = m_width - (x2 - x1 + 1) * m_channels;
198)   for (int y = y1; y <= y2; ++y) {
199)     for (int x = x1; x <= x2; ++x) {
200)       std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
201)       pos += dx;
202)     }
203)     pos += dy;
204)   }
205) }
206) 
Stefan Schuermans pong: display score

Stefan Schuermans authored 5 years ago

207) /**
208)  * @brief draw a small digit (3x5 pixels) to image buffer
209)  * @param[in] topY y coordinate of top of digit
210)  * @param[in] leftX x coordinate of left of digit
211)  * @param[in] digit to draw ('0'..'9')
212)  * @param[in] cd color to use for drawing
213)  */
214) void Game::digit3x5(int topY, int leftX, char digit, ColorData const &cd)
215) {
216)   /**
217)    * digit data in octal,
218)    * each row is 3 bits, msb of row is left,
219)    * msb row is top
220)    */
221)   static const unsigned int digitData[10] = {
222)     075557, 022222, 071747, 071717, 055711,
223)     074717, 074757, 071111, 075757, 075717
224)   };
225) 
226)   if (digit < '0' || digit > '9') {
227)     return;
228)   }
229)   int idx = digit - '0';
230)   unsigned int data = digitData[idx];
231)   for (int y = topY; y < topY + 5; ++y) {
232)     for (int x = leftX; x < leftX + 3; ++x) {
233)       data <<= 1;
234)       if (data & 0100000) {
235)         pixel(y, x, cd);
236)       }
237)     }
238)   }
239) }
240) 
241) /**
242)  * @brief draw small digits (3x5 each, 1 pixel gap) to image buffer
243)  * @param[in] topY y coordinate of top of digits
244)  * @param[in] leftX x coordinate of left of digits
245)  * @param[in] digits to draw (string of '0'..'9')
246)  * @param[in] cd color to use for drawing
247)  */
248) void Game::digits3x5(int topY, int leftX, std::string const &digits,
249)                      ColorData const &cd)
250) {
251)   for (char digit: digits) {
252)     digit3x5(topY, leftX, digit, cd);
253)     leftX += 4;
254)   }
255) }
256) 
257) /**
258)  * @brief draw number using 3x5 digits to image buffer
259)  * @param[in] anchorY y coordinate of anchor point
260)  * @param[in] anchorX x coordinate of anchor point
261)  * @param[in] alignY y alignment, -1 for top, 0 for center, 1 for bottom
262)  * @param[in] alignX x alignment, -1 for left, 0 for center, 1 for right
263)  * @param[in] number non-negative number to draw
264)  * @param[in] cd color to use for drawing
265)  */
266) void Game::number3x5(int anchorY, int anchorX, int alignY, int alignX,
267)                      int number, ColorData const &cd)
268) {
269)   // convert number to digits string
270)   if (number < 0) {
271)     return;
272)   }
273)   std::stringstream digitsStrm;
274)   digitsStrm << number;
275)   std::string const &digits = digitsStrm.str();
276) 
277)   // compute top left position
278)   int topY, leftX;
279)   switch (alignY) {
280)   case -1: topY = anchorY; break;
281)   case 0: topY = anchorY - 2; break;
282)   case 1: topY = anchorY - 4; break;
283)   default: return;
284)   }
285)   switch (alignX) {
286)   case -1: leftX = anchorX; break;
287)   case 0: leftX = anchorX - 2 * digits.length() + 1; break;
288)   case 1: leftX = anchorX - 4 * digits.length() + 2; break;
289)   default: return;
290)   }
291) 
292)   // draw digits
293)   digits3x5(topY, leftX, digits, cd);
294) }
295) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

296) /// process update of color file, return true on update
297) bool Game::colorUpdate(ColorFile &colorFile, ColorData &data) const
298) {
299)   if (colorFile.checkModified()) {
300)     colorFile.update();
301)     color2data(colorFile, data);
302)     return true;
303)   } else {
304)     return false;
305)   }
306) }
307) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

308) /// convert color to raw color data
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

309) void Game::color2data(ColorFile const &colorFile, ColorData &data) const
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

310) {
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

311)   if (! m_fileFormat.m_valid || ! colorFile.m_valid) {
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

312)     data.clear();
313)   } else {
314)     unsigned int channels = m_fileFormat.m_obj.m_channels;
315)     unsigned int maxval = m_fileFormat.m_obj.m_maxval;
316)     data.resize(m_fileFormat.m_obj.m_channels);
317)     Color const &color = colorFile.m_obj;
318)     if (channels == 1) {
319)       // single channel
320)       // convert to monochrome according to CIE XYZ
321)       double val = 0.2125 * color.m_red + 0.7154 * color.m_green
322)                                         + 0.0721 * color.m_blue;
323)       // adapt to maxval and round
324)       data.at(0) = (unsigned char)(val * maxval / 255.0 + 0.5);
325)     } else if (channels == 2) {
326)       // two channels
327)       // adapt to maxval and round, ignore blue
328)       data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5);
329)       data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5);
330)     } else if (channels >= 3) {
331)       // three channels (more than three channels: further channels are dark)
332)       // adapt to maxval and round
333)       data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5);
334)       data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5);
335)       data.at(2) = (unsigned char)(color.m_blue * maxval / 255.0 + 0.5);
336)     }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

337)   }
338) }
339) 
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

340) /// process update of value file, return true on update
341) bool Game::valueUpdate(UIntFile &valueFile, ValueDescr const &descr,
342)                        unsigned int &value)
343) {
344)   if (valueFile.checkModified()) {
345)     valueFile.update();
346)     valueFromFile(valueFile, descr, value);
347)     return true;
348)   } else {
349)     return false;
350)   }
351) }
352) 
353) /// get value from value file
354) void Game::valueFromFile(UIntFile &valueFile, ValueDescr const &descr,
355)                          unsigned int &value)
356) {
357)   if (! valueFile.m_valid) {
358)     value = descr.default_;
359)   } else {
360)     value = valueFile.m_obj.m_uint;
361)     if (value < descr.minimum) {
362)       value = descr.minimum;
363)     }
364)     else if (value > descr.maximum) {
365)       value = descr.maximum;
366)     }
367)   }
368) }
369) 
Stefan Schuermans sound support for pong game

Stefan Schuermans authored 5 years ago

370) /// process update of sound name file
371) void Game::soundUpdate(NameFile &soundFile)
372) {
373)   if (soundFile.checkModified()) {
374)     soundFile.update();
375)   }
376) }
377) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

378) /// send current image buffer as frame to output stream
379) void Game::sendFrame()
380) {
381)   // image buffer available -> send its contents as frame
382)   if (! m_imgBuf.empty()) {
383)     Format const &fmt = m_fileFormat.m_obj;
384)     stBlinkenFrame *pFrame = BlinkenFrameNew(fmt.m_height, fmt.m_width,
385)                                              fmt.m_channels, fmt.m_maxval, 1);
386)     BlinkenFrameSetPixelData(pFrame, 0, m_height, 0, m_width, 0, m_channels,
387)                              m_imgBuf.data());
388)     m_fileOutStream.setFrame(pFrame);
389)     BlinkenFrameFree(pFrame);
390)   }
391)   // no image buffer available -> send "no frame" information
392)   else {
393)     m_fileOutStream.setFrame(NULL);
394)   }
395) }
396) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

397) /// set next game time step - plan timed action of game
398) void Game::setTimeStep(const Time& timeStepTime)
399) {
400)   m_haveTimeStep = true;
401)   m_timeStepTime = timeStepTime;
402)   planTimeCall();
403) }
404) 
405) /// unset game time step - do timed action for game
406) void Game::unsetTimeStep()
407) {
408)   m_haveTimeStep = false;
409)   planTimeCall();
410) }
411) 
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

412) /// play a sound on operator connection (NULL ok) (direct)
413) void Game::playOpConnSound(OpConn *pConn, NameFile const &soundFile)
414) {
415)   if (pConn && soundFile.m_valid) {
416)     pConn->sendPlay(soundFile.m_obj.m_str);
417)   }
418) }
419) 
420) //// request playing a sound on operator connection (NULL ok) (via time call)
421) void Game::requestOpConnSound(OpConn *pConn, NameFile const &soundFile)
422) {
423)   if (pConn) {
424)     m_opConnSounds[pConn] = &soundFile;
Stefan Schuermans add time call for sound req

Stefan Schuermans authored 5 years ago

425)     planTimeCall();
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

426)   }
427) }
428) 
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

429) /// request closing operator connection (NULL ok) (closed via time call)
430) void Game::requestOpConnClose(OpConn *pConn)
431) {
432)   if (pConn) {
433)     m_opConnsClose.insert(pConn);
434)     planTimeCall();
435)   }
436) }
437) 
438) /// remove operator connection from requests (call when op conn is closed)
439) void Game::forgetOpConn(OpConn *pConn)
440) {
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

441)   m_opConnSounds.erase(pConn);
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

442)   m_opConnsClose.erase(pConn);
443)   planTimeCall();
444) }
445) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

446) /// (re-)create image buffer
447) void Game::createImgBuf()
448) {
449)   // get rid of old image buffer
450)   destroyImgBuf();
451) 
452)   // read format from format file
453)   m_fileFormat.update();
454)   if (! m_fileFormat.m_valid)
455)     return;
456) 
457)   // read background color
458)   m_fileBackgroundColor.update();
459)   if (! m_fileBackgroundColor.m_valid)
460)     return;
461) 
462)   // store parameters
463)   m_height = m_fileFormat.m_obj.m_height;
464)   m_width = m_fileFormat.m_obj.m_width;
465)   m_channels = m_fileFormat.m_obj.m_channels;
466) 
467)   // create image buffer
468)   m_imgBuf.resize(m_height * m_width * m_channels);
469) 
470)   // convert background color
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

471)   color2data(m_fileBackgroundColor, m_backgroundColor);
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

472) }
473) 
474) /// tear down image buffer
475) void Game::destroyImgBuf()
476) {
477)   m_imgBuf.clear();
478)   m_backgroundColor.clear();
479) }
480) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

481) /// request next time call - or cancel request if not needed
482) void Game::planTimeCall()
483) {
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

484)   // request immediate time call if there are internal time-based actions
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

485)   if (! m_opConnSounds.empty() || ! m_opConnsClose.empty()) {
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

486)     m_mgrs.m_callMgr.requestTimeCall(this, Time::now());
487)     return;
488)   }
489) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

490)   // no time step requested by game
491)   if (! m_haveTimeStep) {
492)     m_mgrs.m_callMgr.cancelTimeCall(this);
493)     return;
494)   }
495) 
496)   // request next time call
497)   m_mgrs.m_callMgr.requestTimeCall(this, m_timeStepTime);
498) }
499)