Difference between revisions of "Complete Roguelike Tutorial, using python3+libtcod, part 1"

From RogueBasin
Jump to navigation Jump to search
(Real first edit)
 
(34 intermediate revisions by one other user not shown)
Line 1: Line 1:
<center><table border="0" cellpadding="10" cellspacing="0" style="background:#F0E68C"><tr><td><center>
This is part of a series of tutorials; the main page can be found [[Complete Roguelike Tutorial, using python3+libtcod|here]].
</center></td></tr></table></center>
__TOC__
__TOC__


Line 5: Line 9:
== Setting it up ==
== Setting it up ==


Ok, now that we got that out of our way let's get our hands dirty!
See [[Complete Roguelike Tutorial, using python3+libtcod, part 0|Part 0]].
 
=== Windows ===
 
==== Python ====
 
If you haven't already done so, [http://www.python.org/download/ download and install Python 3.6]. Any version of Python 3, from 3.6 and above should be fine, but the latest update (of <i>Python 3</i>, not Python 2) is of course always preferable.
 
There are reports that for older versions of libtcod on 64-bit Windows (Windows 7) that the 32 bit version is preferable, since the 64 bit version of Python seems to cause  problems with libtcod.  This was been tested on Windows 10 with libtcod 1.6, and was not reproducible, so it's up to you.
 
It might be advisable to go with 32 bit just to keep things simple, and when you're gone through the tutorial look at switching to 64 bit if it matters to you to do so, and if you do encounter errors specifically related to this, report them on the [https://bitbucket.org/libtcod/libtcod/issues/ libtcod bug tracker].
 
==== libtcod ====
 
Download the latest release of [https://bitbucket.org/libtcod/libtcod/downloads libtcod 1.6] and extract it somewhere. Be warned that both Python and libtcod must either be <b>both 32 bit</b>, or <b>both 64 bit</b>.  If you get dll loading errors, getting this wrong is the most likely cause.  libtcod will now (as of 1.6.2 and above) check they are compatible, and tell you if they are not.
 
===== Check if libtcod works =====
 
libtcod is compiled with the latest official release of Visual Studio.  In an ideal world, it should just run, and if you have recently updated games installed from Steam or perhaps even some modern applications, it should.  However, some people lack the support for that latest release of Visual Studio, and these steps help update your computer to allow libtcod to be run.
 
<ol>
<li>Within the libtcod release you just extracted, you will see a file called <cite>samples.exe</cite>.</li>
<li>Run <cite>samples.exe</cite> and verify that it appears to work.  If it opens a colourful window, then it is now verified as working for you.</li>
<li>If it just doesn't start and instead you see a message about <cite>vcruntime140.dll</cite> being required, then you need to install the correct Visual Studio 2015 runtime.</li>
<li>Go to the latest Microsoft Visual Studio 2015 [https://www.microsoft.com/en-us/download/details.aspx?id=53587 runtime download page], and download and install the runtime that matches the Python and libtcod you are using.  If you are using 32 bit Python and libtcod, this will be the x86 runtime.  If you are using the 64 bit Python and libtcod, this will be the x64 runtime.  (Tip: If you think you might switch from 32 bit to 64 bit in the future, hedge your bets and install both runtimes to prevent future problems.)</li>
<li>Once you've installed the correct runtime, run <cite>samples.exe</cite> and verify that it now appears to work.  If it still gives the same message, you didn't install the correct runtime. If it opens a colourful window, then it is now verified as working for you.</li>
<li>If it is not verified as working for you, you can open a libtcod bug report [https://bitbucket.org/libtcod/libtcod/issues?status=new&status=open on BitBucket].
</ol>
 
You've verified that libtcod is working?  Great!  You can move onto the next section.
 
==== Your project folder ====
 
Now create your project's folder. Inside it, create an empty file with a name of your choice.  We're using ''firstrl.py''.  It'll make the tutorial easier to just use the same name, and you can always rename it later.
 
[[File:Windows-01-162-project-folder-empty.png|center]]
 
From within the libtcod build you downloaded above, select and copy the relevant DLLs.
 
[[File:Windows-02-162-dlls.png|center]]
 
From within the libtcod build you downloaded above, select and copy the ''libtcodpy'' folder.
 
[[File:Windows-03-162-libtcodpy-folder.png|center]]
 
From within the libtcod build you downloaded above, select and copy the ''arial10x10.png'' font.  If you choose a different one, you'll need to adjust how you do the tutorial accordingly.  It's simpler to just use the same one as we do, and change it later, if it suits you to do so.
 
[[File:Windows-04-162-font.png|center]]
 
At this point, you should have a folder that looks like this:
 
[[File:Windows-05-162-project-folder-ready.png|center]]
 
You're ready to start editing ''firstrl.py''!
 
=== Autotools platforms (Linux, MacOS, ...) ===
 
==== Python ====
 
The tutorial is written for Python 3, so ensure you have it installed and know what it is called.
 
==== SDL2 ====
 
If your OS or OS distribution has a package that can be used for SDL2 development, then the best option is to install it.  This will ensure that you can easily update it.
 
In the event that you cannot find a package, you can compile it yourself as a last resort.  To do so, download the supported SDL2 revision, build and install it:
 
<syntaxhighlight lang="bash">
$ curl -o sdl.tar.gz http://hg.libsdl.org/SDL/archive/007dfe83abf8.tar.gz
$ tar -xf sdl.tar.gz
$ cd SDL-007dfe83abf8/
$ mkdir -p build
$ cd build
$ ../configure
$ make
$ sudo make install
</syntaxhighlight>
 
This will place the libraries at `/usr/local/lib/` and the development headers at `/usr/local/include/SDL2/`.
 
==== libtcod ====
 
Download the latest libtcod release and compile it:
 
<syntaxhighlight lang="bash">
$ wget https://bitbucket.org/libtcod/libtcod/downloads/20161228-libtcod-1.6.2.tbz2
$ tar xf 20161228-libtcod-1.6.2.tbz2
$ cd 20161228-libtcod-1.6.2
 
$ cd build/autotools/
$ autoreconf -i
$ ./configure
$ make
$ cd ../..
</syntaxhighlight>
 
Now you should be in the root libtcod directory. You can see the compiled library like:
 
<syntaxhighlight lang="bash">
$ find . -name "libtcod.so"
./build/autotools/.libs/libtcod.so
</syntaxhighlight>
 
However, notice this .so is actually a link to this other .so.0.0.0 file in the same directory:
 
<syntaxhighlight lang="bash">
$ readlink ./build/autotools/.libs/libtcod.so
libtcod.so.0.0.0
</syntaxhighlight>
 
==== Your project folder ====
 
Now you're ready to make a directory for your new project, copy the compiled library and links, plus the python library and a font into it:
 
<syntaxhighlight lang="bash">
$ mkdir -p ~/tutorial
$ cp -dr build/autotools/.libs/libtcod.so* python/libtcodpy data/fonts/arial10x10.png ~/tutorial/
$ cd ~/tutorial
</syntaxhighlight>
 
You should be able to see something similar to this:


<syntaxhighlight lang="bash">
== Download a font ==
$ ls -lgo
You will also need to download a font file. libtcod does not transform standard .ttf file fonts into something usable. Instead, we must use what's called a bitmap format. As a consequence, the font displayed is static - you will not be able to increase or decrease the size of the font on the screen without picking a new font file. You may find these font files in a variety of [https://github.com/HexDecimal/python-tdl/raw/master/fonts/libtcod/ locations], including the [https://bitbucket.org/libtcod/libtcod/src/c5a397b8f50a5409da26c0025a11f090bb842f73/data/fonts/?at=default bitbucket repo] we downloaded within [[Complete Roguelike Tutorial, using python3+libtcod, part 0|step 0]].
total 1388
-rw-rw-r-- 1  14870 Dec 31 15:57 arial10x10.png
drwxrwxr-x 2    4096 Dec 31 15:57 libtcodpy
lrwxrwxrwx 1      16 Dec 31 15:57 libtcod.so -> libtcod.so.0.0.0
lrwxrwxrwx 1      16 Dec 31 15:57 libtcod.so.0 -> libtcod.so.0.0.0
-rwxrwxr-x 1 1413856 Dec 31 15:57 libtcod.so.0.0.0
</syntaxhighlight>


Now you're ready to start.
For this tutorial, it is recommended that you download [https://bytebucket.org/libtcod/libtcod/raw/c5a397b8f50a5409da26c0025a11f090bb842f73/data/fonts/arial10x10.png arial10x10.png]. You will want to make sure that this font file is found within the same folder as the script we're creating within this tutorial. For those on a posix system, please do not use `curl` or `wget` as this has caused problems in the past. Instead, browse to the font and then save-as to the proper folder.
 
== 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.
 
[[Complete Roguelike Tutorial, using Python3+libtcod, extras#A neat Python shortcut for Notepad++|See this page]] 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.
 
Another quick note, if you're using IDLE. It doesn't seem to let libtcod clean up properly on exit, so it crashes when you end a script. This seems to be more of an IDLE problem than a libtcod problem so there's no fix in sight, for now. Using any of the setups recommended above will work just fine.


For more information on choosing a font, the author of Cogmind has done a great in-depth explanation [http://www.gridsagegames.com/blog/2014/09/fonts-in-roguelikes/ here].


== Showing the @ on screen ==
== Showing the @ on screen ==
Line 152: Line 22:
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!
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. The name ''libtcodpy'' is a bit funky (sorry Jice!) so we'll rename it to just ''libtcod''.
First we import the library. The name ''libtcodpy'' is a bit funky (sorry Jice!) so we'll rename it to just ''tcod'', which is easier to type.
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">import libtcodpy as tcod</syntaxhighlight></div>
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">import libtcodpy as libtcod</syntaxhighlight></div>




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.
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.
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">SCREEN_WIDTH = 80
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">SCREEN_WIDTH = 80
SCREEN_HEIGHT = 50
SCREEN_HEIGHT = 50
Line 166: Line 33:


Now, something libtcod-specific: we're going to use a custom font! It's pretty easy. libtcod comes bundled with a few fonts that are usable right out of the box. 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", most fonts are in this format and thus end with ''_gs_tc''. If you wanna use a font with a different layout or make your own, the [http://roguecentral.org/doryen/data/libtcod/doc/1.5.1/html2/console_set_custom_font.html?c=false&cpp=false&cs=false&py=true&lua=false docs on the subject] are really informative. You can worry about that at a later time though. Notice that the size of a font is automatically detected.
Now, something libtcod-specific: we're going to use a custom font! It's pretty easy. libtcod comes bundled with a few fonts that are usable right out of the box. 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", most fonts are in this format and thus end with ''_gs_tc''. If you wanna use a font with a different layout or make your own, the [http://roguecentral.org/doryen/data/libtcod/doc/1.5.1/html2/console_set_custom_font.html?c=false&cpp=false&cs=false&py=true&lua=false docs on the subject] are really informative. You can worry about that at a later time though. Notice that the size of a font is automatically detected.
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">
 
font_path = 'arial10x10.png' # this will look in the same folder as this script
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)</syntaxhighlight></div>
font_flags = tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD # the layout may need to change with a different font file
tcod.console_set_custom_font(font_path, font_flags)
</syntaxhighlight></div>




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.
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.
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">
 
window_title = 'Python 3 libtcod tutorial'
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'python/libtcod tutorial', False)</syntaxhighlight></div>
fullscreen = False
tcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, window_title, fullscreen)</syntaxhighlight>
</div>




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.)
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.)
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #D0FFC2"><syntaxhighlight lang="python">tcod.sys_set_fps(LIMIT_FPS)</syntaxhighlight></div>
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #D0FFC2"><syntaxhighlight lang="python">libtcod.sys_set_fps(LIMIT_FPS)</syntaxhighlight></div>




Now the main loop. It will keep running the logic of your game as long as the window is not closed.
Now the main loop. It will keep running the logic of your game as long as the window is not closed.
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">while not tcod.console_is_window_closed():</syntaxhighlight></div>
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">while not libtcod.console_is_window_closed():</syntaxhighlight></div>




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. Here we're setting the text color to be white. [http://roguecentral.org/doryen/data/libtcod/doc/1.5.1/html2/color.html?c=false&cpp=false&cs=false&py=true&lua=false There's a good list of colors you can use here], along with some info about mixing them and all that. The zero is the console we're printing to, in this case the screen; more on that later.
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. Here we're setting the text color to be white. [http://roguecentral.org/doryen/data/libtcod/doc/1.5.1/html2/color.html?c=false&cpp=false&cs=false&py=true&lua=false There's a good list of colors you can use here], along with some info about mixing them and all that. The zero is the console we're printing to, in this case the screen; more on that later.
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">    tcod.console_set_default_foreground(0, tcod.white)</syntaxhighlight></div>
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">    libtcod.console_set_default_foreground(0, libtcod.white)</syntaxhighlight></div>




Line 197: Line 63:


Now print a character to the coordinates (1,1). Once more the first zero specifies the console, which is the screen in this case. Can you guess what that character is? No, it doesn't move yet!
Now print a character to the coordinates (1,1). Once more the first zero specifies the console, which is the screen in this case. Can you guess what that character is? No, it doesn't move yet!
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">    tcod.console_put_char(0, 1, 1, '@', tcod.BKGND_NONE)</syntaxhighlight></div>


<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">    libtcod.console_put_char(0, 1, 1, '@', libtcod.BKGND_NONE)</syntaxhighlight></div>




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.
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.
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">    tcod.console_flush()</syntaxhighlight></div>
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">    libtcod.console_flush()</syntaxhighlight></div>




Ta-da! You're done. Run that code and give yourself a pat on the back!
Ta-da! You're done. Run that code and give yourself a pat on the back!


== Common Bugs ==
<center><table border="0" cellpadding="10" cellspacing="0" style="background:#F0E68C" width="65%"><tr><td><center>
<center><table border="0" cellpadding="10" cellspacing="0" style="background:#F0E68C" width="65%"><tr><td><center>
<b>Common reasons the code won't run.</b></center>
<b>Common reasons the code won't run.</b></center>


* Python errors? Using Python 3?<br/><i>This tutorial is only for Python 2.  See our [http://www.roguebasin.com/index.php?title=Complete_Roguelike_Tutorial,_using_python%2Blibtcod,_part_1&action=edit Python 3 tutorial.]</i>
* Python errors? Using Python 2?<br/><i>This tutorial is only for Python 3.  See our [[Complete Roguelike Tutorial, using python+libtcod|Python 2 tutorial]].</i>
* On Windows? Getting the "Incompatible architecture" error?<br/><i>Make sure your Python and libtcod are either BOTH 32 bit, or BOTH 64 bit.</i>
* On Windows? Getting the "Incompatible architecture" error?<br/><i>Make sure your Python and libtcod are either BOTH 32 bit, or BOTH 64 bit.</i>
* On Windows? Getting the "The specified module could not be found" error?<br/><i>You didn't verify that libtcod was working.  Go [[#Check_if_libtcod_works|back and do it]]</i>
* On Windows? Getting the "The specified module could not be found" error?<br/><i>You didn't verify that libtcod was working.  Go [[Complete Roguelike Tutorial, using python3+libtcod, setup Windows#Validating the setup|back and do it]]</i>
* Still blocked for other reasons?  Check out the [[Complete_Roguelike_Tutorial,_using_Python+libtcod,_problems|problems page]].
* Still blocked for other reasons?  Check out the [[Complete_Roguelike_Tutorial,_using_Python3+libtcod, _problems|problems page]].
</td></tr></table></center>
</td></tr></table></center>


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.
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.


[[Complete Roguelike Tutorial, using python+libtcod, part 1 code#Showing the @ on screen|Here]]'s the complete code so far.
[[Complete Roguelike Tutorial, using python3+libtcod, part 1 code#Showing the @ on screen|Here]]'s the complete code so far.


== Moving around ==
== Moving around ==
Line 227: Line 91:
That was pretty neat, huh? Now we're going to move around that @ with the keys!
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.
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.
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">player_x = SCREEN_WIDTH // 2
player_y = SCREEN_HEIGHT // 2</syntaxhighlight></div>


 
<div style="padding: 5px; border: solid 1px #000000; background-color: #F0F0D0"><small>'''''Notes:
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">playerx = SCREEN_WIDTH/2
* The screen coordinates start at the top left corner (0, 0) and end at the bottom right corner (SCREEN_WIDTH, SCREEN_HEIGHT)
playery = SCREEN_HEIGHT/2</syntaxhighlight></div>
* Python 3 has two types of division: "/" and "//".  The former will produce a floating point number (e.g. 1.0, 2.0, 3.0) while the latter will produce an integer (e.g. 1, 2, 3).  The libtcod library is explicitly expecting an integer, so we will make sure that Python produces an integer for the player's (x, y) coordinates.
'''''</small></div>




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.
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.
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">def handle_keys():
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">def handle_keys():
     global playerx, playery
     global player_x, player_y


     #movement keys
     # movement keys
     if libtcod.console_is_key_pressed(libtcod.KEY_UP):
     if tcod.console_is_key_pressed(tcod.KEY_UP):
         playery -= 1
         player_y = player_y - 1


     elif libtcod.console_is_key_pressed(libtcod.KEY_DOWN):
     elif tcod.console_is_key_pressed(tcod.KEY_DOWN):
         playery += 1
         player_y = player_y + 1


     elif libtcod.console_is_key_pressed(libtcod.KEY_LEFT):
     elif tcod.console_is_key_pressed(tcod.KEY_LEFT):
         playerx -= 1
         player_x = player_x - 1


     elif libtcod.console_is_key_pressed(libtcod.KEY_RIGHT):
     elif tcod.console_is_key_pressed(tcod.KEY_RIGHT):
         playerx += 1</syntaxhighlight></div>
         player_x = player_x + 1</syntaxhighlight></div>




Line 257: Line 122:


While we're at it, why not include keys to toggle fullscreen mode, and exit the game? You can put this at the beginning of the function.
While we're at it, why not include keys to toggle fullscreen mode, and exit the game? You can put this at the beginning of the function.
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">
     key = libtcod.console_check_for_keypress()
     key = tcod.console_check_for_keypress()
     if key.vk == libtcod.KEY_ENTER and key.lalt:
     if key.vk == tcod.KEY_ENTER and key.lalt:
         #Alt+Enter: toggle fullscreen
         #Alt+Enter: toggle fullscreen
         libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
         tcod.console_set_fullscreen(not tcod.console_is_fullscreen())


     elif key.vk == libtcod.KEY_ESCAPE:
     elif key.vk == tcod.KEY_ESCAPE:
         return True  #exit game</syntaxhighlight></div>
         return True  #exit game</syntaxhighlight></div>


Line 274: Line 137:


Now here's an important thing: you can use that first line to distinguish between real-time and turn-based gameplay! See, ''console_check_for_keypress'' won't block the game. But if you replace it with this line:
Now here's an important thing: you can use that first line to distinguish between real-time and turn-based gameplay! See, ''console_check_for_keypress'' won't block the game. But if you replace it with this line:
 
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #DFEEFF"><syntaxhighlight lang="python">    key = tcod.console_wait_for_keypress(True)</syntaxhighlight></div>
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #DFEEFF"><syntaxhighlight lang="python">    key = libtcod.console_wait_for_keypress(True)</syntaxhighlight></div>




Line 281: Line 143:


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:
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:
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">
     libtcod.console_set_default_foreground(0, libtcod.white)
     tcod.console_set_default_foreground(0, tcod.white)
     libtcod.console_put_char(0, playerx, playery, '@', libtcod.BKGND_NONE)
     tcod.console_put_char(0, player_x, player_y, '@', tcod.BKGND_NONE)


     libtcod.console_flush()
     tcod.console_flush()


     #handle keys and exit game if needed
     #handle keys and exit game if needed
Line 298: Line 158:


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 = handle_keys()''.
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 = handle_keys()''.
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">        tcod.console_put_char(0, player_x, player_y, ' ', tcod.BKGND_NONE)</syntaxhighlight></div>


== Finishing Touches ==
[[Complete Roguelike Tutorial, using python3+libtcod, part 1 code#Moving around|Here]]'s a rundown of the whole code so far.


<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">        libtcod.console_put_char(0, playerx, playery, ' ', libtcod.BKGND_NONE)</syntaxhighlight></div>
[[Complete Roguelike Tutorial, using python3+libtcod, part 2|Go on to the next part]].
 
 
[[Complete Roguelike Tutorial, using python+libtcod, part 1 code#Moving around|Here]]'s a rundown of the whole code so far.
 
[[Complete Roguelike Tutorial, using python+libtcod, part 2|Go on to the next part]].


[[Category:Developing]]
[[Category:Developing]]

Latest revision as of 01:31, 30 March 2018

This is part of a series of tutorials; the main page can be found here.

Graphics

Setting it up

See Part 0.

Download a font

You will also need to download a font file. libtcod does not transform standard .ttf file fonts into something usable. Instead, we must use what's called a bitmap format. As a consequence, the font displayed is static - you will not be able to increase or decrease the size of the font on the screen without picking a new font file. You may find these font files in a variety of locations, including the bitbucket repo we downloaded within step 0.

For this tutorial, it is recommended that you download arial10x10.png. You will want to make sure that this font file is found within the same folder as the script we're creating within this tutorial. For those on a posix system, please do not use `curl` or `wget` as this has caused problems in the past. Instead, browse to the font and then save-as to the proper folder.

For more information on choosing a font, the author of Cogmind has done a great in-depth explanation here.

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. The name libtcodpy is a bit funky (sorry Jice!) so we'll rename it to just tcod, which is easier to type.

import libtcodpy as tcod


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, something libtcod-specific: we're going to use a custom font! It's pretty easy. libtcod comes bundled with a few fonts that are usable right out of the box. 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", most fonts are in this format and thus end with _gs_tc. If you wanna use a font with a different layout or make your own, the docs on the subject are really informative. You can worry about that at a later time though. Notice that the size of a font is automatically detected.

font_path = 'arial10x10.png'  # this will look in the same folder as this script
font_flags = tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD  # the layout may need to change with a different font file
tcod.console_set_custom_font(font_path, font_flags)


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.

window_title = 'Python 3 libtcod tutorial'
fullscreen = False
tcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, window_title, fullscreen)


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.)

tcod.sys_set_fps(LIMIT_FPS)


Now the main loop. It will keep running the logic of your game as long as the window is not closed.

while not tcod.console_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. Here we're setting the text color to be white. There's a good list of colors you can use here, along with some info about mixing them and all that. The zero is the console we're printing to, in this case the screen; more on that later.

    tcod.console_set_default_foreground(0, tcod.white)


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.

Now print a character to the coordinates (1,1). Once more the first zero specifies the console, which is the screen in this case. Can you guess what that character is? No, it doesn't move yet!

    tcod.console_put_char(0, 1, 1, '@', tcod.BKGND_NONE)


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.

    tcod.console_flush()


Ta-da! You're done. Run that code and give yourself a pat on the back!

Common Bugs

Common reasons the code won't run.
  • Python errors? Using Python 2?
    This tutorial is only for Python 3. See our Python 2 tutorial.
  • On Windows? Getting the "Incompatible architecture" error?
    Make sure your Python and libtcod are either BOTH 32 bit, or BOTH 64 bit.
  • On Windows? Getting the "The specified module could not be found" error?
    You didn't verify that libtcod was working. Go back and do it
  • Still blocked for other reasons? Check out the problems page.

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.

Moving around

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.

player_x = SCREEN_WIDTH // 2
player_y = SCREEN_HEIGHT // 2
Notes:
  • The screen coordinates start at the top left corner (0, 0) and end at the bottom right corner (SCREEN_WIDTH, SCREEN_HEIGHT)
  • Python 3 has two types of division: "/" and "//". The former will produce a floating point number (e.g. 1.0, 2.0, 3.0) while the latter will produce an integer (e.g. 1, 2, 3). The libtcod library is explicitly expecting an integer, so we will make sure that Python produces an integer for the player's (x, y) coordinates.


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 player_x, player_y

    # movement keys
    if tcod.console_is_key_pressed(tcod.KEY_UP):
        player_y = player_y - 1

    elif tcod.console_is_key_pressed(tcod.KEY_DOWN):
        player_y = player_y + 1

    elif tcod.console_is_key_pressed(tcod.KEY_LEFT):
        player_x = player_x - 1

    elif tcod.console_is_key_pressed(tcod.KEY_RIGHT):
        player_x = player_x + 1


Done! These are the arrow keys, if you want to use other keys here's a reference (pay attention to the Python-specific notes).

While we're at it, why not include keys to toggle fullscreen mode, and exit the game? You can put this at the beginning of the function.

    key = tcod.console_check_for_keypress()
    if key.vk == tcod.KEY_ENTER and key.lalt:
        #Alt+Enter: toggle fullscreen
        tcod.console_set_fullscreen(not tcod.console_is_fullscreen())

    elif key.vk == tcod.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.

Notice a subtle difference here. The console_is_key_pressed function is useful for real-time games, since it checks if a key is pressed with no delays. console_check_for_keypress, on the other hand, treats the key like it's being typed. So after the first press, it will stop working for a fraction of a second. This is the same behavior you see when you type, otherwise pressing a key would result in you typing 3 or 4 letters! It's useful for all commands except movement, which you usually want to react as soon as possible with no delays, and continue for as long as you press the movement keys.

Now here's an important thing: you can use that first line to distinguish between real-time and turn-based gameplay! See, console_check_for_keypress won't block the game. But if you replace it with this line:

    key = tcod.console_wait_for_keypress(True)


Then the game won't go on unless the player presses a key. So effectively you have a turn-based game now.

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:

    tcod.console_set_default_foreground(0, tcod.white)
    tcod.console_put_char(0, player_x, player_y, '@', tcod.BKGND_NONE)

    tcod.console_flush()

    #handle keys and exit game if needed
    exit = handle_keys()
    if exit:
        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 = handle_keys().

        tcod.console_put_char(0, player_x, player_y, ' ', tcod.BKGND_NONE)

Finishing Touches

Here's a rundown of the whole code so far.

Go on to the next part.