initial commit backend

This commit is contained in:
daniel
2019-06-17 18:17:59 +02:00
commit ccc9c44ebd
13 changed files with 468 additions and 0 deletions

131
.gitignore vendored Normal file
View File

@@ -0,0 +1,131 @@
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that dont work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# End of https://www.gitignore.io/api/python

34
actors.py Normal file
View File

@@ -0,0 +1,34 @@
import chat.response as response
class ActorManager:
def __init__(self):
self.actors = dict()
def add_character(self, char_dict):
name = char_dict['name']
self.actors[name] = char_dict
print(self.actors)
return response.build_new_character(None, char_dict)
def remove_actor(self, name):
if not name in self.actors:
return None # TODO FEHLERMELDUNG, CHARAKTER EXISTIERT NICHT
self.actors.pop(name)
return response.build_remove_actor(None, None, name)
def move_by(self, name, delta):
if not name in self.actors:
return None # TODO FEHLERMELDUNG, CHARAKTER EXISTIERT NICHT
actor = self.actors[name]
actor.tick += delta
return response.build_new_character(None, None, actor)
def move_to(self, name, tick):
if not name in self.actors:
return None # TODO FEHLERMELDUNG, CHARAKTER EXISTIERT NICHT
actor = self.actors[name]
actor.state = TickState.ACTING.value
actor.tick = tick
return response.build_new_character(None, None, actor)

0
chat/__init__.py Normal file
View File

79
chat/chat_commands.py Normal file
View File

@@ -0,0 +1,79 @@
import chat.dice as dice
import chat.response as response
from chat.command_exception import CommandException
import re
class CommandHandler:
def __init__(self):
self.commands = {
'/roll': self.custom_roll,
'/r': self.custom_roll,
'/whisper': self.whisper,
'/w': self.whisper
}
self.skill_map = {'Tera': {'Akrobatik': 25,
'Arkane Kunde': 10}
}
def handle(self, char, content):
message = content.strip('\n')
if message[0] == '.':
return self.skill_roll(char, message)
else:
message = message.split(' ', 1)
if (len(message) > 1) and (message[0] in self.commands):
func = self.commands[message[0]]
r = func(char, message)
return r
return response.build_public_message(char, content)
def custom_roll(self, char, message):
pattern = '(?P<dice_num>\d+)(?:d|w)(?P<dice_val>\d+)(?P<maths>(?:(?:\+|\-)\d+)*)'
command = re.sub('\s+', '', message[1])
match = re.match(pattern, command)
if match is None:
return response.build_system_message(char,
'Ungültige Formatierung des Befehls: {}'.format(' '.join(message)))
eyes, result = dice.custom_roll(int(match.group('dice_num')),
int(match.group('dice_val')),
match.group('maths'))
return response.build_dice_roll(char, ' '.join(message), eyes, result)
def skill_roll(self, char, message):
print(char)
print(message)
print(self.skill_map)
if char not in self.skill_map:
return response.build_system_message(char, 'Ungültiger Charakter: {}'.format(char))
pattern = '(?P<skill>[a-zA-ZäöüÄÖÜß .&]+)(?P<maths>(?:\s*(?:\+|\-)\s*\d+)*)(?P<type>[nrs]?)'
match = re.match(pattern, message)
if match is None:
return response.build_system_message(char,
'Unültige Formatierung des Befehls: {}'.format(message))
skills = self.skill_map[char]
if match.group('skill')[1:] not in skills:
return response.build_system_message(char,
'{} hat die Fertigkeit {} nicht.'.format(char, match.group('skill')))
skill = skills[match.group('skill')[1:]]
type_ = match.group('type')
if len(type_) == 0 or type_ == 'n':
type_ = dice.RollTypes.NORMAL
elif type_ == 'r':
type_ = dice.RollTypes.RISKY
else:
type_ = dice.RollTypes.SAFE
eyes, result = dice.skill_roll(skill,
match.group('maths'),
type_)
print(eyes, result)
return response.build_dice_roll(char, message, eyes, result)
def whisper(self, char, message):
recipient, message = message.split(' ', 1)
return response.build_private_message(char, message, recipient)

View File

@@ -0,0 +1,3 @@
class CommandException(Exception):
def __init__(self, *args):
super().__init__(*args)

47
chat/dice.py Normal file
View File

@@ -0,0 +1,47 @@
from enum import auto, Enum
import random
class RollTypes(Enum):
NORMAL = auto()
RISKY = auto()
SAFE = auto()
def skill_roll(skill, modifiers, type_=RollTypes.NORMAL):
eyes, result = roll_dice(type_)
result += skill
if len(modifiers) > 0:
result += eval(modifiers)
return eyes, result
def custom_roll(n, eyes, modifiers=None):
eyes = n_random_eyes(n, eyes)
result = sum(eyes)
if len(modifiers) > 0:
result += eval(modifiers)
return eyes, result
def roll_dice(type_):
if type_ is RollTypes.NORMAL:
eyes = n_random_eyes(2)
result = sum(eyes)
elif type_ is RollTypes.RISKY:
eyes = n_random_eyes(4)
eyes.sort(reverse=True)
result = sum(eyes[:2])
else:
eyes = n_random_eyes(2)
result = max(eyes)
return eyes, result
def n_random_eyes(n, eyes=10):
return [random.randint(1, eyes) for _ in range(n)]

67
chat/response.py Normal file
View File

@@ -0,0 +1,67 @@
from abc import ABC
from dataclasses import dataclass
from events import Events
@dataclass
class Response(ABC):
sender: str
event: Events
def to_json(self) -> dict:
d = self.__dict__
d.pop('event')
return d
@dataclass
class NewCharacter(Response):
character: dict
@dataclass
class RemoveActor(Response):
message: str
name: str
@dataclass
class PublicMessage(Response):
message: str
@dataclass
class PrivateMessage(Response):
message: str
recipient: str
@dataclass
class DiceRoll(Response):
message: str
dice: list
result: int
def build_dice_roll(sender, message, dice, result):
return DiceRoll(sender, Events.DICE_ROLL, message, dice, result)
def build_new_character(sender, char_dict):
return NewCharacter(sender, Events.NEW_CHARACTER, char_dict)
def build_remove_actor(sender, message, name):
return RemoveActor(sender, Events.REMOVE_ACTOR, message, name)
def build_private_message(sender, message, recipient):
return PrivateMessage(sender, Events.PRIVATE_CHAT, message, recipient)
def build_public_message(sender, message):
return PublicMessage(sender, Events.PUBLIC_CHAT, message)
def build_system_message(user, message):
return PrivateMessage('System', Events.SYSTEM_MESSAGE, message, user)

14
events.py Normal file
View File

@@ -0,0 +1,14 @@
from enum import Enum
class Events(Enum):
PUBLIC_CHAT = 'public message'
PRIVATE_CHAT = 'private message'
SYSTEM_MESSAGE = 'system message'
DICE_ROLL = 'dice roll'
NEW_CHARACTER = 'new character'
REMOVE_ACTOR = 'remove actor'
ACTOR_ADDED = 'actor added'
USER_EDITED = 'user edited'
USER_ADDED = 'user added'
USER_REMOVED = 'user removed'

66
main.py Executable file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python
# WS server example
import eventlet
import socketio
from actors import ActorManager
from events import Events
from chat.chat_commands import CommandHandler
from users.users import UserManager
sio = socketio.Server()
app = socketio.WSGIApp(sio)
actor_manager = ActorManager()
command_handler = CommandHandler()
user_manager = UserManager()
@sio.on('connect')
def connect(sid, environ):
print("CONNECTED: {}".format(sid))
for user in user_manager.get_users():
sio.emit(Events.USER_ADDED.value, user, room=sid)
@sio.on('disconnect')
def disconnect(sid):
print("DISCONNECTED: {}".format(sid))
name = user_manager.remove_user(sid)
sio.emit(Events.USER_REMOVED.value, name)
@sio.on(Events.PUBLIC_CHAT.value)
def message(sid, data):
# print(data)
response = command_handler.handle(data['char'], data['message'])
# print(response)
# print("\n")
sio.emit(response.event.
value, response.to_json())
@sio.on(Events.NEW_CHARACTER.value)
def message(sid, data):
print("INCOMING\n", data)
actor_manager.add_character(data)
sio.emit(Events.NEW_CHARACTER.value, data)
@sio.on(Events.USER_EDITED.value)
def message(sid, data):
print("USER EDITED: ", sid, data)
if user_manager.has_user(data['old']):
user_manager.remove_user(sid)
sio.emit(Events.USER_REMOVED.value, data['old'])
user_manager.add_user(data['new']['characterName'], sid)
d = data['new']
d['characterLoaded'] = True
d['skills'] = 'Akrobatik\nArkane Kunde\nSeefahrt'
sio.emit(Events.USER_EDITED.value, d, room=sid)
sio.emit(Events.USER_ADDED.value, data['new']['characterName'])
if __name__ == '__main__':
eventlet.wsgi.server(eventlet.listen(('', 3101)), app)

3
test.py Normal file
View File

@@ -0,0 +1,3 @@
from chat import chat_commands
print(chat_commands)

0
users/__init__.py Normal file
View File

0
users/sheet_reader.py Normal file
View File

24
users/users.py Normal file
View File

@@ -0,0 +1,24 @@
import chat.response as response
class UserManager:
def __init__(self):
self.users = dict()
self.skill_map = dict()
def add_user(self, name, sid):
self.users[sid] = name
print("ADDED USER, USERS NOW IS:", self.users)
return name
def remove_user(self, sid):
print("REMOVING USER:", sid)
if sid in self.users:
return self.users.pop(sid)
def has_user(self, name):
return name in self.users.values()
def get_users(self):
return self.users.values()