From b4c8f327001451029a1c143321a150a2a42037a6 Mon Sep 17 00:00:00 2001 From: Nguyễn Gia Phong Date: Tue, 27 Feb 2018 16:34:00 +0700 Subject: Rework on socket server to make it really works (#7) --- brutalmaze/main.py | 119 +++++++++++++++++++++++++++++------------------------ brutalmaze/maze.py | 39 +++++++++++++----- 2 files changed, 93 insertions(+), 65 deletions(-) diff --git a/brutalmaze/main.py b/brutalmaze/main.py index 2300658..a47abf0 100644 --- a/brutalmaze/main.py +++ b/brutalmaze/main.py @@ -27,10 +27,11 @@ try: # Python 3 except ImportError: # Python 2 from ConfigParser import ConfigParser from itertools import repeat -from math import atan2, degrees +from math import atan2, degrees, radians from os.path import join, pathsep -from socket import socket +from socket import socket, SOL_SOCKET, SO_REUSEADDR from sys import stdout +from threading import Thread import pygame @@ -115,14 +116,18 @@ class Game: pygame.mixer.music.play(-1) pygame.display.set_icon(ICON) + pygame.fastevent.init() if config.server: - self.socket = socket() - self.socket.bind((config.host, config.port)) - self.socket.listen() + self.server = socket() + self.server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + self.server.bind((config.host, config.port)) + self.server.listen(1) + print('Socket server is listening on {}:{}'.format(config.host, + config.port)) else: - pygame.fastevent.init() + self.server = None - self.server, self.headless = config.server, config.headless + self.headless = config.headless # self.fps is a float to make sure floordiv won't be used in Python 2 self.max_fps, self.fps = config.max_fps, float(config.max_fps) self.musicvol = config.musicvol @@ -142,17 +147,13 @@ class Game: def export(self): """Export maze data to a bytes object.""" - maze, hero, tick, ne = self.maze, self.hero, get_ticks(), 0 + maze, hero, tick = self.maze, self.hero, get_ticks() walls = [[1 if maze.map[x][y] == WALL else 0 for x in maze.rangex] - for y in maze.rangey] - - x, y = self.expos(maze.x, maze.y) - lines = deque(['{} {} {:.0f} {:d} {:d} {:d}'.format( - x, y, hero.wound * 100, hero.next_strike <= tick, - hero.next_heal <= tick, maze.next_move <= tick)]) + for y in maze.rangey] if maze.next_move <= tick else [] + lines, ne, nb = deque(), 0, 0 for enemy in maze.enemies: - if not enemy.awake: + if not enemy.awake and walls: walls[enemy.y-maze.rangey[0]][enemy.x-maze.rangex[0]] = WALL continue elif enemy.color == 'Chameleon' and maze.next_move <= tick: @@ -162,25 +163,35 @@ class Game: x, y, degrees(enemy.angle))) ne += 1 - if maze.next_move <= tick: - rows = (''.join(str(cell) for cell in row) for row in walls) - else: - rows = repeat('0' * len(maze.rangex), len(maze.rangey)) - lines.appendleft('\n'.join(rows)) - for bullet in maze.bullets: x, y = self.expos(bullet.x, bullet.y) - lines.append('{} {} {} {:.0f}'.format(COLORS[bullet.get_color()], - x, y, degrees(bullet.angle))) + color, angle = COLORS[bullet.get_color()], degrees(bullet.angle) + if color != '0': + lines.append('{} {} {} {:.0f}'.format(color, x, y, angle)) + nb += 1 - lines.appendleft('{} {} {}'.format(len(walls), ne, len(maze.bullets))) + if walls: lines.appendleft('\n'.join(''.join(str(cell) for cell in row) + for row in walls)) + x, y = self.expos(maze.x, maze.y) + lines.appendleft('{} {} {} {} {} {} {:.0f} {:d} {:d}'.format( + len(walls), ne, nb, maze.get_score(), x, y, hero.wound * 100, + hero.next_strike <= tick, hero.next_heal <= tick)) return '\n'.join(lines).encode() - def meta(self): - """Handle meta events on Pygame window. + def update(self): + """Draw and handle meta events on Pygame window. Return False if QUIT event is captured, True otherwise. """ + # Compare current FPS with the average of the last 10 frames + new_fps = self.clock.get_fps() + if new_fps < self.fps: + self.fps -= 1 + elif self.fps < self.max_fps and not self.paused: + self.fps += 5 + if not self.paused: self.maze.update(self.fps) + + self.clock.tick(self.fps) events = pygame.fastevent.get() for event in events: if event.type == QUIT: @@ -189,7 +200,7 @@ class Game: self.maze.resize((event.w, event.h)) elif event.type == KEYDOWN and not self.server: if event.key == self.key['new']: - self.maze.__init__(self.fps) + self.maze.reinit() elif event.key == self.key['pause'] and not self.hero.dead: self.paused ^= True elif event.key == self.key['mute']: @@ -235,18 +246,28 @@ class Game: self.hero.firing = firing self.hero.slashing = slashing - def remote_control(self, connection): + def remote_control(self): """Handle remote control though socket server. - Return False if client disconnect, True otherwise. + This function is supposed to be run in a Thread. """ - data = self.export() - connection.send('{:06}'.format(len(data))) - connection.send(data) - buf = connection.recv(8) - if not buf: return False - x, y, angle, attack = (int(i) for i in buf.decode().split()) - self.control(x, y, angle, attack & 1, attack >> 1) + while True: + connection, address = self.server.accept() + print('Connected to {}:{}'.format(*address)) + self.maze.reinit() + while not self.hero.dead: + data = self.export() + connection.send('{:06}'.format(len(data)).encode()) + connection.send(data) + buf = connection.recv(8) + if not buf: break + move, angle, attack = (int(i) for i in buf.decode().split()) + y, x = (i - 1 for i in divmod(move, 3)) + self.control(x, y, radians(angle), attack & 1, attack >> 1) + self.maze.lose() + print('{1}:{2} scored {0} points'.format( + self.maze.get_score(), *address)) + connection.close() def user_control(self): """Handle direct control from user's mouse and keyboard.""" @@ -271,18 +292,9 @@ class Game: self.control(right, down, angle, firing, slashing) - def update(self): - """Update fps and the maze.""" - # Compare current FPS with the average of the last 10 frames - new_fps = self.clock.get_fps() - if new_fps < self.fps: - self.fps -= 1 - elif self.fps < self.max_fps and not self.paused: - self.fps += 5 - if not self.paused: self.maze.update(self.fps) - self.clock.tick(self.fps) - - def __exit__(self, exc_type, exc_value, traceback): pygame.quit() + def __exit__(self, exc_type, exc_value, traceback): + if self.server is not None: self.server.close() + pygame.quit() def main(): @@ -340,10 +352,9 @@ def main(): # Main loop with Game(config) as game: if config.server: - while game.meta(): - game.remote_control() - game.update() + socket_thread = Thread(target=game.remote_control) + socket_thread.daemon = True # make it disposable + socket_thread.start() + while game.update(): pass else: - while game.meta(): - game.user_control() - game.update() + while game.update(): game.user_control() diff --git a/brutalmaze/maze.py b/brutalmaze/maze.py index f94af16..387c7c6 100644 --- a/brutalmaze/maze.py +++ b/brutalmaze/maze.py @@ -78,14 +78,11 @@ class Maze: sfx_slash (pygame.mixer.Sound): sound effect of slashed enemy sfx_lose (pygame.mixer.Sound): sound effect to be played when you lose """ - def __init__(self, fps, size=None, scrtype=None, headless=False): + def __init__(self, fps, size, scrtype, headless=False): self.fps = fps if not headless: - if size is not None: - self.w, self.h = size - else: - size = self.w, self.h - if scrtype is not None: self.scrtype = scrtype + self.w, self.h = size + self.scrtype = scrtype self.surface = pygame.display.set_mode(size, self.scrtype) self.distance = (self.w * self.h / 416) ** 0.5 @@ -135,6 +132,10 @@ class Maze: return (self.centerx + (x - MIDDLE)*self.distance, self.centery + (y - MIDDLE)*self.distance) + def get_score(self): + """Return the current score.""" + return int(self.score - INIT_SCORE) + def draw(self): """Draw the maze.""" self.surface.fill(BG_COLOR) @@ -151,8 +152,8 @@ class Maze: bullet_radius = self.distance / 4 for bullet in self.bullets: bullet.draw(bullet_radius) pygame.display.flip() - pygame.display.set_caption('Brutal Maze - Score: {}'.format( - int(self.score - INIT_SCORE))) + pygame.display.set_caption( + 'Brutal Maze - Score: {}'.format(self.get_score())) def rotate(self): """Rotate the maze if needed.""" @@ -335,8 +336,8 @@ class Maze: self.centerx = self.x + offsetx*self.distance self.centery = self.y + offsety*self.distance w, h = int(self.w/self.distance/2 + 2), int(self.h/self.distance/2 + 2) - self.rangex = range(MIDDLE - w, MIDDLE + w + 1) - self.rangey = range(MIDDLE - h, MIDDLE + h + 1) + self.rangex = list(range(MIDDLE - w, MIDDLE + w + 1)) + self.rangey = list(range(MIDDLE - h, MIDDLE + h + 1)) self.slashd = self.hero.R + self.distance/SQRT2 def isfast(self): @@ -349,4 +350,20 @@ class Maze: self.hero.slashing = self.hero.firing = False self.vx = self.vy = 0.0 play(self.sfx_lose) - print('Game over. Your score: {}'.format(int(self.score - INIT_SCORE))) + + def reinit(self): + """Open new game.""" + self.centerx, self.centery = self.w / 2.0, self.h / 2.0 + self.score = INIT_SCORE + self.map = deque() + for _ in range(MAZE_SIZE): self.map.extend(new_column()) + self.vx = self.vy = 0.0 + self.rotatex = self.rotatey = 0 + self.bullets, self.enemies = [], [] + self.enemy_weights = {color: MINW for color in ENEMIES} + self.add_enemy() + + self.next_move = self.next_slashfx = 0 + self.hero.next_heal = self.hero.next_beat = self.hero.next_strike = 0 + self.hero.slashing = self.hero.firing = self.hero.dead = False + self.hero.spin_queue = self.hero.wound = 0.0 -- cgit 1.4.1