Skip to content

feat: add some basic classes #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
Next Next commit
feat: add some basic classes - WIP
  • Loading branch information
Nostrademous committed Nov 8, 2021
commit c54cc2d668f3bbe6a3eb0ce2fe75c1150fb830e3
31 changes: 31 additions & 0 deletions src/classes/Dependency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does every file begin with "#" here?


from itertools import chain
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer importing full modules. E.g.:

import itertools

This helps in understanding where imported obhects come from.
For the typing module, prefer to only import individual objects though. E.g.:
Correct:

from typing import Union

Wrong:

import typing

This helps in keeping type signatures readable.


_dependencies = {}
_dependent_vars = set()

def addDependency(dep1: str, dep2: str):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functions, variables and also modules should be named in snake_case. There are a slot of style violations in this PR. I recommend reading https://www.python.org/dev/peps/pep-0008, the Python style guide.

global _dependencies, _dependent_vars
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid global variables where possible. Generally, you can always rewrite your code to share objects by other means instead. That said, in this case this seems fine for a prototype. Note that you only need to declare variables as global of you want to change them globally from a local scope. You can always read variables from outer scopes, so it is unnecessary to declare _dependencies as global here.

if dep1 in _dependencies.keys():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When iterating over a dict, you do not need to call the key method. dict iteration is already over its keys by default.

if not dep2 in _dependencies[dep1]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Membership tests should use not in. E.g.:

if dep2 not in _dependencies[dep1]:

_dependencies[dep1].append(dep2)
else:
_dependencies[dep1] = [dep2]
_dependent_vars = set(chain.from_iterable(_dependencies.values()))

class Dependency:
def _reset_dependent_vars(self, name):
for var in _dependencies[name]:
super().__delattr__(f"{var}")
if var in _dependencies:
self._reset_dependent_vars(var)

def __setattr__(self, name, value):
global _dependencies, _dependent_vars
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, these globals are not needed.

if name in _dependent_vars:
raise AttributeError("Cannot set this value.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error would be more helpful if it specified the attribute that cannot be set.

if name in _dependencies:
self._reset_dependent_vars(name)
name = f"_{name}"
super().__setattr__(name, value)
24 changes: 24 additions & 0 deletions src/classes/Modifiers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#

from Dependency import addDependency

class Mod:
def __init__(self, name: str, type: str, value, source: str, tags: dict = {}):
self.name = name
self.type = type
self.value = value
self.source = source
self.tags = tags
self.process()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since modifiers in our mod DB are always unique by ID and do not need to be ordered, we could simplify the code in mod_db.ModifierDatabase.add_entry by switching from a list for leaf nodes to a set. This nets us O(1) insertion over O(n) insertion accounting for the member check, as well as allowing us to get rid of a conditional in our code. The only complication is that we need to make modifiers.Modifier hashable.

Since we have decided that modifiers should always be inequal to every other modifier than themselves, this is possible to implement correctly even though modifiers.Modifier has mutable attributes:

class Modifier:
    ...

    def __eq__(self, other: Modifier) -> bool:
        return self is other

    def __hash__(self) -> int:
        return id(self)

This allows us to turn mod_db.ModifierDatabase into:

class ModifierDatabase:
    def __init__(self) -> None:
        self.db = collections.defaultdict(lambda: collections.defaultdict(set))

    def add_entry(self, entry: modifiers.Modifier) -> None:
        name = entry.name
        type_ = entry.type_
        self.db[name][type_].add(entry)

and subsequently:

def add_entry(self, entry: modifiers.Modifier) -> None:
    self.db[entry.name][entry.type_].add(entry)

def process(self):
if 'type' in self.tags.keys() and self.tags['type'] == "Multiplier":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Python, prefer EAFP (easier to ask for forgiveness than permission) over LBYL (look before you leap). So instead of checking whether a key exists, just try to access it and handle the exception if it doesnt exist.

addDependency(self.tags['var'].lower(), f"{self.type.lower()}_{self.name.lower()}")

def test():
Mod("Health", "BASE", 12, "", { "type": "Multiplier", "var": "Level" })

if __name__ == "__main__":
from Dependency import _dependencies
test()
print(_dependencies)
69 changes: 69 additions & 0 deletions src/classes/Player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#

from functools import cached_property
from math import floor

from Dependency import Dependency, addDependency

class Player(Dependency):
def __init__(self, level, strength):
self._level = level
self._strength = strength
self._flat_life = 0
self._more_life = 0
self._inc_life = 0
self._base_health = None
self._max_health = None

addDependency("level", "base_health")
addDependency("strength", "base_health")
addDependency("flat_life", "base_health")
addDependency("base_health", "max_health")
addDependency("more_life", "max_health")
addDependency("inc_life", "max_health")

@cached_property
def base_health(self):
print("Base Health calculated")
return 38 + self.level * 12 + floor(self.strength / 2) + self.flat_life

@cached_property
def max_health(self):
print("Max Health calculated")
return self.base_health * (1 + self.inc_life / 100) * (1 + self.more_life / 100)

@property
def level(self):
return self._level

@property
def strength(self):
return self._strength

@property
def flat_life(self):
return self._flat_life

@property
def more_life(self):
return self._more_life

@property
def inc_life(self):
return self._inc_life

def test():
player = Player(1, 20)
print(f"{player.max_health}")

player.level = 5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will shadow the level property, since it cannot be set without providing a setter method. E.g.:

@level.setter
def level(self, value: int) -> None:
    self._level = value

print(f"{player.max_health}")

player.more_life = 100
print(f"{player.max_health}")

player.flat_life = 100
print(f"{player.max_health}")

if __name__ == "__main__":
test()
Empty file added src/classes/__init__.py
Empty file.