shortest-path-wise nearest enemy visible to them. If they see no enemy, they
just wait.
-Once you start a new world, every move of yours is recorded in a file called
-"record". Once you re-start the game, all of your previous moves are replayed
-automatically up to the point wherere you left the game. To start over in a new
-world, simply delete this file.
+Every move of yours re-writes a file "savefile" that describes the new state of
+the world. Once you re-start the game, the game state is recreated from the
+"savefile" file. To start over in a new world, simply delete this file.
System requirements / installation / running the game
-----------------------------------------------------
Replay game recording
---------------------
-Run "./roguelike -s" to watch a recording of the current game from the
-beginning. Hit any player action key to increment turns (they will not trigger
-the actions usually mapped to them, only repeat the actions done at that point
-in the game as defined in the "record" file). Keys to manage windows, scroll on
-the map and quit the program do their usual thing. Append a number to the -s
-option (like "-s100") to start the recording at the respective turn number.
+Once you start a new world, every move of yours is recorded in a file called
+"record". It gets overwritten when a new game world is started after deletion
+of the "savefile" file. Run "./roguelike -s" to watch the current game's
+recording from the beginning. Hit any player action key to increment turns (they
+will not trigger the actions usually mapped to them, only repeat the actions
+done at that point in the game as defined in the "record" file). Keys to manage
+windows, scroll on the map and quit the program do their usual thing. Append a
+number to the -s option (like "-s100") to start the recording at the respective
+turn number.
Hacking / server internals and configuration
--------------------------------------------
Next planned steps in plomrogue development:
-BOTH SERVER/CLIENT:
-
-- make server and client communicate by specific world state info requests
- in server/out, replacing server/worldstate
+IN GENERAL:
- check for return values of *printf()
- be more strict and humble when allocating memory from the stack
+- expand use of hardcoded_strings module(s)
+
+BOTH SERVER/CLIENT:
+
+- make server and client communicate by specific world state info requests
+ in server/out, replacing server/worldstate
+
SERVER:
- optimize too-slow AI / FOV algorithms
-- is it actually useful to define map object action ids in the config file?
-
-- for game continuation, replace re-playing of whole record files with loading
- game state snapshots / save files
+- save confserver/world data in record and save file, too; handle them like god
+ commands
CLIENT:
#include "../common/parse_file.h" /* EDIT_STARTED,parse_init_entry(),
* parse_id_uniq(), parse_unknown_arg(),
* parsetest_too_many_values(), parse_file(),
- * parse_and_reduce_to_readyflag(),parse_val()
+ * parse_and_reduce_to_readyflag(),
+ * parse_flagval()
*/
#include "array_append.h" /* array_append() */
#include "world.h" /* global world */
cmd->dsc_short = strdup(token1);
parse_id_uniq(NULL != get_command(cmd->dsc_short));
}
- else if (!( parse_val(token0, token1, "DESCRIPTION", &cmd_flags,
- DESC_SET, 's', (char *) &cmd->dsc_long)
- || parse_val(token0, token1, "SERVER_COMMAND", &cmd_flags,
- SERVERCMD_SET, 's', (char *) &cmd->server_msg)
- || parse_val(token0, token1, "SERVER_ARGUMENT", &cmd_flags,
- SERVERARG_SET, 'c', (char *) &cmd->arg)))
+ else if (!( parse_flagval(token0, token1, "DESCRIPTION", &cmd_flags,
+ DESC_SET, 's', (char *) &cmd->dsc_long)
+ || parse_flagval(token0, token1,"SERVER_COMMAND", &cmd_flags,
+ SERVERCMD_SET, 's',(char *)&cmd->server_msg)
+ || parse_flagval(token0, token1,"SERVER_ARGUMENT",&cmd_flags,
+ SERVERARG_SET, 'c', (char *) &cmd->arg)))
{
parse_unknown_arg();
}
#include <stdio.h> /* FILE, sprintf() */
#include <string.h> /* strchr(), strcmp(), strdup(), strlen() */
#include <unistd.h> /* optarg, getopt() */
-#include "../common/parse_file.h" /* EDIT_STARTED, parse_file(), parse_val(),
+#include "../common/parse_file.h" /* EDIT_STARTED, parse_file(),parse_flagval(),
* token_from_line(), parsetest_singlechar(),
* parse_and_reduce_to_readyflag(),
* parsetest_defcontext(),parse_unknown_arg(),
uint8_t * ord_flags,uint8_t kbd_flags,char * str_key,
struct Win * win, struct KeyBindingDB * kbdb)
{
- if ( parse_val(token0, token1, "NAME", win_flags,
- NAME_SET, 's', (char *) &win->title)
- || parse_val(token0, token1, "WIDTH", win_flags,
- WIDTH_SET, 'i', (char *) &win->target_width)
- || parse_val(token0, token1, "HEIGHT", win_flags,
- HEIGHT_SET, 'i', (char *) &win->target_height));
- else if (parse_val(token0, token1, "BREAK", win_flags,
- BREAK_SET, '8', (char *) &win->linebreak))
+ if ( parse_flagval(token0, token1, "NAME", win_flags,
+ NAME_SET, 's', (char *) &win->title)
+ || parse_flagval(token0, token1, "WIDTH", win_flags,
+ WIDTH_SET, 'i', (char *) &win->target_width)
+ || parse_flagval(token0, token1, "HEIGHT", win_flags,
+ HEIGHT_SET, 'i', (char *) &win->target_height));
+ else if (parse_flagval(token0, token1, "BREAK", win_flags,
+ BREAK_SET, '8', (char *) &win->linebreak))
{
err_line(2 < win->linebreak, "Value must be 0, 1 or 2.");
}
- else if (parse_val(token0, token1, "WIN_FOCUS", ord_flags,
- FOCUS_SET, 'c', &tmp_active))
+ else if (parse_flagval(token0, token1, "WIN_FOCUS", ord_flags,
+ FOCUS_SET, 'c', &tmp_active))
{
char * err_null = "Value not empty as it should be.";
char * err_outside = "ID not found in WIN_ORDER ID series.";
-/* Set by parse_file(), used by err_line() for more informative messages. */
+/* Set by parse_file(), helps err_line() deciding what to do/output on error. */
static uint32_t err_line_count = 0;
static char * err_line_line = NULL;
static char * err_line_intro = NULL;
+static uint8_t err_line_exit = 1;
char * prefix = "Failed reading config file: \"";
char * affix = "\". ";
size_t size = strlen(prefix) + strlen(path) + strlen(affix) + 1;
- err_line_intro = try_malloc(size, f_name);
- int test = snprintf(err_line_intro, size, "%s%s%s", prefix, path, affix);
+ char * errline_intro = try_malloc(size, f_name);
+ int test = snprintf(errline_intro, size, "%s%s%s", prefix, path, affix);
exit_trouble(test < 0, f_name, "snprintf()");
- exit_err(access(path, F_OK), err_line_intro);
+ exit_err(access(path, F_OK), errline_intro);
FILE * file = try_fopen(path, "r", f_name);
uint32_t linemax = textfile_width(file);
- err_line_line = try_malloc(linemax + 1, f_name);
- err_line_count = 0;
+ char * errline_line = try_malloc(linemax + 1, f_name);
+ set_err_line_options(errline_intro, errline_line, 0, 1);
err_line(0 == linemax, "File is empty.");
char * token0 = NULL; /* For final token_to_entry() if while() stagnates. */
char * token1 = NULL;
char * err_val = "No value given.";
- while (try_fgets(err_line_line, linemax + 1, file, f_name))
+ while (try_fgets(errline_line, linemax + 1, file, f_name))
{
err_line_count++;
err_line(UINT32_MAX == err_line_count, "Line reaches max lines limit.");
- char * line_copy = strdup(err_line_line);
+ char * line_copy = strdup(errline_line);
token0 = token_from_line(line_copy);
if (token0)
{
}
token_to_entry(token0, token1);
try_fclose(file, f_name);
- free(err_line_line);
- free(err_line_intro);
+ free(errline_line);
+ free(errline_intro);
}
-extern void err_line(uint8_t test, char * msg)
+extern void set_err_line_options(char * intro, char * line, uint32_t count,
+ uint8_t exit)
+{
+ err_line_count = count;
+ err_line_line = line;
+ err_line_intro = intro;
+ err_line_exit = exit;
+}
+
+
+
+extern uint8_t err_line(uint8_t test, char * msg)
{
if (!test)
{
- return;
+ return 0;
}
char * f_name = "err_line()";
char * prefix = " Offending line ";
int ret = snprintf(err, size, "%s%s%s%d%s%s", err_line_intro, msg, prefix,
err_line_count, affix, err_line_line);
exit_trouble(ret < 0, f_name, "snprintf()");
- exit_err(1, err);
+ if (err_line_exit)
+ {
+ exit_err(1, err);
+ }
+ exit_trouble(0 > printf("%s\n", err), f_name, "printf()");
+ exit_trouble(EOF == fflush(stdout), f_name, "fflush()");
+ free(err);
+ return 1;
}
*(--final_char) = '\0';
}
}
+ if (final_char < start)
+ {
+ return NULL;
+ }
uint8_t empty = 1;
uint32_t i;
for (i = 0; '\0' != start[i]; i++)
}
if (empty)
{
- return start = NULL;
+ return NULL;
}
set_token_end(&start, &limit_char);
return start;
-extern void parsetest_int(char * string, char type)
+extern uint8_t parsetest_int(char * string, char type)
{
- char * err;
- if ('8' == type)
- {
- err = "Value must be proper representation of unsigned 8 bit integer.";
- }
- if ('i' == type)
- {
- err = "Value must be proper representation of signed 16 bit integer.";
- }
- err_line(strlen(string) < 1, err);
+ char * err_8 = "Value must represent proper unsigned 8 bit integer.";
+ char * err_i = "Value must represent proper signed 16 bit integer.";
+ char * err_u = "Value must represent proper unsigned 16 bit integer.";
+ char * err_U = "Value must represent proper unsigned 32 bit integer.";
+ char * err = ('8' == type) ? err_8 : err_U;
+ err = ('i' == type) ? err_i : err;
+ err = ('u' == type) ? err_u : err;
+ uint8_t ret = err_line(strlen(string) < 1, err);
uint8_t i;
uint8_t test;
for (i = 0; '\0' != string[i]; i++)
{
char * err_many = "Value of too many characters.";
- err_line(string[i + 1] && UINT8_MAX == i, err_many);
+ ret = ret + err_line(string[i + 1] && UINT8_MAX == i, err_many);
test = ( (0 == i && ('-' == string[i] || '+' == string[i]))
|| ('0' <= string[i] && string[i] <= '9'));
- err_line(!test, err);
+ ret = ret + err_line(!test, err);
}
- err_line(strlen(string) < 2 && ('-' == string[i] || '+' == string[i]), err);
- err_line('8'==type && (atoi(string) < 0 || atoi(string) > UINT8_MAX), err);
- test = 'i'==type && (atoi(string) < INT16_MIN || atoi(string) > INT16_MAX);
- err_line(test, err);
+ ret = ret + err_line( strlen(string) < 2
+ && ('-' == string[i] || '+' == string[i]), err);
+ test = ( '8' == type
+ && ( strlen(string) > 4
+ || atoi(string) < 0 || atoi(string) > UINT8_MAX))
+ || ( 'i' == type
+ && ( strlen(string) > 6
+ || atol(string) < INT16_MIN || atol(string) > INT16_MAX))
+ || ( 'u' == type
+ && ( strlen(string) > 6
+ || atoll(string) < 0 || atol(string) > UINT16_MAX))
+ || ( 'U' == type
+ && ( strlen(string) > 11
+ || atoll(string) < 0 || atoll(string) > UINT32_MAX));
+ ret = ret + err_line(test, err);
+ return ret;
}
-extern void parsetest_singlechar(char * string)
+extern uint8_t parsetest_singlechar(char * string)
{
- err_line(1 != strlen(string), "Value must be single ASCII character.");
+ return err_line(1 !=strlen(string),"Value must be single ASCII character.");
}
extern uint8_t parse_val(char * token0, char * token1, char * comparand,
- uint8_t * flags, uint8_t set_flag, char type,
- char * element)
+ char type, char * element)
{
if (!strcmp(token0, comparand))
{
- parsetest_defcontext(*flags);
- *flags = *flags | set_flag;
if ('s' == type)
{
* (char **) element = strdup(token1);
}
- else if ('c' == type)
+ else if ('c' == type && !parsetest_singlechar(token1))
{
- parsetest_singlechar(token1);
*element = (token1)[0];
}
- else if ('8' == type)
- {
- parsetest_int(token1, '8');
- * (uint8_t *) element = atoi(token1);
- }
- else if ('i' == type)
+ else if (!parsetest_int(token1, type))
{
- parsetest_int(token1, 'i');
- * (int16_t *) element = atoi(token1);
+ if ('8' == type)
+ {
+ * (uint8_t *) element = atoi(token1);
+ }
+ else if ('i' == type)
+ {
+ * (int16_t *) element = atoi(token1);
+ }
+ else if ('u' == type)
+ {
+ * (uint16_t *) element = atol(token1);
+ }
+ else if ('U' == type)
+ {
+ * (uint32_t *) element = atoll(token1);
+ }
}
return 1;
}
+extern uint8_t parse_flagval(char * token0, char * token1, char * comparand,
+ uint8_t * flags, uint8_t set_flag, char type,
+ char * element)
+{
+ if (parse_val(token0, token1, comparand, type, element))
+ {
+ parsetest_defcontext(*flags);
+ *flags = *flags | set_flag;
+ return 1;
+ }
+ return 0;
+}
+
+
+
extern void parse_and_reduce_to_readyflag(uint8_t * flags, uint8_t ready_flag)
{
char * err_fin = "Last definition block not finished yet.";
/* Parse file at "path" by passing each line's first two tokens to
- * "token_to_entry". Ignore empty line. Non-empty lines must feature at least
+ * "token_to_entry". Ignore empty lines. Non-empty lines must feature at least
* two tokens as delimited either be whitespace or single quotes (to allow for
* tokens featuring whitespaces). When EOF is reached, token_to_entry() is
* called a last time with a first token of NULL.
*/
extern void parse_file(char * path, void ( *token_to_entry) (char *, char *));
-/* If "test" != 0, exit on output of "msg" and faulty line and line number as
- * parsed by parse_file(). (Ought to be called as offspring to parse_file().)
+/* Set err_line() options: "intro" message, char array used to store analyzed
+ * lines ("line"), line start "count", whether to "exit" on error message.
*/
-extern void err_line(uint8_t test, char * msg);
+extern void set_err_line_options(char * intro, char * line, uint32_t count,
+ uint8_t exit);
+
+/* If "test", output "msg", faulty line, its number and exit if so defined by
+ * set_err_line_options(), else return 1 on "test" and 0 if "test" is 0.
+ */
+extern uint8_t err_line(uint8_t test, char * msg);
/* Return next token from "line" or NULL if none is found. Tokens either a)
* start at the first non-whitespace character and end before the next
* */
extern char * token_from_line(char * line);
-/* Test for "string" to represent proper int16 (type: "i") or uint8 ("8"). */
-extern void parsetest_int(char * string, char type);
+/* Test for "string" to represent proper int16 (type: "i"), uint8 ("8"), uint16
+ * ("u") or uint32 ("U"). Returns 0 if proper value, else >0.
+ */
+extern uint8_t parsetest_int(char * string, char type);
-/* Test for "string" to be of length 1 (excluding "\0" terminator). */
-extern void parsetest_singlechar(char * string);
+/* Test for "string" to be of length 1 (excluding "\0"). Return 1 on failure. */
+extern uint8_t parsetest_singlechar(char * string);
/* Calls err_line() with fitting message if EDIT_STARTED not set in "flags". */
extern void parsetest_defcontext(uint8_t flags);
extern char * parse_init_entry(uint8_t * flags, size_t size);
/* If "token0" fits "comparand", set "element" to value read from "token1" as
- * string (type: "s"), char ("c") uint8 ("8") or int16 ("i"), set that element's
- * flag to "flags" and return 1; else return 0.
+ * string (type: "s"), char ("c") uint8 ("8"), uint16 ("u"), uint32 ("U") or
+ * int16 ("i"), and return 1; else 0.
*/
extern uint8_t parse_val(char * token0, char * token1, char * comparand,
- uint8_t * flags, uint8_t set_flag, char type,
- char * element);
+ char type, char * element);
+
+/* Wrapper to parse_val() that sets "flags" to "flags"|"set_flag" on success. */
+extern uint8_t parse_flagval(char * token0, char * token1, char * comparand,
+ uint8_t * flags, uint8_t set_flag, char type,
+ char * element);
/* Check "ready_flag" is set in "flags", re-set "flags" to "ready_flag" only. */
extern void parse_and_reduce_to_readyflag(uint8_t * flags, uint8_t ready_flag);
#include <stdlib.h> /* free() */
#include "../common/try_malloc.h" /* try_malloc() */
#include "field_of_view.h" /* VISIBLE */
+#include "hardcoded_strings.h" /* s */
#include "thing_actions.h" /* get_thing_action_id_by_name() */
#include "things.h" /* struct Thing */
#include "world.h" /* global world */
extern void ai(struct Thing * t)
{
- t->command = get_thing_action_id_by_name("wait");
- char sel = get_dir_to_nearest_enemy(t);
- if (0 != sel)
- {
- t->command = get_thing_action_id_by_name("move");
+ t->command = get_thing_action_id_by_name(s[CMD_WAIT]);
+ char sel = t->fov_map ? get_dir_to_nearest_enemy(t) : 0;/* t->fov_map may */
+ if (0 != sel) /* be absent due */
+ { /* to god command.*/
+ t->command = get_thing_action_id_by_name(s[CMD_MOVE]);
t->arg = sel;
}
}
#include <stdlib.h> /* free() */
#include <unistd.h> /* unlink() */
#include "../common/readwrite.h" /* try_fclose() */
+#include "hardcoded_strings.h" /* s */
#include "thing_actions.h" /* free_thing_actions() */
#include "things.h" /* free_things(), free_thing_types() */
#include "world.h" /* global world */
free(world.map.cells);
if (cleanup_flags & CLEANUP_WORLDSTATE)
{
- unlink(world.path_worldstate);
+ unlink(s[PATH_WORLDSTATE]);
}
if (cleanup_flags & CLEANUP_THINGS)
{
if (cleanup_flags & CLEANUP_IN)
{
try_fclose(world.file_in, f_name);
- unlink(world.path_in);
+ unlink(s[PATH_IN]);
}
if (cleanup_flags & CLEANUP_OUT)
{
try_fclose(world.file_out, f_name);
free(world.server_test);
- unlink(world.path_out);
+ unlink(s[PATH_OUT]);
}
}
#include "../common/parse_file.h" /* EDIT_STARTED, parsetest_int(),parse_file(),
* parsetest_too_many_values(),parse_id_uniq()
* parse_unknown_arg(), parse_init_entry(),
- * parse_and_reduce_to_readyflag(),parse_val()
+ * parse_and_reduce_to_readyflag(),
+ * parse_flagval()
*/
#include "../common/rexit.h" /* exit_err(), exit_trouble() */
#include "../common/try_malloc.h" /* try_malloc() */
#include "cleanup.h" /* set_cleanup_flag(), CLEANUP_THING_TYPES,
* CLEANUP_THING_ACTIONS
*/
+#include "hardcoded_strings.h" /* s */
#include "thing_actions.h" /* ThingAction */
#include "things.h" /* Thing, ThingType */
#include "world.h" /* world global */
struct ThingType * tt, struct ThingAction * ta)
{
if ( *action_flags & EDIT_STARTED
- && parse_val(token0, token1, "NAME", action_flags,
- NAME_SET, 's', (char *) &ta->name))
+ && parse_flagval(token0, token1, "NAME", action_flags,
+ NAME_SET, 's', (char *) &ta->name))
{
- if (!( try_func_name(ta, "move", actor_move)
- || try_func_name(ta, "pick_up", actor_pick)
- || try_func_name(ta, "drop", actor_drop)
- || try_func_name(ta, "use", actor_use)))
+ if (!( try_func_name(ta, s[CMD_MOVE], actor_move)
+ || try_func_name(ta, s[CMD_PICKUP], actor_pick)
+ || try_func_name(ta, s[CMD_DROP], actor_drop)
+ || try_func_name(ta, s[CMD_USE], actor_use)))
{
ta->func = actor_wait;
}
*action_flags = *action_flags | NAME_SET;
return 1;
}
- else if ( parse_val(token0, token1, "NAME", thing_flags,
- NAME_SET, 's', (char *) &tt->name)
- || parse_val(token0, token1, "SYMBOL", thing_flags,
- SYMBOL_SET, 'c', (char *) &tt->char_on_map)
- || parse_val(token0, token1, "EFFORT", action_flags,
- EFFORT_SET, '8', (char *) &ta->effort)
- || parse_val(token0, token1, "START_NUMBER", thing_flags,
- START_N_SET, '8', (char *) &tt->start_n)
- || parse_val(token0, token1, "LIFEPOINTS", thing_flags,
- LIFEPOINTS_SET, '8', (char *) &tt->lifepoints)
- || parse_val(token0, token1, "CONSUMABLE", thing_flags,
- CONSUMABLE_SET, '8', (char *) &tt->consumable)
- || parse_val(token0, token1, "CORPSE_ID", thing_flags,
- CORPSE_ID_SET, '8', (char *) &tt->corpse_id))
+ else if ( parse_flagval(token0, token1, "NAME", thing_flags,
+ NAME_SET, 's', (char *) &tt->name)
+ || parse_flagval(token0, token1, "SYMBOL", thing_flags,
+ SYMBOL_SET, 'c', (char *) &tt->char_on_map)
+ || parse_flagval(token0, token1, "EFFORT", action_flags,
+ EFFORT_SET, '8', (char *) &ta->effort)
+ || parse_flagval(token0, token1, "START_NUMBER", thing_flags,
+ START_N_SET, '8', (char *) &tt->start_n)
+ || parse_flagval(token0, token1, "LIFEPOINTS", thing_flags,
+ LIFEPOINTS_SET, '8', (char *) &tt->lifepoints)
+ || parse_flagval(token0, token1, "CONSUMABLE", thing_flags,
+ CONSUMABLE_SET, '8', (char *) &tt->consumable)
+ || parse_flagval(token0, token1, "CORPSE_ID", thing_flags,
+ CORPSE_ID_SET, '8', (char *) &tt->corpse_id))
{
return 1;
}
extern void read_config_file()
{
- parse_file(world.path_config, tokens_into_entries);
+ parse_file(s[PATH_CONFIG], tokens_into_entries);
exit_err(!world.map.length, "Map size not defined in config file.");
uint8_t player_type_is_valid = 0;
struct ThingType * tt;
-/* Parse file at world.path_config into thing type and thing action definitions
- * at world.thing_types and world.thing_actions.
+/* Parse thing type / action definitons file nto thing type and thing action
+ * definitions at world.thing_types and world.thing_actions.
*/
extern void read_config_file();
--- /dev/null
+/* hardcoded_strings.c */
+
+#include "hardcoded_strings.h"
+
+
+
+char * s[26];
+
+
+
+extern void init_strings()
+{
+ s[PATH_CONFIG] = "confserver/world";
+ s[PATH_WORLDSTATE] = "server/worldstate";
+ s[PATH_OUT] = "server/out";
+ s[PATH_IN] = "server/in";
+ s[PATH_RECORD] = "record";
+ s[PATH_SUFFIX_TMP] = "_tmp";
+ s[PATH_SAVE] = "savefile";
+ s[CMD_MAKE_WORLD] = "MAKE_WORLD";
+ s[CMD_DO_FOV] = "BUILD_FOVS";
+ s[CMD_SEED_MAP] = "SEED_MAP";
+ s[CMD_SEED_RAND] = "SEED_RANDOMNESS";
+ s[CMD_TURN] = "TURN";
+ s[CMD_THING] = "THING";
+ s[CMD_TYPE] = "TYPE";
+ s[CMD_POS_Y] = "POS_Y";
+ s[CMD_POS_X] = "POS_X";
+ s[CMD_COMMAND] = "COMMAND";
+ s[CMD_ARGUMENT] = "ARGUMENT";
+ s[CMD_PROGRESS] = "PROGRESS";
+ s[CMD_LIFEPOINTS] = "LIFEPOINTS";
+ s[CMD_CARRIES] = "CARRIES";
+ s[CMD_WAIT] = "wait";
+ s[CMD_MOVE] = "move";
+ s[CMD_PICKUP] = "pick_up";
+ s[CMD_DROP] = "drop";
+ s[CMD_USE] = "use";
+}
--- /dev/null
+/* hardcoded_strings.h
+ *
+ * For re-used hardcoded strings.
+ */
+
+#ifndef STRINGS_H
+#define STRINGS_H
+
+
+
+enum string_num
+{
+ PATH_CONFIG,
+ PATH_WORLDSTATE,
+ PATH_OUT,
+ PATH_IN,
+ PATH_RECORD,
+ PATH_SUFFIX_TMP,
+ PATH_SAVE,
+ CMD_MAKE_WORLD,
+ CMD_DO_FOV,
+ CMD_SEED_MAP,
+ CMD_SEED_RAND,
+ CMD_TURN,
+ CMD_THING,
+ CMD_TYPE,
+ CMD_POS_Y,
+ CMD_POS_X,
+ CMD_COMMAND,
+ CMD_ARGUMENT,
+ CMD_PROGRESS,
+ CMD_LIFEPOINTS,
+ CMD_CARRIES,
+ CMD_WAIT,
+ CMD_MOVE,
+ CMD_PICKUP,
+ CMD_DROP,
+ CMD_USE
+};
+
+extern void init_strings();
+
+extern char * s[26];
+
+
+
+#endif
#include "../common/try_malloc.h" /* try_malloc() */
#include "cleanup.h" /* set_cleanup_flag() */
#include "field_of_view.h" /* build_fov_map() */
-#include "map.h" /* init_map() */
+#include "hardcoded_strings.h" /* s */
+#include "map.h" /* remake_map() */
#include "things.h" /* Thing, ThingType, free_things(), add_things(),
* get_player()
*/
+
+/* Replay game from record file up to the turn named in world.replay, then turn
+ * over to manual replay via io_loop().
+ */
+static void replay_game();
+
+
+
+static void replay_game()
+{
+ char * f_name = "replay_game()";
+ exit_err(access(s[PATH_RECORD], F_OK), "No record found to replay.");
+ FILE * file = try_fopen(s[PATH_RECORD], "r", f_name);
+ uint32_t linemax = textfile_width(file);
+ char line[linemax + 1];
+ while ( world.turn < world.replay
+ && NULL != try_fgets(line, linemax + 1, file, f_name))
+ {
+ obey_msg(line, 0);
+ }
+ uint8_t end = 0;
+ while (!io_loop())
+ {
+ if (!end)
+ {
+ end = (NULL == try_fgets(line, linemax + 1, file, f_name));
+ if (!end)
+ {
+ obey_msg(line, 0);
+ }
+ }
+ }
+ try_fclose(file, f_name);
+}
+
+
+
extern void obey_argv(int argc, char * argv[])
{
int opt;
char * f_name = "setup_server_io()";
int test = mkdir("server", 0700);
exit_trouble(test && EEXIST != errno, f_name, "mkdir()");
- world.file_out = try_fopen(world.path_out, "w", f_name);
+ world.file_out = try_fopen(s[PATH_OUT], "w", f_name);
world.server_test = try_malloc(10 + 1 + 10 + 1 + 1, f_name);
sprintf(world.server_test, "%d %d\n", getpid(), (int) time(0));
try_fwrite(world.server_test, strlen(world.server_test), 1,
world.file_out, f_name);
fflush(world.file_out);
set_cleanup_flag(CLEANUP_OUT);
- if (!access(world.path_in, F_OK)) /* This keeps out input from old input */
+ char * path_in = s[PATH_IN];
+ if (!access(path_in, F_OK)) /* This keeps out input from old input */
{ /* file streams of clients */
- unlink(world.path_in); /* communicating with server processes */
+ unlink(path_in) ; /* communicating with server processes */
} /* superseded by this current one. */
- world.file_in = try_fopen(world.path_in, "w", f_name);
+ world.file_in = try_fopen(path_in, "w", f_name);
try_fclose(world.file_in, f_name);
- world.file_in = try_fopen(world.path_in, "r", f_name);
+ world.file_in = try_fopen(path_in, "r", f_name);
set_cleanup_flag(CLEANUP_IN);
}
-extern void remake_world(uint32_t seed)
+extern void remake_world()
{
char * f_name = "remake_world()";
free(world.log);
- world.log = NULL; /* thing_actions.c's update_log() checks for this. */
- world.seed = seed;
- world.thing_count = 0;
- free(world.map.cells);
+ world.log = NULL; /* thing_actions.c's update_log() checks for this. */
+ world.seed_map = world.seed;
free_things(world.things);
world.last_update_turn = 0;
- init_map();
+ remake_map();
struct ThingType * tt;
for (tt = world.thing_types; NULL != tt; tt = tt->next)
{
{
t->fov_map = t->lifepoints ? build_fov_map(t) : NULL;
}
- if (world.turn)
+ if (!access(s[PATH_RECORD], F_OK))
{
- exit_trouble(unlink(world.path_record), f_name, "unlink()");
+ exit_trouble(unlink(s[PATH_RECORD]), f_name, "unlink()");
}
world.turn = 1;
}
extern void run_game()
{
char * f_name = "run_game()";
- if (!access(world.path_record, F_OK))
+ if (world.replay)
{
- FILE * file = try_fopen(world.path_record, "r", f_name);
+ replay_game();
+ return;
+ }
+ char * path_savefile = s[PATH_SAVE];
+ if (!access(path_savefile, F_OK))
+ {
+ FILE * file = try_fopen(path_savefile, "r", f_name);
uint32_t linemax = textfile_width(file);
char line[linemax + 1];
- while ( (!world.replay || (world.turn < world.replay))
- && NULL != try_fgets(line, linemax + 1, file, f_name))
- {
- obey_msg(line, 0);
- }
- if (!world.replay)
- {
- try_fclose(file, f_name);
- io_loop();
- return;
- }
- uint8_t end = 0;
- while (!io_loop())
+ while (NULL != try_fgets(line, linemax + 1, file, f_name))
{
- if (!end)
+ if (strlen(line) && strcmp("\n", line))
{
- end = (NULL == try_fgets(line, linemax + 1, file, f_name));
- if (!end)
- {
- obey_msg(line, 0);
- }
+ obey_msg(line, 0);
}
}
try_fclose(file, f_name);
- return;
}
- exit_err(world.replay, "No record file found to replay.");
- char * command = "seed";
- char msg[strlen(command) + 1 + 11 + 1];
- sprintf(msg, "%s %d", command, (int) time(NULL));
- obey_msg(msg, 1);
+ else
+ {
+ char * command = s[CMD_MAKE_WORLD];
+ char msg[strlen(command) + 1 + 11 + 1];
+ sprintf(msg, "%s %d", command, (int) time(NULL));
+ obey_msg(msg, 1);
+ }
io_loop();
}
/* Start server in file and out file, latter with server process test string. */
extern void setup_server_io();
-/* Dissolves old game world if it exists, and generates a new one from "seed".
- * Unlinks a pre-existing file at world.path_record if called on a world.turn>0,
- * i.e. if called after iterating through an already established game world.
+/* Dissolves old game world if it exists, generates a new one from world.seed.
+ * Unlinks any pre-existing record file.
*
* Thing (action) definitions read in from server config directory are not
* affected. The map is populated accordingly. world.last_update_turn is set to
* 0 and world.turn to 1, so that io_round()'s criteria for updating the output
* file are triggered even when this function is called during a round 1.
*/
-extern void remake_world(uint32_t seed);
+extern void remake_world();
-/* Create a game state from which to play or replay, then enter io_loop().
+/* Create a game world state, then enter play or replay mode.
*
- * If no record file exists at world.path_record, generate new world (by a
- * "seed" command calling remake_world()) in play mode, or error-exit in replay
- * mode. If a record file exists, in play mode auto-replay it up to the last
- * game state before turning over to the player; in replay mode, auto-replay it
- * up to the turn named in world.replay and then turn over to manual replay.
+ * If replay mode is called for, try for the record file and follow its commands
+ + up to the turn specified by the user, then enter manual replay. Otherwise,
+ * start into play mode after having either recreated a game world state from
+ * the savefile, or, if none exists, having created a new world with the
+ * MAKE_WORLD command. Manual replay as well as play mode take place inside
+ * io_loop().
*/
extern void run_game();
#include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(),
* try_fwrite(), try_fputc(), try_fgetc()
*/
+#include "../common/rexit.h" /* exit_trouble() */
#include "../common/try_malloc.h" /* try_malloc() */
#include "cleanup.h" /* set_cleanup_flag() */
#include "field_of_view.h" /* VISIBLE */
+#include "hardcoded_strings.h" /* s */
#include "map.h" /* yx_to_map_pos() */
#include "things.h" /* Thing, ThingType, get_thing_type(), get_player() */
#include "world.h" /* global world */
+/* Write to "file" god commands (one per line) to recreate thing "t". */
+static void write_key_value(FILE * file, char * key, uint32_t value);
+
+/* Write to "file" \n-delimited line of "key" + space + "value" as string. */
+static void write_thing(FILE * file, struct Thing * t);
+
/* Cut out and return first \0-terminated string from world.queue and
* appropriately reduce world.queue_size. Return NULL if queue is empty.
* Superfluous \0 bytes after the string are also cut out. Should the queue
+static void write_key_value(FILE * file, char * key, uint32_t value)
+{
+ char * f_name = "write_key_value()";
+ try_fwrite(key, strlen(key), 1, file, f_name);
+ try_fputc(' ', file, f_name);
+ char * line = try_malloc(11, f_name);
+ exit_trouble(-1 == sprintf(line, "%u", value), f_name, "sprintf()");
+ try_fwrite(line, strlen(line), 1, file, f_name);
+ free(line);
+ try_fputc('\n', file, f_name);
+}
+
+
+
+static void write_thing(FILE * file, struct Thing * t)
+{
+ char * f_name = "write_thing()";
+ struct Thing * o;
+ for (o = t->owns; o; o = o->next)
+ {
+ write_thing(file, o);
+ }
+ write_key_value(file, s[CMD_THING], t->id);
+ write_key_value(file, s[CMD_TYPE], t->type);
+ write_key_value(file, s[CMD_POS_Y], t->pos.y);
+ write_key_value(file, s[CMD_POS_X], t->pos.x);
+ write_key_value(file, s[CMD_COMMAND], t->command);
+ write_key_value(file, s[CMD_ARGUMENT], t->arg);
+ write_key_value(file, s[CMD_PROGRESS], t->progress);
+ write_key_value(file, s[CMD_LIFEPOINTS], t->lifepoints);
+ for (o = t->owns; o; o = o->next)
+ {
+ write_key_value(file, s[CMD_CARRIES], o->id);
+ }
+ try_fputc('\n', file, f_name);
+}
+
+
+
static char * get_message_from_queue()
{
char * f_name = "get_message_from_queue()";
static void update_worldstate_file()
{
char * f_name = "update_worldstate_file()";
- char path_tmp[strlen(world.path_worldstate) + strlen(world.tmp_suffix) + 1];
- sprintf(path_tmp, "%s%s", world.path_worldstate, world.tmp_suffix);
+ char path_tmp[strlen(s[PATH_WORLDSTATE]) + strlen(s[PATH_SUFFIX_TMP]) + 1];
+ sprintf(path_tmp, "%s%s", s[PATH_WORLDSTATE], s[PATH_SUFFIX_TMP]);
FILE * file = try_fopen(path_tmp, "w", f_name);
struct Thing * player = get_player();
write_value_as_line(world.turn, file);
{
try_fwrite(world.log, strlen(world.log), 1, file, f_name);
}
- try_fclose_unlink_rename(file, path_tmp, world.path_worldstate, f_name);
+ try_fclose_unlink_rename(file, path_tmp, s[PATH_WORLDSTATE], f_name);
set_cleanup_flag(CLEANUP_WORLDSTATE);
char * dot = ".\n";;
try_fwrite(dot, strlen(dot), 1, world.file_out, f_name);
uint32_t map_size = world.map.length * world.map.length;
char * visible_map = try_malloc(map_size, f_name);
memset(visible_map, ' ', map_size);
- uint16_t pos_i;
- for (pos_i = 0; pos_i < map_size; pos_i++)
- {
- if (player->fov_map[pos_i] & VISIBLE)
+ if (player->fov_map) /* May fail if player thing was created / positioned */
+ { /* by god command after turning off FOV building. */
+ uint16_t pos_i;
+ for (pos_i = 0; pos_i < map_size; pos_i++)
{
- visible_map[pos_i] = world.map.cells[pos_i];
+ if (player->fov_map[pos_i] & VISIBLE)
+ {
+ visible_map[pos_i] = world.map.cells[pos_i];
+ }
}
- }
- struct Thing * t;
- struct ThingType * tt;
- char c;
- uint8_t i;
- for (i = 0; i < 2; i++)
- {
- for (t = world.things; t != 0; t = t->next)
+ struct Thing * t;
+ struct ThingType * tt;
+ char c;
+ uint8_t i;
+ for (i = 0; i < 2; i++)
{
- if ( player->fov_map[yx_to_map_pos(&t->pos)] & VISIBLE
- && ( (0 == i && 0 == t->lifepoints)
- || (1 == i && 0 < t->lifepoints)))
+ for (t = world.things; t != 0; t = t->next)
{
- tt = get_thing_type(t->type);
- c = tt->char_on_map;
- visible_map[yx_to_map_pos(&t->pos)] = c;
+ if ( player->fov_map[yx_to_map_pos(&t->pos)] & VISIBLE
+ && ( (0 == i && 0 == t->lifepoints)
+ || (1 == i && 0 < t->lifepoints)))
+ {
+ tt = get_thing_type(t->type);
+ c = tt->char_on_map;
+ visible_map[yx_to_map_pos(&t->pos)] = c;
+ }
}
}
}
}
return get_message_from_queue();
}
+
+
+
+extern void save_world()
+{
+ char * f_name = "save_world()";
+ char * path = s[PATH_SAVE];
+ FILE * file = try_fopen(path, "w", f_name);
+ write_key_value(file, s[CMD_DO_FOV], 0);
+ try_fputc('\n', file, f_name);
+ write_key_value(file, s[CMD_SEED_MAP], world.seed_map);
+ write_key_value(file, s[CMD_SEED_RAND], world.seed);
+ write_key_value(file, s[CMD_TURN], world.turn);
+ try_fputc('\n', file, f_name);
+ struct Thing * t;
+ for (t = world.things; t; t = t->next)
+ {
+ write_thing(file, t);
+ }
+ write_key_value(file, s[CMD_DO_FOV], 1);
+ try_fclose(file, f_name);
+}
-/* io.h:
+/* io.h
*
* Communication of the server with the outside world and its client via input,
* output and world state files.
/* Return single \0-terminated string read from input queue (world.queue); or,
* if queue is empty and world.turn is unequal world.last_update_turn, update
- * world state file at world.path_worldstate (and update world.last_update_turn
- * and write a single dot line to output file at world.path_out), then read file
- * at world.path_in for the next load of bytes to put onto the input queue.
+ * world state file (and world.last_update_turn) and write a single dot line to
+ * server out file, then read server in file for the next load of bytes to put
+ * onto the input queue.
*
- * Reading the file at world.path_in may put many \0-terminated strings on the
- * queue at once. Successive calls of io_round() will make these available one
- * by one. Each such call cuts off bytes from the beginning of world.queue, up
- * to and including the last \0 byte that is followed by a non-\0 byte or ends
- * the queue. If the queue starts with a \0 byte, it and its \0 followers are
- * cut and a NULL pointer is returned. Reading from the input file stops only
- * when one or more byte were read and the next read returns 0 bytes. If the
+ * Reading the server in file may put many \0-terminated strings on the queue at
+ * once. Successive calls of io_round() will make these available one by one.
+ * Each such call cuts off bytes from the beginning of world.queue, up to and
+ * including the last \0 byte that is followed by a non-\0 byte or ends the
+ * queue. If the queue starts with a \0 byte, it and its \0 followers are cut
+ * and a NULL pointer is returned. Reading from the input file stops only when
+ * one or more byte were read and the next read returns 0 bytes. If the
* re-filled queue does not end in a \0 byte, a \0 byte is appended to it.
*/
extern char * io_round();
+/* Write to savefile god commands (one per line) to rebuild the current world
+ * state.
+ */
+extern void save_world();
+
#endif
#include "../common/rexit.h" /* exit_err, set_cleanup_func() */
#include "cleanup.h" /* set_cleanup_flag(), cleanup() */
#include "configfile.h" /* read_config_file() */
+#include "hardcoded_strings.h" /* s */
#include "init.h" /* run_game(), obey_argv(), obey_argv(), setup_server_io() */
#include "world.h" /* struct World */
set_cleanup_func(cleanup);
/* Init settings from command line / hard-coded values. Print start info. */
+ init_strings();
obey_argv(argc, argv);
if (world.is_verbose)
{
exit_err(-1 == test, printf_err);
}
}
- world.path_config = "confserver/world";
- world.path_worldstate = "server/worldstate";
- world.path_out = "server/out";
- world.path_in = "server/in";
- world.path_record = "record";
- world.tmp_suffix = "_tmp";
/* Init config file and server i/o files. */
read_config_file();
#include "map.h"
#include <stdint.h> /* uint8_t, uint16_t, uint32_t, UINT16_MAX */
+#include <stdlib.h> /* free() */
#include "../common/rexit.h" /* exit_err() */
#include "../common/try_malloc.h" /* try_malloc() */
#include "../common/yx_uint8.h" /* struct yx_uint8 */
-extern void init_map()
+extern void remake_map()
{
char * f_name = "init_map()";
+ free(world.map.cells);
world.map.cells = try_malloc(world.map.length * world.map.length, f_name);
+ uint32_t store_seed = world.seed;
+ world.seed = world.seed_map;
make_sea();
make_island();
make_trees();
+ world.seed = store_seed;
}
-/* Initialize island map "~" cells representing water and "." cells representing
- * land. The island shape is built randomly by starting with a sea of one land
- * cell in the middle, then going into a cycle of repeatedly selecting a random
- * seal cell and transforming it into land if it is neighbor to land; the cycle
- * ends when a land cell is due to be created right at the border of the map.
- * Lots of 'X' cells representing trees are put on the island, too.
+/* (Re-)make island map "~" cells representing water and "." cells representing
+ * land. The island shape is built randomly from world.seed_map by starting with
+ * a sea of one land cell in the middle, then going into a cycle of repeatedly
+ * selecting a random sea cell and transforming it into land if it is neighbor
+ * to land; the cycle ends when a land cell is due to be created right at the
+ * border of the map. Lots of 'X' cells representing trees are put on the island.
*/
-extern void init_map();
+extern void remake_map();
/* Check if coordinate "pos" on (or beyond) world.map is accessible to thing
* movement.
/* src/server/run.c */
+#define _POSIX_C_SOURCE 200809L
#include "run.h"
#include <stddef.h> /* NULL */
#include <stdint.h> /* uint8_t, uint16_t, uint32_t */
#include <stdio.h> /* FILE, sprintf(), fflush() */
-#include <stdlib.h> /* free() */
-#include <string.h> /* strlen(), strcmp() strncmp(), atoi() */
+#include <stdlib.h> /* free(), atoi() */
+#include <string.h> /* strlen(), strcmp() strncmp(), strdup() */
#include <unistd.h> /* access() */
+#include "../common/parse_file.h" /* set_err_line_options(), token_from_line(),
+ * err_line()
+ */
#include "../common/readwrite.h" /* try_fopen(), try_fcose(), try_fwrite(),
* try_fgets(), try_fclose_unlink_rename(),
* textfile_width(), try_fputc()
*/
#include "../common/rexit.h" /* exit_trouble(), exit_err() */
#include "ai.h" /* ai() */
-#include "cleanup.h" /* unset_cleanup_flag() */
+#include "cleanup.h" /* set_cleanup_flag(), unset_cleanup_flag() */
+#include "field_of_view.h" /* build_fov_map() */
+#include "hardcoded_strings.h" /* s */
#include "init.h" /* remake_world() */
-#include "io.h" /* io_round() */
-#include "thing_actions.h" /* get_thing_action_id_by_name() */
-#include "things.h" /* Thing, get_player() */
+#include "io.h" /* io_round(), save_world() */
+#include "map.h" /* remake_map() */
+#include "thing_actions.h" /* ThingAction */
+#include "things.h" /* Thing, get_thing(), own_thing(), add_thing(),
+ * get_thing_action_id_by_name(), get_player()
+ */
#include "world.h" /* global world */
+/* Parse/apply god command in "tok0"/"tok1" on "t" owning another thing. */
+static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t);
+
+/* Parse/apply god commansd in "tok0"/"tok1" on positioning a thing "t". */
+static uint8_t parse_position(char* tok0, char * tok1, struct Thing * t);
+
+/* Parse/apply god command in "tok0"/"tok1" oo setting "t"'s thing type. */
+static uint8_t parse_thing_type(char * tok0, char * tok1, struct Thing * t);
+
+/* Parse/apply god command in "tok0"/"tok1" on setting up thing "t". */
+static uint8_t parse_thing_command(char * tok0, char * tok1, struct Thing * t);
+
+/* Parse/apply god command on enabling/disabling generation of fields of view on
+ * god commands that may affect them, via static global "do_fov". On enabling,
+ * (re-)generate all animate things' fields of view.
+ */
+static uint8_t parse_do_fov(char * tok0, char * tok1);
+
+/* Parse/apply god command in "tok0"/"tok1" manipulating a thing's state. */
+static uint8_t parse_thing_manipulation(char * tok0, char * tok1);
+
+/* Parse player command in "tok0"/"tok1" to action in player thing. */
+static uint8_t parse_player_command(char * tok0, char * tok1);
+
+/* Compares first line of server out file to world.server_test, aborts if they
+ * don't match, but not before unsetting the flags deleting files in the server
+ * directory, for in that case those must be assumed to belong to another server
+ * process.
+ */
+static void server_test();
+
/* Run the game world and its inhabitants (and their actions) until the player
* avatar is free to receive new commands (or is dead).
*/
static void turn_over();
-/* If "msg"'s first part matches "command_name", set player's Thing's .command
- * to the command's id and its .arg to a numerical value following in the latter
- * part of "msg" (if no digits are found, use 0); then finish player's turn and
- * turn game over to the NPCs via turn_over(); then return 1. Else, return 0.
- */
-static uint8_t apply_player_command(char * msg, char * command_name);
-/* Compares first line of file at world.path_out to world.server_test, aborts if
- * they don't match, but not before unsetting the flags deleting files in the
- * server directory, for in that case those must be assumed to belong to another
- * server process.
- */
-static void server_test();
+
+/* Do god commands to create / position things generate their fields of view? */
+static uint8_t do_fov = 0;
+
+
+
+static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t)
+{
+ uint8_t id;
+ if (parse_val(tok0, tok1, s[CMD_CARRIES], '8', (char *) &id))
+ {
+ if (!err_line(id == t->id, "Thing cannot carry itself."))
+ {
+ struct Thing * o = get_thing(world.things, id, 0);
+ if (!err_line(!o, "Thing cannot carry thing that does not exist."))
+ {
+ own_thing(&(t->owns), &world.things, id);
+ o->pos = t->pos;
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static uint8_t parse_position(char* tok0, char * tok1, struct Thing * t)
+{
+ char axis = 0;
+ if (!strcmp(tok0, s[CMD_POS_Y]))
+ {
+ axis = 'y';
+ }
+ else if (!strcmp(tok0, s[CMD_POS_X]))
+ {
+ axis = 'x';
+ }
+ if (axis && !parsetest_int(tok1, '8'))
+ {
+ uint8_t length = atoi(tok1);
+ char * err = "Position is outside of map.";
+ if (!err_line(length >= world.map.length, err))
+ {
+ if ('y' == axis)
+ {
+ t->pos.y = length;
+ }
+ else if ('x' == axis)
+ {
+ t->pos.x = length;
+ }
+ free(t->fov_map);
+ t->fov_map = do_fov && t->lifepoints ? build_fov_map(t) : t->fov_map;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static uint8_t parse_thing_type(char * tok0, char * tok1, struct Thing * t)
+{
+ uint8_t type;
+ if (parse_val(tok0, tok1, s[CMD_TYPE], '8', (char *) &type))
+ {
+ struct ThingType * tt = world.thing_types;
+ for (; NULL != tt && type != tt->id; tt = tt->next);
+ if (!err_line(!tt, "Thing type does not exist."))
+ {
+ t->type = type;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static uint8_t parse_thing_command(char * tok0, char * tok1, struct Thing * t)
+{
+ uint8_t command;
+ if (parse_val(tok0, tok1, s[CMD_COMMAND], '8', (char *) &command))
+ {
+ if (!command)
+ {
+ t->command = command;
+ return 1;
+ }
+ struct ThingAction * ta = world.thing_actions;
+ for (; NULL != ta && command != ta->id; ta = ta->next);
+ if (!err_line(!ta, "Thing action does not exist."))
+ {
+ t->command = command;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static uint8_t parse_do_fov(char * tok0, char * tok1)
+{
+ if (parse_val(tok0, tok1, s[CMD_DO_FOV], '8', (char *) &do_fov))
+ {
+ if (do_fov)
+ {
+ struct Thing * ti;
+ for (ti = world.things; ti; ti = ti->next)
+ {
+ ti->fov_map = ti->lifepoints ? build_fov_map(ti) : ti->fov_map;
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static uint8_t parse_thing_manipulation(char * tok0, char * tok1)
+{
+ uint8_t id;
+ static struct Thing * t = NULL;
+ if (t && ( parse_thing_type(tok0, tok1, t)
+ || parse_thing_command(tok0, tok1, t)
+ || parse_val(tok0, tok1, s[CMD_ARGUMENT], '8', (char *) &t->arg)
+ || parse_val(tok0, tok1, s[CMD_PROGRESS], '8', (char *) &t->progress)
+
+ || parse_val(tok0, tok1, s[CMD_LIFEPOINTS],'8',(char *) &t->lifepoints)
+ || parse_position(tok0, tok1, t)
+ || parse_carry(tok0, tok1, t)));
+ else if (parse_val(tok0, tok1, s[CMD_THING], '8', (char *) &id))
+ {
+ t = get_thing(world.things, id, 1);
+ if (!t)
+ {
+ t = add_thing(id, 0, 0);
+ set_cleanup_flag(CLEANUP_THINGS);
+ t->fov_map = do_fov && t->lifepoints ? build_fov_map(t) : t->fov_map;
+ }
+ }
+ else
+ {
+ return 0;
+ }
+ return 1;
+}
+
+
+
+static uint8_t parse_player_command(char * tok0, char * tok1)
+{
+ struct Thing * player = get_player();
+ if ( parse_val(tok0, tok1, s[CMD_WAIT], '8', (char *) &player->arg)
+ || parse_val(tok0, tok1, s[CMD_MOVE], '8', (char *) &player->arg)
+ || parse_val(tok0, tok1, s[CMD_PICKUP], '8', (char *) &player->arg)
+ || parse_val(tok0, tok1, s[CMD_DROP], '8', (char *) &player->arg)
+ || parse_val(tok0, tok1, s[CMD_USE], '8', (char *) &player->arg))
+ {
+ player->command = get_thing_action_id_by_name(tok0);
+ turn_over();
+ }
+ else
+ {
+ return 0;
+ }
+ return 1;
+}
+
+
+
+static void server_test()
+{
+ char * f_name = "server_test()";
+ char test[10 + 1 + 10 + 1 + 1];
+ FILE * file = try_fopen(s[PATH_OUT], "r", f_name);
+ try_fgets(test, 10 + 10 + 1 + 1, file, f_name);
+ try_fclose(file, f_name);
+ if (strcmp(test, world.server_test))
+ {
+ unset_cleanup_flag(CLEANUP_WORLDSTATE);
+ unset_cleanup_flag(CLEANUP_OUT);
+ unset_cleanup_flag(CLEANUP_IN);
+ char * msg = "Server test string in server output file does not match. "
+ "This indicates that the current server process has been "
+ "superseded by another one.";
+ exit_err(1, msg);
+ }
+}
-static uint8_t apply_player_command(char * msg, char * command_name)
+static void record_msg(char * msg)
{
- if (!strncmp(msg, command_name, strlen(command_name)))
+ char * f_name = "record_msg()";
+ char path_tmp[strlen(s[PATH_RECORD]) + strlen(s[PATH_SUFFIX_TMP]) + 1];
+ sprintf(path_tmp, "%s%s", s[PATH_RECORD], s[PATH_SUFFIX_TMP]);
+ FILE * file_tmp = try_fopen(path_tmp, "w", f_name);
+ if (!access(s[PATH_RECORD], F_OK))
{
- struct Thing * player = get_player();
- player->arg = atoi(&(msg[strlen(command_name)]));
- player->command = get_thing_action_id_by_name(command_name);
- turn_over();
- return 1;
+ FILE * file_read = try_fopen(s[PATH_RECORD], "r", f_name);
+ uint32_t linemax = textfile_width(file_read);
+ char line[linemax + 1];
+ while (try_fgets(line, linemax + 1, file_read, f_name))
+ {
+ try_fwrite(line, strlen(line), 1, file_tmp, f_name);
+ }
+ try_fclose(file_read, f_name);
}
- return 0;
+ try_fwrite(msg, strlen(msg), 1, file_tmp, f_name);
+ try_fputc('\n', file_tmp, f_name);
+ try_fclose_unlink_rename(file_tmp, path_tmp, s[PATH_RECORD], f_name);
}
-static void server_test()
+extern void obey_msg(char * msg, uint8_t do_record)
{
- char * f_name = "server_test()";
- char test[10 + 1 + 10 + 1 + 1];
- FILE * file = try_fopen(world.path_out, "r", f_name);
- try_fgets(test, 10 + 10 + 1 + 1, file, f_name);
- try_fclose(file, f_name);
- if (strcmp(test, world.server_test))
+ set_err_line_options("Trouble with message: ", msg, 0, 0);
+ char * msg_copy = strdup(msg);
+ char * tok0 = token_from_line(msg_copy);
+ char * tok1 = token_from_line(NULL);
+ char * tok2 = token_from_line(NULL);
+ if (err_line(!(tok0 && tok1) || tok2, "Bad number of tokens."))
{
- unset_cleanup_flag(CLEANUP_WORLDSTATE);
- unset_cleanup_flag(CLEANUP_OUT);
- unset_cleanup_flag(CLEANUP_IN);
- char * msg = "Server test string in server output file does not match. "
- "This indicates that the current server process has been "
- "superseded by another one.";
- exit_err(1, msg);
+ return;
}
-}
+ if ( parse_thing_manipulation(tok0, tok1)
+ || parse_player_command(tok0, tok1)
+ || parse_val(tok0, tok1, s[CMD_SEED_RAND], 'U', (char *) &world.seed)
+ || parse_val(tok0, tok1, s[CMD_TURN], 'u', (char *) &world.turn)
+ || parse_do_fov(tok0, tok1));
+ else if (parse_val(tok0, tok1, s[CMD_SEED_MAP], 'U', (char *) &world.seed_map))
-
-
-extern void obey_msg(char * msg, uint8_t do_record)
-{
- char * f_name = "obey_msg()";
- if ( apply_player_command(msg, "wait") /* TODO: Check for non-error */
- || apply_player_command(msg, "move") /* return value of a modified */
- || apply_player_command(msg, "pick_up")/*get_thing_action_id_by_name()*/
- || apply_player_command(msg, "drop") /* and if id found, execute on */
- || apply_player_command(msg, "use")); /* it what's in */
- else /* apply_player_command(). */
- {
- char * seed_command = "seed";
- if (!strncmp(msg, seed_command, strlen(seed_command)))
- {
- remake_world(atoi(&(msg[strlen(seed_command)])));
- }
+ {
+ remake_map();
+ }
+ else if (parse_val(tok0, tok1, s[CMD_MAKE_WORLD], 'U', (char *) &world.seed))
+ {
+ remake_world();
+ }
+ else
+ {
+ err_line(1, "Unknown command.");
+ free(msg_copy);
+ return;
}
+ world.last_update_turn = 0;
+ free(msg_copy);
if (do_record)
{
- char path_tmp[strlen(world.path_record) + strlen(world.tmp_suffix) + 1];
- sprintf(path_tmp, "%s%s", world.path_record, world.tmp_suffix);
- FILE * file_tmp = try_fopen(path_tmp, "w", f_name);
- if (!access(world.path_record, F_OK))
- {
- FILE * file_read = try_fopen(world.path_record, "r", f_name);
- uint32_t linemax = textfile_width(file_read);
- char line[linemax + 1];
- while (try_fgets(line, linemax + 1, file_read, f_name))
- {
- try_fwrite(line, strlen(line), 1, file_tmp, f_name);
- }
- try_fclose(file_read, f_name);
- }
- try_fwrite(msg, strlen(msg), 1, file_tmp, f_name);
- try_fputc('\n', file_tmp, f_name);
- try_fclose_unlink_rename(file_tmp, path_tmp, world.path_record, f_name);
+ save_world();
+ record_msg(msg);
}
}
-/* Try parsing "msg" into a server or player command to run. Player commands are
- * are recorded into the record file at world.path_record if "do_record" is set.
+/* Try parsing "msg" into a command to apply, and apply it. Record commands to
+ * the file at world.path_record if "do_record" is set.
*/
extern void obey_msg(char * msg, uint8_t do_record);
* on "QUIT" command. In replay mode, exits with 0 on each non-"QUIT" command.
* Writes a "PONG" line to server output file on "PING" command. In play mode,
* processes further incomming commands via obey_msg(). Compares the first line
- * of the file at world.path_out with world.server_test to ensure that the
- * current server process has not been superseded by a new one.
+ * of the server out file with world.server_test to ensure that the current
+ * server process has not been superseded by a new one.
*/
extern uint8_t io_loop();
#include "things.h"
#include <stddef.h> /* NULL */
-#include <stdint.h> /* uint8_t, uint16_t, UINT16_MAX */
+#include <stdint.h> /* uint8_t, uint16_t, UINT8_MAX, UINT16_MAX */
#include <stdlib.h> /* free() */
#include <string.h> /* memset(), strlen() */
#include "../common/rexit.h" /* exit_err() */
#include "../common/try_malloc.h" /* try_malloc() */
-#include "../common/yx_uint8.h" /* yx_uint8 struct */
+#include "../common/yx_uint8.h" /* yx_uint8 */
#include "map.h" /* is_passable() */
#include "rrand.h" /* rrand() */
#include "world.h" /* global world */
-/* Return pointer to thing of "id" in chain starting at "ptr". */
-static struct Thing * get_thing(struct Thing * ptr, uint8_t id);
-/* Add thing of "type" to map on passable position. Don't put actor on actor. */
-static void add_thing(uint8_t type);
+/* Return lowest unused id for new thing. */
+static uint8_t get_lowest_unused_id();
-static struct Thing * get_thing(struct Thing * ptr, uint8_t id)
+static uint8_t get_lowest_unused_id()
+{
+ uint8_t i = 0;
+ while (1)
+ {
+ if (!get_thing(world.things, i, 1))
+ {
+ return i;
+ }
+ exit_err(i == UINT8_MAX, "No unused ID available to add new thing.");
+ i++;
+ }
+}
+
+
+
+extern struct Thing * get_thing(struct Thing * ptr, uint8_t id, uint8_t deep)
{
while (1)
{
{
return ptr;
}
- struct Thing * owned_thing = get_thing(ptr->owns, id);
- if (NULL != owned_thing)
+ if (deep)
{
- return ptr;
+ struct Thing * owned_thing = get_thing(ptr->owns, id, 1);
+ if (NULL != owned_thing)
+ {
+ return ptr;
+ }
}
ptr = ptr->next;
}
-static void add_thing(uint8_t type)
+extern void free_thing_types(struct ThingType * tt_start)
+{
+ if (NULL == tt_start)
+ {
+ return;
+ }
+ free_thing_types(tt_start->next);
+ free(tt_start->name);
+ free(tt_start);
+}
+
+
+
+extern struct Thing * add_thing(int16_t id, uint8_t type, uint8_t find_pos)
{
char * f_name = "add_thing()";
struct ThingType * tt = get_thing_type(type);
struct Thing * t = try_malloc(sizeof(struct Thing), f_name);
memset(t, 0, sizeof(struct Thing));
- t->id = world.thing_count++;
+ t->id = (0 <= id && id <= UINT8_MAX) ? id : get_lowest_unused_id();
t->type = tt->id;
t->lifepoints = tt->lifepoints;
char * err = "Space to put thing on too hard to find. Map too small?";
uint16_t i = 0;
- while (1)
+ memset(&(t->pos), 0, sizeof(struct yx_uint8));
+ while (find_pos)
{
struct yx_uint8 pos;
for (pos.y = pos.x = 0; 0 == is_passable(pos); i++)
struct Thing ** t_ptr_ptr = &world.things;
for (; NULL != * t_ptr_ptr; t_ptr_ptr = &(*t_ptr_ptr)->next);
* t_ptr_ptr = t;
-}
-
-
-
-extern void free_thing_types(struct ThingType * tt_start)
-{
- if (NULL == tt_start)
- {
- return;
- }
- free_thing_types(tt_start->next);
- free(tt_start->name);
- free(tt_start);
+ return t;
}
uint8_t i;
for (i = 0; i < n; i++)
{
- add_thing(type);
+ add_thing(-1, type, 1);
}
}
extern struct Thing * get_player()
{
- return get_thing(world.things, 0);
+ return get_thing(world.things, 0, 1);
}
struct Thing
{
- struct Thing * next; /* pointer to next one in things chain */
- struct Thing * owns; /* chain of things owned / in inventory */
+ struct Thing * next; /* pointer to next one in things chain */
+ struct Thing * owns; /* chain of things owned / in inventory */
struct yx_uint8 pos; /* coordinate on map */
uint8_t * fov_map; /* map of the thing's field of view */
uint8_t id; /* individual thing's unique identifier */
+/* Return thing of "id" in chain at "ptr", search inventories too if "deep". */
+extern struct Thing * get_thing(struct Thing * ptr, uint8_t id, uint8_t deep);
+
/* Free thing types chain starting at "tt_start". */
extern void free_thing_types(struct ThingType * tt_start);
+/* Add thing of "id" and "type" to map on random passable position (positions
+ * which contain an actor are not deemed passable) if "find_pos", else on y=0,
+ * x=0. If "id" is >= 0 and <= UINT8_MAX, use lowest unused id. Return thing.
+ */
+extern struct Thing * add_thing(int16_t id, uint8_t type, uint8_t find_pos);
+
/* Add thing(s) ("n": how many?) of "type" to map on random position(s). New
* animate things are never placed in the same square with other animate ones.
*/
{
FILE * file_in; /* Input stream on file at .path_in. */
FILE * file_out; /* Output stream on file at .path_out. */
- struct Map map;
+ struct Map map; /* Game map. */
struct ThingType * thing_types; /* Thing type definitions. */
struct ThingAction * thing_actions; /* Thing action definitions. */
struct Thing * things; /* All physical things of the game world. */
char * log; /* Logs the game events from the player's view. */
char * server_test; /* String uniquely identifying server process. */
- char * path_in; /* File to write client messages into. */
- char * path_out; /* File to write server messages into. */
- char * path_worldstate; /* File to represent world state to clients.*/
- char * path_record; /* Record file from which to read the game history. */
- char * path_config; /* Path for thing type / action definitions file. */
- char * tmp_suffix; /* Appended to paths of files for their tmp versions. */
char * queue; /* Stores un-processed messages read from the input file. */
uint32_t queue_size;/* Length of .queue sequence of \0-terminated strings.*/
uint32_t seed; /* Randomness seed. */
+ uint32_t seed_map; /* Map seed. */
uint16_t replay; /* Turn up to which to replay game. No replay if zero. */
uint16_t turn; /* Current game turn. */
uint16_t last_update_turn; /* Last turn the .path_out file was updated. */
uint8_t player_type; /* Thing type that player will start as. */
uint8_t is_verbose; /* Should server send debugging info to stdout? */
- uint8_t thing_count; /* Counts things generated so far. */
uint8_t enemy_fov; /* != 0 if non-player actors only see field of view. */
};