YAML существенно лаконичнее, и сегодня, мы научимся им пользоваться.
Для начала, определимся, зачем нам YAML. Я считаю, что, помимо всего прочего, в нем будет удобно хранить описание уровней, например, вот в таком виде:
{ board: { width: 320 }, types: { odd: { inner_color: 0xffffff00, outer_color: 0xff50ff00, width: 40, height: 20 }, even: { inner_color: 0xff1010ff, outer_color: 0xffffffff, width: 40, height: 20 } }, level: [ { type: odd, x: 50, y: 30 }, { type: even, x: 94, y: 30 }, { type: odd, x: 138, y: 30 }, { type: even, x: 182, y: 30 }, { type: odd, x: 226, y: 30 }, { type: even, x: 270, y: 30 }, { type: even, x: 50, y: 54 }, { type: odd, x: 94, y: 54 }, { type: even, x: 138, y: 54 }, { type: odd, x: 182, y: 54 }, { type: even, x: 226, y: 54 }, { type: odd, x: 270, y: 54 }, { type: odd, x: 50, y: 78 }, { type: even, x: 94, y: 78 }, { type: odd, x: 138, y: 78 }, { type: even, x: 182, y: 78 }, { type: odd, x: 226, y: 78 }, { type: even, x: 270, y: 78 }, { type: even, x: 50, y: 102 }, { type: odd, x: 94, y: 102 }, { type: even, x: 138, y: 102 }, { type: odd, x: 182, y: 102 }, { type: even, x: 226, y: 102 }, { type: odd, x: 270, y: 102 } ] }
Только не надо гневно кричать «нас обманули!». Да, это JSON. Хорошая новость заключается в том, что это и YAML тоже. Просто напросто JSON является подмножеством YAML и любое JSON описание должно быть без проблем разобрано YAML-парсером. JSON чуть более синтаксически строг и чуть менее лаконичен (но все равно гораздо лаконичнее чем XML).
Довольно теории, перейдем к делу. Найдем в Интернете любую библиотеку для разбора YAML и попытаемся встроить ее в наш проект. К слову сказать, выбранная нами библиотека разработана Кириллом Симоновым и свободно распространяется по MIT license (о чем можно прочитать в разделе Copyright страницы с описанием библиотеки).
Мы могли бы просто включить все необходимые файлы в mkb-файл Marmalade-проекта, но это будет не очень удобно. Я предлагаю оформить библиотеку в виде подпроекта Marmalade, благо примеров такого оформления в поставке Maramalade предостаточно. Создаем папку «yaml» и размещаем в ней mkf-файл следующего содержания:
includepath h includepath source files { (h) yaml.h config.h (source) yaml_private.h api.c dumper.c emitter.c loader.c parser.c reader.c scanner.c writer.c }
Создаем подкаталоги и размещаем в них исходные тексты библиотеки в соответствии с описанием их размещения в mkf-файле. На этом все. Мы создали полноценный Marmalade-модуль, который легко можем использовать в любом из наших проектов.
Сделаем это:
#!/usr/bin/env mkb options { module_path="../yaml" } subprojects { iwgl yaml } includepath { ./source/Main ./source/Model } files { [Main] (source/Main) Main.cpp Main.h Quads.cpp Quads.h Desktop.cpp Desktop.h IO.cpp IO.h [Model] (source/Model) Board.cpp Board.h Bricks.cpp Bricks.h Ball.cpp Ball.h [Data] (data) } assets { (data) level.json (data-ram/data-gles1, data/data-gles1) }
Теперь, модуль YAML подключен к проекту и нам осталось научиться обрабатывать получаемые от него данные. Достаточно внести ряд изменений в Board:
#ifndef _BOARD_H_ #define _BOARD_H_ #include <yaml.h> #include <vector> #include <String> #include "Bricks.h" #include "Ball.h" #define MAX_NAME_SZ 100 using namespace std; enum EBrickMask { ebmX = 0x01, ebmY = 0x02, ebmComplete = 0x03, ebmWidth = 0x04, ebmHeight = 0x08, ebmIColor = 0x10, ebmOColor = 0x20 }; class Board { private: struct Type { Type(const char* s, const char* n, const char* v): s(s), n(n), v(v) {} Type(const Type& p): s(p.s), n(p.n), v(p.v) {} string s, n, v; }; Bricks bricks; Ball ball; yaml_parser_t parser; yaml_event_t event; vector<string> scopes; vector<Type> types; char currName[MAX_NAME_SZ]; int brickMask; int brickX, brickY, brickW, brickH, brickIC, brickOC; bool isTypeScope; void load(); void clear(); void notify(); const char* getScopeName(); void setProperty(const char* scope, const char* name, const char* value); void closeTag(const char* scope); int fromNum(const char* s); public: Board(): scopes(), types() {} void init(); void refresh(); void update() {} typedef vector<string>::iterator SIter; typedef vector<Type>::iterator TIter; }; #endif // _BOARD_H_
#include "Board.h" #include "Desktop.h" const char* BOARD_SCOPE = "board"; const char* LEVEL_SCOPE = "level"; const char* TYPE_SCOPE = "types"; const char* TYPE_PROPERTY = "type"; const char* WIDTH_PROPERTY = "width"; const char* HEIGHT_PROPERTY = "height"; const char* IC_PROPERTY = "inner_color"; const char* OC_PROPERTY = "outer_color"; const char* X_PROPERTY = "x"; const char* Y_PROPERTY = "y"; void Board::init() { load(); ball.init(); } void Board::clear() { bricks.clear(); scopes.clear(); memset(currName, 0, sizeof(currName)); types.clear(); } void Board::load() { clear(); yaml_parser_initialize(&parser); FILE *input = fopen("level.json", "rb"); yaml_parser_set_input_file(&parser, input); int done = 0; while (!done) { if (!yaml_parser_parse(&parser, &event)) { break; } notify(); done = (event.type == YAML_STREAM_END_EVENT); yaml_event_delete(&event); } yaml_parser_delete(&parser); fclose(input); } void Board::notify() { switch (event.type) { case YAML_MAPPING_START_EVENT: case YAML_SEQUENCE_START_EVENT: scopes.push_back(currName); memset(currName, 0, sizeof(currName)); break; case YAML_MAPPING_END_EVENT: closeTag(getScopeName()); case YAML_SEQUENCE_END_EVENT: scopes.pop_back(); break; case YAML_SCALAR_EVENT: if (currName[0] == 0) { strncpy(currName, (const char*)event.data.scalar.value, sizeof(currName)-1); break; } setProperty(getScopeName(), currName, (const char*)event.data.scalar.value); memset(currName, 0, sizeof(currName)); break; default: break; } } const char* Board::getScopeName() { const char* r = NULL; isTypeScope = false; for (SIter p = scopes.begin(); p !=scopes.end(); ++p) { if (!(*p).empty()) { if (strcmp((*p).c_str(), TYPE_SCOPE) == 0) { isTypeScope = true; continue; } r = (*p).c_str(); } } return r; } int Board::fromNum(const char* s) { int r = 0; int x = 10; for (size_t i = 0; i < strlen(s); i++) { switch (s[i]) { case 'x': case 'X': x = 16; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': x = 16; r *= x; r += s[i] - 'a' + 10; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': x = 16; r *= x; r += s[i] - 'A' + 10; break; default: r *= x; r += s[i] - '0'; break; } } return r; } void Board::setProperty(const char* scope, const char* name, const char* value) { if (scope == NULL) return; if (isTypeScope) { types.push_back(Type(scope, name, value)); return; } if (strcmp(scope, BOARD_SCOPE) == 0) { if (strcmp(name, WIDTH_PROPERTY) == 0) { desktop.setVSize(fromNum(value)); } } if (strcmp(scope, LEVEL_SCOPE) == 0) { if (strcmp(name, TYPE_PROPERTY) == 0) { for (TIter p = types.begin(); p != types.end(); ++p) { if (strcmp(value, p->s.c_str()) == 0) { setProperty(scope, p->n.c_str(), p->v.c_str()); } } } if (strcmp(name, X_PROPERTY) == 0) { brickMask |= ebmX; brickX = fromNum(value); } if (strcmp(name, Y_PROPERTY) == 0) { brickMask |= ebmY; brickY = fromNum(value); } if (strcmp(name, WIDTH_PROPERTY) == 0) { brickMask |= ebmWidth; brickW = fromNum(value); } if (strcmp(name, HEIGHT_PROPERTY) == 0) { brickMask |= ebmHeight; brickH = fromNum(value); } if (strcmp(name, IC_PROPERTY) == 0) { brickMask |= ebmIColor; brickIC = fromNum(value); } if (strcmp(name, OC_PROPERTY) == 0) { brickMask |= ebmOColor; brickOC = fromNum(value); } } } void Board::closeTag(const char* scope) { if (scope == NULL) return; if (strcmp(scope, LEVEL_SCOPE) == 0) { if ((brickMask & ebmComplete) == ebmComplete) { Bricks::SBrick b(desktop.toRSize(brickX), desktop.toRSize(brickY)); if ((brickMask & ebmWidth) != 0) { b.hw = desktop.toRSize(brickW) / 2; } if ((brickMask & ebmHeight) != 0) { b.hh = desktop.toRSize(brickH) / 2; } if ((brickMask & ebmIColor) != 0) { b.ic = brickIC; } if ((brickMask & ebmOColor) != 0) { b.oc = brickOC; } bricks.add(b); } brickMask = 0; } } void Board::refresh() { bricks.refresh(); ball.refresh(); }
Как все это работает? Файл с описанием уровня читается в методе load. После этого, мы вызываем функцию разбора yaml_parser_parse в цикле, анализируя возникающие события разбора. Анализ этот довольно примитивен. Некоторое оживление вносит лишь обработка содержимого секции «types». В ней мы описываем «шаблоны» настроек, которые впоследсвии сможем добавлять в описание «кирпичей», добавляя имя соответвующего типа в качестве значения атрибута «type».
В разделе «board» мы описываем ширину доски. Все остальные размеры, в описании уровня, определяются относительно нее. Обращаю ваше внимание на то, что нам не требуется определять высоту доски в описании уровня. Размеры по вертикали перерассчитываются в том же соотношении, что и размеры по горизонтали. Таким образом, мы добиваемся того, чтобы уровень выглядел практически одинаково на устройствах с различным соотношением ширины и высоты экрана (разница «теряется» в пустой области, имеющейся на любом уровне).
Запустив программу на выполнение, мы увидим, что наши данные прекрасно загрузились:
Осталось заметить, что возможности LibYAML не ограничиваются разбором YAML-файлов. С помощью нее мы можем формировать YAML-файлы сами, сохраняя в них, например, текущее состояние игровых настроек. Пример того как это делается имеется на странице с описанием библиотеки. Сохранять файлы в файловой системе устройства нам поможет настройка DataDirIsRAM:
[S3E] SysGlesVersion=1 DispFixRot=FixedPortrait DataDirIsRAM=1
На этом все. Модуль для работы с YAML выложен на GitHub.
В следующей статье мы научимся работать с Box2D.
ссылка на оригинал статьи http://habrahabr.ru/post/166357/
Добавить комментарий