97359d4dd0ff6c5c4264a2efca433072167da33b
Stefan Schuermans begin of Pong game

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: make ball move

Stefan Schuermans authored 5 years ago

6) #include <stdlib.h>
Stefan Schuermans begin of Pong game

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"
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

18) #include "OpConn.h"
19) #include "OpConnIf.h"
20) #include "OpReqIf.h"
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

21) #include "OutStreamFile.h"
22) #include "Pong.h"
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

23) #include "Time.h"
24) #include "TimeCallee.h"
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

25) 
26) namespace Blinker {
27) 
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

28) /// operator connection name suffix for left player's connection
29) std::string const Pong::OpConnSuffixLeft = "/left";
30) /// operator connection name suffix for right player's connection
31) std::string const Pong::OpConnSuffixRight = "/right";
32) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

33) /**
34)  * @brief constructor
35)  * @param[in] name module name
36)  * @param[in] mgrs managers
37)  * @param[in] dirBase base directory
38)  */
39) Pong::Pong(const std::string &name, Mgrs &mgrs, const Directory &dirBase):
40)   Game(name, mgrs, dirBase),
41)   m_fileBallColor(dirBase.getFile("ballColor")),
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

42)   m_fileLineColor(dirBase.getFile("lineColor")),
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

43)   m_filePadColor(dirBase.getFile("padColor")),
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

44)   m_fileComputerColor(dirBase.getFile("computerColor")),
45)   m_ballColor(), m_lineColor(), m_padColor(), m_computerColor(),
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

46)   m_ballPosX(-1), m_ballPosY(-1), m_ballDirX(0), m_ballDirY(0),
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

47)   m_padSize(0), m_leftPosY(0), m_rightPosY(0), m_leftDelay(0), m_rightDelay(0),
48)   m_pConnLeft(NULL), m_pConnRight(NULL)
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

49) {
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

50)   // open operator connection interfaces for left and right player
51)   m_mgrs.m_opMgr.open(m_name + OpConnSuffixLeft, this);
52)   m_mgrs.m_opMgr.open(m_name + OpConnSuffixRight, this);
53) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

54)   // FIXME: activate at begin for initial development only
55)   activate();
56) }
57) 
58) /// virtual destructor
59) Pong::~Pong()
60) {
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

61)   // close operator connection interfaces
62)   m_mgrs.m_opMgr.close(m_name + OpConnSuffixLeft);
63)   m_mgrs.m_opMgr.close(m_name + OpConnSuffixRight);
64) 
65)   // close open operator connections
66)   if (m_pConnLeft) {
67)     m_pConnLeft->close();
68)     m_pConnLeft = NULL;
69)   }
70)   if (m_pConnRight) {
71)     m_pConnRight->close();
72)     m_pConnRight = NULL;
73)   }
74) 
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

75)   // cancel time callback request
76)   m_mgrs.m_callMgr.cancelTimeCall(this);
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

77) }
78) 
79) /// check for update of configuration (derived game), return true on update
80) bool Pong::updateConfigGame()
81) {
82)   bool ret = false;
83) 
84)   // color file was modified -> convert color, return true for update
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

85)   if (colorUpdate(m_fileBallColor, m_ballColor) ||
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

86)       colorUpdate(m_fileLineColor, m_lineColor) ||
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

87)       colorUpdate(m_filePadColor, m_padColor) ||
88)       colorUpdate(m_fileComputerColor, m_computerColor)) {
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

89)     ret = true;
90)   }
91) 
92)   return ret;
93) }
94) 
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

95) /**
96)  * @brief check if accepting new operator connction is possible
97)  * @param[in] name operator interface name
98)  * @return if accepting new connection is possible
99)  */
100) bool Pong::acceptNewOpConn(const std::string &name)
101) {
102)   // left player can join if none there yet
103)   if (name == m_name + OpConnSuffixLeft && ! m_pConnLeft) {
104)     return true;
105)   }
106)   // right player can join if none there yet
107)   if (name == m_name + OpConnSuffixRight && ! m_pConnRight) {
108)     return true;
109)   }
110)   // default: reject connection
111)   return false;
112) }
113) 
114) /**
115)  * @brief new operator connection
116)  * @param[in] name operator interface name
117)  * @param[in] pConn operator connection object
118)  *
119)  * The new connection may not yet be used for sending inside this callback.
120)  */
121) void Pong::newOpConn(const std::string &name, OpConn *pConn)
122) {
123)   // left player joins if none there yet
124)   if (name == m_name + OpConnSuffixLeft && ! m_pConnLeft) {
125)     m_pConnLeft = pConn;
126)     redraw(); // player color is different for phone / computer
127)     return;
128)   }
129)   // right player joins if none there yet
130)   if (name == m_name + OpConnSuffixRight && ! m_pConnRight) {
131)     m_pConnRight = pConn;
132)     redraw(); // player color is different for phone / computer
133)     return;
134)   }
135) }
136) 
137) /**
138)  * @brief key command received on operator connection
139)  * @param[in] pConn operator connection object
140)  * @param[in] key key that was pressed
141)  */
142) void Pong::opConnRecvKey(OpConn *pConn, char key)
143) {
144)   // left player
145)   if (pConn == m_pConnLeft) {
146)     processKey(key, m_leftPosY);
147)     return;
148)   }
149)   // right player
150)   if (pConn == m_pConnRight) {
151)     processKey(key, m_rightPosY);
152)     return;
153)   }
154) }
155) 
156) /**
157)  * @brief play command received on operator connection
158)  * @param[in] pConn operator connection object
159)  * @param[in] sound name of sound to play
160)  */
161) void Pong::opConnRecvPlay(OpConn *pConn, const std::string &sound)
162) {
163)   (void)pConn;
164)   (void)sound;
165) }
166) 
167) /**
168)  * @brief operator connection is closed
169)  * @param[in] pConn operator connection object
170)  *
171)  * The connection may not be used for sending any more in this callback.
172)  */
173) void Pong::opConnClose(OpConn *pConn)
174) {
175)   // left player leaves
176)   if (pConn == m_pConnLeft) {
177)     m_pConnLeft = NULL;
178)     redraw(); // player color is different for phone / computer
179)     return;
180)   }
181)   // right player leaves
182)   if (pConn == m_pConnRight) {
183)     m_pConnRight = NULL;
184)     redraw(); // player color is different for phone / computer
185)     return;
186)   }
187) }
188) 
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

189) /// re-initialize game (e.g. due to config change)
190) void Pong::reinitialize()
191) {
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

192)   // compute parameters
193)   m_padSize = (m_height + 1) / 3;
194)   m_leftPosY = (m_height - m_padSize) / 2;
195)   m_rightPosY = (m_height - m_padSize) / 2;
Stefan Schuermans pong: make computer slow

Stefan Schuermans authored 5 years ago

196)   m_leftDelay = 0;
197)   m_rightDelay = 0;
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

198) 
199)   // convert colors
200)   color2data(m_fileBallColor, m_ballColor);
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

201)   color2data(m_fileLineColor, m_lineColor);
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

202)   color2data(m_filePadColor, m_padColor);
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

203)   color2data(m_fileComputerColor, m_computerColor);
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

204) 
205)   // FIXME: start ball for development
206)   startBall();
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

207) }
208) 
209) /// redraw current game image, expected to call sendFrame() at end
210) void Pong::redraw()
211) {
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

212)   int y, x;
213) 
214)   // draw background
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

215)   rectFill(0, m_height, 0, m_width, m_backgroundColor);
216) 
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

217)   // draw middle line: single line on odd width, two dashed lines at even width
218)   for (y = 0; y < m_height; ++y) {
219)     x = (m_width - (y & 1)) / 2;
220)     pixel(y, x, m_lineColor);
221)   }
222) 
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

223)   // draw pads
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

224)   lineVert(m_leftPosY, m_leftPosY + m_padSize - 1, 0,
225)            m_pConnLeft ? m_padColor : m_computerColor);
226)   lineVert(m_rightPosY, m_rightPosY + m_padSize - 1, m_width - 1,
227)            m_pConnRight ? m_padColor : m_computerColor);
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

228) 
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

229)   // draw ball
230)   pixel(m_ballPosY, m_ballPosX, m_ballColor);
Stefan Schuermans begin of Pong game

Stefan Schuermans authored 5 years ago

231) 
232)   // send updated image buffer as frame
233)   sendFrame();
234) }
235) 
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

236) /// callback when requested time reached
237) void Pong::timeCall()
238) {
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

239)   // computer player: move pads
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

240)   if (! m_pConnLeft) {
241)     computerLeft();
242)   }
243)   if (! m_pConnRight) {
244)     computerRight();
245)   }
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

246) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

247)   // bounce ball
248)   bounceBall();
249) 
250)   // move ball
251)   m_ballPosX += m_ballDirX;
252)   m_ballPosY += m_ballDirY;
253) 
254)   // draw and send frame
255)   redraw();
256) 
257)   // request next call if needed
258)   planTimeCall();
259) }
260) 
Stefan Schuermans pong: implement phone players

Stefan Schuermans authored 5 years ago

261) /**
262)  * @brief process key received from phone player
263)  * @param[in] key received key from player
264)  * @param[in,out] padPosY y position of player's pad
265)  */
266) void Pong::processKey(char key, int &padPosY)
267) {
268)   // move pad (2 = up, 8 = down), do not move pad out of field
269)   if (key == '2' && padPosY > 0) {
270)     --padPosY;
271)     redraw();
272)   }
273)   else if (key == '8' && padPosY < m_height - m_padSize) {
274)     ++padPosY;
275)     redraw();
276)   }
277) }
278) 
Stefan Schuermans pong: make computer slow

Stefan Schuermans authored 5 years ago

279) /**
280)  * @brief delay processing for computer players
281)  * @param[in,out] delay delay variable of computer player
282)  * @return whether computer player is allowed to move
283)  */
284) bool Pong::computerDelay(int &delay) const
285) 
286) {
287)   // zero delay: generate new delay
288)   if (delay <= 0) {
289)     int avg_steps = (m_height - m_padSize) / 2;
290)     int delay_range = avg_steps > 1 ? m_width / avg_steps: m_width;
291)     delay = rand() % delay_range + delay_range;
292)   }
293) 
294)   // count down delay
295)   --delay;
296) 
297)   // moving allowd if delay expired
298)   return delay <= 0;
299) }
300) 
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

301) /**
302)  * @brief computation of ideal pad position for computer players
303)  * @param[in] padBallX x coordinate of position of ball when hitting the pad
304)  * @param[in] padY current y coordinate of pad
305)  * @param[out] padYmin minimum ideal y position of pad
306)  * @param[out] padYmax maximum ideal y position of pad
307)  */
308) void Pong::computerComputePadPos(int padBallX, int padY,
309)                                  int &padYmin, int &padYmax) const
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

310) {
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

311)   // ball not moving towards pad
312)   if ((padBallX - m_ballPosX) * m_ballDirX <= 0) {
313)     // do not move if ball is still close to pad
314)     if (abs(padBallX - m_ballPosX) <= 2) {
315)       padYmin = padY;
316)       padYmax = padY;
317)       return;
318)     }
319)     // move pad to middle (might be 2 pixels wide)
320)     padYmin = (m_height - m_padSize) / 2;
321)     padYmax = (m_height - m_padSize + 1) / 2;
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

322)     return;
323)   }
324) 
325)   // compute expected ball position at pad
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

326)   int ballPosX = m_ballPosX; // simulate were ball is going
327)   int ballPosY = m_ballPosY;
328)   int ballDirY = m_ballDirY;
329)   while ((padBallX - ballPosX) * m_ballDirX > 0) { // while moving to pad
330)     int deltaX = padBallX - ballPosX;
331)     int deltaY = deltaX * m_ballDirX * ballDirY;
332)     if (deltaY < -ballPosY) {
333)       deltaY = -ballPosY;
334)       ballPosX += deltaY * m_ballDirX * ballDirY;
335)       ballPosY = 0;
336)       ballDirY = 1;
337)     }
338)     else if (deltaY > m_height - 1 - ballPosY) {
339)       deltaY = m_height - 1 - ballPosY;
340)       ballPosX += deltaY * m_ballDirX * ballDirY;
341)       ballPosY = m_height - 1;
342)       ballDirY = -1;
343)     } else {
344)       ballPosX += deltaX;
345)       ballPosY += deltaY;
346)     }
347)   }
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

348) 
349)   // compute pad position to hit ball with center of pad
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

350)   padYmin = ballPosY - m_padSize / 2;
351)   padYmax = ballPosY - (m_padSize - 1) / 2;
352) }
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

353) 
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

354) /**
355)  * @brief move pad for computer players
356)  * @param[in] padYmin minimum desired y position of pad
357)  * @param[in] padYmax maximum desired y position of pad
358)  * @param[in,out] padPosY y position of pad
359)  */
360) void Pong::computerMovePad(int padYmin, int padYmax, int &padPosY) const
361) {
362)   // move pad, do not move pad out of field
363)   if (padPosY > padYmax && padPosY > 0) {
364)     --padPosY;
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

365)   }
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

366)   else if (padPosY < padYmin && padPosY < m_height - m_padSize) {
367)     ++padPosY;
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

368)   }
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

369) }
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

370) 
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

371) /// computer player for left pad
372) void Pong::computerLeft()
373) {
Stefan Schuermans pong: make computer slow

Stefan Schuermans authored 5 years ago

374)   if (computerDelay(m_leftDelay)) {
375)     int padYmin, padYmax;
376)     computerComputePadPos(1, m_leftPosY, padYmin, padYmax);
377)     computerMovePad(padYmin, padYmax, m_leftPosY);
378)   }
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

379) }
380) 
381) /// computer player for right pad
382) void Pong::computerRight()
383) {
Stefan Schuermans pong: make computer slow

Stefan Schuermans authored 5 years ago

384)   if (computerDelay(m_rightDelay)) {
385)     int padYmin, padYmax;
386)     computerComputePadPos(m_width - 2, m_rightPosY, padYmin, padYmax);
387)     computerMovePad(padYmin, padYmax, m_rightPosY);
388)   }
Stefan Schuermans implement simple, fast comp...

Stefan Schuermans authored 5 years ago

389) }
390) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

391) /// bounce ball
392) void Pong::bounceBall()
393) {
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

394)   bounceBallSide(); // must be done before player bounce to be safe
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

395)   bounceBallLeft();
396)   bounceBallRight();
Stefan Schuermans pong: make computer perfect

Stefan Schuermans authored 5 years ago

397)   bounceBallSide(); // must also be done after player bounce to be safe
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

398) }
399) 
400) /// bounce ball at sides
401) void Pong::bounceBallSide()
402) {
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

403)   if (m_ballPosY <= 0 && m_ballDirY < 0) {
404)     m_ballDirY = 1;
405)   }
406)   if (m_ballPosY >= m_height - 1 && m_ballDirY > 0) {
407)     m_ballDirY = -1;
408)   }
409)   if (m_ballPosX <= 0 && m_ballDirX < 0) {
410)     m_ballDirX = 1;
411)   }
412)   if (m_ballPosX >= m_width - 1 && m_ballDirX > 0) {
413)     m_ballDirX = -1;
414)   }
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

415) }
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

416) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

417) /// bounce ball at left pad
418) void Pong::bounceBallLeft()
419) {
420)   if (m_ballPosX != 1 || m_ballDirX >= 0) {
421)     return;
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

422)   }
423) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

424)   // top corner
425)   if (m_ballPosY == m_leftPosY - 1 && m_ballDirY > 0) {
426)     m_ballDirX = 1;
427)     m_ballDirY = -1;
Stefan Schuermans pong: add pads

Stefan Schuermans authored 5 years ago

428)   }
429) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

430)   // bottom corner
431)   else if (m_ballPosY == m_leftPosY + m_padSize && m_ballDirY < 0) {
432)     m_ballDirX = 1;
433)     m_ballDirY = 1;
434)   }
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

435) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

436)   // pad edge
437)   else if (m_ballPosY >= m_leftPosY &&
438)            m_ballPosY < m_leftPosY + m_padSize) {
439)     m_ballDirX = 1;
440)   }
441) }
442) 
443) /// bounce ball at right pad
444) void Pong::bounceBallRight()
445) {
446)   if (m_ballPosX != m_width - 2 || m_ballDirX <= 0) {
447)     return;
448)   }
449) 
450)   // top corner
451)   if (m_ballPosY == m_rightPosY - 1 && m_ballDirY > 0) {
452)     m_ballDirX = -1;
453)     m_ballDirY = -1;
454)   }
455) 
456)   // bottom corner
457)   else if (m_ballPosY == m_rightPosY + m_padSize && m_ballDirY < 0) {
458)     m_ballDirX = -1;
459)     m_ballDirY = 1;
460)   }
461) 
462)   // pad edge
463)   else if (m_ballPosY >= m_rightPosY &&
464)            m_ballPosY < m_rightPosY + m_padSize) {
465)     m_ballDirX = -1;
466)   }
467) }
468) 
469) /// request next time call - or cancel request if not needed
470) void Pong::planTimeCall()
471) {
472)   if (m_ballPosX < 0 ||  m_ballPosY < 0) {
473)     m_mgrs.m_callMgr.cancelTimeCall(this);
474)     return;
475)   }
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

476) 
477)   Time stepTime;
478)   stepTime.fromMs(100);
479)   m_mgrs.m_callMgr.requestTimeCall(this, Time::now() + stepTime);
480) }
481) 
482) /// move ball out of the field and halt it
483) void Pong::hideBall()
484) {
485)   m_ballPosX = -1;
486)   m_ballPosY = -1;
487)   m_ballDirX = 0;
488)   m_ballDirY = 0;
489) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

490)   // update time call request
491)   planTimeCall();
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

492) }
493) 
494) /// start ball
495) void Pong::startBall()
496) {
497)   // ball starts horizontally at middle of field, vertically random
498)   m_ballPosX = (m_width - (rand() & 1)) / 2;
499)   m_ballPosY = rand() % m_height;
500)   // random diagonal direction
501)   m_ballDirX = (rand() & 1) * 2 - 1;
502)   m_ballDirY = (rand() & 1) * 2 - 1;
503) 
Stefan Schuermans pong: split large time func...

Stefan Schuermans authored 5 years ago

504)   // request first time call if needed
505)   planTimeCall();
Stefan Schuermans pong: make ball move

Stefan Schuermans authored 5 years ago

506) }
507)