Parse config file
This commit is contained in:
parent
6acc4202a6
commit
f3d93f6a29
|
@ -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
|
||||
```
|
||||
|
|
50
axuy/peer.py
50
axuy/peer.py
|
@ -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()
|
||||
|
|
|
@ -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
|
119
axuy/view.py
119
axuy/view.py
|
@ -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()
|
||||
|
|
6
setup.py
6
setup.py
|
@ -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']})
|
||||
|
|
|
@ -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 |
Loading…
Reference in New Issue