2017-10-12 15:29:55 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# main.py - main module, starts game and main loop
|
2018-02-27 14:39:28 +01:00
|
|
|
# Copyright (C) 2017, 2018 Nguyễn Gia Phong
|
2017-10-12 15:29:55 +02:00
|
|
|
#
|
2018-02-27 14:39:28 +01:00
|
|
|
# This file is part of Brutal Maze.
|
2017-10-12 15:29:55 +02:00
|
|
|
#
|
2018-02-27 14:39:28 +01:00
|
|
|
# Brutal Maze is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# Brutal Maze is distributed in the hope that it will be useful,
|
2017-10-12 15:29:55 +02:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2018-02-27 14:39:28 +01:00
|
|
|
# GNU Affero General Public License for more details.
|
2017-10-12 15:29:55 +02:00
|
|
|
#
|
2018-02-27 14:39:28 +01:00
|
|
|
# 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/>.
|
2017-10-12 15:29:55 +02:00
|
|
|
|
2018-03-20 08:17:17 +01:00
|
|
|
__version__ = '0.6.4'
|
2018-02-13 14:51:41 +01:00
|
|
|
|
2018-02-08 14:41:08 +01:00
|
|
|
import re
|
2018-02-14 16:26:52 +01:00
|
|
|
from argparse import ArgumentParser, FileType, RawTextHelpFormatter
|
2017-10-19 10:24:56 +02:00
|
|
|
from collections import deque
|
2018-02-08 14:41:08 +01:00
|
|
|
try: # Python 3
|
2018-02-16 14:25:32 +01:00
|
|
|
from configparser import ConfigParser
|
2018-02-08 14:41:08 +01:00
|
|
|
except ImportError: # Python 2
|
2018-02-16 14:25:32 +01:00
|
|
|
from ConfigParser import ConfigParser
|
2018-03-06 15:01:27 +01:00
|
|
|
from math import atan2, radians, pi
|
2018-02-13 14:51:41 +01:00
|
|
|
from os.path import join, pathsep
|
2018-02-27 10:34:00 +01:00
|
|
|
from socket import socket, SOL_SOCKET, SO_REUSEADDR
|
2018-02-14 16:26:52 +01:00
|
|
|
from sys import stdout
|
2018-02-27 10:34:00 +01:00
|
|
|
from threading import Thread
|
2018-02-13 14:51:41 +01:00
|
|
|
|
2017-10-12 15:29:55 +02:00
|
|
|
import pygame
|
2018-03-07 10:13:34 +01:00
|
|
|
from pygame import KEYDOWN, QUIT, VIDEORESIZE
|
2018-02-16 14:25:32 +01:00
|
|
|
from pygame.time import Clock, get_ticks
|
2018-02-13 14:51:41 +01:00
|
|
|
from appdirs import AppDirs
|
2017-10-12 15:29:55 +02:00
|
|
|
|
2018-02-26 15:02:11 +01:00
|
|
|
from .constants import SETTINGS, ICON, MUSIC, HERO_SPEED, COLORS, WALL
|
2017-10-12 15:29:55 +02:00
|
|
|
from .maze import Maze
|
2018-03-05 17:59:02 +01:00
|
|
|
from .misc import deg, round2, sign
|
2018-02-08 14:41:08 +01:00
|
|
|
|
|
|
|
|
2018-02-13 14:51:41 +01:00
|
|
|
class ConfigReader:
|
|
|
|
"""Object reading and processing INI configuration file for
|
|
|
|
Brutal Maze.
|
2018-02-08 14:41:08 +01:00
|
|
|
"""
|
2018-02-19 10:55:55 +01:00
|
|
|
CONTROL_ALIASES = (('New game', 'new'), ('Toggle pause', 'pause'),
|
|
|
|
('Toggle mute', 'mute'),
|
2018-02-16 14:25:32 +01:00
|
|
|
('Move left', 'left'), ('Move right', 'right'),
|
|
|
|
('Move up', 'up'), ('Move down', 'down'),
|
|
|
|
('Long-range attack', 'shot'),
|
|
|
|
('Close-range attack', 'slash'))
|
2018-02-13 14:51:41 +01:00
|
|
|
WEIRD_MOUSE_ERR = '{}: Mouse is not a suitable control'
|
|
|
|
INVALID_CONTROL_ERR = '{}: {} is not recognized as a valid control key'
|
|
|
|
|
|
|
|
def __init__(self, filenames):
|
|
|
|
self.config = ConfigParser()
|
2018-02-14 16:26:52 +01:00
|
|
|
self.config.read(SETTINGS) # default configuration
|
2018-02-13 14:51:41 +01:00
|
|
|
self.config.read(filenames)
|
|
|
|
|
2018-02-26 15:02:11 +01:00
|
|
|
# Fallback to None when attribute is missing
|
|
|
|
def __getattr__(self, name): return None
|
|
|
|
|
|
|
|
def parse(self):
|
|
|
|
"""Parse configurations."""
|
2018-02-14 16:26:52 +01:00
|
|
|
self.size = (self.config.getint('Graphics', 'Screen width'),
|
|
|
|
self.config.getint('Graphics', 'Screen height'))
|
2018-02-18 19:01:14 +01:00
|
|
|
self.max_fps = self.config.getint('Graphics', 'Maximum FPS')
|
2018-02-19 10:55:55 +01:00
|
|
|
self.muted = self.config.getboolean('Sound', 'Muted')
|
|
|
|
self.musicvol = self.config.getfloat('Sound', 'Music volume')
|
2018-03-01 18:13:23 +01:00
|
|
|
self.server = self.config.getboolean('Server', 'Enable')
|
|
|
|
self.host = self.config.get('Server', 'Host')
|
|
|
|
self.port = self.config.getint('Server', 'Port')
|
2018-03-10 12:19:12 +01:00
|
|
|
self.timeout = self.config.getfloat('Server', 'Timeout')
|
2018-03-01 18:13:23 +01:00
|
|
|
self.headless = self.config.getboolean('Server', 'Headless')
|
|
|
|
|
|
|
|
if self.server: return
|
|
|
|
self.key, self.mouse = {}, {}
|
|
|
|
for cmd, alias in self.CONTROL_ALIASES:
|
|
|
|
i = self.config.get('Control', cmd)
|
|
|
|
if re.match('mouse[1-3]$', i.lower()):
|
|
|
|
if alias not in ('shot', 'slash'):
|
|
|
|
raise ValueError(self.WEIRD_MOUSE_ERR.format(cmd))
|
|
|
|
self.mouse[alias] = int(i[-1]) - 1
|
|
|
|
continue
|
|
|
|
if len(i) == 1:
|
|
|
|
self.key[alias] = ord(i.lower())
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
self.key[alias] = getattr(pygame, 'K_{}'.format(i.upper()))
|
|
|
|
except AttributeError:
|
|
|
|
raise ValueError(self.INVALID_CONTROL_ERR.format(cmd, i))
|
2018-02-13 14:51:41 +01:00
|
|
|
|
|
|
|
def read_args(self, arguments):
|
|
|
|
"""Read and parse a ArgumentParser.Namespace."""
|
2018-03-07 10:13:34 +01:00
|
|
|
for option in ('size', 'max_fps', 'muted', 'musicvol',
|
2018-03-10 12:19:12 +01:00
|
|
|
'server', 'host', 'port', 'timeout', 'headless'):
|
2018-02-19 10:55:55 +01:00
|
|
|
value = getattr(arguments, option)
|
|
|
|
if value is not None: setattr(self, option, value)
|
2017-10-12 15:29:55 +02:00
|
|
|
|
|
|
|
|
2018-02-16 14:25:32 +01:00
|
|
|
class Game:
|
|
|
|
"""Object handling main loop and IO."""
|
2018-02-26 15:02:11 +01:00
|
|
|
def __init__(self, config):
|
2018-02-16 14:25:32 +01:00
|
|
|
pygame.mixer.pre_init(frequency=44100)
|
|
|
|
pygame.init()
|
2018-03-01 18:13:23 +01:00
|
|
|
self.headless = config.headless and config.server
|
|
|
|
if config.muted or self.headless:
|
2018-02-19 10:55:55 +01:00
|
|
|
pygame.mixer.quit()
|
|
|
|
else:
|
|
|
|
pygame.mixer.music.load(MUSIC)
|
2018-02-26 15:02:11 +01:00
|
|
|
pygame.mixer.music.set_volume(config.musicvol)
|
2018-02-19 10:55:55 +01:00
|
|
|
pygame.mixer.music.play(-1)
|
2018-02-16 14:25:32 +01:00
|
|
|
pygame.display.set_icon(ICON)
|
2018-02-26 15:02:11 +01:00
|
|
|
|
2018-02-27 10:34:00 +01:00
|
|
|
pygame.fastevent.init()
|
2018-02-26 15:02:11 +01:00
|
|
|
if config.server:
|
2018-02-27 10:34:00 +01:00
|
|
|
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))
|
2018-03-10 12:19:12 +01:00
|
|
|
self.timeout = config.timeout
|
2018-03-02 17:47:26 +01:00
|
|
|
self.sockinp = 0, 0, -pi * 3 / 4, 0, 0 # freeze and point to NW
|
2018-02-26 15:02:11 +01:00
|
|
|
else:
|
2018-02-27 16:25:58 +01:00
|
|
|
self.server = self.sockinp = None
|
2018-02-26 15:02:11 +01:00
|
|
|
|
2018-02-18 19:01:14 +01:00
|
|
|
# self.fps is a float to make sure floordiv won't be used in Python 2
|
2018-02-26 15:02:11 +01:00
|
|
|
self.max_fps, self.fps = config.max_fps, float(config.max_fps)
|
|
|
|
self.musicvol = config.musicvol
|
|
|
|
self.key, self.mouse = config.key, config.mouse
|
2018-03-07 10:13:34 +01:00
|
|
|
self.maze = Maze(config.max_fps, config.size, config.headless)
|
2018-02-16 14:25:32 +01:00
|
|
|
self.hero = self.maze.hero
|
2018-02-19 10:55:55 +01:00
|
|
|
self.clock, self.paused = Clock(), False
|
2018-02-16 14:25:32 +01:00
|
|
|
|
|
|
|
def __enter__(self): return self
|
|
|
|
|
2018-02-26 15:02:11 +01:00
|
|
|
def expos(self, x, y):
|
|
|
|
"""Return position of the given coordinates in rounded percent."""
|
|
|
|
cx = (x+self.maze.x-self.maze.centerx) / self.maze.distance * 100
|
|
|
|
cy = (y+self.maze.y-self.maze.centery) / self.maze.distance * 100
|
|
|
|
return round2(cx), round2(cy)
|
|
|
|
|
|
|
|
def export(self):
|
|
|
|
"""Export maze data to a bytes object."""
|
2018-03-06 15:01:27 +01:00
|
|
|
maze, hero, = self.maze, self.hero
|
2018-03-05 17:59:02 +01:00
|
|
|
lines = deque(['{0} {4} {5} {1} {2:d} {3:d}'.format(
|
|
|
|
COLORS[hero.get_color()], deg(self.hero.angle),
|
2018-03-06 15:01:27 +01:00
|
|
|
hero.next_strike <= 0, hero.next_heal <= 0,
|
2018-03-05 17:59:02 +01:00
|
|
|
*self.expos(maze.x, maze.y))])
|
|
|
|
|
2018-02-26 15:02:11 +01:00
|
|
|
walls = [[1 if maze.map[x][y] == WALL else 0 for x in maze.rangex]
|
2018-03-06 15:01:27 +01:00
|
|
|
for y in maze.rangey] if maze.next_move <= 0 else []
|
2018-03-05 17:59:02 +01:00
|
|
|
ne = nb = 0
|
2018-02-26 15:02:11 +01:00
|
|
|
|
|
|
|
for enemy in maze.enemies:
|
2018-02-27 10:34:00 +01:00
|
|
|
if not enemy.awake and walls:
|
2018-02-26 15:02:11 +01:00
|
|
|
walls[enemy.y-maze.rangey[0]][enemy.x-maze.rangex[0]] = WALL
|
|
|
|
continue
|
2018-03-10 12:19:12 +01:00
|
|
|
# Check Chameleons
|
|
|
|
elif getattr(enemy, 'visible', 1) <= 0 and maze.next_move <= 0:
|
2018-02-26 15:02:11 +01:00
|
|
|
continue
|
2018-03-05 17:59:02 +01:00
|
|
|
lines.append('{0} {2} {3} {1:.0f}'.format(
|
|
|
|
COLORS[enemy.get_color()], deg(enemy.angle),
|
|
|
|
*self.expos(*enemy.get_pos())))
|
2018-02-26 15:02:11 +01:00
|
|
|
ne += 1
|
|
|
|
|
|
|
|
for bullet in maze.bullets:
|
|
|
|
x, y = self.expos(bullet.x, bullet.y)
|
2018-03-05 17:59:02 +01:00
|
|
|
color, angle = COLORS[bullet.get_color()], deg(bullet.angle)
|
2018-02-27 10:34:00 +01:00
|
|
|
if color != '0':
|
|
|
|
lines.append('{} {} {} {:.0f}'.format(color, x, y, angle))
|
|
|
|
nb += 1
|
2018-02-26 15:02:11 +01:00
|
|
|
|
2018-02-27 10:34:00 +01:00
|
|
|
if walls: lines.appendleft('\n'.join(''.join(str(cell) for cell in row)
|
|
|
|
for row in walls))
|
2018-03-05 17:59:02 +01:00
|
|
|
lines.appendleft('{} {} {} {}'.format(len(walls), ne, nb,
|
|
|
|
maze.get_score()))
|
2018-02-26 15:02:11 +01:00
|
|
|
return '\n'.join(lines).encode()
|
|
|
|
|
2018-02-27 10:34:00 +01:00
|
|
|
def update(self):
|
|
|
|
"""Draw and handle meta events on Pygame window.
|
2018-02-26 15:02:11 +01:00
|
|
|
|
|
|
|
Return False if QUIT event is captured, True otherwise.
|
|
|
|
"""
|
|
|
|
events = pygame.fastevent.get()
|
|
|
|
for event in events:
|
|
|
|
if event.type == QUIT:
|
|
|
|
return False
|
|
|
|
elif event.type == VIDEORESIZE:
|
|
|
|
self.maze.resize((event.w, event.h))
|
|
|
|
elif event.type == KEYDOWN and not self.server:
|
|
|
|
if event.key == self.key['new']:
|
2018-02-27 10:34:00 +01:00
|
|
|
self.maze.reinit()
|
2018-02-26 15:02:11 +01:00
|
|
|
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:
|
|
|
|
pygame.mixer.init(frequency=44100)
|
|
|
|
pygame.mixer.music.load(MUSIC)
|
|
|
|
pygame.mixer.music.set_volume(self.musicvol)
|
|
|
|
pygame.mixer.music.play(-1)
|
|
|
|
else:
|
|
|
|
pygame.mixer.quit()
|
2018-02-27 16:25:58 +01:00
|
|
|
|
|
|
|
# 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
|
2018-03-06 15:01:27 +01:00
|
|
|
if not self.paused: self.maze.update(self.fps)
|
|
|
|
if not self.headless: self.maze.draw()
|
2018-02-27 16:25:58 +01:00
|
|
|
self.clock.tick(self.fps)
|
2018-02-26 15:02:11 +01:00
|
|
|
return True
|
|
|
|
|
2018-02-16 14:25:32 +01:00
|
|
|
def move(self, x, y):
|
|
|
|
"""Command the hero to move faster in the given direction."""
|
2018-02-26 15:02:11 +01:00
|
|
|
x, y = -x, -y # or move the maze in the reverse direction
|
2018-02-16 14:25:32 +01:00
|
|
|
velocity = self.maze.distance * HERO_SPEED / self.fps
|
|
|
|
accel = velocity * HERO_SPEED / self.fps
|
|
|
|
|
2018-03-06 15:01:27 +01:00
|
|
|
if self.maze.next_move > 0 or not x:
|
2018-02-16 14:25:32 +01:00
|
|
|
self.maze.vx -= sign(self.maze.vx) * accel
|
|
|
|
if abs(self.maze.vx) < accel * 2: self.maze.vx = 0.0
|
|
|
|
elif x * self.maze.vx < 0:
|
|
|
|
self.maze.vx += x * 2 * accel
|
|
|
|
else:
|
|
|
|
self.maze.vx += x * accel
|
|
|
|
if abs(self.maze.vx) > velocity: self.maze.vx = x * velocity
|
|
|
|
|
2018-03-06 15:01:27 +01:00
|
|
|
if self.maze.next_move > 0 or not y:
|
2018-02-16 14:25:32 +01:00
|
|
|
self.maze.vy -= sign(self.maze.vy) * accel
|
|
|
|
if abs(self.maze.vy) < accel * 2: self.maze.vy = 0.0
|
|
|
|
elif y * self.maze.vy < 0:
|
|
|
|
self.maze.vy += y * 2 * accel
|
|
|
|
else:
|
|
|
|
self.maze.vy += y * accel
|
|
|
|
if abs(self.maze.vy) > velocity: self.maze.vy = y * velocity
|
|
|
|
|
2018-02-26 15:02:11 +01:00
|
|
|
def control(self, x, y, angle, firing, slashing):
|
|
|
|
"""Control how the hero move and attack."""
|
|
|
|
self.move(x, y)
|
|
|
|
self.hero.update_angle(angle)
|
|
|
|
self.hero.firing = firing
|
|
|
|
self.hero.slashing = slashing
|
|
|
|
|
2018-02-27 10:34:00 +01:00
|
|
|
def remote_control(self):
|
2018-02-26 15:02:11 +01:00
|
|
|
"""Handle remote control though socket server.
|
2018-02-16 14:25:32 +01:00
|
|
|
|
2018-02-27 10:34:00 +01:00
|
|
|
This function is supposed to be run in a Thread.
|
2018-02-26 15:02:11 +01:00
|
|
|
"""
|
2018-02-27 16:25:58 +01:00
|
|
|
clock = Clock()
|
2018-02-27 10:34:00 +01:00
|
|
|
while True:
|
|
|
|
connection, address = self.server.accept()
|
2018-03-10 12:19:12 +01:00
|
|
|
connection.settimeout(self.timeout)
|
2018-03-02 17:47:26 +01:00
|
|
|
time = get_ticks()
|
|
|
|
print('[{}] Connected to {}:{}'.format(time, *address))
|
2018-02-27 10:34:00 +01:00
|
|
|
self.maze.reinit()
|
2018-03-05 17:59:02 +01:00
|
|
|
while True:
|
|
|
|
if self.hero.dead:
|
2018-03-07 11:04:48 +01:00
|
|
|
connection.send('0000000'.encode())
|
2018-03-05 17:59:02 +01:00
|
|
|
break
|
2018-02-27 10:34:00 +01:00
|
|
|
data = self.export()
|
2018-03-07 11:04:48 +01:00
|
|
|
connection.send('{:07}'.format(len(data)).encode())
|
2018-02-27 10:34:00 +01:00
|
|
|
connection.send(data)
|
2018-02-27 16:25:58 +01:00
|
|
|
try:
|
2018-03-07 11:04:48 +01:00
|
|
|
buf = connection.recv(7)
|
2018-03-10 12:19:12 +01:00
|
|
|
except: # client is closed or timed out
|
2018-02-27 16:25:58 +01:00
|
|
|
break
|
2018-02-27 10:34:00 +01:00
|
|
|
if not buf: break
|
2018-03-10 12:19:12 +01:00
|
|
|
try:
|
|
|
|
move, angle, attack = map(int, buf.decode().split())
|
|
|
|
except ValueError: # invalid input
|
|
|
|
break
|
2018-02-27 10:34:00 +01:00
|
|
|
y, x = (i - 1 for i in divmod(move, 3))
|
2018-02-27 16:25:58 +01:00
|
|
|
self.sockinp = x, y, radians(angle), attack & 1, attack >> 1
|
|
|
|
clock.tick(self.fps)
|
2018-03-02 17:47:26 +01:00
|
|
|
self.sockinp = 0, 0, -pi * 3 / 4, 0, 0
|
|
|
|
new_time = get_ticks()
|
|
|
|
print('[{0}] {3}:{4} scored {1} points in {2}ms'.format(
|
|
|
|
new_time, self.maze.get_score(), new_time - time, *address))
|
2018-02-27 10:34:00 +01:00
|
|
|
connection.close()
|
2018-03-10 12:19:12 +01:00
|
|
|
if not self.hero.dead: self.maze.lose()
|
2018-02-26 15:02:11 +01:00
|
|
|
|
|
|
|
def user_control(self):
|
|
|
|
"""Handle direct control from user's mouse and keyboard."""
|
2018-02-16 14:25:32 +01:00
|
|
|
if not self.hero.dead:
|
|
|
|
keys = pygame.key.get_pressed()
|
2018-02-26 15:02:11 +01:00
|
|
|
right = keys[self.key['right']] - keys[self.key['left']]
|
|
|
|
down = keys[self.key['down']] - keys[self.key['up']]
|
|
|
|
|
|
|
|
# Follow the mouse cursor
|
|
|
|
x, y = pygame.mouse.get_pos()
|
|
|
|
angle = atan2(y - self.hero.y, x - self.hero.x)
|
|
|
|
|
2018-02-16 14:25:32 +01:00
|
|
|
buttons = pygame.mouse.get_pressed()
|
|
|
|
try:
|
2018-02-26 15:02:11 +01:00
|
|
|
firing = keys[self.key['shot']]
|
2018-02-16 14:25:32 +01:00
|
|
|
except KeyError:
|
2018-02-26 15:02:11 +01:00
|
|
|
firing = buttons[self.mouse['shot']]
|
2018-02-16 14:25:32 +01:00
|
|
|
try:
|
2018-02-26 15:02:11 +01:00
|
|
|
slashing = keys[self.key['slash']]
|
2018-02-16 14:25:32 +01:00
|
|
|
except KeyError:
|
2018-02-26 15:02:11 +01:00
|
|
|
slashing = buttons[self.mouse['slash']]
|
|
|
|
|
|
|
|
self.control(right, down, angle, firing, slashing)
|
2018-02-16 14:25:32 +01:00
|
|
|
|
2018-02-27 10:34:00 +01:00
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
|
|
if self.server is not None: self.server.close()
|
|
|
|
pygame.quit()
|
2018-02-16 14:25:32 +01:00
|
|
|
|
|
|
|
|
2017-10-12 15:29:55 +02:00
|
|
|
def main():
|
2017-10-16 17:12:32 +02:00
|
|
|
"""Start game and main loop."""
|
2018-02-02 17:33:22 +01:00
|
|
|
# Read configuration file
|
2018-02-13 14:51:41 +01:00
|
|
|
dirs = AppDirs(appname='brutalmaze', appauthor=False, multipath=True)
|
|
|
|
parents = dirs.site_config_dir.split(pathsep)
|
|
|
|
parents.append(dirs.user_config_dir)
|
|
|
|
filenames = [join(parent, 'settings.ini') for parent in parents]
|
|
|
|
config = ConfigReader(filenames)
|
2018-02-26 15:02:11 +01:00
|
|
|
config.parse()
|
2018-02-13 14:51:41 +01:00
|
|
|
|
2018-02-13 16:17:04 +01:00
|
|
|
# Parse command-line arguments
|
2018-03-01 18:13:23 +01:00
|
|
|
parser = ArgumentParser(usage='%(prog)s [options]',
|
|
|
|
formatter_class=RawTextHelpFormatter)
|
2018-02-13 14:51:41 +01:00
|
|
|
parser.add_argument('-v', '--version', action='version',
|
|
|
|
version='Brutal Maze {}'.format(__version__))
|
2018-02-14 16:26:52 +01:00
|
|
|
parser.add_argument(
|
|
|
|
'--write-config', nargs='?', const=stdout, type=FileType('w'),
|
|
|
|
metavar='PATH', dest='defaultcfg',
|
|
|
|
help='write default config and exit, if PATH not specified use stdout')
|
2018-02-13 14:51:41 +01:00
|
|
|
parser.add_argument(
|
|
|
|
'-c', '--config', metavar='PATH',
|
|
|
|
help='location of the configuration file (fallback: {})'.format(
|
|
|
|
pathsep.join(filenames)))
|
|
|
|
parser.add_argument(
|
|
|
|
'-s', '--size', type=int, nargs=2, metavar=('X', 'Y'),
|
2018-02-14 16:26:52 +01:00
|
|
|
help='the desired screen size (fallback: {}x{})'.format(*config.size))
|
2018-02-13 14:51:41 +01:00
|
|
|
parser.add_argument(
|
2018-02-18 19:01:14 +01:00
|
|
|
'-f', '--max-fps', type=int, metavar='FPS',
|
2018-02-13 14:51:41 +01:00
|
|
|
help='the desired maximum FPS (fallback: {})'.format(config.max_fps))
|
2018-02-19 10:55:55 +01:00
|
|
|
parser.add_argument(
|
2018-03-01 18:13:23 +01:00
|
|
|
'--mute', '-m', action='store_true', default=None, dest='muted',
|
2018-02-19 10:55:55 +01:00
|
|
|
help='mute all sounds (fallback: {})'.format(config.muted))
|
|
|
|
parser.add_argument('--unmute', action='store_false', dest='muted',
|
|
|
|
help='unmute sound')
|
|
|
|
parser.add_argument(
|
|
|
|
'--music-volume', type=float, metavar='VOL', dest='musicvol',
|
|
|
|
help='between 0.0 and 1.0 (fallback: {})'.format(config.musicvol))
|
2018-03-01 18:13:23 +01:00
|
|
|
parser.add_argument(
|
|
|
|
'--server', action='store_true', default=None,
|
|
|
|
help='enable server (fallback: {})'.format(config.server))
|
|
|
|
parser.add_argument('--no-server', action='store_false', dest='server',
|
|
|
|
help='disable server')
|
|
|
|
parser.add_argument(
|
2018-03-10 12:19:12 +01:00
|
|
|
'--host', help='host to bind server to (fallback: {})'.format(
|
|
|
|
config.host))
|
2018-03-01 18:13:23 +01:00
|
|
|
parser.add_argument(
|
|
|
|
'--port', type=int,
|
|
|
|
help='port for server to listen on (fallback: {})'.format(config.port))
|
|
|
|
parser.add_argument(
|
2018-03-10 12:19:12 +01:00
|
|
|
'-t', '--timeout', type=float,
|
|
|
|
help='socket operations timeout in seconds (fallback: {})'.format(
|
|
|
|
config.timeout))
|
|
|
|
parser.add_argument(
|
2018-03-01 18:13:23 +01:00
|
|
|
'--head', action='store_false', default=None, dest='headless',
|
|
|
|
help='run server with graphics and sound (fallback: {})'.format(
|
|
|
|
not config.headless))
|
|
|
|
parser.add_argument('--headless', action='store_true',
|
|
|
|
help='run server without graphics or sound')
|
2018-02-13 14:51:41 +01:00
|
|
|
args = parser.parse_args()
|
2018-02-14 16:26:52 +01:00
|
|
|
if args.defaultcfg is not None:
|
|
|
|
with open(SETTINGS) as settings: args.defaultcfg.write(settings.read())
|
|
|
|
args.defaultcfg.close()
|
|
|
|
exit()
|
2018-02-13 14:51:41 +01:00
|
|
|
|
|
|
|
# Manipulate config
|
2018-03-01 18:13:23 +01:00
|
|
|
if args.config:
|
|
|
|
config.config.read(args.config)
|
|
|
|
config.parse()
|
2018-02-13 14:51:41 +01:00
|
|
|
config.read_args(args)
|
2018-02-02 17:33:22 +01:00
|
|
|
|
|
|
|
# Main loop
|
2018-02-26 15:02:11 +01:00
|
|
|
with Game(config) as game:
|
|
|
|
if config.server:
|
2018-02-27 10:34:00 +01:00
|
|
|
socket_thread = Thread(target=game.remote_control)
|
|
|
|
socket_thread.daemon = True # make it disposable
|
|
|
|
socket_thread.start()
|
2018-02-27 16:25:58 +01:00
|
|
|
while game.update(): game.control(*game.sockinp)
|
2018-02-26 15:02:11 +01:00
|
|
|
else:
|
2018-02-27 10:34:00 +01:00
|
|
|
while game.update(): game.user_control()
|