ab0d0a082cc59ae70ee13a257a1a9ae0ea0bff2c
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"
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

18) #include "LockNameFile.h"
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

19) #include "Mgrs.h"
20) #include "Module.h"
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

38)   m_height(0), m_width(0), m_channels(0), m_imgBuf(), m_backgroundColor(),
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

39)   m_fileLockName(dirBase.getFile("lockName"), mgrs.m_lockMgr),
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

65)   }
66) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

76) 
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

77)   // process lock name updates
78)   m_fileLockName.updateIfModified();
79) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

82) 
83)   // re-initialize / redraw
84)   if (doReinit) {
85)     reinitialize();
86)     doRedraw = true;
87)   }
88)   if (doRedraw) {
89)     redraw();
90)   }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

91) }
92) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

93) /// callback when requested time reached
94) void Game::timeCall()
95) {
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

96)   // play sounds on operator connections
97)   for (auto & opConnSound : m_opConnSounds) {
98)     playOpConnSound(opConnSound.first, *opConnSound.second);
99)   }
100)   m_opConnSounds.clear();
101) 
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

102)   // close operator connections
103)   for (OpConn *pConn : m_opConnsClose) {
104)     pConn->close();
105)   }
106)   m_opConnsClose.clear();
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

107) 
108)   // time step of game has been reached -> call game
109)   if (m_haveTimeStep && Time::now() >= m_timeStepTime) {
110)     m_haveTimeStep = false;
111)     timeStep();
112)   }
113) 
114)   // plan next time call - if any
115)   planTimeCall();
116) }
117) 
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

118) /**
119)  * @brief check if game can be actiavted: if interlock is free
120)  * @return whether game can be activated (activate() might still fail)
121)  */
122) bool Game::canActivate()
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

123) {
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

124)   return m_fileLockName.isfree();
125) }
126) 
127) /**
128)  * @brief activate game:
129)  *        take interlock, set up image buffer, call redraw()
130)  * @return whether game could be activated
131)  */
132) bool Game::activate()
133) {
134)   if (! m_fileLockName.take()) {
135)     return false;
136)   }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

139)   redraw();
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

140)   return true;
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

141) }
142) 
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

143) /**
144)  * @brief deactivate game:
145)  *        tear down image buffer, deactivate output, release interlock
146)  */
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

147) void Game::deactivate()
148) {
149)   destroyImgBuf();
150)   sendFrame();
Stefan Schuermans add game interlock

Stefan Schuermans authored 5 years ago

151)   m_fileLockName.release();
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

152) }
153) 
Stefan Schuermans pong: activate when player...

Stefan Schuermans authored 5 years ago

154) /// check if game is active
155) bool Game::isActive() const
156) {
157)   return ! m_imgBuf.empty(); // game is active if there is an image buffer
158) }
159) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

160) /// set pixel in image buffer
161) void Game::pixel(int y, int x, ColorData const &cd)
162) {
163)   if (m_imgBuf.empty() ||
164)       ! checkLimitInt(y, 0, m_height - 1) ||
165)       ! checkLimitInt(x, 0, m_width - 1)) {
166)     return;
167)   }
168)   int pos = (y * m_width + x) * m_channels;
169)   std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
170) }
171) 
172) /// draw horizontal line to image buffer
173) void Game::lineHor(int y, int x1, int x2, ColorData const &cd)
174) {
175)   if (m_imgBuf.empty() ||
176)       ! checkLimitInt(y, 0, m_height - 1) ||
177)       ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) {
178)     return;
179)   }
180)   int pos = (y * m_width + x1) * m_channels;
181)   int dx = m_channels;
182)   for (int x = x1; x <= x2; ++x) {
183)     std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
184)     pos += dx;
185)   }
186) }
187) 
188) /// draw vertical line to image buffer
189) void Game::lineVert(int y1, int y2, int x, ColorData const &cd)
190) {
191)   if (m_imgBuf.empty() ||
192)       ! checkIntRangeLimit(y1, y2, 0, m_height - 1) ||
193)       ! checkLimitInt(x, 0, m_width - 1)) {
194)     return;
195)   }
196)   int pos = (y1 * m_width + x) * m_channels;
197)   int dy = m_width * m_channels;
198)   for (int y = y1; y <= y2; ++y) {
199)     std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
200)     pos += dy;
201)   }
202) }
203) 
204) /// draw non-filled rectangle to image buffer
205) void Game::rect(int y1, int y2, int x1, int x2, ColorData const &cd)
206) {
207)   lineHor(y1, x1, x2, cd);
208)   lineHor(y2, x1, x2, cd);
209)   lineVert(y1, y2, x1, cd);
210)   lineVert(y1, y2, x2, cd);
211) }
212) 
213) /// draw filled rectangle to image buffer
214) void Game::rectFill(int y1, int y2, int x1, int x2, ColorData const &cd)
215) {
216)   if (m_imgBuf.empty() ||
217)       ! checkIntRangeLimit(y1, y2, 0, m_height - 1) ||
218)       ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) {
219)     return;
220)   }
221)   int pos = (y1 * m_width + x1) * m_channels;
222)   int dx = m_channels;
223)   int dy = m_width - (x2 - x1 + 1) * m_channels;
224)   for (int y = y1; y <= y2; ++y) {
225)     for (int x = x1; x <= x2; ++x) {
226)       std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
227)       pos += dx;
228)     }
229)     pos += dy;
230)   }
231) }
232) 
Stefan Schuermans pong: display score

Stefan Schuermans authored 5 years ago

233) /**
234)  * @brief draw a small digit (3x5 pixels) to image buffer
235)  * @param[in] topY y coordinate of top of digit
236)  * @param[in] leftX x coordinate of left of digit
237)  * @param[in] digit to draw ('0'..'9')
238)  * @param[in] cd color to use for drawing
239)  */
240) void Game::digit3x5(int topY, int leftX, char digit, ColorData const &cd)
241) {
242)   /**
243)    * digit data in octal,
244)    * each row is 3 bits, msb of row is left,
245)    * msb row is top
246)    */
247)   static const unsigned int digitData[10] = {
248)     075557, 022222, 071747, 071717, 055711,
249)     074717, 074757, 071111, 075757, 075717
250)   };
251) 
252)   if (digit < '0' || digit > '9') {
253)     return;
254)   }
255)   int idx = digit - '0';
256)   unsigned int data = digitData[idx];
257)   for (int y = topY; y < topY + 5; ++y) {
258)     for (int x = leftX; x < leftX + 3; ++x) {
259)       data <<= 1;
260)       if (data & 0100000) {
261)         pixel(y, x, cd);
262)       }
263)     }
264)   }
265) }
266) 
267) /**
268)  * @brief draw small digits (3x5 each, 1 pixel gap) to image buffer
269)  * @param[in] topY y coordinate of top of digits
270)  * @param[in] leftX x coordinate of left of digits
271)  * @param[in] digits to draw (string of '0'..'9')
272)  * @param[in] cd color to use for drawing
273)  */
274) void Game::digits3x5(int topY, int leftX, std::string const &digits,
275)                      ColorData const &cd)
276) {
277)   for (char digit: digits) {
278)     digit3x5(topY, leftX, digit, cd);
279)     leftX += 4;
280)   }
281) }
282) 
283) /**
284)  * @brief draw number using 3x5 digits to image buffer
285)  * @param[in] anchorY y coordinate of anchor point
286)  * @param[in] anchorX x coordinate of anchor point
287)  * @param[in] alignY y alignment, -1 for top, 0 for center, 1 for bottom
288)  * @param[in] alignX x alignment, -1 for left, 0 for center, 1 for right
289)  * @param[in] number non-negative number to draw
290)  * @param[in] cd color to use for drawing
291)  */
292) void Game::number3x5(int anchorY, int anchorX, int alignY, int alignX,
293)                      int number, ColorData const &cd)
294) {
295)   // convert number to digits string
296)   if (number < 0) {
297)     return;
298)   }
299)   std::stringstream digitsStrm;
300)   digitsStrm << number;
301)   std::string const &digits = digitsStrm.str();
302) 
303)   // compute top left position
304)   int topY, leftX;
305)   switch (alignY) {
306)   case -1: topY = anchorY; break;
307)   case 0: topY = anchorY - 2; break;
308)   case 1: topY = anchorY - 4; break;
309)   default: return;
310)   }
311)   switch (alignX) {
312)   case -1: leftX = anchorX; break;
313)   case 0: leftX = anchorX - 2 * digits.length() + 1; break;
314)   case 1: leftX = anchorX - 4 * digits.length() + 2; break;
315)   default: return;
316)   }
317) 
318)   // draw digits
319)   digits3x5(topY, leftX, digits, cd);
320) }
321) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

322) /// process update of color file, return true on update
323) bool Game::colorUpdate(ColorFile &colorFile, ColorData &data) const
324) {
325)   if (colorFile.checkModified()) {
326)     colorFile.update();
327)     color2data(colorFile, data);
328)     return true;
329)   } else {
330)     return false;
331)   }
332) }
333) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

336) {
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

338)     data.clear();
339)   } else {
340)     unsigned int channels = m_fileFormat.m_obj.m_channels;
341)     unsigned int maxval = m_fileFormat.m_obj.m_maxval;
342)     data.resize(m_fileFormat.m_obj.m_channels);
343)     Color const &color = colorFile.m_obj;
344)     if (channels == 1) {
345)       // single channel
346)       // convert to monochrome according to CIE XYZ
347)       double val = 0.2125 * color.m_red + 0.7154 * color.m_green
348)                                         + 0.0721 * color.m_blue;
349)       // adapt to maxval and round
350)       data.at(0) = (unsigned char)(val * maxval / 255.0 + 0.5);
351)     } else if (channels == 2) {
352)       // two channels
353)       // adapt to maxval and round, ignore blue
354)       data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5);
355)       data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5);
356)     } else if (channels >= 3) {
357)       // three channels (more than three channels: further channels are dark)
358)       // adapt to maxval and round
359)       data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5);
360)       data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5);
361)       data.at(2) = (unsigned char)(color.m_blue * maxval / 255.0 + 0.5);
362)     }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

363)   }
364) }
365) 
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

366) /// process update of value file, return true on update
367) bool Game::valueUpdate(UIntFile &valueFile, ValueDescr const &descr,
368)                        unsigned int &value)
369) {
370)   if (valueFile.checkModified()) {
371)     valueFile.update();
372)     valueFromFile(valueFile, descr, value);
373)     return true;
374)   } else {
375)     return false;
376)   }
377) }
378) 
379) /// get value from value file
380) void Game::valueFromFile(UIntFile &valueFile, ValueDescr const &descr,
381)                          unsigned int &value)
382) {
383)   if (! valueFile.m_valid) {
384)     value = descr.default_;
385)   } else {
386)     value = valueFile.m_obj.m_uint;
387)     if (value < descr.minimum) {
388)       value = descr.minimum;
389)     }
390)     else if (value > descr.maximum) {
391)       value = descr.maximum;
392)     }
393)   }
394) }
395) 
Stefan Schuermans sound support for pong game

Stefan Schuermans authored 5 years ago

396) /// process update of sound name file
397) void Game::soundUpdate(NameFile &soundFile)
398) {
399)   if (soundFile.checkModified()) {
400)     soundFile.update();
401)   }
402) }
403) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

404) /// send current image buffer as frame to output stream
405) void Game::sendFrame()
406) {
407)   // image buffer available -> send its contents as frame
408)   if (! m_imgBuf.empty()) {
409)     Format const &fmt = m_fileFormat.m_obj;
410)     stBlinkenFrame *pFrame = BlinkenFrameNew(fmt.m_height, fmt.m_width,
411)                                              fmt.m_channels, fmt.m_maxval, 1);
412)     BlinkenFrameSetPixelData(pFrame, 0, m_height, 0, m_width, 0, m_channels,
413)                              m_imgBuf.data());
414)     m_fileOutStream.setFrame(pFrame);
415)     BlinkenFrameFree(pFrame);
416)   }
417)   // no image buffer available -> send "no frame" information
418)   else {
419)     m_fileOutStream.setFrame(NULL);
420)   }
421) }
422) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

423) /// set next game time step - plan timed action of game
424) void Game::setTimeStep(const Time& timeStepTime)
425) {
426)   m_haveTimeStep = true;
427)   m_timeStepTime = timeStepTime;
428)   planTimeCall();
429) }
430) 
431) /// unset game time step - do timed action for game
432) void Game::unsetTimeStep()
433) {
434)   m_haveTimeStep = false;
435)   planTimeCall();
436) }
437) 
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

438) /// play a sound on operator connection (NULL ok) (direct)
439) void Game::playOpConnSound(OpConn *pConn, NameFile const &soundFile)
440) {
441)   if (pConn && soundFile.m_valid) {
442)     pConn->sendPlay(soundFile.m_obj.m_str);
443)   }
444) }
445) 
446) //// request playing a sound on operator connection (NULL ok) (via time call)
447) void Game::requestOpConnSound(OpConn *pConn, NameFile const &soundFile)
448) {
449)   if (pConn) {
450)     m_opConnSounds[pConn] = &soundFile;
Stefan Schuermans add time call for sound req

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

452)   }
453) }
454) 
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

455) /// request closing operator connection (NULL ok) (closed via time call)
456) void Game::requestOpConnClose(OpConn *pConn)
457) {
458)   if (pConn) {
459)     m_opConnsClose.insert(pConn);
460)     planTimeCall();
461)   }
462) }
463) 
464) /// remove operator connection from requests (call when op conn is closed)
465) void Game::forgetOpConn(OpConn *pConn)
466) {
Stefan Schuermans add playing sounds to game...

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

468)   m_opConnsClose.erase(pConn);
469)   planTimeCall();
470) }
471) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

472) /// (re-)create image buffer
473) void Game::createImgBuf()
474) {
475)   // get rid of old image buffer
476)   destroyImgBuf();
477) 
478)   // read format from format file
479)   m_fileFormat.update();
480)   if (! m_fileFormat.m_valid)
481)     return;
482) 
483)   // read background color
484)   m_fileBackgroundColor.update();
485)   if (! m_fileBackgroundColor.m_valid)
486)     return;
487) 
488)   // store parameters
489)   m_height = m_fileFormat.m_obj.m_height;
490)   m_width = m_fileFormat.m_obj.m_width;
491)   m_channels = m_fileFormat.m_obj.m_channels;
492) 
493)   // create image buffer
494)   m_imgBuf.resize(m_height * m_width * m_channels);
495) 
496)   // convert background color
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

498) }
499) 
500) /// tear down image buffer
501) void Game::destroyImgBuf()
502) {
503)   m_imgBuf.clear();
504)   m_backgroundColor.clear();
505) }
506) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

512)     m_mgrs.m_callMgr.requestTimeCall(this, Time::now());
513)     return;
514)   }
515) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

516)   // no time step requested by game
517)   if (! m_haveTimeStep) {
518)     m_mgrs.m_callMgr.cancelTimeCall(this);
519)     return;
520)   }
521) 
522)   // request next time call
523)   m_mgrs.m_callMgr.requestTimeCall(this, m_timeStepTime);
524) }
525)