Roguelike Tutorial, using python3+tdl, part 1
This is part of a series of tutorials; the main page can be found here.
The tutorial uses tdl version 3.1.0 and Python 3.5
Setting it up
Ok, now that we got that out of our way let's get our hands dirty!
- If you haven't yet, download and install Python 3.5.
- Install the tdl module for python3.
- In windows:
MinGW must be on the Windows path for use with pycparser.
python3 -m pip install tdl
- Note: If you're working on a fresh environment or a new PC, don't forget to install the latest Microsoft Visual C++ runtime. If you've made sure that your x32 or x64 version of Python matches both your operating system and the install packages and you're still getting "ImportError: DLL Load Failed" when you try to execute the code, this may be the issue.
As of version 3, tdl now depends on libtcod-cffi. It will be installed automatically by pip.
- In Linux:
If using a Debian or Ubuuntu based distro, install the following dependencies:
apt-get install gcc libsdl2-dev libffi-dev python-dev
If you use a different distro, you'll have to figure out the equivalent packages. Feel free to update this wiki with the relevant information.
Next, install tdl:
pip3 install tdl
Now to create your project's folder. Create an empty file with a name of your choice, like firstrl.py. You'll also need a font file in png format. We chose arial10x10.png, which you can download here.
More fonts are available here.
Choice of code editor
If you're just starting out with Python, you'll find that many Python coders just use a simple editor and run their scripts from a console to see any debugging output. Most Python coders don't feel the need to use a fancy IDE! On Windows, Notepad++ is an excellent bet; most Linux programmers already have an editor of choice. Almost all editors allow you to configure shortcut keys (like F5 for instance) to quickly run the script you're editing, without having to switch to a console.
See this page from the libtcod tutorial if you want to set up a Notepad++ shortcut with a couple of nice features for debugging, or if you tried to roll your own and hit the infamous "module not found" error.
Showing the @ on screen
This first part will be a bit of a crash-course. The reason is that you need a few lines of boilerplate code that will initialize and handle the basics of a libtcod window. And though there are many options, we won't explain them all or this part will really start to drag out. Fortunately the code involved is not as much as in many other libraries!
First we import the library.
Then, a couple of important values. It's good practice to define special numbers that might get reused. Many people capitalize them to distinguish from variables that may change.
SCREEN_WIDTH = 80 SCREEN_HEIGHT = 50 LIMIT_FPS = 20
Now we're going to use a custom font! It's pretty easy. You can find many choices here. Remember however that they can be in different formats, and you'll need to tell it about this. This one is "grayscale" and using the "tcod layout", which we indicate by setting the parameter altLayout=True. Most fonts are in this format and thus end with _gs_tc. If you want to use a font with a different layout or make your own, the docs on the subject are really informative. If you do switch the font, you may also need to change the "greyscale" and/or the "altLayout" settings to "False." You can worry about that at a later time though. Notice that the size of a font is automatically detected.
tdl.set_font('arial10x10.png', greyscale=True, altLayout=True)
This is probably the most important call, initializing the window. We're specifying its size, the title (change it now if you want to), and the last parameter tells it if it should be fullscreen or not.
console = tdl.init(SCREEN_WIDTH, SCREEN_HEIGHT, title="Roguelike", fullscreen=False)
For a real-time roguelike, you wanna limit the speed of the game (frames-per-second or FPS). If you want it to be turn-based, ignore this line. (This line will simply have no effect if your game is turn-based.)
Now the main loop. It will keep running the logic of your game as long as the window is not closed.
while not tdl.event.is_window_closed():
For each iteration we'll want to print something useful to the window. If your game is turn-based each iteration is a turn; if it's real-time, each one is a frame.
Now print a character to the coordinates (1,1). Can you guess what that character is? No, it doesn't move yet!
Don't forget the indentation at the beginning of the line, it's extra-important in Python. Make sure you don't mix tabs with spaces for indentation! This comes up often if you copy-and-paste code from the net, and you'll see an error telling you something about the indentation (that's a pretty big clue right there!). Choose one option and stick with it. In this tutorial we're using the 4-spaces convention, but tabs are easy to work with in many editors so they're a valid choice too.
console.draw_char(1, 1, '@', bg=None, fg=(255,255,255))
Here we're setting the text color to be white. In RGB color codes, that's 255,255,255 There's a good list of colors you can use here, along with some info about mixing them and all that. Alternatively, we can use hexidecimal color codes. For white, that would be: 0xFFFFFF (note that there are no quotations around it, since Python will read it as a hexidecimal integer, not a string.)
At the end of the main loop you'll always need to present the changes to the screen. This is called flushing the console and is done with the following line.
Ta-da! You're done. Run that code and give yourself a pat on the back!
Note that since we don't have any input handling code, the game may crash on exit (it won't process the OS's requests to close). Oops! Don't worry though, this problem will go away as soon as we add keyboard support.
Here's the complete code so far.
That was pretty neat, huh? Now we're going to move around that @ with the keys!
First, we need to keep track of the player's position. We'll use these variables for that, and take the opportunity to initialize them to the center of the screen instead of the top-left corner. This can go just before the main loop.
playerx = SCREEN_WIDTH//2 playery = SCREEN_HEIGHT//2
There are functions to check for pressed keys. When that happens, just change the coordinates accordingly. Then, print the @ at those coordinates. We'll make a separate function to handle the keys.
def handle_keys(): global playerx, playery user_input = tdl.event.key_wait() #movement keys if user_input.key == 'UP': playery -= 1 elif user_input.key == 'DOWN': playery += 1 elif user_input.key == 'LEFT': playerx -= 1 elif user_input.key == 'RIGHT': playerx += 1
Done! These are the arrow keys, if you want to use other keys here's a reference. Most codes are self-explanatory. KP stands for keypad.
While we're at it, why not include keys to toggle fullscreen mode, and exit the game? You can put this just above the movement keys.
if user_input.key == 'ENTER' and user_input.alt: #Alt+Enter: toggle fullscreen tdl.set_fullscreen(not tdl.get_fullscreen()) elif user_input.key == 'ESCAPE': return True #exit game
From now on, we'll show code for a real-time game with a green background, and code for a turn-based game with a blue background.
So far, we've been writing a turn-based game. Then the game won't go on unless the player presses a key. So effectively you have a turn-based game now. However, we'll introduce another option at this point. If we comment out the line user_input = tdl.event.key_wait() and replace it with the following block of code, we'll have a real-time game:
#realtime keypress = False for event in tdl.event.get(): if event.type == 'KEYDOWN': user_input = event keypress = True if not keypress: return
This works because tdl.event.get() won't block the game.
Now, the main loop needs to call this function in order for it to work. If the returned value is True, then we "break" from the main loop, ending the game. The inside of the main loop should now look like this:
console.draw_char(playerx, playery, '@', bg=None, fg=(255,255,255)) tdl.flush() #handle keys and exit game if needed exit_game = handle_keys() if exit_game: break
The reason why we draw stuff before handling key input is that, in a turn-based game, the first screen is shown before the first key is pressed (otherwise the first screen would be blank).
One more thing! If you try that, you'll see that moving you leave around a trail of little @'s. That's not what we want! We need to clear the character at the last position before moving to the new one, this can be done by simply printing a space there. Put this just before exit_game = handle_keys().
console.draw_char(playerx, playery, ' ', bg=None)
Here's a rundown of the whole code so far.