Estoy una vez más programando un parser de un archivo de dialogos de npcs intencionalmente simple, pero que ha resultado ser inadvertidamente complicado. Ya he dicho varias veces (1, 2, para la definición de parser ver aquí) que he tenido que hacer varios parsers en mi vida de programador de juegos inéditos. Si leemos aqui, veremos que mi opinión es que usar los viejos atlantes de la programación de compiladores (lex y yacc) es un trabajo de meses. Esa es una exageración, pero las cosas resultan algo como eso (esta librería de facebook usa bison y yacc para parsing de GraphQL, un lenguaje para describir requerimientos de datos). Pero ¿qué tal hacerlo a mano? Depende del lenguaje. Hace años hice un intérprete de un lenguaje similar a C y si mal no recuerdo no resultó complicado. Pero esta vez la complicación provino de la recursividad. Mi “simple” formato incluye un nombre de npc, que puede tener una o más palabras claves, que tienen asociado uno o más párrafos. Todo salpicado con formatos del tipo {[1]}, {[2d6]}, etc. que indican cuántas veces se debe decir el texto. Finalmente se pueden insertar %%comentarios%%, que abarcan una o más líneas. Simple ¿verdad?
No.
[code language=”cpp”]
for (ilineno = 0; ilineno < MAX_LINES;ilineno++) { if (dialog_id && variable_id) { // variable_id not assigned yet to its dialog update_dialog_variable (dialog_id, variable_id); variable_id = 0; dialog_id = 0; } bool found = false; // search a npc while (parser->getline (line)) {
lineno++;
// handle comments
if (handleComments (parser, line)) {
continue;
}
begin = parser->after_pattern(line, CIS_TAG_NPC);
if (begin == std::string::npos) {
continue;
}
std::string s = line.substr (begin);
end = parser->before_pattern(s, CIS_TAG_NPC);
if (end != std::string::npos) {
npcname = s.substr (0, end);
found = true;
break;
}
}
if (!found) {
break; // finished… maybe
}
std::transform(npcname.begin(), npcname.end(), npcname.begin(), ::tolower);
int id = npcs->getId (npcname);
if (id == -1) {
id = npcs->insert (npcname); // create instance
}
(id == -1) && panic ("CIntSystem::load: can’t insert/update a new npc");
// now a loop of ::keywords[,keywords]:: text,text…
while (parser->peekline (line)) { // give me the line but keep it in the input pipeline (so I can do a getline (line) after)
begin = parser->after_pattern(line, CIS_TAG_NPC);
if (begin != std::string::npos) { // we found a npc so we finished
break;
}
dialog_id = insert_dialog (); // create empty dialog
while (parser->getline (line)) {
lineno++;
// handle comments
if (handleComments (parser, line)) {
continue;
}
begin = parser->after_pattern(line, CIS_TAG_KEYW);
if (begin == std::string::npos) {
continue;
}
int i;
for (i = 0; i < MAX_KEYWORDS;i++) { std::string s = line.substr (begin); end = parser->before_pattern(s, CIS_TAG_SEP);
if (end != std::string::npos) {
keyword = s.substr (0, end);
insert_keyword (dialog_id, keyword);
begin = parser->after_pattern (s, CIS_TAG_SEP);
} else {
end = parser->before_pattern(s, CIS_TAG_KEYW);
value = missing + "’" + std::string(CIS_TAG_KEYW) + "’ at line " + itos (lineno);
end == std::string::npos && panic (value);
keyword = s.substr (0, end);
insert_keyword (dialog_id, keyword);
break;
}
}
(i == MAX_KEYWORDS) && panic ("CIntSystem::load: Too much keywords or parsing error");
break;
}
// now dialog’s lines
int index = 0;
while (parser->peekline (line)) { // give me the line but keep it in the input pipeline (so I can do a getline (line) after)
begin = parser->after_pattern(line, CIS_TAG_NPC);
if (begin != std::string::npos) { // we found a npc so we finished
break;
}
begin = parser->after_pattern(line, CIS_TAG_KEYW);
if (begin != std::string::npos) { // we found a another keyword so we finished current keyword
break;
}
while (true) { // comments loop
parser->getline (line); // get the line
begin = parser->before_pattern(line, "::Lugar::");
if (begin != std::string::npos) {
int kk = 0;
kk++;
}
if (lineno == 28) {
int kk = 0;
kk++;
}
// handle comments
if (!handleComments (parser, line)) {
parser->getline (line); // eat the line
break;
}
// verify missing tag
begin = parser->before_pattern(line, CIS_TAG_NPC);
end = parser->before_pattern(line, CIS_TAG_KEYW);
(begin != std::string::npos || end != std::string::npos) && panic ("CIntSystem::load: Wrong comment in line " + line);
}
while (true) { // this loop travel the line until its end… truly amazing
// as the documents is generated using Word, the text is in one line without newline (LF+CR)
// anyway this parser assume is possible to have several lines and each one is taken as a paragraph.
int initial_size = line.size ();
begin = parser->before_pattern(line, CIS_TAG_FMTBEG);
if (begin == std::string::npos) {
if (dialog_id == 0) { // we found a line previously so lets create a new one
dialog_id = insert_dialog (line, 0, index); // create empty dialog
}
// reset all
variable_id = 0;
dialog_id = 0;
trigger_id = 0;
// check next line
break; // exit the loop analizing this line so get a new line.
}
// begin != std::string::npos
// we found some formatting instructions
if (0 != begin) {
value = line.substr (0, begin); // <=:- value is here in case you’re like me update_dialog (dialog_id, value, std::string(), variable_id); variable_id = 0; // this means that variable_id is assigned to his dialog } begin++; // skip CIS_TAG_FMTBEG line = line.substr (begin); begin = line.find_last_not_of(whitespaces); // skip any white spaces
switch (line[begin]) { case CIS_TAG_CNTBEG: // [ // count …
break;
case CIS_TAG_VARBEG: // $
…
break;
case CIS_TAG_VARACT: // =
…
break;
case CIS_TAG_VARDIS: // ~
…
break;
default:
…
break; // we don’t reach this point.
}
…
}
}
}
}
:
…
[/code]
De enlaces
- Una discusión sobre una patente de ascensor espacial sin que se mencione en lo absoluto a Arthur C. Clarke. Al parecer esta patente gira sobre conceptos que no encajan exactamente con la definición de ascensor espacial, es más bien una torre muy alta con un espaciopuerto en la azotea. Alguien hizo un post en 9gag sobre lo mismo.
- Leyendo aquí descubrí Carrion Fields, un roleplaying mud, que inclusive está de 6to en topmuds. Raro que no lo vi hace meses cuando huía despavorido de batmud. En esa ocasión visité Aardwolf (suele estar de primero, me gustó hasta dónde llegué), landsofredemption.com, abandonedrealms.com y Slothmud.
- Tú necesitas un plan y un diseño. No. Tú necesitas ponerte a trabajar. Planear lo hacen los aviones 🙂 Hablando en serio me gustaría hacer un inventario de cuántos posts como este he leído en los últimos 20 años. Pero si usted nunca ha leído uno así hágalo, la planificación es en mi opinión inútil pero siempre sirve como divertimento medieval cuando no tenemos nada mejor que hacer. Yo escribo ideas todo el tiempo, así que organizarlas en un plan no molesta en lo absoluto, así que sí, planifique, planifique, eso es bueno.