e83c6676c5f77d90cef1eeb093fb1d0784d92e29
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
76)   if (updateConfigGame()) {
77)     doRedraw = true;
78)   }
79) 
80)   // re-initialize / redraw
81)   if (doReinit) {
82)     reinitialize();
83)     doRedraw = true;
84)   }
85)   if (doRedraw) {
86)     redraw();
87)   }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

88) }
89) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

312) {
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

339)   }
340) }
341) 
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

428)   }
429) }
430) 
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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