Update socket output

This commit is contained in:
Nguyễn Gia Phong 2018-08-05 18:05:07 +07:00
parent 834fa33ec0
commit ba2aaeb1f1
6 changed files with 47 additions and 62 deletions

View File

@ -21,6 +21,7 @@ Brutal Maze has a few notable features:
* Somewhat a realistic physic and logic system. * Somewhat a realistic physic and logic system.
* Resizable game window in-game. * Resizable game window in-game.
* Easily customizable via INI file format. * Easily customizable via INI file format.
* Recordable in JSON (some kind of silent screencast).
* Remote control through TCP/IP socket (can be used in AI researching). * Remote control through TCP/IP socket (can be used in AI researching).
Installation Installation

View File

@ -17,17 +17,16 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with Brutal Maze. If not, see <https://www.gnu.org/licenses/>. # along with Brutal Maze. If not, see <https://www.gnu.org/licenses/>.
__version__ = '0.8.1' __version__ = '0.8.20'
import re import re
from argparse import ArgumentParser, FileType, RawTextHelpFormatter from argparse import ArgumentParser, FileType, RawTextHelpFormatter
from collections import deque
try: # Python 3 try: # Python 3
from configparser import ConfigParser from configparser import ConfigParser
except ImportError: # Python 2 except ImportError: # Python 2
from ConfigParser import ConfigParser from ConfigParser import ConfigParser
from math import atan2, radians, pi from math import atan2, radians, pi
from os.path import join, pathsep from os.path import join as pathjoin, pathsep
from socket import socket, SOL_SOCKET, SO_REUSEADDR from socket import socket, SOL_SOCKET, SO_REUSEADDR
from sys import stdout from sys import stdout
from threading import Thread from threading import Thread
@ -37,10 +36,9 @@ from pygame import KEYDOWN, QUIT, VIDEORESIZE
from pygame.time import Clock, get_ticks from pygame.time import Clock, get_ticks
from appdirs import AppDirs from appdirs import AppDirs
from .constants import ( from .constants import SETTINGS, ICON, MUSIC, NOISE, HERO_SPEED, MIDDLE
SETTINGS, ICON, MUSIC, NOISE, HERO_SPEED, COLORS, MIDDLE, WALL)
from .maze import Maze from .maze import Maze
from .misc import deg, round2, sign from .misc import sign, deg, join
class ConfigReader: class ConfigReader:
@ -73,6 +71,8 @@ class ConfigReader:
self.muted = self.config.getboolean('Sound', 'Muted') self.muted = self.config.getboolean('Sound', 'Muted')
self.musicvol = self.config.getfloat('Sound', 'Music volume') self.musicvol = self.config.getfloat('Sound', 'Music volume')
self.space = self.config.getboolean('Sound', 'Space theme') self.space = self.config.getboolean('Sound', 'Space theme')
self.export_dir = self.config.get('Record', 'Directory')
self.export_rate = self.config.getint('Record', 'Frequency')
self.server = self.config.getboolean('Server', 'Enable') self.server = self.config.getboolean('Server', 'Enable')
self.host = self.config.get('Server', 'Host') self.host = self.config.get('Server', 'Host')
self.port = self.config.getint('Server', 'Port') self.port = self.config.getint('Server', 'Port')
@ -98,8 +98,9 @@ class ConfigReader:
def read_args(self, arguments): def read_args(self, arguments):
"""Read and parse a ArgumentParser.Namespace.""" """Read and parse a ArgumentParser.Namespace."""
for option in ('size', 'max_fps', 'muted', 'musicvol', 'space', for option in (
'server', 'host', 'port', 'timeout', 'headless'): 'size', 'max_fps', 'muted', 'musicvol', 'space', 'export_dir',
'export_rate', 'server', 'host', 'port', 'timeout', 'headless'):
value = getattr(arguments, option) value = getattr(arguments, option)
if value is not None: setattr(self, option, value) if value is not None: setattr(self, option, value)
@ -134,51 +135,20 @@ class Game:
self.max_fps, self.fps = config.max_fps, float(config.max_fps) self.max_fps, self.fps = config.max_fps, float(config.max_fps)
self.musicvol = config.musicvol self.musicvol = config.musicvol
self.key, self.mouse = config.key, config.mouse self.key, self.mouse = config.key, config.mouse
self.maze = Maze(config.max_fps, config.size, config.headless) self.maze = Maze(config.max_fps, config.size, config.headless,
config.export_dir, 1000.0 / config.export_rate)
self.hero = self.maze.hero self.hero = self.maze.hero
self.clock, self.paused = Clock(), False self.clock, self.paused = Clock(), False
def __enter__(self): return self def __enter__(self): return self
def expos(self, x, y): def export_txt(self):
"""Return position of the given coordinates in rounded percent.""" """Export maze data to string."""
cx = (x+self.maze.x-self.maze.centerx) / self.maze.distance * 100 export = self.maze.update_export(forced=True)
cy = (y+self.maze.y-self.maze.centery) / self.maze.distance * 100 return '{} {} {} {}\n{}{}{}{}'.format(
return round2(cx), round2(cy) len(export['m']), len(export['e']), len(export['b']), export['s'],
''.join(row + '\n' for row in export['m']), join(export['h']),
def export(self): ''.join(map(join, export['e'])), ''.join(map(join, export['b'])))
"""Export maze data to a bytes object."""
maze, hero, = self.maze, self.hero
lines = deque(['{0} {4} {5} {1} {2:d} {3:d}'.format(
COLORS[hero.get_color()], deg(self.hero.angle),
hero.next_strike <= 0, hero.next_heal <= 0,
*self.expos(maze.x, maze.y))])
walls = [[1 if maze.map[x][y] == WALL else 0 for x in maze.rangex]
for y in maze.rangey] if maze.next_move <= 0 else []
ne = nb = 0
for enemy in maze.enemies:
# Check Chameleons
if getattr(enemy, 'visible', 1) <= 0 and maze.next_move <= 0:
continue
lines.append('{0} {2} {3} {1:.0f}'.format(
COLORS[enemy.get_color()], deg(enemy.angle),
*self.expos(*enemy.get_pos())))
ne += 1
for bullet in maze.bullets:
x, y = self.expos(bullet.x, bullet.y)
color, angle = COLORS[bullet.get_color()], deg(bullet.angle)
if color != '0':
lines.append('{} {} {} {:.0f}'.format(color, x, y, angle))
nb += 1
if walls: lines.appendleft('\n'.join(''.join(str(cell) for cell in row)
for row in walls))
lines.appendleft('{} {} {} {}'.format(len(walls), ne, nb,
maze.get_score()))
return '\n'.join(lines).encode()
def update(self): def update(self):
"""Draw and handle meta events on Pygame window. """Draw and handle meta events on Pygame window.
@ -191,12 +161,8 @@ class Game:
return False return False
elif event.type == VIDEORESIZE: elif event.type == VIDEORESIZE:
self.maze.resize((event.w, event.h)) self.maze.resize((event.w, event.h))
elif event.type == KEYDOWN and not self.server: elif event.type == KEYDOWN:
if event.key == self.key['new']: if event.key == self.key['mute']:
self.maze.reinit()
elif event.key == self.key['pause'] and not self.hero.dead:
self.paused ^= True
elif event.key == self.key['mute']:
if pygame.mixer.get_init() is None: if pygame.mixer.get_init() is None:
pygame.mixer.init(frequency=44100) pygame.mixer.init(frequency=44100)
pygame.mixer.music.load(MUSIC) pygame.mixer.music.load(MUSIC)
@ -204,6 +170,11 @@ class Game:
pygame.mixer.music.play(-1) pygame.mixer.music.play(-1)
else: else:
pygame.mixer.quit() pygame.mixer.quit()
elif not self.server:
if event.key == self.key['new']:
self.maze.reinit()
elif event.key == self.key['pause'] and not self.hero.dead:
self.paused ^= True
# Compare current FPS with the average of the last 10 frames # Compare current FPS with the average of the last 10 frames
new_fps = self.clock.get_fps() new_fps = self.clock.get_fps()
@ -269,7 +240,8 @@ class Game:
if self.hero.dead: if self.hero.dead:
connection.send('0000000'.encode()) connection.send('0000000'.encode())
break break
data = self.export() data = self.export_txt().encode()
alpha = deg(self.hero.angle)
connection.send('{:07}'.format(len(data)).encode()) connection.send('{:07}'.format(len(data)).encode())
connection.send(data) connection.send(data)
try: try:
@ -282,7 +254,9 @@ class Game:
except ValueError: # invalid input except ValueError: # invalid input
break break
y, x = (i - 1 for i in divmod(move, 3)) y, x = (i - 1 for i in divmod(move, 3))
self.sockinp = x, y, radians(angle), attack & 1, attack >> 1 # Time is the essence.
angle = self.hero.angle if angle == alpha else radians(angle)
self.sockinp = x, y, angle, attack & 1, attack >> 1
clock.tick(self.fps) clock.tick(self.fps)
self.sockinp = 0, 0, -pi * 3 / 4, 0, 0 self.sockinp = 0, 0, -pi * 3 / 4, 0, 0
new_time = get_ticks() new_time = get_ticks()
@ -329,6 +303,7 @@ class Game:
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
if self.server is not None: self.server.close() if self.server is not None: self.server.close()
if not self.hero.dead: self.maze.dump_records()
pygame.quit() pygame.quit()
@ -338,7 +313,7 @@ def main():
dirs = AppDirs(appname='brutalmaze', appauthor=False, multipath=True) dirs = AppDirs(appname='brutalmaze', appauthor=False, multipath=True)
parents = dirs.site_config_dir.split(pathsep) parents = dirs.site_config_dir.split(pathsep)
parents.append(dirs.user_config_dir) parents.append(dirs.user_config_dir)
filenames = [join(parent, 'settings.ini') for parent in parents] filenames = [pathjoin(parent, 'settings.ini') for parent in parents]
config = ConfigReader(filenames) config = ConfigReader(filenames)
config.parse() config.parse()
@ -369,10 +344,19 @@ def main():
parser.add_argument( parser.add_argument(
'--music-volume', type=float, metavar='VOL', dest='musicvol', '--music-volume', type=float, metavar='VOL', dest='musicvol',
help='between 0.0 and 1.0 (fallback: {})'.format(config.musicvol)) help='between 0.0 and 1.0 (fallback: {})'.format(config.musicvol))
parser.add_argument('--space-music', action='store_true', dest='space', parser.add_argument(
default=None, help='use space music background') '--space-music', action='store_true', dest='space', default=None,
help='use space music background (fallback: {})'.format(config.space))
parser.add_argument('--default-music', action='store_false', dest='space', parser.add_argument('--default-music', action='store_false', dest='space',
help='use default music background') help='use default music background')
parser.add_argument(
'--record-dir', metavar='DIR', dest='export_dir',
help='directory to write game records (fallback: {})'.format(
config.export_dir or '*disabled*'))
parser.add_argument(
'--record-rate', metavar='SPF', dest='export_rate',
help='snapshots of game state per second (fallback: {})'.format(
config.export_rate))
parser.add_argument( parser.add_argument(
'--server', action='store_true', default=None, '--server', action='store_true', default=None,
help='enable server (fallback: {})'.format(config.server)) help='enable server (fallback: {})'.format(config.server))

View File

@ -31,7 +31,7 @@ Close-range attack: Space
[Record] [Record]
# Directory to write record of game states, leave blank to disable. # Directory to write record of game states, leave blank to disable.
Directory: . Directory:
# Number of snapshots per second. This is preferably from 3 to 60. # Number of snapshots per second. This is preferably from 3 to 60.
Frequency: 30 Frequency: 30

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -7,7 +7,7 @@ with open('README.rst') as f:
setup( setup(
name='brutalmaze', name='brutalmaze',
version='0.8.1', version='0.8.20',
description='A minimalist TPS game with fast-paced action', description='A minimalist TPS game with fast-paced action',
long_description=long_description, long_description=long_description,
url='https://github.com/McSinyx/brutalmaze', url='https://github.com/McSinyx/brutalmaze',

2
wiki

@ -1 +1 @@
Subproject commit 1c771f4d4d258394614ad7a09ccaff9a6c756d76 Subproject commit 3c1a1a840b6b9959b676a3937df1099dcb24fc4c