Skip to content

Commit

Permalink
Merge pull request #4 from sumpfralle/arguments-logging-style-loader
Browse files Browse the repository at this point in the history
Muitiple changes: argument parsing, logging, code style and scenario loading
  • Loading branch information
Trilarion committed Jun 29, 2017
2 parents 9e7db55 + b5bb7a3 commit d7dc348
Show file tree
Hide file tree
Showing 22 changed files with 557 additions and 338 deletions.
8 changes: 3 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@
/.idea/misc.xml

# Python

__pycache__
*.egg-info

# Sphinx Doc
# vim
.*.swp

# Sphinx Doc
*.doctree
_build
environment.pickle

# ours

/data/manual
/build


5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ install:
- pip install -r requirements.txt

script:
- cd test
- python test_runner.py
- python test/test_runner.py

notifications:
email: false
email: false
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ of the underlying C++ Qt 5.X framework is very useful since PyQt5 is almost 100%

### Start

- Run file "./source/start.py" with working directory "./".
- Start with command line parameter "debug" for (more) output on the console.
- A folder with log files and settings is created under "user folder/Imperialism Remake User Data" where "user folder" is the typical user folder of your system (Windows C:/Users/XXX/).
- Run file `source/imperialism_remake/start.py`
- Start with command line parameter "--debug" for (more) output on the console.
- A folder with log files and settings is created under "user folder/Imperialism Remake User Data" where "user folder" is the typical user folder of your system (Windows: C:/Users/XXX/, Linux: ~/.config/).

### IDE

I use [PyCharm Community Edition](http://www.jetbrains.com/pycharm/download/). Since the .idea folder is contained the project
I use [PyCharm Community Edition](http://www.jetbrains.com/pycharm/download/). Since the `.idea` folder is contained the project
can probably be opened directly with PyCharm. Another nice IDE is [Spyder](https://code.google.com/p/spyderlib/).

### Tools
Expand All @@ -75,4 +75,4 @@ can probably be opened directly with PyCharm. Another nice IDE is [Spyder](https

### Packaging

Under construction.
Under construction.
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[bdist_wheel]
python-tag = py3
python-tag = py3

[flake8]
max-line-length = 119
23 changes: 17 additions & 6 deletions source/imperialism_remake/base/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
Only static values here.
"""

import os
from enum import unique
import os

from imperialism_remake.base import switches
from imperialism_remake.lib import utils
Expand All @@ -34,13 +34,13 @@ def extend(path, *parts):
extended = os.path.join(path, *parts)
if switches.FILE_EXISTENCE_CHECK and not os.path.exists(extended):
raise RuntimeError('constructed path {} does not exist'.format(extended))
return extended
return os.path.abspath(os.path.realpath(extended))


# TODO track used resources by the program

#: base folders (do not directly contain data)
DATA_FOLDER = extend('.', 'data')
DATA_FOLDER = extend(os.path.dirname(__file__), os.path.pardir, 'data')
ARTWORK_FOLDER = extend(DATA_FOLDER, 'artwork')

#: scenarios (save games)
Expand Down Expand Up @@ -97,18 +97,23 @@ class Option(utils.AutoNumberedEnum):
SOUNDTRACK_MUTE = () # bool
SOUNDTRACK_VOLUME = () # int from 0 to 100


Options = list(Option)

#: default values for the Options
Option.LOCALSERVER_OPEN.default = False
Option.LOCALSERVER_NAME.default = 'Alice'
Option.LOCALCLIENT_NAME.default = 'Bob'
Option.MAINWINDOW_BOUNDS.default = None # no bounds
Option.MAINWINDOW_FULLSCREEN.default = True # we start full screen (can be unset by the program for some linux desktop environments
Option.MAINWINDOW_FULLSCREEN_SUPPORTED.default = True # we assume it is until we detect it isn't
# no bounds
Option.MAINWINDOW_BOUNDS.default = None
# we start full screen (can be unset by the program for some linux desktop environments)
Option.MAINWINDOW_FULLSCREEN.default = True
# we assume it is until we detect it isn't
Option.MAINWINDOW_FULLSCREEN_SUPPORTED.default = True
Option.SOUNDTRACK_MUTE.default = False
Option.SOUNDTRACK_VOLUME.default = 50


@unique
class C(utils.AutoNumberedEnum):
"""
Expand All @@ -120,6 +125,7 @@ class C(utils.AutoNumberedEnum):
SYSTEM = ()
LOBBY = ()


@unique
class M(utils.AutoNumberedEnum):
"""
Expand All @@ -140,6 +146,7 @@ class M(utils.AutoNumberedEnum):
LOBBY_SCENARIO_PREVIEW = ()
LOBBY_CONNECTED_CLIENTS = ()


@unique
class TileDirections(utils.AutoNumberedEnum):
"""
Expand Down Expand Up @@ -170,6 +177,7 @@ class ScenarioProperty(utils.AutoNumberedEnum):
RULES = ()
GAME_YEAR_RANGE = ()


@unique
class NationProperty(utils.AutoNumberedEnum):
"""
Expand All @@ -181,6 +189,7 @@ class NationProperty(utils.AutoNumberedEnum):
DESCRIPTION = ()
CAPITAL_PROVINCE = ()


@unique
class ProvinceProperty(utils.AutoNumberedEnum):
"""
Expand All @@ -191,6 +200,7 @@ class ProvinceProperty(utils.AutoNumberedEnum):
NATION = ()
TOWN_LOCATION = ()


#: name of properties file in a zipped scenario file
SCENARIO_FILE_PROPERTIES = 'scenario-properties'
#: name of maps file in a zipped scenario file
Expand All @@ -207,6 +217,7 @@ class ClientConfiguration:
"""
OVERVIEW_WIDTH = 'overview.width'


@unique
class OverviewMapMode(utils.AutoNumberedEnum):
"""
Expand Down
64 changes: 39 additions & 25 deletions source/imperialism_remake/base/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,33 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>

"""
Using Signals of Qt, we refine on the network Client class in lib/network.py. Channels are introduced which have
names and a signal to connect/disconnect to.
Using Signals of Qt, we refine on the network Client class in lib/network.py. Channels are
introduced which have names and a signal to connect/disconnect to.
"""

import logging

import PyQt5.QtCore as QtCore
import PyQt5.QtNetwork as QtNetwork

from imperialism_remake.base import constants
import imperialism_remake.lib.network as lib_network


logger = logging.getLogger(__name__)


class NetworkClient(lib_network.ExtendedTcpSocket):
"""
Extending the Client class (wrapper around QTcpSocket sending and receiving messages) with channels (see Channel)
and processing logic, as well as further wrapping the messages (specifying the channel as address).
Extending the Client class (wrapper around QTcpSocket sending and receiving messages) with
channels (see Channel) and processing logic, as well as further wrapping the messages
(specifying the channel as address).
The Channel implementation is completely hidden, one just calls the connect/disconnect methods in this class.
The Channel implementation is completely hidden, one just calls the connect/disconnect methods
in this class.
This is kind of a service-subscription pattern and allows for reducing complexity and decoupling of the message
transport and message processing.
This is kind of a service-subscription pattern and allows for reducing complexity and
decoupling of the message transport and message processing.
"""

def __init__(self, socket: QtNetwork.QTcpSocket = None):
Expand All @@ -49,8 +56,8 @@ def __init__(self, socket: QtNetwork.QTcpSocket = None):

def create_new_channel(self, channel: constants.C):
"""
Given a new channel name (cannot already exist, otherwise an error will be thrown) creates a channel of
this name.
Given a new channel name (cannot already exist, otherwise an error will be thrown) creates
a channel of this name.
:param channel: Name of the channel.
"""
Expand All @@ -60,11 +67,12 @@ def create_new_channel(self, channel: constants.C):

def remove_channel(self, channel: constants.C, ignore_not_existing=False):
"""
Given a channel name, removes this channel if it is existing. Raises an error if not existing and
ignore_not_existing is not True.
Given a channel name, removes this channel if it is existing. Raises an error if not
existing and ignore_not_existing is not True.
:param channel: Name of the channel
:param ignore_not_existing: If True does not raise an error if channel with this name is already existing.
:param ignore_not_existing: If True does not raise an error if channel with this name is
already existing.
"""
if channel in self.channels:
del self.channels[channel]
Expand All @@ -75,7 +83,8 @@ def connect_to_channel(self, channel: constants.C, callback: callable):
"""
Connect a callback to a channel with a specific name.
As a convenience shortcut, if a channel of this name is not yet existing, creates a new one before.
As a convenience shortcut, if a channel of this name is not yet existing, creates a new one
before.
:param channel: Name of the channel
:param callback: A callable
Expand All @@ -86,8 +95,8 @@ def connect_to_channel(self, channel: constants.C, callback: callable):

def disconnect_from_channel(self, channel: constants.C, callback: callable):
"""
Given a channel name (which must exist, otherwise an error is raised) disconnects a callback from this
channel.
Given a channel name (which must exist, otherwise an error is raised) disconnects a
callback from this channel.
:param channel: Name of the channel
:param callback: A callable
Expand All @@ -98,30 +107,35 @@ def disconnect_from_channel(self, channel: constants.C, callback: callable):

def _process(self, letter):
"""
A letter (a message) was received from the underlying ExtendedTcpSocket framework. Not intended for outside use.
Here we assume that it's a dictionary with keys 'channel' and 'content' where the value for key 'channel' is
the name of the channel and the value of the key 'content' is the message.
A letter (a message) was received from the underlying ExtendedTcpSocket framework.
Not intended for outside use. Here we assume that it's a dictionary with keys 'channel'
and 'content' where the value for key 'channel' is the name of the channel and the value of
the key 'content' is the message.
We get the corresponding Channel object and emit its received signal.
:param letter: The letter that was received
"""
print('network client received letter: {}'.format(letter))
logger.debug('network client received letter: {}'.format(letter))
channel = letter['channel']

# do we have receivers in this category
if channel not in self.channels:
raise RuntimeError('Received message on channel {} which is not existing.'.format(channel))
raise RuntimeError('Received message on channel {} which is not existing.'
.format(channel))

# send to channel and increase channel counter
self.channels[channel].message_counter += 1
self.channels[channel].received.emit(self, channel, letter['action'], letter['content'])

# note: channel with name channel_name may now already not be existing anymore (may be removed during processing)
# note: channel with name channel_name may now already not be existing anymore (may be
# removed during processing)

def send(self, channel: constants.C, action: constants.M, content=None):
"""
Given a channel and a action id and optionally a message content wraps them in one dict (a letter) and sends it.
Given a channel and a action id and optionally a message content wraps them in one dict
(a letter) and sends it.
:param channel: Channel id
:param action: action id
Expand All @@ -136,9 +150,9 @@ def send(self, channel: constants.C, action: constants.M, content=None):

class Channel(QtCore.QObject):
"""
Just contains a single signal you can connect/disconnect to/from and which sends a client and a value (the message)
when emitted. This way many outside parts can subscribe to this channel and using Qt also we can cross thread
boundaries easily.
Just contains a single signal you can connect/disconnect to/from and which sends a client and a
value (the message) when emitted. This way many outside parts can subscribe to this channel and
using Qt also we can cross thread boundaries easily.
"""

#: signal
Expand Down
35 changes: 2 additions & 33 deletions source/imperialism_remake/base/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
and base.constants and is specifically used by the project.
"""

import sys
import PyQt5.QtGui as QtGui

import imperialism_remake.base.constants as constants
Expand All @@ -37,37 +36,6 @@ def load_ui_icon(name):
return QtGui.QIcon(file_name)


def log_info(text):
"""
Prints a INFO message to stdout.
:param text:
"""
utils.log_write_entry(sys.stdout, "INFO", text)


def log_warning(text):
"""
Prints a WARNING message to stdout.
:param text:
"""
utils.log_write_entry(sys.stdout, "WARNING", text)


def log_error(text, exception=None):
"""
Prints a ERROR message and exception to stderr.
:param text:
:param exception:
"""
utils.log_write_entry(sys.stderr, "ERROR", text, exception)
# in case we send to somewhere else also send it to the standard error output (console)
if sys.stderr is not sys.__stderr__:
utils.log_write_entry(sys.__stderr__, "ERROR", text, exception)


def find_unused_resources():
"""
Report on unused resources.
Expand Down Expand Up @@ -101,7 +69,8 @@ def load_options(file_name):
# copy values that are in Constants.Options but not here
for option in constants.Options:
if option not in options and hasattr(option, 'default'):
options[option] = option.default
options[option] = option.default


def get_option(option):
"""
Expand Down
Loading

0 comments on commit d7dc348

Please sign in to comment.