Rework on socket server to make it really works (#7)

This commit is contained in:
Nguyễn Gia Phong 2018-02-27 16:34:00 +07:00
parent 3e85f0c3a1
commit b4c8f32700
2 changed files with 93 additions and 65 deletions

View File

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

View File

@ -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