/ main.py
main.py
  1  from kivy.app import App
  2  from kivy.clock import Clock
  3  # from kivy.core.audio import SoundLoader
  4  from kivy.core.window import Window
  5  from kivy.properties import (NumericProperty, ReferenceListProperty, ObjectProperty)
  6  from kivy.uix.widget import Widget
  7  from kivy.vector import Vector
  8  
  9  import json
 10  import random
 11  import include.multicast.vault_multicast as helper_multicast
 12  import include.udp.vault_udp.vault_ip as helper_ip
 13  import include.udp.vault_udp.vault_udp_socket as helper_udp
 14  
 15  
 16  class PongPaddle(Widget):
 17      """Paddle class for "Pong" game"""
 18      score = NumericProperty(0)
 19      name = ObjectProperty("searching...")
 20  
 21      def bounce_ball(self, ball, acceleration=0.5):
 22          """Bounce and accelerate the ball if it collides the paddle, top or bottom
 23              :param ball: ball of game "Pong"
 24              :type ball: PongBall
 25              :param acceleration: acceleration rate
 26              :type acceleration: float
 27              :return: true if collided and accelerated, false if not collided
 28              :rtype: bool
 29          """
 30          if self.collide_widget(ball):
 31              vx, vy = ball.velocity
 32              if 0 < vx + acceleration < 38:
 33                  vx += acceleration
 34  
 35              elif 0 > vx - acceleration > -38:
 36                  vx -= acceleration
 37  
 38              offset = (ball.center_y - self.center_y) / (self.height / 2)
 39              bounced = Vector(-1 * vx, vy)
 40              vel = bounced
 41              ball.velocity = vel.x, vel.y + offset
 42              return True
 43          return False
 44  
 45  
 46  class PongBall(Widget):
 47      """Ball class for "Pong" game"""
 48      velocity_x = NumericProperty(0)
 49      velocity_y = NumericProperty(0)
 50      velocity = ReferenceListProperty(velocity_x, velocity_y)
 51      control_mode = ObjectProperty(None)
 52      end_game_text = ObjectProperty(None)
 53  
 54      def move(self):
 55          """Move the ball to next position in current direction"""
 56          self.pos = Vector(*self.velocity) + self.pos
 57  
 58  
 59  class PongGame(Widget):
 60      ball = ObjectProperty(None)
 61      player1 = ObjectProperty(None)
 62      player2 = ObjectProperty(None)
 63  
 64      def __init__(self):
 65          """Process of starting of the game:
 66               1. Each player starts multicast publisher and listener
 67                 - publisher publishes his own data: ip, port, name, public key, type of connection
 68               2. One of the players automatically receives published data of another
 69                 - creates key based on own private key and public key of publisher
 70                 - starts udp connection to the publisher with his ip and port
 71                 - sends init message to the publisher: own ip, port, pub_key and name
 72                 - initialises the game
 73                 - closes publisher and listener
 74               3. Another player receives init message sent through UDP
 75                 - creates key based on own private key and received public key
 76                 - starts udp connection
 77                 - initialises the game
 78                 - closes publisher and listener
 79          """
 80          super().__init__()
 81          self.debug = True
 82          self.pl_name = "Dave_{}".format(random.randint(1000000, 10000000))
 83          self.sd_type = "pong"
 84          self.enemy_name = None
 85  
 86          # ip and port settings
 87          self.ip = helper_ip.get_ips()[0][0]
 88          self.port = random.randint(2000, 20000)
 89  
 90          # variables
 91          self.is_connected = False
 92          self.sym_init = False
 93          self.game_owner = True
 94  
 95          self.ball.end_game_text = ""
 96          self.pause = False
 97          self.win_size_pl1 = [800, 600]
 98  
 99          self.enemy = None
100          self.me = None
101          self.game_over = None
102  
103          self.all_controllers = ["mix", "mouse", "keyboard"]
104          self.ball.control_mode = self.all_controllers[0]
105  
106          self.listener = helper_multicast.VaultMultiListener()
107          self.listener.start()
108          self.listener.recv_signal.connect(self.on_recv_listener)
109  
110          # initialize udp
111          self.udp = helper_udp.UDPSocketClass(recv_port=self.port)
112          self.pub_key = self.udp.pkse.public_key
113  
114          # multicast publisher and listener
115          mc_msg = {"addr": (self.ip, self.port), "name": self.pl_name, "key": self.pub_key, "type": self.sd_type}
116          self.publisher = helper_multicast.VaultMultiPublisher(message=json.dumps(mc_msg))
117  
118          self.dprint("Welcome {} - let's play pong".format(self.pl_name))
119          self.dprint("multicast started with msg: {}".format(mc_msg))
120  
121          # audio player
122          # self.sound = SoundLoader.load("ball_bounce.wav")
123          # udp receive data event
124          self.udp.udp_recv_data.connect(self.on_recv_data)
125          # keyboard press event
126          self.keyboard = Window.request_keyboard(self.keyboard_closed, self)
127          # window close event:
128          Window.bind(on_request_close=self.stop_thread)
129  
130      def init_game_connection(self):
131          """Initialize the game after successful connection"""
132          if self.game_owner:
133              self.enemy = self.player2
134              self.me = self.player1
135  
136              # equalise window size
137              win_size = self.get_root_window().size
138  
139              msg = json.dumps({"win_size": [win_size[0], win_size[1]]})
140              self.udp.send_data(msg)
141  
142          else:
143              self.enemy = self.player1
144              self.me = self.player2
145  
146              # set default speed and pos by start
147              self.ball.pos = [729.0, 275.0]
148              self.ball.velocity = [-6.5, 0]
149  
150          # set player names
151          self.me.name = "You: {}".format(self.pl_name)
152          self.enemy.name = "Enemy: {}".format(self.enemy_name)
153  
154          self.dprint("enemy defined")
155          self.dprint("game owner: {}".format(self.game_owner))
156  
157          # key pressed event
158          self.keyboard.bind(on_key_down=self.on_keyboard_down)
159          # window resized event
160          Window.bind(on_resize=self.on_resize_window)
161  
162          self.serve_ball()
163          Clock.schedule_interval(self.update, 1.0 / 60.0)
164  
165      def serve_ball(self, vel=(6, 0)):
166          """Throw the ball from middle"""
167          self.ball.center = self.center
168          self.ball.velocity = vel
169  
170      def update(self, *args):
171          """Update game"""
172          # print(*args)
173          if not self.pause:
174              self.ball.move()
175  
176          # bounce off paddles
177          bounce_pl1 = self.player1.bounce_ball(self.ball)
178          bounce_pl2 = self.player2.bounce_ball(self.ball)
179  
180          if bounce_pl1 or bounce_pl2:
181              # self.sound.play()
182              if self.game_owner:
183                  msg = {"ball_vel": self.ball.velocity, "ball_pos": self.ball.pos}
184                  self.udp.send_data(json.dumps(msg))
185  
186          # bounce ball off bottom or top
187          if (self.ball.y < self.y) or (self.ball.top > self.top):
188              self.ball.velocity_y *= -1
189  
190          # went off to a side to score point?
191          if self.ball.x < self.x - 10:
192              if self.game_owner:
193                  self.player2.score += 1
194                  msg = {"score_pl2": self.player2.score}
195                  self.udp.send_data(json.dumps(msg))
196              self.check_player_win(self.player2)
197              self.serve_ball(vel=(6, 0))
198          if self.ball.right > self.width + 10:
199              if self.game_owner:
200                  self.player1.score += 1
201                  msg = {"score_pl1": self.player1.score}
202                  self.udp.send_data(json.dumps(msg))
203              self.check_player_win(self.player1)
204              self.serve_ball(vel=(-6, 0))
205  
206      def on_recv_data(self, data, addr):
207          """Receive data sent through UDP
208              :param data: received data
209              :type data: str
210              :param addr: IP and Port of sender
211              :type addr: list
212          """
213          # exception handling for json data missing
214          try:
215              data_dict = json.loads(data)
216          except Exception as e:
217              self.dprint(str(e))
218              # if not self.sym_init:
219              #     self.dprint("address: {}".format(addr))
220              #     # self.udp.sym_encryption.update_key(addr=(addr[0], addr[1]), key=self.key)
221              #     self.sym_init = True
222  
223          # exception handling for not iterable missing
224          self.dprint("data recv {} from {}".format(data.replace("\n", ""), addr))
225          for key, value in data_dict.items():
226              if key == "pad_pos":
227                  self.enemy.pos = value
228              if key == "pause":
229                  self.set_pause(value)
230              if key == "ball_vel":
231                  self.ball.velocity = value
232              if key == "ball_pos" and not value == self.ball.pos:
233                  self.ball.pos = value
234              if key == "win_size":
235                  self.win_size_pl1 = value
236                  self.get_root_window().size = value
237              if key == "reset_scores":
238                  self.player1.score = 0
239                  self.player2.score = 0
240              if key == "score_pl1":
241                  self.player1.score = value
242                  self.check_player_win(self.player1)
243              if key == "score_pl2":
244                  self.player2.score = value
245                  self.check_player_win(self.player2)
246              if key == "game_close":
247                  self.player1.score = 0
248                  self.player2.score = 0
249                  self.pause = True
250                  self.ball.end_game_text = "enemy left"
251              if key == "init" and not self.is_connected:
252                  self.sym_init = True
253  
254                  # received message multicast listener
255                  client_ip = value.get("ip", addr[0])
256                  client_port = value.get("port", addr[1])
257                  client_key = value.get("key", "")
258                  client_name = value.get("name", "")
259                  self.dprint("Enemy {} connected from [{}]:{}".format(client_name, client_ip, client_port))
260                  self.enemy_name = client_name
261  
262                  # initialize udp and encryption
263                  self.udp.pkse.update_key(addr=(client_ip, client_port), key=client_key)
264                  self.udp.update_addr(addr=(client_ip, client_port))
265  
266                  # initialize game
267                  self.init_game_connection()
268                  self.is_connected = True
269                  self.publisher.stop()
270                  self.listener.recv_signal.disconnect(self.on_recv_listener)
271                  self.listener.stop()
272  
273      def on_recv_listener(self, msg):
274          """Listen to Multicast publisher
275              :param msg: consists of ip, port, name, public key, type of connection
276              :type msg: dict
277          """
278          if not self.is_connected and not msg.get("name", self.pl_name) == self.pl_name and msg.get("type", "error") == self.sd_type:
279              # received message from multicast
280              server_ip = msg.get("addr")[0]
281              server_port = msg.get("addr")[1]
282              self.enemy_name = msg.get("name")
283  
284              # encryption
285              server_key = msg.get("key")
286  
287              # udp
288              msg = {"init": {"ip": self.ip, "port": self.port, "key": self.pub_key, "name": self.pl_name}}
289              self.udp.update_addr(addr=(server_ip, server_port))
290              self.udp.pkse.update_key(addr=(server_ip, server_port), key=server_key)
291              self.udp.send_data(json.dumps(msg))
292  
293              # self.udp.sym_encryption.update_key(addr=(server_ip, server_port), key=self.key)
294              self.dprint("found server ip: {}; port: {}; multicast_msg: {}".format(server_ip, server_port, msg))
295  
296              # initialize game
297              self.game_owner = False
298              self.is_connected = True
299              self.init_game_connection()
300              self.listener.recv_signal.disconnect(self.on_recv_listener)
301              self.listener.stop()
302              self.publisher.stop()
303  
304      def check_player_win(self, player):
305          """Check if player has certain points. If yes stop game.
306              :param player: player
307              :type player: PongBall
308          """
309          if player.score >= 10:
310              self.game_over = True
311              self.pause = True
312              if player == self.me:
313                  self.ball.end_game_text = "You won!!!"
314              else:
315                  self.ball.end_game_text = "You lost"
316  
317      def on_touch_move(self, touch):
318          """Move paddle if mouse moved
319              :param touch: mouse pos
320              :type touch: points
321          """
322          if self.ball.control_mode in ["mouse", "mix"]:
323              if self.me == self.player1:
324                  if touch.x < self.width / 3:
325                      self.move_paddle(self.me, touch.y)
326              elif self.me == self.player2:
327                  if touch.x > self.width - self.width / 3:
328                      self.move_paddle(self.me, touch.y)
329  
330      def on_keyboard_down(self, keyboard, keycode, text, modifiers):
331          """Move paddle if keyboard buttons pressed
332              :param keycode: button name
333              :type keyboard: list
334              :return: True
335              :rtype: bool
336          """
337          move_speed = 80
338          if self.ball.control_mode in ["keyboard", "mix"]:
339              if keycode[1] in ['w', "up"]:
340                  self.move_paddle(paddle=self.me, goal_pos=self.me.center_y + move_speed)
341              elif keycode[1] in ['s', "down"]:
342                  self.move_paddle(paddle=self.me, goal_pos=self.me.center_y - move_speed)
343  
344          if keycode[1] == "p":
345              self.switch_pause_play()
346              if (self.player1.score or self.player2.score) >= 10:
347                  self.player1.score = 0
348                  self.player2.score = 0
349          return True
350  
351      def keyboard_closed(self):
352          """Unbind keyboard"""
353          self.keyboard.unbind(on_key_down=self.on_keyboard_down)
354          self.keyboard = None
355  
356      def move_paddle(self, paddle, goal_pos):
357          """Moves paddle according to the size of window in order paddle to not exit from window
358              :param paddle: paddle
359              :type paddle:PongPaddle
360              :param goal_pos: goal position to move to
361              :type goal_pos: float
362          """
363          # possible maximum upper position of paddle
364          upper_pos = goal_pos + paddle.height / 2
365          # possible maximum lower position of paddle
366          lower_pos = goal_pos - paddle.height / 2
367  
368          # firstly set touch position to center of paddle
369          paddle.center_y = goal_pos
370          # avoid paddle to exit window borders
371          if upper_pos >= self.height:
372              paddle.center_y = self.height - paddle.height / 2
373          elif lower_pos <= 0:
374              paddle.center_y = 0 + paddle.height / 2
375  
376          if paddle == self.me:
377              msg = json.dumps({"pad_pos": self.me.pos})
378              self.udp.send_data(msg)
379  
380      def switch_pause_play(self):
381          """Switch pause or play"""
382          if not self.pause:
383              self.set_pause(True)
384          else:
385              self.set_pause(False)
386  
387      def set_pause(self, pause=False):
388          """Set pause or play
389              :param pause: if true-pause, if false-play
390          """
391          if pause:
392              self.pause = True
393              self.ball.end_game_text = "PAUSE"
394          else:
395              self.pause = False
396              self.ball.end_game_text = ""
397  
398      def on_press_pause_play(self):
399          """Signal on press button "Pause_Play". Switch pause. Reset score if one of players reached certain point"""
400          self.switch_pause_play()
401  
402          msg = {"pause": self.pause}
403          if self.player1.score >= 10 or self.player2.score >= 10:
404              self.player1.score = 0
405              self.player2.score = 0
406              msg = msg | {"reset_scores": True}
407  
408          self.udp.send_data(json.dumps(msg))
409  
410      def on_press_control_mode(self):
411          """Signal on press button "Control mode". Changes control mode"""
412          current_mode_index = self.all_controllers.index(self.ball.control_mode)
413          self.ball.control_mode = self.all_controllers[current_mode_index - 1]
414  
415      def on_resize_window(self, win, w, h):
416          """Signal on resize window size
417              :param w: width of window after resizing
418              :type w: float
419              :param h: height of window after resizing
420              :type h: float
421          """
422          if self.game_owner:
423              msg = json.dumps({"win_size": [w, h]})
424              self.udp.send_data(msg)
425          else:
426              self.get_root_window().size = self.win_size_pl1
427  
428      def dprint(self, text):
429          """Prints given text if debug option is on
430              :param text: text to be printed
431              :type text: str
432          """
433          if self.debug:
434              print(text)
435  
436      def stop_thread(self, *args):
437          """Stop all threads if game is closed"""
438          self.dprint("try to stop")
439          msg = json.dumps({"game_close": True})
440          self.udp.send_data(msg)
441  
442          self.listener.recv_signal.disconnect(self.on_recv_listener)
443          self.listener.stop()
444          self.publisher.stop()
445          self.udp.udp_recv_data.disconnect(self.on_recv_data)
446  
447          self.udp.stop()
448  
449  
450  class PongApp(App):
451      def __init__(self, **kwargs):
452          """Pong game. Build with Kivy"""
453          super().__init__(**kwargs)
454  
455      def build(self):
456          game = PongGame()
457          return game
458  
459  
460  if __name__ == '__main__':
461      PongApp().run()