Chapter 2. Calling C from Lua

C is the lingua franca of programming languages. Anything your system can do—such as access the internet, play an audio file, or generate real-time GPU-based 3D graphics—you can do via C code linked with the right libraries. Lua takes advantage of C’s universality by making it easy to share C functionality with Lua code. That’s what we’ll do in this chapter.

The example code in this chapter is split across two pairs of files. The first pair contains the files eatyguy1.c and eatyguy1.lua, whereas the second pair, in a rather predictable manner, contains the files eatyguy2.c and eatyguy2.lua. Thus continues the saga of the EatyGuy example code, which will grow iteratively throughout the remainder of this book, consistently with filenames that begin with eatyguy<num>. I hope this naming scheme makes it easier for you to see where each iteration fits into the big picture. It also reflects the typical iterative nature of software projects.

EatyGuy Version 1: A Lua-Callable C Function

Let’s begin by giving EatyGuy the ability to draw text characters with different background colors. We’ll write C code that creates the function listed in Table 2-1 in Lua.

Table 2-1. Description of the set_color() Lua function that we’ll implement in C
Lua function
set_color(<color_index>) Change the terminal’s current background color to <color_index>. Text printed out after this function is run will be printed with the given background color.

The tput Shell Command

Most terminals support at least eight colors, and some support an astonishing 256 colors. How many colors your terminal supports depends on which terminal application you use, and is codified in the value of your $TERM environment variable. This variable maps to a precise set of terminal capabilities via a database called terminfo; if you want to learn more about it, in your shell, type man terminfo.

Luckily for us, there is an incredibly handy little shell command called tput that allows us to blithely forge ahead using terminal capabilities while knowing next to nothing about your $TERM value or the terminfo database. The only assumption I’m making is that your terminal supports at least eight colors, which you can check by running tput colors in your shell. The result should be a number greater than or equal to eight.

In general, tput works by accepting command-line parameters in the following form:

tput <command> [command args]

Depending on the value of <command> and the optional arguments after it, tput will send a specific string to stdout. In many cases, you won’t see this string because it’s composed of a special sequence of bytes that the terminal application knows to ingest and interpret as a change in behavior, such as changing the color of all subsequently printed text to red (this particular change is realized with the command tput setaf 1). In theory, any program can print out those special byte sequences to achieve the same effects; the value of tput is that it knows how to speak the languages of many different types of terminals, much like a well-made protocol droid.

Table 2-2 lists the tput commands that the code in this book uses.

Table 2-2. Summary of the tput commands that we’ll use in the example code
tput command Description
tput setab <color> Set the terminal background color to <color>, which is expected to be an integer in the range [0, #colors – 1], where #colors is the number of colors supported by your terminal.
tput setaf <color> Set the terminal foreground color to <color>, which is expected to be an integer in the range [0, #colors – 1].
tput cup <y> <x> Move the terminal’s cursor to position (<x>, <y>). The upper-left corner of your terminal window is position (0, 0).
tput reset Reset terminal colors to their default values, clear the window, and return the cursor to the upper-left of the window. This is useful if you’ve gotten yourself into a weird state and can’t read what you’re typing or find the cursor.

Figure 2-1 demonstrates the default eight colors as displayed on my terminal by the short bash function in the image.

The default eight colors supported by tput. Color 7 is white, so the text blends in with the background in the color bar
Figure 2-1. The default eight colors supported by tput; color 7 is white, so the text blends in with the background in the color bar

If you are wealthy enough to afford a top-end 256-color terminal, you might extend the preceding bash function to see a color table like the one shown in Figure 2-2.

The default color palette for a 256-color terminal.
Figure 2-2. The default color palette for a 256-color terminal

However, this book is not written for the aristocracy, so the code herein will work with the traditional eight colors available to the masses.

The Core Mechanics of Lua-Callable C Functions

C’s system() function can call shell commands like tput. Here is the C code for the set_color() function:

// This function will be part of eatyguy1.c.
// It requires us to #include <stdlib.h>.

// The C version of Lua's set_color(<color>) function.
// Set the terminal background color to <color>.
int set_color(lua_State *L) {
  int color = lua_tonumber(L, 1);
  char cmd[1024];
  snprintf(cmd, 1024, "tput setab %d", color);
  system(cmd);
  return 0;  // 0 is the number of Lua-visible return values.
}

Table 2-3 descibes the new lua_tonumber() function.

Table 2-3. The new C API function, lua_tonumber(), used to implement set_color()
C API function Description Stack use
lua_tonumber(L, <index>) Convert the Lua stack value at <index> into the C type lua_Number and return that value. Numbers and appropriately formatted strings can be converted, but other values or empty stack slots can’t, in which case 0 is returned. [–0, +0]

This function provides visibility into a value on the Lua stack. The lua_Number type is typically the same as C’s double type, although in some cases it can be a float or another type. The exact mapping is defined in the file luaconf.h, which is meant to be somewhat user-editable so that you can customize Lua to work well on various platforms. For learning purposes, you can safely treat the lua_Number type as if it were double.

Notice that the set_color() function accepts a single lua_State pointer as input and returns an int. Every Lua-callable function written in C has exactly this input type and output type. The list of input and output types of a function are called the signature of that function. Armed with this terminology, we can succinctly state that every Lua-callable C function must have the same signature.

The lua_State provides all of set_color()’s Lua inputs by placing them in order on the stack. Nothing else will be on the stack when Lua makes the call into your function—only the Lua arguments given by the caller when calling your function. Passing in arguments on the stack allows C functions to accept an arbitrary number of input values, as is standard in Lua but not C. The output value of set_color()—the int—indicates how many return values you want to send back to Lua. Normally, C functions can have zero or one return values, whereas Lua functions can have arbitrarily many. If your C function returns the integer n, the top n values on the Lua stack when the function ends are the return values visible from Lua. If the stack has too many values, the bottom-most values are ignored; if the stack has too few values, nil values are returned in place of the missing values.

Let’s see how these mechanisms work for the set_color() function. The function begins by examining the value in stack location 1, which represents the first argument passed in. If that value were 5, it would make the system call tput setab 5, setting the background color to 5. The C function ends by returning 0, meaning that there are no return values visible in Lua.

We’ll see more interesting examples of Lua-callable C functions later on in this chapter. Before that, there’s one more critical step required to make a C function visible to Lua. Specifically, Lua needs to have a name for the function that it can call. Here is the file eatyguy1.c, which you can see in Chapter 1 as runmodule.c, modified to include the new set_color() function; note the three new lines in bold:

// eatyguy1.c
//
// Loads eatyguy1.lua and runs eatyguy.init().
//

#include <stdlib.h>

#include "lauxlib.h"
#include "lua.h"
#include "lualib.h"


// Lua-visible functions.

int set_color(lua_State *L) {
  int color = lua_tonumber(L, 1);
  char cmd[1024];
  snprintf(cmd, 1024, "tput setab %d", color);
  system(cmd);
  return 0;
}


// Main.

int main() {

  // Create a Lua state and load the module.
  lua_State *L = luaL_newstate();
  luaL_openlibs(L);
  luaL_dofile(L, "eatyguy1.lua");
  lua_setglobal(L, "eatyguy");
  lua_settop(L, 0);

  // Make the set_color function visible to Lua.
  lua_pushcfunction(L, set_color);
  lua_setglobal(L, "set_color");

  // Run the init() function.
  lua_getglobal(L, "eatyguy");
  lua_getfield(L, -1, "init");  // -1 means stack top.
  lua_call(L, 0, 0);            // 0, 0 = #args, #retvals

  return 0;
}

The filename passed to luaL_dofile(), which was previously eatyguy0.lua, is now eatyguy1.lua. In general, every version of eatyguy<n>.c will contain a call to luaL_dofile() that loads the corresponding eatyguy<n>.lua file (e.g., eatyguy3.c will load eatyguy3.lua and not eatyguy2.lua).

The lua_setglobal() function is covered in Chapter 1, but lua_pushcfunction() (Table 2-4) is new.

Table 2-4. The new C API function, lua_pushcfunction(), used in eatyguy1.c
C API function Description Stack use
lua_pushcfunction(L, <fn>) Push the function <fn> onto the top of the stack. [–0, +1]

The combo of lua_pushcfunction() followed by lua_setglobal() works like this pseudocode:

<the lua global "set_color"> = <the C function "set_color">

When that’s done, the C code can safely call a Lua function that itself calls set_color(). In fact, now is the perfect time to write eatyguy1.lua, a file that builds on eatyguy0.lua, to take advantage of our fun new function.

In Chapter 1, the walls of a maze were drawn as # characters, and the open spaces were drawn as space characters. This time around, walls will be blue space characters and open spaces will be black space characters. Figure 2-3 shows the result.

An example output from eatyguy1.
Figure 2-3. An example output from eatyguy1

The only part of eatyguy1.lua that needs to differ from eatyguy0.lua is the maze-drawing code. Here’s the updated portion, with the key lines in bold:

  -- Part of eatyguy1.lua

  -- Draw the maze.
  for y = 0, grid_h + 1 do
    for x = 0, grid_w + 1 do
      if grid[x] and grid[x][y] then
        set_color(0)                 -- Black; open space color.
      else
        set_color(4)                 -- Blue; wall color.
      end
      io.write('  ')
      io.flush()                     -- Needed for color output.
    end
    set_color(0)                     -- End the lines in black.
    io.write(' 
')                -- Move cursor to next row.
  end
end

I found out through bitter, bitter experience that the call to io.flush() was necessary on my terminal in order to correctly draw the maze. Without flushing the output of the io.write() calls, the terminal might not see the space characters until an output buffer is triggered to send the characters to the terminal, but the tput commands are always immediately sent. In other words, the terminal would receive the color-changing signals from the tput calls, but they would not be correctly interwoven with the space characters, so that the color changes might not be correctly synchronized with the corresponding spaces. Calling io.flush() effectively turns off output buffering and fixes this issue.

Your terminal colors might end up in a funky state after you run eatyguy1. To recover from this, simply run the reset command. (This will work even if you find yourself typing black characters on a black background!) The reset command clears your terminal screen and restores what it considers to be your terminal’s default colors. Future versions of EatyGuy avoid this issue by cleaning up after themselves—much more polite!

EatyGuy Version 2: Adding an Animated Player

Next, we’ll build the following three C-based, Lua-callable functions with which we can draw an animated player that moves around the maze. The new version of set_color() shown in Table 2-5 is different in that it can set either a background or foreground color, whereas the old version could only set a background color.

Table 2-5. A new set of Lua functions that we’ll implement in C
Lua function Description
set_color(’b’ or ’f’, <color>) If the first parameter is ’b’, set the background color to <color>. If the first parameter is ’f’, set the foreground color, instead.
set_pos(<x>, <y>) Move the cursor to position (<x>, <y>). The upper-left corner of the window is considered position (0, 0).
timestamp() Return a high-resolution timestamp in seconds.

These functions demonstrate how C code can work with strings on the Lua stack as well as how to return values from a C function back to Lua.

Strings and Multiple Arguments with set_color() and set_pos()

The new and improved set_color() function now accepts a first argument to determine whether to change the foreground or background color:

// Part of eatyguy2.c.

// Lua: set_color('b' or 'f', <color>)
int set_color(lua_State *L) {
  const char *b_or_f = lua_tostring(L, 1);
  int color = lua_tonumber(L, 2);
  char cmd[1024];
  snprintf(cmd, 1024, "tput seta%s %d", b_or_f, color);
  system(cmd);
  return 0;
}

The first two lines extract a string and a number from the Lua stack, which are the first two arguments provided to the Lua function call. Note that these two function calls refer to indexes 1 and 2, respectively, in order to extract values from the first two parameters given on the Lua stack.

Table 2-6 presents the new lua_tostring() function.

Table 2-6. The C API function lua_tostring() used to implement the updated set_color() function
C API function Description Stack use
lua_tostring(L, <index>) Convert the stack value at <index> into a string and return a null-terminated C string with the same data. Note that this alters stack values that are not already strings, so it is not strictly a read-only function. The returned string is owned by Lua and is guaranteed to exist only as long as the corresponding stack value remains on the stack. [–0, +0]

It’s useful to understand how Lua treats its memory with respect to the C API. For the most part, Lua controls its memory allocation by not letting you touch its stuff. If you had a direct pointer to a Lua table, for example, the Lua garbage collector would have a difficult time determining when you were done with that pointer. Instead, Lua holds all complex values exclusively on its stack, and provides access to this data in terms of API calls that read and write primitive values to the stack.

The lua_tostring() function is a rare exception to this rule because you do get a pointer into Lua-owned memory. Lua’s memory management still works because such a string comes with clear rules: don’t edit the string, and don’t rely on the string existing after its originating stack value is off the stack. If you break these rules, everything can explode and Lua can legitimately say, “I told you so!” It’s also worth noting that Lua internally treats strings as [pointer, length] pairs so that a Lua string can contain arbitrarily many null bytes (the byte with all bits set to zero), whereas many C functions assume a C string contains no null bytes except for the last character, which must be a null byte. If you want your C code to receive binary data from Lua that might contain null bytes, you can use lua_tolstring() instead of lua_tostring().

Now that we’re basically experts at retrieving numbers from the Lua stack, the set_pos() function is straightforward:

// Part of eatyguy2.c.

// Lua: set_pos(x, y)
int set_pos(lua_State *L) {
  int x = lua_tonumber(L, 1);
  int y = lua_tonumber(L, 2);
  char cmd[1024];
  // The 'tput cup' command accepts y before x; not a typo.
  snprintf(cmd, 1024, "tput cup %d %d", y, x);
  system(cmd);
  return 0;
}

All of our Lua-callable functions so far have ended with return 0;. Let’s change that.

Sending a Return Value from C to Lua by Using timestamp()

Lua has a function called os.time(), which returns the time in seconds. But this isn’t good enough for a game because os.time() provides an integer, meaning that we can tell when time marches forward only in full second intervals. Call me persnickety, but I’ve noticed that good games tend to draw more than one frame per second, so it would be nice to have a similar function that returns the time in seconds, but with at least millisecond resolution. We can use the gettimeofday() function to achieve that:

// Part of eatyguy2.c

// This requires us to #include <sys/time.h>.

double gettime() {
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return tv.tv_sec + 1e-6 * tv.tv_usec;
}

// Lua: timestamp()
// Return a high-resolution timestamp in seconds.
int timestamp(lua_State *L) {
  lua_pushnumber(L, gettime());
  return 1;
}

Did you see that? We have a Lua-callable function that returns 1 instead of 0! Things are getting crazy.

This function works by pushing the current time, theoretically with microsecond resolution, onto the Lua stack, and then returning 1 in C, which indicates that the top 1 value(s) on the stack are to be handed back to the calling Lua function as return value(s).

Table 2-7 introduces a new function, lua_pushnumber(), that handles this.

Table 2-7. The C API function lua_pushnumber() used by timestamp()
C API function Description Stack use
lua_pushnumber(L, <num>) Push <num> onto the top of the stack. [–0, +1]

This function is similar to lua_pushcfunction() in that it takes a C value and pushes the Lua version of it onto the top of the stack. There are multiple variations on this theme; they’re all functions named lua_pushX(), where X can be nil, string, Boolean, or other values.

Fancier Maze Drawing

Now that we’ve updated the functions that Lua can call, including set_color(), we need to upgrade the maze-drawing code that was used in eatyguy1.lua to something a little more sophisticated in eatyguy2.lua. This upgrade reflects the fact that set_color() now accepts two parameters, and that the values in grid will become two-character sequences chosen to graphically represent either a dot that can be eaten or open space (in a moment, we’ll examine other code changes related to the values in grid). Here’s the updated maze-drawing code, which resides inside the eatyguy.init() method, with changes in bold:

  -- Draw the maze.
  set_color('f', 7)                  -- White foreground.
  for y = 0, grid_h + 1 do
    for x = 0, grid_w + 1 do
      if grid[x] and grid[x][y] then
        set_color('b', 0)            -- Black; open space color.
        io.write(grid[x][y])
      else
        set_color('b', 4)            -- Blue; wall color.
        io.write('  ')
      end
      io.flush()                     -- Needed for color output.
    end
    set_color('b', 0)                -- End the lines in black.
    io.write(' 
')                -- Move cursor to next row.
  end

Starting, Stopping, and Sharing Functions with Lua

The demo we’re building works by means of a run loop, meaning that it will execute until we break out of it. Because the code calls system() often and will run in a potentially infinite loop, it’s only polite to make it easier for the user to cleanly exit. Along those lines, we’ll put together C functions called start() and done(), and provide a new main() function that uses them. The code also includes a new getkey() function that can check for keypresses from the user without waiting for them to press anything. (I discuss the workings of getkey() in Chapter 3, when we talk more about getting input.) This code is an improvement built on the previous eatyguy1.c code. Again, the differences are in bold:

// This code is part of the file eatyguy2.c, which is the same
// as eatyguy1.c except for lines in bold.

// This requires us to #include <fcntl.h> and <unistd.h>.

// 27 is the decimal representation of Esc in ASCII.
#define ESC_KEY 27

int getkey() {

  // Make reading from stdin nonblocking.
  int flags = fcntl(STDIN_FILENO, F_GETFL);
  fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);

  int ch = getchar();

  // Turn off nonblocking I/O. On some systems, leaving stdin
  // nonblocking will also leave stdout nonblocking, which
  // can cause printing errors.
  fcntl(STDIN_FILENO, F_SETFL, flags);
  return ch;
}

void start() {

  // Terminal setup.
  system("tput clear");      // Clear the screen.
  system("tput civis");      // Hide the cursor.
  system("stty raw -echo");  // Improve access to keypresses.
}

void done() {

  // Put the terminal back into a decent state.
  system("stty cooked echo");  // Undo init call to "stty raw".
  system("tput reset");        // Reset colors and clear screen.

  exit(0);
}

int main() {

  start();

  // Create a Lua state and load the module.
  lua_State *L = luaL_newstate();
  luaL_openlibs(L);

  // Make our Lua-callable functions visible to Lua.
  lua_register(L, "set_color", set_color);
  lua_register(L, "set_pos",   set_pos);
  lua_register(L, "timestamp", timestamp);

  // Load eatyguy2.lua and run the init() function.
  luaL_dofile(L, "eatyguy2.lua");
  lua_setglobal(L, "eatyguy");
  lua_settop(L, 0);
  lua_getglobal(L, "eatyguy");
  lua_getfield(L, -1, "init");  // -1 means stack top.
  lua_call(L, 0, 0);            // 0, 0 = #args, #retvals

  lua_getglobal(L, "eatyguy");
  while (1) {
    // Call eatyguy.loop().
    lua_getfield(L, -1, "loop");
    lua_call(L, 0, 0);

    int c = getkey();
    if (c == ESC_KEY || c == 'q' || c == 'Q') done();
  }

  return 0;
}

The lua_register() function shown in Table 2-8 is a slightly shorter version of the lua_pushcfunction()/lua_setglobal() combo that was used in eatyguy1.c.

Table 2-8. The C API function lua_register()
C API function Description Stack use
lua_register(L, <lua_name>, <c_fn>) Push the C function <c_fn> onto the stack, set the value of the Lua global variable <lua_name> equal to this <c_fn>, and remove <c_fn> from the stack. [–0, +0]

The start() and done() functions set up and tear down the terminal for use as a game interface. It’s nice to make stdin nonblocking so that calls to getchar() return immediately, even when no key has been pressed. This enables the program to continuously draw new frames and still respond immediately when a key is pressed.

The infinite loop in main() operates as follows:

  1. Before the loop begins, the eatyguy table is placed on top of the stack.

  2. eatyguy.loop() is loaded from that table and called.

  3. Because lua_call() removes the function it calls, that function must be reloaded with each iteration of the loop.

  4. If the call to lua_getglobal() had been inside the while loop, we’d overflow the stack and could end up with an error such as a segmentation fault (not that I made that mistake myself or anything…).

To help illustrate a correct use of the stack in this loop, here’s a version of the code with more comments:

  lua_getglobal(L, "eatyguy");
  // stack   = [.., eatyguy]
  while (1) {
    // Call eatyguy.loop().
    lua_getfield(L, -1, "loop");
    // stack = [.., eatyguy, eatyguy.loop]
    lua_call(L, 0, 0);
    // stack = [.., eatyguy]
    ...
  }

Comments like these can be useful when you’re learning about or debugging a series of Lua’s C API calls. Another approach to avoiding stack overflow is to use the lua_gettop() function described in Table 2-9.

Table 2-9. The C API function lua_gettop()
C API function Description Stack use
lua_gettop(L) Return the number of values on the stack. [–0, +0]

If the return value of this function appears to be increasing without bound, your stack is on its way to an overflow. In that case, ensure that your code is designed to use a finite number of stack slots, and look for the point in the code where the stack appears to be growing more than expected.

Game State and Lua’s Event Loop

Let’s write eatyguy2.lua to take advantage of the new functionality provided by C. Previously, the Lua code was called a single time in an init() function. Now, however, there is a new loop() function that will be called until the player quits by pressing the Esc key or the Q key. A common pattern for games is to update the state of the game and then render this state visually. We’ll take that approach:

-- Part of eatyguy2.lua

function eatyguy.loop()
  update()
  draw()
end

We’ll add two kinds of game state. First, there will be a player character that moves within the maze. Second, the maze will initially be filled with tasty dots, and these dots will disappear as the player eats them. I’m getting hungry just thinking about it!

The player state consists of a position, a direction, and a next-direction, indicating which way the player would like to move when he gets a chance to turn. This can be set up like so:

-- Part of eatyguy2.lua

local player = {pos      = {1, 1},
                dir      = {1, 0},
                next_dir = {1, 0}}

The dots can be stored directly in grid. Instead of storing the value 'open' for open spaces, we can store the string '. ' in spaces with a dot, and the string ' ' for spaces where the dot has been eaten. Specifically, the first line of drill_path_from() becomes the following:

grid[x][y] = '. '

Likewise, any other instance of 'open' in the old script also becomes '. ' in this new version.

Updating Player State

Here’s the code to update the player state every time loop() is called:

-- Part of eatyguy2.lua
-- Check whether a character can move in a given direction.
-- Return can_move, new_pos.
local function can_move_in_dir(character, dir)
  local pos = character.pos
  local grid_x, grid_y = pos[1] + dir[1], pos[2] + dir[2]
  local can_move = grid[grid_x] and grid[grid_x][grid_y]
  return can_move, {grid_x, grid_y}
end

local move_delta     = 0.2  -- seconds
local next_move_time = nil

local function update()
  -- Ensure any dot under the player has been eaten.
  local p = player.pos
  grid[p[1]][p[2]] = '  '

  -- Only move every move_delta seconds.
  if next_move_time == nil then
    next_move_time = timestamp() + move_delta
  end
  if timestamp() < next_move_time then return end
  next_move_time = next_move_time + move_delta

  -- Try to change direction; if we can't, next_dir will take
  -- effect at a corner where we can turn in that direction.
  local all_dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}
  if can_move_in_dir(player, player.next_dir) then
    player.dir      = player.next_dir
    player.next_dir = all_dirs[math.random(4)]
  end

  -- Move in direction player.dir if possible.
  local can_move, new_pos = can_move_in_dir(player, player.dir)
  while not can_move do
    player.dir = all_dirs[math.random(4)]
    can_move, new_pos = can_move_in_dir(player, player.dir)
  end
  player.old_pos = player.pos  -- Save the old position.
  player.pos = new_pos
end

The function update() begins by ensuring that the dot the player was on has been eaten by updating the grid[x][y] value at that spot. The move_delta controls how often the player can move to a new position. This value is in seconds, and uses the C-implemented timestamp() function to know how many seconds have elapsed since the last move. Player directions are chosen randomly, but if the player can no longer move in his original direction, the code makes sure to pick a new direction in which he can move. This avoids the player repeatedly walking head-first into a wall.

Drawing the Player State

Some creativity is needed to make ASCII characters look like game characters. We’ll use an apostrophe and a less-than sign to produce our hero, shown in Figure 2-4.

An example rendering of the player in EatyGuy. The less-than sign represents a mouth, and the apostrophe represents an eyeball.
Figure 2-4. An example rendering of the player in EatyGuy; the less-than sign represents a mouth, and the apostrophe represents an eyeball

Here’s the code to draw this character:

-- Part of eatyguy2.lua

local function draw()

  -- Choose the sprite to draw. For example, a right-facing
  -- player is drawn as '< alternated with '-
  local draw_data = {
    [ '1,0'] = {"'<", "'-"},
    ['-1,0'] = {">'", "-'"},
    [ '0,1'] = {"^'", "|'"},
    ['0,-1'] = {"v.", "'."}
  }
  local anim_timestep = 0.2
  local dirkey   = ('%d,%d'):format(player.dir[1],
                                    player.dir[2])
  -- framekey switches between 1 & 2; basic sprite animation.
  local time     = timestamp()
  local framekey = math.floor(time / anim_timestep) % 2 + 1
  local chars    = draw_data[dirkey][framekey]

  -- Draw the player.
  set_color('b', 3)  -- Yellow background.
  set_color('f', 0)  -- Black foreground.
  local x = 2 * player.pos[1]  -- Each tile is 2 chars wide.
  local y =     player.pos[2]
  set_pos(x, y)
  io.write(chars)
  io.flush()

  -- Erase the old player pos if appropriate.
  if player.old_pos then
    local x = 2 * player.old_pos[1]
    local y =     player.old_pos[2]
    set_pos(x, y)
    set_color('b', 0)  -- Black background.
    io.write('  ')
    io.flush()
    player.old_pos = nil
  end
end

The draw_data table contains the ASCII characters chosen to animate the character depending on the direction it’s facing. The player’s mouth opens and closes over time, which is captured by the framekey that alternates between 0 and 1. The set_color() and set_pos() functions are used to render the player in yellow and black as well as to erase the old position of the character if it has moved.

Figure 2-5 depicts the example code in action.

A time-lapse sequence of the player moving through a maze. These are screenshots from eatyguy2, which includes a slightly resized maze.
Figure 2-5. A time-lapse sequence of the player moving through a maze; these are screenshots from eatyguy2, which includes a slightly resized maze

Calling Lua Functions from C More Effectively

In this chapter, we built something startlingly close to a bare-bones API and used that API to build an app that looks rather like a game. One thing that we haven’t done much of is actively pass data from C to Lua—for example, when C calls init() or loop(), it never provides any arguments. We change this in Chapter 3 by adding the ability to control the hero with the arrow keys.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.106.233