63dfd259223551b1e767e3b34444ecfdf1bf5944
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 op conn close requests

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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 "OutStreamFile.h"
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

35)   m_height(0), m_width(0), m_channels(0), m_imgBuf(), m_backgroundColor(),
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

61)   }
62) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

72) 
73)   // check config update of derived game
74)   if (updateConfigGame()) {
75)     doRedraw = true;
76)   }
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 op conn close requests

Stefan Schuermans authored 5 years ago

91)   // close operator connections
92)   for (OpConn *pConn : m_opConnsClose) {
93)     pConn->close();
94)   }
95)   m_opConnsClose.clear();
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

96) 
97)   // time step of game has been reached -> call game
98)   if (m_haveTimeStep && Time::now() >= m_timeStepTime) {
99)     m_haveTimeStep = false;
100)     timeStep();
101)   }
102) 
103)   // plan next time call - if any
104)   planTimeCall();
105) }
106) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

107) /// activate game: set up image buffer, call redraw()
108) void Game::activate()
109) {
110)   createImgBuf();
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

112)   redraw();
113) }
114) 
115) /// deactivate game: tear down image buffer, deactivate output
116) void Game::deactivate()
117) {
118)   destroyImgBuf();
119)   sendFrame();
120) }
121) 
Stefan Schuermans pong: activate when player...

Stefan Schuermans authored 5 years ago

122) /// check if game is active
123) bool Game::isActive() const
124) {
125)   return ! m_imgBuf.empty(); // game is active if there is an image buffer
126) }
127) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

290) /// process update of color file, return true on update
291) bool Game::colorUpdate(ColorFile &colorFile, ColorData &data) const
292) {
293)   if (colorFile.checkModified()) {
294)     colorFile.update();
295)     color2data(colorFile, data);
296)     return true;
297)   } else {
298)     return false;
299)   }
300) }
301) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

304) {
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

331)   }
332) }
333) 
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

334) /// process update of value file, return true on update
335) bool Game::valueUpdate(UIntFile &valueFile, ValueDescr const &descr,
336)                        unsigned int &value)
337) {
338)   if (valueFile.checkModified()) {
339)     valueFile.update();
340)     valueFromFile(valueFile, descr, value);
341)     return true;
342)   } else {
343)     return false;
344)   }
345) }
346) 
347) /// get value from value file
348) void Game::valueFromFile(UIntFile &valueFile, ValueDescr const &descr,
349)                          unsigned int &value)
350) {
351)   if (! valueFile.m_valid) {
352)     value = descr.default_;
353)   } else {
354)     value = valueFile.m_obj.m_uint;
355)     if (value < descr.minimum) {
356)       value = descr.minimum;
357)     }
358)     else if (value > descr.maximum) {
359)       value = descr.maximum;
360)     }
361)   }
362) }
363) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

364) /// send current image buffer as frame to output stream
365) void Game::sendFrame()
366) {
367)   // image buffer available -> send its contents as frame
368)   if (! m_imgBuf.empty()) {
369)     Format const &fmt = m_fileFormat.m_obj;
370)     stBlinkenFrame *pFrame = BlinkenFrameNew(fmt.m_height, fmt.m_width,
371)                                              fmt.m_channels, fmt.m_maxval, 1);
372)     BlinkenFrameSetPixelData(pFrame, 0, m_height, 0, m_width, 0, m_channels,
373)                              m_imgBuf.data());
374)     m_fileOutStream.setFrame(pFrame);
375)     BlinkenFrameFree(pFrame);
376)   }
377)   // no image buffer available -> send "no frame" information
378)   else {
379)     m_fileOutStream.setFrame(NULL);
380)   }
381) }
382) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

383) /// set next game time step - plan timed action of game
384) void Game::setTimeStep(const Time& timeStepTime)
385) {
386)   m_haveTimeStep = true;
387)   m_timeStepTime = timeStepTime;
388)   planTimeCall();
389) }
390) 
391) /// unset game time step - do timed action for game
392) void Game::unsetTimeStep()
393) {
394)   m_haveTimeStep = false;
395)   planTimeCall();
396) }
397) 
Stefan Schuermans add op conn close requests

Stefan Schuermans authored 5 years ago

398) /// request closing operator connection (NULL ok) (closed via time call)
399) void Game::requestOpConnClose(OpConn *pConn)
400) {
401)   if (pConn) {
402)     m_opConnsClose.insert(pConn);
403)     planTimeCall();
404)   }
405) }
406) 
407) /// remove operator connection from requests (call when op conn is closed)
408) void Game::forgetOpConn(OpConn *pConn)
409) {
410)   m_opConnsClose.erase(pConn);
411)   planTimeCall();
412) }
413) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

414) /// (re-)create image buffer
415) void Game::createImgBuf()
416) {
417)   // get rid of old image buffer
418)   destroyImgBuf();
419) 
420)   // read format from format file
421)   m_fileFormat.update();
422)   if (! m_fileFormat.m_valid)
423)     return;
424) 
425)   // read background color
426)   m_fileBackgroundColor.update();
427)   if (! m_fileBackgroundColor.m_valid)
428)     return;
429) 
430)   // store parameters
431)   m_height = m_fileFormat.m_obj.m_height;
432)   m_width = m_fileFormat.m_obj.m_width;
433)   m_channels = m_fileFormat.m_obj.m_channels;
434) 
435)   // create image buffer
436)   m_imgBuf.resize(m_height * m_width * m_channels);
437) 
438)   // convert background color
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

440) }
441) 
442) /// tear down image buffer
443) void Game::destroyImgBuf()
444) {
445)   m_imgBuf.clear();
446)   m_backgroundColor.clear();
447) }
448) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

452)   // request immediate time call if there are internal time-based actions
453)   if (! m_opConnsClose.empty()) {
454)     m_mgrs.m_callMgr.requestTimeCall(this, Time::now());
455)     return;
456)   }
457) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

458)   // no time step requested by game
459)   if (! m_haveTimeStep) {
460)     m_mgrs.m_callMgr.cancelTimeCall(this);
461)     return;
462)   }
463) 
464)   // request next time call
465)   m_mgrs.m_callMgr.requestTimeCall(this, m_timeStepTime);
466) }
467)