/ 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()