92f7925256891c6a5ceb984ba2ac403b89b008fe
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 pong: display score

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

7) #include <string>
8) #include <vector>
9) 
10) #include <BlinkenLib/BlinkenFrame.h>
11) 
12) #include "File.h"
13) #include "Format.h"
14) #include "FormatFile.h"
15) #include "Game.h"
16) #include "Mgrs.h"
17) #include "Module.h"
18) #include "OutStreamFile.h"
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

34)   m_height(0), m_width(0), m_channels(0), m_imgBuf(), m_backgroundColor(),
35)   m_haveTimeStep(false), m_timeStepTime()
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

60)   }
61) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

71) 
72)   // check config update of derived game
73)   if (updateConfigGame()) {
74)     doRedraw = true;
75)   }
76) 
77)   // re-initialize / redraw
78)   if (doReinit) {
79)     reinitialize();
80)     doRedraw = true;
81)   }
82)   if (doRedraw) {
83)     redraw();
84)   }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

87) /// callback when requested time reached
88) void Game::timeCall()
89) {
90)   // TODO: internal stuff
91) 
92)   // time step of game has been reached -> call game
93)   if (m_haveTimeStep && Time::now() >= m_timeStepTime) {
94)     m_haveTimeStep = false;
95)     timeStep();
96)   }
97) 
98)   // plan next time call - if any
99)   planTimeCall();
100) }
101) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

102) /// activate game: set up image buffer, call redraw()
103) void Game::activate()
104) {
105)   createImgBuf();
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

107)   redraw();
108) }
109) 
110) /// deactivate game: tear down image buffer, deactivate output
111) void Game::deactivate()
112) {
113)   destroyImgBuf();
114)   sendFrame();
115) }
116) 
Stefan Schuermans pong: activate when player...

Stefan Schuermans authored 5 years ago

117) /// check if game is active
118) bool Game::isActive() const
119) {
120)   return ! m_imgBuf.empty(); // game is active if there is an image buffer
121) }
122) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

299) {
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

326)   }
327) }
328) 
Stefan Schuermans pong: configurable speed

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

378) /// set next game time step - plan timed action of game
379) void Game::setTimeStep(const Time& timeStepTime)
380) {
381)   m_haveTimeStep = true;
382)   m_timeStepTime = timeStepTime;
383)   planTimeCall();
384) }
385) 
386) /// unset game time step - do timed action for game
387) void Game::unsetTimeStep()
388) {
389)   m_haveTimeStep = false;
390)   planTimeCall();
391) }
392) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

393) /// (re-)create image buffer
394) void Game::createImgBuf()
395) {
396)   // get rid of old image buffer
397)   destroyImgBuf();
398) 
399)   // read format from format file
400)   m_fileFormat.update();
401)   if (! m_fileFormat.m_valid)
402)     return;
403) 
404)   // read background color
405)   m_fileBackgroundColor.update();
406)   if (! m_fileBackgroundColor.m_valid)
407)     return;
408) 
409)   // store parameters
410)   m_height = m_fileFormat.m_obj.m_height;
411)   m_width = m_fileFormat.m_obj.m_width;
412)   m_channels = m_fileFormat.m_obj.m_channels;
413) 
414)   // create image buffer
415)   m_imgBuf.resize(m_height * m_width * m_channels);
416) 
417)   // convert background color
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

419) }
420) 
421) /// tear down image buffer
422) void Game::destroyImgBuf()
423) {
424)   m_imgBuf.clear();
425)   m_backgroundColor.clear();
426) }
427) 
Stefan Schuermans move time call logic to gam...

Stefan Schuermans authored 5 years ago

428) /// request next time call - or cancel request if not needed
429) void Game::planTimeCall()
430) {
431)   // no time step requested by game
432)   if (! m_haveTimeStep) {
433)     m_mgrs.m_callMgr.cancelTimeCall(this);
434)     return;
435)   }
436) 
437)   // request next time call
438)   m_mgrs.m_callMgr.requestTimeCall(this, m_timeStepTime);
439) }
440)