Parse config file

This commit is contained in:
Nguyễn Gia Phong 2019-07-15 17:11:40 +07:00
parent 6acc4202a6
commit f3d93f6a29
7 changed files with 179 additions and 56 deletions

View File

@ -9,10 +9,10 @@ Mininalist first-person shooter
The game is still under development. For testing, first install GLFW version 3.3
(or higher), then install the game in editable mode:
```bash
```sh
git clone https://github.com/McSinyx/axuy.git
pip3 install -e axuy
# Example launch commands
axuy --host localhost --port 12345 --width 1234 --height 567
axuy --seeder=localhost:12345 --host localhost --port 8888 --width 1234 --height 567
axuy # default to port 12345
axuy --seeder=localhost:12345 --port 6789
```

View File

@ -16,6 +16,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with Axuy. If not, see <https://www.gnu.org/licenses/>.
__version__ = '0.0.1'
__doc__ = 'Axuy main loop'
from argparse import ArgumentParser, RawTextHelpFormatter
@ -25,7 +26,7 @@ from socket import socket, SOCK_DGRAM, SOL_SOCKET, SO_REUSEADDR
from threading import RLock, Thread, Semaphore
from .misc import mapgen, mapidgen
from .view import Pico, View
from .view import ConfigReader, Pico, View
class Peer:
@ -33,13 +34,13 @@ class Peer:
TODO: Documentation
"""
def __init__(self, args):
if args.seeder is None:
def __init__(self, config):
if config.seeder is None:
mapid = mapidgen()
self.peers = []
else:
client = socket()
host, port = args.seeder.split(':')
host, port = config.seeder.split(':')
self.peers = [(host, int(port))]
client.connect(*self.peers)
data = loads(client.recv(1024))
@ -47,11 +48,12 @@ class Peer:
self.peers.extend(data['peers'])
self.semaphore, lock = Semaphore(0), RLock()
self.addr = args.host, args.port
self.addr = config.host, config.port
self.space = mapgen(mapid)
self.pico = Pico(self.addr, self.space, (0, 0, 0))
self.view = View(self.addr, self.pico, self.space,
args.width, args.height, lock)
config.size, config.vsync,
{'key': config.key, 'mouse': config.mouse}, lock)
data_server = Thread(target=self.serve, args=(mapid,))
data_server.daemon = True
@ -110,15 +112,37 @@ class Peer:
def main():
"""Parse command-line arguments and start main loop."""
"""Parse arguments and start main loop."""
# Read configuration files
config = ConfigReader()
config.parse()
# Parse command-line arguments
parser = ArgumentParser(usage='%(prog)s [options]',
formatter_class=RawTextHelpFormatter)
parser.add_argument('--seeder')
parser.add_argument('--host')
parser.add_argument('--port', type=int)
parser.add_argument('--width', type=int, help='window width')
parser.add_argument('--height', type=int, help='window height')
with Peer(parser.parse_args()) as peer:
parser.add_argument('-v', '--version', action='version',
version='Axuy {}'.format(__version__))
parser.add_argument(
'--host',
help='host to bind this peer to (fallback: {})'.format(config.host))
parser.add_argument(
'--port', type=int,
help='port to bind this peer to (fallback: {})'.format(config.port))
parser.add_argument('--seeder',
help='address of the peer that created the map')
parser.add_argument(
'-s', '--size', type=int, nargs=2, metavar=('X', 'Y'),
help='the desired screen size (fallback: {}x{})'.format(*config.size))
parser.add_argument(
'--vsync', action='store_true', default=None,
help='enable vertical synchronization (fallback: {})'.format(
config.vsync))
parser.add_argument('--no-vsync', action='store_false', dest='server',
help='disable vertical synchronization')
args = parser.parse_args()
config.read_args(args)
with Peer(config) as peer:
while peer.view.is_running:
for _ in peer.peers: peer.semaphore.release()
peer.view.update()

15
axuy/settings.ini Normal file
View File

@ -0,0 +1,15 @@
[Graphics]
Screen width: 640
Screen height: 480
V-sync: yes
[Control]
Move forward: w
Move backward: s
Move left: a
Move right: d
Primary: MOUSE_BUTTON_1
[Peer]
Host: localhost
Port: 12345

View File

@ -18,25 +18,35 @@
__doc__ = 'Axuy module for map class'
from configparser import ConfigParser
from itertools import product
from os.path import join as pathjoin, pathsep
from math import sqrt
from random import randint
from warnings import warn
import glfw
import moderngl
import numpy as np
from appdirs import AppDirs
from PIL import Image
from pyrr import Matrix44
from .pico import SHARD_LIFE, Picobot
from .misc import abspath, color, neighbors
CONTROL_ALIASES = (('Move left', 'left'), ('Move right', 'right'),
('Move forward', 'forward'), ('Move backward', 'backward'),
('Primary', '1st'))
MOUSE_PATTERN = 'MOUSE_BUTTON_[1-{}]'.format(glfw.MOUSE_BUTTON_LAST + 1)
INVALID_CONTROL_ERR = '{}: {} is not recognized as a valid control key'
FOV_MIN = 30
FOV_MAX = 120
FOV_INIT = (FOV_MIN+FOV_MAX) // 2
CONWAY = 1.303577269034
MOUSE_SPEED = 1/8
EDGE_BRIGHTNESS = 0.5
EDGE_BRIGHTNESS = 1/3
QUAD = np.float32([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]).tobytes()
OXY = np.float32([[0, 0, 0], [1, 0, 0], [1, 1, 0],
@ -69,6 +79,67 @@ with open(abspath('shaders/gauss.frag')) as f: GAUSS_FRAGMENT = f.read()
with open(abspath('shaders/comb.frag')) as f: COMBINE_FRAGMENT = f.read()
class ConfigReader:
"""Object reading and processing command-line arguments
and INI configuration file for Axuy.
Attributes
----------
config : ConfigParser
INI configuration file parser.
host : str
Host to bind the peer to.
port : int
Port to bind the peer to.
seeder : str
Address of the peer that created the map.
size : (int, int)
GLFW window resolution.
vsync : bool
Vertical synchronization.
key, mouse : dict of (str, int)
Input control.
"""
def __init__(self):
dirs = AppDirs(appname='axuy', appauthor=False, multipath=True)
parents = dirs.site_config_dir.split(pathsep)
parents.append(dirs.user_config_dir)
filenames = [pathjoin(parent, 'settings.ini') for parent in parents]
self.config = ConfigParser()
self.config.read(abspath('settings.ini')) # default configuration
self.config.read(filenames)
# Fallback to None when attribute is missing
def __getattr__(self, name): return None
def parse(self):
"""Parse configurations."""
self.size = (self.config.getint('Graphics', 'Screen width'),
self.config.getint('Graphics', 'Screen height'))
self.vsync = self.config.getboolean('Graphics', 'V-sync')
self.host = self.config.get('Peer', 'Host')
self.port = self.config.getint('Peer', 'Port')
self.key, self.mouse = {}, {}
for cmd, alias in CONTROL_ALIASES:
i = self.config.get('Control', cmd)
try:
self.mouse[alias] = getattr(glfw, i.upper())
except AttributeError:
try:
self.key[alias] = getattr(glfw, 'KEY_{}'.format(i.upper()))
except AttributeError:
raise ValueError(INVALID_CONTROL_ERR.format(cmd, i))
def read_args(self, arguments):
"""Read and parse a argparse.ArgumentParser.Namespace."""
for option in ('size', 'vsync', 'host', 'port', 'seeder'):
value = getattr(arguments, option)
if value is not None: setattr(self, option, value)
class Pico(Picobot):
def look(self, window, xpos, ypos):
"""Look according to cursor position.
@ -91,6 +162,12 @@ class View:
Protagonist whose view is the camera.
space : np.ndarray of shape (12, 12, 9) of bools
3D array of occupied space.
size : (int, int)
GLFW window resolution.
vsync : bool
Vertical synchronization.
ctl : dict of (str, int)
Input control.
lock : RLock
Compound data lock to avoid size change during iteration.
@ -141,7 +218,7 @@ class View:
timestamp in seconds of the previous frame.
"""
def __init__(self, address, camera, space, width, height, lock):
def __init__(self, address, camera, space, size, vsync, ctl, lock):
# Create GLFW window
if not glfw.init(): raise RuntimeError('Failed to initialize GLFW')
glfw.window_hint(glfw.CLIENT_API, glfw.OPENGL_API)
@ -151,10 +228,12 @@ class View:
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.DEPTH_BITS, 24)
width, height = size
self.window = glfw.create_window(width, height, 'Axuy', None, None)
if not self.window:
glfw.terminate()
raise RuntimeError('Failed to create GLFW window')
self.key, self.mouse = ctl['key'], ctl['mouse']
# Attributes for event-handling
self.camera = camera
@ -166,17 +245,23 @@ class View:
# Window's rendering and event-handling configuration
glfw.set_window_icon(self.window, 1, Image.open(abspath('icon.png')))
glfw.make_context_current(self.window)
glfw.swap_interval(1)
glfw.swap_interval(vsync)
glfw.set_window_size_callback(self.window, self.resize)
glfw.set_input_mode(self.window, glfw.CURSOR, glfw.CURSOR_DISABLED)
if glfw.raw_mouse_motion_supported():
glfw.set_input_mode(self.window, glfw.RAW_MOUSE_MOTION, True)
glfw.set_input_mode(self.window, glfw.STICKY_KEYS, True)
glfw.set_cursor_pos_callback(self.window, self.camera.look)
self.fov = FOV_INIT
glfw.set_scroll_callback(self.window, self.zoom)
glfw.set_mouse_button_callback(self.window, self.shoot)
try:
if glfw.raw_mouse_motion_supported():
glfw.set_input_mode(self.window, glfw.RAW_MOUSE_MOTION, True)
except AttributeError:
warn('Your GLFW version appear to be lower than 3.3, '
'which might cause stuttering camera rotation.',
category=RuntimeWarning)
# Create OpenGL context
self.context = context = moderngl.create_context()
context.enable_only(moderngl.DEPTH_TEST)
@ -272,36 +357,36 @@ class View:
Present as a callback for GLFW MouseButton event.
"""
if button == glfw.MOUSE_BUTTON_LEFT and action == glfw.PRESS:
if button == self.mouse['1st'] and action == glfw.PRESS:
self.camera.shoot()
@property
def width(self):
def width(self) -> int:
"""Viewport width."""
return self.context.viewport[2]
@property
def height(self):
def height(self) -> int:
"""Viewport height."""
return self.context.viewport[3]
@property
def pos(self):
def pos(self) -> np.float32:
"""Camera position in a NumPy array."""
return self.camera.pos
@property
def right(self):
def right(self) -> np.float32:
"""Camera right direction."""
return self.camera.rot[0]
@property
def upward(self):
def upward(self) -> np.float32:
"""Camera upward direction."""
return self.camera.rot[1]
@property
def forward(self):
def forward(self) -> np.float32:
"""Camera forward direction."""
return self.camera.rot[2]
@ -316,7 +401,7 @@ class View:
return np.float32(3240 / (self.fov + 240))
@property
def fps(self):
def fps(self) -> float:
"""Currently rendered frames per second."""
return self.camera.fps
@ -399,10 +484,10 @@ class View:
# Character movements
right, upward, forward = 0, 0, 0
if self.is_pressed(glfw.KEY_UP): forward += 1
if self.is_pressed(glfw.KEY_DOWN): forward -= 1
if self.is_pressed(glfw.KEY_LEFT): right -= 1
if self.is_pressed(glfw.KEY_RIGHT): right += 1
if self.is_pressed(self.key['forward']): forward += 1
if self.is_pressed(self.key['backward']): forward -= 1
if self.is_pressed(self.key['left']): right -= 1
if self.is_pressed(self.key['right']): right += 1
self.camera.move(right, upward, forward)
self.fb.use()

View File

@ -1,2 +0,0 @@
[bdist_wheel]
universal=1

View File

@ -27,6 +27,8 @@ setup(
'Topic :: Games/Entertainment :: First Person Shooters'],
keywords='fps p2p opengl glfw',
packages=['axuy'],
install_requires=['numpy', 'pyrr', 'moderngl', 'glfw>=1.8', 'Pillow'],
package_data={'axuy': ['map.npy', 'shaders/*', 'icon.png']},
install_requires=['appdirs', 'numpy', 'pyrr',
'moderngl', 'glfw>=1.8', 'Pillow'],
package_data={'axuy': ['map.npy', 'shaders/*',
'icon.png', 'settings.ini']},
entry_points={'console_scripts': ['axuy = axuy.peer:main']})

View File

@ -1,4 +1,4 @@
<?xml version="1.1" standalone="no"?>
<?xml version="1.0" standalone="no"?>
<!--
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0
International License. To view a copy of this license, visit
@ -6,25 +6,24 @@
to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
-->
<svg xmlns="http://www.w3.org/2000/svg"
width="576" height="576"
viewBox="-288 -288 576 576">
width="576" height="576" viewBox="-288 -288 576 576">
<title>Axuy icon in Scalable Vector Graphics</title>
<path fill="#eeeeec"
d="M -288 0
C 0 0, 0 288, 242.1782 242.1782
C 288 196.3563, 288 126.2, 288 0
C 288 -126.2, 288 -196.3563, 242.1782 -242.1782
C 196.3563 -288, 126.2 -288, 0 -288
C -126.2 -288, -196.3563 -288, -242.1782 -242.1782
Q 242.1782 -242.1782, -288 0
Z"/>
d="M -288 0
C 0 0, 0 288, 242.1782 242.1782
C 288 196.3563, 288 126.2, 288 0
C 288 -126.2, 288 -196.3563, 242.1782 -242.1782
C 196.3563 -288, 126.2 -288, 0 -288
C -126.2 -288, -196.3563 -288, -242.1782 -242.1782
Q 242.1782 -242.1782, -288 0
Z"/>
<path fill="#2e3436"
d="M -288 0
C -288 126.2, -288 196.3563, -242.1782 242.1782
C -196.3563 288, -126.2 288, 0 288
C 126.2 288, 196.3563 288, 242.1782 242.1782
C 0 288, 0 0, -288 0
C -288 -126.2, -288 -196.3563, -242.1782 -242.1782
Q 242.1782 -242.1782, -288 0
Z"/>
d="M -288 0
C -288 126.2, -288 196.3563, -242.1782 242.1782
C -196.3563 288, -126.2 288, 0 288
C 126.2 288, 196.3563 288, 242.1782 242.1782
C 0 288, 0 0, -288 0
C -288 -126.2, -288 -196.3563, -242.1782 -242.1782
Q 242.1782 -242.1782, -288 0
Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB