diff --git a/content/tutorials/tcod/2019/part-0.md b/content/tutorials/tcod/2019/part-0.md index f0a329a1..a53e6980 100644 --- a/content/tutorials/tcod/2019/part-0.md +++ b/content/tutorials/tcod/2019/part-0.md @@ -19,9 +19,8 @@ paragraph if you're feeling bold\! #### Installation -To do this tutorial, you'll need Python version 3.5 or higher. The -latest version of Python is recommended (currently 3.7 as of March -2019). **Note: Python 2 is not compatible.** +To do this tutorial, you'll need Python version 3.8 or higher. The +latest version of Python is recommended. **Note: Python 2 is not compatible.** [Download Python here](https://www.python.org/downloads/). @@ -53,7 +52,7 @@ create a new file (in whatever directory you plan on using for the tutorial) called `engine.py`, and enter the following text into it: ```py3 -import tcod as libtcod +import tcod def main(): @@ -174,13 +173,13 @@ be no spaces before "from"): {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod +from input_handlers import handle_keys {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -
import tcod as libtcod
+ import tcod
from input_handlers import handle_keys
{{ original-tab >}}
diff --git a/content/tutorials/tcod/2019/part-1.md b/content/tutorials/tcod/2019/part-1.md
index d9df9986..d9bddf8b 100644
--- a/content/tutorials/tcod/2019/part-1.md
+++ b/content/tutorials/tcod/2019/part-1.md
@@ -26,7 +26,7 @@ Assuming that you've done all that, let's get started. Modify (or
create, if you haven't already) the file `engine.py` to look like this:
{{< highlight py3 >}}
-import tcod as libtcod
+import tcod
def main():
@@ -60,7 +60,7 @@ Overflow](https://stackoverflow.com/a/419185) gives a pretty good
overview.
Confirm that the above program runs (if not, there's probably an issue
-with your libtcod setup). Once that's done, we can move on to bigger and
+with your tcod setup). Once that's done, we can move on to bigger and
better things. The first major step to creating any roguelike is getting
an '@' character on the screen and moving, so let's get started with
that.
@@ -68,26 +68,37 @@ that.
Modify `engine.py` to look like this:
{{< highlight py3 >}}
-import tcod as libtcod
+import tcod
def main():
screen_width = 80
screen_height = 50
- libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
+ tileset = tcod.tileset.load_tilesheet(
+ 'arial10x10.png', 32, 8, tcod.tileset.CHARMAP_TCOD
+ )
- libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False)
+ with tcod.context.new(
+ columns=screen_width,
+ rows=screen_height,
+ tileset=tileset,
+ title='libtcod tutorial revised',
+ vsync=True,
+ ) as context:
+ root_console = tcod.console.Console(screen_width, screen_height, order='F')
- while not libtcod.console_is_window_closed():
- libtcod.console_set_default_foreground(0, libtcod.white)
- libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)
- libtcod.console_flush()
+ while True:
+ root_console.print(1, 1, '@', fg=(255, 255, 255))
+ context.present(root_console)
+ root_console.clear()
- key = libtcod.console_check_for_keypress()
-
- if key.vk == libtcod.KEY_ESCAPE:
- return True
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit()
+ if isinstance(event, tcod.event.KeyDown):
+ if event.sym == tcod.event.KeySym.ESCAPE:
+ raise SystemExit()
if __name__ == '__main__':
@@ -96,7 +107,7 @@ if __name__ == '__main__':
Run `engine.py` again, and you should see an '@' symbol on the screen.
Once you've fully soaked in the glory on the screen in front of you, you
-can hit the \`Esc\` key to exit the program.
+can hit the `Esc` key to exit the program.
There's a lot going on here, so let's break it down line by line.
@@ -112,71 +123,80 @@ have some more variables like
this.
{{< highlight py3 >}}
- libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
+ tileset = tcod.tileset.load_tilesheet(
+ 'arial10x10.png', 32, 8, tcod.tileset.CHARMAP_TCOD
+ )
{{ highlight >}}
-Here, we're telling libtcod which font to use. The `'arial10x10.png'`
+Here, we're telling tcod which font to use. The `'arial10x10.png'`
bit is the actual file we're reading from (this should exist in your
-project folder). The other two parts are telling libtcod which type of
-file we're
-reading.
+project folder). The numbers `32, 8` specify the layout of the tile
+sheet (32 columns, 8 rows of characters), and `tcod.tileset.CHARMAP_TCOD`
+tells tcod which character mapping to use.
{{< highlight py3 >}}
- libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'libtcod tutorial revised', False)
+ with tcod.context.new(
+ columns=screen_width,
+ rows=screen_height,
+ tileset=tileset,
+ title='libtcod tutorial revised',
+ vsync=True,
+ ) as context:
{{ highlight >}}
-This line is what actually creates the screen. We're giving it the
-`screen_width` and `screen_height` values from before (80 and 50,
-respectively), along with a title (change this if you've already got
-your game's name figured out), and a boolean value that tells libtcod
-whether to go full screen or not.
+This is what actually creates the window. We use a `with` statement so
+that the window is automatically cleaned up when our program exits. We
+pass it the `screen_width` and `screen_height` values from before (80 and
+50, respectively), the tileset we just loaded, a title for the window,
+and `vsync=True` to enable vertical sync (smooth rendering).
{{< highlight py3 >}}
- while not libtcod.console_is_window_closed():
+ while True:
{{ highlight >}}
This is what's called our 'game loop'. Basically, this is a loop that
-won't ever end, until we close the screen. Every game has some sort of
+won't ever end until we explicitly exit. Every game has some sort of
game loop or another.
{{< highlight py3 >}}
- libtcod.console_set_default_foreground(0, libtcod.white)
+ root_console.print(1, 1, '@', fg=(255, 255, 255))
{{ highlight >}}
-This line tells libtcod to set the color for our '@' symbol. If you want
-your character to be a different color, change `libtcod.white` to
-something like `libtcod.red` and see what happens. The '0' in this
-function is the console we're drawing to. We'll go over that more later.
+This prints our '@' character to the console at position (1, 1). The
+`fg=(255, 255, 255)` sets the foreground color to white (as an RGB
+tuple). If you want your character to be a different color, try changing
+those numbers (they represent red, green, and blue values from 0 to 255).
{{< highlight py3 >}}
- libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)
+ context.present(root_console)
{{ highlight >}}
-The first argument is '0' (again, the console we're printing to). The
-next two are x and y coordinates, in this case, 1 and 1 (try changing
-that and see what happens). Next, we're printing the '@' symbol, and
-setting the background to 'none' with `libtcod.BKGND_NONE`.
+This is the part that presents everything on the screen. The
+`root_console` is passed to `context.present()`, which draws it to the
+window. Pretty straightforward.
{{< highlight py3 >}}
- libtcod.console_flush()
+ root_console.clear()
{{ highlight >}}
-This is the part that presents everything on the screen. Pretty
-straightforward.
+After presenting, we clear the console so it's ready for the next frame.
+This erases everything we've drawn, so the next loop iteration starts
+with a blank slate.
{{< highlight py3 >}}
- key = libtcod.console_check_for_keypress()
-
- if key.vk == libtcod.KEY_ESCAPE:
- return True
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit()
+ if isinstance(event, tcod.event.KeyDown):
+ if event.sym == tcod.event.KeySym.ESCAPE:
+ raise SystemExit()
{{ highlight >}}
-This part gives us a way to gracefully exit (i.e. not crashing) the
-program by hitting the `Esc` key. The
-`libtcod.console_check_for_keypress()` function gets any keyboard input
-to the program, which we store in the `key` variable. From there, we
-check if the key pressed was the `Esc` key or not. If it was, then we
-exit the loop, thus ending the program.
+`tcod.event.wait()` pauses the program and returns events one at a time
+as the user interacts with the window. We check if the event is a
+`Quit` event (the user closed the window) or a `KeyDown` event (the user
+pressed a key). If the user presses `Esc`, we raise `SystemExit()` to
+close the program gracefully.
So we've got our '@' symbol drawn, now let's get it moving around\!
@@ -192,7 +212,7 @@ create two variables, `player_x` and `player_y` to keep track of this.
+ player_x = int(screen_width / 2)
+ player_y = int(screen_height / 2)
+
- libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
+ tileset = tcod.tileset.load_tilesheet(
...
{{ highlight >}}
{{ diff-tab >}}
@@ -203,7 +223,7 @@ create two variables, `player_x` and `player_y` to keep track of this.
player_x = int(screen_width / 2)
player_y = int(screen_height / 2)
- libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
+ tileset = tcod.tileset.load_tilesheet(
...
{{ original-tab >}}
{{ codetab >}}
@@ -216,32 +236,26 @@ The green lines denote code that you should be adding.*
We're placing the player right in the middle of the screen. What's with
the `int()` function though? Well, Python 3 doesn't automatically
truncate division like Python 2 does, so we have to cast the division
-result (a float) to an integer. If we don't, libtcod will give an error.
+result (a float) to an integer. If we don't, tcod will give an error.
-We also have to modify the command to put the '@' symbol to use these
+We also have to modify the command to print the '@' symbol to use these
new coordinates.
{{< codetab >}}
{{< diff-tab >}}
{{< highlight diff >}}
...
- libtcod.console_set_default_foreground(0, libtcod.white)
-- libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)
-+ libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE)
- libtcod.console_flush()
-
-+ libtcod.console_put_char(0, player_x, player_y, ' ', libtcod.BKGND_NONE)
+- root_console.print(1, 1, '@', fg=(255, 255, 255))
++ root_console.print(player_x, player_y, '@', fg=(255, 255, 255))
+ context.present(root_console)
...
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
... - libtcod.console_set_default_foreground(0, libtcod.white) - libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE) - libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE) - libtcod.console_flush() - - libtcod.console_put_char(0, player_x, player_y, ' ', libtcod.BKGND_NONE) + root_console.print(1, 1, '@', fg=(255, 255, 255)) + root_console.print(player_x, player_y, '@', fg=(255, 255, 255)) + context.present(root_console) ...{{ original-tab >}} {{ codetab >}} @@ -251,66 +265,9 @@ new coordinates. Run the code now and you should see the '@' in the center of the screen. Let's take care of moving it around now. -Put the following two lines right above the main game loop. - -{{< codetab >}} -{{< diff-tab >}} -{{< highlight diff >}} - ... - libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False) - -+ key = libtcod.Key() -+ mouse = libtcod.Mouse() - - while not libtcod.console_is_window_closed(): - ... -{{ highlight >}} -{{ diff-tab >}} -{{< original-tab >}} -
...
- libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False)
-
- key = libtcod.Key()
- mouse = libtcod.Mouse()
-
- while not libtcod.console_is_window_closed():
- ...
-{{ original-tab >}}
-{{ codetab >}}
-
-As the names imply, these variables will hold our keyboard and mouse
-input. We aren't implementing the mouse yet, but the function we're
-about to add take it into account, so we might as well add it.
-
-{{< codetab >}}
-{{< diff-tab >}}
-{{< highlight diff >}}
- ...
- while not libtcod.console_is_window_closed():
-+ libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)
-
- libtcod.console_set_default_foreground(0, libtcod.white)
- ...
-{{ highlight >}}
-{{ diff-tab >}}
-{{< original-tab >}}
- ...
- while not libtcod.console_is_window_closed():
- libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)
-
- libtcod.console_set_default_foreground(0, libtcod.white)
- ...
-{{ original-tab >}}
-{{ codetab >}}
-
-This is the function that actually captures new "events" (user input).
-It will update the `key` and `mouse` variables with what the user
-inputs. Again, we're only concerned with `key` for right now.
-
-Okay, so we're updating the `key` variable with the user's input. But
-what do we actually *do* with it? Let's define a function to handle the
-user's input. It will essentially translate the user's key presses into
-game actions.
+Okay, so we're rendering the '@' but it just sits there. We need to
+define a function to handle the user's input. It will essentially
+translate the user's key presses into game actions.
Up until now, this tutorial hasn't deviated all that much from the
original one, but here's a critical turning point. We're about to define
@@ -325,27 +282,30 @@ create a new file, called `input_handlers.py`. Put the following code
inside that new file.
{{< highlight py3 >}}
-import tcod as libtcod
+import tcod
-def handle_keys(key):
- # Movement keys
- if key.vk == libtcod.KEY_UP:
- return {'move': (0, -1)}
- elif key.vk == libtcod.KEY_DOWN:
- return {'move': (0, 1)}
- elif key.vk == libtcod.KEY_LEFT:
- return {'move': (-1, 0)}
- elif key.vk == libtcod.KEY_RIGHT:
- return {'move': (1, 0)}
+def handle_keys(event):
+ if isinstance(event, tcod.event.KeyDown):
+ key = event.sym
- if key.vk == libtcod.KEY_ENTER and key.lalt:
- # Alt+Enter: toggle full screen
- return {'fullscreen': True}
+ # Movement keys
+ if key == tcod.event.KeySym.UP:
+ return {'move': (0, -1)}
+ elif key == tcod.event.KeySym.DOWN:
+ return {'move': (0, 1)}
+ elif key == tcod.event.KeySym.LEFT:
+ return {'move': (-1, 0)}
+ elif key == tcod.event.KeySym.RIGHT:
+ return {'move': (1, 0)}
- elif key.vk == libtcod.KEY_ESCAPE:
- # Exit the game
- return {'exit': True}
+ if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT:
+ # Alt+Enter: toggle full screen
+ return {'fullscreen': True}
+
+ elif key == tcod.event.KeySym.ESCAPE:
+ # Exit the game
+ return {'exit': True}
# No key was pressed
return {}
@@ -355,23 +315,32 @@ That's a lot to take in all at once, so again, let's break it down a
bit.
{{< highlight py3 >}}
-def handle_keys(key):
+def handle_keys(event):
{{ highlight >}}
We're defining a function called `handle_keys`, which takes one
-argument, `key`. `key` in this case will be the key variable we captured
-earlier.
+argument, `event`. `event` in this case will be a tcod event object from
+the event loop.
{{< highlight py3 >}}
- if key.vk == libtcod.KEY_UP:
+ if isinstance(event, tcod.event.KeyDown):
+ key = event.sym
+{{ highlight >}}
+
+First we check if the event is actually a key press (`tcod.event.KeyDown`).
+If it is, we grab the key symbol from `event.sym` and store it in `key`
+for convenience.
+
+{{< highlight py3 >}}
+ if key == tcod.event.KeySym.UP:
{{ highlight >}}
This if statement (along with the other elifs) just tell us which key
was pressed. Right now, it's one of the arrow keys for movement. What's
-more interesting is the code inside these if statements
+more interesting is the code inside these if statements.
{{< highlight py3 >}}
- return {'move': (0, -1)}
+ return {'move': (0, -1)}
{{ highlight >}}
So what's going on here? Well, when we return from this function, the
@@ -388,17 +357,19 @@ what direction to move the player. So for example, the 'up' key will
move us '0' on the x axis, and '-1' on the y axis.
{{< highlight py3 >}}
- if key.vk == libtcod.KEY_ENTER and key.lalt:
- # Alt+Enter: toggle full screen
- return {'fullscreen': True}
- elif key.vk == libtcod.KEY_ESCAPE:
- # Exit the game
- return {'exit': True}
+ if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT:
+ # Alt+Enter: toggle full screen
+ return {'fullscreen': True}
+ elif key == tcod.event.KeySym.ESCAPE:
+ # Exit the game
+ return {'exit': True}
{{ highlight >}}
These are our non-movement actions that we're allowing for now. If the
user pressed ALT+Enter, the game will go full screen. If the user
-presses 'Esc', the game will exit.
+presses 'Esc', the game will exit. Note that `event.mod &
+tcod.event.Modifier.LALT` checks whether the left Alt key is held down
+at the same time as the Enter key.
{{< highlight py3 >}}
return {}
@@ -414,59 +385,62 @@ function.
{{< codetab >}}
{{< diff-tab >}}
{{< highlight diff >}}
- ...
- libtcod.console_flush()
-
-- key = libtcod.console_check_for_keypress()
-+ action = handle_keys(key)
+ ...
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.Quit):
+ raise SystemExit()
+- if isinstance(event, tcod.event.KeyDown):
+- if event.sym == tcod.event.KeySym.ESCAPE:
+- raise SystemExit()
++ action = handle_keys(event)
+
-+ move = action.get('move')
-+ exit = action.get('exit')
-+ fullscreen = action.get('fullscreen')
-
-+ if move:
-+ dx, dy = move
-+ player_x += dx
-+ player_y += dy
-
-- if key.vk == libtcod.KEY_ESCAPE:
-+ if exit:
-+ return True
++ move = action.get('move')
++ exit = action.get('exit')
++ fullscreen = action.get('fullscreen')
+
-+ if fullscreen:
-+ libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
- ...
++ if move:
++ dx, dy = move
++ player_x += dx
++ player_y += dy
++
++ if exit:
++ raise SystemExit()
++
++ if fullscreen:
++ context.sdl_window.fullscreen = not context.sdl_window.fullscreen
+ ...
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
-... - libtcod.console_flush() - - key = libtcod.console_check_for_keypress() - action = handle_keys(key) - - move = action.get('move') - exit = action.get('exit') - fullscreen = action.get('fullscreen') - - if move: - dx, dy = move - player_x += dx - player_y += dy - - if key.vk == libtcod.KEY_ESCAPE: - if exit: - return True - - if fullscreen: - libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) - ...+
... + for event in tcod.event.wait(): + if isinstance(event, tcod.event.Quit): + raise SystemExit() + if isinstance(event, tcod.event.KeyDown): + if event.sym == tcod.event.KeySym.ESCAPE: + raise SystemExit() + action = handle_keys(event) + + move = action.get('move') + exit = action.get('exit') + fullscreen = action.get('fullscreen') + + if move: + dx, dy = move + player_x += dx + player_y += dy + + if exit: + raise SystemExit() + + if fullscreen: + context.sdl_window.fullscreen = not context.sdl_window.fullscreen + ...{{ original-tab >}} {{ codetab >}} Note: I'll denote lines to delete in red. So in this case, remove the -`key = libtcod.console_check_for_keypress()` and `if key.vk == -libtcod.KEY_ESCAPE` lines. +inline key-check block and replace it with the calls to `handle_keys`. Also be sure to import the `handle_keys` function at the top of `engine.py`. @@ -474,13 +448,13 @@ Also be sure to import the `handle_keys` function at the top of {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod +from input_handlers import handle_keys {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -
import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -277,10 +272,10 @@ to the `constants` dictionary. + constants['fov_algorithm']) - render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, -- screen_height, bar_width, panel_height, panel_y, mouse, colors, game_state) +- screen_height, bar_width, panel_height, panel_y, mouse_pos, colors, game_state, root_console) + render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, + constants['screen_width'], constants['screen_height'], constants['bar_width'], -+ constants['panel_height'], constants['panel_y'], mouse, constants['colors'], game_state) ++ constants['panel_height'], constants['panel_y'], mouse_pos, constants['colors'], game_state, root_console) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} @@ -291,10 +286,10 @@ to the `constants` dictionary. constants['fov_algorithm']) render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, - screen_height, bar_width, panel_height, panel_y, mouse, colors, game_state) + screen_height, bar_width, panel_height, panel_y, mouse_pos, colors, game_state, root_console) render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, constants['screen_width'], constants['screen_height'], constants['bar_width'], - constants['panel_height'], constants['panel_y'], mouse, constants['colors'], game_state) + constants['panel_height'], constants['panel_y'], mouse_pos, constants['colors'], game_state, root_console) {{ original-tab >}} {{ codetab >}} @@ -325,7 +320,7 @@ def get_constants(): +def get_game_variables(constants): + fighter_component = Fighter(hp=30, defense=2, power=5) + inventory_component = Inventory(26) -+ player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, ++ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, + fighter=fighter_component, inventory=inventory_component) + entities = [player] + @@ -348,7 +343,7 @@ def get_constants(): def get_game_variables(constants): fighter_component = Fighter(hp=30, defense=2, power=5) inventory_component = Inventory(26) - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, inventory=inventory_component) entities = [player] @@ -369,8 +364,6 @@ We'll need to include a few imports in `initialize_new_game.py` for this: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod - +from components.fighter import Fighter +from components.inventory import Inventory + @@ -390,9 +383,7 @@ def get_constants(): {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod from input_handlers import handle_keys{{ original-tab >}} @@ -497,66 +471,48 @@ Try running the engine.py file now. You should be able to move around. Exciting! One last thing before we move on. Take a look at our drawing functions. -Notice how the first argument is '0'? In truth, that represents the -current 'console' we are drawing to, 0 is the default. Rather than just -drawing to the default we'll want to specify which console to draw to, -after initiating a new one. The reasoning is that it will make it easier -to make new consoles and draw to them in the future. This will be -especially useful when we get to the GUI portion of this series. +Notice how we're drawing directly to `root_console`? Rather than doing +that, we'll want to use an offscreen console, then 'blit' (copy) it to +the root console before presenting. This will make it easier to manage +multiple consoles when we get to the GUI portion of this series. Modify the `engine.py` file like this: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - ... - libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False) - -+ con = libtcod.console_new(screen_width, screen_height) - - key = libtcod.Key() - mouse = libtcod.Mouse() - - while not libtcod.console_is_window_closed(): - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) -+ -+ libtcod.console_set_default_foreground(con, libtcod.white) -+ libtcod.console_put_char(con, player_x, player_y, '@', libtcod.BKGND_NONE) -+ libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) -- libtcod.console_set_default_foreground(0, libtcod.white) -- libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE) - libtcod.console_flush() -+ -+ libtcod.console_put_char(con, player_x, player_y, ' ', libtcod.BKGND_NONE) -- libtcod.console_put_char(0, player_x, player_y, ' ', libtcod.BKGND_NONE) + ... + root_console = tcod.console.Console(screen_width, screen_height, order='F') ++ con = tcod.console.Console(screen_width, screen_height, order='F') + + while True: +- root_console.print(player_x, player_y, '@', fg=(255, 255, 255)) ++ con.print(player_x, player_y, '@', fg=(255, 255, 255)) ++ con.blit(dest=root_console) + context.present(root_console) +- root_console.clear() ++ con.clear() + ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -... - libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False) - - con = libtcod.console_new(screen_width, screen_height) - - key = libtcod.Key() - mouse = libtcod.Mouse() - - while not libtcod.console_is_window_closed(): - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - - libtcod.console_set_default_foreground(con, libtcod.white) - libtcod.console_put_char(con, player_x, player_y, '@', libtcod.BKGND_NONE) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) - libtcod.console_set_default_foreground(0, libtcod.white) - libtcod.console_put_char(0, player_x, player_y, '@', libtcod.BKGND_NONE) - libtcod.console_flush() - - libtcod.console_put_char(con, player_x, player_y, ' ', libtcod.BKGND_NONE) - libtcod.console_put_char(0, player_x, player_y, ' ', libtcod.BKGND_NONE)+... + root_console = tcod.console.Console(screen_width, screen_height, order='F') + con = tcod.console.Console(screen_width, screen_height, order='F') + + while True: + root_console.print(player_x, player_y, '@', fg=(255, 255, 255)) + con.print(player_x, player_y, '@', fg=(255, 255, 255)) + con.blit(dest=root_console) + context.present(root_console) + root_console.clear() + con.clear() + ...{{ original-tab >}} {{ codetab >}} -`libtcod.console_blit` is used to copy `con` to libtcod's root console -which is then presented by `libtcod.console_flush`. +`con.blit(dest=root_console)` is used to copy `con` to the root console, +which is then presented to the screen by `context.present(root_console)`. That wraps up part one of this tutorial\! If you're using git or some other form of version control (and I recommend you do), commit your diff --git a/content/tutorials/tcod/2019/part-10.md b/content/tutorials/tcod/2019/part-10.md index 154a0825..51a35cbf 100644 --- a/content/tutorials/tcod/2019/part-10.md +++ b/content/tutorials/tcod/2019/part-10.md @@ -28,9 +28,6 @@ Our first function in this new file will return the variables that are currently at the top of the `main` function. It looks like this: {{< highlight py3 >}} -import tcod as libtcod - - def get_constants(): window_title = 'Roguelike Tutorial Revised' @@ -60,10 +57,10 @@ def get_constants(): max_items_per_room = 2 colors = { - 'dark_wall': libtcod.Color(0, 0, 100), - 'dark_ground': libtcod.Color(50, 50, 150), - 'light_wall': libtcod.Color(130, 110, 50), - 'light_ground': libtcod.Color(200, 180, 50) + 'dark_wall': (0, 0, 100), + 'dark_ground': (50, 50, 150), + 'light_wall': (130, 110, 50), + 'light_ground': (200, 180, 50) } constants = { @@ -210,62 +207,60 @@ to the `constants` dictionary. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) - -- libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False) -+ libtcod.console_init_root(constants['screen_width'], constants['screen_height'], constants['window_title'], False) - -- con = libtcod.console_new(screen_width, screen_height) -- panel = libtcod.console_new(screen_width, panel_height) -+ con = libtcod.console_new(constants['screen_width'], constants['screen_height']) -+ panel = libtcod.console_new(constants['screen_width'], constants['panel_height']) - -- game_map = GameMap(map_width, map_height) -- game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, -- max_monsters_per_room, max_items_per_room) -+ game_map = GameMap(constants['map_width'], constants['map_height']) -+ game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'], -+ constants['map_width'], constants['map_height'], player, entities, -+ constants['max_monsters_per_room'], constants['max_items_per_room']) - - fov_recompute = True - - fov_map = initialize_fov(game_map) - -- message_log = MessageLog(message_x, message_width, message_height) -+ message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height']) - - key = libtcod.Key() + tileset = tcod.tileset.load_tilesheet('arial10x10.png', 32, 8, tcod.tileset.CHARMAP_TCOD) + +- with tcod.context.new(columns=screen_width, rows=screen_height, title='libtcod tutorial revised', tileset=tileset) as context: ++ with tcod.context.new(columns=constants['screen_width'], rows=constants['screen_height'], title=constants['window_title'], tileset=tileset) as context: +- root_console = tcod.console.Console(screen_width, screen_height, order='F') +- con = tcod.console.Console(screen_width, screen_height, order='F') +- panel = tcod.console.Console(screen_width, panel_height, order='F') ++ root_console = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F') ++ con = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F') ++ panel = tcod.console.Console(constants['screen_width'], constants['panel_height'], order='F') + +- game_map = GameMap(map_width, map_height) +- game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, +- max_monsters_per_room, max_items_per_room) ++ game_map = GameMap(constants['map_width'], constants['map_height']) ++ game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'], ++ constants['map_width'], constants['map_height'], player, entities, ++ constants['max_monsters_per_room'], constants['max_items_per_room']) + + fov_recompute = True + + fov_map = initialize_fov(game_map) + +- message_log = MessageLog(message_x, message_width, message_height) ++ message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height']) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}... - libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) - - libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False) - libtcod.console_init_root(constants['screen_width'], constants['screen_height'], constants['window_title'], False) - - con = libtcod.console_new(screen_width, screen_height) - panel = libtcod.console_new(screen_width, panel_height) - con = libtcod.console_new(constants['screen_width'], constants['screen_height']) - panel = libtcod.console_new(constants['screen_width'], constants['panel_height']) - - game_map = GameMap(map_width, map_height) - game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, - max_monsters_per_room, max_items_per_room) - game_map = GameMap(constants['map_width'], constants['map_height']) - game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'], - constants['map_width'], constants['map_height'], player, entities, - constants['max_monsters_per_room'], constants['max_items_per_room']) - - fov_recompute = True - - fov_map = initialize_fov(game_map) - - message_log = MessageLog(message_x, message_width, message_height) - message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height']) - - key = libtcod.Key()+ tileset = tcod.tileset.load_tilesheet('arial10x10.png', 32, 8, tcod.tileset.CHARMAP_TCOD) + + with tcod.context.new(columns=screen_width, rows=screen_height, title='libtcod tutorial revised', tileset=tileset) as context: + with tcod.context.new(columns=constants['screen_width'], rows=constants['screen_height'], title=constants['window_title'], tileset=tileset) as context: + root_console = tcod.console.Console(screen_width, screen_height, order='F') + con = tcod.console.Console(screen_width, screen_height, order='F') + panel = tcod.console.Console(screen_width, panel_height, order='F') + root_console = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F') + con = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F') + panel = tcod.console.Console(constants['screen_width'], constants['panel_height'], order='F') + + game_map = GameMap(map_width, map_height) + game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, + max_monsters_per_room, max_items_per_room) + game_map = GameMap(constants['map_width'], constants['map_height']) + game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'], + constants['map_width'], constants['map_height'], player, entities, + constants['max_monsters_per_room'], constants['max_items_per_room']) + + fov_recompute = True + + fov_map = initialize_fov(game_map) + + message_log = MessageLog(message_x, message_width, message_height) + message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height'])
import tcod as libtcod
-
-from components.fighter import Fighter
+from components.fighter import Fighter
from components.inventory import Inventory
from entity import Entity
@@ -440,76 +431,70 @@ Then modify the `main` function like this:
...
- fighter_component = Fighter(hp=30, defense=2, power=5)
- inventory_component = Inventory(26)
-- player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR,
+- player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR,
- fighter=fighter_component, inventory=inventory_component)
- entities = [player]
- libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
+ tileset = tcod.tileset.load_tilesheet('arial10x10.png', 32, 8, tcod.tileset.CHARMAP_TCOD)
- libtcod.console_init_root(constants['screen_width'], constants['screen_height'], constants['window_title'], False)
+ with tcod.context.new(columns=constants['screen_width'], rows=constants['screen_height'], title=constants['window_title'], tileset=tileset) as context:
+ root_console = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F')
+ con = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F')
+ panel = tcod.console.Console(constants['screen_width'], constants['panel_height'], order='F')
- con = libtcod.console_new(constants['screen_width'], constants['screen_height'])
- panel = libtcod.console_new(constants['screen_width'], constants['panel_height'])
+- game_map = GameMap(constants['map_width'], constants['map_height'])
+- game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'],
+- constants['map_width'], constants['map_height'], player, entities,
+- constants['max_monsters_per_room'], constants['max_items_per_room'])
-- game_map = GameMap(constants['map_width'], constants['map_height'])
-- game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'],
-- constants['map_width'], constants['map_height'], player, entities,
-- constants['max_monsters_per_room'], constants['max_items_per_room'])
++ player, entities, game_map, message_log, game_state = get_game_variables(constants)
-+ player, entities, game_map, message_log, game_state = get_game_variables(constants)
+ fov_recompute = True
- fov_recompute = True
-
- fov_map = initialize_fov(game_map)
-
-- message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height'])
+ fov_map = initialize_fov(game_map)
- key = libtcod.Key()
- mouse = libtcod.Mouse()
+- message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height'])
-- game_state = GameStates.PLAYERS_TURN
- previous_game_state = game_state
+- game_state = GameStates.PLAYERS_TURN
+ previous_game_state = game_state
- targeting_item = None
- ...
+ targeting_item = None
+ ...
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
...
fighter_component = Fighter(hp=30, defense=2, power=5)
inventory_component = Inventory(26)
- player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR,
+ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR,
fighter=fighter_component, inventory=inventory_component)
entities = [player]
- libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
+ tileset = tcod.tileset.load_tilesheet('arial10x10.png', 32, 8, tcod.tileset.CHARMAP_TCOD)
- libtcod.console_init_root(constants['screen_width'], constants['screen_height'], constants['window_title'], False)
+ with tcod.context.new(columns=constants['screen_width'], rows=constants['screen_height'], title=constants['window_title'], tileset=tileset) as context:
+ root_console = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F')
+ con = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F')
+ panel = tcod.console.Console(constants['screen_width'], constants['panel_height'], order='F')
- con = libtcod.console_new(constants['screen_width'], constants['screen_height'])
- panel = libtcod.console_new(constants['screen_width'], constants['panel_height'])
+ game_map = GameMap(constants['map_width'], constants['map_height'])
+ game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'],
+ constants['map_width'], constants['map_height'], player, entities,
+ constants['max_monsters_per_room'], constants['max_items_per_room'])
- game_map = GameMap(constants['map_width'], constants['map_height'])
- game_map.make_map(constants['max_rooms'], constants['room_min_size'], constants['room_max_size'],
- constants['map_width'], constants['map_height'], player, entities,
- constants['max_monsters_per_room'], constants['max_items_per_room'])
+ player, entities, game_map, message_log, game_state = get_game_variables(constants)
- player, entities, game_map, message_log, game_state = get_game_variables(constants)
+ fov_recompute = True
- fov_recompute = True
-
- fov_map = initialize_fov(game_map)
+ fov_map = initialize_fov(game_map)
- message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height'])
+ message_log = MessageLog(constants['message_x'], constants['message_width'], constants['message_height'])
- key = libtcod.Key()
- mouse = libtcod.Mouse()
+ game_state = GameStates.PLAYERS_TURN
+ previous_game_state = game_state
- game_state = GameStates.PLAYERS_TURN
- previous_game_state = game_state
-
- targeting_item = None
- ...
+ targeting_item = None
+ ...
{{ original-tab >}}
{{ codetab >}}
@@ -518,13 +503,13 @@ the imports we did before. Modify your import section at the top of
`engine.py` to look like this:
{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}}
-import tcod as libtcod
+import tcod
-from components.fighter import Fighter
-from components.inventory import Inventory
from death_functions import kill_monster, kill_player
-from entity import Entity, get_blocking_entities_at_location
-+from entity get_blocking_entities_at_location
++from entity import get_blocking_entities_at_location
from fov_functions import initialize_fov, recompute_fov
-from game_messages import Message, MessageLog
+from game_messages import Message
@@ -537,14 +522,14 @@ from loader_functions.initialize_new_game import get_constants, get_game_variabl
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
-import tcod as libtcod
+import tcod
from components.fighter import Fighter
from components.inventory import Inventory
from death_functions import kill_monster, kill_player
-from entity import Entity, get_blocking_entities_at_location
+from entity import Entity, get_blocking_entities_at_location
from fov_functions import initialize_fov, recompute_fov
-from game_messages import Message, MessageLog
+from game_messages import Message, MessageLog
from game_states import GameStates
from input_handlers import handle_keys, handle_mouse
from loader_functions.initialize_new_game import get_constants, get_game_variables
@@ -673,37 +658,31 @@ We'll need a new menu function to display our main menu. Open up
it:
{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}}
-def inventory_menu(con, header, inventory, inventory_width, screen_width, screen_height):
+def inventory_menu(con, root_console, header, inventory, inventory_width, screen_width, screen_height):
...
-+def main_menu(con, background_image, screen_width, screen_height):
-+ libtcod.image_blit_2x(background_image, 0, 0, 0)
++def main_menu(con, root_console, screen_width, screen_height):
++ root_console.print(int(screen_width / 2), int(screen_height / 2) - 4, 'TOMBS OF THE ANCIENT KINGS',
++ fg=(255, 255, 63))
++ root_console.print(int(screen_width / 2), int(screen_height - 2), 'By (Your name here)',
++ fg=(255, 255, 63))
+
-+ libtcod.console_set_default_foreground(0, libtcod.light_yellow)
-+ libtcod.console_print_ex(0, int(screen_width / 2), int(screen_height / 2) - 4, libtcod.BKGND_NONE, libtcod.CENTER,
-+ 'TOMBS OF THE ANCIENT KINGS')
-+ libtcod.console_print_ex(0, int(screen_width / 2), int(screen_height - 2), libtcod.BKGND_NONE, libtcod.CENTER,
-+ 'By (Your name here)')
-+
-+ menu(con, '', ['Play a new game', 'Continue last game', 'Quit'], 24, screen_width, screen_height)
++ menu(con, root_console, '', ['Play a new game', 'Continue last game', 'Quit'], 24, screen_width, screen_height)
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
-def inventory_menu(con, header, inventory, inventory_width, screen_width, screen_height):
+def inventory_menu(con, root_console, header, inventory, inventory_width, screen_width, screen_height):
...
-def main_menu(con, background_image, screen_width, screen_height):
- libtcod.image_blit_2x(background_image, 0, 0, 0)
-
- libtcod.console_set_default_foreground(0, libtcod.light_yellow)
- libtcod.console_print_ex(0, int(screen_width / 2), int(screen_height / 2) - 4, libtcod.BKGND_NONE, libtcod.CENTER,
- 'TOMBS OF THE ANCIENT KINGS')
- libtcod.console_print_ex(0, int(screen_width / 2), int(screen_height - 2), libtcod.BKGND_NONE, libtcod.CENTER,
- 'By (Your name here)')
+def main_menu(con, root_console, screen_width, screen_height):
+ root_console.print(int(screen_width / 2), int(screen_height / 2) - 4, 'TOMBS OF THE ANCIENT KINGS',
+ fg=(255, 255, 63))
+ root_console.print(int(screen_width / 2), int(screen_height - 2), 'By (Your name here)',
+ fg=(255, 255, 63))
- menu(con, '', ['Play a new game', 'Continue last game', 'Quit'], 24, screen_width, screen_height)
+ menu(con, root_console, '', ['Play a new game', 'Continue last game', 'Quit'], 24, screen_width, screen_height)
{{ original-tab >}}
{{ codetab >}}
@@ -721,38 +700,43 @@ much to
cover.*
{{< highlight py3 >}}
-def play_game(player, entities, game_map, message_log, game_state, con, panel, constants):
+def play_game(player, entities, game_map, message_log, game_state, con, panel, root_console, context, constants):
fov_recompute = True
fov_map = initialize_fov(game_map)
- key = libtcod.Key()
- mouse = libtcod.Mouse()
+ mouse_pos = (0, 0)
game_state = GameStates.PLAYERS_TURN
previous_game_state = game_state
targeting_item = None
- while not libtcod.console_is_window_closed():
- libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse)
-
+ while True:
if fov_recompute:
recompute_fov(fov_map, player.x, player.y, constants['fov_radius'], constants['fov_light_walls'],
constants['fov_algorithm'])
render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log,
constants['screen_width'], constants['screen_height'], constants['bar_width'],
- constants['panel_height'], constants['panel_y'], mouse, constants['colors'], game_state)
+ constants['panel_height'], constants['panel_y'], mouse_pos, constants['colors'], game_state,
+ root_console)
fov_recompute = False
- libtcod.console_flush()
+ context.present(root_console)
clear_all(con, entities)
- action = handle_keys(key, game_state)
- mouse_action = handle_mouse(mouse)
+ action = {}
+ mouse_action = {}
+
+ for event in tcod.event.wait():
+ if isinstance(event, tcod.event.MouseMotion):
+ mouse_pos = (event.tile.x, event.tile.y)
+ elif isinstance(event, tcod.event.MouseButtonDown):
+ mouse_action = handle_mouse(event)
+ action = handle_keys(event, game_state)
move = action.get('move')
pickup = action.get('pickup')
@@ -793,7 +777,7 @@ def play_game(player, entities, game_map, message_log, game_state, con, panel, c
break
else:
- message_log.add_message(Message('There is nothing here to pick up.', libtcod.yellow))
+ message_log.add_message(Message('There is nothing here to pick up.', (255, 255, 0)))
if show_inventory:
previous_game_state = game_state
@@ -833,7 +817,7 @@ def play_game(player, entities, game_map, message_log, game_state, con, panel, c
return True
if fullscreen:
- libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
+ context.sdl_window.fullscreen = not context.sdl_window.fullscreen
for player_turn_result in player_turn_results:
message = player_turn_result.get('message')
@@ -924,74 +908,74 @@ an existing one, or exit the program.
def main():
constants = get_constants()
- libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
-
- libtcod.console_init_root(constants['screen_width'], constants['screen_height'], constants['window_title'], False)
-
- con = libtcod.console_new(constants['screen_width'], constants['screen_height'])
- panel = libtcod.console_new(constants['screen_width'], constants['panel_height'])
-
- player = None
- entities = []
- game_map = None
- message_log = None
- game_state = None
+ tileset = tcod.tileset.load_tilesheet('arial10x10.png', 32, 8, tcod.tileset.CHARMAP_TCOD)
- show_main_menu = True
- show_load_error_message = False
+ with tcod.context.new(
+ columns=constants['screen_width'],
+ rows=constants['screen_height'],
+ title=constants['window_title'],
+ tileset=tileset,
+ ) as context:
+ root_console = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F')
+ con = tcod.console.Console(constants['screen_width'], constants['screen_height'], order='F')
+ panel = tcod.console.Console(constants['screen_width'], constants['panel_height'], order='F')
- main_menu_background_image = libtcod.image_load('menu_background.png')
+ player = None
+ entities = []
+ game_map = None
+ message_log = None
+ game_state = None
- key = libtcod.Key()
- mouse = libtcod.Mouse()
+ show_main_menu = True
+ show_load_error_message = False
- while not libtcod.console_is_window_closed():
- libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse)
+ while True:
+ root_console.clear()
- if show_main_menu:
- main_menu(con, main_menu_background_image, constants['screen_width'],
- constants['screen_height'])
+ if show_main_menu:
+ main_menu(con, root_console, constants['screen_width'], constants['screen_height'])
- if show_load_error_message:
- message_box(con, 'No save game to load', 50, constants['screen_width'], constants['screen_height'])
+ if show_load_error_message:
+ message_box(con, root_console, 'No save game to load', 50, constants['screen_width'],
+ constants['screen_height'])
- libtcod.console_flush()
+ context.present(root_console)
- action = handle_main_menu(key)
+ action = {}
+ for event in tcod.event.wait():
+ action = handle_main_menu(event)
+ if action:
+ break
- new_game = action.get('new_game')
- load_saved_game = action.get('load_game')
- exit_game = action.get('exit')
+ new_game = action.get('new_game')
+ load_saved_game = action.get('load_game')
+ exit_game = action.get('exit')
- if show_load_error_message and (new_game or load_saved_game or exit_game):
- show_load_error_message = False
- elif new_game:
- player, entities, game_map, message_log, game_state = get_game_variables(constants)
- game_state = GameStates.PLAYERS_TURN
+ if show_load_error_message and (new_game or load_saved_game or exit_game):
+ show_load_error_message = False
+ elif new_game:
+ player, entities, game_map, message_log, game_state = get_game_variables(constants)
+ game_state = GameStates.PLAYERS_TURN
- show_main_menu = False
- elif load_saved_game:
- try:
- player, entities, game_map, message_log, game_state = load_game()
show_main_menu = False
- except FileNotFoundError:
- show_load_error_message = True
- elif exit_game:
- break
+ elif load_saved_game:
+ try:
+ player, entities, game_map, message_log, game_state = load_game()
+ show_main_menu = False
+ except FileNotFoundError:
+ show_load_error_message = True
+ elif exit_game:
+ break
- else:
- libtcod.console_clear(con)
- play_game(player, entities, game_map, message_log, game_state, con, panel, constants)
+ else:
+ con.clear()
+ play_game(player, entities, game_map, message_log, game_state, con, panel, root_console, context,
+ constants)
- show_main_menu = True
+ show_main_menu = True
{{ highlight >}}
-We're loading a background image with `image_load` to display in our
-main menu. The sample image used for this tutorial can be [found
-here](http://roguecentral.org/doryen/files/menu_background1.png).
-Download it and put in in your project's directory.
-
-Other than that, a lot of this should look familiar. We're displaying
+A lot of this should look familiar. We're displaying
the main menu with three options, and accepting keyboard input to
determine which option to go with. If the user starts a new game, we use
our `get_game_variables` function from earlier, and if an old game is
@@ -1005,13 +989,13 @@ yet, so let's do so now. We'll start with `message_box` and we'll put it
in `menus.py`, at the bottom of the file:
{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}}
-+def message_box(con, header, width, screen_width, screen_height):
-+ menu(con, header, [], width, screen_width, screen_height)
++def message_box(con, root_console, header, width, screen_width, screen_height):
++ menu(con, root_console, header, [], width, screen_width, screen_height)
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
-def message_box(con, header, width, screen_width, screen_height):
- menu(con, header, [], width, screen_width, screen_height)
+def message_box(con, root_console, header, width, screen_width, screen_height):
+ menu(con, root_console, header, [], width, screen_width, screen_height)
{{ original-tab >}}
{{ codetab >}}
@@ -1021,44 +1005,44 @@ basically.
Now on to `handle_main_menu`, which goes in `input_handlers.py`:
{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}}
-def handle_inventory_keys(key):
+def handle_inventory_keys(event):
...
-+def handle_main_menu(key):
-+ key_char = chr(key.c)
-+
-+ if key_char == 'a':
-+ return {'new_game': True}
-+ elif key_char == 'b':
-+ return {'load_game': True}
-+ elif key_char == 'c' or key.vk == libtcod.KEY_ESCAPE:
-+ return {'exit': True}
++def handle_main_menu(event):
++ if isinstance(event, tcod.event.KeyDown):
++ key = event.sym
++ if key == tcod.event.KeySym.a:
++ return {'new_game': True}
++ elif key == tcod.event.KeySym.b:
++ return {'load_game': True}
++ elif key == tcod.event.KeySym.c or key == tcod.event.KeySym.ESCAPE:
++ return {'exit': True}
+
+ return {}
-def handle_mouse(mouse):
+def handle_mouse(event):
...
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
-def handle_inventory_keys(key): +{{ original-tab >}} {{ codetab >}} @@ -437,7 +437,7 @@ following to the bottom of the `game_map.py`: player.fighter.heal(player.fighter.max_hp // 2) - message_log.add_message(Message('You take a moment to rest, and recover your strength.', libtcod.light_violet)) + message_log.add_message(Message('You take a moment to rest, and recover your strength.', (191, 63, 255))) return entities {{ highlight >}} @@ -467,11 +467,11 @@ At last, let's modify `engine.py` to use this new function. + entities = game_map.next_floor(player, message_log, constants) + fov_map = initialize_fov(game_map) + fov_recompute = True -+ libtcod.console_clear(con) ++ con.clear() + + break + else: -+ message_log.add_message(Message('There are no stairs here.', libtcod.yellow)) ++ message_log.add_message(Message('There are no stairs here.', (255, 255, 0))) if game_state == GameStates.TARGETING: ... @@ -493,11 +493,11 @@ At last, let's modify `engine.py` to use this new function. entities = game_map.next_floor(player, message_log, constants) fov_map = initialize_fov(game_map) fov_recompute = True - libtcod.console_clear(con) + con.clear() break else: - message_log.add_message(Message('There are no stairs here.', libtcod.yellow)) + message_log.add_message(Message('There are no stairs here.', (255, 255, 0))) if game_state == GameStates.TARGETING: ... @@ -515,11 +515,10 @@ bar, by rendering the `render_all` function like so: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... render_bar(panel, 1, 1, bar_width, 'HP', player.fighter.hp, player.fighter.max_hp, - libtcod.light_red, libtcod.darker_red) -+ libtcod.console_print_ex(panel, 1, 3, libtcod.BKGND_NONE, libtcod.LEFT, -+ 'Dungeon level: {0}'.format(game_map.dungeon_level)) + (255, 63, 63), (128, 0, 0)) ++ panel.print(1, 3, 'Dungeon level: {0}'.format(game_map.dungeon_level)) - libtcod.console_set_default_foreground(panel, libtcod.light_gray) + panel.default_fg = (191, 191, 191) ... {{ highlight >}} {{ diff-tab >}} @@ -527,11 +526,10 @@ bar, by rendering the `render_all` function like so:def handle_inventory_keys(event): ... -def handle_main_menu(key): - key_char = chr(key.c) - - if key_char == 'a': - return {'new_game': True} - elif key_char == 'b': - return {'load_game': True} - elif key_char == 'c' or key.vk == libtcod.KEY_ESCAPE: - return {'exit': True} +def handle_main_menu(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym + if key == tcod.event.KeySym.a: + return {'new_game': True} + elif key == tcod.event.KeySym.b: + return {'load_game': True} + elif key == tcod.event.KeySym.c or key == tcod.event.KeySym.ESCAPE: + return {'exit': True} return {} -def handle_mouse(mouse): +def handle_mouse(event): ...{{ original-tab >}} {{ codetab >}} @@ -1070,7 +1054,7 @@ option can be done through the 'c' key or 'Escape'. Remember to import these new functions into `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod from death_functions import kill_monster, kill_player from entity import get_blocking_entities_at_location @@ -1087,7 +1071,7 @@ from render_functions import clear_all, render_all {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -389,10 +389,10 @@ function: elif key_char == 'd': return {'drop_inventory': True} -+ elif key.vk == libtcod.KEY_ENTER: ++ elif event.sym == tcod.event.KeySym.RETURN: + return {'take_stairs': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ... {{ highlight >}} {{ diff-tab >}} @@ -401,10 +401,10 @@ function: elif key_char == 'd': return {'drop_inventory': True} - elif key.vk == libtcod.KEY_ENTER: + elif event.sym == tcod.event.KeySym.RETURN: return {'take_stairs': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ...import tcod from death_functions import kill_monster, kill_player from entity import get_blocking_entities_at_location diff --git a/content/tutorials/tcod/2019/part-11.md b/content/tutorials/tcod/2019/part-11.md index 58db6b9a..3325fcc3 100644 --- a/content/tutorials/tcod/2019/part-11.md +++ b/content/tutorials/tcod/2019/part-11.md @@ -189,7 +189,7 @@ this: num_rooms += 1 + stairs_component = Stairs(self.dungeon_level + 1) -+ down_stairs = Entity(center_of_last_room_x, center_of_last_room_y, '>', libtcod.white, 'Stairs', ++ down_stairs = Entity(center_of_last_room_x, center_of_last_room_y, '>', (255, 255, 255), 'Stairs', + render_order=RenderOrder.STAIRS, stairs=stairs_component) + entities.append(down_stairs) {{ highlight >}} @@ -258,7 +258,7 @@ this: num_rooms += 1 stairs_component = Stairs(self.dungeon_level + 1) - down_stairs = Entity(center_of_last_room_x, center_of_last_room_y, '>', libtcod.white, 'Stairs', + down_stairs = Entity(center_of_last_room_x, center_of_last_room_y, '>', (255, 255, 255), 'Stairs', render_order=RenderOrder.STAIRS, stairs=stairs_component) entities.append(down_stairs){{ original-tab >}} @@ -340,19 +340,19 @@ To make this happen, we can modify the `draw_entity` function inside {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def draw_entity(con, entity, fov_map): +def draw_entity(con, entity, fov_map, game_map): -- if libtcod.map_is_in_fov(fov_map, entity.x, entity.y): -+ if libtcod.map_is_in_fov(fov_map, entity.x, entity.y) or (entity.stairs and game_map.tiles[entity.x][entity.y].explored): - libtcod.console_set_default_foreground(con, entity.color) - libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE) +- if fov_map[entity.x, entity.y]: ++ if fov_map[entity.x, entity.y] or (entity.stairs and game_map.tiles[entity.x][entity.y].explored): + con.default_fg = entity.color + con.print(entity.x, entity.y, entity.char) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}def draw_entity(con, entity, fov_map): def draw_entity(con, entity, fov_map, game_map): - if libtcod.map_is_in_fov(fov_map, entity.x, entity.y): - if libtcod.map_is_in_fov(fov_map, entity.x, entity.y) or (entity.stairs and game_map.tiles[entity.x][entity.y].explored): - libtcod.console_set_default_foreground(con, entity.color) - libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE)+ if fov_map[entity.x, entity.y]: + if fov_map[entity.x, entity.y] or (entity.stairs and game_map.tiles[entity.x][entity.y].explored): + con.default_fg = entity.color + con.print(entity.x, entity.y, entity.char)
...
render_bar(panel, 1, 1, bar_width, 'HP', player.fighter.hp, player.fighter.max_hp,
- libtcod.light_red, libtcod.darker_red)
- libtcod.console_print_ex(panel, 1, 3, libtcod.BKGND_NONE, libtcod.LEFT,
- 'Dungeon level: {0}'.format(game_map.dungeon_level))
+ (255, 63, 63), (128, 0, 0))
+ panel.print(1, 3, 'Dungeon level: {0}'.format(game_map.dungeon_level))
- libtcod.console_set_default_foreground(panel, libtcod.light_gray)
+ panel.default_fg = (191, 191, 191)
...
{{ original-tab >}}
@@ -589,14 +587,14 @@ each fighter component.
+ fighter_component = Fighter(hp=10, defense=0, power=3, xp=35)
ai_component = BasicMonster()
- monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True,
+ monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True,
render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component)
else:
- fighter_component = Fighter(hp=16, defense=1, power=4)
+ fighter_component = Fighter(hp=16, defense=1, power=4, xp=100)
ai_component = BasicMonster()
- monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component,
+ monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component,
render_order=RenderOrder.ACTOR, ai=ai_component)
...
{{ highlight >}}
@@ -607,13 +605,13 @@ each fighter component.
fighter_component = Fighter(hp=10, defense=0, power=3, xp=35)
ai_component = BasicMonster()
- monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True,
+ monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True,
render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component)
else:
fighter_component = Fighter(hp=16, defense=1, power=4, xp=100)
ai_component = BasicMonster()
- monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component,
+ monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component,
render_order=RenderOrder.ACTOR, ai=ai_component)
...
{{ original-tab >}}
@@ -757,7 +755,7 @@ Now we'll need to add it to the `player` object. Open
fighter_component = Fighter(hp=30, defense=2, power=5)
inventory_component = Inventory(26)
+ level_component = Level()
- player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR,
+ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR,
- fighter=fighter_component, inventory=inventory_component)
+ fighter=fighter_component, inventory=inventory_component, level=level_component)
{{ highlight >}}
@@ -767,7 +765,7 @@ Now we'll need to add it to the `player` object. Open
fighter_component = Fighter(hp=30, defense=2, power=5)
inventory_component = Inventory(26)
level_component = Level()
- player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR,
+ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR,
fighter=fighter_component, inventory=inventory_component, level=level_component)
{{ original-tab >}}
@@ -853,7 +851,7 @@ And now let's process the result in `engine.py`:
+ if leveled_up:
+ message_log.add_message(Message(
+ 'Your battle skills grow stronger! You reached level {0}'.format(
-+ player.level.current_level) + '!', libtcod.yellow))
++ player.level.current_level) + '!', (255, 255, 0)))
+ previous_game_state = game_state
+ game_state = GameStates.LEVEL_UP
@@ -873,7 +871,7 @@ And now let's process the result in `engine.py`:
if leveled_up:
message_log.add_message(Message(
'Your battle skills grow stronger! You reached level {0}'.format(
- player.level.current_level) + '!', libtcod.yellow))
+ player.level.current_level) + '!', (255, 255, 0)))
previous_game_state = game_state
game_state = GameStates.LEVEL_UP
@@ -917,34 +915,34 @@ Let's create a new menu function, called `level_up_menu`, which will
display our options:
{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}}
-def main_menu(con, background_image, screen_width, screen_height):
+def main_menu(con, root_console, screen_width, screen_height):
...
-+def level_up_menu(con, header, player, menu_width, screen_width, screen_height):
++def level_up_menu(con, root_console, header, player, menu_width, screen_width, screen_height):
+ options = ['Constitution (+20 HP, from {0})'.format(player.fighter.max_hp),
+ 'Strength (+1 attack, from {0})'.format(player.fighter.power),
+ 'Agility (+1 defense, from {0})'.format(player.fighter.defense)]
+
-+ menu(con, header, options, menu_width, screen_width, screen_height)
++ menu(con, root_console, header, options, menu_width, screen_width, screen_height)
-def message_box(con, header, width, screen_width, screen_height):
+def message_box(con, root_console, header, width, screen_width, screen_height):
...
{{ highlight >}}
{{ diff-tab >}}
{{< original-tab >}}
-def main_menu(con, background_image, screen_width, screen_height): +def main_menu(con, root_console, screen_width, screen_height): ... -def level_up_menu(con, header, player, menu_width, screen_width, screen_height): +def level_up_menu(con, root_console, header, player, menu_width, screen_width, screen_height): options = ['Constitution (+20 HP, from {0})'.format(player.fighter.max_hp), 'Strength (+1 attack, from {0})'.format(player.fighter.power), 'Agility (+1 defense, from {0})'.format(player.fighter.defense)] - menu(con, header, options, menu_width, screen_width, screen_height) + menu(con, root_console, header, options, menu_width, screen_width, screen_height) -def message_box(con, header, width, screen_width, screen_height): +def message_box(con, root_console, header, width, screen_width, screen_height): ...{{ original-tab >}} {{ codetab >}} @@ -953,7 +951,7 @@ Modify the `render_all` function to display this menu, after importing the `level_up_menu` function. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod from enum import Enum @@ -965,7 +963,7 @@ from game_states import GameStates {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +import tcod from enum import Enum @@ -981,7 +979,7 @@ from menus import inventory_menu, level_up_menu ... + elif game_state == GameStates.LEVEL_UP: -+ level_up_menu(con, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height) ++ level_up_menu(con, root_console, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} @@ -990,7 +988,7 @@ from menus import inventory_menu, level_up_menu ... elif game_state == GameStates.LEVEL_UP: - level_up_menu(con, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height) + level_up_menu(con, root_console, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height){{ original-tab >}} {{ codetab >}} @@ -999,46 +997,44 @@ Of course, we'll need to handle the input for this menu. Open up `input_handlers.py` and add the following function: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_main_menu(key): +def handle_main_menu(event): ... -+def handle_level_up_menu(key): -+ if key: -+ key_char = chr(key.c) -+ -+ if key_char == 'a': ++def handle_level_up_menu(event): ++ if isinstance(event, tcod.event.KeyDown): ++ key = event.sym ++ if key == tcod.event.KeySym.a: + return {'level_up': 'hp'} -+ elif key_char == 'b': ++ elif key == tcod.event.KeySym.b: + return {'level_up': 'str'} -+ elif key_char == 'c': ++ elif key == tcod.event.KeySym.c: + return {'level_up': 'def'} + + return {} -def handle_mouse(mouse): +def handle_mouse(event): ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_main_menu(key): +def handle_main_menu(event): ... -def handle_level_up_menu(key): - if key: - key_char = chr(key.c) - - if key_char == 'a': +def handle_level_up_menu(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym + if key == tcod.event.KeySym.a: return {'level_up': 'hp'} - elif key_char == 'b': + elif key == tcod.event.KeySym.b: return {'level_up': 'str'} - elif key_char == 'c': + elif key == tcod.event.KeySym.c: return {'level_up': 'def'} return {} -def handle_mouse(mouse): +def handle_mouse(event): ...{{ original-tab >}} {{ codetab >}} @@ -1046,33 +1042,33 @@ def handle_mouse(mouse): Modify the `handle_keys` function to use this new handler: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) elif game_state == GameStates.TARGETING: - return handle_targeting_keys(key) + return handle_targeting_keys(event) elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) + elif game_state == GameStates.LEVEL_UP: -+ return handle_level_up_menu(key) ++ return handle_level_up_menu(event) return {} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_keys(key, game_state): +{{ original-tab >}} {{ codetab >}} We also need to update the initialization of the player in `engine.py`: -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -- player = Entity(0, 0, '@', libtcod.white) -+ player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True) +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} +- player = Entity(0, 0, '@', (255, 255, 255)) ++ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) elif game_state == GameStates.TARGETING: - return handle_targeting_keys(key) + return handle_targeting_keys(event) elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) elif game_state == GameStates.LEVEL_UP: - return handle_level_up_menu(key) + return handle_level_up_menu(event) return {}{{ original-tab >}} @@ -1170,25 +1166,25 @@ key to `handle_player_turn_keys`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - elif key.vk == libtcod.KEY_ENTER: + elif event.sym == tcod.event.KeySym.RETURN: return {'take_stairs': True} + elif key_char == 'c': + return {'show_character_screen': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}... - elif key.vk == libtcod.KEY_ENTER: + elif event.sym == tcod.event.KeySym.RETURN: return {'take_stairs': True} elif key_char == 'c': return {'show_character_screen': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ...{{ original-tab >}} @@ -1245,32 +1241,34 @@ is handles the 'Escape' key, since the character screen isn't interactive in any way. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_level_up_menu(key): +def handle_level_up_menu(event): ... -+def handle_character_screen(key): -+ if key.vk == libtcod.KEY_ESCAPE: -+ return {'exit': True} ++def handle_character_screen(event): ++ if isinstance(event, tcod.event.KeyDown): ++ if event.sym == tcod.event.KeySym.ESCAPE: ++ return {'exit': True} + + return {} -def handle_mouse(mouse): +def handle_mouse(event): ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_level_up_menu(key): +{{ original-tab >}} {{ codetab >}} @@ -180,63 +201,26 @@ set `fov_recompute` to True. {{ original-tab >}} {{ codetab >}} -But where does the recompute actually *happen*? For that, let's add a -new function to `fov_functions.py` to do the recomputing. The recompute -function will modify the `fov_map` variable based on where the player -is, what the radius for lighting is, whether or not to light the walls, -and what algorithm we're using. - -That's a lot of variables, but consider this: in your game, you'll -probably pick one FOV algorithm and stick with it. Also, whether or not -you light the walls probably won't change during the course of the game. -So why not create our function with default arguments? That way, we can -pass the `light_walls` and `algorithm` variables if we want to, but if -not, a default is chosen. That looks like this: - -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def initialize_fov(game_map): - ... +But where does the recompute actually *happen*? Put your fov +recomputation in `engine.py` at the start of the game loop, before the +render call. -+def recompute_fov(fov_map, x, y, radius, light_walls=True, algorithm=0): -+ libtcod.map_compute_fov(fov_map, x, y, radius, light_walls, algorithm) -{{ highlight >}} -{{ diff-tab >}} -{{< original-tab >}} -def handle_level_up_menu(event): ... -def handle_character_screen(key): - if key.vk == libtcod.KEY_ESCAPE: - return {'exit': True} +def handle_character_screen(event): + if isinstance(event, tcod.event.KeyDown): + if event.sym == tcod.event.KeySym.ESCAPE: + return {'exit': True} return {} -def handle_mouse(mouse): +def handle_mouse(event): ...{{ original-tab >}} {{ codetab >}} @@ -1279,37 +1277,37 @@ Modify `handle_keys` to call this function when showing the character screen: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) elif game_state == GameStates.TARGETING: - return handle_targeting_keys(key) + return handle_targeting_keys(event) elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) elif game_state == GameStates.LEVEL_UP: - return handle_level_up_menu(key) + return handle_level_up_menu(event) + elif game_state == GameStates.CHARACTER_SCREEN: -+ return handle_character_screen(key) ++ return handle_character_screen(event) return {} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_keys(key, game_state): +{{ original-tab >}} diff --git a/content/tutorials/tcod/2019/part-13.md b/content/tutorials/tcod/2019/part-13.md index 59c5e2fe..0a98c9de 100644 --- a/content/tutorials/tcod/2019/part-13.md +++ b/content/tutorials/tcod/2019/part-13.md @@ -154,7 +154,7 @@ Like the other components we've created, we'll need to add these new ones to the `Entity` class. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod import math @@ -217,7 +217,7 @@ class Entity: {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) elif game_state == GameStates.TARGETING: - return handle_targeting_keys(key) + return handle_targeting_keys(event) elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) elif game_state == GameStates.LEVEL_UP: - return handle_level_up_menu(key) + return handle_level_up_menu(event) elif game_state == GameStates.CHARACTER_SCREEN: - return handle_character_screen(key) + return handle_character_screen(event) return {}{{ original-tab >}} @@ -1354,68 +1352,54 @@ the following function. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def level_up_menu(con, header, player, menu_width, screen_width, screen_height): +def level_up_menu(con, root_console, header, player, menu_width, screen_width, screen_height): ... -+def character_screen(player, character_screen_width, character_screen_height, screen_width, screen_height): -+ window = libtcod.console_new(character_screen_width, character_screen_height) ++def character_screen(root_console, player, character_screen_width, character_screen_height, screen_width, screen_height): ++ window = tcod.console.Console(character_screen_width, character_screen_height, order='F') + -+ libtcod.console_set_default_foreground(window, libtcod.white) ++ window.default_fg = (255, 255, 255) + -+ libtcod.console_print_rect_ex(window, 0, 1, character_screen_width, character_screen_height, libtcod.BKGND_NONE, -+ libtcod.LEFT, 'Character Information') -+ libtcod.console_print_rect_ex(window, 0, 2, character_screen_width, character_screen_height, libtcod.BKGND_NONE, -+ libtcod.LEFT, 'Level: {0}'.format(player.level.current_level)) -+ libtcod.console_print_rect_ex(window, 0, 3, character_screen_width, character_screen_height, libtcod.BKGND_NONE, -+ libtcod.LEFT, 'Experience: {0}'.format(player.level.current_xp)) -+ libtcod.console_print_rect_ex(window, 0, 4, character_screen_width, character_screen_height, libtcod.BKGND_NONE, -+ libtcod.LEFT, 'Experience to Level: {0}'.format(player.level.experience_to_next_level)) -+ libtcod.console_print_rect_ex(window, 0, 6, character_screen_width, character_screen_height, libtcod.BKGND_NONE, -+ libtcod.LEFT, 'Maximum HP: {0}'.format(player.fighter.max_hp)) -+ libtcod.console_print_rect_ex(window, 0, 7, character_screen_width, character_screen_height, libtcod.BKGND_NONE, -+ libtcod.LEFT, 'Attack: {0}'.format(player.fighter.power)) -+ libtcod.console_print_rect_ex(window, 0, 8, character_screen_width, character_screen_height, libtcod.BKGND_NONE, -+ libtcod.LEFT, 'Defense: {0}'.format(player.fighter.defense)) ++ window.print(0, 1, 'Character Information') ++ window.print(0, 2, 'Level: {0}'.format(player.level.current_level)) ++ window.print(0, 3, 'Experience: {0}'.format(player.level.current_xp)) ++ window.print(0, 4, 'Experience to Level: {0}'.format(player.level.experience_to_next_level)) ++ window.print(0, 6, 'Maximum HP: {0}'.format(player.fighter.max_hp)) ++ window.print(0, 7, 'Attack: {0}'.format(player.fighter.power)) ++ window.print(0, 8, 'Defense: {0}'.format(player.fighter.defense)) + + x = screen_width // 2 - character_screen_width // 2 + y = screen_height // 2 - character_screen_height // 2 -+ libtcod.console_blit(window, 0, 0, character_screen_width, character_screen_height, 0, x, y, 1.0, 0.7) ++ window.blit(dest=root_console, dest_x=x, dest_y=y, fg_alpha=1.0, bg_alpha=0.7) -def message_box(con, header, width, screen_width, screen_height): +def message_box(con, root_console, header, width, screen_width, screen_height): ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def level_up_menu(con, header, player, menu_width, screen_width, screen_height): +{{ original-tab >}} @@ -482,26 +482,26 @@ def get_game_variables(constants): if item_choice == 'healing_potion': - item_component = Item(use_function=heal, amount=4) + item_component = Item(use_function=heal, amount=40) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'fireball_scroll': - item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( -- 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), +- 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), - damage=12, radius=3) + item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( -+ 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), ++ 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), + damage=25, radius=3) - item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'confusion_scroll': item_component = Item(use_function=cast_confuse, targeting=True, targeting_message=Message( - 'Left-click an enemy to confuse it, or right-click to cancel.', libtcod.light_cyan)) - item = Entity(x, y, '#', libtcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, + 'Left-click an enemy to confuse it, or right-click to cancel.', (63, 255, 255))) + item = Entity(x, y, '#', (255, 114, 114), 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component) else: - item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) + item_component = Item(use_function=cast_lightning, damage=40, maximum_range=5) - item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component) ... {{ highlight >}} @@ -514,26 +514,26 @@ def get_game_variables(constants): if item_choice == 'healing_potion': item_component = Item(use_function=heal, amount=4) item_component = Item(use_function=heal, amount=40) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'fireball_scroll': item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( - 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), + 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), damage=12, radius=3) item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( - 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), + 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), damage=25, radius=3) - item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'confusion_scroll': item_component = Item(use_function=cast_confuse, targeting=True, targeting_message=Message( - 'Left-click an enemy to confuse it, or right-click to cancel.', libtcod.light_cyan)) - item = Entity(x, y, '#', libtcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, + 'Left-click an enemy to confuse it, or right-click to cancel.', (63, 255, 255))) + item = Entity(x, y, '#', (255, 114, 114), 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component) else: item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) item_component = Item(use_function=cast_lightning, damage=40, maximum_range=5) - item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component) ...def level_up_menu(con, root_console, header, player, menu_width, screen_width, screen_height): ... -def character_screen(player, character_screen_width, character_screen_height, screen_width, screen_height): - window = libtcod.console_new(character_screen_width, character_screen_height) - - libtcod.console_set_default_foreground(window, libtcod.white) - - libtcod.console_print_rect_ex(window, 0, 1, character_screen_width, character_screen_height, libtcod.BKGND_NONE, - libtcod.LEFT, 'Character Information') - libtcod.console_print_rect_ex(window, 0, 2, character_screen_width, character_screen_height, libtcod.BKGND_NONE, - libtcod.LEFT, 'Level: {0}'.format(player.level.current_level)) - libtcod.console_print_rect_ex(window, 0, 3, character_screen_width, character_screen_height, libtcod.BKGND_NONE, - libtcod.LEFT, 'Experience: {0}'.format(player.level.current_xp)) - libtcod.console_print_rect_ex(window, 0, 4, character_screen_width, character_screen_height, libtcod.BKGND_NONE, - libtcod.LEFT, 'Experience to Level: {0}'.format(player.level.experience_to_next_level)) - libtcod.console_print_rect_ex(window, 0, 6, character_screen_width, character_screen_height, libtcod.BKGND_NONE, - libtcod.LEFT, 'Maximum HP: {0}'.format(player.fighter.max_hp)) - libtcod.console_print_rect_ex(window, 0, 7, character_screen_width, character_screen_height, libtcod.BKGND_NONE, - libtcod.LEFT, 'Attack: {0}'.format(player.fighter.power)) - libtcod.console_print_rect_ex(window, 0, 8, character_screen_width, character_screen_height, libtcod.BKGND_NONE, - libtcod.LEFT, 'Defense: {0}'.format(player.fighter.defense)) +def character_screen(root_console, player, character_screen_width, character_screen_height, screen_width, screen_height): + window = tcod.console.Console(character_screen_width, character_screen_height, order='F') + + window.default_fg = (255, 255, 255) + + window.print(0, 1, 'Character Information') + window.print(0, 2, 'Level: {0}'.format(player.level.current_level)) + window.print(0, 3, 'Experience: {0}'.format(player.level.current_xp)) + window.print(0, 4, 'Experience to Level: {0}'.format(player.level.experience_to_next_level)) + window.print(0, 6, 'Maximum HP: {0}'.format(player.fighter.max_hp)) + window.print(0, 7, 'Attack: {0}'.format(player.fighter.power)) + window.print(0, 8, 'Defense: {0}'.format(player.fighter.defense)) x = screen_width // 2 - character_screen_width // 2 y = screen_height // 2 - character_screen_height // 2 - libtcod.console_blit(window, 0, 0, character_screen_width, character_screen_height, 0, x, y, 1.0, 0.7) + window.blit(dest=root_console, dest_x=x, dest_y=y, fg_alpha=1.0, bg_alpha=0.7) -def message_box(con, header, width, screen_width, screen_height): +def message_box(con, root_console, header, width, screen_width, screen_height): ...{{ original-tab >}} @@ -1425,7 +1409,7 @@ In order to display this new menu, we'll modify `render_all` once again. Start by importing the menu. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod from enum import Enum @@ -1437,7 +1421,7 @@ from game_states import GameStates {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +@@ -442,14 +442,14 @@ def get_game_variables(constants): + fighter_component = Fighter(hp=20, defense=0, power=4, xp=35) ai_component = BasicMonster() - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) else: - fighter_component = Fighter(hp=16, defense=1, power=4, xp=100) + fighter_component = Fighter(hp=30, defense=2, power=8, xp=100) ai_component = BasicMonster() - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, + monster = Entity(x, y, 'T', (0, 128, 0), 'Troll', blocks=True, fighter=fighter_component, render_order=RenderOrder.ACTOR, ai=ai_component) ... {{ highlight >}} @@ -461,14 +461,14 @@ def get_game_variables(constants): fighter_component = Fighter(hp=20, defense=0, power=4, xp=35) ai_component = BasicMonster() - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) else: fighter_component = Fighter(hp=16, defense=1, power=4, xp=100) fighter_component = Fighter(hp=30, defense=2, power=8, xp=100) ai_component = BasicMonster() - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, + monster = Entity(x, y, 'T', (0, 128, 0), 'Troll', blocks=True, fighter=fighter_component, render_order=RenderOrder.ACTOR, ai=ai_component) ...import tcod from enum import Enum @@ -1453,18 +1437,18 @@ Now, add the menu to the bottom of `render_all`. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} elif game_state == GameStates.LEVEL_UP: - level_up_menu(con, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height) + level_up_menu(con, root_console, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height) + elif game_state == GameStates.CHARACTER_SCREEN: -+ character_screen(player, 30, 10, screen_width, screen_height) ++ character_screen(root_console, player, 30, 10, screen_width, screen_height) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}{{ original-tab >}} {{ codetab >}} @@ -1476,51 +1460,55 @@ will start getting more difficult. Open up `input_handlers.py` and add the following to `handle_player_turn_keys`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_player_turn_keys(key): - key_char = chr(key.c) - - if key.vk == libtcod.KEY_UP or key_char == 'k': - return {'move': (0, -1)} - elif key.vk == libtcod.KEY_DOWN or key_char == 'j': - return {'move': (0, 1)} - elif key.vk == libtcod.KEY_LEFT or key_char == 'h': - return {'move': (-1, 0)} - elif key.vk == libtcod.KEY_RIGHT or key_char == 'l': - return {'move': (1, 0)} - elif key_char == 'y': - return {'move': (-1, -1)} - elif key_char == 'u': - return {'move': (1, -1)} - elif key_char == 'b': - return {'move': (-1, 1)} - elif key_char == 'n': - return {'move': (1, 1)} -+ elif key_char == 'z': -+ return {'wait': True} +def handle_player_turn_keys(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym + key_char = chr(key) if tcod.event.KeySym.a <= key <= tcod.event.KeySym.z else '' + + if key == tcod.event.KeySym.UP or key_char == 'k': + return {'move': (0, -1)} + elif key == tcod.event.KeySym.DOWN or key_char == 'j': + return {'move': (0, 1)} + elif key == tcod.event.KeySym.LEFT or key_char == 'h': + return {'move': (-1, 0)} + elif key == tcod.event.KeySym.RIGHT or key_char == 'l': + return {'move': (1, 0)} + elif key_char == 'y': + return {'move': (-1, -1)} + elif key_char == 'u': + return {'move': (1, -1)} + elif key_char == 'b': + return {'move': (-1, 1)} + elif key_char == 'n': + return {'move': (1, 1)} ++ elif key_char == 'z': ++ return {'wait': True} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -elif game_state == GameStates.LEVEL_UP: - level_up_menu(con, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height) + level_up_menu(con, root_console, 'Level up! Choose a stat to raise:', player, 40, screen_width, screen_height) elif game_state == GameStates.CHARACTER_SCREEN: - character_screen(player, 30, 10, screen_width, screen_height)+ character_screen(root_console, player, 30, 10, screen_width, screen_height)def handle_player_turn_keys(key): - key_char = chr(key.c) - - if key.vk == libtcod.KEY_UP or key_char == 'k': - return {'move': (0, -1)} - elif key.vk == libtcod.KEY_DOWN or key_char == 'j': - return {'move': (0, 1)} - elif key.vk == libtcod.KEY_LEFT or key_char == 'h': - return {'move': (-1, 0)} - elif key.vk == libtcod.KEY_RIGHT or key_char == 'l': - return {'move': (1, 0)} - elif key_char == 'y': - return {'move': (-1, -1)} - elif key_char == 'u': - return {'move': (1, -1)} - elif key_char == 'b': - return {'move': (-1, 1)} - elif key_char == 'n': - return {'move': (1, 1)} - elif key_char == 'z': - return {'wait': True}+def handle_player_turn_keys(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym + key_char = chr(key) if tcod.event.KeySym.a <= key <= tcod.event.KeySym.z else '' + + if key == tcod.event.KeySym.UP or key_char == 'k': + return {'move': (0, -1)} + elif key == tcod.event.KeySym.DOWN or key_char == 'j': + return {'move': (0, 1)} + elif key == tcod.event.KeySym.LEFT or key_char == 'h': + return {'move': (-1, 0)} + elif key == tcod.event.KeySym.RIGHT or key_char == 'l': + return {'move': (1, 0)} + elif key_char == 'y': + return {'move': (-1, -1)} + elif key_char == 'u': + return {'move': (1, -1)} + elif key_char == 'b': + return {'move': (-1, 1)} + elif key_char == 'n': + return {'move': (1, 1)} + elif key_char == 'z': + return {'wait': True}{{ original-tab >}} {{ codetab >}} diff --git a/content/tutorials/tcod/2019/part-12.md b/content/tutorials/tcod/2019/part-12.md index 8ae25524..a5072ae2 100644 --- a/content/tutorials/tcod/2019/part-12.md +++ b/content/tutorials/tcod/2019/part-12.md @@ -93,13 +93,13 @@ from render_functions import RenderOrder fighter_component = Fighter(hp=10, defense=0, power=3, xp=35) ai_component = BasicMonster() - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) else: fighter_component = Fighter(hp=16, defense=1, power=4, xp=100) ai_component = BasicMonster() - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, + monster = Entity(x, y, 'T', (0, 128, 0), 'Troll', blocks=True, fighter=fighter_component, render_order=RenderOrder.ACTOR, ai=ai_component) entities.append(monster) @@ -115,24 +115,24 @@ from render_functions import RenderOrder - if item_chance < 70: + if item_choice == 'healing_potion': item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) - elif item_chance < 80: + elif item_choice == 'fireball_scroll': item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( - 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), + 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), damage=12, radius=3) - item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) - elif item_chance < 90: + elif item_choice == 'confusion_scroll': item_component = Item(use_function=cast_confuse, targeting=True, targeting_message=Message( - 'Left-click an enemy to confuse it, or right-click to cancel.', libtcod.light_cyan)) - item = Entity(x, y, '#', libtcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, + 'Left-click an enemy to confuse it, or right-click to cancel.', (63, 255, 255))) + item = Entity(x, y, '#', (255, 114, 114), 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component) else: item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) - item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component) entities.append(item) @@ -158,13 +158,13 @@ from render_functions import RenderOrder fighter_component = Fighter(hp=10, defense=0, power=3, xp=35) ai_component = BasicMonster() - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) else: fighter_component = Fighter(hp=16, defense=1, power=4, xp=100) ai_component = BasicMonster() - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, + monster = Entity(x, y, 'T', (0, 128, 0), 'Troll', blocks=True, fighter=fighter_component, render_order=RenderOrder.ACTOR, ai=ai_component) entities.append(monster) @@ -180,24 +180,24 @@ from render_functions import RenderOrder if item_chance < 70: if item_choice == 'healing_potion': item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) elif item_chance < 80: elif item_choice == 'fireball_scroll': item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( - 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), + 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), damage=12, radius=3) - item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) elif item_chance < 90: elif item_choice == 'confusion_scroll': item_component = Item(use_function=cast_confuse, targeting=True, targeting_message=Message( - 'Left-click an enemy to confuse it, or right-click to cancel.', libtcod.light_cyan)) - item = Entity(x, y, '#', libtcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, + 'Left-click an enemy to confuse it, or right-click to cancel.', (63, 255, 255))) + item = Entity(x, y, '#', (255, 114, 114), 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component) else: item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) - item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component) entities.append(item)import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -100,27 +100,48 @@ It's `True` by default, because we have to compute it right when the game starts. Now let's initialize our field of view, which we'll store in a variable -called `fov_map`. `fov_map` will need to not only be initialized, but -recomputed when the player moves. Let's keep these functions out of -`engine.py`, and instead, put them in a new file, called -`fov_functions.py`. In that file, put the following: +called `fov_map`. Instead of a special object, `fov_map` is simply a +numpy boolean array: `True` means that tile is currently visible, `False` +means it is not. Let's keep these functions out of `engine.py`, and +instead, put them in a new file, called `fov_functions.py`. In that +file, put the following: {{< highlight py3 >}} -import tcod as libtcod +import numpy as np +import tcod def initialize_fov(game_map): - fov_map = libtcod.map_new(game_map.width, game_map.height) + return np.zeros((game_map.width, game_map.height), dtype=bool) + + +def recompute_fov(fov_map, game_map, x, y, radius, light_walls=True, algorithm=0): + transparent = np.array( + [[not game_map.tiles[tx][ty].block_sight for ty in range(game_map.height)] + for tx in range(game_map.width)], + dtype=bool + ) + fov_map[:] = tcod.map.compute_fov( + transparent.T, + (y, x), + radius=radius, + light_walls=light_walls, + algorithm=algorithm, + ).T +{{ highlight >}} - for y in range(game_map.height): - for x in range(game_map.width): - libtcod.map_set_properties(fov_map, x, y, not game_map.tiles[x][y].block_sight, - not game_map.tiles[x][y].blocked) +`initialize_fov` returns an all-`False` array the same size as the map — +nothing is visible when the game starts. `recompute_fov` builds a +`transparent` array from the tile data (True where a tile doesn't block +sight), then calls `tcod.map.compute_fov`. The `.T` transposes between +the `[x, y]` layout we use and the `[y, x]` (row, column) layout tcod +expects. After recomputing, `fov_map[x, y]` is `True` wherever the +player can see. - return fov_map -{{ highlight >}} +Note that `recompute_fov` now takes `game_map` as a parameter — it needs +the tile data to build the transparency array. -Call this function in `engine.py` and store the result in `fov_map`. +Call `initialize_fov` in `engine.py` and store the result in `fov_map`. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... @@ -128,7 +149,7 @@ Call this function in `engine.py` and store the result in `fov_map`. + fov_map = initialize_fov(game_map) - key = libtcod.Key() + while True: ... {{ highlight >}} {{ diff-tab >}} @@ -138,7 +159,7 @@ Call this function in `engine.py` and store the result in `fov_map`. fov_map = initialize_fov(game_map) - key = libtcod.Key() + while True: ...import tcod import math @@ -291,9 +291,9 @@ Let's add the new `Equipment` component to the player, in ... level_component = Level() + equipment_component = Equipment() -- player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, +- player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, - fighter=fighter_component, inventory=inventory_component, level=level_component) -+ player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, ++ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, + fighter=fighter_component, inventory=inventory_component, level=level_component, + equipment=equipment_component) entities = [player] @@ -304,9 +304,9 @@ Let's add the new `Equipment` component to the player, in{{ original-tab >}} {{ codetab >}} -`render_all` now loops through each tile in the game map, and checks if -it blocks sight or not. If it does, then it draws it as a wall, and if -not, it draws a floor. +`render_all` now loops through each tile in the game map and checks if +it blocks sight or not. If it does, it draws a wall background; if not, +a floor background. `con.bg[x, y]` directly sets the background color of +tile (x, y) on the console. Run the project now, and you should see the 'map' drawn with some color to it. You'll see our three block wall as well, but there's one problem: @@ -529,21 +488,21 @@ part where the player's move function gets called to look like this: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - if move: - dx, dy = move + if move: + dx, dy = move -+ if not game_map.is_blocked(player.x + dx, player.y + dy): -+ player.move(dx, dy) -- player.move(dx, dy) ++ if not game_map.is_blocked(player.x + dx, player.y + dy): ++ player.move(dx, dy) +- player.move(dx, dy) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -... level_component = Level() equipment_component = Equipment() - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, inventory=inventory_component, level=level_component) - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, inventory=inventory_component, level=level_component, equipment=equipment_component) entities = [player] @@ -317,7 +317,7 @@ Let's add the new `Equipment` component to the player, in Be sure to import the component in this file as well. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod +from components.equipment import Equipment from components.fighter import Fighter @@ -327,7 +327,7 @@ from components.level import Level {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -@@ -670,14 +670,14 @@ from components.fighter import Fighter ... if item_choice == 'healing_potion': item_component = Item(use_function=heal, amount=40) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) + elif item_choice == 'sword': + equippable_component = Equippable(EquipmentSlots.MAIN_HAND, power_bonus=3) -+ item = Entity(x, y, '/', libtcod.sky, 'Sword', equippable=equippable_component) ++ item = Entity(x, y, '/', (0, 191, 255), 'Sword', equippable=equippable_component) + elif item_choice == 'shield': + equippable_component = Equippable(EquipmentSlots.OFF_HAND, defense_bonus=1) -+ item = Entity(x, y, '[', libtcod.darker_orange, 'Shield', equippable=equippable_component) ++ item = Entity(x, y, '[', (128, 64, 0), 'Shield', equippable=equippable_component) elif item_choice == 'fireball_scroll': ... {{ highlight >}} @@ -686,14 +686,14 @@ from components.fighter import Fighterimport tcod as libtcod +else: ...import tcod from components.equipment import Equipment from components.fighter import Fighter @@ -345,13 +345,13 @@ in `Inventory` to equip an item if its equippable, like this: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... if item_component.use_function is None: -- results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) +- results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))}) + equippable_component = item_entity.equippable + + if equippable_component: + results.append({'equip': item_entity}) + else: -+ results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) ++ results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))}) else: ... {{ highlight >}} @@ -359,13 +359,13 @@ in `Inventory` to equip an item if its equippable, like this: {{< original-tab >}}... if item_component.use_function is None: - results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) + results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))}) equippable_component = item_entity.equippable if equippable_component: results.append({'equip': item_entity}) else: - results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) + results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))})... if item_choice == 'healing_potion': item_component = Item(use_function=heal, amount=40) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) elif item_choice == 'sword': equippable_component = Equippable(EquipmentSlots.MAIN_HAND, power_bonus=3) - item = Entity(x, y, '/', libtcod.sky, 'Sword', equippable=equippable_component) + item = Entity(x, y, '/', (0, 191, 255), 'Sword', equippable=equippable_component) elif item_choice == 'shield': equippable_component = Equippable(EquipmentSlots.OFF_HAND, defense_bonus=1) - item = Entity(x, y, '[', libtcod.darker_orange, 'Shield', equippable=equippable_component) + item = Entity(x, y, '[', (128, 64, 0), 'Shield', equippable=equippable_component) elif item_choice == 'fireball_scroll': ...{{ original-tab >}} @@ -706,7 +706,7 @@ function in `initialize_new_game.py` to give the player a dagger at the start. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod from components.equipment import Equipment +from components.equippable import Equippable @@ -727,13 +727,13 @@ def get_game_variables(constants): inventory_component = Inventory(26) level_component = Level() equipment_component = Equipment() - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, inventory=inventory_component, level=level_component, equipment=equipment_component) entities = [player] + equippable_component = Equippable(EquipmentSlots.MAIN_HAND, power_bonus=2) -+ dagger = Entity(0, 0, '-', libtcod.sky, 'Dagger', equippable=equippable_component) ++ dagger = Entity(0, 0, '-', (0, 191, 255), 'Dagger', equippable=equippable_component) + player.inventory.add_item(dagger) + player.equipment.toggle_equip(dagger) @@ -742,7 +742,7 @@ def get_game_variables(constants): {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} {{ codetab >}} Now that our map object is ready to go, let's pass it to `render_all` so that we can draw it. We'll also pass the `colors` dictionary, because `render_all` will need to know what colors to draw the various parts of -the map. Note that the order in which you pass these arguments doesn't -matter, it just has to match in the function definition and when you -call it. +the map. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -- render_all(con, entities, screen_width, screen_height) -+ render_all(con, entities, game_map, screen_width, screen_height, colors) +- render_all(con, root_console, entities, screen_width, screen_height) ++ render_all(con, root_console, entities, game_map, screen_width, screen_height, colors) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod from components.equipment import Equipment from components.equippable import Equippable @@ -763,13 +763,13 @@ def get_game_variables(constants): inventory_component = Inventory(26) level_component = Level() equipment_component = Equipment() - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, inventory=inventory_component, level=level_component, equipment=equipment_component) entities = [player] equippable_component = Equippable(EquipmentSlots.MAIN_HAND, power_bonus=2) - dagger = Entity(0, 0, '-', libtcod.sky, 'Dagger', equippable=equippable_component) + dagger = Entity(0, 0, '-', (0, 191, 255), 'Dagger', equippable=equippable_component) player.inventory.add_item(dagger) player.equipment.toggle_equip(dagger) diff --git a/content/tutorials/tcod/2019/part-2.md b/content/tutorials/tcod/2019/part-2.md index fafea627..98821644 100644 --- a/content/tutorials/tcod/2019/part-2.md +++ b/content/tutorials/tcod/2019/part-2.md @@ -50,7 +50,7 @@ Let's put our fancy new class into action\! Modify the first part of {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod +from entity import Entity from input_handlers import handle_keys @@ -63,14 +63,14 @@ def main(): - player_x = int(screen_width / 2) - player_y = int(screen_height / 2) -+ player = Entity(int(screen_width / 2), int(screen_height / 2), '@', libtcod.white) -+ npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', libtcod.yellow) ++ player = Entity(int(screen_width / 2), int(screen_height / 2), '@', (255, 255, 255)) ++ npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', (255, 255, 0)) + entities = [npc, player] ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -{{ original-tab >}} {{ codetab >}} These colors will serve as our wall and ground outside the FOV, when we -get there (hence the 'dark' in the names). +get there (hence the 'dark' in the names). Colors are specified as +`(red, green, blue)` tuples, with each value between 0 and 255. Now let's initialize the game map itself. This can go anywhere before -the main loop; I put mine right below the console initialization. +the main loop; I put mine inside the `with` block, right below the +console initialization. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - con = libtcod.console_new(screen_width, screen_height) + root_console = tcod.console.Console(screen_width, screen_height, order='F') + con = tcod.console.Console(screen_width, screen_height, order='F') -+ game_map = GameMap(map_width, map_height) ++ game_map = GameMap(map_width, map_height) - key = libtcod.Key() + while True: {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod ++ player = Entity(int(screen_width / 2), int(screen_height / 2), '@', (255, 255, 255))import tcod from entity import Entity from input_handlers import handle_keys @@ -83,16 +83,17 @@ def main(): player_x = int(screen_width / 2) player_y = int(screen_height / 2) - player = Entity(int(screen_width / 2), int(screen_height / 2), '@', libtcod.white) - npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', libtcod.yellow) + player = Entity(int(screen_width / 2), int(screen_height / 2), '@', (255, 255, 255)) + npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', (255, 255, 0)) entities = [npc, player] ...{{ original-tab >}} {{ codetab >}} We're importing the `Entity` class into `engine.py`, and using it to -initialize the player and a new NPC. We store these two in a list, that -will eventually hold all our entities on the map. +initialize the player and a new NPC. Colors are specified as RGB tuples — +`(255, 255, 255)` for white and `(255, 255, 0)` for yellow. We store +these two in a list that will eventually hold all our entities on the map. Also modify the part where we handle movement so that the Entity class handles the actual movement. @@ -100,58 +101,45 @@ handles the actual movement. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - if move: - dx, dy = move -- player_x += dx -- player_x += dy -+ player.move(dx, dy) + if move: + dx, dy = move +- player_x += dx +- player_y += dy ++ player.move(dx, dy) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -if move: - dx, dy = move - player_x += dx - player_x += dy - player.move(dx, dy)+if move: + dx, dy = move + player_x += dx + player_y += dy + player.move(dx, dy){{ original-tab >}} {{ codetab >}} -Lastly, update the drawing functions to use the new player object: +Lastly, update the drawing function to use the new player object's +coordinates: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - while not libtcod.console_is_window_closed(): - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - - libtcod.console_set_default_foreground(con, libtcod.white) -- libtcod.console_put_char(con, player_x, player_y, '@', libtcod.BKGND_NONE) -+ libtcod.console_put_char(con, player.x, player.y, '@', libtcod.BKGND_NONE) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) - libtcod.console_flush() - -- libtcod.console_put_char(con, player_x, player_y, ' ', libtcod.BKGND_NONE) -+ libtcod.console_put_char(con, player.x, player.y, ' ', libtcod.BKGND_NONE) - - action = handle_keys(key) - + while True: +- con.print(player_x, player_y, '@', fg=(255, 255, 255)) ++ con.print(player.x, player.y, '@', fg=(255, 255, 255)) + con.blit(dest=root_console) + context.present(root_console) + con.clear() + ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -while not libtcod.console_is_window_closed(): - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - - libtcod.console_set_default_foreground(con, libtcod.white) - libtcod.console_put_char(con, player_x, player_y, '@', libtcod.BKGND_NONE) - libtcod.console_put_char(con, player.x, player.y, '@', libtcod.BKGND_NONE) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) - libtcod.console_flush() - - libtcod.console_put_char(con, player_x, player_y, ' ', libtcod.BKGND_NONE) - libtcod.console_put_char(con, player.x, player.y, ' ', libtcod.BKGND_NONE) - - action = handle_keys(key) -+while True: + con.print(player_x, player_y, '@', fg=(255, 255, 255)) + con.print(player.x, player.y, '@', fg=(255, 255, 255)) + con.blit(dest=root_console) + context.present(root_console) + con.clear() + ...{{ original-tab >}} {{ codetab >}} @@ -161,54 +149,38 @@ some functions to draw not only the player, but any entity currently in our entities list. Create a new file called `render_functions.py`. This will hold our -functions for drawing and clearing from the screen. Put the following -code in that file. +functions for drawing to the screen. Put the following code in that file. {{< highlight py3 >}} -import tcod as libtcod +import tcod -def render_all(con, entities, screen_width, screen_height): +def render_all(con, root_console, entities, screen_width, screen_height): # Draw all entities in the list for entity in entities: draw_entity(con, entity) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) - - -def clear_all(con, entities): - for entity in entities: - clear_entity(con, entity) + con.blit(dest=root_console) def draw_entity(con, entity): - libtcod.console_set_default_foreground(con, entity.color) - libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE) - - -def clear_entity(con, entity): - # erase the character that represents this object - libtcod.console_put_char(con, entity.x, entity.y, ' ', libtcod.BKGND_NONE) + con.print(entity.x, entity.y, entity.char, fg=entity.color) {{ highlight >}} Here's a quick breakdown of what these functions do: - The `render_all` function is what we'll call from our game loop to draw entities and, shortly, the map. For now, it takes the console - (con), a list of entities, and the screen width/height as - parameters, and calls the `draw_entity` function on each. It then - blits the changes to the screen. - - `draw_entity` is what does the actual drawing. The code should look - very similar to what's in our game loop right now, except it's using - the entity's variables (x, y, char, and color) to do the drawing. - This makes it flexible enough to, theoretically, draw any entity we - pass to it. - - `clear_all` is what we'll use to clear all the entities after - drawing them to the screen. It's just a loop that calls another - function. - - `clear_entity` just does what our previous line did, and clears the - entity from the screen (so that when it moves, it doesn't leave a - trail behind). + (`con`), the root console, a list of entities, and the screen + width/height as parameters. It calls `draw_entity` on each, then + blits the offscreen console to the root. + - `draw_entity` is what does the actual drawing. It calls `con.print()` + with the entity's position, character, and color. This makes it + flexible enough to draw any entity we pass to it. + +Note that we no longer need a separate `clear_entity` or `clear_all` +function — calling `con.clear()` in the game loop (which we already do) +erases the entire console each frame, which is simpler and more thorough. Now that we've gotten a few functions to assist drawing the entities, let's put them to use. Make the following modifications to the section @@ -217,61 +189,45 @@ where we drew the player (in `engine.py`). {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} + while True: +- con.print(player.x, player.y, '@', fg=(255, 255, 255)) +- con.blit(dest=root_console) ++ render_all(con, root_console, entities, screen_width, screen_height) + context.present(root_console) + con.clear() ... - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - -- libtcod.console_set_default_foreground(con, libtcod.white) -- libtcod.console_put_char(con, player.x, player.y, '@', libtcod.BKGND_NONE) -- libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) -+ render_all(con, entities, screen_width, screen_height) - - libtcod.console_flush() - -- libtcod.console_put_char(con, player.x, player.y, ' ', libtcod.BKGND_NONE) -+ clear_all(con, entities) - - action = handle_keys(key) - ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -... - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - - libtcod.console_set_default_foreground(con, libtcod.white) - libtcod.console_put_char(con, player.x, player.y, '@', libtcod.BKGND_NONE) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) - render_all(con, entities, screen_width, screen_height) - - libtcod.console_flush() - - libtcod.console_put_char(con, player.x, player.y, ' ', libtcod.BKGND_NONE) - clear_all(con, entities) - - action = handle_keys(key) - ...+while True: + con.print(player.x, player.y, '@', fg=(255, 255, 255)) + con.blit(dest=root_console) + render_all(con, root_console, entities, screen_width, screen_height) + context.present(root_console) + con.clear() + ...{{ original-tab >}} {{ codetab >}} -Don't forget to import `render_all` and `clear_all` at the top of your -file. Your imports section should now look something like this: +Don't forget to import `render_all` at the top of your file. Your +imports section should now look something like this: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod from entity import Entity from input_handlers import handle_keys -+from render_functions import clear_all, render_all ++from render_functions import render_all {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -372,7 +328,7 @@ initialization, and two, because I prefer keeping `__init__` functions as simple as possible. Go back to `engine.py`, where we'll make a few changes so that our map -get initialized and then drawn to the screen. +gets initialized and then drawn to the screen. Firstly, we need to define what colors to draw for blocked and non-blocked tiles. Let's set up a dictionary that holds the colors we'll @@ -385,11 +341,11 @@ be using for now (it will expand as this tutorial goes on). map_height = 45 + colors = { -+ 'dark_wall': libtcod.Color(0, 0, 100), -+ 'dark_ground': libtcod.Color(50, 50, 150) ++ 'dark_wall': (0, 0, 100), ++ 'dark_ground': (50, 50, 150) + } - player = Entity(int(screen_width / 2), int(screen_height / 2), '@', libtcod.white) + player = Entity(int(screen_width / 2), int(screen_height / 2), '@', (255, 255, 255)) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} @@ -397,36 +353,40 @@ be using for now (it will expand as this tutorial goes on). map_height = 45 colors = { - 'dark_wall': libtcod.Color(0, 0, 100), - 'dark_ground': libtcod.Color(50, 50, 150) + 'dark_wall': (0, 0, 100), + 'dark_ground': (50, 50, 150) } - player = Entity(int(screen_width / 2), int(screen_height / 2), '@', libtcod.white)import tcod from entity import Entity from input_handlers import handle_keys -from render_functions import clear_all, render_all+from render_functions import render_allcon = libtcod.console_new(screen_width, screen_height) +{{ original-tab >}} {{ codetab >}} @@ -439,34 +399,32 @@ in the engine. from entity import Entity from input_handlers import handle_keys +from map_objects.game_map import GameMap -from render_functions import clear_all, render_all +from render_functions import render_all {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}root_console = tcod.console.Console(screen_width, screen_height, order='F') + con = tcod.console.Console(screen_width, screen_height, order='F') - game_map = GameMap(map_width, map_height) + game_map = GameMap(map_width, map_height) - key = libtcod.Key()+ while True:from entity import Entity from input_handlers import handle_keys from map_objects.game_map import GameMap -from render_functions import clear_all, render_all+from render_functions import render_allrender_all(con, entities, screen_width, screen_height) - render_all(con, entities, game_map, screen_width, screen_height, colors)+render_all(con, root_console, entities, screen_width, screen_height) + render_all(con, root_console, entities, game_map, screen_width, screen_height, colors){{ original-tab >}} {{ codetab >}} @@ -475,49 +433,50 @@ Open up `render_functions.py` and modify `render_all` like this: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --def render_all(con, entities, screen_width, screen_height): -+def render_all(con, entities, game_map, screen_width, screen_height, colors): +-def render_all(con, root_console, entities, screen_width, screen_height): ++def render_all(con, root_console, entities, game_map, screen_width, screen_height, colors): + # Draw all the tiles in the game map + for y in range(game_map.height): + for x in range(game_map.width): + wall = game_map.tiles[x][y].block_sight + + if wall: -+ libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET) ++ con.bg[x, y] = colors.get('dark_wall') + else: -+ libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET) ++ con.bg[x, y] = colors.get('dark_ground') + # Draw all entities in the list for entity in entities: draw_entity(con, entity) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) + con.blit(dest=root_console) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, entities, screen_width, screen_height): -def render_all(con, entities, game_map, screen_width, screen_height, colors): ++ con.blit(dest=root_console)def render_all(con, root_console, entities, screen_width, screen_height): +def render_all(con, root_console, entities, game_map, screen_width, screen_height, colors): # Draw all the tiles in the game map for y in range(game_map.height): for x in range(game_map.width): wall = game_map.tiles[x][y].block_sight if wall: - libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('dark_wall') else: - libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET) - + con.bg[x, y] = colors.get('dark_ground') +# Draw all entities in the list for entity in entities: draw_entity(con, entity) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0)if move: - dx, dy = move +{{ original-tab >}} {{ codetab >}} diff --git a/content/tutorials/tcod/2019/part-4.md b/content/tutorials/tcod/2019/part-4.md index e74984e4..25852f34 100644 --- a/content/tutorials/tcod/2019/part-4.md +++ b/content/tutorials/tcod/2019/part-4.md @@ -12,7 +12,7 @@ beginning? Most roguelikes (not all\!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the "Field of View" for our adventurer, and fortunately, -libtcod makes that easy\! +tcod makes that easy\! We'll need to define a few variables before we get started. Add these in the same section as our screen and map variables: @@ -42,7 +42,7 @@ the same section as our screen and map variables: {{ original-tab >}} {{ codetab >}} -'0' is just the default algorithm that libtcod uses; it has more, and I +'0' is just the default algorithm that tcod uses; it has more, and I encourage you to experiment with them later. `fov_light_walls` just tells us whether or not to 'light up' the walls we see; you can change it if you don't like the way it looks. `fov_radius` is somewhat obvious, @@ -55,10 +55,10 @@ outside what we can see. {{< highlight py3 >}} colors = { - 'dark_wall': libtcod.Color(0, 0, 100), - 'dark_ground': libtcod.Color(50, 50, 150), - 'light_wall': libtcod.Color(130, 110, 50), - 'light_ground': libtcod.Color(200, 180, 50) + 'dark_wall': (0, 0, 100), + 'dark_ground': (50, 50, 150), + 'light_wall': (130, 110, 50), + 'light_ground': (200, 180, 50) } {{ highlight >}} @@ -69,8 +69,8 @@ If you don't like these colors, feel free to change them to your liking. The thing about field of view is that it doesn't need to be computed every turn. In fact, it would be quite a waste to do so\! We really only -need change it when the player moves. Attacking, using an item, or just -standing still for a turn doesn't alter FOV. We can handle this by +need to change it when the player moves. Attacking, using an item, or +just standing still for a turn doesn't alter FOV. We can handle this by having a boolean variable, which we'll call `fov_recompute`, which tells us if we need to recompute. We can define it somewhere above our game loop (I put mine right after the map initialization). @@ -81,7 +81,7 @@ loop (I put mine right after the map initialization). + fov_recompute = True - key = libtcod.Key() + while True: ... {{ highlight >}} {{ diff-tab >}} @@ -91,7 +91,7 @@ loop (I put mine right after the map initialization). fov_recompute = True - key = libtcod.Key() + while True: ...if move: + dx, dy = move - if not game_map.is_blocked(player.x + dx, player.y + dy): - player.move(dx, dy) - player.move(dx, dy)+ if not game_map.is_blocked(player.x + dx, player.y + dy): + player.move(dx, dy) + player.move(dx, dy)def initialize_fov(game_map): - ... - -def recompute_fov(fov_map, x, y, radius, light_walls=True, algorithm=0): - libtcod.map_compute_fov(fov_map, x, y, radius, light_walls, algorithm)-{{ original-tab >}} -{{ codetab >}} - -So when we call the function, we have to give fov\_map, x, y, and -radius, but we don't necessarily have to pass in light\_walls or -algorithm. In my `engine.py` file, I'll pass them in anyway, but you -don't have to if you don't want to (you can also change the defaults I -gave above to whatever you prefer). - -Whatever you decide, put your fov recomputation in `engine.py` like so: - -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - ... - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - -+ if fov_recompute: -+ recompute_fov(fov_map, player.x, player.y, fov_radius, fov_light_walls, fov_algorithm) - - render_all(con, entities, game_map, screen_width, screen_height, colors) +{{< codetab >}} {{< diff-tab >}} +{{< highlight diff >}} + while True: ++ if fov_recompute: ++ recompute_fov(fov_map, game_map, player.x, player.y, fov_radius, fov_light_walls, fov_algorithm) ++ + render_all(con, root_console, entities, game_map, screen_width, screen_height, colors) ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -... - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - - if fov_recompute: - recompute_fov(fov_map, player.x, player.y, fov_radius, fov_light_walls, fov_algorithm) - - render_all(con, entities, game_map, screen_width, screen_height, colors) +{{ original-tab >}} {{ codetab >}} @@ -413,7 +394,7 @@ Run the project again, and you won't see the NPC unless it's in your field of view. Now for the map. In traditional roguelikes, your character can only see -whats inside its field of view, but it will "remember" areas that were +what's inside its field of view, but it will "remember" areas that were explored previously. We can accomplish this effect by adding a variable called `explored` to our `Tile` class. Modify the `__init__` function in `Tile` to include this new variable: @@ -440,43 +421,43 @@ field of view if we've explored them previously. Also, any tiles that {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - visible = libtcod.map_is_in_fov(fov_map, x, y) + visible = fov_map[x, y] wall = game_map.tiles[x][y].block_sight if visible: if wall: - libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('light_wall') else: - libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('light_ground') + game_map.tiles[x][y].explored = True - else: + elif game_map.tiles[x][y].explored: if wall: - libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('dark_wall') else: - libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('dark_ground') ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}while True: + if fov_recompute: + recompute_fov(fov_map, game_map, player.x, player.y, fov_radius, fov_light_walls, fov_algorithm) + + render_all(con, root_console, entities, game_map, screen_width, screen_height, colors) ...{{ original-tab >}} {{ codetab >}} @@ -267,11 +251,11 @@ to recalculate, but it won't if we do something else. With our field of view calculated, we need to actually *display* it (if you run the code now, you won't notice any visible change). Open up `render_functions.py` and modify the `render_all` function like - this: +this: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def render_all(con, entities, game_map, screen_width, screen_height, colors): -+def render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +-def render_all(con, root_console, entities, game_map, screen_width, screen_height, colors): ++def render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): - for y in range(game_map.height): + if fov_recompute: - for x in range(game_map.width): @@ -279,68 +263,68 @@ def render_all(con, entities, game_map, screen_width, screen_height, colors): - wall = game_map.tiles[x][y].block_sight - - if wall: -- libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET) +- con.bg[x, y] = colors.get('dark_wall') - else: -- libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET) +- con.bg[x, y] = colors.get('dark_ground') + for x in range(game_map.width): -+ visible = libtcod.map_is_in_fov(fov_map, x, y) ++ visible = fov_map[x, y] + wall = game_map.tiles[x][y].block_sight + if visible: + if wall: -+ libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET) ++ con.bg[x, y] = colors.get('light_wall') + else: -+ libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET) ++ con.bg[x, y] = colors.get('light_ground') + else: + if wall: -+ libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET) ++ con.bg[x, y] = colors.get('dark_wall') + else: -+ libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET) ++ con.bg[x, y] = colors.get('dark_ground') # Draw all entities in the list for entity in entities: draw_entity(con, entity) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) + con.blit(dest=root_console) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, entities, game_map, screen_width, screen_height, colors): -def render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +{{ original-tab >}} {{ codetab >}} -*\* Note: Blue denotes lines that are exactly the same as before, expect +*\* Note: Blue denotes lines that are exactly the same as before, except for their indentation. The if statements for `fov_recompute` and `visible` force certain lines to be indented farther than they were before. Remember, this is Python, indentation matters\!* Now our `render_all` function will display tiles differently, depending -on if they're in our field of view or not. If a tile falls in the -`fov_map`, we draw it with the 'light' colors, and if not, we draw the -'dark' version. +on if they're in our field of view or not. If a tile's position is +`True` in `fov_map`, we draw it with the 'light' colors, and if not, we +draw the 'dark' version. The definition of `render_all` has changed, so be sure to update it in `engine.py`. While we're at it, let's set `fov_recompute` to `False` @@ -348,16 +332,16 @@ after we call `render_all`. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... -- render_all(con, entities, game_map, screen_width, screen_height, colors) -+ render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) +- render_all(con, root_console, entities, game_map, screen_width, screen_height, colors) ++ render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) + + fov_recompute = False {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}def render_all(con, root_console, entities, game_map, screen_width, screen_height, colors): +def render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): if fov_recompute: for y in range(game_map.height): for x in range(game_map.width): - visible = libtcod.map_is_in_fov(fov_map, x, y) + visible = fov_map[x, y] wall = game_map.tiles[x][y].block_sight if visible: if wall: - libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('light_wall') else: - libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('light_ground') else: if wall: - libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('dark_wall') else: - libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('dark_ground') # Draw all entities in the list for entity in entities: draw_entity(con, entity) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0)+ con.blit(dest=root_console)... - render_all(con, entities, game_map, screen_width, screen_height, colors) - render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) + render_all(con, root_console, entities, game_map, screen_width, screen_height, colors) + render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) fov_recompute = False{{ original-tab >}} @@ -373,21 +357,18 @@ Let's start with our NPC. We should just be able to modify our solve our problem. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def draw_entity(con, entity): +-def draw_entity(con, entity): +def draw_entity(con, entity, fov_map): -- libtcod.console_set_default_foreground(con, entity.color) -- libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE) -+ if libtcod.map_is_in_fov(fov_map, entity.x, entity.y): -+ libtcod.console_set_default_foreground(con, entity.color) -+ libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE) +- con.print(entity.x, entity.y, entity.char, fg=entity.color) ++ if fov_map[entity.x, entity.y]: ++ con.print(entity.x, entity.y, entity.char, fg=entity.color) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}def draw_entity(con, entity): def draw_entity(con, entity, fov_map): - if libtcod.map_is_in_fov(fov_map, entity.x, entity.y): - libtcod.console_set_default_foreground(con, entity.color) - libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE)+ if fov_map[entity.x, entity.y]: + con.print(entity.x, entity.y, entity.char, fg=entity.color)... - visible = libtcod.map_is_in_fov(fov_map, x, y) + visible = fov_map[x, y] wall = game_map.tiles[x][y].block_sight if visible: if wall: - libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('light_wall') else: - libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('light_ground') game_map.tiles[x][y].explored = True else: elif game_map.tiles[x][y].explored: if wall: - libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('dark_wall') else: - libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET) + con.bg[x, y] = colors.get('dark_ground') ...{{ original-tab >}} {{ codetab >}} diff --git a/content/tutorials/tcod/2019/part-5.md b/content/tutorials/tcod/2019/part-5.md index f93ee313..311f16d2 100644 --- a/content/tutorials/tcod/2019/part-5.md +++ b/content/tutorials/tcod/2019/part-5.md @@ -11,7 +11,9 @@ attacked (the actual attacking part we'll save for next time). To start, we'll need a function to place the enemies in the dungeon; let's call it `place_entities` and put it in the `GameMap` class. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} def create_v_tunnel(self, y1, y2, x): ... @@ -26,9 +28,9 @@ we'll need a function to place the enemies in the dungeon; let's call it + + if not any([entity for entity in entities if entity.x == x and entity.y == y]): + if randint(0, 100) < 80: -+ monster = Entity(x, y, 'o', libtcod.desaturated_green) ++ monster = Entity(x, y, 'o', (63, 127, 63)) + else: -+ monster = Entity(x, y, 'T', libtcod.darker_green) ++ monster = Entity(x, y, 'T', (0, 100, 0)) + + entities.append(monster) @@ -51,9 +53,9 @@ we'll need a function to place the enemies in the dungeon; let's call it if not any([entity for entity in entities if entity.x == x and entity.y == y]): if randint(0, 100) < 80: - monster = Entity(x, y, 'o', libtcod.desaturated_green) + monster = Entity(x, y, 'o', (63, 127, 63)) else: - monster = Entity(x, y, 'T', libtcod.darker_green) + monster = Entity(x, y, 'T', (0, 100, 0)) entities.append(monster) @@ -65,13 +67,16 @@ we'll need a function to place the enemies in the dungeon; let's call it In this function, we're choosing a random amount of enemies to place, between 0 and the maximum we specify. Then, we take a random x and y, and, if no other monster is currently at that location, we place a -monster there. There's an 80% chance of it being an Orc, and a 20% -chance of it being a Troll. +monster there. There's an 80% chance of it being an Orc (color +`(63, 127, 63)` — a muted green) and a 20% chance of it being a Troll +(color `(0, 100, 0)` — a darker green). Feel free to adjust these colors. -We'll need to import both `libtcod` and the `Entity` class. +We'll need to import both `tcod` and the `Entity` class. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -+import tcod as libtcod +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} ++import tcod from random import randint +from entity import Entity @@ -80,7 +85,7 @@ from map_objects.tile import Tile {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -225,7 +228,9 @@ Let's modify the `Entity` class to include the "blocks" variable. While we're modifying this class, we should also pass in a "name" for the Entity, which will be useful a little later. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} class Entity: - def __init__(self, x, y, char, color): + def __init__(self, x, y, char, color, name, blocks=False): @@ -262,35 +267,39 @@ it will be False by default. Go back to `game_map.py` and modify the `place_entities` method, where we declare our monsters. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} if randint(0, 100) < 80: -- monster = Entity(x, y, 'o', libtcod.desaturated_green) -+ monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True) +- monster = Entity(x, y, 'o', (63, 127, 63)) ++ monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True) else: -- monster = Entity(x, y, 'T', libtcod.darker_green) -+ monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True) +- monster = Entity(x, y, 'T', (0, 100, 0)) ++ monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}import tcod from random import randint from entity import Entity @@ -92,7 +97,9 @@ from map_objects.tile import TileNow let's modify our `make_map` function to include the `place_entities` function. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} ... self.create_h_tunnel(prev_x, new_x, new_y) @@ -118,7 +125,9 @@ variables, we should modify our `make_map` function definition to include them. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} - def make_map(self, max_rooms, room_min_size, room_max_size, map_width, map_height, player): + def make_map(self, max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, + max_monsters_per_room): @@ -133,40 +142,38 @@ them. We're all set up here; now we have to modify `engine.py` to match this new `make_map` function. Also, we'll need to create the -`max_room_per_monsters` variable before calling the function. Finally, +`max_monsters_per_room` variable before calling the function. Finally, we'll change our `entities` list to include only the player at first, and we'll completely remove our dummy NPC from before. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} ... fov_radius = 10 + max_monsters_per_room = 3 colors = { - 'dark_wall': libtcod.Color(0, 0, 100), - 'dark_ground': libtcod.Color(50, 50, 150), - 'light_wall': libtcod.Color(130, 110, 50), - 'light_ground': libtcod.Color(200, 180, 50) + 'dark_wall': (0, 0, 100), + 'dark_ground': (50, 50, 150), + 'light_wall': (130, 110, 50), + 'light_ground': (200, 180, 50) } -- player = Entity(int(screen_width / 2), int(screen_height / 2), '@', libtcod.white) -- npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', libtcod.yellow) +- player = Entity(int(screen_width / 2), int(screen_height / 2), '@', (255, 255, 255)) +- npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', (255, 255, 0)) - entities = [npc, player] -+ player = Entity(0, 0, '@', libtcod.white) ++ player = Entity(0, 0, '@', (255, 255, 255)) + entities = [player] - libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) - - libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False) - - con = libtcod.console_new(screen_width, screen_height) + ... - game_map = GameMap(map_width, map_height) -- game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player) -+ game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, max_monsters_per_room) + game_map = GameMap(map_width, map_height) +- game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player) ++ game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, max_monsters_per_room) - fov_recompute = True + fov_recompute = True ... {{ highlight >}} {{ diff-tab >}} @@ -177,29 +184,25 @@ and we'll completely remove our dummy NPC from before. max_monsters_per_room = 3 colors = { - 'dark_wall': libtcod.Color(0, 0, 100), - 'dark_ground': libtcod.Color(50, 50, 150), - 'light_wall': libtcod.Color(130, 110, 50), - 'light_ground': libtcod.Color(200, 180, 50) + 'dark_wall': (0, 0, 100), + 'dark_ground': (50, 50, 150), + 'light_wall': (130, 110, 50), + 'light_ground': (200, 180, 50) } - player = Entity(int(screen_width / 2), int(screen_height / 2), '@', libtcod.white) - npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', libtcod.yellow) + player = Entity(int(screen_width / 2), int(screen_height / 2), '@', (255, 255, 255)) + npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), '@', (255, 255, 0)) entities = [npc, player] - player = Entity(0, 0, '@', libtcod.white) + player = Entity(0, 0, '@', (255, 255, 255)) entities = [player] - libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) - - libtcod.console_init_root(screen_width, screen_height, 'libtcod tutorial revised', False) - - con = libtcod.console_new(screen_width, screen_height) + ... - game_map = GameMap(map_width, map_height) - game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player) - game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, max_monsters_per_room) + game_map = GameMap(map_width, map_height) + game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player) + game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player, entities, max_monsters_per_room) - fov_recompute = True + fov_recompute = True ...if randint(0, 100) < 80: - monster = Entity(x, y, 'o', libtcod.desaturated_green) - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True) + monster = Entity(x, y, 'o', (63, 127, 63)) + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True) else: - monster = Entity(x, y, 'T', libtcod.darker_green) - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True)+ monster = Entity(x, y, 'T', (0, 100, 0)) + monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True)player = Entity(0, 0, '@', libtcod.white) - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True)+player = Entity(0, 0, '@', (255, 255, 255)) + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True){{ original-tab >}} {{ codetab >}} @@ -305,7 +314,9 @@ class. Add the function to `entity.py` like this: -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} class Entity: ... @@ -342,52 +353,56 @@ move into the same tile. With that in place, let's return to our movement function. Modify the code that moves the player in `engine.py` like this: -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - if move: - dx, dy = move +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} + if move: + dx, dy = move -- player.move(dx, dy) +- player.move(dx, dy) - -- fov_recompute = True +- fov_recompute = True -+ destination_x = player.x + dx -+ destination_y = player.y + dy ++ destination_x = player.x + dx ++ destination_y = player.y + dy -- if not game_map.is_blocked(player.x + dx, player.y + dy): -+ if not game_map.is_blocked(destination_x, destination_y): -+ target = get_blocking_entities_at_location(entities, destination_x, destination_y) +- if not game_map.is_blocked(player.x + dx, player.y + dy): ++ if not game_map.is_blocked(destination_x, destination_y): ++ target = get_blocking_entities_at_location(entities, destination_x, destination_y) + -+ if target: -+ print('You kick the ' + target.name + ' in the shins, much to its annoyance!') -+ else: -+ player.move(dx, dy) ++ if target: ++ print('You kick the ' + target.name + ' in the shins, much to its annoyance!') ++ else: ++ player.move(dx, dy) + -+ fov_recompute = True ++ fov_recompute = True {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -if move: - dx, dy = move - destination_x = player.x + dx - destination_y = player.y + dy +if move: + dx, dy = move + destination_x = player.x + dx + destination_y = player.y + dy - if not game_map.is_blocked(player.x + dx, player.y + dy): - if not game_map.is_blocked(destination_x, destination_y): - target = get_blocking_entities_at_location(entities, destination_x, destination_y) + if not game_map.is_blocked(player.x + dx, player.y + dy): + if not game_map.is_blocked(destination_x, destination_y): + target = get_blocking_entities_at_location(entities, destination_x, destination_y) - if target: - print('You kick the ' + target.name + ' in the shins, much to its annoyance!') - else: - player.move(dx, dy) + if target: + print('You kick the ' + target.name + ' in the shins, much to its annoyance!') + else: + player.move(dx, dy) - fov_recompute = True + fov_recompute = True{{ original-tab >}} {{ codetab >}} Also be sure to import the function `get_blocking_entities_at_location` at the top of `engine.py`. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} - from entity import Entity + from entity import Entity, get_blocking_entities_at_location {{ highlight >}} @@ -435,7 +450,9 @@ able to.* Let's put this new `GameStates` enum into action. Start by importing it at the top. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} ... from fov_functions import initialize_fov, recompute_fov +from game_states import GameStates @@ -453,25 +470,27 @@ from input_handlers import handle_keys {{ codetab >}} Then, create a variable called `game_state`, which we'll set initially -to the player's turn. +to the player's turn. Put this right before the game loop. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} ... - mouse = libtcod.Mouse() + fov_map = initialize_fov(game_map) + game_state = GameStates.PLAYERS_TURN - while not libtcod.console_is_window_closed(): + while True: ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}... - mouse = libtcod.Mouse() + fov_map = initialize_fov(game_map) game_state = GameStates.PLAYERS_TURN - while not libtcod.console_is_window_closed(): + while True: ...{{ original-tab >}} {{ codetab >}} @@ -481,44 +500,46 @@ player's movement. The player can only move on the players turn, so let's modify our `if move:` section to handle this. After the player successfully moves, we'll set the state to `ENEMY_TURN`. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -- if move: -+ if move and game_state == GameStates.PLAYERS_TURN: - dx, dy = move - destination_x = player.x + dx - destination_y = player.y + dy +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} +- if move: ++ if move and game_state == GameStates.PLAYERS_TURN: + dx, dy = move + destination_x = player.x + dx + destination_y = player.y + dy - if not game_map.is_blocked(destination_x, destination_y): - target = get_blocking_entities_at_location(entities, destination_x, destination_y) + if not game_map.is_blocked(destination_x, destination_y): + target = get_blocking_entities_at_location(entities, destination_x, destination_y) - if target: - print('You kick the ' + target.name + ' in the shins, much to its annoyance!') - else: - player.move(dx, dy) + if target: + print('You kick the ' + target.name + ' in the shins, much to its annoyance!') + else: + player.move(dx, dy) - fov_recompute = True + fov_recompute = True -+ game_state = GameStates.ENEMY_TURN ++ game_state = GameStates.ENEMY_TURN {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -if move: - if move and game_state == GameStates.PLAYERS_TURN: - dx, dy = move - destination_x = player.x + dx - destination_y = player.y + dy +{{ original-tab >}} {{ codetab >}} @@ -529,10 +550,15 @@ Note that you *can* exit the game and make it full screen, because we're not stopping the player from doing those things when it isn't the player's turn. -{{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} - ... - if fullscreen: - libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) +Add the enemy turn block at the `while True` level, after the event +loop: + +{{< codetab >}} +{{< diff-tab >}} +{{< highlight diff >}} + ... + if fullscreen: + context.sdl_window.fullscreen = not context.sdl_window.fullscreen + if game_state == GameStates.ENEMY_TURN: + for entity in entities: @@ -543,9 +569,9 @@ player's turn. {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -if move: + if move and game_state == GameStates.PLAYERS_TURN: + dx, dy = move + destination_x = player.x + dx + destination_y = player.y + dy - if not game_map.is_blocked(destination_x, destination_y): - target = get_blocking_entities_at_location(entities, destination_x, destination_y) + if not game_map.is_blocked(destination_x, destination_y): + target = get_blocking_entities_at_location(entities, destination_x, destination_y) - if target: - print('You kick the ' + target.name + ' in the shins, much to its annoyance!') - else: - player.move(dx, dy) + if target: + print('You kick the ' + target.name + ' in the shins, much to its annoyance!') + else: + player.move(dx, dy) - fov_recompute = True + fov_recompute = True - game_state = GameStates.ENEMY_TURN+ game_state = GameStates.ENEMY_TURN... - if fullscreen: - libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) +... + if fullscreen: + context.sdl_window.fullscreen = not context.sdl_window.fullscreen if game_state == GameStates.ENEMY_TURN: for entity in entities: diff --git a/content/tutorials/tcod/2019/part-6.md b/content/tutorials/tcod/2019/part-6.md index 285d1fdd..579ed9ce 100644 --- a/content/tutorials/tcod/2019/part-6.md +++ b/content/tutorials/tcod/2019/part-6.md @@ -117,14 +117,14 @@ directly), but it does need the `Fighter` component. First, import the `Fighter` component into `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod +from components.fighter import Fighter from entity import Entity, get_blocking_entities_at_location {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -{{ original-tab >}} {{ codetab >}} -For this to work, we'll need to import `libtcod` into `entity.py`: +For this to work, we'll need to import `numpy` and `tcod` into `entity.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -+import tcod as libtcod ++import numpy as np ++import tcod import math ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -193,7 +193,6 @@ components for them. Remember to import the needed classes at the top. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod from random import randint +from components.ai import BasicMonster @@ -206,8 +205,7 @@ from map_objects.tile import Tile {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod from components.fighter import Fighter from entity import Entity, get_blocking_entities_at_location@@ -135,8 +135,8 @@ Then, create the component and add it to the player Entity. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} + fighter_component = Fighter(hp=30, defense=2, power=5) -- player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True) -+ player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, fighter=fighter_component) +- player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True) ++ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, fighter=fighter_component) entities = [player] ... {{ highlight >}} @@ -144,8 +144,8 @@ Then, create the component and add it to the player Entity. {{< original-tab >}}fighter_component = Fighter(hp=30, defense=2, power=5) - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True) - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, fighter=fighter_component) + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True) + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, fighter=fighter_component) entities = [player] ...{{ original-tab >}} @@ -159,15 +159,15 @@ components for them. + fighter_component = Fighter(hp=10, defense=0, power=3) + ai_component = BasicMonster() -- monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True) -+ monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, +- monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True) ++ monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, + fighter=fighter_component, ai=ai_component) else: + fighter_component = Fighter(hp=16, defense=1, power=4) + ai_component = BasicMonster() -- monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True) -+ monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, +- monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True) ++ monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component, + ai=ai_component) {{ highlight >}} {{ diff-tab >}} @@ -177,15 +177,15 @@ components for them. fighter_component = Fighter(hp=10, defense=0, power=3) ai_component = BasicMonster() - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True) - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True) + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, fighter=fighter_component, ai=ai_component) else: fighter_component = Fighter(hp=16, defense=1, power=4) ai_component = BasicMonster() - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True) - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, + monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True) + monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component, ai=ai_component)import tcod as libtcod -from random import randint +from random import randint from components.ai import BasicMonster from components.fighter import Fighter @@ -348,15 +346,12 @@ Now let's replace our placeholder `take_turn` function with one that will actually move the Entity. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod - - class BasicMonster: - def take_turn(self): + def take_turn(self, target, fov_map, game_map, entities): - print('The ' + self.owner.name + ' wonders when it will get to move.') + monster = self.owner -+ if libtcod.map_is_in_fov(fov_map, monster.x, monster.y): ++ if fov_map[monster.x, monster.y]: + + if monster.distance_to(target) >= 2: + monster.move_towards(target.x, target.y, game_map, entities) @@ -366,15 +361,12 @@ class BasicMonster: {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod - - -class BasicMonster: +class BasicMonster: def take_turn(self): def take_turn(self, target, fov_map, game_map, entities): print('The ' + self.owner.name + ' wonders when it will get to move.') monster = self.owner - if libtcod.map_is_in_fov(fov_map, monster.x, monster.y): + if fov_map[monster.x, monster.y]: if monster.distance_to(target) >= 2: monster.move_towards(target.x, target.y, game_map, entities) @@ -413,124 +405,106 @@ to allow us to move diagonally. Modify the movement part of that function like so: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_keys(key): -+ key_char = chr(key.c) - -- if key.vk == libtcod.KEY_UP: -+ if key.vk == libtcod.KEY_UP or key_char == 'k': - return {'move': (0, -1)} -- elif key.vk == libtcod.KEY_DOWN: -+ elif key.vk == libtcod.KEY_DOWN or key_char == 'j': - return {'move': (0, 1)} -- elif key.vk == libtcod.KEY_LEFT: -+ elif key.vk == libtcod.KEY_LEFT or key_char == 'h': - return {'move': (-1, 0)} -- elif key.vk == libtcod.KEY_RIGHT: -+ elif key.vk == libtcod.KEY_RIGHT or key_char == 'l': - return {'move': (1, 0)} -+ elif key_char == 'y': -+ return {'move': (-1, -1)} -+ elif key_char == 'u': -+ return {'move': (1, -1)} -+ elif key_char == 'b': -+ return {'move': (-1, 1)} -+ elif key_char == 'n': -+ return {'move': (1, 1)} - - ... +def handle_keys(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym + +- if key == tcod.event.KeySym.UP: ++ if key == tcod.event.KeySym.UP or key == tcod.event.KeySym.k: + return {'move': (0, -1)} +- elif key == tcod.event.KeySym.DOWN: ++ elif key == tcod.event.KeySym.DOWN or key == tcod.event.KeySym.j: + return {'move': (0, 1)} +- elif key == tcod.event.KeySym.LEFT: ++ elif key == tcod.event.KeySym.LEFT or key == tcod.event.KeySym.h: + return {'move': (-1, 0)} +- elif key == tcod.event.KeySym.RIGHT: ++ elif key == tcod.event.KeySym.RIGHT or key == tcod.event.KeySym.l: + return {'move': (1, 0)} ++ elif key == tcod.event.KeySym.y: ++ return {'move': (-1, -1)} ++ elif key == tcod.event.KeySym.u: ++ return {'move': (1, -1)} ++ elif key == tcod.event.KeySym.b: ++ return {'move': (-1, 1)} ++ elif key == tcod.event.KeySym.n: ++ return {'move': (1, 1)} + + elif key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: + return {'fullscreen': True} + elif key == tcod.event.KeySym.ESCAPE: + return {'exit': True} + return {} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -+ self.move_towards(target.x, target.y, game_map, entities)def handle_keys(key): - key_char = chr(key.c) - - if key.vk == libtcod.KEY_UP or key_char == 'k': - return {'move': (0, -1)} - elif key.vk == libtcod.KEY_DOWN or key_char == 'j': - return {'move': (0, 1)} - elif key.vk == libtcod.KEY_LEFT or key_char == 'h': - return {'move': (-1, 0)} - elif key.vk == libtcod.KEY_RIGHT or key_char == 'l': - return {'move': (1, 0)} - elif key_char == 'y': - return {'move': (-1, -1)} - elif key_char == 'u': - return {'move': (1, -1)} - elif key_char == 'b': - return {'move': (-1, 1)} - elif key_char == 'n': - return {'move': (1, 1)} - - ...+def handle_keys(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym + + if key == tcod.event.KeySym.UP or key == tcod.event.KeySym.k: + return {'move': (0, -1)} + elif key == tcod.event.KeySym.DOWN or key == tcod.event.KeySym.j: + return {'move': (0, 1)} + elif key == tcod.event.KeySym.LEFT or key == tcod.event.KeySym.h: + return {'move': (-1, 0)} + elif key == tcod.event.KeySym.RIGHT or key == tcod.event.KeySym.l: + return {'move': (1, 0)} + elif key == tcod.event.KeySym.y: + return {'move': (-1, -1)} + elif key == tcod.event.KeySym.u: + return {'move': (1, -1)} + elif key == tcod.event.KeySym.b: + return {'move': (-1, 1)} + elif key == tcod.event.KeySym.n: + return {'move': (1, 1)} + + elif key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: + return {'fullscreen': True} + elif key == tcod.event.KeySym.ESCAPE: + return {'exit': True} + return {}{{ original-tab >}} {{ codetab >}} -The first line is just getting the 'character' that we pressed on the -keyboard. This will be handy in other spots as well, when we check for -inventory and pickup commands. - For diagonal movement, we've implemented the "vim keys" for movement, -while also retaining the arrow keys for cardinal directions. Vim keys -allow you to move diagonally without the help of a numpad. A lot of -older roguelikes do 8 directions through the numpad, but personally, I -play all my roguelikes on a laptop, which doesn't have one, so the Vim -keys are useful. +while also retaining the arrow keys for cardinal directions. We use +`tcod.event.KeySym` to compare key symbols directly, so `KeySym.k`, +`KeySym.j`, `KeySym.h`, `KeySym.l` etc. map to the vim movement keys. +A lot of older roguelikes do 8 directions through the numpad, but +personally, I play all my roguelikes on a laptop, which doesn't have +one, so the Vim keys are useful. Getting the enemies to move in eight directions is going to be a bit more complicated. For that, we'll want to use a pathfinding algorithm -known as A-star. I'm simply going to be copying the code from the -[Roguebasin -extra](http://www.roguebasin.com/index.php?title=Complete_Roguelike_Tutorial,_using_Python%2Blibtcod,_extras#A.2A_Pathfinding) -for our purposes. I won't go into detail explaining how this works, but -if you want to know more about the details of the algorithm, [click -here](https://en.wikipedia.org/wiki/A*_search_algorithm). +known as A-star. tcod includes a pathfinding module (`tcod.path`) that +we can use directly. I won't go into detail explaining how the A* +algorithm works, but if you want to know more about the details, +[click here](https://en.wikipedia.org/wiki/A*_search_algorithm). {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} def move_towards(self, target_x, target_y, game_map, entities): ... + def move_astar(self, target, entities, game_map): -+ # Create a FOV map that has the dimensions of the map -+ fov = libtcod.map_new(game_map.width, game_map.height) -+ -+ # Scan the current map each turn and set all the walls as unwalkable -+ for y1 in range(game_map.height): -+ for x1 in range(game_map.width): -+ libtcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, -+ not game_map.tiles[x1][y1].blocked) -+ -+ # Scan all the objects to see if there are objects that must be navigated around -+ # Check also that the object isn't self or the target (so that the start and the end points are free) -+ # The AI class handles the situation if self is next to the target so it will not use this A* function anyway ++ walkable = np.array( ++ [[not game_map.tiles[x1][y1].blocked for y1 in range(game_map.height)] ++ for x1 in range(game_map.width)], ++ dtype=np.int8 ++ ) + for entity in entities: + if entity.blocks and entity != self and entity != target: -+ # Set the tile as a wall so it must be navigated around -+ libtcod.map_set_properties(fov, entity.x, entity.y, True, False) -+ -+ # Allocate a A* path -+ # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited -+ my_path = libtcod.path_new_using_map(fov, 1.41) -+ -+ # Compute the path between self's coordinates and the target's coordinates -+ libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) -+ -+ # Check if the path exists, and in this case, also the path is shorter than 25 tiles -+ # The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor -+ # It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away -+ if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 25: -+ # Find the next coordinates in the computed full path -+ x, y = libtcod.path_walk(my_path, True) -+ if x or y: -+ # Set self's coordinates to the next path tile -+ self.x = x -+ self.y = y ++ walkable[entity.x, entity.y] = 0 ++ graph = tcod.path.SimpleGraph(cost=walkable.T.copy(), cardinal=2, diagonal=3) ++ pathfinder = tcod.path.Pathfinder(graph) ++ pathfinder.add_root((self.y, self.x)) ++ path = pathfinder.path_to((target.y, target.x))[1:].tolist() ++ if path and len(path) < 25: ++ dest_y, dest_x = path[0] ++ self.x = dest_x ++ self.y = dest_y + else: -+ # Keep the old move function as a backup so that if there are no paths (for example another monster blocks a corridor) -+ # it will still try to move towards the player (closer to the corridor opening) + self.move_towards(target.x, target.y, game_map, entities) -+ -+ # Delete the path to free memory -+ libtcod.path_delete(my_path) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} @@ -538,61 +512,40 @@ here](https://en.wikipedia.org/wiki/A*_search_algorithm). ... def move_astar(self, target, entities, game_map): - # Create a FOV map that has the dimensions of the map - fov = libtcod.map_new(game_map.width, game_map.height) - - # Scan the current map each turn and set all the walls as unwalkable - for y1 in range(game_map.height): - for x1 in range(game_map.width): - libtcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, - not game_map.tiles[x1][y1].blocked) - - # Scan all the objects to see if there are objects that must be navigated around - # Check also that the object isn't self or the target (so that the start and the end points are free) - # The AI class handles the situation if self is next to the target so it will not use this A* function anyway + walkable = np.array( + [[not game_map.tiles[x1][y1].blocked for y1 in range(game_map.height)] + for x1 in range(game_map.width)], + dtype=np.int8 + ) for entity in entities: if entity.blocks and entity != self and entity != target: - # Set the tile as a wall so it must be navigated around - libtcod.map_set_properties(fov, entity.x, entity.y, True, False) - - # Allocate a A* path - # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited - my_path = libtcod.path_new_using_map(fov, 1.41) - - # Compute the path between self's coordinates and the target's coordinates - libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) - - # Check if the path exists, and in this case, also the path is shorter than 25 tiles - # The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor - # It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away - if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 25: - # Find the next coordinates in the computed full path - x, y = libtcod.path_walk(my_path, True) - if x or y: - # Set self's coordinates to the next path tile - self.x = x - self.y = y + walkable[entity.x, entity.y] = 0 + graph = tcod.path.SimpleGraph(cost=walkable.T.copy(), cardinal=2, diagonal=3) + pathfinder = tcod.path.Pathfinder(graph) + pathfinder.add_root((self.y, self.x)) + path = pathfinder.path_to((target.y, target.x))[1:].tolist() + if path and len(path) < 25: + dest_y, dest_x = path[0] + self.x = dest_x + self.y = dest_y else: - # Keep the old move function as a backup so that if there are no paths (for example another monster blocks a corridor) - # it will still try to move towards the player (closer to the corridor opening) - self.move_towards(target.x, target.y, game_map, entities) - - # Delete the path to free memory - libtcod.path_delete(my_path)import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -125,14 +117,14 @@ Be sure to update the call to `render_all` in `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -- render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) -+ render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, +- render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) ++ render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, + bar_width, panel_height, panel_y, colors) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import numpy as np +import tcod import math ...@@ -817,7 +770,7 @@ class BasicMonster: + results = [] monster = self.owner - if libtcod.map_is_in_fov(fov_map, monster.x, monster.y): + if fov_map[monster.x, monster.y]: if monster.distance_to(target) >= 2: monster.move_astar(target, entities, game_map) @@ -836,7 +789,7 @@ class BasicMonster: results = [] monster = self.owner - if libtcod.map_is_in_fov(fov_map, monster.x, monster.y): + if fov_map[monster.x, monster.y]: if monster.distance_to(target) >= 2: monster.move_astar(target, entities, game_map) @@ -882,7 +835,7 @@ So what do we actually *do* with this `results` list? Lets modify return True if fullscreen: - libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) + context.sdl_window.fullscreen = not context.sdl_window.fullscreen + for player_turn_result in player_turn_results: + message = player_turn_result.get('message') @@ -943,7 +896,7 @@ So what do we actually *do* with this `results` list? Lets modify return True if fullscreen: - libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) + context.sdl_window.fullscreen = not context.sdl_window.fullscreen for player_turn_result in player_turn_results: message = player_turn_result.get('message') @@ -986,14 +939,12 @@ now. Create a new python file called `death_functions.py` and put the following two functions in it: {{< highlight py3 >}} -import tcod as libtcod - from game_states import GameStates def kill_player(player): player.char = '%' - player.color = libtcod.dark_red + player.color = (139, 0, 0) return 'You died!', GameStates.PLAYER_DEAD @@ -1002,7 +953,7 @@ def kill_monster(monster): death_message = '{0} is dead!'.format(monster.name.capitalize()) monster.char = '%' - monster.color = libtcod.dark_red + monster.color = (139, 0, 0) monster.blocks = False monster.fighter = None monster.ai = None @@ -1158,31 +1109,29 @@ statement (note that the player needs to be passed to `render_all` now). {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --def render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): -+def render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +-def render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): ++def render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): ... for entity in entities: draw_entity(con, entity, fov_map) -+ libtcod.console_set_default_foreground(con, libtcod.white) -+ libtcod.console_print_ex(con, 1, screen_height - 2, libtcod.BKGND_NONE, libtcod.LEFT, -+ 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp)) ++ con.print(1, screen_height - 2, 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp), ++ fg=(255, 255, 255)) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) + con.blit(dest=root_console) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): -def render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +{{ original-tab >}} {{ codetab >}} @@ -1190,13 +1139,13 @@ Update the call to `render_all` in `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) -+render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) +-render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) ++render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +def render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): ... for entity in entities: draw_entity(con, entity, fov_map) - libtcod.console_set_default_foreground(con, libtcod.white) - libtcod.console_print_ex(con, 1, screen_height - 2, libtcod.BKGND_NONE, libtcod.LEFT, - 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp)) + con.print(1, screen_height - 2, 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp), + fg=(255, 255, 255)) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0)+ con.blit(dest=root_console)render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) -render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors)+render_all(con, root_console, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) +render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors){{ original-tab >}} {{ codetab >}} @@ -1211,7 +1160,7 @@ above the Entities. Add the following to `render_functions.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod +from enum import Enum + @@ -1222,12 +1171,12 @@ import tcod as libtcod + ACTOR = 3 -def render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +def render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -63,16 +63,12 @@ but above def render_bar(panel, x, y, total_width, name, value, maximum, bar_color, back_color): bar_width = int(float(value) / maximum * total_width) - libtcod.console_set_default_background(panel, back_color) - libtcod.console_rect(panel, x, y, total_width, 1, False, libtcod.BKGND_SCREEN) - - libtcod.console_set_default_background(panel, bar_color) + panel.bg[x:x + total_width, y] = back_color if bar_width > 0: - libtcod.console_rect(panel, x, y, bar_width, 1, False, libtcod.BKGND_SCREEN) + panel.bg[x:x + bar_width, y] = bar_color - libtcod.console_set_default_foreground(panel, libtcod.white) - libtcod.console_print_ex(panel, int(x + total_width / 2), y, libtcod.BKGND_NONE, libtcod.CENTER, - '{0}: {1}/{2}'.format(name, value, maximum)) + panel.print(int(x + total_width / 2), y, '{0}: {1}/{2}'.format(name, value, maximum), + fg=(255, 255, 255), alignment=tcod.CENTER) {{ highlight >}} Now let's use this function in `render_all`. Remove the HP indicator we @@ -81,43 +77,39 @@ the function. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --def render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): -+def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, bar_width, -+ panel_height, panel_y, colors): +-def render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): ++def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, ++ bar_width, panel_height, panel_y, colors): ... -- libtcod.console_set_default_foreground(con, libtcod.white) -- libtcod.console_print_ex(con, 1, screen_height - 2, libtcod.BKGND_NONE, libtcod.LEFT, -- 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp)) +- con.print(1, screen_height - 2, 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp), +- fg=(255, 255, 255)) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) + con.blit(dest=root_console) -+ libtcod.console_set_default_background(panel, libtcod.black) -+ libtcod.console_clear(panel) ++ panel.clear() + + render_bar(panel, 1, 1, bar_width, 'HP', player.fighter.hp, player.fighter.max_hp, -+ libtcod.light_red, libtcod.darker_red) ++ (255, 114, 114), (127, 0, 0)) + -+ libtcod.console_blit(panel, 0, 0, screen_width, panel_height, 0, 0, panel_y) ++ panel.blit(dest=root_console, dest_x=0, dest_y=panel_y) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod from enum import Enum @@ -1238,7 +1187,7 @@ class RenderOrder(Enum): ACTOR = 3 -def render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +def render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): ...{{ original-tab >}} {{ codetab >}} @@ -1247,8 +1196,9 @@ Now modify the `__init__` function in `Entity` to take this into account. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod import math +import numpy as np +import tcod +from render_functions import RenderOrder @@ -1271,8 +1221,9 @@ class Entity: {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod -import math ++ con = tcod.console.Console(screen_width, screen_height, order='F') + panel = tcod.console.Console(screen_width, panel_height, order='F')import math +import numpy as np +import tcod from render_functions import RenderOrder @@ -1298,24 +1249,24 @@ Now modify our Entity initializations, starting with `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, fighter=fighter_component) -+player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component) +-player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, fighter=fighter_component) ++player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -{{ original-tab >}} {{ codetab >}} diff --git a/content/tutorials/tcod/2019/part-7.md b/content/tutorials/tcod/2019/part-7.md index 936fad1f..c360bfc3 100644 --- a/content/tutorials/tcod/2019/part-7.md +++ b/content/tutorials/tcod/2019/part-7.md @@ -27,8 +27,8 @@ variables in `engine.py`: - map_height = 45 + map_height = 43 ... - con = libtcod.console_new(screen_width, screen_height) -+ panel = libtcod.console_new(screen_width, panel_height) + con = tcod.console.Console(screen_width, screen_height, order='F') ++ panel = tcod.console.Console(screen_width, panel_height, order='F') {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} @@ -43,8 +43,8 @@ variables in `engine.py`: map_height = 45 map_height = 43 ... - con = libtcod.console_new(screen_width, screen_height) - panel = libtcod.console_new(screen_width, panel_height)player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component)+player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component){{ original-tab >}} {{ codetab >}} ... And don't leave out the import: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --from render_functions import clear_all, render_all -+from render_functions import clear_all, render_all, RenderOrder +-from render_functions import render_all ++from render_functions import render_all, RenderOrder {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -from render_functions import clear_all, render_all, RenderOrder+from render_functions import render_all, RenderOrder{{ original-tab >}} {{ codetab >}} @@ -1326,17 +1277,17 @@ Now for the monsters, in `game_map.py`: fighter_component = Fighter(hp=10, defense=0, power=3) ai_component = BasicMonster() -- monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, +- monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, - fighter=fighter_component, ai=ai_component) -+ monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, ++ monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, + render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) else: fighter_component = Fighter(hp=16, defense=1, power=4) ai_component = BasicMonster() -- monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, +- monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component, - ai=ai_component) -+ monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, ++ monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component, + render_order=RenderOrder.ACTOR, ai=ai_component) {{ highlight >}} {{ diff-tab >}} @@ -1345,17 +1296,17 @@ Now for the monsters, in `game_map.py`: fighter_component = Fighter(hp=10, defense=0, power=3) ai_component = BasicMonster() - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, fighter=fighter_component, ai=ai_component) - monster = Entity(x, y, 'o', libtcod.desaturated_green, 'Orc', blocks=True, + monster = Entity(x, y, 'o', (63, 127, 63), 'Orc', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, ai=ai_component) else: fighter_component = Fighter(hp=16, defense=1, power=4) ai_component = BasicMonster() - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, + monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component, ai=ai_component) - monster = Entity(x, y, 'T', libtcod.darker_green, 'Troll', blocks=True, fighter=fighter_component, + monster = Entity(x, y, 'T', (0, 100, 0), 'Troll', blocks=True, fighter=fighter_component, render_order=RenderOrder.ACTOR, ai=ai_component)def render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): -def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, bar_width, - panel_height, panel_y, colors): +def render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors): +def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, + bar_width, panel_height, panel_y, colors): ... - libtcod.console_set_default_foreground(con, libtcod.white) - libtcod.console_print_ex(con, 1, screen_height - 2, libtcod.BKGND_NONE, libtcod.LEFT, - 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp)) + con.print(1, screen_height - 2, 'HP: {0:02}/{1:02}'.format(player.fighter.hp, player.fighter.max_hp), + fg=(255, 255, 255)) - libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0) + con.blit(dest=root_console) - libtcod.console_set_default_background(panel, libtcod.black) - libtcod.console_clear(panel) + panel.clear() render_bar(panel, 1, 1, bar_width, 'HP', player.fighter.hp, player.fighter.max_hp, - libtcod.light_red, libtcod.darker_red) + (255, 114, 114), (127, 0, 0)) - libtcod.console_blit(panel, 0, 0, screen_width, panel_height, 0, 0, panel_y)+ panel.blit(dest=root_console, dest_x=0, dest_y=panel_y)render_all(con, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) - render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, +{{ original-tab >}} {{ codetab >}} @@ -263,8 +251,6 @@ all the `print` statements, replacing them with the message log. Let's start with the death functions. In `death_functions.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod - +from game_messages import Message from game_states import GameStates @@ -274,22 +260,20 @@ from render_functions import RenderOrder def kill_player(player): player.char = '%' - player.color = libtcod.dark_red + player.color = (139, 0, 0) - return 'You died!', GameStates.PLAYER_DEAD -+ return Message('You died!', libtcod.red), GameStates.PLAYER_DEAD ++ return Message('You died!', (255, 0, 0)), GameStates.PLAYER_DEAD def kill_monster(monster): - death_message = '{0} is dead!'.format(monster.name.capitalize()) -+ death_message = Message('{0} is dead!'.format(monster.name.capitalize()), libtcod.orange) ++ death_message = Message('{0} is dead!'.format(monster.name.capitalize()), (255, 127, 0)) ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -render_all(con, root_console, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, colors) + render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, bar_width, panel_height, panel_y, colors){{ original-tab >}} {{ codetab >}} @@ -174,13 +166,11 @@ and one for the messages inside it. Start by creating a new file, called `game_messages.py`. Put the following code inside it: {{< highlight py3 >}} -import tcod as libtcod - import textwrap class Message: - def __init__(self, text, color=libtcod.white): + def __init__(self, text, color=(255, 255, 255)): self.text = text self.color = color @@ -229,16 +219,14 @@ log to `engine.py`: fov_map = initialize_fov(game_map) + message_log = MessageLog(message_x, message_width, message_height) - - key = libtcod.Key() ++ mouse_pos = (0, 0) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}fov_map = initialize_fov(game_map) - message_log = MessageLog(message_x, message_width, message_height) - - key = libtcod.Key()+ message_log = MessageLog(message_x, message_width, message_height) + mouse_pos = (0, 0)import tcod as libtcod - -from game_messages import Message +{{ original-tab >}} {{ codetab >}} -You'll need to import both libtcod and Message for this to work: +You'll need to import `Message` for this to work: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -+import tcod as libtcod - +from game_messages import Message @@ -417,9 +399,7 @@ class Fighter: {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -from game_messages import Message from game_states import GameStates @@ -298,15 +282,15 @@ from render_functions import RenderOrder def kill_player(player): player.char = '%' - player.color = libtcod.dark_red + player.color = (139, 0, 0) return 'You died!', GameStates.PLAYER_DEAD - return Message('You died!', libtcod.red), GameStates.PLAYER_DEAD + return Message('You died!', (255, 0, 0)), GameStates.PLAYER_DEAD def kill_monster(monster): death_message = '{0} is dead!'.format(monster.name.capitalize()) - death_message = Message('{0} is dead!'.format(monster.name.capitalize()), libtcod.orange) + death_message = Message('{0} is dead!'.format(monster.name.capitalize()), (255, 127, 0)) ...{{ original-tab >}} {{ codetab >}} @@ -375,13 +359,13 @@ Now for our action messages. In `fighter.py`: - results.append({'message': '{0} attacks {1} for {2} hit points.'.format(self.owner.name.capitalize(), - target.name, str(damage))}) + results.append({'message': Message('{0} attacks {1} for {2} hit points.'.format( -+ self.owner.name.capitalize(), target.name, str(damage)), libtcod.white)}) ++ self.owner.name.capitalize(), target.name, str(damage)), (255, 255, 255))}) results.extend(target.fighter.take_damage(damage)) else: - results.append({'message': '{0} attacks {1} but does no damage.'.format(self.owner.name.capitalize(), - target.name)}) + results.append({'message': Message('{0} attacks {1} but does no damage.'.format( -+ self.owner.name.capitalize(), target.name), libtcod.white)}) ++ self.owner.name.capitalize(), target.name), (255, 255, 255))}) return results {{ highlight >}} @@ -392,23 +376,21 @@ Now for our action messages. In `fighter.py`: results.append({'message': '{0} attacks {1} for {2} hit points.'.format(self.owner.name.capitalize(), target.name, str(damage))}) results.append({'message': Message('{0} attacks {1} for {2} hit points.'.format( - self.owner.name.capitalize(), target.name, str(damage)), libtcod.white)}) + self.owner.name.capitalize(), target.name, str(damage)), (255, 255, 255))}) results.extend(target.fighter.take_damage(damage)) else: results.append({'message': '{0} attacks {1} but does no damage.'.format(self.owner.name.capitalize(), target.name)}) results.append({'message': Message('{0} attacks {1} but does no damage.'.format( - self.owner.name.capitalize(), target.name), libtcod.white)}) + self.owner.name.capitalize(), target.name), (255, 255, 255))}) return resultsimport tcod as libtcod - -from game_messages import Message +from game_messages import Message class Fighter: @@ -465,35 +445,33 @@ shows up yet. Let's modify `render_all` to display the message log we've created. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, bar_width, -- panel_height, panel_y, colors): -+def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, -+ bar_width, panel_height, panel_y, colors): +-def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, +- bar_width, panel_height, panel_y, colors): ++def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, ++ screen_height, bar_width, panel_height, panel_y, colors): ... - libtcod.console_clear(panel) + panel.clear() + # Print the game messages, one line at a time + y = 1 + for message in message_log.messages: -+ libtcod.console_set_default_foreground(panel, message.color) -+ libtcod.console_print_ex(panel, message_log.x, y, libtcod.BKGND_NONE, libtcod.LEFT, message.text) ++ panel.print(message_log.x, y, message.text, fg=message.color) + y += 1 ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -@@ -282,69 +282,69 @@ while we're targeting, and also add a generalized mouse handler, to know where the player clicks. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) + elif game_state == GameStates.TARGETING: -+ return handle_targeting_keys(key) ++ return handle_targeting_keys(event) elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) ... -+def handle_targeting_keys(key): -+ if key.vk == libtcod.KEY_ESCAPE: -+ return {'exit': True} ++def handle_targeting_keys(event): ++ if isinstance(event, tcod.event.KeyDown): ++ if event.sym == tcod.event.KeySym.ESCAPE: ++ return {'exit': True} + + return {} -def handle_player_dead_keys(key): +def handle_player_dead_keys(event): ... -+def handle_mouse(mouse): -+ (x, y) = (mouse.cx, mouse.cy) -+ -+ if mouse.lbutton_pressed: -+ return {'left_click': (x, y)} -+ elif mouse.rbutton_pressed: -+ return {'right_click': (x, y)} ++def handle_mouse(event): ++ if isinstance(event, tcod.event.MouseButtonDown): ++ if event.button == 1: ++ return {'left_click': (event.tile.x, event.tile.y)} ++ elif event.button == 3: ++ return {'right_click': (event.tile.x, event.tile.y)} + + return {} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, bar_width, - panel_height, panel_y, colors): -def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, - bar_width, panel_height, panel_y, colors): +{{ original-tab >}} {{ codetab >}} @@ -767,15 +766,15 @@ call in `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, -- screen_height, bar_width, panel_height, panel_y, mouse, colors) -+render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, -+ screen_height, bar_width, panel_height, panel_y, mouse, colors, game_state) +-render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, +- screen_height, bar_width, panel_height, panel_y, mouse_pos, colors) ++render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, ++ screen_height, bar_width, panel_height, panel_y, mouse_pos, colors, game_state) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, + bar_width, panel_height, panel_y, colors): +def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, + screen_height, bar_width, panel_height, panel_y, colors): ... - libtcod.console_clear(panel) + panel.clear() # Print the game messages, one line at a time y = 1 for message in message_log.messages: - libtcod.console_set_default_foreground(panel, message.color) - libtcod.console_print_ex(panel, message_log.x, y, libtcod.BKGND_NONE, libtcod.LEFT, message.text) + panel.print(message_log.x, y, message.text, fg=message.color) y += 1 ...{{ original-tab >}} @@ -504,17 +482,17 @@ message log: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -- render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, -- bar_width, panel_height, panel_y, colors) -+ render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, -+ screen_height, bar_width, panel_height, panel_y, colors) +- render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, +- screen_height, bar_width, panel_height, panel_y, colors) ++ render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, ++ screen_width, screen_height, bar_width, panel_height, panel_y, colors) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, screen_height, - bar_width, panel_height, panel_y, colors) - render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, - screen_height, bar_width, panel_height, panel_y, colors)+render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, screen_width, + screen_height, bar_width, panel_height, panel_y, colors) + render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, + screen_width, screen_height, bar_width, panel_height, panel_y, colors){{ original-tab >}} {{ codetab >}} @@ -528,30 +506,33 @@ orcs and trolls right now, but perhaps someday it will have dozens (hundreds?) of different monster and item types. It would be nice if we could see what they are by moving our mouse over them. -Lucky for us, we're already capturing Mouse input, in the `mouse` -variable right above the game loop. All we need to do is adjust our call -to `libtcod.sys_check_for_event` to respond to the mouse, and write the -code that displays the name when we move the mouse over something. +The `tcod.event.wait()` loop already receives all events including mouse +movement. We just need to handle `tcod.event.MouseMotion` events to track +the current tile position. Update the event loop in `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -- libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) -+ libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse) + for event in tcod.event.wait(): ++ if isinstance(event, tcod.event.MouseMotion): ++ mouse_pos = (event.tile.x, event.tile.y) + action = handle_keys(event) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse) - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse)+for event in tcod.event.wait(): + if isinstance(event, tcod.event.MouseMotion): + mouse_pos = (event.tile.x, event.tile.y) + action = handle_keys(event){{ original-tab >}} {{ codetab >}} Put the following function in `render_functions.py`, above `render_bar`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -+def get_names_under_mouse(mouse, entities, fov_map): -+ (x, y) = (mouse.cx, mouse.cy) ++def get_names_under_mouse(mouse_pos, entities, fov_map): ++ (x, y) = mouse_pos + names = [entity.name for entity in entities -+ if entity.x == x and entity.y == y and libtcod.map_is_in_fov(fov_map, entity.x, entity.y)] ++ if entity.x == x and entity.y == y and fov_map[entity.x, entity.y]] + names = ', '.join(names) + return names.capitalize() @@ -562,11 +543,11 @@ def render_bar(panel, x, y, total_width, name, value, maximum, bar_color, back_c {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def get_names_under_mouse(mouse, entities, fov_map): - (x, y) = (mouse.cx, mouse.cy) +{{ original-tab >}} @@ -230,8 +230,8 @@ In `engine.py`: ... fighter_component = Fighter(hp=30, defense=2, power=5) + inventory_component = Inventory(26) -- player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component) -+ player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, +- player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component) ++ player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, + fighter=fighter_component, inventory=inventory_component) entities = [player] ... @@ -241,8 +241,8 @@ In `engine.py`:def get_names_under_mouse(mouse_pos, entities, fov_map): + (x, y) = mouse_pos names = [entity.name for entity in entities - if entity.x == x and entity.y == y and libtcod.map_is_in_fov(fov_map, entity.x, entity.y)] + if entity.x == x and entity.y == y and fov_map[entity.x, entity.y]] names = ', '.join(names) return names.capitalize() @@ -583,35 +564,31 @@ take advantage of our new function. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, -- bar_width, panel_height, panel_y, colors): -+def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, -+ bar_width, panel_height, panel_y, mouse, colors): +-def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, +- screen_height, bar_width, panel_height, panel_y, colors): ++def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, ++ screen_height, bar_width, panel_height, panel_y, mouse_pos, colors): ... render_bar(panel, 1, 1, bar_width, 'HP', player.fighter.hp, player.fighter.max_hp, - libtcod.light_red, libtcod.darker_red) + (255, 114, 114), (127, 0, 0)) -+ libtcod.console_set_default_foreground(panel, libtcod.light_gray) -+ libtcod.console_print_ex(panel, 1, 0, libtcod.BKGND_NONE, libtcod.LEFT, -+ get_names_under_mouse(mouse, entities, fov_map)) ++ panel.print(1, 0, get_names_under_mouse(mouse_pos, entities, fov_map), fg=(191, 191, 191)) - libtcod.console_blit(panel, 0, 0, screen_width, panel_height, 0, 0, panel_y) + panel.blit(dest=root_console, dest_x=0, dest_y=panel_y) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, - bar_width, panel_height, panel_y, colors): -def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, - bar_width, panel_height, panel_y, mouse, colors): +{{ original-tab >}} {{ codetab >}} @@ -620,17 +597,17 @@ And, of course, we'll need to modify the call to `render_all` in definition. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -- render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, -- screen_height, bar_width, panel_height, panel_y, colors) -+ render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, -+ screen_height, bar_width, panel_height, panel_y, mouse, colors) +- render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, +- screen_width, screen_height, bar_width, panel_height, panel_y, colors) ++ render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, ++ screen_width, screen_height, bar_width, panel_height, panel_y, mouse_pos, colors) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, + screen_height, bar_width, panel_height, panel_y, colors): +def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, + screen_height, bar_width, panel_height, panel_y, mouse_pos, colors): ... render_bar(panel, 1, 1, bar_width, 'HP', player.fighter.hp, player.fighter.max_hp, - libtcod.light_red, libtcod.darker_red) + (255, 114, 114), (127, 0, 0)) - libtcod.console_set_default_foreground(panel, libtcod.light_gray) - libtcod.console_print_ex(panel, 1, 0, libtcod.BKGND_NONE, libtcod.LEFT, - get_names_under_mouse(mouse, entities, fov_map)) + panel.print(1, 0, get_names_under_mouse(mouse_pos, entities, fov_map), fg=(191, 191, 191)) - libtcod.console_blit(panel, 0, 0, screen_width, panel_height, 0, 0, panel_y)+ panel.blit(dest=root_console, dest_x=0, dest_y=panel_y)render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, - screen_height, bar_width, panel_height, panel_y, colors) - render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, - screen_height, bar_width, panel_height, panel_y, mouse, colors)+render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, + screen_width, screen_height, bar_width, panel_height, panel_y, colors) + render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, + screen_width, screen_height, bar_width, panel_height, panel_y, mouse_pos, colors){{ original-tab >}} {{ codetab >}} diff --git a/content/tutorials/tcod/2019/part-8.md b/content/tutorials/tcod/2019/part-8.md index 5c876944..08cdf317 100644 --- a/content/tutorials/tcod/2019/part-8.md +++ b/content/tutorials/tcod/2019/part-8.md @@ -35,7 +35,7 @@ map. + y = randint(room.y1 + 1, room.y2 - 1) + + if not any([entity for entity in entities if entity.x == x and entity.y == y]): -+ item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM) ++ item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM) + + entities.append(item) {{ highlight >}} @@ -53,7 +53,7 @@ map. y = randint(room.y1 + 1, room.y2 - 1) if not any([entity for entity in entities if entity.x == x and entity.y == y]): - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM) + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM) entities.append(item)... fighter_component = Fighter(hp=30, defense=2, power=5) inventory_component = Inventory(26) - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component) - player = Entity(0, 0, '@', libtcod.white, 'Player', blocks=True, render_order=RenderOrder.ACTOR, + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component) + player = Entity(0, 0, '@', (255, 255, 255), 'Player', blocks=True, render_order=RenderOrder.ACTOR, fighter=fighter_component, inventory=inventory_component) entities = [player] ...@@ -271,15 +271,15 @@ function. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} + item_component = Item() -- item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM) -+ item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, +- item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM) ++ item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, + item=item_component) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}item_component = Item() - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM) + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component){{ original-tab >}} {{ codetab >}} @@ -316,25 +316,25 @@ key: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - elif key_char == 'n': + elif key == tcod.event.KeySym.n: return {'move': (1, 1)} -+ if key_char == 'g': ++ if key == tcod.event.KeySym.g: + return {'pickup': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}... - elif key_char == 'n': + elif key == tcod.event.KeySym.n: return {'move': (1, 1)} - if key_char == 'g': + if key == tcod.event.KeySym.g: return {'pickup': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ...{{ original-tab >}} {{ codetab >}} @@ -367,7 +367,7 @@ indicates a failure. The engine will then have to determine what to do with the item entity. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -+import tcod as libtcod ++import tcod +from game_messages import Message @@ -383,12 +383,12 @@ class Inventory: + if len(self.items) >= self.capacity: + results.append({ + 'item_added': None, -+ 'message': Message('You cannot carry any more, your inventory is full', libtcod.yellow) ++ 'message': Message('You cannot carry any more, your inventory is full', (255, 255, 0)) + }) + else: + results.append({ + 'item_added': item, -+ 'message': Message('You pick up the {0}!'.format(item.name), libtcod.blue) ++ 'message': Message('You pick up the {0}!'.format(item.name), (0, 0, 255)) + }) + + self.items.append(item) @@ -397,7 +397,7 @@ class Inventory: {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +@@ -562,64 +562,63 @@ the inventory and any other menus we'll need for this tutorial. Put the following code in that file: {{< highlight py3 >}} -import tcod as libtcod +import tcod -def menu(con, header, options, width, screen_width, screen_height): +def menu(con, root_console, header, options, width, screen_width, screen_height): if len(options) > 26: raise ValueError('Cannot have a menu with more than 26 options.') # calculate total height for the header (after auto-wrap) and one line per option - header_height = libtcod.console_get_height_rect(con, 0, 0, width, screen_height, header) + header_height = con.get_height_rect(0, 0, width, screen_height, header) height = len(options) + header_height # create an off-screen console that represents the menu's window - window = libtcod.console_new(width, height) + window = tcod.console.Console(width, height, order='F') # print the header, with auto-wrap - libtcod.console_set_default_foreground(window, libtcod.white) - libtcod.console_print_rect_ex(window, 0, 0, width, height, libtcod.BKGND_NONE, libtcod.LEFT, header) + window.print_box(0, 0, width, height, header, fg=(255, 255, 255)) # print all the options y = header_height letter_index = ord('a') for option_text in options: text = '(' + chr(letter_index) + ') ' + option_text - libtcod.console_print_ex(window, 0, y, libtcod.BKGND_NONE, libtcod.LEFT, text) + window.print(0, y, text, fg=(255, 255, 255)) y += 1 letter_index += 1 # blit the contents of "window" to the root console x = int(screen_width / 2 - width / 2) y = int(screen_height / 2 - height / 2) - libtcod.console_blit(window, 0, 0, width, height, 0, x, y, 1.0, 0.7) + window.blit(dest=root_console, dest_x=x, dest_y=y, fg_alpha=1.0, bg_alpha=0.7) {{ highlight >}} {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def menu(con, header, options, width, screen_width, screen_height): +def menu(con, root_console, header, options, width, screen_width, screen_height): ... -+def inventory_menu(con, header, inventory, inventory_width, screen_width, screen_height): ++def inventory_menu(con, root_console, header, inventory, inventory_width, screen_width, screen_height): + # show a menu with each item of the inventory as an option + if len(inventory.items) == 0: + options = ['Inventory is empty.'] + else: + options = [item.name for item in inventory.items] + -+ menu(con, header, options, inventory_width, screen_width, screen_height) ++ menu(con, root_console, header, options, inventory_width, screen_width, screen_height) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod from game_messages import Message @@ -413,12 +413,12 @@ class Inventory: if len(self.items) >= self.capacity: results.append({ 'item_added': None, - 'message': Message('You cannot carry any more, your inventory is full', libtcod.yellow) + 'message': Message('You cannot carry any more, your inventory is full', (255, 255, 0)) }) else: results.append({ 'item_added': item, - 'message': Message('You pick up the {0}!'.format(item.name), libtcod.blue) + 'message': Message('You pick up the {0}!'.format(item.name), (0, 0, 255)) }) self.items.append(item) @@ -442,7 +442,7 @@ item to the inventory. + + break + else: -+ message_log.add_message(Message('There is nothing here to pick up.', libtcod.yellow)) ++ message_log.add_message(Message('There is nothing here to pick up.', (255, 255, 0))) if exit: ... @@ -460,7 +460,7 @@ item to the inventory. break else: - message_log.add_message(Message('There is nothing here to pick up.', libtcod.yellow))+ message_log.add_message(Message('There is nothing here to pick up.', (255, 255, 0))) if exit: ...def menu(con, header, options, width, screen_width, screen_height): +{{ original-tab >}} {{ codetab >}} @@ -656,25 +655,25 @@ key to do this. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - if key_char == 'g': + if key == tcod.event.KeySym.g: return {'pickup': True} -+ elif key_char == 'i': ++ elif key == tcod.event.KeySym.i: + return {'show_inventory': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}def menu(con, root_console, header, options, width, screen_width, screen_height): ... -def inventory_menu(con, header, inventory, inventory_width, screen_width, screen_height): +def inventory_menu(con, root_console, header, inventory, inventory_width, screen_width, screen_height): # show a menu with each item of the inventory as an option if len(inventory.items) == 0: options = ['Inventory is empty.'] else: options = [item.name for item in inventory.items] - menu(con, header, options, inventory_width, screen_width, screen_height)+ menu(con, root_console, header, options, inventory_width, screen_width, screen_height)... - if key_char == 'g': + if key == tcod.event.KeySym.g: return {'pickup': True} - elif key_char == 'i': + elif key == tcod.event.KeySym.i: return {'show_inventory': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ...{{ original-tab >}} {{ codetab >}} @@ -691,7 +690,7 @@ track of the last game state. game_state = GameStates.PLAYERS_TURN + previous_game_state = game_state - while not libtcod.console_is_window_closed(): + while True: ... {{ highlight >}} {{ diff-tab >}} @@ -700,7 +699,7 @@ track of the last game state. game_state = GameStates.PLAYERS_TURN previous_game_state = game_state - while not libtcod.console_is_window_closed(): + while True: ...render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, - screen_height, bar_width, panel_height, panel_y, mouse, colors, game_state)+render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, + screen_height, bar_width, panel_height, panel_y, mouse_pos, colors, game_state){{ original-tab >}} {{ codetab >}} @@ -783,27 +782,27 @@ And now for the definition: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, -- bar_width, panel_height, panel_y, mouse, colors): -+ bar_width, panel_height, panel_y, mouse, colors, game_state): +def render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, +- bar_width, panel_height, panel_y, mouse_pos, colors): ++ bar_width, panel_height, panel_y, mouse_pos, colors, game_state): ... ... - libtcod.console_blit(panel, 0, 0, screen_width, panel_height, 0, 0, panel_y) + panel.blit(dest=root_console, dest_x=0, dest_y=panel_y) + if game_state == GameStates.SHOW_INVENTORY: -+ inventory_menu(con, 'Press the key next to an item to use it, or Esc to cancel.\n', ++ inventory_menu(con, root_console, 'Press the key next to an item to use it, or Esc to cancel.\n', + player.inventory, 50, screen_width, screen_height) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, - bar_width, panel_height, panel_y, mouse, colors, game_state): +{{ original-tab >}} {{ codetab >}} @@ -198,15 +198,15 @@ def cast_lightning(*args, **kwargs): + + results = [] + -+ if not libtcod.map_is_in_fov(fov_map, target_x, target_y): -+ results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) ++ if not fov_map[target_x, target_y]: ++ results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', (255, 255, 0))}) + return results + -+ results.append({'consumed': True, 'message': Message('The fireball explodes, burning everything within {0} tiles!'.format(radius), libtcod.orange)}) ++ results.append({'consumed': True, 'message': Message('The fireball explodes, burning everything within {0} tiles!'.format(radius), (255, 127, 0))}) + + for entity in entities: + if entity.distance(target_x, target_y) <= radius and entity.fighter: -+ results.append({'message': Message('The {0} gets burned for {1} hit points.'.format(entity.name, damage), libtcod.orange)}) ++ results.append({'message': Message('The {0} gets burned for {1} hit points.'.format(entity.name, damage), (255, 127, 0))}) + results.extend(entity.fighter.take_damage(damage)) + + return results @@ -227,15 +227,15 @@ def cast_lightning(*args, **kwargs): results = [] - if not libtcod.map_is_in_fov(fov_map, target_x, target_y): - results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) + if not fov_map[target_x, target_y]: + results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', (255, 255, 0))}) return results - results.append({'consumed': True, 'message': Message('The fireball explodes, burning everything within {0} tiles!'.format(radius), libtcod.orange)}) + results.append({'consumed': True, 'message': Message('The fireball explodes, burning everything within {0} tiles!'.format(radius), (255, 127, 0))}) for entity in entities: if entity.distance(target_x, target_y) <= radius and entity.fighter: - results.append({'message': Message('The {0} gets burned for {1} hit points.'.format(entity.name, damage), libtcod.orange)}) + results.append({'message': Message('The {0} gets burned for {1} hit points.'.format(entity.name, damage), (255, 127, 0))}) results.extend(entity.fighter.take_damage(damage)) return resultsdef render_all(con, root_console, panel, entities, player, game_map, fov_map, fov_recompute, message_log, screen_width, screen_height, + bar_width, panel_height, panel_y, mouse_pos, colors, game_state): ... ... - libtcod.console_blit(panel, 0, 0, screen_width, panel_height, 0, 0, panel_y) + panel.blit(dest=root_console, dest_x=0, dest_y=panel_y) if game_state == GameStates.SHOW_INVENTORY: - inventory_menu(con, 'Press the key next to an item to use it, or Esc to cancel.\n', + inventory_menu(con, root_console, 'Press the key next to an item to use it, or Esc to cancel.\n', player.inventory, 50, screen_width, screen_height){{ original-tab >}} {{ codetab >}} @@ -811,7 +810,7 @@ def render_all(con, panel, entities, player, game_map, fov_map, fov_recompute, m We'll need to import `GameStates` and `inventory_menu` for this to work. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod from enum import Enum @@ -825,7 +824,7 @@ class RenderOrder(Enum): {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} @@ -91,16 +91,16 @@ lightning scrolls as well. In `game_map.py`: if not any([entity for entity in entities if entity.x == x and entity.y == y]): + item_chance = randint(0, 100) - item_component = Item(use_function=heal, amount=4) -- item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, +- item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, - item=item_component) + + if item_chance < 70: + item_component = Item(use_function=heal, amount=4) -+ item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, ++ item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, + item=item_component) + else: + item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) -+ item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, ++ item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, + item=item_component) {{ highlight >}} {{ diff-tab >}} @@ -111,11 +111,11 @@ lightning scrolls as well. In `game_map.py`: if item_chance < 70: item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) else: item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) - item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component)import tcod from enum import Enum @@ -855,13 +854,13 @@ the game's state. Rename the `handle_keys` function to `handle_player_turn_keys`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --def handle_keys(key): -+def handle_player_turn_keys(key): +-def handle_keys(event): ++def handle_player_turn_keys(event): {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -{{ original-tab >}} diff --git a/content/tutorials/tcod/2019/part-9.md b/content/tutorials/tcod/2019/part-9.md index bfe0ad5a..aa3ec28c 100644 --- a/content/tutorials/tcod/2019/part-9.md +++ b/content/tutorials/tcod/2019/part-9.md @@ -32,7 +32,7 @@ def heal(*args, **kwargs): + closest_distance = maximum_range + 1 + + for entity in entities: -+ if entity.fighter and entity != caster and libtcod.map_is_in_fov(fov_map, entity.x, entity.y): ++ if entity.fighter and entity != caster and fov_map[entity.x, entity.y]: + distance = caster.distance_to(entity) + + if distance < closest_distance: @@ -43,7 +43,7 @@ def heal(*args, **kwargs): + results.append({'consumed': True, 'target': target, 'message': Message('A lighting bolt strikes the {0} with a loud thunder! The damage is {1}'.format(target.name, damage))}) + results.extend(target.fighter.take_damage(damage)) + else: -+ results.append({'consumed': False, 'target': None, 'message': Message('No enemy is close enough to strike.', libtcod.red)}) ++ results.append({'consumed': False, 'target': None, 'message': Message('No enemy is close enough to strike.', (255, 0, 0))}) + + return results {{ highlight >}} @@ -65,7 +65,7 @@ def heal(*args, **kwargs): closest_distance = maximum_range + 1 for entity in entities: - if entity.fighter and entity != caster and libtcod.map_is_in_fov(fov_map, entity.x, entity.y): + if entity.fighter and entity != caster and fov_map[entity.x, entity.y]: distance = caster.distance_to(entity) if distance < closest_distance: @@ -76,7 +76,7 @@ def heal(*args, **kwargs): results.append({'consumed': True, 'target': target, 'message': Message('A lighting bolt strikes the {0} with a loud thunder! The damage is {1}'.format(target.name, damage))}) results.extend(target.fighter.take_damage(damage)) else: - results.append({'consumed': False, 'target': None, 'message': Message('No enemy is close enough to strike.', libtcod.red)}) + results.append({'consumed': False, 'target': None, 'message': Message('No enemy is close enough to strike.', (255, 0, 0))}) return resultsdef handle_keys(key): -def handle_player_turn_keys(key):+def handle_keys(event): +def handle_player_turn_keys(event):{{ original-tab >}} {{ codetab >}} @@ -869,36 +868,36 @@ Then, create a new `handle_keys` function, which calls `handle_player_turn_keys` {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod +import tcod +from game_states import GameStates + + -+def handle_keys(key, game_state): ++def handle_keys(event, game_state): + if game_state == GameStates.PLAYERS_TURN: -+ return handle_player_turn_keys(key) ++ return handle_player_turn_keys(event) + + return {} -def handle_player_turn_keys(key): +def handle_player_turn_keys(event): ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod +{{ original-tab >}} {{ codetab >}} @@ -1260,7 +1253,7 @@ function? Add the following functions to `Inventory`: + item_component = item_entity.item + + if item_component.use_function is None: -+ results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) ++ results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))}) + else: + kwargs = {**item_component.function_kwargs, **kwargs} + item_use_results = item_component.use_function(self.owner, **kwargs) @@ -1285,7 +1278,7 @@ function? Add the following functions to `Inventory`: item_component = item_entity.item if item_component.use_function is None: - results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) + results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))}) else: kwargs = {**item_component.function_kwargs, **kwargs} item_use_results = item_component.use_function(self.owner, **kwargs) @@ -1396,25 +1389,25 @@ Then, modify `handle_player_turn_keys` to respond to the 'd' key: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - elif key_char == 'i': + elif key == tcod.event.KeySym.i: return {'show_inventory': True} -+ elif key_char == 'd': ++ elif key == tcod.event.KeySym.d: + return {'drop_inventory': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ... {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}import tcod from game_states import GameStates -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) return {} -def handle_player_turn_keys(key): +def handle_player_turn_keys(event): ...{{ original-tab >}} {{ codetab >}} @@ -906,12 +905,12 @@ def handle_player_turn_keys(key): Don't forget to modify the call to `handle_keys` in `engine.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} --action = handle_keys(key) -+action = handle_keys(key, game_state) +-action = handle_keys(event) ++action = handle_keys(event, game_state) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -action = handle_keys(key, game_state)+action = handle_keys(event, game_state){{ original-tab >}} {{ codetab >}} @@ -920,62 +919,60 @@ in a key handler for when the player is dead. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) + elif game_state == GameStates.PLAYER_DEAD: -+ return handle_player_dead_keys(key) ++ return handle_player_dead_keys(event) return {} -def handle_player_turn_keys(key): +def handle_player_turn_keys(event): ... -+def handle_player_dead_keys(key): -+ key_char = chr(key.c) ++def handle_player_dead_keys(event): ++ if isinstance(event, tcod.event.KeyDown): ++ key = event.sym + -+ if key_char == 'i': -+ return {'show_inventory': True} ++ if key == tcod.event.KeySym.i: ++ return {'show_inventory': True} + -+ if key.vk == libtcod.KEY_ENTER and key.lalt: -+ # Alt+Enter: toggle full screen -+ return {'fullscreen': True} -+ elif key.vk == libtcod.KEY_ESCAPE: -+ # Exit the menu -+ return {'exit': True} ++ elif key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ++ return {'fullscreen': True} ++ elif key == tcod.event.KeySym.ESCAPE: ++ return {'exit': True} + + return {} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}... -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) return {} -def handle_player_turn_keys(key): +def handle_player_turn_keys(event): ... -def handle_player_dead_keys(key): - key_char = chr(key.c) +def handle_player_dead_keys(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym - if key_char == 'i': - return {'show_inventory': True} + if key == tcod.event.KeySym.i: + return {'show_inventory': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: - # Alt+Enter: toggle full screen - return {'fullscreen': True} - elif key.vk == libtcod.KEY_ESCAPE: - # Exit the menu - return {'exit': True} + elif key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: + return {'fullscreen': True} + elif key == tcod.event.KeySym.ESCAPE: + return {'exit': True} return {}{{ original-tab >}} @@ -986,58 +983,56 @@ will handle our input when the inventory menu is open. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) + elif game_state == GameStates.SHOW_INVENTORY: -+ return handle_inventory_keys(key) ++ return handle_inventory_keys(event) return {} ... -+def handle_inventory_keys(key): -+ index = key.c - ord('a') ++def handle_inventory_keys(event): ++ if isinstance(event, tcod.event.KeyDown): ++ key = event.sym + -+ if index >= 0: -+ return {'inventory_index': index} ++ if tcod.event.KeySym.a <= key <= tcod.event.KeySym.z: ++ return {'inventory_index': key - tcod.event.KeySym.a} + -+ if key.vk == libtcod.KEY_ENTER and key.lalt: -+ # Alt+Enter: toggle full screen -+ return {'fullscreen': True} -+ elif key.vk == libtcod.KEY_ESCAPE: -+ # Exit the menu -+ return {'exit': True} ++ elif key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ++ return {'fullscreen': True} ++ elif key == tcod.event.KeySym.ESCAPE: ++ return {'exit': True} + + return {} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}... -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) elif game_state == GameStates.SHOW_INVENTORY: - return handle_inventory_keys(key) + return handle_inventory_keys(event) return {} ... -def handle_inventory_keys(key): - index = key.c - ord('a') +def handle_inventory_keys(event): + if isinstance(event, tcod.event.KeyDown): + key = event.sym - if index >= 0: - return {'inventory_index': index} + if tcod.event.KeySym.a <= key <= tcod.event.KeySym.z: + return {'inventory_index': key - tcod.event.KeySym.a} - if key.vk == libtcod.KEY_ENTER and key.lalt: - # Alt+Enter: toggle full screen - return {'fullscreen': True} - elif key.vk == libtcod.KEY_ESCAPE: - # Exit the menu - return {'exit': True} + elif key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: + return {'fullscreen': True} + elif key == tcod.event.KeySym.ESCAPE: + return {'exit': True} return {}{{ original-tab >}} @@ -1142,8 +1137,6 @@ Create a file, called `item_functions.py`, and put the following function in it: {{< highlight py3 >}} -import tcod as libtcod - from game_messages import Message @@ -1154,10 +1147,10 @@ def heal(*args, **kwargs): results = [] if entity.fighter.hp == entity.fighter.max_hp: - results.append({'consumed': False, 'message': Message('You are already at full health', libtcod.yellow)}) + results.append({'consumed': False, 'message': Message('You are already at full health', (255, 255, 0))}) else: entity.fighter.heal(amount) - results.append({'consumed': True, 'message': Message('Your wounds start to feel better!', libtcod.green)}) + results.append({'consumed': True, 'message': Message('Your wounds start to feel better!', (0, 255, 0))}) return results {{ highlight >}} @@ -1211,7 +1204,7 @@ potions. Let's do that now; in the `place_entities` function in if not any([entity for entity in entities if entity.x == x and entity.y == y]): - item_component = Item() + item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) {{ highlight >}} {{ diff-tab >}} @@ -1220,7 +1213,7 @@ potions. Let's do that now; in the `place_entities` function in if not any([entity for entity in entities if entity.x == x and entity.y == y]): item_component = Item() item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component)... - elif key_char == 'i': + elif key == tcod.event.KeySym.i: return {'show_inventory': True} - elif key_char == 'd': + elif key == tcod.event.KeySym.d: return {'drop_inventory': True} - if key.vk == libtcod.KEY_ENTER and key.lalt: + if key == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.LALT: ...{{ original-tab >}} {{ codetab >}} @@ -1477,27 +1470,27 @@ dropping inventory, but we actually don't; we can just use our code for in `handle_keys`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -def handle_keys(key, game_state): +def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) - elif game_state == GameStates.SHOW_INVENTORY: + elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) return {} {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_keys(key, game_state): +{{ original-tab >}} {{ codetab >}} @@ -1612,7 +1605,7 @@ player's feet), and return the results. + + self.remove_item(item) + results.append({'item_dropped': item, 'message': Message('You dropped the {0}'.format(item.name), -+ libtcod.yellow)}) ++ (255, 255, 0))}) + + return results {{ highlight >}} @@ -1630,7 +1623,7 @@ player's feet), and return the results. self.remove_item(item) results.append({'item_dropped': item, 'message': Message('You dropped the {0}'.format(item.name), - libtcod.yellow)}) + (255, 255, 0))}) return resultsdef handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) elif game_state == GameStates.SHOW_INVENTORY: elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) return {}{{ original-tab >}} @@ -1535,7 +1528,7 @@ title to it. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - if game_state == GameStates.SHOW_INVENTORY: -- inventory_menu(con, 'Press the key next to an item to use it, or Esc to cancel.\n', +- inventory_menu(con, root_console, 'Press the key next to an item to use it, or Esc to cancel.\n', - player.inventory, 50, screen_width, screen_height) + if game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): @@ -1544,13 +1537,13 @@ title to it. + else: + inventory_title = 'Press the key next to an item to drop it, or Esc to cancel.\n' + -+ inventory_menu(con, inventory_title, player.inventory, 50, screen_width, screen_height) ++ inventory_menu(con, root_console, inventory_title, player.inventory, 50, screen_width, screen_height) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}... if game_state == GameStates.SHOW_INVENTORY: - inventory_menu(con, 'Press the key next to an item to use it, or Esc to cancel.\n', + inventory_menu(con, root_console, 'Press the key next to an item to use it, or Esc to cancel.\n', player.inventory, 50, screen_width, screen_height) if game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): @@ -1559,7 +1552,7 @@ title to it. else: inventory_title = 'Press the key next to an item to drop it, or Esc to cancel.\n' - inventory_menu(con, inventory_title, player.inventory, 50, screen_width, screen_height)+ inventory_menu(con, root_console, inventory_title, player.inventory, 50, screen_width, screen_height)def handle_keys(key, game_state): +@@ -936,8 +942,6 @@ You'll need to import the `ConfusedMonster` class to the top of the file: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod - +from components.ai import ConfusedMonster from game_messages import Message @@ -945,9 +949,7 @@ from game_messages import Message {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -def handle_keys(event, game_state): if game_state == GameStates.PLAYERS_TURN: - return handle_player_turn_keys(key) + return handle_player_turn_keys(event) elif game_state == GameStates.PLAYER_DEAD: - return handle_player_dead_keys(key) + return handle_player_dead_keys(event) elif game_state == GameStates.TARGETING: - return handle_targeting_keys(key) + return handle_targeting_keys(event) elif game_state in (GameStates.SHOW_INVENTORY, GameStates.DROP_INVENTORY): - return handle_inventory_keys(key) + return handle_inventory_keys(event) ... -def handle_targeting_keys(key): - if key.vk == libtcod.KEY_ESCAPE: - return {'exit': True} +def handle_targeting_keys(event): + if isinstance(event, tcod.event.KeyDown): + if event.sym == tcod.event.KeySym.ESCAPE: + return {'exit': True} return {} -def handle_player_dead_keys(key): +def handle_player_dead_keys(event): ... -def handle_mouse(mouse): - (x, y) = (mouse.cx, mouse.cy) - - if mouse.lbutton_pressed: - return {'left_click': (x, y)} - elif mouse.rbutton_pressed: - return {'right_click': (x, y)} +def handle_mouse(event): + if isinstance(event, tcod.event.MouseButtonDown): + if event.button == 1: + return {'left_click': (event.tile.x, event.tile.y)} + elif event.button == 3: + return {'right_click': (event.tile.x, event.tile.y)} return {}{{ original-tab >}} @@ -358,8 +358,13 @@ Modify `engine.py` to accept the mouse inputs: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} ... - action = handle_keys(key, game_state) -+ mouse_action = handle_mouse(mouse) ++ mouse_action = {} + for event in tcod.event.wait(): + if isinstance(event, tcod.event.MouseMotion): + mouse_pos = (event.tile.x, event.tile.y) ++ elif isinstance(event, tcod.event.MouseButtonDown): ++ mouse_action = handle_mouse(event) + action = handle_keys(event, game_state) move = action.get('move') pickup = action.get('pickup') @@ -376,8 +381,13 @@ Modify `engine.py` to accept the mouse inputs: {{ diff-tab >}} {{< original-tab >}}... - action = handle_keys(key, game_state) - mouse_action = handle_mouse(mouse) + mouse_action = {} + for event in tcod.event.wait(): + if isinstance(event, tcod.event.MouseMotion): + mouse_pos = (event.tile.x, event.tile.y) + elif isinstance(event, tcod.event.MouseButtonDown): + mouse_action = handle_mouse(event) + action = handle_keys(event, game_state) move = action.get('move') pickup = action.get('pickup') @@ -456,7 +466,7 @@ the previous code section in the "else" clause, like this: item_component = item_entity.item if item_component.use_function is None: - results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) + results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))}) else: - kwargs = {**item_component.function_kwargs, **kwargs} - item_use_results = item_component.use_function(self.owner, **kwargs) @@ -488,7 +498,7 @@ the previous code section in the "else" clause, like this: item_component = item_entity.item if item_component.use_function is None: - results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), libtcod.yellow)}) + results.append({'message': Message('The {0} cannot be used'.format(item_entity.name), (255, 255, 0))}) else: if item_component.targeting and not (kwargs.get('target_x') or kwargs.get('target_y')): results.append({'targeting': item_entity}) @@ -525,7 +535,7 @@ loop to keep track of the targeting item that was selected. + targeting_item = None - while not libtcod.console_is_window_closed(): + while True: ... message = player_turn_result.get('message') dead_entity = player_turn_result.get('dead') @@ -554,7 +564,7 @@ loop to keep track of the targeting item that was selected. targeting_item = None - while not libtcod.console_is_window_closed(): + while True: ... message = player_turn_result.get('message') dead_entity = player_turn_result.get('dead') @@ -684,17 +694,17 @@ Finally, let's add the fireball scroll to the map. Modify if item_chance < 70: item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) + elif item_chance < 85: + item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( -+ 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), ++ 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), + damage=12, radius=3) -+ item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, ++ item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, + item=item_component) else: item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) - item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component) {{ highlight >}} {{ diff-tab >}} @@ -704,17 +714,17 @@ Finally, let's add the fireball scroll to the map. Modify if item_chance < 70: item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) elif item_chance < 85: item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( - 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), + 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), damage=12, radius=3) - item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) else: item_component = Item(use_function=cast_lightning, damage=20, maximum_range=5) - item = Entity(x, y, '#', libtcod.yellow, 'Lightning Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 255, 0), 'Lightning Scroll', render_order=RenderOrder.ITEM, item=item_component){{ original-tab >}} {{ codetab >}} @@ -786,8 +796,6 @@ spell ends. We'll begin by adding the confused AI, to `ai.py`: {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} -import tcod as libtcod - +from random import randint + +from game_messages import Message @@ -815,15 +823,13 @@ class BasicMonster: + self.number_of_turns -= 1 + else: + self.owner.ai = self.previous_ai -+ results.append({'message': Message('The {0} is no longer confused!'.format(self.owner.name), libtcod.red)}) ++ results.append({'message': Message('The {0} is no longer confused!'.format(self.owner.name), (255, 0, 0))}) + + return results {{ highlight >}} {{ diff-tab >}} {{< original-tab >}} -import tcod as libtcod - -from random import randint +{{ original-tab >}} @@ -877,8 +883,8 @@ def cast_fireball(*args, **kwargs): + + results = [] + -+ if not libtcod.map_is_in_fov(fov_map, target_x, target_y): -+ results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) ++ if not fov_map[target_x, target_y]: ++ results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', (255, 255, 0))}) + return results + + for entity in entities: @@ -888,11 +894,11 @@ def cast_fireball(*args, **kwargs): + confused_ai.owner = entity + entity.ai = confused_ai + -+ results.append({'consumed': True, 'message': Message('The eyes of the {0} look vacant, as he starts to stumble around!'.format(entity.name), libtcod.light_green)}) ++ results.append({'consumed': True, 'message': Message('The eyes of the {0} look vacant, as he starts to stumble around!'.format(entity.name), (63, 255, 63))}) + + break + else: -+ results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', libtcod.yellow)}) ++ results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', (255, 255, 0))}) + + return results + @@ -910,8 +916,8 @@ def cast_fireball(*args, **kwargs): results = [] - if not libtcod.map_is_in_fov(fov_map, target_x, target_y): - results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', libtcod.yellow)}) + if not fov_map[target_x, target_y]: + results.append({'consumed': False, 'message': Message('You cannot target a tile outside your field of view.', (255, 255, 0))}) return results for entity in entities: @@ -921,11 +927,11 @@ def cast_fireball(*args, **kwargs): confused_ai.owner = entity entity.ai = confused_ai - results.append({'consumed': True, 'message': Message('The eyes of the {0} look vacant, as he starts to stumble around!'.format(entity.name), libtcod.light_green)}) + results.append({'consumed': True, 'message': Message('The eyes of the {0} look vacant, as he starts to stumble around!'.format(entity.name), (63, 255, 63))}) break else: - results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', libtcod.yellow)}) + results.append({'consumed': False, 'message': Message('There is no targetable enemy at that location.', (255, 255, 0))}) return resultsfrom random import randint from game_messages import Message @@ -850,7 +856,7 @@ class BasicMonster: self.number_of_turns -= 1 else: self.owner.ai = self.previous_ai - results.append({'message': Message('The {0} is no longer confused!'.format(self.owner.name), libtcod.red)}) + results.append({'message': Message('The {0} is no longer confused!'.format(self.owner.name), (255, 0, 0))}) return resultsimport tcod as libtcod - -from components.ai import ConfusedMonster +from components.ai import ConfusedMonster from game_messages import Message ...@@ -985,38 +987,38 @@ chance of spawning. {{< codetab >}} {{< diff-tab >}} {{< highlight diff >}} if item_chance < 70: item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) - elif item_chance < 85: + elif item_chance < 80: item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( - 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), + 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), damage=12, radius=3) - item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) + elif item_chance < 90: + item_component = Item(use_function=cast_confuse, targeting=True, targeting_message=Message( -+ 'Left-click an enemy to confuse it, or right-click to cancel.', libtcod.light_cyan)) -+ item = Entity(x, y, '#', libtcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, ++ 'Left-click an enemy to confuse it, or right-click to cancel.', (63, 255, 255))) ++ item = Entity(x, y, '#', (255, 114, 114), 'Confusion Scroll', render_order=RenderOrder.ITEM, + item=item_component) {{ highlight >}} {{ diff-tab >}} {{< original-tab >}}if item_chance < 70: item_component = Item(use_function=heal, amount=4) - item = Entity(x, y, '!', libtcod.violet, 'Healing Potion', render_order=RenderOrder.ITEM, + item = Entity(x, y, '!', (127, 0, 255), 'Healing Potion', render_order=RenderOrder.ITEM, item=item_component) elif item_chance < 85: elif item_chance < 80: item_component = Item(use_function=cast_fireball, targeting=True, targeting_message=Message( - 'Left-click a target tile for the fireball, or right-click to cancel.', libtcod.light_cyan), + 'Left-click a target tile for the fireball, or right-click to cancel.', (63, 255, 255)), damage=12, radius=3) - item = Entity(x, y, '#', libtcod.red, 'Fireball Scroll', render_order=RenderOrder.ITEM, + item = Entity(x, y, '#', (255, 0, 0), 'Fireball Scroll', render_order=RenderOrder.ITEM, item=item_component) elif item_chance < 90: item_component = Item(use_function=cast_confuse, targeting=True, targeting_message=Message( - 'Left-click an enemy to confuse it, or right-click to cancel.', libtcod.light_cyan)) - item = Entity(x, y, '#', libtcod.light_pink, 'Confusion Scroll', render_order=RenderOrder.ITEM, + 'Left-click an enemy to confuse it, or right-click to cancel.', (63, 255, 255))) + item = Entity(x, y, '#', (255, 114, 114), 'Confusion Scroll', render_order=RenderOrder.ITEM, item=item_component){{ original-tab >}} {{ codetab >}}