plomlompom has insanely ambitious long-term plans).
You can move around a player on an island and meet different enemies. You have 5
-hitpoints to lose before death; they start with different amounts of hitpoints,
-depending on their species. Dead enemies become dirt, skeletons or "magic meat"
--- such objects can be collected, and "magic meat" can be consumed to gain
-hitpoints. Note that different kinds of movements/actions take different numbers
-of turns to finish.
+hitpoints to lose before death. Enemies start with different amounts of
+hitpoints, depending on their species. Dead enemies become dirt, skeletons or
+"magic meat" -- such objects can be collected, and "magic meat" can be consumed
+to gain hitpoints. Note that different kinds of movements/actions take different
+numbers of turns to finish.
Enemies' AI is very dumb so far: Each turn, they try to move towards their
-path-wise nearest enemy. If no enemy is found in their surroundings, they just
-wait.
+shortest-path-wise nearest enemy. If no enemy is found in their surroundings,
+they just wait.
-Note that diagonal movement is possible, but takes (40%) longer than orthogonal
-movement.
+Diagonal movement is possible, but takes (40%) longer than orthogonal movement.
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
(It may also work on other Unix-like systems with ncurses, who knows.)
-Note that make generates two executables ./roguelike-server and
-./roguelike-client. ./roguelike is a pre-existing shell script that merely
-executes both of them, with the server as a background job. You can also
-ignore the script and start the two by hand.
+Make generates two executables ./roguelike-server and ./roguelike-client.
+./roguelike is a pre-existing shell script that merely executes both of them,
+with the server as a background job. You can also ignore the script and start
+the two by hand.
Client's keybindings and window management
------------------------------------------
plomrogue's internals. The ./roguelike-server executable can be run with a -v
option for helpful debugging info (mostly: what messages the client sends to the
server). Server and client communicate via files in the ./server/ directory
-(generated when the server is first run). The ./server/in fifo receives commands
-for the server as null-terminated strings. The ./server/out file contains a
-serialized representation of the game world's data as it is to be available to
+(generated when the server is first run). The ./server/in file is read by the
+server for newline-delimited commands. The ./server/out file contains server
+messages to be read by clients. The ./server/worldstate file contains a
+serialized representation of the game world's data as it is to be visible to
the player / the player's client.
BOTH SERVER/CLIENT:
-- implement better server/client architecture, meeting these criteria:
- - keep interaction of server and client in the filesystem (as currently
- happens by use of plain text files and fifos) to allow for easy scripting
- - allow for specific requests of the client for specific server data (such as:
- "what path do you propose for the player character to get from A to B?")
- - reduce hard disk i/o for server/client interaction (use fifos only?)
- - further avoid exits that keep the server running and the lock file existing
- when it shouldn't
+- make server and client communicate by specific world state info requests
+ in server/out, replacing server/worldstate
- check for return values of *printf()
- make config files format more readable / self-explanatory
+- get rid of the useless "internal error code" in error messages
+
SERVER:
- implement field of view / line of sight and obstacles for those on the map
sleep 0.01
# The client should not start if the server is not running. (If the server was
-# running in the foreround, any error exit of it so far would be caught by "set
+# running in the foreground, any error exit of it so far would be caught by "set
# -e" above. But "set -e" is blind to error codes generated in the background.)
kill -0 $! 2> /dev/null
-# Give server some time (max. 10 seconds) to generate its out file.
+# Give server some time (max. 10 seconds) to generate its worldstate file.
i=0
-while [ ! -e server/out ] && [ $i -le 1000 ]
+while [ ! -e server/worldstate ] && [ $i -le 1000 ]
do
sleep 0.01
i=`expr $i + 1`
done
-if [ ! -e server/out ]
+if [ ! -e server/worldstate ]
then
- echo "Server failed generating outfile within given time limit."
+ echo "Server failed generating worldstate file within given time limit."
false
fi
#include <ncurses.h> /* for endwin() */
#include <stdint.h> /* uint32_t */
#include <stdlib.h> /* free() */
+#include "../common/readwrite.h" /* try_fclose() */
#include "command_db.h" /* free_command_db() */
#include "misc.h" /* unload_interface_conf() */
#include "world.h" /* world global */
extern void cleanup()
{
+ char * f_name = "cleanup()";
free(world.map.cells);
free(world.log);
free(world.player_inventory);
{
free_command_db();
}
+ if (cleanup_flags & CLEANUP_SERVER_IN)
+ {
+ try_fclose(world.file_server_in, f_name);
+ }
+ if (cleanup_flags & CLEANUP_SERVER_OUT)
+ {
+ try_fclose(world.file_server_out, f_name);
+ }
}
{
CLEANUP_NCURSES = 0x0001,
CLEANUP_INTERFACE = 0x0002,
- CLEANUP_COMMANDS = 0x0004
+ CLEANUP_COMMANDS = 0x0004,
+ CLEANUP_SERVER_IN = 0x0008,
+ CLEANUP_SERVER_OUT = 0x0008
};
extern void set_cleanup_flag(enum cleanup_flag flag);
#include <stdint.h> /* uint8_t, uint16_t */
#include <stdio.h> /* sprintf() */
#include <string.h> /* strlen() */
-#include "io.h" /* try_send() */
+#include "io.h" /* send() */
#include "keybindings.h" /* get_command_to_keycode(), get_keycode_to_command(),
* mod_selected_keyb(), move_keyb_selection()
*/
uint8_t arg_size = 3;
char msg[command_size + 1 + arg_size + 1];
sprintf(msg, "%s %d", command->server_msg, arg);
- try_send(msg);
+ send(msg);
return 1;
}
return 0;
/* src/client/io.c */
#include "io.h"
-#include <errno.h> /* global errno */
-#include <fcntl.h> /* open() */
#include <limits.h> /* PIPE_BUF */
#include <ncurses.h> /* halfdelay(), getch() */
#include <stddef.h> /* NULL */
#include <stdint.h> /* uint8_t, uint16_t, uint32_t */
-#include <stdio.h> /* FILE, sprintf(), fseek() */
+#include <stdio.h> /* FILE, sprintf(), fseek(), fflush() */
#include <string.h> /* strcmp(), strlen(), memcpy() */
#include <stdlib.h> /* free(), atoi() */
#include <sys/stat.h> /* stat() */
-#include <unistd.h> /* access(), write() */
+#include <sys/types.h> /* time_t */
+#include <time.h> /* time() */
+#include <unistd.h> /* access() */
#include "../common/try_malloc.h" /* try_malloc() */
#include "../common/rexit.h" /* exit_trouble(), exit_err() */
#include "../common/readwrite.h" /* try_fopen(), try_fclose(), try_fgets(),
- * try_fgetc(), textfile_width()
+ * try_fgetc(), textfile_width(), try_fputc()
*/
#include "control.h" /* try_key() */
#include "map.h" /* map_center() */
static uint16_t read_value_from_line(char * read_buf, uint32_t linemax,
FILE * file);
-/* If the server's out file has changed since the last read_world(), return a
- * pointer to its file descriptor; else, return NULL.
+/* If the server's worldstate file has changed since the last read_world(),
+ * return a pointer to its file descriptor; else, return NULL.
*
* Two tests are performed to check for a file change. The file's last data
* modification time in seconds via stat() is compared against world.last_update
* the new world also starts in turn 1, not signifying any world change to the
* turn check. The stat() check detects this change with at most 1 second delay.
*/
-static FILE * changed_server_out_file(char * path);
+static FILE * changed_worldstate_file(char * path);
-/* Attempt to read the server's out file as representation of the game world in
- * a hard-coded serialization format. Returns 1 on success and 0 if the out file
- * wasn't read for supposedly not having changed since a last read_world() call.
+/* Attempt to read the server's worldstate file as representation of the game
+ * world in a hard-coded serialization format. Returns 1 on success and 0 if the
+ * out file wasn't read for supposedly not having changed since a last
+ * read_world() call.
*
* map_center() is triggered by the first successful read_world() or on turn 1,
* so the client focuses the map window on the player on client and world start.
*/
static uint8_t read_world();
+/* If "last_server_answer_time" is too old, send a PING to the server; or, if a
+ * previous PING has not sparked any answer after a while, abort the client.
+ */
+static void test_ping_pong(time_t last_server_answer_time);
+
+/* Update "last_server_answer_time" if new stuff has been written to the
+ * server's out file.
+ */
+static void test_server_activity(time_t * last_server_answer_time);
+
static void read_inventory(char * read_buf, uint32_t linemax, FILE * file)
-static FILE * changed_server_out_file(char * path)
+static FILE * changed_worldstate_file(char * path)
{
- char * f_name = "changed_server_out_file()";
+ char * f_name = "changed_worldstate_file()";
struct stat stat_buf;
exit_trouble(stat(path, &stat_buf), f_name, "stat()");
if (stat_buf.st_mtime != world.last_update)
static uint8_t read_world()
{
char * f_name = "read_world()";
- char * path = "server/out";
- char * quit_msg = "No server out file found to read. Server may be down.";
+ char * path = "server/worldstate";
+ char * quit_msg = "No worldstate file found to read. Server may be down.";
static uint8_t first_read = 1;
exit_err(access(path, F_OK), quit_msg);
- FILE * file = changed_server_out_file(path);
+ FILE * file = changed_worldstate_file(path);
if (!file)
{
return 0;
-extern void try_send(char * msg)
+static void test_ping_pong(time_t last_server_answer_time)
{
- char * f_name = "try_send()";
- uint32_t msg_size = strlen(msg) + 1;
- char * err = "try_send() tries to send message larger than PIPE_BUF bytes.";
- exit_err(msg_size > PIPE_BUF, err);
- int fd_out;
- uint16_t j = 1;
- while (0 != j)
+ static uint8_t ping_sent = 0;
+ time_t now = time(0);
+ if (ping_sent && last_server_answer_time > now - 3)
{
- fd_out = open(world.path_server_in, O_WRONLY | O_NONBLOCK);
- if (fd_out > 0)
- {
- break;
- }
- exit_err(-1 == fd_out && ENXIO != errno, "Server fifo not found.");
- j++;
+ ping_sent = 0;
}
- exit_err(0 == j, "Failed to open server fifo for writing.");
- j = 1;
- while (0 != j)
+ if (!ping_sent && last_server_answer_time < now - 3)
{
- int test = write(fd_out, msg, msg_size);
- if (test > 0)
- {
- break;
- }
- j++;
+ send("PING");
+ ping_sent = 1;
+ return;
+ }
+ exit_err(last_server_answer_time < now - 6, "Server not answering.");
+}
+
+
+
+static void test_server_activity(time_t * last_server_answer_time)
+{
+ char * f_name = "test_server_activity()";
+ int test = try_fgetc(world.file_server_out, f_name);
+ if (EOF == test)
+ {
+ return;
+ }
+ do
+ {
+ ;
}
- exit_err(0 == j, "Failed to write to server fifo.");
- exit_trouble(-1 == close(fd_out), f_name, "close()");
+ while (EOF != (test = try_fgetc(world.file_server_out, f_name)));
+ * last_server_answer_time = time(0);
+}
+
+
+
+extern void send(char * msg)
+{
+ char * f_name = "send()";
+ uint32_t msg_size = strlen(msg) + 1;
+ char * err = "send() tries to send message larger than PIPE_BUF bytes.";
+ exit_err(msg_size > PIPE_BUF, err);
+ try_fwrite(msg, strlen(msg), 1, world.file_server_in, f_name);
+ try_fputc('\n', world.file_server_in, f_name);
+ fflush(world.file_server_in);
}
world.halfdelay = 1; /* Ensures read_world() is only called 10 */
halfdelay(world.halfdelay); /* times a second during user inactivity. */
uint8_t change_in_client = 0;
+ time_t last_server_answer_time = time(0);
while (1)
{
+ test_server_activity(&last_server_answer_time);
+ test_ping_pong(last_server_answer_time);
if (world.winch)
{
reset_windows_on_winch();
}
}
}
- try_send("QUIT");
+ send("QUIT");
return "Sent QUIT to server.";
}
-/* Try sending "msg" to the server by writing it to the file at
- * world.path_server_in. Try to open it 2^16 times before giving up. After
- * opening, try to write to it 2^16 times before giving up.
+/* Write "msg" plus newline to server input file at world.path_server_in.
+ *
+ * "msg" must fit into size defined by PIPE_BUF so that no race conditiosn
+ * arise by many clients writing to the file in parallel.
*/
-extern void try_send(char * msg);
-
-/* Keep checking for user input and a changed server out file. Update client's
- * world representation on out file changes. Manipulate the client and send
- * commands to server based on the user input as interpreted by the control.h
- * library. On each change / activity, re-draw the windows with draw_all_wins().
- * When the loop ends regularly (due to the user sending a quit command), return
- * an appropriate quit message to write to stdout when the client winds down.
- * Also call reset_windows() on receiving a SIGWINCH.
+extern void send(char * msg);
+
+/* Keep checking for user input, a changed worldstate file and the server's
+ * wakefulness. Update client's world representation on worldstate file changes.
+ * Manipulate the client and send commands to server based on the user input as
+ * interpreted by the control.h library.
+ *
+ * On each change / activity, re-draw the windows with draw_all_wins(). When the
+ * loop ends regularly (due to the user sending a quit command), return an
+ * appropriate quit message to write to stdout when the client winds down. Call
+ * reset_windows() on receiving a SIGWINCH. Abort on assumed server death if the
+ * server's out file does not get updated, even on PING requests.
*/
extern char * io_loop();
#include <string.h> /* memset() */
#include <unistd.h> /* access() */
#include "../common/err_try_fgets.h" /* set_err_try_fgets_delim() */
+#include "../common/readwrite.h" /* try_fopen() */
#include "../common/rexit.h" /* set_cleanup_func(), exit_trouble(),exit_err() */
#include "cleanup.h" /* cleanup(), set_cleanup_flag() */
#include "command_db.h" /* init_command_db() */
-#include "io.h" /* io_loop(), try_send() */
+#include "io.h" /* io_loop() */
#include "misc.h" /* load_interface_conf(), winch_called() */
#include "windows.h" /* winch_called() */
#include "world.h" /* struct World */
char * f_name = "main()";
/* Declare hard-coded paths and values here. */
- world.path_server_in = "server/in";
- world.path_commands = "confclient/commands";
- world.path_interface = "confclient/interface_conf";
- world.winDB.legal_ids = "012ciklm";
- world.delim = "%\n";
+ world.path_commands = "confclient/commands";
+ world.path_interface = "confclient/interface_conf";
+ world.winDB.legal_ids = "012ciklm";
+ world.delim = "%\n";
set_err_try_fgets_delim(world.delim);
+ char * path_server_in = "server/in";
+ char * path_server_out = "server/out";
/* Parse command line arguments. */
obey_argv(argc, argv);
act.sa_handler = &winch_called;
exit_trouble(sigaction(SIGWINCH, &act, NULL), f_name, "sigaction()");
+ /* Open file streams for communicating with the server. */
+ exit_err(access(path_server_in, F_OK), "No server input file found.");
+ world.file_server_in = try_fopen(path_server_in, "a", f_name);
+ set_cleanup_flag(CLEANUP_SERVER_IN);
+ world.file_server_out = try_fopen(path_server_out, "r", f_name);
+ set_cleanup_flag(CLEANUP_SERVER_OUT);
+
/* This is where most everything happens. */
char * quit_msg = io_loop();
#define WORLD_H
#include <stdint.h> /* uint8_t, uint16_t */
+#include <stdio.h> /* FILE */
#include <sys/types.h> /* time_t */
#include "map.h" /* struct Map */
#include "../common/yx_uint8.h" /* struct yx_uint8 */
struct World
{
+ FILE * file_server_in; /* server input file to write commands to */
+ FILE * file_server_out; /* server output file to read messages from */
struct WinDB winDB; /* data for window management and individual windows */
struct CommandDB commandDB; /* data on commands from commands config file */
struct KeyBindingDB kb_global; /* globally availabe keybindings */
struct KeyBindingDB kb_wingeom; /* Win geometry config view keybindings */
struct KeyBindingDB kb_winkeys; /* Win keybindings config view keybindings*/
struct Map map; /* game map geometry and content */
- time_t last_update; /* used for comparison with server outfile' mtime */
+ time_t last_update; /* used for comparison with worldstate file's mtime */
char * log; /* log of player's activities */
- char * path_server_in; /* path of server's input fifo */
char * path_interface; /* path of interface configuration file */
char * path_commands; /* path of commands config file */
char * player_inventory; /* one-item-per-line string list of owned items */
#include <stdint.h> /* uint32_t */
#include <stdlib.h> /* free() */
#include <unistd.h> /* unlink() */
+#include "../common/readwrite.h" /* try_fclose() */
#include "map_object_actions.h" /* free_map_object_actions() */
#include "map_objects.h" /* free_map_objects(), free_map_object_defs() */
#include "world.h" /* global world */
+
/* The clean-up flags set by set_cleanup_flag(). */
static uint32_t cleanup_flags = 0x0000;
extern void cleanup()
{
+ char * f_name = "cleanup()";
free(world.queue);
free(world.log);
free(world.map.cells);
- if (cleanup_flags & CLEANUP_OUTFILE)
+ if (cleanup_flags & CLEANUP_WORLDSTATE)
{
- unlink(world.path_out);
+ unlink(world.path_worldstate);
}
if (cleanup_flags & CLEANUP_MAP_OBJECTS)
{
{
free_map_object_actions(world.map_obj_acts);
}
- if (cleanup_flags & CLEANUP_FIFO) /* Fifo also serves as lockfile that */
- { /* affirms the running of a server */
- unlink(world.path_in); /* instance. Therefore it should be */
- } /* the last thing to be deleted. */
+ if (cleanup_flags & CLEANUP_IN)
+ {
+ try_fclose(world.file_in, f_name);
+ unlink(world.path_in);
+ }
+ if (cleanup_flags & CLEANUP_OUT)
+ {
+ try_fclose(world.file_out, f_name);
+ free(world.server_test);
+ unlink(world.path_out);
+ }
}
{
cleanup_flags = cleanup_flags | flag;
}
+
+
+
+extern void unset_cleanup_flag(enum cleanup_flag flag)
+{
+ cleanup_flags = cleanup_flags ^ flag;
+}
enum cleanup_flag
{
CLEANUP_FIFO = 0x0001,
- CLEANUP_OUTFILE = 0x0002,
+ CLEANUP_WORLDSTATE = 0x0002,
CLEANUP_MAP_OBJECT_DEFS = 0x0004,
CLEANUP_MAP_OBJECTS = 0x0008,
- CLEANUP_MAP_OBJECT_ACTS = 0x0010
+ CLEANUP_MAP_OBJECT_ACTS = 0x0010,
+ CLEANUP_IN = 0x0020,
+ CLEANUP_OUT = 0x0040
};
+/* In addition, unset_cleanup_flag() may be used to unset flags. */
extern void set_cleanup_flag(enum cleanup_flag flag);
+extern void unset_cleanup_flag(enum cleanup_flag flag);
/* Frees memory and unlinks some files. */
extern void cleanup();
#include "io.h"
#include <errno.h> /* global errno */
-#include <fcntl.h> /* open(), O_RDONLY, O_NONBLOCK */
#include <limits.h> /* PIPE_BUF */
#include <stddef.h> /* size_t, NULL */
#include <stdint.h> /* uint8_t, uint32_t */
-#include <stdio.h> /* define FILE, sprintf() */
+#include <stdio.h> /* defines EOF, FILE, sprintf() */
#include <stdlib.h> /* free() */
-#include <string.h> /* strlen(), memset(), memcpy() */
-#include <unistd.h> /* read(), close() */
+#include <string.h> /* strlen(), memcpy() */
+#include <sys/types.h> /* time_t */
+#include <time.h> /* time() */
#include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(),
- * try_fwrite(), try_fputc()
+ * 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 "map_objects.h" /* structs MapObj, MapObjDef, get_map_obj_def() */
*/
static char * get_message_from_queue();
-/* Read fifo input to put into queue. Only stop reading when bytes were received
- * and the receiving has stopped. Read max. PIPE_BUF-sized chunks for atomicity.
+/* Read input file for input into world.queue. new queue input. Wait a few
+ * seconds until giving up. Translate '\n' chars in input file into '\0' chars.
*/
-static void read_fifo_into_queue();
+static void read_file_into_queue();
-/* Write to output file the world state as it is to be visible to clients. */
-static void update_out_file();
+/* Write world state as visible to clients to its file. Write single dot line to
+ * server output file to satisfy client ping mechanisms.
+ */
+static void update_worldstate_file();
/* Write "value" to new \n-delimited line of "file". */
static void write_value_as_line(uint32_t value, FILE * file);
{
char * f_name = "get_message_from_queue()";
char * message = NULL;
- size_t cutout_len = strlen(world.queue);
- if (0 < cutout_len)
- {
- cutout_len++;
- message = try_malloc(cutout_len, f_name);
- memcpy(message, world.queue, cutout_len);
- }
- for (;
- cutout_len != world.queue_size && '\0' == world.queue[cutout_len];
- cutout_len++);
- world.queue_size = world.queue_size - cutout_len;
- if (0 == world.queue_size)
+ if (world.queue_size)
{
- free(world.queue); /* NULL so read_fifo_into_queue() may free() this */
- world.queue = NULL; /* every time, even when it's un-allocated first. */
- }
- else
- {
- char * new_queue = try_malloc(world.queue_size, f_name);
- memcpy(new_queue, &(world.queue[cutout_len]), world.queue_size);
- free(world.queue);
- world.queue = new_queue;
+ size_t cutout_len = strlen(world.queue);
+ if (0 < cutout_len)
+ {
+ cutout_len++;
+ message = try_malloc(cutout_len, f_name);
+ memcpy(message, world.queue, cutout_len);
+ }
+ for (;
+ cutout_len != world.queue_size && '\0' == world.queue[cutout_len];
+ cutout_len++);
+ world.queue_size = world.queue_size - cutout_len;
+ if (0 == world.queue_size)
+ {
+ free(world.queue); /* NULL so read_file_into_queue() may free() */
+ world.queue = NULL; /* this every time, even when it's */
+ } /* un-allocated first. */
+ else
+ {
+ char * new_queue = try_malloc(world.queue_size, f_name);
+ memcpy(new_queue, &(world.queue[cutout_len]), world.queue_size);
+ free(world.queue);
+ world.queue = new_queue;
+ }
}
return message;
}
-static void read_fifo_into_queue()
+static void read_file_into_queue()
{
- char * f_name = "read_fifo_into_queue()";
- uint32_t buf_size = PIPE_BUF;
- int fdesc_infile = open(world.path_in, O_RDONLY | O_NONBLOCK);
- exit_trouble(-1 == fdesc_infile, "open()", f_name);
- char buf[buf_size];
- memset(buf, 0, buf_size);
- int bytes_read;
- uint8_t read_state = 0; /* 0: waiting for input. 1: started receiving it. */
- while (1)
+ char * f_name = "read_file_into_queue()";
+ uint8_t wait_seconds = 5;
+ time_t now = time(0);
+ int test;
+ while (EOF == (test = try_fgetc(world.file_in, f_name)))
{
- bytes_read = read(fdesc_infile, buf, buf_size);
- if (bytes_read > 0)
+ if (time(0) > now + wait_seconds)
{
- read_state = 1;
- uint32_t old_queue_size = world.queue_size;
- world.queue_size = world.queue_size + bytes_read;
- char * new_queue = try_malloc(world.queue_size, f_name);
- memcpy(new_queue, world.queue, old_queue_size);
- memcpy(&(new_queue[old_queue_size]), buf, bytes_read);
- free(world.queue);
- world.queue = new_queue;
- memset(buf, 0, buf_size);
- continue;
+ return;
}
- exit_trouble(-1 == bytes_read && errno != EAGAIN, "read()", f_name);
- if (1 == read_state)
+ }
+ do
+ {
+ char c = (char) test;
+ if ('\n' == c)
{
- break;
+ c = '\0';
}
+ char * new_queue = try_malloc(world.queue_size + 1, f_name);
+ memcpy(new_queue, world.queue, world.queue_size);
+ char * new_pos = new_queue + world.queue_size;
+ * new_pos = c;
+ world.queue_size++;
+ free(world.queue);
+ world.queue = new_queue;
}
- exit_trouble(close(fdesc_infile), f_name, "close()");
+ while (EOF != (test = try_fgetc(world.file_in, f_name)));
}
-static void update_out_file()
+static void update_worldstate_file()
{
- char * f_name = "update_out_file()";
- char path_tmp[strlen(world.path_out) + strlen(world.tmp_suffix) + 1];
- sprintf(path_tmp, "%s%s", world.path_out, world.tmp_suffix);
+ 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);
FILE * file = try_fopen(path_tmp, "w", f_name);
struct MapObj * 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_out, f_name);
- set_cleanup_flag(CLEANUP_OUTFILE);
+ try_fclose_unlink_rename(file, path_tmp, world.path_worldstate, f_name);
+ set_cleanup_flag(CLEANUP_WORLDSTATE);
+ char * dot = ".\n";;
+ try_fwrite(dot, strlen(dot), 1, world.file_out, f_name);
+ fflush(world.file_out);
}
}
if (world.turn != world.last_update_turn)
{
- update_out_file();
+ update_worldstate_file();
world.last_update_turn = world.turn;
}
- read_fifo_into_queue();
- if ('\0' != world.queue[world.queue_size - 1])
+ read_file_into_queue();
+ if (world.queue_size && '\0' != world.queue[world.queue_size - 1])
{
char * new_queue = try_malloc(world.queue_size + 1, f_name);
memcpy(new_queue, world.queue, world.queue_size);
/* io.h:
*
- * Communication of the server with the outside world via input fifo and output
- * file.
+ * Communication of the server with the outside world and its client via input,
+ * output and world state files.
*/
#ifndef IO_H
/* 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
- * output file at world.path_out (and update world.last_update_turn), then read
- * file at world.path_in for the next load of bytes to put onto the input queue.
+ * 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.
*
* 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
/* src/server/main.c */
#include <errno.h> /* global errno */
-#include <stdio.h> /* printf() */
+#include <stdio.h> /* printf(), fflush() */
#include <stdlib.h> /* exit() */
+#include <string.h> /* strlen() */
#include <sys/stat.h> /* mkfifo(), mkdir() */
-#include <unistd.h> /* access() */
+#include <sys/types.h> /* defines pid_t, time_t */
+#include <time.h> /* #time() */
+#include <unistd.h> /* access(), getpid(), unlink() */
#include "../common/err_try_fgets.h" /* set_err_try_fgets_delim() */
-#include "../common/rexit.h" /* exit_err, exit_trouble(), set_cleanup_func() */
+#include "../common/readwrite.h" /* try_fopen(), try_fwrite(), try_fclose() */
+#include "../common/rexit.h" /* exit_err, set_cleanup_func() */
+#include "../common/try_malloc.h" /* try_malloc() */
#include "cleanup.h" /* set_cleanup_flag(), cleanup() */
#include "init.h" /* run_game(), obey_argv() */
#include "map_object_actions.h" /* init_map_object_actions() */
}
world.path_map_obj_defs = "confserver/defs";
world.path_map_obj_acts = "confserver/map_object_actions";
- world.path_in = "server/in";
- world.path_out = "server/out";
- world.path_record = "record";
- world.tmp_suffix = "_tmp";
+ world.path_worldstate = "server/worldstate";
+ world.path_out = "server/out";
+ world.path_in = "server/in";
+ world.path_record = "record";
+ world.tmp_suffix = "_tmp";
set_err_try_fgets_delim("%%\n");
/* Set map geometry. */
exit_err(access(world.path_map_obj_defs, F_OK), err_mod);
exit_err(access(world.path_map_obj_acts, F_OK), err_moa);
- /* Treat world.path_in file as server process lock file. */
- char * err = "Found pre-existing input fifo file. This indicates another "
- "roguelike-server may be running. It should be killed first.";
- exit_err(!access(world.path_in, F_OK), err);
- int test = mkdir("server", 0700);
- exit_trouble(test && EEXIST != errno, f_name, "mkdir()");
- exit_trouble(mkfifo(world.path_in, 0600), f_name, "mkfifo()");
- set_cleanup_flag(CLEANUP_FIFO);
-
/* Init from config files map object (action) definitions. */
init_map_object_defs();
init_map_object_actions();
+ /* Create server directory if it does not exist yet. */
+ int test = mkdir("server", 0700);
+ exit_trouble(test && EEXIST != errno, f_name, "mkdir()");
+
+ /* Create server out file and start it with server process test string. */
+ world.file_out = try_fopen(world.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);
+
+ /* Create server in file, switch to reading it. */
+ if (!access(world.path_in, F_OK)) /* This keeps out input from old input */
+ { /* file streams of clients */
+ unlink(world.path_in); /* communicating with server processes */
+ } /* superseded by this current one. */
+ world.file_in = try_fopen(world.path_in, "w", f_name);
+ try_fclose(world.file_in, f_name);
+ world.file_in = try_fopen(world.path_in, "r", f_name);
+ set_cleanup_flag(CLEANUP_IN);
+
/* Enter play or replay mode loops, then leave properly. */
run_game();
cleanup();
#include "run.h"
#include <stddef.h> /* NULL */
#include <stdint.h> /* uint8_t, uint16_t, uint32_t */
-#include <stdio.h> /* FILE, sprintf() */
+#include <stdio.h> /* FILE, sprintf(), fflush() */
#include <stdlib.h> /* free() */
-#include <string.h> /* strlen(), strncmp(), atoi() */
+#include <string.h> /* strlen(), strcmp() strncmp(), atoi() */
#include <unistd.h> /* access() */
#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() */
+#include "../common/rexit.h" /* exit_trouble(), exit_err() */
#include "ai.h" /* ai() */
+#include "cleanup.h" /* unset_cleanup_flag() */
#include "init.h" /* remake_world() */
#include "io.h" /* io_round() */
#include "map_object_actions.h" /* get_moa_id_by_name() */
*/
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();
+
static void turn_over()
+static void server_test()
+{
+ 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))
+ {
+ 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);
+ }
+}
+
+
+
extern void obey_msg(char * msg, uint8_t do_record)
{
char * f_name = "obey_msg()";
char * f_name = "io_loop()";
while (1)
{
+ server_test();
char * msg = io_round();
if (NULL == msg)
{
free(msg);
return 1;
}
+ if (!strcmp("PING", msg))
+ {
+ free(msg);
+ char * pong = "PONG\n";
+ try_fwrite(pong, strlen(pong), 1, world.file_out, f_name);
+ fflush(world.file_out);
+ continue;
+ }
if (world.replay)
{
free(msg);
/* Loop for receiving commands via io_round() and acting on them. Exits with 1
* on "QUIT" command. In replay mode, exits with 0 on each non-"QUIT" command.
- * In play mode, processes incomming commands via obey_msg().
+ * 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.
*/
extern uint8_t io_loop();
#define MAIN_H
#include <stdint.h> /* uint8_t, uint16_t, uint32_t */
+#include <stdio.h> /* define FILE */
#include "map.h" /* struct Map */
struct MapObjDef;
struct MapObjAct;
struct World
{
+ FILE * file_in; /* Input stream on file at .path_in. */
+ FILE * file_out; /* Output stream on file at .path_out. */
struct Map map;
struct MapObjDef * map_obj_defs; /* Map object definitions. */
struct MapObjAct * map_obj_acts; /* Map object action definitions. */
struct MapObj * map_objs; /* Map objects. */
char * log; /* Logs the game events from the player's view. */
- char * path_in; /* Fifo to receive command messages. */
- char * path_out; /* File to write the game state as visible to clients.*/
+ 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_map_obj_defs; /* path for map object definitions config file */
char * path_map_obj_acts; /* path for map object actions config file */
char * tmp_suffix; /* Appended to paths of files for their tmp versions. */
- char * queue; /* Stores un-processed messages received via input fifo. */
+ 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. */
uint16_t replay; /* Turn up to which to replay game. No replay if zero. */