mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Fix merge conflict
This commit is contained in:
commit
bb501d990f
1 changed files with 91 additions and 90 deletions
|
|
@ -1,18 +1,16 @@
|
|||
# Code structure and Utilities
|
||||
# Code Structure and Utilities
|
||||
|
||||
In this lesson we will set up the file structure for _EvAdventure_. We will make some
|
||||
In this lesson, we will set up the file structure for _EvAdventure_. We will make some
|
||||
utilities that will be useful later. We will also learn how to write _tests_.
|
||||
|
||||
## Folder structure
|
||||
## Folder Structure
|
||||
|
||||
```{sidebar} This layout is for the tutorial!
|
||||
We make the `evadventure` folder stand-alone for the sake of the tutorial only. Leaving the code isolated makes it clear what we changed - and for you to grab what you want later. It also makes it easier to refer to matching code in `evennia/contrib/tutorials/evadventure`.
|
||||
We make the `evadventure` folder stand-alone for the sake of the tutorial only. Leaving the code isolated in this way makes it clear what we have changed — and for you to grab what you want later. It also makes it easier to refer to the matching code in `evennia/contrib/tutorials/evadventure`.
|
||||
|
||||
For your own game you are encouraged to modify your game dir in-place instead (such as add to `commands/commands.py` and to modify the `typeclasses/` modules directly). Except for the `server/` folder, you are infact free to structure your game dir code pretty much as you like.
|
||||
For your own game, you are instead encouraged to modify your game dir in-place (i.e, directly add to `commands/commands.py` and modify the `typeclasses/` modules directly). Except for the `server/` folder, you are, in fact, free to structure your game dir code pretty much as you like.
|
||||
```
|
||||
Create a new folder under your `mygame` folder, named `evadventure`. Inside it, create
|
||||
another folder `tests/` and make sure to put empty `__init__.py` files in both. This turns both
|
||||
folders into packages Python understands to import from.
|
||||
Create a new folder named `evadventure` under your `mygame` folder. Inside it the new folder, create another folder named `tests/`. Make sure to put empty `__init__.py` files in both new folders. Doing so turns both new folders into packages from which Python understands to import automatically.
|
||||
|
||||
```
|
||||
mygame/
|
||||
|
|
@ -30,79 +28,79 @@ mygame/
|
|||
|
||||
```
|
||||
|
||||
Importing anything from inside this folder from anywhere else under `mygame` will be done by
|
||||
Importing anything from inside this folder from anywhere else under `mygame` will be done by
|
||||
|
||||
```python
|
||||
```python
|
||||
# from anywhere in mygame/
|
||||
from evadventure.yourmodulename import whatever
|
||||
from evadventure.yourmodulename import whatever
|
||||
```
|
||||
|
||||
This is the 'absolute path` type of import.
|
||||
This is the 'absolute path` type of import.
|
||||
|
||||
Between two modules both in `evadventure/`, you can use a 'relative' import with `.`:
|
||||
|
||||
```python
|
||||
```python
|
||||
# from a module inside mygame/evadventure
|
||||
from .yourmodulename import whatever
|
||||
```
|
||||
|
||||
From e.g. inside `mygame/evadventure/tests/` you can import from one level above using `..`:
|
||||
|
||||
```python
|
||||
# from mygame/evadventure/tests/
|
||||
```python
|
||||
# from mygame/evadventure/tests/
|
||||
from ..yourmodulename import whatever
|
||||
```
|
||||
|
||||
|
||||
## Enums
|
||||
## Enums
|
||||
|
||||
```{sidebar}
|
||||
A full example of the enum module is found in
|
||||
A full example of the enum module is found in
|
||||
[evennia/contrib/tutorials/evadventure/enums.py](../../../api/evennia.contrib.tutorials.evadventure.enums.md).
|
||||
```
|
||||
Create a new file `mygame/evadventure/enums.py`.
|
||||
|
||||
An [enum](https://docs.python.org/3/library/enum.html) (enumeration) is a way to establish constants in Python. Best is to show an example:
|
||||
An [enum](https://docs.python.org/3/library/enum.html) (enumeration) is a way to establish constants in Python. For example:
|
||||
|
||||
```python
|
||||
```python
|
||||
# in a file mygame/evadventure/enums.py
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class Ability(Enum):
|
||||
class Ability(Enum):
|
||||
|
||||
STR = "strength"
|
||||
|
||||
```
|
||||
|
||||
You access an enum like this:
|
||||
You can then access an enum like this:
|
||||
|
||||
```
|
||||
```
|
||||
# from another module in mygame/evadventure
|
||||
|
||||
from .enums import Ability
|
||||
from .enums import Ability
|
||||
|
||||
Ability.STR # the enum itself
|
||||
Ability.STR # the enum itself
|
||||
Ability.STR.value # this is the string "strength"
|
||||
|
||||
```
|
||||
|
||||
Having enums is recommended practice. With them set up, it means we can make sure to refer to the same thing every time. Having all enums in one place also means you have a good overview of the constants you are dealing with.
|
||||
Using enums is a recommended practice. With enums set up, we can make sure to refer to the same constant or variable every time. Keeping all enums in one place also means we have a good overview of the constants with which we are dealing.
|
||||
|
||||
The alternative would be to for example pass around a string `"constitution"`. If you mis-spell this (`"consitution"`), you would not necessarily know it right away - the error would happen later when the string is not recognized. If you make a typo getting `Ability.COM` instead of `Ability.CON`, Python will immediately raise an error since this enum is not recognized.
|
||||
The alternative to enums would be, for example, to pass around a string named `"constitution"`. If you mis-spelled this as, say, `"consitution"`, you would not necessarily know it right away because the error would happen later when the string is not recognized. By using the enum practice,should you make a typo getting `Ability.COM` instead of `Ability.CON`, Python will immediately raise an error becase this enum with the typo will not be recognized.
|
||||
|
||||
With enums you can also do nice direct comparisons like `if ability is Ability.WIS: <do stuff>`.
|
||||
With enums, you can also do nice direct comparisons like `if ability is Ability.WIS: <do stuff>`.
|
||||
|
||||
Note that the `Ability.STR` enum does not have the actual _value_ of e.g. your Strength. It's just a fixed label for the Strength ability.
|
||||
Note that the `Ability.STR` enum does not have the actual _value_ of, for instance, your Strength. `Ability.STR` is just a fixed label for the Strength ability.
|
||||
|
||||
Here is the `enum.py` module needed for _Knave_. It covers the basic aspects of rule systems we need to track (check out the _Knave_ rules. If you use another rule system you'll likely gradually expand on your enums as you figure out what you'll need).
|
||||
Below is the `enum.py` module needed for _Knave_. It covers the basic aspects of the rule system we need to track. (Check out the _Knave_ rules.) Should you later use another rule system, you'll likely expand on your enums gradually as you figure out what you'll need.
|
||||
|
||||
```python
|
||||
```python
|
||||
# mygame/evadventure/enums.py
|
||||
|
||||
class Ability(Enum):
|
||||
"""
|
||||
The six base ability-bonuses and other
|
||||
The six base ability-bonuses and other
|
||||
abilities
|
||||
|
||||
"""
|
||||
|
|
@ -113,62 +111,62 @@ class Ability(Enum):
|
|||
INT = "intelligence"
|
||||
WIS = "wisdom"
|
||||
CHA = "charisma"
|
||||
|
||||
|
||||
ARMOR = "armor"
|
||||
|
||||
|
||||
CRITICAL_FAILURE = "critical_failure"
|
||||
CRITICAL_SUCCESS = "critical_success"
|
||||
|
||||
|
||||
ALLEGIANCE_HOSTILE = "hostile"
|
||||
ALLEGIANCE_NEUTRAL = "neutral"
|
||||
ALLEGIANCE_FRIENDLY = "friendly"
|
||||
|
||||
|
||||
ABILITY_REVERSE_MAP = {
|
||||
"str": Ability.STR,
|
||||
"str": Ability.STR,
|
||||
"dex": Ability.DEX,
|
||||
"con": Ability.CON,
|
||||
"int": Ability.INT,
|
||||
"wis": Ability.WIS,
|
||||
"cha": Ability.CHA
|
||||
"cha": Ability.CHA
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Here the `Ability` class holds basic properties of a character sheet.
|
||||
Above, the `Ability` class holds some basic properties of a character sheet.
|
||||
|
||||
The `ABILITY_REVERSE_MAP` is a convenient map to go the other way - if you in some command were to enter the string 'cha', we could use this mapping to directly convert your input to the correct `Ability`:
|
||||
The `ABILITY_REVERSE_MAP` is a convenient map to go the other way &mdas; if in some command we were to enter the string 'cha', we could use this mapping to directly convert your input to the correct `Ability`. For example:
|
||||
|
||||
ability = ABILITY_REVERSE_MAP.get(your_input)
|
||||
|
||||
|
||||
## Utility module
|
||||
## Utility Module
|
||||
|
||||
> Create a new module `mygame/evadventure/utils.py`
|
||||
|
||||
```{sidebar}
|
||||
An example of the utility module is found in
|
||||
An example of the utility module is found in
|
||||
[evennia/contrib/tutorials/evadventure/utils.py](../../../api/evennia.contrib.tutorials.evadventure.utils.md)
|
||||
```
|
||||
|
||||
This is for general functions we may need from all over. In this case we only picture one utility, a function that produces a pretty display of any object we pass to it.
|
||||
The utility module is used to contain general functions we may need to call repeatedly from various other modules. In this tutorial example, we only crate one utility: a function that produces a pretty display of any object we pass to it.
|
||||
|
||||
This is an example of the string we want to see:
|
||||
Below is an example of the string we want to see:
|
||||
|
||||
```
|
||||
Chipped Sword
|
||||
Chipped Sword
|
||||
Value: ~10 coins [wielded in Weapon hand]
|
||||
|
||||
A simple sword used by mercenaries all over
|
||||
|
||||
A simple sword used by mercenaries all over
|
||||
the world.
|
||||
|
||||
|
||||
Slots: 1, Used from: weapon hand
|
||||
Quality: 3, Uses: None
|
||||
Attacks using strength against armor.
|
||||
Damage roll: 1d6
|
||||
```
|
||||
|
||||
Here's the start of how the function could look:
|
||||
And, here's the start of how the function might look:
|
||||
|
||||
```python
|
||||
# in mygame/evadventure/utils.py
|
||||
|
|
@ -186,23 +184,23 @@ Damage roll: |w{damage_roll}|n
|
|||
""".strip()
|
||||
|
||||
|
||||
def get_obj_stats(obj, owner=None):
|
||||
"""
|
||||
def get_obj_stats(obj, owner=None):
|
||||
"""
|
||||
Get a string of stats about the object.
|
||||
|
||||
|
||||
Args:
|
||||
obj (Object): The object to get stats for.
|
||||
owner (Object): The one currently owning/carrying `obj`, if any. Can be
|
||||
owner (Object): The one currently owning/carrying `obj`, if any. Can be
|
||||
used to show e.g. where they are wielding it.
|
||||
Returns:
|
||||
str: A nice info string to display about the object.
|
||||
|
||||
|
||||
"""
|
||||
return _OBJ_STATS.format(
|
||||
key=obj.key,
|
||||
value=10,
|
||||
carried="[Not carried]",
|
||||
desc=obj.db.desc,
|
||||
key=obj.key,
|
||||
value=10,
|
||||
carried="[Not carried]",
|
||||
desc=obj.db.desc,
|
||||
size=1,
|
||||
quality=3,
|
||||
uses="infinite",
|
||||
|
|
@ -212,55 +210,54 @@ def get_obj_stats(obj, owner=None):
|
|||
damage_roll="1d6"
|
||||
)
|
||||
```
|
||||
Here we set up the string template with place holders for where every piece of info should go. Study this string so you understand what it does. The `|c`, `|y`, `|w` and `|n` markers are [Evennia color markup](../../../Concepts/Colors.md) for making the text cyan, yellow, white and neutral-color respectively.
|
||||
In our new `get_obj_stats` function above, we set up a string template with place holders for where every element of stats information should go. Study this string so that you understand what it does. The `|c`, `|y`, `|w` and `|n` markers are [Evennia color markup](../../../Concepts/Colors.md) for making the text cyan, yellow, white and neutral-color, respectively.
|
||||
|
||||
We can guess some things, such that `obj.key` is the name of the object, and that `obj.db.desc` will hold its description (this is how it is in default Evennia).
|
||||
Some stats elements are easy to identify in the above code. For instance, `obj.key` is the name of an object and `obj.db.desc` will hold an object's description — this is also how default Evennia works.
|
||||
|
||||
But so far we have not established how to get any of the other properties like `size` or `attack_type`. So we just set them to dummy values. We'll need to get back to this when we have more code in place!
|
||||
So far, here in our tutorial, we have not yet established how to get any of the other properties like `size` or `attack_type`. For our current purposes, we will just set them to dummy values and we'll need to revisit them later when we have more code in place!
|
||||
|
||||
## Testing
|
||||
## Testing
|
||||
|
||||
Evennia comes with extensive functionality to help you test your code. A _unit test_ allows you to set up automated testing of code. Once you've written your test, you can then run it over and over again to ensure later changes to your code didn't break things by introducing errors.
|
||||
|
||||
> create a new module `mygame/evadventure/tests/test_utils.py`
|
||||
|
||||
How do you know if you made a typo in the code above? You could _manually_ test it by reloading your Evennia server and do the following from in-game:
|
||||
How would you know if you made a typo in the code above? You can _manually_ test it by reloading your Evennia server and issuing the following in-game python command:
|
||||
|
||||
py from evadventure.utils import get_obj_stats;print(get_obj_stats(self))
|
||||
|
||||
You should get back a nice string about yourself! If that works, great! But you'll need to remember doing that test when you change this code later.
|
||||
Doing so should spit back a nice bit of string ouput about yourself! If that works, great! But, you'll need to remember re-running that test manually when you later change the code.
|
||||
|
||||
```{sidebar}
|
||||
In [evennia/contrib/tutorials/evadventure/tests/test_utils.py](evennia.contrib.tutorials.evadventure.tests.test_utils)
|
||||
is an example of the testing module. To dive deeper into unit testing in Evennia, see the [Unit testing](../../../Coding/Unit-Testing.md) documentation.
|
||||
```
|
||||
|
||||
A _unit test_ allows you to set up automated testing of code. Once you've written your test you can run it over and over and make sure later changes to your code didn't break things.
|
||||
In our particular case of this tutorial, we should _expect_ to need to later update the test when the `get_obj_stats` code becomes more complete and returns more pertinent data.
|
||||
|
||||
In this particular case, we _expect_ to later have to update the test when `get_obj_stats` becomes more complete and returns more reasonable data.
|
||||
Here's a module for testing `get_obj_stats`.
|
||||
|
||||
Evennia comes with extensive functionality to help you test your code. Here's a module for
|
||||
testing `get_obj_stats`.
|
||||
|
||||
```python
|
||||
```python
|
||||
# mygame/evadventure/tests/test_utils.py
|
||||
|
||||
from evennia.utils import create
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.utils import create
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
|
||||
from ..import utils
|
||||
|
||||
class TestUtils(EvenniaTest):
|
||||
def test_get_obj_stats(self):
|
||||
# make a simple object to test with
|
||||
# make a simple object to test with
|
||||
obj = create.create_object(
|
||||
key="testobj",
|
||||
key="testobj",
|
||||
attributes=(("desc", "A test object"),)
|
||||
)
|
||||
# run it through the function
|
||||
)
|
||||
# run it through the function
|
||||
result = utils.get_obj_stats(obj)
|
||||
# check that the result is what we expected
|
||||
self.assertEqual(
|
||||
result,
|
||||
"""
|
||||
result,
|
||||
"""
|
||||
|ctestobj|n
|
||||
Value: ~|y10|n coins[not carried]
|
||||
|
||||
|
|
@ -275,37 +272,41 @@ Damage roll: |w1d6|n
|
|||
|
||||
```
|
||||
|
||||
What happens here is that we create a new test-class `TestUtils` that inherits from `EvenniaTest`. This inheritance is what makes this a testing class.
|
||||
What happens in the above code is that we create a new test-class named `TestUtils` that inherits from `EvenniaTest`. It is this inheritance that makes this a testing class.
|
||||
|
||||
|
||||
```{important}
|
||||
It's useful for any game dev to know how to effectively test their code. So we'll try to include a *Testing* section at the end of each of the implementation lessons to follow. Writing tests for your code is optional but highly recommended. It can feel a little cumbersome or time-consuming at first ... but you'll thank yourself later.
|
||||
It's useful for any game dev to know how to test their code effectively. So, we'll try to include a *Testing* section at the end of each implementation lesson that follows in this tutorial.
|
||||
|
||||
Writing tests for your code is optional, but highly recommended. Initially, unit testing may feel a little cumbersome or time-consuming... but you'll thank yourself later.
|
||||
```
|
||||
|
||||
We can have any number of methods on this class. To have a method recognized as one containing code to test, its name _must_ start with `test_`. We have one - `test_get_obj_stats`.
|
||||
We can have any number of methods called on this class. To have Evennia automatically recognize a method as one containing code to test, its name _must_ start with the `test_` prefix. We have one here as `test_get_obj_stats`.
|
||||
|
||||
In this method we create a dummy `obj` and gives it a `key` "testobj". Note how we add the `desc` [Attribute](../../../Components/Attributes.md) directly in the `create_object` call by specifying the attribute as a tuple `(name, value)`!
|
||||
In our `test_get_obj_stats` method, we create a dummy `obj` and assign it a `key` "testobj". Note that we add the`desc` [Attribute](../../../Components/Attributes.md) directly in the `create_object` call by specifying the attribute as a tuple `(name, value)`!
|
||||
|
||||
We then get the result of passing this dummy-object through `get_obj_stats` we imported earlier.
|
||||
Then, we can get the result of passing this dummy-object through the `get_obj_stats` function that we imported earlier.
|
||||
|
||||
The `assertEqual` method is available on all testing classes and checks that the `result` is equal to the string we specify. If they are the same, the test _passes_, otherwise it _fails_ and we need to investigate what went wrong.
|
||||
The `assertEqual` method is available on all testing classes and checks that the `result` is equal to the string we specify. If they are the same, the test _passes_. Otherwise, it _fails_ and we need to investigate what went wrong.
|
||||
|
||||
### Running your test
|
||||
### Running your Test
|
||||
|
||||
To run your test you need to stand inside your `mygame` folder and execute the following command:
|
||||
To run our utility module test, we need to issue the following command directly from the `mygame` folder:
|
||||
|
||||
evennia test --settings settings.py evadventure.tests
|
||||
|
||||
This will run all your `evadventure` tests (if you had more of them). To only run your utility tests you could do
|
||||
The above command will run all `evadventure` tests found in the `mygame/evadventure/tests` folder. To run only our utility tests we might instead specify the test individually:
|
||||
|
||||
evennia test --settings settings.py evadventure.tests.test_utils
|
||||
|
||||
If all goes well, you should get an `OK` back. Otherwise you need to check the failure, maybe your return string doesn't quite match what you expected.
|
||||
If all goes well, the above utility test should produce output ending with `OK` to indicate our code has passed the test.
|
||||
|
||||
> Hint: The example unit test code above contains a deliberate error in capitalization. See if you can interpret the error and fix it!
|
||||
However, if our return string doesn't quite match what we expected, the test will fail. We will then need to begin examining and troubleshooting our failing code.
|
||||
|
||||
## Summary
|
||||
> Hint: The above example unit test code contains a deliberate error in capitalization. See if you can examine the output to interpret the deliberate error, and then fix it!
|
||||
|
||||
It's very important to understand how you import code between modules in Python, so if this is still confusing to you, it's worth to read up on this more.
|
||||
## Summary
|
||||
|
||||
That said, many newcomers are confused with how to begin, so by creating the folder structure, some small modules and even making your first unit test, you are off to a great start!
|
||||
It's very important to understand how you import code among modules in Python. If importing from Python modules is still confusing to you, it's worth it to read more on the topic.
|
||||
|
||||
That said, many newcomers are confused with how to tackle these concepts. In this lesson, by creating the folder structure, two small modules and even making our first unit test, you are off to a great start!
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue