3e790c77486205e58a0bb765ac95289617e5b3b5
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"
19) 
20) namespace Blinker {
21) 
22) /**
23)  * @brief constructor
24)  * @param[in] name module name
25)  * @param[in] mgrs managers
26)  * @param[in] dirBase base directory
27)  */
28) Game::Game(const std::string &name, Mgrs &mgrs, const Directory &dirBase):
29)   Module(name, mgrs, dirBase),
30)   m_fileFormat(dirBase.getFile("format")),
31)   m_fileBackgroundColor(dirBase.getFile("backgroundColor")),
32)   m_fileOutStream(dirBase.getFile("outstream"), mgrs.m_streamMgr),
33)   m_height(0), m_width(0), m_channels(0), m_imgBuf(), m_backgroundColor()
34) {
35) }
36) 
37) /// virtual destructor
38) Game::~Game()
39) {
40)   // clean up
41)   deactivate();
42) }
43) 
44) /// check for update of configuration
45) void Game::updateConfig()
46) {
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

47)   bool doReinit = false;
48)   bool doRedraw = false;
49) 
50)   // format file was modified -> re-create canvas and schedule redraw
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

55)   }
56) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

60)   }
61) 
62)   // output stream name file was modified -> re-get output stream
63)   if (m_fileOutStream.checkModified()) {
64)     m_fileOutStream.update();
65)   }
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

66) 
67)   // check config update of derived game
68)   if (updateConfigGame()) {
69)     doRedraw = true;
70)   }
71) 
72)   // re-initialize / redraw
73)   if (doReinit) {
74)     reinitialize();
75)     doRedraw = true;
76)   }
77)   if (doRedraw) {
78)     redraw();
79)   }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

80) }
81) 
82) /// activate game: set up image buffer, call redraw()
83) void Game::activate()
84) {
85)   createImgBuf();
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

87)   redraw();
88) }
89) 
90) /// deactivate game: tear down image buffer, deactivate output
91) void Game::deactivate()
92) {
93)   destroyImgBuf();
94)   sendFrame();
95) }
96) 
Stefan Schuermans pong: activate when player...

Stefan Schuermans authored 5 years ago

97) /// check if game is active
98) bool Game::isActive() const
99) {
100)   return ! m_imgBuf.empty(); // game is active if there is an image buffer
101) }
102) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

103) /// set pixel in image buffer
104) void Game::pixel(int y, int x, ColorData const &cd)
105) {
106)   if (m_imgBuf.empty() ||
107)       ! checkLimitInt(y, 0, m_height - 1) ||
108)       ! checkLimitInt(x, 0, m_width - 1)) {
109)     return;
110)   }
111)   int pos = (y * m_width + x) * m_channels;
112)   std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
113) }
114) 
115) /// draw horizontal line to image buffer
116) void Game::lineHor(int y, int x1, int x2, ColorData const &cd)
117) {
118)   if (m_imgBuf.empty() ||
119)       ! checkLimitInt(y, 0, m_height - 1) ||
120)       ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) {
121)     return;
122)   }
123)   int pos = (y * m_width + x1) * m_channels;
124)   int dx = m_channels;
125)   for (int x = x1; x <= x2; ++x) {
126)     std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
127)     pos += dx;
128)   }
129) }
130) 
131) /// draw vertical line to image buffer
132) void Game::lineVert(int y1, int y2, int x, ColorData const &cd)
133) {
134)   if (m_imgBuf.empty() ||
135)       ! checkIntRangeLimit(y1, y2, 0, m_height - 1) ||
136)       ! checkLimitInt(x, 0, m_width - 1)) {
137)     return;
138)   }
139)   int pos = (y1 * m_width + x) * m_channels;
140)   int dy = m_width * m_channels;
141)   for (int y = y1; y <= y2; ++y) {
142)     std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
143)     pos += dy;
144)   }
145) }
146) 
147) /// draw non-filled rectangle to image buffer
148) void Game::rect(int y1, int y2, int x1, int x2, ColorData const &cd)
149) {
150)   lineHor(y1, x1, x2, cd);
151)   lineHor(y2, x1, x2, cd);
152)   lineVert(y1, y2, x1, cd);
153)   lineVert(y1, y2, x2, cd);
154) }
155) 
156) /// draw filled rectangle to image buffer
157) void Game::rectFill(int y1, int y2, int x1, int x2, ColorData const &cd)
158) {
159)   if (m_imgBuf.empty() ||
160)       ! checkIntRangeLimit(y1, y2, 0, m_height - 1) ||
161)       ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) {
162)     return;
163)   }
164)   int pos = (y1 * m_width + x1) * m_channels;
165)   int dx = m_channels;
166)   int dy = m_width - (x2 - x1 + 1) * m_channels;
167)   for (int y = y1; y <= y2; ++y) {
168)     for (int x = x1; x <= x2; ++x) {
169)       std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos);
170)       pos += dx;
171)     }
172)     pos += dy;
173)   }
174) }
175) 
Stefan Schuermans pong: display score

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

265) /// process update of color file, return true on update
266) bool Game::colorUpdate(ColorFile &colorFile, ColorData &data) const
267) {
268)   if (colorFile.checkModified()) {
269)     colorFile.update();
270)     color2data(colorFile, data);
271)     return true;
272)   } else {
273)     return false;
274)   }
275) }
276) 
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

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

Stefan Schuermans authored 5 years ago

279) {
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

280)   if (! m_fileFormat.m_valid) {
281)     data.clear();
282)   } else {
283)     unsigned int channels = m_fileFormat.m_obj.m_channels;
284)     unsigned int maxval = m_fileFormat.m_obj.m_maxval;
285)     data.resize(m_fileFormat.m_obj.m_channels);
286)     Color const &color = colorFile.m_obj;
287)     if (channels == 1) {
288)       // single channel
289)       // convert to monochrome according to CIE XYZ
290)       double val = 0.2125 * color.m_red + 0.7154 * color.m_green
291)                                         + 0.0721 * color.m_blue;
292)       // adapt to maxval and round
293)       data.at(0) = (unsigned char)(val * maxval / 255.0 + 0.5);
294)     } else if (channels == 2) {
295)       // two channels
296)       // adapt to maxval and round, ignore blue
297)       data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5);
298)       data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5);
299)     } else if (channels >= 3) {
300)       // three channels (more than three channels: further channels are dark)
301)       // adapt to maxval and round
302)       data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5);
303)       data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5);
304)       data.at(2) = (unsigned char)(color.m_blue * maxval / 255.0 + 0.5);
305)     }
Stefan Schuermans base class for games

Stefan Schuermans authored 5 years ago

306)   }
307) }
308) 
309) /// send current image buffer as frame to output stream
310) void Game::sendFrame()
311) {
312)   // image buffer available -> send its contents as frame
313)   if (! m_imgBuf.empty()) {
314)     Format const &fmt = m_fileFormat.m_obj;
315)     stBlinkenFrame *pFrame = BlinkenFrameNew(fmt.m_height, fmt.m_width,
316)                                              fmt.m_channels, fmt.m_maxval, 1);
317)     BlinkenFrameSetPixelData(pFrame, 0, m_height, 0, m_width, 0, m_channels,
318)                              m_imgBuf.data());
319)     m_fileOutStream.setFrame(pFrame);
320)     BlinkenFrameFree(pFrame);
321)   }
322)   // no image buffer available -> send "no frame" information
323)   else {
324)     m_fileOutStream.setFrame(NULL);
325)   }
326) }
327) 
328) /// (re-)create image buffer
329) void Game::createImgBuf()
330) {
331)   // get rid of old image buffer
332)   destroyImgBuf();
333) 
334)   // read format from format file
335)   m_fileFormat.update();
336)   if (! m_fileFormat.m_valid)
337)     return;
338) 
339)   // read background color
340)   m_fileBackgroundColor.update();
341)   if (! m_fileBackgroundColor.m_valid)
342)     return;
343) 
344)   // store parameters
345)   m_height = m_fileFormat.m_obj.m_height;
346)   m_width = m_fileFormat.m_obj.m_width;
347)   m_channels = m_fileFormat.m_obj.m_channels;
348) 
349)   // create image buffer
350)   m_imgBuf.resize(m_height * m_width * m_channels);
351) 
352)   // convert background color
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

353)   color2data(m_fileBackgroundColor, m_backgroundColor);