Skip to content

Commit 8546d46

Browse files
committed
Upload 2021 tutorials parts 2 and 3.
1 parent 1881eb5 commit 8546d46

File tree

5 files changed

+691
-144
lines changed

5 files changed

+691
-144
lines changed

_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
title: libtcod.github.io
2222
email: 4b796c65+libtcod@gmail.com
2323
description: >- # this means to ignore newlines until "baseurl:"
24-
A website that hosts some libtcod-related projects.
24+
Libtcod on GitHub Pages.
2525
baseurl: "" # the subpath of your site, e.g. /blog
2626
url: "https://libtcod.github.io" # the base hostname & protocol for your site, e.g. http://example.com
2727
repository: libtcod/libtcod.github.io

tutorials/python/2021/index.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ The event is still ongoing.
1111
It isn't unusual for tcod to be updated during the event, so you might have to upgrade it has you progress on this version of the tutorial.
1212
Earlier pages may also be updated at times.
1313

14+
You can point out or fix typos by [making an issue or pull request](https://github.com/libtcod/libtcod.github.io). Code suggestions go [here](https://github.com/TStand90/tcod_tutorial_v2) instead.
15+
1416
## Main Tutorial:
1517

1618
- [Part 0 - Setting Up](part-0)
1719
- [Part 1 - Drawing the ‘@’ symbol and moving it around](part-1)
18-
- Part 2 - The generic Entity, the render functions, and the map (July 6th)
19-
- Part 3 - Generating a dungeon
20+
- [Part 2 - The generic Entity, the render functions, and the map](part-2)
21+
- [Part 3 - Generating a dungeon](part-3)
2022
- Part 4 - Field of view (July 13th)
2123
- Part 5 - Placing enemies and kicking them (harmlessly)
2224
- Part 6 - Doing (and taking) some damage (July 20th)

tutorials/python/2021/part-1.md

Lines changed: 31 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -13,141 +13,27 @@ If not, be sure to check that page, and make sure that you've got Python and TCO
1313

1414
Assuming that you've done all that, let's get started.
1515

16-
1716
We will start by setting up the following directory structure.
18-
The font from [Part 0](part-0) will go into a `data` directory, then a `game` package will be created with some Python modules, then finally `main.py` will be created as an entry point.
17+
The font from [Part 0](part-0) will go into a `data` directory, then `main.py` will be created as an entry point.
1918

2019
```
2120
/data/dejavu16x16_gs_tc.png
22-
/game/__init__.py
23-
/game/actions.py
24-
/game/input_handlers.py
2521
/main.py
2622
```
2723

28-
`/game/__init__.py` will be a blank file as it's only needed to [define a Python package](https://docs.python.org/3/tutorial/modules.html#packages).
29-
30-
```python
31-
# game/__init__.py
32-
```
33-
34-
`/game/actions.py` is based loosely on [Bob Nystrom's "Is There More to Game Architecture than ECS?"](https://www.youtube.com/watch?v=JxI3Eu5DPwE).
35-
36-
```python
37-
# game/actions.py
38-
from __future__ import annotations
39-
40-
41-
class Action:
42-
pass
43-
44-
45-
class Move(Action):
46-
def __init__(self, dx: int, dy: int):
47-
super().__init__()
48-
49-
self.dx = dx
50-
self.dy = dy
51-
```
52-
53-
This has the base class `Action` and the real action `Move` for relative movement, the `Move` class holds the direction of movement.
54-
Since this is in a module in a package the fully qualified name for `Move` is `game.actions.Move`, so it won't be necessary to add the word `Action` to any sub-classes of `Action`.
55-
56-
`from __future__ import annotations` tells Python to do [Postponed Evaluation of Annotations](https://www.python.org/dev/peps/pep-0563/), this helps reduce issues from modules referencing each other which can happen often whenever type-hinting is being used.
57-
This will be added to the beginning of most new modules.
58-
59-
`/game/input_handlers.py`
24+
`/main.py` is the entry point of the program. You can use `python main.py` to start the program after the following is implemented.
6025

6126
```python
62-
# game/input_handlers.py
63-
from __future__ import annotations
64-
65-
from typing import Optional
66-
27+
#!/usr/bin/env python3
28+
# main.py
6729
import tcod
6830

69-
import game.actions
70-
71-
MOVE_KEYS = {
72-
# Arrow keys.
73-
tcod.event.K_UP: (0, -1),
74-
tcod.event.K_DOWN: (0, 1),
75-
tcod.event.K_LEFT: (-1, 0),
76-
tcod.event.K_RIGHT: (1, 0),
77-
tcod.event.K_HOME: (-1, -1),
78-
tcod.event.K_END: (-1, 1),
79-
tcod.event.K_PAGEUP: (1, -1),
80-
tcod.event.K_PAGEDOWN: (1, 1),
81-
# Numpad keys.
82-
tcod.event.K_KP_1: (-1, 1),
83-
tcod.event.K_KP_2: (0, 1),
84-
tcod.event.K_KP_3: (1, 1),
85-
tcod.event.K_KP_4: (-1, 0),
86-
tcod.event.K_KP_6: (1, 0),
87-
tcod.event.K_KP_7: (-1, -1),
88-
tcod.event.K_KP_8: (0, -1),
89-
tcod.event.K_KP_9: (1, -1),
90-
# Vi keys.
91-
tcod.event.K_h: (-1, 0),
92-
tcod.event.K_j: (0, 1),
93-
tcod.event.K_k: (0, -1),
94-
tcod.event.K_l: (1, 0),
95-
tcod.event.K_y: (-1, -1),
96-
tcod.event.K_u: (1, -1),
97-
tcod.event.K_b: (-1, 1),
98-
tcod.event.K_n: (1, 1),
99-
}
100-
101-
102-
class EventHandler(tcod.event.EventDispatch[game.actions.Action]):
103-
def ev_quit(self, event: tcod.event.Quit) -> Optional[game.actions.Action]:
104-
raise SystemExit(0)
105-
106-
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[game.actions.Action]:
107-
key = event.sym
108-
109-
if key in MOVE_KEYS:
110-
dx, dy = MOVE_KEYS[key]
111-
return game.actions.Move(dx=dx, dy=dy)
112-
elif key == tcod.event.K_ESCAPE:
113-
raise SystemExit(0)
114-
115-
return None
116-
```
117-
118-
[Optional](https://docs.python.org/3/library/typing.html#typing.Optional) is imported from the typing module.
119-
`tcod` and `game.actions` is also used in this module so they are imported as well.
120-
121-
`MOVE_KEYS` can be simplified if you don't need diagonal movement:
122-
123-
```python
12431
MOVE_KEYS = {
12532
tcod.event.K_UP: (0, -1),
12633
tcod.event.K_DOWN: (0, 1),
12734
tcod.event.K_LEFT: (-1, 0),
12835
tcod.event.K_RIGHT: (1, 0),
12936
}
130-
```
131-
132-
The `EventHandler` class inherits from [tcod.event.EventDispatch](https://python-tcod.readthedocs.io/en/latest/tcod/event.html#tcod.event.EventDispatch), the generic type is filled with `game.actions.Action` which means the event methods can return that type and that the caller of the [dispatch](https://python-tcod.readthedocs.io/en/latest/tcod/event.html#tcod.event.EventDispatch.dispatch) method can receive that type.
133-
134-
Trying to close the window will trigger a call to `ev_quit`.
135-
This will raise [SystemExit](https://docs.python.org/3/library/exceptions.html#SystemExit) which will propagate and terminate the script.
136-
137-
When a key is pressed then `ev_keydown` is triggered.
138-
This will check if that key is one of the keys in `MOVE_KEYS`, if it is then `game.actions.Move` is returned with the values of `MOVE_KEYS[event.sym]`.
139-
Any unexpected key will return `None` instead.
140-
141-
142-
`/main.py` is the entry point of the program. You can use `python main.py` to start the program after the following is implemented.
143-
144-
```python
145-
#!/usr/bin/env python3
146-
# main.py
147-
import tcod
148-
149-
import game.actions
150-
import game.input_handlers
15137

15238

15339
def main() -> None:
@@ -159,8 +45,6 @@ def main() -> None:
15945

16046
tileset = tcod.tileset.load_tilesheet("data/dejavu16x16_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD)
16147

162-
event_handler = game.input_handlers.EventHandler()
163-
16448
with tcod.context.new(
16549
columns=screen_width,
16650
rows=screen_height,
@@ -170,31 +54,34 @@ def main() -> None:
17054
) as context:
17155
root_console = tcod.Console(screen_width, screen_height, order="F")
17256
while True:
57+
root_console.clear()
17358
root_console.print(x=player_x, y=player_y, string="@")
174-
17559
context.present(root_console)
17660

177-
root_console.clear()
178-
17961
for event in tcod.event.wait():
180-
action = event_handler.dispatch(event)
181-
182-
if isinstance(action, game.actions.Move):
183-
new_x = player_x + action.dx
184-
new_y = player_y + action.dy
185-
if 0 <= new_x < screen_width and 0 <= new_y < screen_height:
186-
player_x, player_y = new_x, new_y
62+
if isinstance(event, tcod.event.Quit):
63+
raise SystemExit(0)
64+
if isinstance(event, tcod.event.KeyDown):
65+
if event.sym in MOVE_KEYS:
66+
delta_x, delta_y = MOVE_KEYS[event.sym]
67+
dest_x = player_x + delta_x
68+
dest_y = player_y + delta_y
69+
if 0 <= dest_x < screen_width and 0 <= dest_y < screen_height:
70+
player_x, player_y = dest_x, dest_y
18771

18872

18973
if __name__ == "__main__":
19074
main()
19175
```
19276

19377
`#!/usr/bin/env python3` is a [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) and must be the first line to be useful.
194-
It's normally used to make scripts executable on Linux, but is sometimes used by Python launchers on other platforms as well.
78+
It is normally used to make scripts executable on Linux, but is also used by the Windows Python launcher.
19579

19680
`tcod` is imported along with the two other modules we've added.
19781

82+
`MOVE_KEYS` is a Python dictionary mapping of [tcod KeySym's](https://python-tcod.readthedocs.io/en/latest/tcod/event.html#tcod.event.KeySym) to in-game directions.
83+
This will be expanded in later parts.
84+
19885
The `main` function will be the entry point of the program.
19986
There's nothing special about the name other than the terminology, the special nature of this function comes from the `__name__ == "__main__"` condition at the bottom of the script which is only True when the script is directly run, compared to importing main from an interactive prompt.
20087

@@ -204,8 +91,6 @@ The floor division operator is used so that the numbers don't promote to a float
20491
The tileset is loaded with [tcod.tileset.load_tilesheet](https://python-tcod.readthedocs.io/en/latest/tcod/tileset.html#tcod.tileset.load_tilesheet)
20592
The Python-tcod docs have a [character reference](https://python-tcod.readthedocs.io/en/latest/tcod/charmap-reference.html) to keep track of which layouts have what glyphs.
20693

207-
The `event_handler` is now initialized.
208-
20994
[tcod.context.new](https://python-tcod.readthedocs.io/en/latest/tcod/context.html#tcod.context.new) is used to setup the window and returns a [Context](https://python-tcod.readthedocs.io/en/latest/tcod/context.html#tcod.context.Context) instance.
21095
Contexts must be closed once you're done with them, but the `with` statement will do this automatically when exiting the with-block.
21196

@@ -215,21 +100,26 @@ This is a convenient way to handle array indexes so we'll use `order="F"` a lot
215100
You might wonder why `order="F"` isn't the default [and there is an explanation for that](https://numpy.org/doc/stable/reference/internals.html#multidimensional-array-indexing-order-issues).
216101
These arrays are not being used yet.
217102

218-
Now with `while True:` the game-loop begins, with the only way of existing this loop normally being the `SystemExit` exceptions implemented earlier.
219-
The player position is printed to `root_console`, then `root_console` is displayed using `context.present(root_console)`, after that the `root_console` is cleared for the next frame.
103+
Now with `while True:` the game-loop begins, with the only way of existing this loop normally being the `SystemExit` exceptions implemented later.
104+
`root_console` is cleared, then the player position is printed to `root_console`, then `root_console` is displayed using `context.present(root_console)`.
220105

221106
The for-loop waits for there to be events then iterates over all events until none are left.
222107
This is an efficient way to handle events when the game doesn't have real-time animations or mechanics.
223108
If you have real-time effects then you should replace [tcod.event.wait](https://python-tcod.readthedocs.io/en/latest/tcod/event.html#tcod.event.wait) with [tcod.event.get](https://python-tcod.readthedocs.io/en/latest/tcod/event.html#tcod.event.get).
224109

225-
Events are sent to the `EventHandler` class and the action to be performed is returned.
226-
This could be `None` or any `Action`.
227-
`isinstance(action, game.actions.Move)` tests if `action` is an instance of `Move`.
228-
This affects type checking which can now assume that `action` is that type within the if-branch.
229-
It can then unpack its values and checks if the destination is in the bounds of the screen before setting the player position.
110+
[isinstance](https://docs.python.org/3/library/functions.html#isinstance) is being used here to sort events by type.
111+
This has an effect on type-checking, where anything within an `isinstance` branch can be assumed to actually be that type, much like a type cast in any other language.
112+
113+
Trying to close the window will result in a `Quit` event.
114+
When this happens we will raise [SystemExit](https://docs.python.org/3/library/exceptions.html#SystemExit) which will propagate and terminate the script.
115+
116+
For `KeyDown` events we check if the key is in the `MOVE_KEYS` dictionary then try to move by that amount.
117+
If the destination is on the screen then the player moves to that spot.
230118

231119
You can see the current progress of this code in its entirety [here](https://github.com/TStand90/tcod_tutorial_v2/tree/2021/part-1).
232120

233-
Part-2 isn't available yet, [but you can setup distribution in the meantime](distribution).
121+
If you want to distribute your game then [you can set that up now or at any time later](distribution).
122+
123+
[Continue to part 2](part-2).
234124

235125
[Return to the hub](.).

0 commit comments

Comments
 (0)