Part 1 - Drawing the ‘@’ symbol and moving it around

Welcome to part 1 of this tutorial! This series will help you create your very first roguelike game, written in Python!

The 2021 tutorial is largely based off the 2020 tutorial, and is primarily a smaller update rather than a larger rewrite that the previous versions were.

This part assumes that you have either checked Part 0 or are already familiar with Python and Python-tcod. If not, be sure to check that page, and make sure that you’ve got Python and TCOD installed, as well as having an IDE or editor ready.

Assuming that you’ve done all that, let’s get started.

We will start by setting up the following directory structure. The font from Part 0 will go into a data directory, then main.py will be created as an entry point.

/data/dejavu16x16_gs_tc.png
/main.py

/main.py is the entry point of the program. You can use python main.py to start the program after the following is implemented.

#!/usr/bin/env python3
# main.py
import tcod

MOVE_KEYS = {
    tcod.event.K_UP: (0, -1),
    tcod.event.K_DOWN: (0, 1),
    tcod.event.K_LEFT: (-1, 0),
    tcod.event.K_RIGHT: (1, 0),
}


def main() -> None:
    screen_width = 80
    screen_height = 50

    player_x: int = screen_width // 2
    player_y: int = screen_height // 2

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

    with tcod.context.new(
        columns=screen_width,
        rows=screen_height,
        tileset=tileset,
        title="Yet Another Roguelike Tutorial",
        vsync=True,
    ) as context:
        root_console = tcod.Console(screen_width, screen_height, order="F")
        while True:
            root_console.clear()
            root_console.print(x=player_x, y=player_y, string="@")
            context.present(root_console)

            for event in tcod.event.wait():
                if isinstance(event, tcod.event.Quit):
                    raise SystemExit(0)
                if isinstance(event, tcod.event.KeyDown):
                    if event.sym in MOVE_KEYS:
                        delta_x, delta_y = MOVE_KEYS[event.sym]
                        dest_x = player_x + delta_x
                        dest_y = player_y + delta_y
                        if 0 <= dest_x < screen_width and 0 <= dest_y < screen_height:
                            player_x, player_y = dest_x, dest_y


if __name__ == "__main__":
    main()

#!/usr/bin/env python3 is a shebang and must be the first line to be useful. It is normally used to make scripts executable on Linux, but is also used by the Windows Python launcher.

tcod is imported along with the two other modules we’ve added.

MOVE_KEYS is a Python dictionary mapping of tcod KeySym’s to in-game directions. This will be expanded in later parts.

The main function will be the entry point of the program. There’s nothing special about the name other than the terminology, the special nature of this function comes from the __name__ == "__main__" condition at the bottom of the script which is only True when the script is directly run, compared to importing main from an interactive prompt.

main starts by setting the screen size, then sets the player position to the center of the screen. The floor division operator is used so that the numbers don’t promote to a float type.

The tileset is loaded with tcod.tileset.load_tilesheet The Python-tcod docs have a character reference to keep track of which layouts have what glyphs.

tcod.context.new is used to setup the window and returns a Context instance. Contexts must be closed once you’re done with them, but the with statement will do this automatically when exiting the with-block.

Then a tcod.Console is created with the same size that was given to tcod.context.new, order="F" sets the console arrays to be indexed in x, y order. This is a convenient way to handle array indexes so we’ll use order="F" a lot when working with NumPy. You might wonder why order="F" isn’t the default and there is an explanation for that. These arrays are not being used yet.

Now with while True: the game-loop begins, with the only way of existing this loop normally being the SystemExit exceptions implemented later. root_console is cleared, then the player position is printed to root_console, then root_console is displayed using context.present(root_console).

The for-loop waits for there to be events then iterates over all events until none are left. This is an efficient way to handle events when the game doesn’t have real-time animations or mechanics. If you have real-time effects then you should replace tcod.event.wait with tcod.event.get.

isinstance is being used here to sort events by type. This has an effect on type-checking, where anything within an isinstance branch can be assumed to actually be that type, much like a type cast in any other language.

Trying to close the window will result in a Quit event. When this happens we will raise SystemExit which will propagate and terminate the script.

For KeyDown events we check if the key is in the MOVE_KEYS dictionary then try to move by that amount. If the destination is on the screen then the player moves to that spot.

You can see the current progress of this code in its entirety here.

If you want to distribute your game then you can set that up now or at any time later.

Continue to part 2.

Return to the hub.


Project maintained by HexDecimal Hosted on GitHub Pages — Theme by mattgraham