28 марта 2010

C + Lua == "Часть 2: Гоблин"

Привет. В прошлой статье С + Lua == "Часть 1: Вводная" я объяснил как вызвать C функцию из Lua и как вызвать Lua функцию из C, теперь мы усложняем себе задачу. Lua хороший язык, но у него есть один большой минус, у него нет такого типа данных как гоблин. К нашему счастью и зависти других, это легко поправимо, мы можем сами добавить этот тип в Lua.

Давайте представим как будет выглядеть наш скрипт на Lua использующий новый тип данных, гоблина.

math.randomseed(seed())

-- Заставим гоблинов на нести друг другу по удару.
function battle(g1, g2)
   -- Вычисляется урон наносимый первым гоблином
   a1 = math.random(g1:get_min_at(),g1:get_max_at()) - g2:get_defend()
   -- вторым гоблином
   a2 = math.random(g2:get_min_at(),g2:get_max_at()) - g1:get_defend()
   -- Если урон усугубит здоровье гоблина, то он его усугубляет
   if a1 > 0 then
      g2:set_cur_hp(g2:get_cur_hp() - a1)
   end
   if a2 > 0 then
      g1:set_cur_hp(g1:get_cur_hp() - a2)
   end
   return a1, a2
end

-- Создаем гоблина "boo" и "foo".
g1 = goblin.new(10, 10, 2, 4, 0, "boo")
g2 = goblin.new(nil, nil, nil, nil, 1, "foo")

-- Печатаем гоблинов.
print(g1)
print(g2)

-- Пока один из гоблинов (а могут и оба) не падет в битве.
while g1:get_cur_hp() > 0 and g2:get_cur_hp() > 0 do
   battle(g1, g2)
end

-- Печатаем пустую строку.
print()

-- Определяем жив ли кто.
if g1:get_cur_hp() > 0 then
   print(tostring(g1).." WIN!")
elseif g2:get_cur_hp() > 0 then
   print(tostring(g2).." WIN!")
else
   print("no winers")
end

Давайте посмотрим как выглядеть код на C будет, который и обеспечит работу нашего скрипта.

#if 0
gcc -o simple3 -llua -lm $0 ; exit
#endif
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

typedef struct goblin
{
  int max_hp;
  int cur_hp;
  int min_at;
  int max_at;
  int defend;
  const char *name;
} goblin;

/* Это функция проверяет количество переданных аргументов. */
void
check_arg_number(lua_State *lua, int n)
{
  int top = lua_gettop(lua);
  if (top != n)
    {
      lua_Debug dbg;
      lua_getinfo(lua, "Sl", &dbg);
      lua_pushfstring(lua, "%s:%d: error: got %d, expected %d",
                      dbg.short_src, dbg.currentline, top, n);
      lua_error(lua);
    }
}

/*
  Это функция проверяет что элемент стека гоблин и возвращает
  указатель на него.
*/
goblin*
check_goblin(lua_State *lua, int index)
{
  goblin *g;
  luaL_checktype(lua, index, LUA_TUSERDATA);
  g = (goblin*)luaL_checkudata(lua, index, "goblin");
  if (g == NULL)
    luaL_typerror(lua, index, "goblin");
  return g;
}

/*
  Помещает гоблина в стек. Возвращая указатель на только
  что созданного в Lua гоблина. Его нужно будет инициализировать.
*/
goblin*
push_goblin(lua_State *lua)
{
  goblin *g = (goblin*)lua_newuserdata(lua, sizeof(goblin));
  luaL_getmetatable(lua, "goblin");
  lua_setmetatable(lua, -2);
  return g;
}

/* Это функция для создания гоблина. Она возвращает гоблина. */
int
goblin_new(lua_State *lua)
{
  /*
    Функции luaL_opt* позволяет в случае если в стеке nil (или вообще
    ничего нет), получить значение переданное третьим параметром в функцию.
  */
  int cur_hp = luaL_optint(lua, 1, 10);
  int max_hp = luaL_optint(lua, 2, 10);
  int min_at = luaL_optint(lua, 3, 1);
  int max_at = luaL_optint(lua, 4, 3);
  int defend = luaL_optint(lua, 5, 0);
  const char *name = luaL_optstring(lua, 6, "goblin");
  goblin *g = push_goblin(lua);
  g->cur_hp = cur_hp;
  g->max_hp = max_hp;
  g->min_at = min_at;
  g->max_at = max_at;
  g->defend = defend;
  g->name = name;
  return 1;
}

/* Дальше идут геттеры. */
int
goblin_get_max_hp(lua_State *lua)
{
  goblin *g;
  check_arg_number(lua, 1);
  g = check_goblin(lua, 1);
  lua_pushnumber(lua, g->max_hp);
  return 1;
}

int
goblin_get_cur_hp(lua_State *lua)
{
  goblin *g;
  check_arg_number(lua, 1);
  g = check_goblin(lua, 1);
  lua_pushnumber(lua, g->cur_hp);
  return 1;
}

int
goblin_get_min_at(lua_State *lua)
{
  goblin *g;
  check_arg_number(lua, 1);
  g = check_goblin(lua, 1);
  lua_pushnumber(lua, g->min_at);
  return 1;
}

int
goblin_get_max_at(lua_State *lua)
{
  goblin *g;
  check_arg_number(lua, 1);
  g = check_goblin(lua, 1);
  lua_pushnumber(lua, g->max_at);
  return 1;
}

int
goblin_get_defend(lua_State *lua)
{
  goblin *g;
  check_arg_number(lua, 1);
  g = check_goblin(lua, 1);
  lua_pushnumber(lua, g->defend);
  return 1;
}

int
goblin_get_name(lua_State *lua)
{
  goblin *g;
  check_arg_number(lua, 1);
  g = check_goblin(lua, 1);
  lua_pushstring(lua, g->name);
  return 1;
}

/* Дальше идут сеттеры. */
int
goblin_set_max_hp(lua_State *lua)
{
  int max_hp;
  goblin *g;
  check_arg_number(lua, 2);
  max_hp = luaL_checkint(lua, 2);
  g = check_goblin(lua, 1);
  g->max_hp = max_hp;
  return 0;
}

int
goblin_set_cur_hp(lua_State *lua)
{
  int cur_hp;
  goblin *g;
  check_arg_number(lua, 2);
  cur_hp = luaL_checkint(lua, 2);
  g = check_goblin(lua, 1);
  g->cur_hp = cur_hp;
  return 0;
}

int
goblin_set_min_at(lua_State *lua)
{
  int min_at;
  goblin *g;
  check_arg_number(lua, 2);
  min_at = luaL_checkint(lua, 2);
  g = check_goblin(lua, 1);
  g->min_at = min_at;
  return 0;
}

int
goblin_set_max_at(lua_State *lua)
{
  int max_at;
  goblin *g;
  check_arg_number(lua, 2);
  max_at = luaL_checkint(lua, 2);
  g = check_goblin(lua, 1);
  g->max_at = max_at;
  return 0;
}

int
goblin_set_defend(lua_State *lua)
{
  int defend;
  goblin *g;
  check_arg_number(lua, 2);
  defend = luaL_checkint(lua, 2);
  g = check_goblin(lua, 1);
  g->defend = defend;
  return 0;
}

int
goblin_set_name(lua_State *lua)
{
  const char *name;
  goblin *g;
  check_arg_number(lua, 2);
  name = luaL_checkstring(lua, 2);
  g = check_goblin(lua, 1);
  g->name = name;
  return 0;
}

/* Это функция вызывается при сборке мусора в Lua. */
int
goblin_gc(lua_State *lua)
{
  /*
    Она полезна если необходимо не только освободить память но и
    закрыть файл, разорвать соединение с СУБД.
  */
  return 0;
}

/* Это функция превращает нашего гоблина в строку. */
int
goblin_to_string(lua_State *lua)
{
  goblin *g = check_goblin(lua, 1);
  lua_pushfstring(lua, "%s hp: %d(%d) at: %d-%d df: %d",
                  g->name, g->cur_hp, g->max_hp,
                  g->min_at, g->max_at, g->defend);
  return 1;
}

/* Это список методов нашего гоблинов */
const luaL_Reg goblin_methods[] = {
  {"new", goblin_new},
  {"get_max_hp", goblin_get_max_hp},
  {"get_cur_hp", goblin_get_cur_hp},
  {"get_min_at", goblin_get_min_at},
  {"get_max_at", goblin_get_max_at},
  {"get_defend", goblin_get_defend},
  {"get_name",   goblin_get_name},
  {"set_max_hp", goblin_set_max_hp},
  {"set_cur_hp", goblin_set_cur_hp},
  {"set_min_at", goblin_set_min_at},
  {"set_max_at", goblin_set_max_at},
  {"set_defend", goblin_set_defend},
  {"set_name",   goblin_set_name},
  {NULL, NULL}
};

/*
  Метатаблица гоблина, здесь указываются функции вызываемые
  при сборке мусора, конвертации в строку и много чего еще.
*/
const luaL_Reg goblin_meta[] = {
  {"__gc", goblin_gc},
  {"__tostring", goblin_to_string},
  {NULL, NULL}
};

/*
  Это функция переселяет гоблина в Lua.
*/
int
goblin_register(lua_State *lua)
{
  int ret;
  /* Регистрирует гоблина как библиотеку. */
  luaL_register(lua, "goblin", goblin_methods);
  /* Создает метатаблицу. */
  ret = luaL_newmetatable(lua, "goblin");
  if (ret)
    {
      luaL_register(lua, 0, goblin_meta);
      /*
        Я не знаю как объяснить что делают 6 строчек ниже.
        Если коротка, то эта настройка метотаблицы.
        Установка метаметода __index.
        Установка данной метатаблицы, как метатаблицы для гоблина.
        Ох, надеюсь я ничего не напутал.
      */
      lua_pushliteral(lua, "__index");
      lua_pushvalue(lua, -3);
      lua_rawset(lua, -3);
      lua_pushliteral(lua, "__metatable");
      lua_pushvalue(lua, -3);
      lua_rawset(lua, -3);
    }
  lua_pop(lua, 2);
  return ret;
}

/* Это функция должна заменить os.time(). */
int
seed(lua_State *lua)
{
  check_arg_number(lua, 0);
  lua_pushnumber(lua, time(NULL));
  return 1;
}

int
main(int argc, char *argv[])
{
  const char *str;
  lua_State *lua = luaL_newstate();
  if (!lua)
    {
      fprintf(stderr,
              "%s (%s : %d)\n",
              strerror(errno), __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
  /* Это открытие базовой библиотеки Lua */
  lua_pushcfunction(lua, luaopen_base);
  lua_call(lua, 0, 0);
  /* Это открытие математической библиотеки Lua */
  lua_pushcfunction(lua, luaopen_math);
  lua_call(lua, 0, 0);
  lua_pushcfunction(lua, seed);
  lua_setglobal(lua, "seed");
  goblin_register(lua);
  if (luaL_dofile(lua, "simple.lua"))
    {
      str = lua_tostring(lua, 1);
      fprintf(stderr,
              "%s (%s : %d)\n",
              str, __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
  lua_close(lua);
  return 0;
}

Вот так, немного магии и в Lua поселился гоблин.

Вы заметили что мы использовали не все стандартные библиотеки Lua. Это полезно, когда нужно ограничить возможности скриптов. Например с помощь os.remove(file) можно удалить file, что согласитесь опасно.

Продолжение следует...