From: Christian Heller Date: Tue, 21 Jan 2014 04:15:19 +0000 (+0100) Subject: MAJOR re-write. Split plomrogue into a server and a client. Re-wrote large parts X-Git-Tag: tce~894 X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/%7B%7Byoutube_prefix%7D%7D%7B%7Bvideo_id%7D%7D?a=commitdiff_plain;h=dd9d65ee727ac7e95801da0f8b5bae7009811802;p=plomrogue MAJOR re-write. Split plomrogue into a server and a client. Re-wrote large parts of everything to accomodate this strong architectural change. On the way, dropped old savefiles; they are now completely replaced by record files, that are re-played up to their last point when the game starts anew. Also re-factored large parts of the code and corrected some minor errors. --- diff --git a/Makefile b/Makefile index 56a158c..c7831bd 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,42 @@ -TARGET=roguelike -SRCDIR=src -BUILDDIR=build CC=gcc CFLAGS=-Wall -g +TARGET_SERVER=roguelike-server +TARGET_CLIENT=roguelike-client +SRCDIR=src +BUILDDIR=build +SERVERDIR=server +CLIENTDIR=client +COMMONDIR=common + +# Build object file lists by building object paths from all source file paths. +SOURCES_SERVER=$(shell find $(SRCDIR)/$(SERVERDIR)/ -type f -name \*.c) +SOURCES_CLIENT=$(shell find $(SRCDIR)/$(CLIENTDIR)/ -type f -name \*.c) +SOURCES_COMMON=$(shell find $(SRCDIR)/$(COMMONDIR)/ -type f -name \*.c) +OBJECTS_SERVER=$(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES_SERVER:.c=.o)) +OBJECTS_CLIENT=$(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES_CLIENT:.c=.o)) +OBJECTS_COMMON=$(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES_COMMON:.c=.o)) + +# "make" without further specifications builds both server and client. +default : $(TARGET_SERVER) $(TARGET_CLIENT) + +# "make roguelike-server" builds only the server. +$(TARGET_SERVER) : $(OBJECTS_SERVER) $(OBJECTS_COMMON) + $(CC) $(CFLAGS) -o $(TARGET_SERVER) $(OBJECTS_SERVER) $(OBJECTS_COMMON) -# Use all .c files below SRCDIR for sources. Build object file paths by -# replacing SRCDIR with BUILDDIR and .c endings with .o endings. -SOURCES=$(shell find $(SRCDIR)/ -type f -name \*.c) -OBJECTS=$(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.c=.o)) +# "make roguelike-client" builds only the ncurses client. +$(TARGET_CLIENT) : $(OBJECTS_CLIENT) $(OBJECTS_COMMON) + $(CC) $(CFLAGS) -o $(TARGET_CLIENT) $(OBJECTS_CLIENT) $(OBJECTS_COMMON)\ + -lncurses -# Linking TARGET demands generation of all OBJECTS files. To build them, -# rely on this rule: Each file of path BUILDDIR/[name].o is compiled -# from a file of path SRCDIR/[name].c. Build BUILDDIR as needed. -$(TARGET): $(OBJECTS) - $(CC) $(CFLAGS) -o roguelike $(OBJECTS) -lncurses -$(BUILDDIR)/%.o: $(SRCDIR)/%.c - mkdir -p $(BUILDDIR); $(CC) $(CFLAGS) -c $< -o $@ +# Build respective object file to any source file. Create build dirs as needed. +$(BUILDDIR)/%.o : $(SRCDIR)/%.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ -# Use "clean" to delete build and target files. Tell make that "clean" -# is a "phony" target, i.e. this is not about building a file. -.PHONY: clean -clean: - rm $(OBJECTS) - rmdir $(BUILDDIR) - rm $(TARGET) +# "make clean" to tries to delete all files that could possibly have been built. +# Declare target "phony", i.e. this is not about building a file. +.PHONY : clean +clean : + rm -rf $(BUILDDIR) + rm -f $(TARGET_SERVER) + rm -f $(TARGET_CLIENT) diff --git a/README b/README index 5ce32d5..a672317 100644 --- a/README +++ b/README @@ -1,81 +1,105 @@ plomrogue ========= -plomlompom tries to build his own roguelike. Currently, it doesn't do much -interesting. +plomlompom tries to build his own roguelike. It doesn't do much yet (although +there are insanely ambitious long-term plans). -You can move around a player and meet a number of different enemies. You have 5 -hitpoints to lose before death; they start with different amounts of hitpoints -depending on their species. Your score grows by killing enemies, to the amount -of hitpoints each killed enemy started with. 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 moves take different -numbers of turns to finish. +You can move around a player on an island and meet a number of different +enemies. You have 5 hitpoints to lose before death; they start with different +amounts of hitpoints, depending on their species. Your score grows by killing +enemies, to the amount of hitpoints each killed enemy started with. 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 in the (beeline) -direction of the nearest enemy, so they often bump into obstacles. +direction of the nearest enemy, so they often bump into, and get stuck behind, +obstacles. You can use that for your advantage. -There is only one save file (named "savefile"), and it gets overwritten each new -turn. To start over with a new world, delete it. +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 +automatially until you end up where you left the game. To start over in a new +world, delete this file. -Install/run ------------ +System requirements / installation / running the game +----------------------------------------------------- -Dependencies: the ncurses library. +The game is expected to run on Linux systems that contain the ncurses library. +Perform the following steps: -git clone https://github.com/plomlompom/plomrogue -cd plomrogue -make -./roguelike +$ git clone https://github.com/plomlompom/plomrogue +$ cd plomrogue +$ make +$ ./roguelike -Keybindings and window configuration ------------------------------------- +(It might also work on other Unix-like systems with ncurses, who knows.) -In the default window configuration, the window appearing on the left sports a -list of keybindings available globally and additionally via the window currently -selected as active. +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 may also +ignore the script and start the two by hand. + +Client's keybindings and window management +------------------------------------------ + +In the client's default window configuration, the window appearing on the left +sports a list of keybindings available globally, and additionally via the window +currently selected as active. Hit "w" (per default keybindings) to switch the "active" window to a view that allows changing its geometry. One further hit on "w" switches the window to a view that allows changing its window-specific keybindings. The global keybindings may be changed in the "Global keys" window, those of the window -geometry configuration in the "Window geometry keys" window" and those of the +geometry configuration in the "Window geometry keys" window, and those of the window-specific keybindings configuration in the "Window keybinding keys" window; by default, these three windows are not visible, but may be turned on by hitting the "F6", "F7" and "F8" keys. What actions are available globally or only in specific windows can be further -manipulated by editing the files config/keybindings_global and -config/windows/Win_* that map keycodes to commands to the respective keybinding -repositories. While keybindings_global contains merely a list of keycode command -mappings, the Win_* files start with the name of the windows to be configured, -followed by a one-character line for internal use (mapping the window to one of -several internally available window content drawing functions), followed by two -lines describing the window's designated height and width, and only then an -optional list of keybindings specific to that window. +manipulated by editing the files confclient/keybindings_global and +confclient/windows/Win_* that map keycodes to commands to the respective +keybinding repositories. While keybindings_global contains merely a list of +keycode command mappings, the Win_* files start with the name of the windows to +be configured, followed by a one-character line for internal use (mapping the +window to one of several internally available window content drawing functions), +followed by two lines describing the window's designated height and width, and +only then an optional list of keybindings specific to that window. Replay game recording --------------------- Run "./roguelike -s" to watch a recording of the current game from the -beginning. Hit the "next turn / wait" key to increment turns. Keys to -manage windows, scroll on the map and quit the program are active; keys -to perform player actions are inactive. Append a number to the -s option -(like "-s100") to start the recording at the respective turn number. +beginning. Hit any player action key to increment turns (they will not trigger +the actions usually mapped to them, only repeat the actions performed 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 -------- +Hacking / server internals and configuration +-------------------------------------------- The movements/actions available to the player and the enemies are defined and -can be changed in config/map_object_actions. Each line consists of, first, a -numerical ID used internally to manage the action, secondly the number of turns -the action takes, and thirdly a string representing the action internally. +can be changed in ./confserver/map_object_actions. Each line consists of, first, +a numerical ID used internally to manage the action, secondly the number of +turns the action takes, and thirdly a string representing the action internally. The different map object types, i.e. species (including the player's human one) -and item types, can be edited in config/defs. Here the first value is a +and item types, can be edited in ./confserver/defs. Here the first value is a numerical ID that represents the object type, the second one describes what type this object decomposes to when it gets destroyed/killed, the third value is the character used to represent the object visually on the map, the fourth value is the number of hitpoints the object starts with (items are dead and start with zero hitpoints, anything else moves), the fifth is the string that names the -object in the game log. +object in the game log. Note that the only valid item use so far, consuming +"magic meat" to gain hitpoints, is so far hard-coded (this should change in the +future). + +All source files are thoroughly documented to explain more details of +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 +the player / the player's client. diff --git a/confclient/commands b/confclient/commands new file mode 100644 index 0000000..9368f0b --- /dev/null +++ b/confclient/commands @@ -0,0 +1,51 @@ +1 quit quit +2 wait wait / next turn +3 player_u player up +4 player_d player down +5 player_l player left +6 player_r player right +7 to_g_keywin toggle global keys window +8 to_mapwin toggle map window +9 to_infowin toggle info window +10 to_logwin toggle log window +11 to_wg_keywin toggle window geometry keys window +12 to_wk_keywin toggle window keybinding keys window +13 cyc_win_f cycle window selection forwards +14 winconf toggle window configuration view +15 cyc_win_b cycle window selection backwards +16 scrl_l scroll pad left +17 scrl_r scroll pad right +18 g_keys_d global keybindings nav down +19 g_keys_u global keybindings nav up +20 g_keys_m global keybindings modify +21 shift_f shift window forwards +22 shift_b shift window backwards +23 grow_h grow window horizontally +24 shri_h shrink window horizontally +25 grow_v grow window vertically +26 shri_v shrink window vertically +27 to_height_t toggle window height type +28 to_width_t toggle window width type +29 w_keys_d win keys nav down +30 w_keys_u win keys nav up +31 w_keys_m win keys mod +32 wg_keys_d win geometry config keybindings nav down +33 wg_keys_u win geometry config keybindings nav up +34 wg_keys_m win geometry config keybindings modify +35 wk_keys_d win keybindings config keybindings nav down +36 wk_keys_u win keybindings config keybindings nav up +37 wk_keys_m win keybindings config keybindings modify +38 reload_conf reload windows and keybindings configuration +39 save_conf save windows and keybindings +41 map_u map up +42 map_d map down +43 map_l map left +44 map_r map right +45 map_c map center player +46 to_a_keywin toggle view of available keybindings +46 to_inv toggle inventory window +47 drop drop object from inventory +48 pick pick up object from ground +49 inv_d inventory selection down +50 inv_u inventory selection up +51 use use selected inventory object diff --git a/confclient/keybindings_global b/confclient/keybindings_global new file mode 100644 index 0000000..739af7d --- /dev/null +++ b/confclient/keybindings_global @@ -0,0 +1,16 @@ +81 quit +265 to_a_keywin +266 to_mapwin +267 to_infowin +268 to_inv +269 to_logwin +270 to_g_keywin +271 to_wg_keywin +272 to_wk_keywin +119 winconf +62 cyc_win_f +60 cyc_win_b +262 scrl_l +360 scrl_r +114 reload_conf +67 save_conf diff --git a/confclient/keybindings_wingeom b/confclient/keybindings_wingeom new file mode 100644 index 0000000..d322501 --- /dev/null +++ b/confclient/keybindings_wingeom @@ -0,0 +1,8 @@ +258 shift_f +259 shift_b +42 grow_h +95 shri_h +43 grow_v +45 shri_v +121 to_height_t +120 to_width_t diff --git a/confclient/keybindings_winkeys b/confclient/keybindings_winkeys new file mode 100644 index 0000000..8a15320 --- /dev/null +++ b/confclient/keybindings_winkeys @@ -0,0 +1,3 @@ +258 w_keys_d +259 w_keys_u +10 w_keys_m diff --git a/confclient/windows/Win_0 b/confclient/windows/Win_0 new file mode 100644 index 0000000..e79bdcd --- /dev/null +++ b/confclient/windows/Win_0 @@ -0,0 +1,7 @@ +Global keys +0 +-13 +29 +258 g_keys_d +259 g_keys_u +10 g_keys_m diff --git a/confclient/windows/Win_1 b/confclient/windows/Win_1 new file mode 100644 index 0000000..645c8c4 --- /dev/null +++ b/confclient/windows/Win_1 @@ -0,0 +1,7 @@ +Window geometry keys +1 +8 +29 +258 wg_keys_d +259 wg_keys_u +10 wg_keys_m diff --git a/confclient/windows/Win_2 b/confclient/windows/Win_2 new file mode 100644 index 0000000..c1d9448 --- /dev/null +++ b/confclient/windows/Win_2 @@ -0,0 +1,7 @@ +Window keybinding keys +2 +3 +29 +258 wk_keys_d +259 wk_keys_u +10 wk_keys_m diff --git a/confclient/windows/Win_c b/confclient/windows/Win_c new file mode 100644 index 0000000..b513c69 --- /dev/null +++ b/confclient/windows/Win_c @@ -0,0 +1,8 @@ +Inventory +c +5 +33 +100 drop +259 inv_u +258 inv_d +117 use diff --git a/confclient/windows/Win_i b/confclient/windows/Win_i new file mode 100644 index 0000000..dfc519f --- /dev/null +++ b/confclient/windows/Win_i @@ -0,0 +1,4 @@ +Info +i +3 +33 diff --git a/confclient/windows/Win_k b/confclient/windows/Win_k new file mode 100644 index 0000000..ca67a5f --- /dev/null +++ b/confclient/windows/Win_k @@ -0,0 +1,4 @@ +Available keys +k +0 +29 diff --git a/confclient/windows/Win_l b/confclient/windows/Win_l new file mode 100644 index 0000000..e2fff52 --- /dev/null +++ b/confclient/windows/Win_l @@ -0,0 +1,4 @@ +Log +l +-10 +33 diff --git a/confclient/windows/Win_m b/confclient/windows/Win_m new file mode 100644 index 0000000..a83973f --- /dev/null +++ b/confclient/windows/Win_m @@ -0,0 +1,15 @@ +Map +m +0 +-64 +112 pick +58 wait +107 player_u +106 player_d +104 player_l +108 player_r +259 map_u +258 map_d +260 map_l +261 map_r +46 map_c diff --git a/confclient/windows/toggle_order_and_active b/confclient/windows/toggle_order_and_active new file mode 100644 index 0000000..f188d83 --- /dev/null +++ b/confclient/windows/toggle_order_and_active @@ -0,0 +1,2 @@ +kmicl +m \ No newline at end of file diff --git a/config/commands b/config/commands deleted file mode 100644 index 9368f0b..0000000 --- a/config/commands +++ /dev/null @@ -1,51 +0,0 @@ -1 quit quit -2 wait wait / next turn -3 player_u player up -4 player_d player down -5 player_l player left -6 player_r player right -7 to_g_keywin toggle global keys window -8 to_mapwin toggle map window -9 to_infowin toggle info window -10 to_logwin toggle log window -11 to_wg_keywin toggle window geometry keys window -12 to_wk_keywin toggle window keybinding keys window -13 cyc_win_f cycle window selection forwards -14 winconf toggle window configuration view -15 cyc_win_b cycle window selection backwards -16 scrl_l scroll pad left -17 scrl_r scroll pad right -18 g_keys_d global keybindings nav down -19 g_keys_u global keybindings nav up -20 g_keys_m global keybindings modify -21 shift_f shift window forwards -22 shift_b shift window backwards -23 grow_h grow window horizontally -24 shri_h shrink window horizontally -25 grow_v grow window vertically -26 shri_v shrink window vertically -27 to_height_t toggle window height type -28 to_width_t toggle window width type -29 w_keys_d win keys nav down -30 w_keys_u win keys nav up -31 w_keys_m win keys mod -32 wg_keys_d win geometry config keybindings nav down -33 wg_keys_u win geometry config keybindings nav up -34 wg_keys_m win geometry config keybindings modify -35 wk_keys_d win keybindings config keybindings nav down -36 wk_keys_u win keybindings config keybindings nav up -37 wk_keys_m win keybindings config keybindings modify -38 reload_conf reload windows and keybindings configuration -39 save_conf save windows and keybindings -41 map_u map up -42 map_d map down -43 map_l map left -44 map_r map right -45 map_c map center player -46 to_a_keywin toggle view of available keybindings -46 to_inv toggle inventory window -47 drop drop object from inventory -48 pick pick up object from ground -49 inv_d inventory selection down -50 inv_u inventory selection up -51 use use selected inventory object diff --git a/config/defs b/config/defs deleted file mode 100644 index f114ce2..0000000 --- a/config/defs +++ /dev/null @@ -1,7 +0,0 @@ -0 5 @ 5 HUMAN -1 4 a 1 ANT -2 5 z 3 ZOMBIE -3 6 S 9 SHOGGOTH -4 4 # 0 DIRT -5 4 % 0 SKELETON -6 4 m 0 MAGIC MEAT diff --git a/config/keybindings_global b/config/keybindings_global deleted file mode 100644 index 739af7d..0000000 --- a/config/keybindings_global +++ /dev/null @@ -1,16 +0,0 @@ -81 quit -265 to_a_keywin -266 to_mapwin -267 to_infowin -268 to_inv -269 to_logwin -270 to_g_keywin -271 to_wg_keywin -272 to_wk_keywin -119 winconf -62 cyc_win_f -60 cyc_win_b -262 scrl_l -360 scrl_r -114 reload_conf -67 save_conf diff --git a/config/keybindings_wingeom b/config/keybindings_wingeom deleted file mode 100644 index d322501..0000000 --- a/config/keybindings_wingeom +++ /dev/null @@ -1,8 +0,0 @@ -258 shift_f -259 shift_b -42 grow_h -95 shri_h -43 grow_v -45 shri_v -121 to_height_t -120 to_width_t diff --git a/config/keybindings_winkeys b/config/keybindings_winkeys deleted file mode 100644 index 8a15320..0000000 --- a/config/keybindings_winkeys +++ /dev/null @@ -1,3 +0,0 @@ -258 w_keys_d -259 w_keys_u -10 w_keys_m diff --git a/config/map_object_actions b/config/map_object_actions deleted file mode 100644 index 1ed03ea..0000000 --- a/config/map_object_actions +++ /dev/null @@ -1,5 +0,0 @@ -0 1 wait -1 3 move -2 10 pick_up -3 3 drop -4 30 use diff --git a/config/windows/Win_0 b/config/windows/Win_0 deleted file mode 100644 index e79bdcd..0000000 --- a/config/windows/Win_0 +++ /dev/null @@ -1,7 +0,0 @@ -Global keys -0 --13 -29 -258 g_keys_d -259 g_keys_u -10 g_keys_m diff --git a/config/windows/Win_1 b/config/windows/Win_1 deleted file mode 100644 index 645c8c4..0000000 --- a/config/windows/Win_1 +++ /dev/null @@ -1,7 +0,0 @@ -Window geometry keys -1 -8 -29 -258 wg_keys_d -259 wg_keys_u -10 wg_keys_m diff --git a/config/windows/Win_2 b/config/windows/Win_2 deleted file mode 100644 index c1d9448..0000000 --- a/config/windows/Win_2 +++ /dev/null @@ -1,7 +0,0 @@ -Window keybinding keys -2 -3 -29 -258 wk_keys_d -259 wk_keys_u -10 wk_keys_m diff --git a/config/windows/Win_c b/config/windows/Win_c deleted file mode 100644 index b513c69..0000000 --- a/config/windows/Win_c +++ /dev/null @@ -1,8 +0,0 @@ -Inventory -c -5 -33 -100 drop -259 inv_u -258 inv_d -117 use diff --git a/config/windows/Win_i b/config/windows/Win_i deleted file mode 100644 index dfc519f..0000000 --- a/config/windows/Win_i +++ /dev/null @@ -1,4 +0,0 @@ -Info -i -3 -33 diff --git a/config/windows/Win_k b/config/windows/Win_k deleted file mode 100644 index ca67a5f..0000000 --- a/config/windows/Win_k +++ /dev/null @@ -1,4 +0,0 @@ -Available keys -k -0 -29 diff --git a/config/windows/Win_l b/config/windows/Win_l deleted file mode 100644 index e2fff52..0000000 --- a/config/windows/Win_l +++ /dev/null @@ -1,4 +0,0 @@ -Log -l --10 -33 diff --git a/config/windows/Win_m b/config/windows/Win_m deleted file mode 100644 index a83973f..0000000 --- a/config/windows/Win_m +++ /dev/null @@ -1,15 +0,0 @@ -Map -m -0 --64 -112 pick -58 wait -107 player_u -106 player_d -104 player_l -108 player_r -259 map_u -258 map_d -260 map_l -261 map_r -46 map_c diff --git a/config/windows/toggle_order_and_active b/config/windows/toggle_order_and_active deleted file mode 100644 index f188d83..0000000 --- a/config/windows/toggle_order_and_active +++ /dev/null @@ -1,2 +0,0 @@ -kmicl -m \ No newline at end of file diff --git a/confserver/defs b/confserver/defs new file mode 100644 index 0000000..f114ce2 --- /dev/null +++ b/confserver/defs @@ -0,0 +1,7 @@ +0 5 @ 5 HUMAN +1 4 a 1 ANT +2 5 z 3 ZOMBIE +3 6 S 9 SHOGGOTH +4 4 # 0 DIRT +5 4 % 0 SKELETON +6 4 m 0 MAGIC MEAT diff --git a/confserver/map_object_actions b/confserver/map_object_actions new file mode 100644 index 0000000..1ed03ea --- /dev/null +++ b/confserver/map_object_actions @@ -0,0 +1,5 @@ +0 1 wait +1 3 move +2 10 pick_up +3 3 drop +4 30 use diff --git a/roguelike b/roguelike new file mode 100755 index 0000000..2143f05 --- /dev/null +++ b/roguelike @@ -0,0 +1,30 @@ +#!/bin/sh + +# This is only to ensure I write no unintentional bashisms. +set -o posix + +# Abort the script on error. +set -e + +# Use the shell script's arguments to the server's arguments. +./roguelike-server $@ & + +# Give server some time to start up and exit on error. +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 +# -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. +i=0 +while [ ! -e server/out ] && [ $i -le 10000 ] +do + sleep 0.01 + i=`expr $i + 1` +done +kill -0 $! 2> /dev/null + +# Only start the interface when everything else went well. +./roguelike-client diff --git a/src/ai.c b/src/ai.c deleted file mode 100644 index a4d4fbe..0000000 --- a/src/ai.c +++ /dev/null @@ -1,115 +0,0 @@ -/* ai.c */ - -#include "ai.h" -#include /* for free() */ -#include /* for strlen(), memset() */ -#include /* for uint8_t */ -#include "main.h" /* for world global */ -#include "misc.h" /* for try_malloc() */ -#include "map.h" /* for Map struct */ -#include "yx_uint16.h" /* for yx_uint16 struct,yx_uint16_cmp(),mv_yx_in_dir() */ -#include "map_objects.h" /* for MapObj struct */ -#include "map_object_actions.h" /* for get_moa_id_by_name() */ - - - -/* Change cardinal direction string ("NNE" etc.) of any length >1 pointed to by - * "path_ptr" one step clockwise ("NNE" -> "NEE", "NEE" -> "EEE" etc.). - */ -static void clockwise_path(char ** path_ptr); - -/* Return dir char (north = "N" etc.) to enemy nearest to "origin" (beeline). */ -static char nearest_enemy_dir(struct yx_uint16 origin); - - - -static void clockwise_path(char ** path_ptr) -{ - char * path = * path_ptr; - char old_char = path[0]; - char new_char = 'N'; - if ('N' == old_char) - { - new_char = 'E'; - } - else if ('E' == old_char) - { - new_char = 'S'; - } - else if ('S' == old_char) - { - new_char = 'W'; - } - uint8_t len = strlen(path); - uint8_t i; - for (; i < len; i++) - { - uint8_t next_i = i + 1; - if (next_i == len || old_char != path[next_i]) - { - break; - } - } - path[i] = new_char; -} - - - -static char nearest_enemy_dir(struct yx_uint16 origin) -{ - char * f_name = "nearest_enemy_dir()"; - struct MapObj * mo; - char sel = 0; - uint16_t dist_max = world.map->size.y; - if (world.map->size.x > world.map->size.y) - { - dist_max = world.map->size.x; - } - uint8_t escape = 0; - uint8_t dist, j; - uint16_t i; - for (dist = 1; !escape && dist <= dist_max; dist++) - { - char * path = try_malloc(dist + 1, f_name); - memset(path, 'N', dist); - path[dist] = '\0'; - for (i = 0; !escape && i < (dist * 4); i++) - { - clockwise_path(&path); - struct yx_uint16 testpos = origin; - for (j = 0; j < dist; j++) - { - testpos = mv_yx_in_dir(path[j], testpos); - } - if (yx_uint16_cmp(&testpos, &origin) || - testpos.y > world.map->size.y || testpos.x > world.map->size.x) - { - continue; - } - for (mo = world.map_objs; mo != 0; mo = mo->next) - { - if (mo->lifepoints && 1 == yx_uint16_cmp(&testpos, &mo->pos)) - { - sel = path[0]; - escape = 1; - break; - } - } - } - free(path); - } - return sel; -} - - - -extern void pretty_dumb_ai(struct MapObj * mo) -{ - mo->command = get_moa_id_by_name("wait"); - char sel = nearest_enemy_dir(mo->pos); - if (0 != sel) - { - mo->command = get_moa_id_by_name("move"); - mo->arg = sel; - } -} diff --git a/src/ai.h b/src/ai.h deleted file mode 100644 index 3799f96..0000000 --- a/src/ai.h +++ /dev/null @@ -1,18 +0,0 @@ -/* ai.h - * - * Pseudo AI for actor movement. - */ - -#ifndef AI_H -#define AI_H - -struct MapObj; - - - -/* Determine next non-player actor command / arguments. Pretty dumb so far. */ -extern void pretty_dumb_ai(struct MapObj * mo); - - - -#endif diff --git a/src/client/cleanup.c b/src/client/cleanup.c new file mode 100644 index 0000000..7df98e7 --- /dev/null +++ b/src/client/cleanup.c @@ -0,0 +1,41 @@ +/* src/client/cleanup.c */ + +#include "cleanup.h" +#include /* uint32_t */ +#include /* free() */ +#include "command_db.h" /* free_command_db() */ +#include "misc.h" /* unload_interface_conf() */ +#include "windows.h" /* free_winmeta_and_endwin() */ +#include "world.h" /* world global */ + + + +/* The clean-up flags set by set_cleanup_flag(). */ +static uint32_t cleanup_flags = 0x0000; + + + +extern void cleanup() +{ + free(world.map.cells); + free(world.log); + free(world.player_inventory); + if (cleanup_flags & CLEANUP_INTERFACE)/* Must come pre ncurses cleanup, */ + { /* for by closing all windows it */ + unload_interface_conf(); /* relies on world.wmeta data freed */ + } /* by free_winmeta_and_endwin(). */ + if (cleanup_flags & CLEANUP_NCURSES) + { + free_winmeta_and_endwin(); + } + if (cleanup_flags & CLEANUP_COMMANDS) + { + free_command_db(); + } +} + + +extern void set_cleanup_flag(enum cleanup_flag flag) +{ + cleanup_flags = cleanup_flags | flag; +} diff --git a/src/client/cleanup.h b/src/client/cleanup.h new file mode 100644 index 0000000..ce4866d --- /dev/null +++ b/src/client/cleanup.h @@ -0,0 +1,29 @@ +/* src/client/cleanup.h + * + * Stuff defining / performing the cleanup called by rexit.h's exit functions. + */ + +#ifndef CLEANUP_H +#define CLEANUP_H + + + +/* set_cleanup_flag() sets any of the flags defined in cleanup_flag to announce + * the resources that need cleaning up upon program exit. It is to be called at + * the earliest moment possible after resource creation / initialization. + */ +enum cleanup_flag +{ + CLEANUP_NCURSES = 0x0001, + CLEANUP_INTERFACE = 0x0002, + CLEANUP_COMMANDS = 0x0004 +}; + +extern void set_cleanup_flag(enum cleanup_flag flag); + +/* Frees memory and properly winds down ncurses / resets the terminal. */ +extern void cleanup(); + + + +#endif diff --git a/src/client/command_db.c b/src/client/command_db.c new file mode 100644 index 0000000..4835392 --- /dev/null +++ b/src/client/command_db.c @@ -0,0 +1,119 @@ +/* src/client/command_db.c */ + +#include "command_db.h" +#include /* uint8_t, uint32_t */ +#include /* FILE */ +#include /* free() */ +#include /* memcpy(), strlen(), strtok(), strcmp() */ +#include "../common/readwrite.h" /* try_fopen(), try_fclose(), try_fgets() + * textfile_sizes() + */ +#include "../common/try_malloc.h" /* try_malloc() */ +#include "cleanup.h" /* set_cleanup_flag() */ +#include "world.h" /* global world */ + + + +/* Build string pointed to by "ch_ptr" from next token delimited by "delim". */ +static void copy_tokenized_string(char ** ch_ptr, char * delim); + + + +static void copy_tokenized_string(char ** ch_ptr, char * delim) +{ + char * f_name = "copy_tokenized_string()"; + char * dsc_ptr = strtok(NULL, delim); + * ch_ptr = try_malloc(strlen(dsc_ptr) + 1, f_name); + memcpy(* ch_ptr, dsc_ptr, strlen(dsc_ptr) + 1); +} + + + +extern uint8_t is_command_id_shortdsc(uint8_t id, char * shortdsc) +{ + struct Command * cmd_ptr = world.cmd_db.cmds; + while (1) + { + if (id == cmd_ptr->id) + { + if (strcmp(shortdsc, cmd_ptr->dsc_short)) + { + return 0; + } + return 1; + } + cmd_ptr = &cmd_ptr[1]; + } +} + + + +extern uint8_t get_command_id(char * dsc_short) +{ + struct Command * cmd_ptr = world.cmd_db.cmds; + while (1) + { + if (0 == strcmp(dsc_short, cmd_ptr->dsc_short)) + { + return cmd_ptr->id; + } + cmd_ptr = &cmd_ptr[1]; + } +} + + + +extern char * get_command_longdsc(char * dsc_short) +{ + struct Command * cmd_ptr = world.cmd_db.cmds; + while (1) + { + if (0 == strcmp(dsc_short, cmd_ptr->dsc_short)) + { + return cmd_ptr->dsc_long; + } + cmd_ptr = &cmd_ptr[1]; + } +} + + + +extern void init_command_db() +{ + char * f_name = "init_command_db()"; + char * path = "confclient/commands"; + FILE * file = try_fopen(path, "r", f_name); + uint32_t lines; + uint32_t linemax = textfile_sizes(file, &lines); + char line[linemax + 1]; + world.cmd_db.cmds = try_malloc(lines * sizeof(struct Command), f_name); + uint8_t i = 0; + while (try_fgets(line, linemax + 1, file, f_name)) + { + if ('\n' == line[0] || 0 == line[0]) + { + break; + } + world.cmd_db.cmds[i].id = atoi(strtok(line, " ")); + copy_tokenized_string(&world.cmd_db.cmds[i].dsc_short, " "); + copy_tokenized_string(&world.cmd_db.cmds[i].dsc_long, "\n"); + i++; + } + try_fclose(file, f_name); + world.cmd_db.n = lines; + set_cleanup_flag(CLEANUP_COMMANDS); +} + + + +extern void free_command_db() +{ + uint8_t i = 0; + while (i < world.cmd_db.n) + { + free(world.cmd_db.cmds[i].dsc_short); + free(world.cmd_db.cmds[i].dsc_long); + i++; + } + free(world.cmd_db.cmds); +} diff --git a/src/client/command_db.h b/src/client/command_db.h new file mode 100644 index 0000000..769670b --- /dev/null +++ b/src/client/command_db.h @@ -0,0 +1,46 @@ +/* src/client/command_db.h + * + * The Command DB collects identifiers and verbal descriptions of all commands + * the user can give. + */ + +#ifndef COMMAND_DB_H +#define COMMAND_DB_H + +#include /* uint8_t */ + + + +struct Command +{ + char * dsc_short; /* short string name of command to be used internally */ + char * dsc_long; /* long string description of command for the user */ + uint8_t id; /* unique identifier of command */ +}; + +struct CommandDB +{ + struct Command * cmds; /* memory area for sequence of all Command structs */ + uint8_t n; /* number of Command structs in database */ +}; + + + +/* Is "id" the ID of command whose dsc_short is "shortdsc"? Answer in binary. */ +extern uint8_t is_command_id_shortdsc(uint8_t id, char * shortdsc); + +/* Give short description of command ("dsc_short"), get its ID. */ +extern uint8_t get_command_id(char * dsc_short); + +/* Give short description of command ("dsc_short"), get long description. */ +extern char * get_command_longdsc(char * dsc_short); + +/* Reads CommandDB from CommandDB file, line by line, until first empty line. */ +extern void init_command_db(); + +/* Free all memory allocated with init_command_db. */ +extern void free_command_db(); + + + +#endif diff --git a/src/client/control.c b/src/client/control.c new file mode 100644 index 0000000..1ccaf45 --- /dev/null +++ b/src/client/control.c @@ -0,0 +1,267 @@ +/* src/client/control.c */ + +#include "control.h" +#include /* uint8_t, uint16_t */ +#include /* sprintf() */ +#include /* strlen() */ +#include "command_db.h" /* get_command_id(), is_command_id_shortdsc() */ +#include "io.h" /* try_send() */ +#include "keybindings.h" /* struct KeyBindingDB, get_actionname_to_keycode(), + * get_keycode_to_action(), mod_selected_keyb(), + * move_keyb_mod_selection() + */ +#include "map_window.h" /* for map_scroll(), map_center() */ +#include "misc.h" /* reload_interface_conf(), save_interface_conf(), + * nav_inventory() + */ +#include "wincontrol.h" /* struct WinConf, toggle_window(), toggle_winconfig(), + * scroll_pad(), get_winconf_by_win(), + * growshrink_active_window(), toggle_win_size_type() + */ +#include "windows.h" /* for cycle_active_win(), shift_active_win() */ +#include "world.h" /* for global world */ + + + +/* If "cmd" matches "match" in get_available_keycode_to_action(), execute "f" + * with provided char arguments and return 1; else only return 0. + */ +static uint8_t try_cmd_0args(int cmd, char * match, void (* f) ()); +static uint8_t try_cmd_1args(int cmd, char * match, void (* f) (char), char c); +static uint8_t try_cmd_2args(int cmd, char * match, + void (* f) (char, char), char c1, char c2); + +/* If "action" is id of command named "match", send (via try_send()) a string + * of "match" + " " + the string representation of "arg" to the server. + */ +static uint8_t try_player_cmd(int action, char * match, char * command, + uint8_t arg); + +/* Return keycode to action of "name" if available in current window config. */ +static uint16_t get_available_keycode_to_action(char * name); + +/* Return pointer to global keybindings or to keybindings for wingeometry config + * (c = "g") or winkeys config (c = "k") or active window's keybindings ("w"). + */ +static struct KeyBindingDB * select_keybindingdb_pointer(char c); + +/* Wrappers to make some functions compatible to try_cmd_* single char args. */ +static void wrap_mod_selected_keyb(char c); +static void wrap_mv_kb_mod(char c1, char c2); + + + +static uint8_t try_cmd_0args(int cmd, char * match, void (* f) ()) +{ + if (cmd == get_available_keycode_to_action(match)) + { + f(); + return 1; + } + return 0; +} + + + +static uint8_t try_cmd_1args(int cmd, char * match, void (* f) (char), char c) +{ + if (cmd == get_available_keycode_to_action(match)) + { + f(c); + return 1; + } + return 0; +} + + + +static uint8_t try_cmd_2args(int cmd, char * match, + void (* f) (char, char), char c1, char c2) +{ + if (cmd == get_available_keycode_to_action(match)) + { + f(c1, c2); + return 1; + } + return 0; +} + + + +static uint8_t try_player_cmd(int action, char * match, char * command, + uint8_t arg) +{ + if (is_command_id_shortdsc(action, match)) + { + uint8_t command_size = strlen(command); + uint8_t arg_size = 3; + char msg[command_size + 1 + arg_size + 1]; + sprintf(msg, "%s %d", command, arg); + try_send(msg); + return 1; + } + return 0; +} + + + +static uint16_t get_available_keycode_to_action(char * name) +{ + uint16_t keycode = get_keycode_to_action(world.kb_global.kbs, name); + if (0 != keycode || 0 == world.wmeta.active) + { + return keycode; + } + struct WinConf * wc = get_winconf_by_win(world.wmeta.active); + if (0 == wc->view) + { + keycode = get_keycode_to_action(wc->kb.kbs, name); + } + else if (1 == wc->view) + { + keycode = get_keycode_to_action(world.kb_wingeom.kbs, name); + } + else if (2 == wc->view) + { + keycode = get_keycode_to_action(world.kb_winkeys.kbs, name); + } + return keycode; +} + + + +static struct KeyBindingDB * select_keybindingdb_pointer(char c) +{ + struct KeyBindingDB * kbd; + kbd = &world.kb_global; + if ('g' == c) + { + kbd = &world.kb_wingeom; + } + else if ('k' == c) + { + kbd = &world.kb_winkeys; + } + else if ('w' == c) + { + struct WinConf * wc = get_winconf_by_win(world.wmeta.active); + kbd = &wc->kb; + } + return kbd; +} + + + +static void wrap_mod_selected_keyb(char c) +{ + mod_selected_keyb(select_keybindingdb_pointer(c)); +} + + + +static void wrap_mv_kb_mod(char c1, char c2) +{ + move_keyb_mod_selection(select_keybindingdb_pointer(c1), c2); +} + + + +extern uint8_t player_control(int key) +{ + char * action_name = get_actionname_to_keycode(world.kb_global.kbs, key); + if (NULL == action_name && 0 != world.wmeta.active) + { + struct WinConf * wc = get_winconf_by_win(world.wmeta.active); + action_name = get_actionname_to_keycode(wc->kb.kbs, key); + } + if (NULL != action_name) + { + uint8_t id = get_command_id(action_name); + if ( try_player_cmd(id, "wait", "wait", 0) + || try_player_cmd(id, "drop", "drop", world.player_inventory_select) + || try_player_cmd(id, "pick", "pick_up", 0) + || try_player_cmd(id, "use", "use", world.player_inventory_select) + || try_player_cmd(id, "player_u", "move", 'N') + || try_player_cmd(id, "player_d", "move", 'S') + || try_player_cmd(id, "player_r", "move", 'E') + || try_player_cmd(id, "player_l", "move", 'W')) + { + return 1; + } + } + return 0; +} + + + +extern uint8_t wingeom_control(int key) +{ + if ( try_cmd_1args(key, "to_height_t", toggle_win_size_type, 'y') + || try_cmd_1args(key, "to_width_t", toggle_win_size_type, 'x') + || try_cmd_1args(key, "grow_h", growshrink_active_window, '*') + || try_cmd_1args(key, "shri_h", growshrink_active_window, '_') + || try_cmd_1args(key, "grow_v", growshrink_active_window, '+') + || try_cmd_1args(key, "shri_v", growshrink_active_window, '-') + || try_cmd_1args(key, "shift_f", shift_active_win, 'f') + || try_cmd_1args(key, "shift_b", shift_active_win, 'b')) + { + return 1; + } + return 0; +} + + + +extern uint8_t winkeyb_control(int key) +{ + if ( try_cmd_1args(key, "w_keys_m", wrap_mod_selected_keyb, 'w') + || try_cmd_2args(key, "w_keys_u", wrap_mv_kb_mod, 'w', 'u') + || try_cmd_2args(key, "w_keys_d", wrap_mv_kb_mod, 'w', 'd')) + { + return 1; + } + return 0; +} + + + +extern uint8_t meta_control(int key) +{ + uint8_t ret = 2 * (key == get_available_keycode_to_action("quit")); + if ( (0 == ret) + && ( try_cmd_0args(key, "winconf", toggle_winconfig) + || try_cmd_0args(key, "reload_conf", reload_interface_conf) + || try_cmd_0args(key, "save_conf", save_interface_conf) + || try_cmd_0args(key, "map_c", map_center) + || try_cmd_1args(key, "scrl_r", scroll_pad, '+') + || try_cmd_1args(key, "scrl_l", scroll_pad, '-') + || try_cmd_1args(key, "to_a_keywin", toggle_window, 'k') + || try_cmd_1args(key, "to_g_keywin", toggle_window, '0') + || try_cmd_1args(key, "to_wg_keywin", toggle_window, '1') + || try_cmd_1args(key, "to_wk_keywin", toggle_window, '2') + || try_cmd_1args(key, "to_mapwin", toggle_window, 'm') + || try_cmd_1args(key, "to_infowin", toggle_window, 'i') + || try_cmd_1args(key, "to_inv", toggle_window, 'c') + || try_cmd_1args(key, "to_logwin", toggle_window, 'l') + || try_cmd_1args(key, "cyc_win_f", cycle_active_win, 'f') + || try_cmd_1args(key, "cyc_win_b", cycle_active_win, 'b') + || try_cmd_1args(key, "g_keys_m", wrap_mod_selected_keyb, 'G') + || try_cmd_1args(key, "wg_keys_m", wrap_mod_selected_keyb, 'g') + || try_cmd_1args(key, "wk_keys_m", wrap_mod_selected_keyb, 'k') + || try_cmd_1args(key, "inv_u", nav_inventory, 'u') + || try_cmd_1args(key, "inv_d", nav_inventory, 'd') + || try_cmd_1args(key, "map_u", map_scroll, 'N') + || try_cmd_1args(key, "map_d", map_scroll, 'S') + || try_cmd_1args(key, "map_r", map_scroll, 'E') + || try_cmd_1args(key, "map_l", map_scroll, 'W') + || try_cmd_2args(key, "g_keys_u", wrap_mv_kb_mod, 'G', 'u') + || try_cmd_2args(key, "g_keys_d", wrap_mv_kb_mod, 'G', 'd') + || try_cmd_2args(key, "wg_keys_u", wrap_mv_kb_mod, 'g', 'u') + || try_cmd_2args(key, "wg_keys_d", wrap_mv_kb_mod, 'g', 'd') + || try_cmd_2args(key, "wk_keys_u", wrap_mv_kb_mod, 'k', 'u') + || try_cmd_2args(key, "wk_keys_d", wrap_mv_kb_mod, 'k', 'd'))) + { + ret = 1; + } + return ret; +} diff --git a/src/client/control.h b/src/client/control.h new file mode 100644 index 0000000..71aa30a --- /dev/null +++ b/src/client/control.h @@ -0,0 +1,33 @@ +/* src/client/control.h + * + * Routines for handling control input from the keyboard. + */ + +#ifndef CONTROL_H +#define CONTROL_H + +#include /* uint8_t, uint16_t */ + + + +/* Try to call by "key" player actions triggering messages to the server. Return + * 1 on success, 0 on failure. + */ +extern uint8_t player_control(int key); + +/* Try to call by "key" actions for active window's keybindings/geometry config + * view. Return 1 on success, 0 on failure. + */ +extern uint8_t wingeom_control(int key); +extern uint8_t winkeyb_control(int key); + +/* Try to call by "key" basic window and game management actions that do not + * send a message to the server and do not change individual windows' + * keybindings or geometries. Returns 0 on failure, 2 for calling a "quit" + * action (to be handled externally) and 1 for calling any other action. + */ +extern uint8_t meta_control(int key); + + + +#endif diff --git a/src/client/draw_wins.c b/src/client/draw_wins.c new file mode 100644 index 0000000..5fe0530 --- /dev/null +++ b/src/client/draw_wins.c @@ -0,0 +1,472 @@ +/* src/client/draw_wins.c */ + +#include "draw_wins.h" +#include /* attri_t, chtype */ +#include /* uint8_t, uint16_t, uint32_t, int16_t */ +#include /* for sprintf() */ +#include /* free() */ +#include /* strlen(), strtok() */ +#include "../common/try_malloc.h" /* for try_malloc() */ +#include "command_db.h" /* for get_command_longdsc */ +#include "keybindings.h" /* struct KeyBinding, get_name_to_keycode() */ +#include "wincontrol.h" /* struct WinConf, get_winconf_by_win() */ +#include "windows.h" /* struct Win */ +#include "world.h" /* global world */ + + + +/* Apply to the winmap of Win "w" the new sizes "new_size_y" and "new_size_x" + * to the degree that they extend it. Re-shape the window content accordingly. + */ +static void try_resize_winmap(struct Win * w, int new_size_y, int new_size_x); + +/* In Win "w", write "ch" to coordinate "y"/"x". */ +static void set_ch_on_yx(struct Win * w, int y, int x, chtype ch); + +/* Add "text" into window "win". Break text at right window edge. Also break at + * newlines. If "text" ends in a newline, ignore it. + */ +static void add_text_with_linebreaks(struct Win * win, char * text); + +/* Add "line" into window "w". Apply ncurses attribute "attri" to all + * characters drawn. If "attri" is non-zero, fill the entire line until the + * right window edge with empty characters, so "attri" applies on those too. + */ +static void add_line(struct Win * w, char * line, attr_t attri); + +/* Write "text" with add_text_with_linebreaks() as not starting from the top but + * from bottom of "win". Draw only what fits in window (avoid scroll hints). + */ +static void draw_text_from_bottom(struct Win * win, char * text); + +/* Return keybinding list line via "kb_pp", iterate pointer pointed to by it. */ +static char * get_kb_line_and_iterate(struct KeyBinding ** kb_pp); + +/* Draw from line "start" on config view for keybindings defined at "kb". */ +static void draw_kb_view(struct Win * w, struct KeyBindingDB * kb, + uint8_t start); + +/* Draw into window "w" from line "start" on a "title" followed by an empty + * line followed by a list of all keybindings starting at kb_p. + */ +static uint16_t draw_titled_keybinding_list(char * title, struct Win * w, + uint16_t start, + struct KeyBinding * kb_p); + + + +static void try_resize_winmap(struct Win * w, int new_size_y, int new_size_x) +{ + char * f_name = "try_resize_winmap()"; + if (w->winmapsize.y >= new_size_y && w->winmapsize.x >= new_size_x) + { + return; + } + if (w->winmapsize.y > new_size_y) + { + new_size_y = w->winmapsize.y; + } + else if (w->winmapsize.x > new_size_x) + { + new_size_x = w->winmapsize.x; + } + chtype * old_winmap = w->winmap; + uint32_t new_size = sizeof(chtype) * new_size_y * new_size_x; + w->winmap = try_malloc(new_size, f_name); + uint16_t y, x; + for (y = 0; y < new_size_y; y++) + { + for (x = 0; y < w->winmapsize.y && x < w->winmapsize.x; x++) + { + chtype ch = old_winmap[(y * w->winmapsize.x) + x]; + w->winmap[(y * new_size_x) + x] = ch; + } + for (; x < new_size_x; x++) + { + w->winmap[(y * new_size_x) + x] = ' '; + } + } + free(old_winmap); + w->winmapsize.y = new_size_y; + w->winmapsize.x = new_size_x; +} + + + +static void set_ch_on_yx(struct Win * w, int y, int x, chtype ch) +{ + w->winmap[(y * w->winmapsize.x) + x] = ch; +} + + + +static void add_text_with_linebreaks(struct Win * win, char * text) +{ + uint16_t x, y; + int16_t z = -1; + for (y = win->winmapsize.y; ; y++) + { + try_resize_winmap(win, y + 1, win->framesize.x); + for (x = 0; x < win->framesize.x; x++) + { + z++; + if ('\n' == text[z]) + { + break; + } + else if ('\0' == text[z]) + { + return; + } + else + { + set_ch_on_yx(win, y, x, text[z]); + } + if ('\n' == text[z+1]) + { + z++; + break; + } + else if ('\0' == text[z+1]) + { + return; + } + } + } +} + + + +static void add_line(struct Win * w, char * line, attr_t attri) +{ + uint16_t y = w->winmapsize.y; + uint16_t len_line = strlen(line); + if (attri + && w->winmapsize.x < w->framesize.x && w->framesize.x > len_line) + { + try_resize_winmap(w, y + 1, w->framesize.x); + } + else + { + try_resize_winmap(w, y + 1, strlen(line)); + } + uint16_t x = 0; + for (; x < len_line; x++) + { + set_ch_on_yx(w, y, x, line[x] | attri); + } + if (attri) + { + for (; x < w->framesize.x; x++) + { + set_ch_on_yx(w, y, x, ' ' | attri); + } + } +} + + + +static void draw_text_from_bottom(struct Win * win, char * text) +{ + /* Determine number of lines text would have in a window of win's width, + * but infinite height. Treat \n and \0 as control chars for incrementing + * y and stopping the loop. Make sure +they* don't count as cell space. + */ + char toggle = 0; + uint16_t x, y; + int16_t z = -1; + for (y = 0; 0 == toggle; y++) + { + for (x = 0; x < win->framesize.x; x++) + { + z++; + if ('\n' == text[z]) + { + break; + } + if ('\n' == text[z+1]) + { + z++; + break; + } + else if (0 == text[z+1]) + { + toggle = 1; + break; + } + } + } + z = -1; + + /* Depending on what's bigger, determine start point in window or text. */ + uint16_t start_y = 0; + if (y < win->framesize.y) + { + start_y = win->framesize.y - y; + } + else if (y > win->framesize.y) + { + uint16_t offset = y - win->framesize.y; + for (y = 0; y < offset; y++) + { + for (x = 0; x < win->framesize.x; x++) + { + z++; + if ('\n' == text[z]) + { + break; + } + if ('\n' == text[z+1]) + { + z++; + break; + } + } + } + text = text + (sizeof(char) * (z + 1)); + } + + try_resize_winmap(win, start_y, 1); + add_text_with_linebreaks(win, text); +} + + + +static char * get_kb_line_and_iterate(struct KeyBinding ** kb_pp) +{ + char * f_name = "get_kb_line_and_iterate()"; + struct KeyBinding * kb_p = * kb_pp; + char * keyname = get_name_to_keycode(kb_p->key); + char * cmd_dsc = get_command_longdsc(kb_p->name); + uint16_t size = 9 + 1 + strlen(cmd_dsc) + 1; + char * line = try_malloc(size, f_name); + sprintf(line, "%-9s %s", keyname, cmd_dsc); + free(keyname); + * kb_pp = kb_p->next; + return line; +} + + + +static void draw_kb_view(struct Win * w, struct KeyBindingDB * kb, + uint8_t start) +{ + if (0 == kb->kbs) + { + add_line(w, "(none)", 0); + return; + } + struct KeyBinding * kb_p = kb->kbs; + uint16_t y; + for (y = start; 0 != kb_p; y++) + { + attr_t attri = 0; + if (y - start == kb->select) + { + attri = A_REVERSE; + if (1 == kb->edit) + { + attri = attri | A_BLINK; + } + } + char * kb_line = get_kb_line_and_iterate(&kb_p); + add_line(w, kb_line, attri); + free(kb_line); + } +} + + + +static uint16_t draw_titled_keybinding_list(char * title, struct Win * w, + uint16_t start, + struct KeyBinding * kb_p) +{ + uint16_t y; + uint8_t state = 0; + for (y = start; (0 == state || 0 != kb_p); y++) + { + if (0 == state) + { + add_line(w, title, 0); + y++; + add_line(w, " ", 0); + state = 1 + (0 == kb_p); + continue; + } + char * kb_line = get_kb_line_and_iterate(&kb_p); + add_line(w, kb_line, 0); + free(kb_line); + } + if (2 == state) + { + char * none = "(none)"; + add_line(w, none, 0); + y++; + } + return y; +} + + + +extern void draw_win_log(struct Win * win) +{ + char * log = ""; + if (world.log) + { + log = world.log; + } + draw_text_from_bottom(win, log); +} + + + +extern void draw_win_map(struct Win * win) +{ + try_resize_winmap(win, world.map.size.y, world.map.size.x); + uint16_t z = 0; + uint16_t x, y; + for (y = 0; y < world.map.size.y; y++) + { + for (x = 0; x < world.map.size.x; x++) + { + set_ch_on_yx(win, y, x, world.map.cells[z]); + z++; + } + } +} + + + +extern void draw_win_info(struct Win * win) +{ + char * dsc_turn = "Turn: "; + char * dsc_hitpoints = "\nHitpoints: "; + char * dsc_score = "\nScore: "; + uint16_t maxl = strlen(dsc_turn) + strlen(dsc_hitpoints) + strlen(dsc_score) + + 5 + 3 + 5; /* Max strlens of strings of numbers to use. */ + char text[maxl + 1]; + sprintf(text, "%s%d%s%d%s%d", + dsc_turn, world.turn, + dsc_hitpoints, world.player_lifepoints, + dsc_score, world.score); + add_text_with_linebreaks(win, text); +} + + + +extern void draw_win_inventory(struct Win * win) +{ + win->center.y = world.player_inventory_select; + char inventory_copy[strlen(world.player_inventory) + 1]; + sprintf(inventory_copy, "%s", world.player_inventory); + char * foo = inventory_copy; + uint8_t i = 0; + while (1) + { + char * object = strtok(foo, "\n"); + foo = NULL; + if (NULL == object) + { + return; + } + attr_t attri = 0; + if (i == world.player_inventory_select) + { + attri = A_REVERSE; + } + add_line(win, object, attri); + i++; + } +} + + + +extern void draw_win_available_keybindings(struct Win * win) +{ + char * title = "Active window's keybindings:"; + struct KeyBinding * kb_p; + struct WinConf * wc = get_winconf_by_win(world.wmeta.active); + if (0 == wc->view) + { + kb_p = wc->kb.kbs; + } + else if (1 == wc->view) + { + kb_p = world.kb_wingeom.kbs; + } + else if (2 == wc->view) + { + kb_p = world.kb_winkeys.kbs; + } + uint16_t offset = draw_titled_keybinding_list(title, win, 0, kb_p); + add_line(win, " ", 0); + struct KeyBinding * kbs_glo = world.kb_global.kbs; + draw_titled_keybinding_list("Global keybindings", win, offset + 1, kbs_glo); +} + + + +extern void draw_win_keybindings_global(struct Win * win) +{ + win->center.y = world.kb_global.select; + draw_kb_view(win, &world.kb_global, 0); +} + + + +extern void draw_win_keybindings_winconf_geometry(struct Win * win) +{ + win->center.y = world.kb_wingeom.select; + draw_kb_view(win, &world.kb_wingeom, 0); +} + + + +extern void draw_win_keybindings_winconf_keybindings(struct Win * win) +{ + win->center.y = world.kb_winkeys.select; + draw_kb_view(win, &world.kb_winkeys, 0); +} + + + +extern void draw_winconf_keybindings(struct Win * win) +{ + struct WinConf * wc = get_winconf_by_win(win); + char * title = "Window's keybindings:"; + add_line(win, title, 0); + add_line(win, " ", 0); + draw_kb_view(win, &wc->kb, 2); + win->center.y = wc->kb.select + 2; +} + + + +extern void draw_winconf_geometry(struct Win * win) +{ + struct WinConf * wcp = get_winconf_by_win(win); + char * title = "Window's geometry:\n"; + char * h_d = "\nHeight to save: "; + char * h_pos = " (width in cells)"; + char * h_neg = " (negative diff: cells to screen width)"; + char * w_d = "\n\nWidth to save: "; + char * w_pos = " (height in cells)"; + char * w_neg = " (negative diff: cells to screen height)"; + char * h_t = h_pos; + char * w_t = w_pos; + if (1 == wcp->height_type) + { + h_t = h_neg; + } + if (1 == wcp->width_type) + { + w_t = w_neg; + } + uint16_t maxl = strlen(title) + + strlen(h_t) + strlen(h_d) + 6 /* 6 = n of chars to */ + + strlen(w_t) + strlen(w_d) + 6 + 1; /* write max int16_t */ + char text[maxl + 1]; + sprintf(text, "%s%s%d%s%s%d%s", title, h_d, wcp->height, h_t, + w_d, wcp->width, w_t); + add_text_with_linebreaks(win, text); +} diff --git a/src/client/draw_wins.h b/src/client/draw_wins.h new file mode 100644 index 0000000..4dd30b8 --- /dev/null +++ b/src/client/draw_wins.h @@ -0,0 +1,29 @@ +/* src/client/draw_wins.h + * + * Routines for drawing the game's windows' contents. + */ + +#ifndef DRAW_WINS_H +#define DRAW_WINS_H + +struct Win; + + + +/* Default routines to draw the various windows' standard contents. */ +extern void draw_win_log(struct Win * win); +extern void draw_win_map(struct Win * win); +extern void draw_win_info(struct Win * win); +extern void draw_win_inventory(struct Win * win); +extern void draw_win_available_keybindings(struct Win * win); +extern void draw_win_keybindings_global(struct Win * win); +extern void draw_win_keybindings_winconf_geometry(struct Win * win); +extern void draw_win_keybindings_winconf_keybindings(struct Win * win); + +/* Routines to draw windows' configuration views. */ +extern void draw_winconf_keybindings(struct Win * win); +extern void draw_winconf_geometry(struct Win * win); + + + +#endif diff --git a/src/client/io.c b/src/client/io.c new file mode 100644 index 0000000..3284389 --- /dev/null +++ b/src/client/io.c @@ -0,0 +1,290 @@ +/* src/client/io.c */ + +#include "io.h" +#include /* global errno */ +#include /* open() */ +#include /* PIPE_BUF */ +#include /* halfdelay(), getch() */ +#include /* uint8_t, uint16_t, uint32_t */ +#include /* FILE, sprintf(), fseek() */ +#include /* strcmp(), strlen(), memcpy() */ +#include /* free(), atoi() */ +#include /* stat() */ +#include /* access(), write() */ +#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() + */ +#include "control.h" /* meta_control(), player_control(), wingeom_control(), + * winkeyb_control() + */ +#include "map_window.h" /* for map_center() */ +#include "wincontrol.h" /* WinConf struct, get_winconf_by_win() */ +#include "windows.h" /* draw_all_wins() */ +#include "world.h" /* world global */ + + + +/* Read next lines of "file" up to (and excluding) a line "%\n" into the + * world.player_inventory string. + */ +static void read_inventory(char * read_buf, uint32_t linemax, FILE * file); + +/* Read the next characters in "file" into world.map.cells. In detail: Read + * world.map.size.y times world.map.size.x characters, followed by one ignored + * character (that we assume is a newline). + */ +static void read_map_cells(FILE * file); + +/* Repeatedly use try_fgets() with given arguments to read the remaining lines + * of "file" into the world.log string. + */ +static void read_log(char * read_buf, uint32_t linemax, FILE * file); + +/* Return value seen by atoi() in next line of "file" when passed to try_fgets() + * with the given arguments. + */ +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. + * + * 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 + * (and if it is changed, world.last_update is re-set to it). If this does not + * verify a change, the first bytes of the file are read to compare the game + * turn number described therein to the last read turn number in world.turn. + * + * The stat() check is mostly useless, for it only detects file updates once a + * second. But the turn check fails if a new world is generated from turn 1 on: + * 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); + +/* 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. + * + * Note that the first successful read_world() triggers map_center(), so that on + * start the client focuses the map window on the player. + */ +static uint8_t read_world(); + + + +static void read_inventory(char * read_buf, uint32_t linemax, FILE * file) +{ + char * f_name = "read_inventory()"; + char * delimiter = "%\n"; + free(world.player_inventory); + world.player_inventory = NULL; + while (1) + { + try_fgets(read_buf, linemax + 1, file, f_name); + if (!(strcmp(read_buf, delimiter))) + { + break; + } + int old_size = 0; + if (NULL != world.player_inventory) + { + old_size = strlen(world.player_inventory); + } + int new_size = strlen(read_buf); + char * new_inventory = try_malloc(old_size + new_size + 1, f_name); + memcpy(new_inventory, world.player_inventory, old_size); + sprintf(new_inventory + old_size, "%s", read_buf); + free(world.player_inventory); + world.player_inventory = new_inventory; + } + world.player_inventory[strlen(world.player_inventory) - 1] = '\0'; + world.player_inventory_select = 0; +} + + + +static void read_map_cells(FILE * file) +{ + char * f_name = "read_map_cells()"; + free(world.map.cells); + world.map.cells = try_malloc(world.map.size.y * world.map.size.x, f_name); + uint16_t y, x; + for (y = 0; y < world.map.size.y; y++) + { + for (x = 0; x < world.map.size.x; x++) + { + char c = try_fgetc(file, f_name); + world.map.cells[(y * world.map.size.x) + x] = c; + } + try_fgetc(file, f_name); + } +} + + + +static void read_log(char * read_buf, uint32_t linemax, FILE * file) +{ + char * f_name = "read_log()"; + free(world.log); + world.log = NULL; + while (try_fgets(read_buf, linemax + 1, file, f_name)) + { + int old_size = 0; + if (NULL != world.log) + { + old_size = strlen(world.log); + } + int new_size = strlen(read_buf); + char * new_log = try_malloc(old_size + new_size + 1, f_name); + memcpy(new_log, world.log, old_size); + sprintf(new_log + old_size, "%s", read_buf); + free(world.log); + world.log = new_log; + } +} + + + +static uint16_t read_value_from_line(char * read_buf, uint32_t linemax, + FILE * file) +{ + char * f_name = "read_value_from_line()"; + try_fgets(read_buf, linemax + 1, file, f_name); + return atoi(read_buf); +} + + + +static FILE * changed_server_out_file(char * path) +{ + char * f_name = "changed_server_out_file()"; + struct stat stat_buf; + exit_trouble(stat(path, &stat_buf), f_name, "stat()"); + if (stat_buf.st_mtime != world.last_update) + { + world.last_update = stat_buf.st_mtime; + return try_fopen(path, "r", f_name); + } + FILE * file = try_fopen(path, "r", f_name); + char turn_string[6]; + try_fgets(turn_string, 6, file, f_name); + if (world.turn == atoi(turn_string)) + { + try_fclose(file, f_name); + return NULL; + } + exit_trouble(fseek(file, 0, SEEK_SET), f_name, "fseek()"); + return file; +} + + + +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."; + static uint8_t first_read = 1; + exit_err(access(path, F_OK), quit_msg); + FILE * file = changed_server_out_file(path); + if (!file) + { + return 0; + } + uint32_t linemax = textfile_sizes(file, NULL); + char * read_buf = try_malloc(linemax + 1, f_name); + world.turn = read_value_from_line(read_buf, linemax, file); + world.score = read_value_from_line(read_buf, linemax, file); + world.player_lifepoints = read_value_from_line(read_buf, linemax, file); + read_inventory(read_buf, linemax, file); + world.player_pos.y = read_value_from_line(read_buf, linemax, file); + world.player_pos.x = read_value_from_line(read_buf, linemax, file); + if (first_read) + { + map_center(); + first_read = 0; + } + world.map.size.y = read_value_from_line(read_buf, linemax, file); + world.map.size.x = read_value_from_line(read_buf, linemax, file); + read_map_cells(file); + read_log(read_buf, linemax, file); + free(read_buf); + try_fclose(file, f_name); + return 1; +} + + + +extern void try_send(char * msg) +{ + 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) + { + 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++; + } + exit_err(0 == j, "Failed to open server fifo for writing."); + j = 1; + while (0 != j) + { + int test = write(fd_out, msg, msg_size); + if (test > 0) + { + break; + } + j++; + } + exit_err(0 == j, "Failed to write to server fifo."); + exit_trouble(-1 == close(fd_out), f_name, "close()"); +} + + + +extern char * io_loop() +{ + 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; + while (1) + { + if (read_world() || change_in_client) + { + draw_all_wins(); + } + change_in_client = 0; + int key = getch(); + if (ERR != key) + { + change_in_client = meta_control(key); + if (2 == change_in_client) + { + break; + } + if (!change_in_client) + { + change_in_client = player_control(key); + } + if (!change_in_client) + { + struct WinConf * wc = get_winconf_by_win(world.wmeta.active); + change_in_client = (1 == wc->view && wingeom_control(key)) + || (2 == wc->view && winkeyb_control(key)); + } + } + } + try_send("QUIT"); + return "Sent QUIT to server."; +} diff --git a/src/client/io.h b/src/client/io.h new file mode 100644 index 0000000..10851d3 --- /dev/null +++ b/src/client/io.h @@ -0,0 +1,29 @@ +/* src/client/io.h + * + * Communication of the client with the server (by reading and writing files) + * and the user (by writing to the screen and reading keypresses). + */ + +#ifndef IO_H +#define IO_H + + + +/* 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. + */ +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. + */ +extern char * io_loop(); + + + +#endif diff --git a/src/client/keybindings.c b/src/client/keybindings.c new file mode 100644 index 0000000..afafb9c --- /dev/null +++ b/src/client/keybindings.c @@ -0,0 +1,257 @@ +/* src/client/keybindings.c */ + +#include "keybindings.h" +#include /* keycode defines, cbreak(), halfdelay(), getch() */ +#include /* FILE, sprintf(), snprintf() */ +#include /* uint8_t, uint16_t, uint32_t */ +#include /* free(), atoi() */ +#include /* strlen(), strchr(), strcmp(), memcpy() */ +#include "../common/readwrite.h" /* try_fopen(), textfile_sizes(), try_fgets(), + * try_fclose(), try_fclose_unlink_rename(), + * try_fwrite() + */ +#include "../common/try_malloc.h" /* for try_malloc() */ +#include "windows.h" /* draw_all_wins() */ +#include "world.h" /* global world */ + + + +/* Return "n"-th keybinding in keybindings chain from "kb_p" on. */ +static struct KeyBinding * get_keyb_of_n(struct KeyBinding * kb_p, uint16_t n); + +//* Return number of keybindings in keybindings chain from "kb_p" on. */ +static uint16_t get_n_of_keybs(struct KeyBinding * kb_p); + +/* If "keycode_given" equals "keycode_match", copy "keyname_match" to "keyname" + * and return 1; otherwise return 0. + */ +static uint8_t try_keycode(uint16_t keycode_given, char * keyname, + uint16_t keycode_match, char * keyname_match); + + + +static struct KeyBinding * get_keyb_of_n(struct KeyBinding * kb_p, uint16_t n) +{ + uint16_t i = 0; + while (1) + { + if (n == i) + { + break; + } + i++; + kb_p = kb_p->next; + } + return kb_p; +} + + + +static uint16_t get_n_of_keybs(struct KeyBinding * kb_p) +{ + uint16_t i = 0; + while (1) + { + if (0 == kb_p) + { + break; + } + i++; + kb_p = kb_p->next; + } + return i; +} + + + +static uint8_t try_keycode(uint16_t keycode_given, char * keyname, + uint16_t keycode_match, char * keyname_match) +{ + if (keycode_given == keycode_match) + { + sprintf(keyname, keyname_match); + return 1; + } + return 0; +} + + + +extern char * get_actionname_to_keycode(struct KeyBinding * kb_p, uint16_t key) +{ + while (0 != kb_p) + { + if (key == kb_p->key) + { + return kb_p->name; + } + kb_p = kb_p->next; + } + return NULL; +} + + + +extern uint16_t get_keycode_to_action(struct KeyBinding * kb_p, char * name) +{ + while (0 != kb_p) + { + if (0 == strcmp(kb_p->name, name)) + { + return kb_p->key; + } + kb_p = kb_p->next; + } + return 0; +} + + + +extern char * get_name_to_keycode(uint16_t keycode) +{ + char * f_name = "get_name_to_keycode()"; + char * keyname = try_malloc(15, f_name); /* FIXME: Why 15? */ + if (32 < keycode && keycode < 127) + { + sprintf(keyname, "%c", keycode); + } + else if (keycode >= KEY_F0 && keycode <= KEY_F(63)) + { + uint16_t f = keycode - KEY_F0; + sprintf(keyname, "F%d", f); + } + else if ( try_keycode(keycode, keyname, 9, "TAB") + || try_keycode(keycode, keyname, 10, "RETURN") + || try_keycode(keycode, keyname, 27, "ESCAPE") + || try_keycode(keycode, keyname, 32, "SPACE") + || try_keycode(keycode, keyname, KEY_UP, "UP") + || try_keycode(keycode, keyname, KEY_DOWN, "DOWN") + || try_keycode(keycode, keyname, KEY_LEFT, "LEFT") + || try_keycode(keycode, keyname, KEY_RIGHT, "RIGHT") + || try_keycode(keycode, keyname, KEY_HOME, "HOME") + || try_keycode(keycode, keyname, KEY_BACKSPACE, "BACKSPACE") + || try_keycode(keycode, keyname, KEY_DC, "DELETE") + || try_keycode(keycode, keyname, KEY_IC, "INSERT") + || try_keycode(keycode, keyname, KEY_NPAGE, "NEXT PAGE") + || try_keycode(keycode, keyname, KEY_PPAGE, "PREV PAGE") + || try_keycode(keycode, keyname, KEY_END, "END")) + { + ; + } + else + { + sprintf(keyname, "(unknown)"); + } + return keyname; +} + + + +extern void init_keybindings(char * path, struct KeyBindingDB * kbd) +{ + char * f_name = "init_keybindings()"; + FILE * file = try_fopen(path, "r", f_name); + uint32_t lines; + uint32_t linemax = textfile_sizes(file, &lines); + char command[linemax + 1]; + char * cmdptr; + struct KeyBinding ** loc_last_ptr = &kbd->kbs; + * loc_last_ptr = 0; + while (try_fgets(command, linemax + 1, file, f_name)) + { + if ('\n' == command[0] || 0 == command[0]) + { + break; + } + * loc_last_ptr = try_malloc(sizeof(struct KeyBinding), f_name); + struct KeyBinding * kb_p = * loc_last_ptr; + kb_p->next = 0; + kb_p->key = atoi(command); + cmdptr = strchr(command, ' ') + 1; + kb_p->name = try_malloc(strlen(cmdptr), f_name); + memcpy(kb_p->name, cmdptr, strlen(cmdptr) - 1); + kb_p->name[strlen(cmdptr) - 1] = '\0'; + loc_last_ptr = & kb_p->next; + } + try_fclose(file, f_name); + kbd->edit = 0; + kbd->select = 0; +} + + + +extern void save_keybindings(char * path, struct KeyBindingDB * kbd) +{ + char * f_name = "save_keybindings()"; + char path_tmp[strlen(path) + 4 + 1]; + sprintf(path_tmp, "%s_tmp", path); + FILE * file = try_fopen(path_tmp, "w", f_name); + uint16_t linemax = 0; + struct KeyBinding * kb_p = kbd->kbs; + while (0 != kb_p) + { + if (strlen(kb_p->name) > linemax) + { + linemax = strlen(kb_p->name); + } + kb_p = kb_p->next; + } + linemax = linemax + 6; /* + 6 = + 3 digits + whitespace + \n + \0 */ + char line[linemax]; + kb_p = kbd->kbs; + while (0 != kb_p) + { + snprintf(line, linemax, "%d %s\n", kb_p->key, kb_p->name); + try_fwrite(line, sizeof(char), strlen(line), file, f_name); + kb_p = kb_p->next; + } + try_fclose_unlink_rename(file, path_tmp, path, f_name); +} + + + +extern void free_keybindings(struct KeyBinding * kb_start) +{ + if (0 == kb_start) + { + return; + } + struct KeyBinding * kb_p = kb_start->next; + if (0 != kb_p) + { + free_keybindings(kb_p); + } + free(kb_start->name); + free(kb_start); +} + + + +extern void mod_selected_keyb(struct KeyBindingDB * kbd) +{ + kbd->edit = 1; + draw_all_wins(); + cbreak(); + int key = getch(); + halfdelay(world.halfdelay); + if (key < 1000) + { + struct KeyBinding * kb_p = get_keyb_of_n(kbd->kbs, kbd->select); + kb_p->key = key; + } + kbd->edit = 0; +} + + + +extern void move_keyb_mod_selection(struct KeyBindingDB * kbd, char dir) +{ + if ('u' == dir && kbd->select > 0) + { + kbd->select--; + } + else if ('d' == dir && kbd->select < get_n_of_keybs(kbd->kbs) - 1) + { + kbd->select++; + } +} diff --git a/src/client/keybindings.h b/src/client/keybindings.h new file mode 100644 index 0000000..4f04342 --- /dev/null +++ b/src/client/keybindings.h @@ -0,0 +1,63 @@ +/* src/client/keybindings.h + * + * Database of keybindings and functions to read and manipulate it. + */ + +#ifndef KEYBINDINGS_H +#define KEYBINDINGS_H + +#include /* uint8_t, uint16_t */ + + + +struct KeyBinding +{ + struct KeyBinding * next; + uint16_t key; /* keycode */ + char * name; /* name of functionality bound to keycode */ +}; + +struct KeyBindingDB +{ + struct KeyBinding * kbs; + uint16_t select; /* linear list index of keybinding selected for editing */ + uint8_t edit; /* 1 if currently editing a keybinding, else 0 */ +}; + + + +/* Return name of action / functionality coupled to keycode; NULL on failure. */ +extern char * get_actionname_to_keycode(struct KeyBinding * kb_p, uint16_t key); + +/* Return keycode matched by keybinding to command of "name". */ +extern uint16_t get_keycode_to_action(struct KeyBinding * keybindings, + char * name); + +/* Return human-readable name (of maximum 9 chars) for "keycode" as matched by + * ncurses.h; if none is found, return "UNKNOWN". + */ +extern char * get_name_to_keycode(uint16_t keycode); + +/* Initialize/save keybindings data from/to file at "path" to/from keybindings + * data pointer "kbd". + */ +extern void init_keybindings(char * path, struct KeyBindingDB * kbd); +extern void save_keybindings(char * path, struct KeyBindingDB * kbd); + +/* Free keybinding chain starting at "kb_start". */ +extern void free_keybindings(struct KeyBinding * kb_start); + +/* Mark keybinding selected for modification as being edited, get user input to + * modify it, then unmark it again. Ensure there are max. three digits in the + * ASCII representation of the keycode read from the user. + */ +extern void mod_selected_keyb(struct KeyBindingDB * kbd); + +/* Move keybinding modification selection upwards ("dir"=="u") or downwards + * ("dir"=="d") within the limits of the keybindings chain length. + */ +extern void move_keyb_mod_selection(struct KeyBindingDB * kbd, char dir); + + + +#endif diff --git a/src/client/main.c b/src/client/main.c new file mode 100644 index 0000000..26ad728 --- /dev/null +++ b/src/client/main.c @@ -0,0 +1,39 @@ +/* main.c */ + +#include /* exit() */ +#include "../common/rexit.h" /* set_cleanup_func() */ +#include "cleanup.h" /* cleanup() */ +#include "command_db.h" /* init_command_db() */ +#include "io.h" /* io_loop(), try_send() */ +#include "misc.h" /* load_interface_conf() */ +#include "windows.h" /* init_win_meta(); */ +#include "world.h" /* struct World */ + + + +struct World world; + + + +int main() +{ + /* Declare hard-coded paths here. */ + world.path_server_in = "server/in"; + + /* So error exits also go through the client's cleanup() function. */ + set_cleanup_func(cleanup); + + /* Initialize the whole interface. */ + init_win_meta(); + keypad(world.wmeta.screen, TRUE); + init_command_db(); + load_interface_conf(); + + /* This is where most everything happens. */ + char * quit_msg = io_loop(); + + /* Leave properly. */ + cleanup(); + printf("%s\n", quit_msg); + exit(EXIT_SUCCESS); +} diff --git a/src/client/map_window.c b/src/client/map_window.c new file mode 100644 index 0000000..aace59c --- /dev/null +++ b/src/client/map_window.c @@ -0,0 +1,48 @@ +/* src/client/map_window.c */ + +#include "map_window.h" +#include /* uint16_t */ +#include "misc.h" /* center_offset() */ +#include "wincontrol.h" /* get_win_by_id() */ +#include "windows.h" /* struct Win */ +#include "world.h" /* for global world */ + + + +extern void map_scroll(char d) +{ + struct Win * win = get_win_by_id('m'); + uint16_t offset; + if (('N' == d || 'S' == d) && world.map.size.y > win->framesize.y) + { + offset = center_offset(win->center.y, + world.map.size.y, win->framesize.y); + win->center.y = offset + (win->framesize.y / 2); + if ('S' == d && win->center.y < world.map.size.y - 1) + { + win->center.y++; + return; + } + win->center.y = win->center.y - ('N' == d && win->center.y > 0); + } + else if (('W' == d || 'E' == d) && world.map.size.x > win->framesize.x) + { + offset = center_offset(win->center.x, + world.map.size.x, win->framesize.x); + win->center.x = offset + (win->framesize.x / 2); + if ('E' == d && win->center.x < world.map.size.x - 1) + { + win->center.x++; + return; + } + win->center.x = win->center.x - ('W' == d && win->center.x > 0); + } +} + + + +extern void map_center() +{ + struct Win * win_map = get_win_by_id('m'); + win_map->center = world.player_pos; +} diff --git a/src/client/map_window.h b/src/client/map_window.h new file mode 100644 index 0000000..46ca435 --- /dev/null +++ b/src/client/map_window.h @@ -0,0 +1,21 @@ +/* src/client/map_window.h + * + * Routines to re-focus the game map window. + */ + +#ifndef MAP_WINDOW_H +#define MAP_WINDOW_H + +#include "../common/yx_uint16.h" /* for yx_uint16 */ + + + +/* Try changing map window's focus into direction "d" (north = "N" etc.). */ +extern void map_scroll(char d); + +/* Center map window on player (even if it is non-visible). */ +extern void map_center(); + + + +#endif diff --git a/src/client/misc.c b/src/client/misc.c new file mode 100644 index 0000000..0b052de --- /dev/null +++ b/src/client/misc.c @@ -0,0 +1,102 @@ +/* src/client/misc.c */ + +#include "misc.h" +#include /* uint8_t, uint16_t */ +#include "cleanup.h" /* for set_cleanup_flag() */ +#include "keybindings.h" /* init_keybindings(), free_keybindings(), + * save_keybindings() + */ +#include "wincontrol.h" /* init_winconfs(), init_wins(), + * sorted_wintoggle_and_activate() + */ +#include "windows.h" /* suspend_win() */ +#include "world.h" /* global world */ + + + +extern void save_interface_conf() +{ + save_keybindings("confclient/keybindings_global", &world.kb_global); + save_keybindings("confclient/keybindings_wingeom", &world.kb_wingeom); + save_keybindings("confclient/keybindings_winkeys", &world.kb_winkeys); + save_win_configs(); +} + + + +extern void load_interface_conf() +{ + init_keybindings("confclient/keybindings_global", &world.kb_global); + init_keybindings("confclient/keybindings_wingeom", &world.kb_wingeom); + init_keybindings("confclient/keybindings_winkeys", &world.kb_winkeys); + init_winconfs(); + init_wins(); + sorted_wintoggle_and_activate(); + set_cleanup_flag(CLEANUP_INTERFACE); +} + + + +extern void unload_interface_conf() +{ + free_keybindings(world.kb_global.kbs); + free_keybindings(world.kb_wingeom.kbs); + free_keybindings(world.kb_winkeys.kbs); + while (0 != world.wmeta.active) + { + suspend_win(world.wmeta.active); + } + free_winconfs(); +} + + + +extern void reload_interface_conf() +{ + unload_interface_conf(); + load_interface_conf(); +} + + + +extern uint16_t center_offset(uint16_t position, uint16_t mapsize, + uint16_t framesize) +{ + uint16_t offset = 0; + if (mapsize > framesize) + { + if (position > framesize / 2) + { + if (position < mapsize - (framesize / 2)) + { + offset = position - (framesize / 2); + } + else + { + offset = mapsize - framesize; + } + } + } + return offset; +} + + + +extern void nav_inventory(char dir) +{ + if ('u' == dir) + { + world.player_inventory_select = world.player_inventory_select + - (world.player_inventory_select > 0); + return; + } + uint8_t n_elems = 0; + uint8_t i; + for (i = 0; '\0' != world.player_inventory[i]; i++) + { + n_elems = n_elems + ('\n' == world.player_inventory[i]); + } + world.player_inventory_select = world.player_inventory_select + + (world.player_inventory_select < n_elems); +} + diff --git a/src/client/misc.h b/src/client/misc.h new file mode 100644 index 0000000..cd283ed --- /dev/null +++ b/src/client/misc.h @@ -0,0 +1,29 @@ +/* src/client/misc.h + * + * Miscellaneous routines that have not yet found a proper parent module. Having + * LOTS of stuff in here is a sure sign that better modularization is in order. + */ + +#ifndef MISC_H +#define MISC_H + +#include /* for uint16_t */ + + + +/* Save / load / unload (free) / reload interface configuration data. */ +extern void save_interface_conf(); +extern void load_interface_conf(); +extern void unload_interface_conf(); +extern void reload_interface_conf(); + +/* Return offset into center map of "mapsize" on "position" in "framesize". */ +extern uint16_t center_offset(uint16_t position, + uint16_t mapsize, uint16_t framesize); + +/* Move world.inventory_sel up ("dir"="u") or down (else) as far as possible. */ +extern void nav_inventory(char dir); + + + +#endif diff --git a/src/client/wincontrol.c b/src/client/wincontrol.c new file mode 100644 index 0000000..b272905 --- /dev/null +++ b/src/client/wincontrol.c @@ -0,0 +1,574 @@ +/* src/client/wincontrol.c */ + +#include "wincontrol.h" +#include /* global errno */ +#include /* DIR, struct dirent, opendir(), closedir(), readdir() */ +#include /* uint8_t, uint16_t, uint32_t */ +#include /* FILE */ +#include /* free(), atoi() */ +#include /* strlen(), strchr(), strstr(), memcpy() */ +#include "../common/readwrite.h" /* try_fopen(), textfile_sizes(), try_fgets(), + * textfile_sizes(), try_fwrite(), try_fgetc(), + * try_fclose_unlink_rename(), try_fputc() + */ +#include "../common/rexit.h" /* exit_err(), exit_trouble() */ +#include "../common/try_malloc.h" /* try_malloc() */ +#include "../common/yx_uint16.h" /* struct yx_uint16 */ +#include "draw_wins.h" /* draw_win_map(), draw_win_info(), draw_win_log(), + * draw_win_available_keybindings(), + * draw_win_inventory(), draw_win_keybindings_global(), + * draw_win_keybindings_winconf_geometry(), + * draw_win_keybindings_winconf_keybindings(), + * draw_winconf_geometry(), draw_winconf_keybindings() + */ +#include "keybindings.h" /* struct KeyBinding, free_keybindings() */ +#include "windows.h" /* struct Win, resize_active_win(), reset_pad_offset(), + * append_win(), suspend_win(), init_win(), free_win() + */ +#include "world.h" /* global world */ + + + +/* Return string "prefix" + "id"; malloc()'s string, remember to call free()! */ +static char * string_prefixed_id(char * prefix, char id); + +/* Initialize Winconf of "id" from appropriate config file.*/ +static void init_winconf_from_file(char id, struct WinConf * winconf); + +/* Wrapper around init_win() called with values from Winconf of "id". */ +static void init_win_from_winconf(char id); + +/* Save title, draw function, size of window identified by "id" to conffile. */ +static void save_win_config(char id); + +/* Free data pointed to inside individual WinConf struct of "id". */ +static void free_winconf_data(char id); + +/* Write geometry of a window to its WinConf, as positive or negative values + * (dependent on state ofWinConf->height_type / WinConf->width_type). + */ +static void set_winconf_geometry(char id); + +/* Get WinConf by "id"; get id of WinConf mothering "win". */ +static struct WinConf * get_winconf_by_id(char id); + +/* Get (Win->draw) function identified by "c"; NULL if c not mapped to one. */ +static void * get_drawfunc_by_char(char c); + +/* Iterate over chars of world.winconf_db.winconf_ids array. Restart after \0.*/ +static char get_next_winconf_id(); + + + +static char * string_prefixed_id(char * prefix, char id) +{ + uint8_t size = strlen(prefix) + 2; + char * path = try_malloc(size, "string_prefixed_id()"); + sprintf(path, "%s_", prefix); + path[size - 2] = id; + return path; +} + + + +static void init_winconf_from_file(char id, struct WinConf * winconf) +{ + /* Assign WinConf id to filename path, error message context, winconf->id.*/ + char * tmp = "init_winconf_from_file() on window id '_'"; + char * context = try_malloc(strlen(tmp) + 1, "init_winconf_from_file()"); + memcpy(context, tmp, strlen(tmp) + 1); + context[strlen(tmp) - 2] = id; + char * path = string_prefixed_id("confclient/windows/Win_", id); + winconf->id = id; + + /* Prepare reading in file line by line into "line" array. */ + FILE * file = try_fopen(path, "r", context); + free(path); + uint32_t linemax = textfile_sizes(file, NULL); + char line[linemax + 1]; + + /* Read/determine winconf->title, ->draw, ->height(_type),->width(_type). */ + try_fgets(line, linemax + 1, file, context); + winconf->title = try_malloc(strlen(line), context); + memcpy(winconf->title, line, strlen(line) - 1); /* Eliminate newline char */ + winconf->title[strlen(line) - 1] = '\0'; /* char at end of string. */ + try_fgets(line, linemax + 1, file, context); + winconf->draw = line[0]; + try_fgets(line, linemax + 1, file, context); + winconf->height = atoi(line); + winconf->height_type = (0 >= winconf->height); + try_fgets(line, linemax + 1, file, context); + winconf->width = atoi(line); + winconf->width_type = (0 >= winconf->width); + + /* Read in window-specific keybindings (winconf->kb). */ + char command[linemax + 1]; + char * cmdptr; + struct KeyBinding ** loc_last_ptr = &winconf->kb.kbs; + * loc_last_ptr = 0; + while (try_fgets(command, linemax + 1, file, context)) + { + if ('\n' == command[0] || 0 == command[0]) + { + break; + } + * loc_last_ptr = try_malloc(sizeof(struct KeyBinding), context); + struct KeyBinding * kb_p = * loc_last_ptr; + kb_p->next = 0; + kb_p->key = atoi(command); + cmdptr = strchr(command, ' ') + 1; + kb_p->name = try_malloc(strlen(cmdptr), context); + memcpy(kb_p->name, cmdptr, strlen(cmdptr) - 1); + kb_p->name[strlen(cmdptr) - 1] = '\0'; + loc_last_ptr = & kb_p->next; + } + + /* Init remaining values to zero and cleaning up. */ + winconf->view = 0; + winconf->kb.edit = 0; + winconf->kb.select = 0; + try_fclose(file, context); + free(context); +} + + + +static void init_win_from_winconf(char id) +{ + char * err = "get_drawfunc_by_char() returns NULL to init_win_from_file()."; + struct WinConf * winconf = get_winconf_by_id(id); + void * f = get_drawfunc_by_char(winconf->draw); + exit_err(NULL == f, err); + init_win(&winconf->win, winconf->title, winconf->height, winconf->width, f); +} + + + +static void save_win_config(char id) +{ + char * f_name = "save_win_config()"; + + /* Prepare atomic file saving. */ + char * path_tmp = string_prefixed_id("confclient/windows/Win_tmp_", id); + FILE * file = try_fopen(path_tmp, "w", f_name); + + /* Save, line by line, ->title, ->draw, ->height and ->width. */ + struct WinConf * wc = get_winconf_by_id(id); + uint8_t size = strlen(wc->title) + 2; + if (size < 7) /* Ensure that at least 5 + 2 char fit into line so that */ + { /* the digit representation of any uint16_t may be stored. */ + size = 7; + } + char line[size]; + sprintf(line, "%s\n", wc->title); + try_fwrite(line, sizeof(char), strlen(line), file, f_name); + sprintf(line, "%c\n", wc->draw); + try_fwrite(line, sizeof(char), strlen(line), file, f_name); + sprintf(line, "%d\n", wc->height); + try_fwrite(line, sizeof(char), strlen(line), file, f_name); + sprintf(line, "%d\n", wc->width); + try_fwrite(line, sizeof(char), strlen(line), file, f_name); + + /* Save window-specific keybindings (->kb.kbs). */ + uint16_t linemax = 0; + struct KeyBinding * kb_p = wc->kb.kbs; + while (0 != kb_p) + { + if (strlen(kb_p->name) > linemax) + { + linemax = strlen(kb_p->name); + } + kb_p = kb_p->next; + } + linemax = linemax + 6; /* + 6: + 3 digits + whitespace + \n + \0 */ + char kb_line[linemax]; + kb_p = wc->kb.kbs; + while (0 != kb_p) + { + sprintf(kb_line, "%d %s\n", kb_p->key, kb_p->name); + try_fwrite(kb_line, sizeof(char), strlen(kb_line), file, f_name); + kb_p = kb_p->next; + } + + /* Finish atomic file saving and clean up. */ + char * path = string_prefixed_id("confclient/windows/Win_", id); + try_fclose_unlink_rename(file, path_tmp, path, f_name); + free(path); + free(path_tmp); +} + + + +static void free_winconf_data(char id) +{ + struct WinConf * wc = get_winconf_by_id(id); + free(wc->title); + free_keybindings(wc->kb.kbs); + free_win(wc->win); +} + + + +static void set_winconf_geometry(char id) +{ + struct WinConf * wcp = get_winconf_by_id(id); + if (0 == wcp->height_type) + { + wcp->height = wcp->win->framesize.y; + } + else if (1 == wcp->height_type) + { + wcp->height = wcp->win->framesize.y - world.wmeta.padsize.y + 1; + } + if (0 == wcp->width_type) + { + wcp->width = wcp->win->framesize.x; + } + else if (1 == wcp->width_type) + { + wcp->width = wcp->win->framesize.x - world.wmeta.padsize.x; + } +} + + + +static struct WinConf * get_winconf_by_id(char id) +{ + uint8_t i = 0; + while (1) + { + if (id == world.winconf_db.winconfs[i].id) + { + return &world.winconf_db.winconfs[i]; + } + i++; + } +} + + + +static void * get_drawfunc_by_char(char c) +{ + + if ('c' == c) + { + return draw_win_inventory; + } + else if ('i' == c) + { + return draw_win_info; + } + else if ('l' == c) + { + return draw_win_log; + } + else if ('k' == c) + { + return draw_win_available_keybindings; + } + else if ('m' == c) + { + return draw_win_map; + } + else if ('0' == c) + { + return draw_win_keybindings_global; + } + else if ('1' == c) + { + return draw_win_keybindings_winconf_geometry; + } + else if ('2' == c) + { + return draw_win_keybindings_winconf_keybindings; + } + return NULL; +} + + + +static char get_next_winconf_id() +{ + static uint8_t i = 0; + char c = world.winconf_db.winconf_ids[i]; + if (0 == c) + { + i = 0; + return c; + } + i++; + return c; +} + + + +extern struct WinConf * get_winconf_by_win(struct Win * win) +{ + uint8_t i = 0; + while (1) + { + if (win == world.winconf_db.winconfs[i].win) + { + return &world.winconf_db.winconfs[i]; + } + i++; + } +} + + + +extern struct Win * get_win_by_id(char id) +{ + struct WinConf * wc = get_winconf_by_id(id); + return wc->win; +} + + + +extern void init_winconfs() +{ + char * f_name = "init_winconfs()"; + + /* Fill world.winconf_db.winconf_ids with confclient/windows/Win_* + * filenames' end chars. + */ + uint8_t max_wins = 255; /* Maximum number of window ids to store. */ + DIR * dp = opendir("confclient/windows"); + exit_trouble(NULL == dp, f_name, "opendir()"); + struct dirent * fn; + errno = 0; + char * winconf_ids = try_malloc(max_wins + 1, f_name); + uint8_t i = 0; + char id; + while (NULL != (fn = readdir(dp)) && i < max_wins) + { + if (5 == strlen(fn->d_name) && fn->d_name == strstr(fn->d_name, "Win_")) + { + id = fn->d_name[4]; + winconf_ids[i] = id; + i++; + } + } + winconf_ids[i] = '\0'; + exit_trouble(errno, f_name, "readdir()"); + exit_trouble(closedir(dp), f_name, "closedir()"); + world.winconf_db.winconf_ids = try_malloc(strlen(winconf_ids) + 1, f_name); + memcpy(world.winconf_db.winconf_ids, winconf_ids, strlen(winconf_ids) + 1); + free(winconf_ids); + + /* Initialize world.winconf_db.winconfs from Win_* files named in + * world.winconf_db.winconf_ids. + */ + size_t size = strlen(world.winconf_db.winconf_ids) * sizeof(struct WinConf); + world.winconf_db.winconfs = try_malloc(size, f_name); + i = 0; + while (0 != (id = get_next_winconf_id())) + { + init_winconf_from_file(id, &world.winconf_db.winconfs[i]); + i++; + } +} + + + +extern void free_winconfs() +{ + char id; + while (0 != (id = get_next_winconf_id())) + { + free_winconf_data(id); + } + free(world.winconf_db.winconf_ids); + free(world.winconf_db.winconfs); +} + + + +extern void init_wins() +{ + char id; + while (0 != (id = get_next_winconf_id())) + { + init_win_from_winconf(id); + } +} + + + +extern void sorted_wintoggle_and_activate() +{ + char * f_name = "sorted_wintoggle_and_activate()"; + + /* Read from file order of windows to be toggled + active win selection. */ + char * path = "confclient/windows/toggle_order_and_active"; + FILE * file = try_fopen(path, "r", f_name); + uint32_t linemax = textfile_sizes(file, NULL); + char win_order[linemax + 1]; + try_fgets(win_order, linemax + 1, file, f_name); + int char_or_eof = try_fgetc(file, f_name); + exit_trouble(EOF==char_or_eof, f_name, "fgetc() unexpectedly hitting EOF"); + uint8_t a = (uint8_t) char_or_eof; + try_fclose(file, f_name); + + /* Toggle windows and set active window selection. */ + uint8_t i = 0; + for (; i < strlen(win_order) - 1; i++) + { + if (NULL == strchr(world.winconf_db.winconf_ids, win_order[i])) + { + continue; + } + toggle_window(win_order[i]); + if (a == (uint8_t) win_order[i]) + { + world.wmeta.active = get_win_by_id(win_order[i]); + } + } +} + + + +extern void save_win_configs() +{ + char * f_name = "save_win_configs()"; + + /* Save individual world.winconf_db.winconfs to their proper files. */ + uint8_t max_wins = 255; /* n of WinConf fitting into world.winconf_db */ + char id; + while (0 != (id = get_next_winconf_id())) + { + save_win_config(id); + } + + /* Save order of windows to toggle on start / which to select as active. */ + char * path = "confclient/windows/toggle_order_and_active"; + char * path_tmp = "confclient/windows/toggle_order_and_active_tmp"; + FILE * file = try_fopen(path_tmp, "w", f_name); + char line[max_wins + 2]; + struct Win * w_p = world.wmeta.chain_start; + uint8_t i = 0; + while (0 != w_p && i < max_wins) + { + struct WinConf * wc = get_winconf_by_win(w_p); + line[i] = wc->id; + w_p = w_p->next; + i++; + } + line[i] = '\n'; + line[i + 1] = '\0'; + try_fwrite(line, sizeof(char), strlen(line), file, f_name); + if (0 != world.wmeta.active) + { + struct WinConf * wc = get_winconf_by_win(world.wmeta.active); + try_fputc(wc->id, file, f_name); + } + try_fclose_unlink_rename(file, path_tmp, path, f_name); +} + + + +extern void toggle_window(char id) +{ + struct Win * win = get_win_by_id(id); + if (0 == win->prev && world.wmeta.chain_start != win) /* Win struct is */ + { /* outside chain? */ + append_win(win); + } + else + { + suspend_win(win); + } +} + + + +extern void toggle_winconfig() +{ + struct Win * win = world.wmeta.active; + struct WinConf * wcp = get_winconf_by_win(win); + if (0 == wcp->view) + { + wcp->view = 1; + win->draw = draw_winconf_geometry; + wcp->center = win->center; + win->center.y = 0; + win->center.x = 0; + } + else if (1 == wcp->view) + { + wcp->view = 2; + win->draw = draw_winconf_keybindings; + win->center.x = 0; + } + else + { + wcp->view = 0; + win->draw = get_drawfunc_by_char(wcp->draw); + win->center = wcp->center; + } +} + + + +extern void toggle_win_size_type(char axis) +{ + struct Win * win = world.wmeta.active; + struct WinConf * wcp = get_winconf_by_win(win); + if ('y' == axis) + { + wcp->height_type = (0 == wcp->height_type); + set_winconf_geometry(wcp->id); + return; + } + wcp->width_type = ( 0 == wcp->width_type + && win->framesize.x <= world.wmeta.padsize.x); + set_winconf_geometry(wcp->id); +} + + + +extern void scroll_pad(char dir) +{ + if ('+' == dir) + { + reset_pad_offset(world.wmeta.pad_offset + 1); + } + else if ('-' == dir) + { + reset_pad_offset(world.wmeta.pad_offset - 1); + } +} + + + +extern void growshrink_active_window(char change) +{ + if (0 != world.wmeta.active) + { + struct yx_uint16 size = world.wmeta.active->framesize; + if (change == '-') + { + size.y--; + } + else if (change == '+') + { + size.y++; + } + else if (change == '_') + { + size.x--; + } + else if (change == '*') + { + size.x++; + } + resize_active_win(size); + struct WinConf * wcp = get_winconf_by_win(world.wmeta.active); + if ( 1 == wcp->width_type + && world.wmeta.active->framesize.x > world.wmeta.padsize.x) + { + wcp->width_type = 0; + } + set_winconf_geometry(wcp->id); + } +} diff --git a/src/client/wincontrol.h b/src/client/wincontrol.h new file mode 100644 index 0000000..778e34f --- /dev/null +++ b/src/client/wincontrol.h @@ -0,0 +1,96 @@ +/* src/client/wincontrol.h + * + * Routines that build on top of the windows library to provide a simple window + * management API to the game. Also helps managing window-specific keybindings. + */ + +#ifndef WINCONTROL_H +#define WINCONTROL_H + +#include /* for uint8_t, int16_t */ +#include "keybindings.h" /* for KeyBindingDB struct */ +#include "../common/yx_uint16.h" /* for yx_uint16 struct */ +struct Win; + + + +struct WinConfDB +{ + struct WinConf * winconfs; + char * winconf_ids; +}; + +/* Stores a window's configuration (like geometry, keybindings) and a pointer to + * the respective Win struct itself. + */ +struct WinConf +{ + char id; /* Unique identifier of WinConf, doubles aas identifier for .win */ + /* and the char following "Win_" in the respective conffile name.*/ + struct Win * win; /* Window / Win struct configured by this WinConf. */ + struct KeyBindingDB kb; /* Window-specific keybindings. */ + uint8_t view; /* 0: use .draw as Win.draw; 1/2: use draw_winconf()_(1/2). */ + int16_t height; /* Designated height to pass to init_win(). */ + int16_t width; /* Designated width to pass to init_win(). */ + uint8_t height_type; /* 0: read .height/.width as size in positive cells; */ + uint8_t width_type; /* 1: as negative diff in cells to the screen size. */ + char * title; /* Designated title to pass to init_win(). */ + char draw; /* Identifier of designated Win.draw; passed to init_win() */ + /* and reset after toggling Win.draw via toggle_winconf(). */ + struct yx_uint16 center; /* Designated Win.center; to be reset after */ +}; /* toggling Win.center via toggle_winconf(). */ + + + +/* Get WinConf fathering "win" / get Win of WinConf of "id". */ +extern struct WinConf * get_winconf_by_win(struct Win * win); +extern struct Win * get_win_by_id(char id); + +/* Create, initialize (from config files)/free world.winconfs and their Wins. */ +extern void init_winconfs(); +extern void free_winconfs(); +extern void init_wins(); + +/* Toggle windows in the order desribed by 1st line of toggle_order_and_active + * file in client config windows directory, where each char may fit a Winconf.id + * in world.winconfs. Silently ignore id chars not found there. The 1st char of + * the 2nd line of the same file determines which window (by its .id) to focus + * as active (but only if this window belongs to the ones just toggled). + */ +extern void sorted_wintoggle_and_activate(); + +/* Save world.winconfs, visible window chain and active window selection to the + * respective configuration files in client config windows directory. + */ +extern void save_win_configs(); + +/* Toggle "window configuration" view for active window. Sets sensible + * Win.center values for the various configuration views (for winconf_geometry: + * y=0, x=0; for winconf_keys: x=0 (y is set by draw_winconf_keybindings()). + */ +extern void toggle_winconfig(); + +/* Toggle WinConf.(height/width)_type ("axis" = "y": height; else: width). Avoid + * positive diff to screen width (value would be wrongly read as a non-diff), + * width_type toggles to 1 only if world.wmeta->screen's width >= WinConf.width. + */ +extern void toggle_win_size_type(char axis); + +/* Toggle display of a window identified by "id". */ + extern void toggle_window(char id); + +/* Try scrolling virtual screen left ("dir" = "-") or right ("dir" = "+") to the + * degree allowed by the window manager's reset_pad_offset(). + */ +extern void scroll_pad(char dir); + +/* Try to grow or shrink the active window horizontally ("change" = "*"/"_") or + * vertically ("change = "+"/"-") by one cell size to the degree allowed by the + * window manager's resize_active_win(). If a new window width would surpass + * that of the terminal screen, set WinConf.width_type to 0. + */ +extern void growshrink_active_window(char change); + + + +#endif diff --git a/src/client/windows.c b/src/client/windows.c new file mode 100644 index 0000000..dcf450b --- /dev/null +++ b/src/client/windows.c @@ -0,0 +1,664 @@ +/* src/client/windows.c */ + +#include "windows.h" +#include /* uint8_t, uint16_t, uint32_t, UINT16_MAX */ +#include /* sprintf() */ +#include /* free() */ +#include /* strlen(), memcpy(), strnlen() */ +#include /* pnoutrefresh(), doupdate(), werase(), wnoutrefresh(), + * erase(), getmaxx(), getmaxy(), delwin(), endwin(), + * initscr(), noecho(), curs_set(), newpad(), mvwaddch(), + * mvwaddstr(), wresize(), chtype + */ +#include "../common/rexit.h" /* for exit_err() */ +#include "../common/try_malloc.h" /* for try_malloc() */ +#include "../common/yx_uint16.h" /* for struct yx_uint16 */ +#include "cleanup.h" /* for set_cleanup_flag() */ +#include "misc.h" /* for center_offset() */ +#include "world.h" /* for world global */ + + + + +/* Make virtual screen just wide enough to contain all visible windows. */ +static void refit_pad(); + +/* Update geometry (sizes, positions) of window "w" and its successors in the + * window chain. Use place_win() for the positioning algorithm. + */ +static void update_wins(struct Win * w); +static void place_win(struct Win * w); + +/* Draw scroll hint (a line saying that there are "dist" more elements of "unit" + * further into the direction symbolized by "dir") into virtual screen, onto an + * appropriate edge of a window or the screen; the left/right edge if "dir" is + * "<"/">", or the top/bottom edge if it is "^"/"v". "start" be either the start + * coordinate of a window's frame, or .y=0, .x=wm->pad_offset for the virtual + * screen. winscroll_hint() and padscroll_hint() are wrappers to both cases. + */ +static void scroll_hint(struct yx_uint16 fsize, char dir, uint16_t dist, + char * unit, struct yx_uint16 start); +static void winscroll_hint(struct Win * w, char dir, uint16_t dist); +static void padscroll_hint(char dir, uint16_t dist); + +/* Draw contents of all windows in window chain from window "w" onwards. */ +static void draw_wins(struct Win * w); + +/* draw_win_borderlines() draws vertical/horizontal borders of window "w" sans + * corners into the virtual screen. It draws the top border line as the windows' + * title bar (highlighted if the window is selected as active). It is called + * recursively by draw_wins_borderlines() on all windows from "w" on. + * draw_wins_bordercorners() draws the border corners of "w" and its successors. + */ +static void draw_win_borderlines(struct Win * w); +static void draw_wins_borderlines(struct Win * w); +static void draw_wins_bordercorners(struct Win * w); + +/* Shift active window forwards / backwards in window chain. */ +static void shift_win_forward(); +static void shift_win_backward(); + + + +static void refit_pad() +{ + /* Determine rightmost window column. */ + uint32_t lastwcol = 0; + struct Win * wp = world.wmeta.chain_start; + while (wp != 0) + { + if ((uint32_t) wp->start.x + (uint32_t) wp->framesize.x > lastwcol + 1) + { + lastwcol = (uint32_t) wp->start.x + (uint32_t) wp->framesize.x - 1; + } + wp = wp->next; + } + + /* Only resize the pad if the rightmost window column has changed. */ + char * err_s = "refit_pad() extends virtual screen beyond legal sizes."; + char * err_m = "refit_pad() triggers memory alloc error via wresize()."; + if (getmaxx(world.wmeta.pad) + 1 != lastwcol) + { + uint8_t t = (lastwcol + 2 > UINT16_MAX); + exit_err(t, err_s); + t = wresize(world.wmeta.pad, getmaxy(world.wmeta.pad), lastwcol + 2); + exit_err(t, err_m); + } +} + + + +static void update_wins(struct Win * w) +{ + place_win(w); + refit_pad(); + if (0 != w->next) + { + update_wins(w->next); + } +} + + + +static void place_win(struct Win * w) +{ + /* If w is first window, it goes into the top left corner. */ + w->start.x = 0; + w->start.y = 1; /* Leave space for title bar. */ + if (0 != w->prev) + { + + /* If not, fit w's top left to top right of last top predecessor. */ + struct Win * w_top = w->prev; + while (w_top->start.y != 1) + { + w_top = w_top->prev; + } + w->start.x = w_top->start.x + w_top->framesize.x + 1; + + /* Fit w's top left to bottom left of its ->prev if enough space. */ + uint16_t w_prev_maxy = w->prev->start.y + w->prev->framesize.y; + if ( w->framesize.x <= w->prev->framesize.x + && w->framesize.y < world.wmeta.padsize.y - w_prev_maxy) + { + w->start.x = w->prev->start.x; + w->start.y = w_prev_maxy + 1; + } + + /* Failing that, try to fit w' top left to the top right of the last + * predecessor w_test 1) not followed by windows with a left corner + * further rightwards than its own 2) with enough space rightwards for w + * until the bottom right of w_thr directly throning over it 3) and with + * this same space extending far enough to the bottom for fitting in w. + */ + else + { + struct Win * w_test = w->prev; + struct Win * w_thr; + while (w_test != w_top) + { + w_thr = w_test->prev; + for (; w_test->start.y <= w_thr->start.y; w_thr = w_thr->prev); + uint16_t w_thr_bottom = w_thr->start.y + w_thr->framesize.y; + uint16_t free_width = (w_thr->start.x + w_thr->framesize.x) + - (w_test->start.x + w_test->framesize.x); + if ( w->framesize.y < world.wmeta.padsize.y - w_thr_bottom + && w->framesize.x < free_width) + { + w->start.x = w_test->start.x + w_test->framesize.x + 1; + w->start.y = w_thr_bottom + 1; + break; + } + w_test = w_thr; + } + } + } +} + + + +static void scroll_hint(struct yx_uint16 fsize, char dir, uint16_t dist, + char * unit, struct yx_uint16 start) +{ + /* Decide on alignment (vertical/horizontal?), thereby hint text space. */ + char * more = "more"; + uint16_t dsc_space = fsize.x; + if ('<' == dir || '>' == dir) + { + dsc_space = fsize.y; + } /* vv-- 10 = max strlen for uint16_t */ + char scrolldsc[1 + strlen(more) + 1 + 10 + 1 + strlen(unit) + 1 + 1]; + sprintf(scrolldsc, " %d %s %s ", dist, more, unit); + + /* Decide on offset of the description text inside the scroll hint line. */ + uint16_t dsc_offset = 1; + if (dsc_space > strlen(scrolldsc) + 1) + { + dsc_offset = (dsc_space - strlen(scrolldsc)) / 2; + } + + /* Draw scroll hint line as dir symbols bracketing description text. */ + uint16_t draw_offset = 0; + if ('>' == dir) + { + draw_offset = fsize.x - 1; + } + else if ('v' == dir) + { + draw_offset = fsize.y - 1; + } + uint16_t q = 0; + for (; q < dsc_space; q++) + { + chtype c = dir | A_REVERSE; + if (q >= dsc_offset && q < strlen(scrolldsc) + dsc_offset) + { + c = scrolldsc[q - dsc_offset] | A_REVERSE; + } + if ('<' == dir || '>' == dir) + { + mvwaddch(world.wmeta.pad, start.y + q, start.x + draw_offset, c); + continue; + } + mvwaddch(world.wmeta.pad, start.y + draw_offset, start.x + q, c); + } +} + + +static void padscroll_hint(char dir, uint16_t dist) +{ + struct yx_uint16 start; + start.y = 0; + start.x = world.wmeta.pad_offset; + scroll_hint(world.wmeta.padsize, dir, dist, "columns", start); +} + + + +static void winscroll_hint(struct Win * w, char dir, uint16_t dist) +{ + char * unit = "lines"; + if ('<' == dir || '>' == dir) + { + unit = "columns"; + } + struct yx_uint16 start = w->start; + scroll_hint(w->framesize, dir, dist, unit, start); +} + + + +static void draw_wins(struct Win * w) +{ + w->draw(w); + uint16_t size_y = w->winmapsize.y; + uint16_t size_x = w->winmapsize.x; + uint16_t offset_y = center_offset(w->center.y, size_y, w->framesize.y); + uint16_t offset_x = center_offset(w->center.x, size_x, w->framesize.x); + uint16_t y, x; + for (y = offset_y; y < w->framesize.y + offset_y && y < size_y; y++) + { + for (x = offset_x; x < w->framesize.x + offset_x && x < size_x; x++) + { + chtype ch = w->winmap[(y * w->winmapsize.x) + x]; + mvwaddch(world.wmeta.pad, w->start.y + (y - offset_y), + w->start.x + (x - offset_x), ch); + } + } + free(w->winmap); + w->winmap = NULL; + w->winmapsize.y = 0; + w->winmapsize.x = 0; + if (offset_y > 0) + { + winscroll_hint(w, '^', offset_y + 1); + } + if (size_y > offset_y + w->framesize.y) + { + winscroll_hint(w, 'v', size_y - ((offset_y + w->framesize.y) - 1)); + } + if (offset_x > 0) + { + winscroll_hint(w, '<', offset_x + 1); + } + if (size_x > offset_x + w->framesize.x) + { + winscroll_hint(w, '>', size_x - ((offset_x + w->framesize.x) - 1)); + } + if (0 != w->next) + { + return draw_wins(w->next); + } +} + + + +static void draw_win_borderlines(struct Win * w) +{ + /* Draw vertical and horizontal border lines. */ + uint16_t y, x; + for (y = w->start.y; y <= w->start.y + w->framesize.y; y++) + { + mvwaddch(world.wmeta.pad, y, w->start.x - 1, '|'); + mvwaddch(world.wmeta.pad, y, w->start.x + w->framesize.x, '|'); + } + for (x = w->start.x; x <= w->start.x + w->framesize.x; x++) + { + mvwaddch(world.wmeta.pad, w->start.y - 1, x, '-'); + mvwaddch(world.wmeta.pad, w->start.y + w->framesize.y, x, '-'); + } + + /* Draw as much as possible of the title into center of top border line. */ + char min_title_length_visible = 3; /* min. 1 char + 2 padding/decoration */ + if (w->framesize.x >= min_title_length_visible) + { + uint16_t title_offset = 0; + if (w->framesize.x > strlen(w->title) + 2) + { + title_offset = (w->framesize.x - (strlen(w->title) + 2)) / 2; + } /* +2 is for padding/decoration */ + uint16_t length_visible = strnlen(w->title, w->framesize.x - 2); + char title[length_visible + 3]; + char decoration = ' '; + if (w == world.wmeta.active) + { + decoration = '$'; + } + memcpy(title + 1, w->title, length_visible); + title[0] = title[length_visible + 1] = decoration; + title[length_visible + 2] = '\0'; + mvwaddstr(world.wmeta.pad, + w->start.y - 1, w->start.x + title_offset, title); + } +} + + + +static void draw_wins_borderlines(struct Win * w) +{ + draw_win_borderlines(w); + if (0 != w->next) + { + draw_wins_borderlines(w->next); + } +} + + + +static void draw_wins_bordercorners(struct Win * w) +{ + mvwaddch(world.wmeta.pad, w->start.y - 1, w->start.x - 1, '+'); + mvwaddch(world.wmeta.pad, w->start.y - 1, w->start.x + w->framesize.x,'+'); + mvwaddch(world.wmeta.pad, w->start.y + w->framesize.y, w->start.x - 1,'+'); + mvwaddch(world.wmeta.pad, w->start.y + w->framesize.y, + w->start.x + w->framesize.x, '+'); + if (0 != w->next) + { + draw_wins_bordercorners(w->next); + } +} + + + +static void shift_win_forward() +{ + if (world.wmeta.active == world.wmeta.chain_end) + { + world.wmeta.chain_end = world.wmeta.active->prev; + world.wmeta.chain_end->next = 0; + world.wmeta.active->next = world.wmeta.chain_start; + world.wmeta.active->next->prev = world.wmeta.active; + world.wmeta.chain_start = world.wmeta.active; + world.wmeta.chain_start->prev = 0; + } + else + { + struct Win * old_prev = world.wmeta.active->prev; + struct Win * old_next = world.wmeta.active->next; + if (world.wmeta.chain_end == world.wmeta.active->next) + { + world.wmeta.chain_end = world.wmeta.active; + world.wmeta.active->next = 0; + } + else + { + world.wmeta.active->next = old_next->next; + world.wmeta.active->next->prev = world.wmeta.active; + } + if (world.wmeta.chain_start == world.wmeta.active) + { + world.wmeta.chain_start = old_next; + } + else + { + old_prev->next = old_next; + } + old_next->prev = old_prev; + old_next->next = world.wmeta.active; + world.wmeta.active->prev = old_next; + } +} + + + +static void shift_win_backward() +{ + if (world.wmeta.active == world.wmeta.chain_start) + { + world.wmeta.chain_start = world.wmeta.active->next; + world.wmeta.chain_start->prev = 0; + world.wmeta.active->prev = world.wmeta.chain_end; + world.wmeta.active->prev->next = world.wmeta.active; + world.wmeta.chain_end = world.wmeta.active; + world.wmeta.chain_end->next = 0; + } + else + { + struct Win * old_prev = world.wmeta.active->prev; + struct Win * old_next = world.wmeta.active->next; + if (world.wmeta.chain_start == world.wmeta.active->prev) + { + world.wmeta.chain_start = world.wmeta.active; + world.wmeta.active->prev = 0; + } + else + { + world.wmeta.active->prev = old_prev->prev; + world.wmeta.active->prev->next = world.wmeta.active; + } + if (world.wmeta.chain_end == world.wmeta.active) + { + world.wmeta.chain_end = old_prev; + } + else + { + old_next->prev = old_prev; + } + old_prev->next = old_next; + old_prev->prev = world.wmeta.active; + world.wmeta.active->next = old_prev; + } +} + + + +extern void init_win_meta() +{ + char * err_s = "init_win_meta() creates virtual screen beyond legal size."; + char * err_m = "init_win_meta() triggers memory alloc error via newpad()."; + world.wmeta.screen = initscr(); + set_cleanup_flag(CLEANUP_NCURSES); + noecho(); + curs_set(0); + uint32_t maxy_test = getmaxy(world.wmeta.screen); + uint32_t maxx_test = getmaxx(world.wmeta.screen); + exit_err(maxy_test > UINT16_MAX || maxx_test > UINT16_MAX, err_s); + world.wmeta.padsize.y = maxy_test; + world.wmeta.padsize.x = maxx_test; + world.wmeta.chain_start = 0; + world.wmeta.chain_end = 0; + world.wmeta.pad_offset = 0; + world.wmeta.pad = newpad(world.wmeta.padsize.y, 1); + exit_err(NULL == world.wmeta.pad, err_m); + world.wmeta.active = 0; +} + + + +extern void init_win(struct Win ** wp, char * title, int16_t height, + int16_t width, void * func) +{ + char * f_name = "init_win()"; + struct Win * w = try_malloc(sizeof(struct Win), f_name); + w->prev = 0; + w->next = 0; + w->winmapsize.y = 0; + w->winmapsize.x = 0; + w->winmap = NULL; + w->title = try_malloc(strlen(title) + 1, f_name); + sprintf(w->title, "%s", title); + w->draw = func; + w->center.y = 0; + w->center.x = 0; + if (0 < width) + { + w->framesize.x = width; + } + else if (0 >= width) + { + w->framesize.x = world.wmeta.padsize.x + width; + } + if (0 < height && height <= world.wmeta.padsize.y - 1) + { + w->framesize.y = height; + } + else if (0 >= height && world.wmeta.padsize.y + (height - 1) > 0) + { + w->framesize.y = world.wmeta.padsize.y + (height - 1); + } + *wp = w; +} + + + +extern void free_winmeta_and_endwin() +{ + delwin(world.wmeta.pad); + endwin(); +} + + + +extern void free_win(struct Win * win) +{ + free(win->title); + free(win); +} + + + +extern void append_win(struct Win * w) +{ + if (0 != world.wmeta.chain_start) + { + w->prev = world.wmeta.chain_end; + world.wmeta.chain_end->next = w; + } + else + { + world.wmeta.active = w; + world.wmeta.chain_start = w; + } + world.wmeta.chain_end = w; + update_wins(w); +} + + + +extern void suspend_win(struct Win * w) +{ + if (world.wmeta.chain_start != w) + { + w->prev->next = w->next; + } + else + { + world.wmeta.chain_start = w->next; + } + uint8_t pad_refitted = 0; + if (world.wmeta.chain_end != w) + { + w->next->prev = w->prev; + if (world.wmeta.active == w) + { + world.wmeta.active = w->next; + } + update_wins(w->next); /* Positioning of successor windows may be */ + pad_refitted = 1; /* affected / need correction. Note that */ + } /* update_wins() already refits the pad, */ + else /* voiding later need for that. */ + { + world.wmeta.chain_end = w->prev; + if (world.wmeta.active == w) + { + world.wmeta.active = w->prev; + } + } + w->prev = 0; + w->next = 0; + if (0 == pad_refitted) + { + refit_pad(); + } +} + + + +extern void reset_pad_offset(uint16_t new_offset) +{ + if (new_offset >= 0 + && (new_offset < world.wmeta.pad_offset + || new_offset + world.wmeta.padsize.x < getmaxx(world.wmeta.pad))) + { + world.wmeta.pad_offset = new_offset; + } +} + + + +extern void resize_active_win(struct yx_uint16 size) +{ + if (0 != world.wmeta.active + && size.x > 0 && size.y > 0 && size.y < world.wmeta.padsize.y) + { + world.wmeta.active->framesize = size; + update_wins(world.wmeta.active); /* Positioning of following */ + } /* windows may be affected. */ +} + + + +extern void cycle_active_win(char dir) +{ + if (0 != world.wmeta.active) + { + if ('f' == dir) + { + if (world.wmeta.active->next != 0) + { + world.wmeta.active = world.wmeta.active->next; + } + else + { + world.wmeta.active = world.wmeta.chain_start; + } + } + else + { + if (world.wmeta.active->prev != 0) + { + world.wmeta.active = world.wmeta.active->prev; + } + else + { + world.wmeta.active = world.wmeta.chain_end; + } + } + } +} + + + +extern void shift_active_win(char dir) +{ + if ( 0 == world.wmeta.active /* No shifting with < 2 windows visible. */ + || world.wmeta.chain_start == world.wmeta.chain_end) + { + return; + } + if ('f' == dir) + { + shift_win_forward(); + update_wins(world.wmeta.chain_start); + return; + } + shift_win_backward(); + update_wins(world.wmeta.chain_start); +} + + + +extern void draw_all_wins() +{ + /* Empty everything before filling it a-new. */ + erase(); + wnoutrefresh(world.wmeta.screen); + werase(world.wmeta.pad); + if (world.wmeta.chain_start) + { + + /* Draw windows' borders first, then windows. */ + draw_wins_borderlines(world.wmeta.chain_start); + draw_wins_bordercorners(world.wmeta.chain_start); + draw_wins(world.wmeta.chain_start); + + /* Draw virtual screen scroll hints. */ + if (world.wmeta.pad_offset > 0) + { + padscroll_hint('<', world.wmeta.pad_offset + 1); + } + uint16_t size_x = getmaxx(world.wmeta.pad); + uint16_t right_edge = world.wmeta.pad_offset + world.wmeta.padsize.x; + if (right_edge < size_x - 1) + { + padscroll_hint('>', size_x - right_edge); + } + + /* Write pad segment to be shown on physical screen to screen buffer. */ + pnoutrefresh(world.wmeta.pad, 0, world.wmeta.pad_offset, 0, 0, + world.wmeta.padsize.y, world.wmeta.padsize.x - 1); + } + + /* Only at the end write accumulated changes to the physical screen. */ + doupdate(); +} diff --git a/src/client/windows.h b/src/client/windows.h new file mode 100644 index 0000000..7c0ef77 --- /dev/null +++ b/src/client/windows.h @@ -0,0 +1,134 @@ +/* src/client/windows.h + * + * A tiled window manager for the terminal. + * + * It provides a virtual screen that can be scrolled horizontally and may carry + * any number of windows to be appeared, disappeared, resized and moved around. + * They have borders and a title bar and are positioned automatically. + * + * Windows can be any width between 1 and 2^16 cells. The virtual screen grows + * with them as needed -- but only horizontally and only up to 2^16 cells. Their + * height is limited by the height of the terminal screen (maximum 2^16 cells). + * + * Windows' positioning can be influenced only indirectly: by resizing them, and + * by shifting their relative position inside the (currently invisible) chain + * that the window manager treats their plurality as. The first window goes into + * the top left corner of the virtual screen. Further windows are fitted as + * left-aligned as possible below their (chain-wise) closest predecessor that + * thrones over enough space to contain them and that is open to the right. If + * that fails, they're fitted up-right to the window with the rightmost border. + * + * TODO: Think up a more intuitive window positioning algorithm. + */ + +#ifndef WINDOWS_H +#define WINDOWS_H + +#include /* WINDOW, chtype */ +#include /* uint16_t, int16_t */ +#include "../common/yx_uint16.h" /* yx_uint16 struct */ + + + +/* "Win" structs describe windows as frames located inside the virtual screen + * pad through which "winmaps" are visible, 2-dimensional maps of ncurses + * chtypes. If a winmap is bigger than its frame, scrolling hints will appear at + * the proper edges. Win structs are chained into a linked list of all the + * windows visible on the virtual screen and also contain pointers to what + * content is to be drawn inside the window, and by use of what method. + */ +struct Win +{ + struct Win * prev; /* chain pointers; if 0, they mark the start or end */ + struct Win * next; /* of the chain; if both are 0, Win is outside chain */ + struct yx_uint16 framesize; /* window frame size to see winmap through */ + struct yx_uint16 start; /* upper left corner of window in pad */ + struct yx_uint16 center; /* winmap cell to center frame on if smaller*/ + char * title; /* title to be used in window title bar */ + void (* draw) (struct Win *); /* function that draws/updates the winmap */ + chtype * winmap; /* sequence of cells, sorted into lines ... */ + struct yx_uint16 winmapsize; /* ... with these geometry infos */ +}; + +/* The window manager's parent struct WinMeta contains the virtual screen, + * relates it to the terminal screen and anchors the chain of visible windows. + */ +struct WinMeta +{ + struct yx_uint16 padsize; /* virtual screen size */ + WINDOW * screen; /* ncurses' pointer to the terminal screen */ + WINDOW * pad; /* ncurses pad of virtual screen */ + struct Win * chain_start; /* first Win in chain; its _prev == 0 */ + struct Win * chain_end; /* last Win in chain; its _next == 0 */ + struct Win * active; /* Win highlighted/selected for manipulation */ + uint16_t pad_offset; /* number of cells view is moved to the right */ +}; + + + +/* Initialize ncurses and world.wmeta on terminal screen. All struct members + * initialize to 0, except for .screen, the newly created virtual screen .pad + * and its .padsize (height: that of the terminal screen; width: 1 cell). + */ +extern void init_win_meta(); + +/* Initialize a Win child "wp" of "wmeta" to "title", "height" and "width" and + * appoint "func"() as its .draw. Initialize other members to 0. + * + * Pass 0 for "width" to make the window as wide as the terminal screen. Pass 0 + * for "height" for the maximum allowed height: one cell smaller than that of + * the terminal screen. Pass negative values for either of them to make the + * window width/height so many cells smaller than what 0 would set. The maximum + * allowed height is also applied for positive height values that exceed it or + * negative values that would reduce the window height to less than 1 cell. + */ +extern void init_win(struct Win ** wp, char * title, int16_t height, + int16_t width, void * func); + +/* Free memory initialized below world.wmeta and run endwin(). */ +extern void free_winmeta_and_endwin(); + +/* Free memory initianized Win structs. */ +extern void free_win(struct Win * win); + +/* Append/suspend window "w" to/from chain of visible windows below "wmeta". + * Appended windows will become active. Suspended active windows will move the + * active window selection to their successor in the window chain or, failing + * that, their predecessor, or, failing that, to 0 (no window active). + */ +extern void append_win(struct Win * w); +extern void suspend_win(struct Win * w); + +/* Apply scrolling offset "new_offset" to virtual screen if it is equal/greater + * 0 and does not push the view (further) beyond the virtual screen's border. If + * the view is already beyond the virtual screen's border due to it having + * shrunk after suspension of windows, only allow screen scrolling leftwards. + */ +extern void reset_pad_offset(uint16_t new_offset); + +/* Apply "size" to the active window if it provides a minimum size of 1x1 cells + * and is in height at least one cell smaller than the screen's vertical height + * (to provide space for the title bar). Does nothing if no window is active. + */ +extern void resize_active_win(struct yx_uint16 size); + +/* Cycle active window selection forwards ("dir" == "f") or backwards (any + * other "dir"). Wrap around in the windows chain if start / end of it is met. + * Does nothing if no window is active. + */ +extern void cycle_active_win(char dir); + +/* Move active window forwards ("dir" == "f") or backwards (any other "dir") in + * the window chain. Wrap around in the window chain if start / end of it is + * met. Does nothing if no window is active. + */ +extern void shift_active_win(char dir); + +/* Draw virtual screen and its windows. Add scroll hints where edges of terminal + * screen hit non-edges inside the virtual screen. Then update terminal screen. + */ +extern void draw_all_wins(); + + + +#endif diff --git a/src/client/world.h b/src/client/world.h new file mode 100644 index 0000000..e17ab09 --- /dev/null +++ b/src/client/world.h @@ -0,0 +1,47 @@ +/* src/client/world.h + * + * Contains the World struct holding all quasi-global game data together. + */ + +#ifndef WORLD_H +#define WORLD_H + +#include /* uint8_t, uint16_t */ +#include /* time_t */ +#include "../common/map.h" /* struct Map */ +#include "../common/yx_uint16.h" /* struct yx_uint16 */ +#include "keybindings.h" /* stuct KeyBindingDB */ +#include "command_db.h" /* struct CommandDB */ +#include "windows.h" /* struct WinMeta */ +#include "wincontrol.h" /* WinConfDB */ + + + +struct World +{ + struct WinMeta wmeta; + struct WinConfDB winconf_db; + struct CommandDB cmd_db; /* Command database. */ + struct KeyBindingDB kb_global; /* Global keybindings. */ + struct KeyBindingDB kb_wingeom; /* Window geometry config keybindings. */ + struct KeyBindingDB kb_winkeys; /* Window keybinding config keybindings.*/ + struct Map map; /* Pointer to the game map cells. */ + time_t last_update; + struct yx_uint16 player_pos; + char * log; + char * path_server_in; + char * player_inventory; + uint16_t turn; + uint16_t score; + uint8_t halfdelay; + uint8_t player_inventory_select; + uint8_t player_lifepoints; +}; + + + +extern struct World world; + + + +#endif diff --git a/src/command_db.c b/src/command_db.c deleted file mode 100644 index 37f4d69..0000000 --- a/src/command_db.c +++ /dev/null @@ -1,120 +0,0 @@ -/* command.c */ - -#include "command_db.h" -#include /* for free() */ -#include /* for FILE typedef */ -#include /* for uint8_t */ -#include /* for strlen(), strtok() */ -#include "main.h" /* for world global */ -#include "readwrite.h" /* for textfile_sizes(), try_fopen(), try_fclose(), - * try_fgets() - */ -#include "misc.h" /* for try_malloc() */ - - - -/* Build string pointed to by "ch_ptr" from next token delimited by "delim". */ -static void copy_tokenized_string(char ** ch_ptr, char * delim); - - - -static void copy_tokenized_string(char ** ch_ptr, char * delim) -{ - char * f_name = "copy_tokenized_string()"; - char * dsc_ptr = strtok(NULL, delim); - * ch_ptr = try_malloc(strlen(dsc_ptr) + 1, f_name); - memcpy(* ch_ptr, dsc_ptr, strlen(dsc_ptr) + 1); -} - - - -extern uint8_t is_command_id_shortdsc(uint8_t id, char * shortdsc) -{ - struct Command * cmd_ptr = world.cmd_db->cmds; - while (1) - { - if (id == cmd_ptr->id) - { - if (strcmp(shortdsc, cmd_ptr->dsc_short)) - { - return 0; - } - return 1; - } - cmd_ptr = &cmd_ptr[1]; - } -} - - - -extern uint8_t get_command_id(char * dsc_short) -{ - struct Command * cmd_ptr = world.cmd_db->cmds; - while (1) - { - if (0 == strcmp(dsc_short, cmd_ptr->dsc_short)) - { - return cmd_ptr->id; - } - cmd_ptr = &cmd_ptr[1]; - } -} - - - -extern char * get_command_longdsc(char * dsc_short) -{ - struct Command * cmd_ptr = world.cmd_db->cmds; - while (1) - { - if (0 == strcmp(dsc_short, cmd_ptr->dsc_short)) - { - return cmd_ptr->dsc_long; - } - cmd_ptr = &cmd_ptr[1]; - } -} - - - -extern void init_command_db() -{ - char * f_name = "init_command_db()"; - char * path = "config/commands"; - FILE * file = try_fopen(path, "r", f_name); - uint16_t lines; - uint16_t linemax = textfile_sizes(file, &lines); - char line[linemax + 1]; - struct Command * cmds = try_malloc(lines * sizeof(struct Command), f_name); - uint8_t i = 0; - while (try_fgets(line, linemax + 1, file, f_name)) - { - if ('\n' == line[0] || 0 == line[0]) - { - break; - } - cmds[i].id = atoi(strtok(line, " ")); - copy_tokenized_string(&cmds[i].dsc_short, " "); - copy_tokenized_string(&cmds[i].dsc_long, "\n"); - i++; - } - try_fclose(file, f_name); - world.cmd_db = try_malloc(sizeof(struct CommandDB), f_name); - world.cmd_db->cmds = cmds; - world.cmd_db->n = lines; -} - - - -extern void free_command_db() -{ - uint8_t i = 0; - while (i < world.cmd_db->n) - { - free(world.cmd_db->cmds[i].dsc_short); - free(world.cmd_db->cmds[i].dsc_long); - i++; - } - free(world.cmd_db->cmds); - free(world.cmd_db); -} diff --git a/src/command_db.h b/src/command_db.h deleted file mode 100644 index 866f5e3..0000000 --- a/src/command_db.h +++ /dev/null @@ -1,47 +0,0 @@ -/* command.h - * - * The Command DB collects all commands the user can give. It only contains - * identifiers and descriptions of commands, not the functions executing these - * commands. Coupling with those happens elsewhere. - */ - -#ifndef COMMAND_DB_H -#define COMMAND_DB_H - -#include /* for uint8_t */ - - - -struct Command -{ - uint8_t id; /* unique identifier of command */ - char * dsc_short; /* short string name of command to be used internally */ - char * dsc_long; /* long string description of command for the user */ -}; - -struct CommandDB -{ - uint8_t n; /* number of Command structs in database*/ - struct Command * cmds; /* pointer to first Command struct in database */ -}; - - - -/* Is "id" the ID of command whose dsc_short is "shortdsc"? Answer in binary. */ -extern uint8_t is_command_id_shortdsc(uint8_t id, char * shortdsc); - -/* Give short description of command ("dsc_short"), get its ID. */ -extern uint8_t get_command_id(char * dsc_short); - -/* Give short description of command ("dsc_short"), get long description. */ -extern char * get_command_longdsc(char * dsc_short); - -/* Read in CommandDB from file "config/commands" to world.cmd_db. */ -extern void init_command_db(); - -/* Free all memory allocated with init_command_db. */ -extern void free_command_db(); - - - -#endif diff --git a/src/common/map.h b/src/common/map.h new file mode 100644 index 0000000..cac29dc --- /dev/null +++ b/src/common/map.h @@ -0,0 +1,21 @@ +/* src/common/map.h + * + * Struct for the game map. + */ + +#ifndef MAP_H +#define MAP_H + +#include "yx_uint16.h" /* yx_uint16 struct */ + + + +struct Map +{ + struct yx_uint16 size; /* map's height/width in number of cells */ + char * cells; /* sequence of bytes encoding map cells */ +}; + + + +#endif diff --git a/src/common/readwrite.c b/src/common/readwrite.c new file mode 100644 index 0000000..026c9ad --- /dev/null +++ b/src/common/readwrite.c @@ -0,0 +1,136 @@ +/* src/common/readwrite.c */ + +#include "readwrite.h" +#include /* size_t */ +#include /* uint8_t, uint16_t, uint32_t */ +#include /* FILE, fseek(), sprintf(), fgets(), fgetc(), ferror(), + * fputc(), fwrite(), fclose(), fopen() + */ +#include /* strlen() */ +#include /* for access(), unlink() */ +#include "rexit.h" /* exit_err(), exit_trouble() */ + + + +extern FILE * try_fopen(char * path, char * mode, char * f) +{ + char * msg1 = "Trouble in "; + char * msg2 = " with fopen() (mode '"; + char * msg3 = "') on path '"; + char * msg4 = "'."; + uint16_t size = strlen(msg1) + strlen(msg2) + strlen(msg3) + strlen(msg4) + + strlen(f) + strlen(path) + strlen(mode) + 1; + char msg[size]; + sprintf(msg, "%s%s%s%s%s%s%s", msg1, f, msg2, mode, msg3, path, msg4); + FILE * file_p = fopen(path, mode); + exit_err(NULL == file_p, msg); + return file_p; +} + + + +extern void try_fclose(FILE * file, char * f) +{ + exit_trouble(fclose(file), f, "fclose()"); +} + + + +extern void try_fwrite(void * ptr, size_t size, size_t nmemb, FILE * stream, + char * f) +{ + exit_trouble(0 == fwrite(ptr, size, nmemb, stream), f, "fwrite()"); +} + + + +extern void try_fputc(uint8_t c, FILE * file, char * f) +{ + exit_trouble(EOF == fputc(c, file), f, "fputc()"); +} + + + +extern int try_fgetc(FILE * file, char * f) +{ + int test = fgetc(file); + exit_trouble(EOF == test && ferror(file), f, "fgetc()"); + return test; +} + + + +extern char * try_fgets(char * line, int linemax, FILE * file, char * f) +{ + char * test = fgets(line, linemax, file); + exit_trouble(NULL == test && ferror(file), f, "fgets()"); + return test; +} + + + +extern void try_fclose_unlink_rename(FILE * file, char * p1, char * p2, + char * f) +{ + try_fclose(file, f); + char * msg1 = "Trouble in "; + char * msg4 = "'."; + if (!access(p2, F_OK)) + { + char * msg2 = " with unlink() on path '"; + uint16_t size = strlen(msg1) + strlen(msg2) + strlen(msg4) + + strlen(f) + strlen(p2) + 1; + char msg[size]; + sprintf(msg, "%s%s%s%s%s", msg1, f, msg2, p2, msg4); + exit_err(unlink(p2), msg); + } + char * msg2 = " with rename() from '"; + char * msg3 = "' to '"; + uint16_t size = strlen(msg1) + strlen(f) + strlen(msg2) + strlen(p1) + + strlen(msg3) + strlen(p2) + strlen(msg4) + 1; + char msg[size]; + sprintf(msg, "%s%s%s%s%s%s%s", msg1, f, msg2, p1, msg3, p2, msg4); + exit_err(rename(p1, p2), msg); +} + + + +extern uint32_t textfile_sizes(FILE * file, uint32_t * n_lines_p) +{ + char * f_name = "textfile_sizes()"; + int c = 0; + uint32_t c_count = 0; + uint32_t n_lines = 0; + uint32_t linemax = 0; + while (1) + { + c = try_fgetc(file, f_name); + if (EOF == c) + { + break; + } + c_count++; + if ('\n' == c) + { + if (c_count > linemax) + { + linemax = c_count; + } + c_count = 0; + if (n_lines_p) + { + n_lines++; + } + } + } + if (0 == linemax && 0 < c_count) /* Handle files that consist of only one */ + { /* line / lack newline chars. */ + linemax = c_count; + } + exit_trouble(-1 == fseek(file, 0, SEEK_SET), f_name, "fseek()"); + if (n_lines_p) + { + * n_lines_p = n_lines; + } + return linemax; +} diff --git a/src/common/readwrite.h b/src/common/readwrite.h new file mode 100644 index 0000000..9c1358a --- /dev/null +++ b/src/common/readwrite.h @@ -0,0 +1,45 @@ +/* src/common/readwrite.h: + * + * Routines for reading and writing files. + */ + +#ifndef READWRITE_H +#define READWRITE_H + +#include /* uint8_t, uint32_t */ +#include /* FILE */ + + + +/* Wrappers to fopen(), fclose(), fgets() and fwrite() from function called "f", + * calling exit_err() upon error with appropriate error messages. + */ +extern FILE * try_fopen(char * path, char * mode, char * f); +extern void try_fclose(FILE * file, char * f); +extern void try_fwrite(void * ptr, size_t size, size_t nmemb, FILE * stream, + char * f); +extern void try_fputc(uint8_t c, FILE * file, char * f); + +/* Wrapper to calling fgetc() and fgets() from function "f". The return code is + * returned unless ferror() indicates an error (i.e. to signify an end of file, + * fgetc() may return an EOF and fgets() a NULL. + */ +extern int try_fgetc(FILE * file, char * f); +extern char * try_fgets(char * line, int size, FILE * file, char * f); + +/* Wrapper to successive call of fclose() from function called "f" on "file", + * then unlink() on file at path "p2" if it exists, then rename() from path "p1" + * to "p2". Used for handling atomic saving of files via temp files. + */ +extern void try_fclose_unlink_rename(FILE * file, char * p1, char * p2, + char * f); + +/* Return largest line length from "file" the largest line length (including + * newline chars) and write the number of newline chars in "file" to the memory + * pointed to by "n_lines_p" if it is not passed as NULL. + */ +extern uint32_t textfile_sizes(FILE * file, uint32_t * n_lines_p); + + + +#endif diff --git a/src/common/rexit.c b/src/common/rexit.c new file mode 100644 index 0000000..7b837e9 --- /dev/null +++ b/src/common/rexit.c @@ -0,0 +1,56 @@ +/* src/common/rexit.c */ + +#include "rexit.h" +#include /* global errno */ +#include /* uint16_t */ +#include /* printf(), perror(), sprintf() */ +#include /* exit(), EXIT_FAILURE */ +#include /* strlen() */ + + + +void (* cleanup_func) (); + + + +extern void set_cleanup_func(void (* f)()) +{ + cleanup_func = f; +} + + + +extern void exit_err(int err, char * msg) +{ + if (0 == err) + { + return; + } + cleanup_func(); + if (NULL == msg) + { + msg = "Details unknown."; + } + printf("Aborted program due to error. %s\n" + "Internal error code: %d\n", + msg, err); + if (0 != errno) + { + perror("errno states"); + } + exit(EXIT_FAILURE); +} + + + +extern void exit_trouble(int err, char * parent, char * child) +{ + char * p1 = "Trouble in "; + char * p2 = " with "; + char * p3 = "."; + uint16_t size = strlen(p1) + strlen(parent) + strlen(p2) + strlen(child) + + strlen(p3) + 1; + char msg[size]; + sprintf(msg, "%s%s%s%s%s", p1, parent, p2, child, p3); + exit_err(err, msg); +} diff --git a/src/common/rexit.h b/src/common/rexit.h new file mode 100644 index 0000000..3095cf8 --- /dev/null +++ b/src/common/rexit.h @@ -0,0 +1,27 @@ +/* src/common/rexit.h + * + * Routines to exit the game orderly on error, with cleaning up and a somewhat + * informative message. + */ + +#ifndef REXIT_H +#define REXIT_H + + + +/* Set "f" as the cleanup function to be called on exit. */ +extern void set_cleanup_func(void (* f)()); + +/* If "err" == 0, do nothing. Else, clean up and exit with an error message that + * consists, first, of "msg" or (if "msg" is a NULL pointer) a generic "Details + * unknown", secondly of "err" as the "internal error code", and thirdly of + * errno if it is non-zero. + */ +extern void exit_err(int err, char * msg); + +/* Do exit_err() with "msg" as: "Trouble in ".parent." with ".child."." */ +extern void exit_trouble(int err, char * parent, char * child); + + + +#endif diff --git a/src/common/try_malloc.c b/src/common/try_malloc.c new file mode 100644 index 0000000..2a21927 --- /dev/null +++ b/src/common/try_malloc.c @@ -0,0 +1,15 @@ +/* src/common/try_malloc.c */ + +#include "try_malloc.h" +#include /* for malloc */ +#include /* for size_t */ +#include "rexit.h" /* for exit_trouble() */ + + + +extern void * try_malloc(size_t size, char * f) +{ + void * p = malloc(size); + exit_trouble(NULL == p, f, "malloc()"); + return p; +} diff --git a/src/common/try_malloc.h b/src/common/try_malloc.h new file mode 100644 index 0000000..9cf76f2 --- /dev/null +++ b/src/common/try_malloc.h @@ -0,0 +1,18 @@ +/* src/common/try_malloc.h + * + * malloc() wrapper. + */ + +#ifndef TRY_MALLOC_H +#define TRY_MALLOC_H + +#include /* for size_t */ + + + +/* Call malloc("size") from function called "f"; exit_trouble() on error. */ +extern void * try_malloc(size_t size, char * f); + + + +#endif diff --git a/src/common/yx_uint16.h b/src/common/yx_uint16.h new file mode 100644 index 0000000..6dd359a --- /dev/null +++ b/src/common/yx_uint16.h @@ -0,0 +1,23 @@ +/* yx_uint16.h + * + * Struct coordinates in 2-dimensional space (such as the ncurses screen and + * game maps). + */ + +#ifndef YX_UINT16_H +#define YX_UINT16_H + +#include /* for uint16_t */ + + + +/* Coordinates for maps of max. 65536x65536 cells. */ +struct yx_uint16 +{ + uint16_t y; + uint16_t x; +}; + + + +#endif diff --git a/src/control.c b/src/control.c deleted file mode 100644 index 1e9265e..0000000 --- a/src/control.c +++ /dev/null @@ -1,272 +0,0 @@ -/* control.c */ - -#include "control.h" -#include /* for uint8_t, uint16_t */ -#include "windows.h" /* for cycle_active_win(), shift_active_win(), struct Win, - * struct WinMeta - */ -#include "keybindings.h" /* for get_keycode_to_action(), mod_selected_keyb(), - * move_keyb_mod_selection(), get_func_to_keycode() - */ -#include "map.h" /* for map_scroll(), map_center() */ -#include "main.h" /* for world global */ -#include "wincontrol.h" /* for struct WinConf, scroll_pad(), toggle_window(), - * growshrink_active_window(), toggle_winconfig(), - * toggle_win_size_type() - */ -#include "map_object_actions.h" /* for get_moa_id_by_name() */ -#include "command_db.h" /* for is_command_id_shortdsc() */ -#include "misc.h" /* for reload_interface_conf(), save_interface_conf(), - * nav_inventory(), turn_over() - */ -#include "map_objects.h" /* for get_player() */ - - - -/* If "cmd" matches "match" in get_available_keycode_to_action(), execute "f" - * with provided char arguments and return 1; else only return 0. - */ -static uint8_t try_cmd_0args(int cmd, char * match, void (* f) ()); -static uint8_t try_cmd_1args(int cmd, char * match, void (* f) (char), char c); -static uint8_t try_cmd_2args(int cmd, char * match, - void (* f) (char, char), char c1, char c2); - -/* If "action" is id of command named "match", set player's .arg and .command - * and call turn_over(). - */ -static uint8_t try_player_cmd(int action, char * match, char * command, - uint8_t arg); - -/* Return pointer to global keybindings or to keybindings for wingeometry config - * (c = "g") or winkeys config (c = "k") or active window's keybindings ("w"). - */ -static struct KeyBiData * select_keybidata_pointer(char c); - -/* Wrappers to make some functions compatible to try_cmd_* single char args. */ -static void wrap_mod_selected_keyb(char c); -static void wrap_mv_kb_mod(char c1, char c2); - - - -static uint8_t try_cmd_0args(int cmd, char * match, void (* f) ()) -{ - if (cmd == get_available_keycode_to_action(match)) - { - f(); - return 1; - } - return 0; -} - - - -static uint8_t try_cmd_1args(int cmd, char * match, void (* f) (char), char c) -{ - if (cmd == get_available_keycode_to_action(match)) - { - f(c); - return 1; - } - return 0; -} - - - -static uint8_t try_cmd_2args(int cmd, char * match, - void (* f) (char, char), char c1, char c2) -{ - if (cmd == get_available_keycode_to_action(match)) - { - f(c1, c2); - return 1; - } - return 0; -} - - - -static uint8_t try_player_cmd(int action, char * match, char * command, - uint8_t arg) -{ - if (is_command_id_shortdsc(action, match)) - { - struct MapObj * player = get_player(); - player->arg = arg; - player->command = get_moa_id_by_name(command); - turn_over(get_command_id(match)); - return 1; - } - return 0; -} - - - -static struct KeyBiData * select_keybidata_pointer(char c) -{ - struct KeyBiData * kbd; - kbd = &world.kb_global; - if ('g' == c) - { - kbd = &world.kb_wingeom; - } - else if ('k' == c) - { - kbd = &world.kb_winkeys; - } - else if ('w' == c) - { - struct WinConf * wc = get_winconf_by_win(world.wmeta->active); - kbd = &wc->kb; - } - return kbd; -} - - - -static void wrap_mod_selected_keyb(char c) -{ - mod_selected_keyb(select_keybidata_pointer(c)); -} - - - -static void wrap_mv_kb_mod(char c1, char c2) -{ - move_keyb_mod_selection(select_keybidata_pointer(c1), c2); -} - - - -extern uint16_t get_available_keycode_to_action(char * name) -{ - uint16_t keycode = get_keycode_to_action(world.kb_global.kbs, name); - if (0 != keycode || 0 == world.wmeta->active) - { - return keycode; - } - struct WinConf * wc = get_winconf_by_win(world.wmeta->active); - if (0 == wc->view) - { - keycode = get_keycode_to_action(wc->kb.kbs, name); - } - else if (1 == wc->view) - { - keycode = get_keycode_to_action(world.kb_wingeom.kbs, name); - } - else if (2 == wc->view) - { - keycode = get_keycode_to_action(world.kb_winkeys.kbs, name); - } - return keycode; -} - - - -extern uint8_t player_control_by_key(int key) -{ - char * action_name = get_func_to_keycode(world.kb_global.kbs, key); - if (NULL == action_name && 0 != world.wmeta->active) - { - struct WinConf * wc = get_winconf_by_win(world.wmeta->active); - action_name = get_func_to_keycode(wc->kb.kbs, key); - } - if (NULL != action_name) - { - uint8_t action_id = get_command_id(action_name); - return player_control_by_id(action_id); - } - return 0; -} - - - - -extern uint8_t player_control_by_id(int action) -{ - if ( try_player_cmd(action, "wait", "wait", 0) - || try_player_cmd(action, "drop", "drop", world.inventory_sel) - || try_player_cmd(action, "pick", "pick_up", 0) - || try_player_cmd(action, "use", "use", world.inventory_sel) - || try_player_cmd(action, "player_u", "move", 'N') - || try_player_cmd(action, "player_d", "move", 'S') - || try_player_cmd(action, "player_r", "move", 'E') - || try_player_cmd(action, "player_l", "move", 'W')) - { - return 1; - } - return 0; -} - - - -extern uint8_t wingeom_control(int key) -{ - if ( try_cmd_1args(key, "to_height_t", toggle_win_size_type, 'y') - || try_cmd_1args(key, "to_width_t", toggle_win_size_type, 'x') - || try_cmd_1args(key, "grow_h", growshrink_active_window, '*') - || try_cmd_1args(key, "shri_h", growshrink_active_window, '_') - || try_cmd_1args(key, "grow_v", growshrink_active_window, '+') - || try_cmd_1args(key, "shri_v", growshrink_active_window, '-') - || try_cmd_1args(key, "shift_f", shift_active_win, 'f') - || try_cmd_1args(key, "shift_b", shift_active_win, 'b')) - { - return 1; - } - return 0; -} - - - -extern uint8_t winkeyb_control(int key) -{ - if ( try_cmd_1args(key, "w_keys_m", wrap_mod_selected_keyb, 'w') - || try_cmd_2args(key, "w_keys_u", wrap_mv_kb_mod, 'w', 'u') - || try_cmd_2args(key, "w_keys_d", wrap_mv_kb_mod, 'w', 'd')) - { - return 1; - } - return 0; -} - - - -extern uint8_t meta_control(int key) -{ - uint8_t ret = (key == get_available_keycode_to_action("quit")); - if ( (0 == ret) - && ( try_cmd_0args(key, "winconf", toggle_winconfig) - || try_cmd_0args(key, "reload_conf", reload_interface_conf) - || try_cmd_0args(key, "save_conf", save_interface_conf) - || try_cmd_0args(key, "map_c", map_center) - || try_cmd_1args(key, "scrl_r", scroll_pad, '+') - || try_cmd_1args(key, "scrl_l", scroll_pad, '-') - || try_cmd_1args(key, "to_a_keywin", toggle_window, 'k') - || try_cmd_1args(key, "to_g_keywin", toggle_window, '0') - || try_cmd_1args(key, "to_wg_keywin", toggle_window, '1') - || try_cmd_1args(key, "to_wk_keywin", toggle_window, '2') - || try_cmd_1args(key, "to_mapwin", toggle_window, 'm') - || try_cmd_1args(key, "to_infowin", toggle_window, 'i') - || try_cmd_1args(key, "to_inv", toggle_window, 'c') - || try_cmd_1args(key, "to_logwin", toggle_window, 'l') - || try_cmd_1args(key, "cyc_win_f", cycle_active_win, 'f') - || try_cmd_1args(key, "cyc_win_b", cycle_active_win, 'b') - || try_cmd_1args(key, "g_keys_m", wrap_mod_selected_keyb, 'G') - || try_cmd_1args(key, "wg_keys_m", wrap_mod_selected_keyb, 'g') - || try_cmd_1args(key, "wk_keys_m", wrap_mod_selected_keyb, 'k') - || try_cmd_1args(key, "inv_u", nav_inventory, 'u') - || try_cmd_1args(key, "inv_d", nav_inventory, 'd') - || try_cmd_1args(key, "map_u", map_scroll, 'N') - || try_cmd_1args(key, "map_d", map_scroll, 'S') - || try_cmd_1args(key, "map_r", map_scroll, 'E') - || try_cmd_1args(key, "map_l", map_scroll, 'W') - || try_cmd_2args(key, "g_keys_u", wrap_mv_kb_mod, 'G', 'u') - || try_cmd_2args(key, "g_keys_d", wrap_mv_kb_mod, 'G', 'd') - || try_cmd_2args(key, "wg_keys_u", wrap_mv_kb_mod, 'g', 'u') - || try_cmd_2args(key, "wg_keys_d", wrap_mv_kb_mod, 'g', 'd') - || try_cmd_2args(key, "wk_keys_u", wrap_mv_kb_mod, 'k', 'u') - || try_cmd_2args(key, "wk_keys_d", wrap_mv_kb_mod, 'k', 'd'))) - { - ; - } - return ret; -} diff --git a/src/control.h b/src/control.h deleted file mode 100644 index 492b80b..0000000 --- a/src/control.h +++ /dev/null @@ -1,33 +0,0 @@ -/* control.h - * - * Routines for handling control input from keyboard or record file. - */ - -#ifndef CONTROL_H -#define CONTROL_H - -#include /* for uint8_t */ - - - -/* Return keycode to action of "name" if available in current window config. */ -extern uint16_t get_available_keycode_to_action(char * name); - -/* Control the player character, either via action id "action" or pressed "key". - * Return 1 on success, 0 if no appropriate action to trigger was found. - */ -extern uint8_t player_control_by_key(int key); -extern uint8_t player_control_by_id(int action); - -/* Control via "key" active window's config view's geometry / keybindings. */ -extern uint8_t wingeom_control(int key); -extern uint8_t winkeyb_control(int key); - -/* Call via "key" further game/window management actions not influencing the - * player character. If "quit" is called, return 1, else 0. - */ -extern uint8_t meta_control(int key); - - - -#endif diff --git a/src/draw_wins.c b/src/draw_wins.c deleted file mode 100644 index b589a30..0000000 --- a/src/draw_wins.c +++ /dev/null @@ -1,495 +0,0 @@ -/* draw_wins.c */ - -#include "draw_wins.h" -#include /* for free() */ -#include /* for uint16_t */ -#include /* for strlen() */ -#include /* for attri_t, chtype */ -#include "windows.h" /* for struct Win */ -#include "misc.h" /* for try_malloc() */ -#include "keybindings.h" /* for struct KeyBinding, for get_name_to_keycode() */ -#include "map_objects.h" /* for structs MapObj, get_map_object_def(), - * get_player() - */ -#include "map.h" /* for Map struct */ -#include "main.h" /* for world global */ -#include "command_db.h" /* for get_command_longdesc() */ -#include "wincontrol.h" /* for WinConf struct, get_winconf_by_win() */ - - - -/* Apply to the winmap of Win "w" the new sizes "new_size_y" and "new_size_x" - * to the degree that they extend it. Re-shape the window content accordingly. - */ -static void try_resize_winmap(struct Win * w, int new_size_y, int new_size_x); - -/* In Win "w", write "ch" to coordinate "y"/"x". */ -static void set_ch_on_yx(struct Win * w, int y, int x, chtype ch); - -/* Add "text" into window "win". Break text at right window edge. Also break at - * newlines. - */ -static void add_text_with_linebreaks(struct Win * win, char * text); - -/* Add "line" into window "w". Apply ncurses attribute "attri" to all - * characters drawn. If "fill" is non-zero, fill the entire line until the - * right window edge with empty characters ("attri" also applied on these). - */ -static void add_line(struct Win * w, char * line, attr_t attri, uint8_t fill); - -/* Write "text" with add_text_with_linebreaks() as not starting from the top but - * from bottom of "win". Draw only what fits in window (avoid scroll hints). - */ -static void draw_text_from_bottom(struct Win * win, char * text); - -/* Draw onto "map" in "win" the objects in the chain at "start". */ -static void draw_map_objects(struct MapObj * start, struct Map * map, - struct Win * win); - -/* Return keybinding list line via "kb_pp", iterate pointer pointed to by it. */ -static char * get_kb_line_and_iterate(struct KeyBinding ** kb_pp); - -/* Draw from line "start" on config view for keybindings defined at "kb". */ -static void draw_kb_view(struct Win * w, struct KeyBiData * kb, uint8_t start); - -/* Draw into window "w" from line "start" on a "title" followed by an empty - * line followed by a list of all keybindings starting at kb_p. - */ -static uint16_t draw_titled_keybinding_list(char * title, struct Win * w, - uint16_t start, - struct KeyBinding * kb_p); - - - -static void try_resize_winmap(struct Win * w, int new_size_y, int new_size_x) -{ - char * f_name = "try_resize_winmap()"; - if (w->winmapsize.y >= new_size_y && w->winmapsize.x >= new_size_x) - { - return; - } - if (w->winmapsize.y > new_size_y) - { - new_size_y = w->winmapsize.y; - } - else if (w->winmapsize.x > new_size_x) - { - new_size_x = w->winmapsize.x; - } - chtype * old_winmap = w->winmap; - uint32_t new_size = sizeof(chtype) * new_size_y * new_size_x; - w->winmap = try_malloc(new_size, f_name); - uint16_t y, x; - for (y = 0; y < new_size_y; y++) - { - for (x = 0; y < w->winmapsize.y && x < w->winmapsize.x; x++) - { - chtype ch = old_winmap[(y * w->winmapsize.x) + x]; - w->winmap[(y * new_size_x) + x] = ch; - } - for (; x < new_size_x; x++) - { - w->winmap[(y * new_size_x) + x] = ' '; - } - } - free(old_winmap); - w->winmapsize.y = new_size_y; - w->winmapsize.x = new_size_x; -} - - - -static void set_ch_on_yx(struct Win * w, int y, int x, chtype ch) -{ - w->winmap[(y * w->winmapsize.x) + x] = ch; -} - - - -static void add_text_with_linebreaks(struct Win * win, char * text) -{ - uint16_t x, y; - int16_t z = -1; - for (y = win->winmapsize.y; ; y++) - { - try_resize_winmap(win, y + 1, win->framesize.x); - for (x = 0; x < win->framesize.x; x++) - { - z++; - if ('\n' == text[z]) - { - break; - } - else - { - set_ch_on_yx(win, y, x, text[z]); - } - if ('\n' == text[z+1]) - { - z++; - break; - } - else if (0 == text[z+1]) - { - return; - } - } - } -} - - - -static void add_line(struct Win * w, char * line, attr_t attri, uint8_t fill) -{ - uint16_t y = w->winmapsize.y; - uint16_t len_line = strlen(line); - if (0 != fill - && w->winmapsize.x < w->framesize.x && w->framesize.x > len_line) - { - try_resize_winmap(w, y + 1, w->framesize.x); - } - else - { - try_resize_winmap(w, y + 1, strlen(line)); - } - uint16_t x = 0; - for (; x < len_line; x++) - { - set_ch_on_yx(w, y, x, line[x] | attri); - } - if (0 != fill) - { - for (; x < w->framesize.x; x++) - { - set_ch_on_yx(w, y, x, ' ' | attri); - } - } -} - - - -static void draw_text_from_bottom(struct Win * win, char * text) -{ - /* Determine number of lines text would have in a window of win's width, - * but infinite height. Treat \n and \0 as control chars for incrementing - * y and stopping the loop. Make sure +they* don't count as cell space. - */ - char toggle = 0; - uint16_t x, y; - int16_t z = -1; - for (y = 0; 0 == toggle; y++) - { - for (x = 0; x < win->framesize.x; x++) - { - z++; - if ('\n' == text[z]) - { - break; - } - if ('\n' == text[z+1]) - { - z++; - break; - } - else if (0 == text[z+1]) - { - toggle = 1; - break; - } - } - } - z = -1; - - /* Depending on what's bigger, determine start point in window or text. */ - uint16_t start_y = 0; - if (y < win->framesize.y) - { - start_y = win->framesize.y - y; - } - else if (y > win->framesize.y) - { - uint16_t offset = y - win->framesize.y; - for (y = 0; y < offset; y++) - { - for (x = 0; x < win->framesize.x; x++) - { - z++; - if ('\n' == text[z]) - { - break; - } - if ('\n' == text[z+1]) - { - z++; - break; - } - } - } - text = text + (sizeof(char) * (z + 1)); - } - - try_resize_winmap(win, start_y, 1); - add_text_with_linebreaks(win, text); -} - - - -static void draw_map_objects(struct MapObj * start, struct Map * map, - struct Win * win) -{ - struct MapObj * o; - struct MapObjDef * d; - char c; - uint8_t i; - for (i = 0; i < 2; i++) - { - for (o = start; o != 0; o = o->next) - { - if (( (0 == i && 0 == o->lifepoints) /* Draw in-animate */ - || (1 == i && 0 < o->lifepoints))) /* objects first. */ - { - d = get_map_object_def(o->type); - c = d->char_on_map; - set_ch_on_yx(win, o->pos.y, o->pos.x, c); - } - } - } -} - - - -static char * get_kb_line_and_iterate(struct KeyBinding ** kb_pp) -{ - char * f_name = "get_kb_line_and_iterate()"; - struct KeyBinding * kb_p = * kb_pp; - char * keyname = get_name_to_keycode(kb_p->key); - char * cmd_dsc = get_command_longdsc(kb_p->name); - uint16_t size = 9 + 1 + strlen(cmd_dsc) + 1; - char * line = try_malloc(size, f_name); - sprintf(line, "%-9s %s", keyname, cmd_dsc); - free(keyname); - * kb_pp = kb_p->next; - return line; -} - - - -static void draw_kb_view(struct Win * w, struct KeyBiData * kb, uint8_t start) -{ - if (0 == kb->kbs) - { - add_line(w, "(none)", 0, 0); - return; - } - struct KeyBinding * kb_p = kb->kbs; - uint16_t y; - for (y = start; 0 != kb_p; y++) - { - attr_t attri = 0; - if (y - start == kb->select) - { - attri = A_REVERSE; - if (1 == kb->edit) - { - attri = attri | A_BLINK; - } - } - char * kb_line = get_kb_line_and_iterate(&kb_p); - add_line(w, kb_line, attri, 1); - free(kb_line); - } -} - - - -static uint16_t draw_titled_keybinding_list(char * title, struct Win * w, - uint16_t start, - struct KeyBinding * kb_p) -{ - uint16_t y; - uint8_t state = 0; - for (y = start; (0 == state || 0 != kb_p); y++) - { - if (0 == state) - { - add_line(w, title, 0, 0); - y++; - add_line(w, " ", 0, 0); - state = 1 + (0 == kb_p); - continue; - } - char * kb_line = get_kb_line_and_iterate(&kb_p); - add_line(w, kb_line, 0, 0); - free(kb_line); - } - if (2 == state) - { - char * none = "(none)"; - add_line(w, none, 0, 0); - y++; - } - return y; -} - - - -extern void draw_win_log(struct Win * win) -{ - draw_text_from_bottom(win, world.log); -} - - - -extern void draw_win_map(struct Win * win) -{ - struct Map * map = world.map; - char * cells = map->cells; - try_resize_winmap(win, map->size.y, map->size.x); - uint16_t z = 0; - uint16_t x, y; - for (y = 0; y < map->size.y; y++) - { - for (x = 0; x < map->size.x; x++) - { - set_ch_on_yx(win, y, x, cells[z]); - z++; - } - } - draw_map_objects(world.map_objs, map, win); -} - - - -extern void draw_win_info(struct Win * win) -{ - char * dsc_turn = "Turn: "; - char * dsc_hitpoints = "\nHitpoints: "; - char * dsc_score = "\nScore: "; - uint16_t maxl = strlen(dsc_turn) + strlen(dsc_hitpoints) + strlen(dsc_score) - + 10 + 5 + 10; /* max strlens of numbers to be used */ - char text[maxl + 1]; - struct MapObj * player = get_player(); - sprintf(text, "%s%d%s%d%s%d", - dsc_turn, world.turn, - dsc_hitpoints, player->lifepoints, - dsc_score, world.score); - add_text_with_linebreaks(win, text); -} - - - -extern void draw_win_inventory(struct Win * win) -{ - struct MapObj * player = get_player(); - if (NULL == player->owns) - { - add_line(win, "(none)", 0, 0); - return; - } - win->center.y = world.inventory_sel; - struct MapObj * owned = player->owns; - uint8_t y; - for (y = 0; NULL != owned; y++) - { - attr_t attri = 0; - if (y == world.inventory_sel) - { - attri = A_REVERSE; - } - struct MapObjDef * mod = get_map_object_def(owned->type); - add_line(win, mod->name, attri, 0); - owned = owned->next; - } -} - - - -extern void draw_win_available_keybindings(struct Win * win) -{ - char * title = "Active window's keybindings:"; - struct KeyBinding * kb_p; - struct WinConf * wc = get_winconf_by_win(world.wmeta->active); - if (0 == wc->view) - { - kb_p = wc->kb.kbs; - } - else if (1 == wc->view) - { - kb_p = world.kb_wingeom.kbs; - } - else if (2 == wc->view) - { - kb_p = world.kb_winkeys.kbs; - } - uint16_t offset = draw_titled_keybinding_list(title, win, 0, kb_p); - add_line(win, " ", 0, 0); - struct KeyBinding * kbs_glo = world.kb_global.kbs; - draw_titled_keybinding_list("Global keybindings", win, offset + 1, kbs_glo); -} - - - -extern void draw_win_keybindings_global(struct Win * win) -{ - win->center.y = world.kb_global.select; - draw_kb_view(win, &world.kb_global, 0); -} - - - -extern void draw_win_keybindings_winconf_geometry(struct Win * win) -{ - win->center.y = world.kb_wingeom.select; - draw_kb_view(win, &world.kb_wingeom, 0); -} - - - -extern void draw_win_keybindings_winconf_keybindings(struct Win * win) -{ - win->center.y = world.kb_winkeys.select; - draw_kb_view(win, &world.kb_winkeys, 0); -} - - - -extern void draw_winconf_keybindings(struct Win * win) -{ - struct WinConf * wc = get_winconf_by_win(win); - char * title = "Window's keybindings:"; - add_line(win, title, 0, 0); - add_line(win, " ", 0, 0); - draw_kb_view(win, &wc->kb, 2); - win->center.y = wc->kb.select + 2; -} - - - -extern void draw_winconf_geometry(struct Win * win) -{ - struct WinConf * wcp = get_winconf_by_win(/*&world, */win); - char * title = "Window's geometry:\n"; - char * h_d = "\nHeight to save: "; - char * h_pos = " (width in cells)"; - char * h_neg = " (negative diff: cells to screen width)"; - char * w_d = "\n\nWidth to save: "; - char * w_pos = " (height in cells)"; - char * w_neg = " (negative diff: cells to screen height)"; - char * h_t = h_pos; - char * w_t = w_pos; - if (1 == wcp->height_type) - { - h_t = h_neg; - } - if (1 == wcp->width_type) - { - w_t = w_neg; - } - uint16_t maxl = strlen(title) - + strlen(h_t) + strlen(h_d) + 6 - + strlen(w_t) + strlen(w_d) + 6 + 1; - char text[maxl + 1]; - sprintf(text, "%s%s%d%s%s%d%s", title, h_d, wcp->height, h_t, - w_d, wcp->width, w_t); - add_text_with_linebreaks(win, text); -} diff --git a/src/draw_wins.h b/src/draw_wins.h deleted file mode 100644 index 958d8a9..0000000 --- a/src/draw_wins.h +++ /dev/null @@ -1,30 +0,0 @@ -/* draw_wins.h - * - * Routnes for drawing the game's windows' contents. - */ - -#ifndef DRAW_WINS_H -#define DRAW_WINS_H - -#include /* for uint16_t */ -struct Win; - - - -/* Default routines to draw the various windows' contents. */ -extern void draw_win_log(struct Win * win); -extern void draw_win_map(struct Win * win); -extern void draw_win_info(struct Win * win); -extern void draw_win_inventory(struct Win * win); -extern void draw_win_available_keybindings(struct Win * win); -extern void draw_win_keybindings_global(struct Win * win); -extern void draw_win_keybindings_winconf_geometry(struct Win * win); -extern void draw_win_keybindings_winconf_keybindings(struct Win * win); - -/* Routines to draw windows' configuration views. */ -extern void draw_winconf_keybindings(struct Win * win); -extern void draw_winconf_geometry(struct Win * win); - - - -#endif diff --git a/src/keybindings.c b/src/keybindings.c deleted file mode 100644 index d48bf4e..0000000 --- a/src/keybindings.c +++ /dev/null @@ -1,249 +0,0 @@ -/* keybindings.c */ - -#include "keybindings.h" -#include /* for FILE typedef*/ -#include /* for free(), atoi() */ -#include /* for uint8_t, uint16_t */ -#include /* for keycode defines in get_name_to_keycode() */ -#include /* for strchr(), strlen(), strcmp(), memcpy() */ -#include "windows.h" /* for draw_all_wins() */ -#include "readwrite.h" /* for texfile_sizes(), try_fopen(), try_fclose() - * try_fclose_unlink_rename(), try_fwrite(), try_fgets() - */ -#include "main.h" /* for world global */ -#include "misc.h" /* for try_malloc() */ - - - -/* If "keycode_given" equals "keycode_match", copy "keyname_match" to "keyname" - * and return 1; otherwise return 0. - */ -static uint8_t try_keycode(uint16_t keycode_given, char * keyname, - uint16_t keycode_match, char * keyname_match); - - - -static uint8_t try_keycode(uint16_t keycode_given, char * keyname, - uint16_t keycode_match, char * keyname_match) -{ - if (keycode_given == keycode_match) - { - sprintf(keyname, keyname_match); - return 1; - } - return 0; -} - - - -extern char * get_func_to_keycode(struct KeyBinding * kb_p, uint16_t key) -{ - while (0 != kb_p) - { - if (key == kb_p->key) - { - return kb_p->name; - } - kb_p = kb_p->next; - } - return NULL; -} - - - -extern uint16_t get_keycode_to_action(struct KeyBinding * kb_p, char * name) -{ - while (0 != kb_p) - { - if (0 == strcmp(kb_p->name, name)) - { - return kb_p->key; - } - kb_p = kb_p->next; - } - return 0; -} - - - -extern char * get_name_to_keycode(uint16_t keycode) -{ - char * f_name = "get_name_to_keycode()"; - char * keyname = try_malloc(15, f_name); - if (32 < keycode && keycode < 127) - { - sprintf(keyname, "%c", keycode); - } - else if (keycode >= KEY_F0 && keycode <= KEY_F(63)) - { - uint16_t f = keycode - KEY_F0; - sprintf(keyname, "F%d", f); - } - else if ( try_keycode(keycode, keyname, 9, "TAB") - || try_keycode(keycode, keyname, 10, "RETURN") - || try_keycode(keycode, keyname, 27, "ESCAPE") - || try_keycode(keycode, keyname, 32, "SPACE") - || try_keycode(keycode, keyname, KEY_UP, "UP") - || try_keycode(keycode, keyname, KEY_DOWN, "DOWN") - || try_keycode(keycode, keyname, KEY_LEFT, "LEFT") - || try_keycode(keycode, keyname, KEY_RIGHT, "RIGHT") - || try_keycode(keycode, keyname, KEY_HOME, "HOME") - || try_keycode(keycode, keyname, KEY_BACKSPACE, "BACKSPACE") - || try_keycode(keycode, keyname, KEY_DC, "DELETE") - || try_keycode(keycode, keyname, KEY_IC, "INSERT") - || try_keycode(keycode, keyname, KEY_NPAGE, "NEXT PAGE") - || try_keycode(keycode, keyname, KEY_PPAGE, "PREV PAGE") - || try_keycode(keycode, keyname, KEY_END, "END")) - { - ; - } - else - { - sprintf(keyname, "(unknown)"); - } - return keyname; -} - - - -extern uint16_t get_n_of_keybs(struct KeyBinding * kb_p) -{ - uint16_t i = 0; - while (1) - { - if (0 == kb_p) - { - break; - } - i++; - kb_p = kb_p->next; - } - return i; -} - - - -extern struct KeyBinding * get_keyb_of_n(struct KeyBinding * kb_p, uint16_t n) -{ - uint16_t i = 0; - while (1) - { - if (n == i) - { - break; - } - i++; - kb_p = kb_p->next; - } - return kb_p; -} - - - -extern void init_keybindings(char * path, struct KeyBiData * kbd) -{ - char * f_name = "init_keybindings()"; - FILE * file = try_fopen(path, "r", f_name); - uint16_t lines; - uint16_t linemax = textfile_sizes(file, &lines); - char command[linemax + 1]; - char * cmdptr; - struct KeyBinding ** loc_last_ptr = &kbd->kbs; - * loc_last_ptr = 0; - while (try_fgets(command, linemax + 1, file, f_name)) - { - if ('\n' == command[0] || 0 == command[0]) - { - break; - } - * loc_last_ptr = try_malloc(sizeof(struct KeyBinding), f_name); - struct KeyBinding * kb_p = * loc_last_ptr; - kb_p->next = 0; - kb_p->key = atoi(command); - cmdptr = strchr(command, ' ') + 1; - kb_p->name = try_malloc(strlen(cmdptr), f_name); - memcpy(kb_p->name, cmdptr, strlen(cmdptr) - 1); - kb_p->name[strlen(cmdptr) - 1] = '\0'; - loc_last_ptr = & kb_p->next; - } - try_fclose(file, f_name); - kbd->edit = 0; - kbd->select = 0; -} - - - -extern void save_keybindings(char * path, struct KeyBiData * kbd) -{ - char * f_name = "save_keybindings()"; - char path_tmp[strlen(path) + 4 + 1]; - sprintf(path_tmp, "%s_tmp", path); - FILE * file = try_fopen(path_tmp, "w", f_name); - uint16_t linemax = 0; - struct KeyBinding * kb_p = kbd->kbs; - while (0 != kb_p) - { - if (strlen(kb_p->name) > linemax) - { - linemax = strlen(kb_p->name); - } - kb_p = kb_p->next; - } - linemax = linemax + 6; /* + 6 = + 3 digits + whitespace + \n + \0 */ - char line[linemax]; - kb_p = kbd->kbs; - while (0 != kb_p) - { - snprintf(line, linemax, "%d %s\n", kb_p->key, kb_p->name); - try_fwrite(line, sizeof(char), strlen(line), file, f_name); - kb_p = kb_p->next; - } - - try_fclose_unlink_rename(file, path_tmp, path, f_name); -} - - - -extern void free_keybindings(struct KeyBinding * kb_start) -{ - if (0 == kb_start) - { - return; - } - struct KeyBinding * kb_p = kb_start->next; - if (0 != kb_p) - { - free_keybindings(kb_p); - } - free(kb_start->name); - free(kb_start); -} - - - -extern void mod_selected_keyb(struct KeyBiData * kbd) -{ - kbd->edit = 1; - draw_all_wins(); - int key = getch(); - if (key < 1000) - { - struct KeyBinding * kb_p = get_keyb_of_n(kbd->kbs, kbd->select); - kb_p->key = key; - } - kbd->edit = 0; -} - - - -extern void move_keyb_mod_selection(struct KeyBiData * kbd, char dir) -{ - if ('u' == dir && kbd->select > 0) - { - kbd->select--; - } - else if ('d' == dir && kbd->select < get_n_of_keybs(kbd->kbs) - 1) - { - kbd->select++; - } -} diff --git a/src/keybindings.h b/src/keybindings.h deleted file mode 100644 index 43a5edd..0000000 --- a/src/keybindings.h +++ /dev/null @@ -1,71 +0,0 @@ -/* keybindings.h - * - * Retrieval and storage of keybindings. - */ - -#ifndef KEYBINDINGS_H -#define KEYBINDINGS_H - -#include /* for uint8_t, uint16_t */ - - - -/* Individual keybinding in keybinding chain. */ -struct KeyBinding -{ - struct KeyBinding * next; - uint16_t key; /* keycode */ - char * name; /* name of functionality bound to keycode */ -}; - -/* Wrapper to keybinding chain, contains some keybinding editing metadata. */ -struct KeyBiData -{ - struct KeyBinding * kbs; - uint8_t edit; /* 1 if currently editing a keybinding, else 0 */ - uint16_t select; /* linear list index of keybinding selected for editing */ -}; - - - -/* Return name of action / functionality coupled to keycode; NULL on failure. */ -extern char * get_func_to_keycode(struct KeyBinding * kb_p, uint16_t key); - -/* Return keycode matched by keybinding to command of "name". */ -extern uint16_t get_keycode_to_action(struct KeyBinding * keybindings, - char * name); - -/* Return human-readable name (of maximum 9 chars) for "keycode" as matched by - * ncurses.h; if none is found, return "UNKNOWN". - */ -extern char * get_name_to_keycode(uint16_t keycode); - -/* Return number of keybindings in keybindings chain from "kb_p" on. */ -extern uint16_t get_n_of_keybs(struct KeyBinding * kb_p); - -/* Return "n"-th keybinding in keybindings chain from "kb_p" on. */ -extern struct KeyBinding * get_keyb_of_n(struct KeyBinding * kb_p, uint16_t n); - -/* Initialize/save keybindings data from/to file at "path" to/from keybindings - * data pointer "kbd". - */ -extern void init_keybindings(char * path, struct KeyBiData * kbd); -extern void save_keybindings(char * path, struct KeyBiData * kbd); - -/* Free keybinding chain starting at "kb_start". */ -extern void free_keybindings(struct KeyBinding * kb_start); - -/* Mark keybinding selected for modification as being edited, get user input to - * modify it, then unmark it again. Ensure there are max. three digits in the - * keycode ASCII representation. - */ -extern void mod_selected_keyb(struct KeyBiData * kbd); - -/* Move keybinding modification selection upwards ("dir"=="u") or downwards - * ("dir"=="d") within the limits of the keybindings chain length. - */ -extern void move_keyb_mod_selection(struct KeyBiData * kbd, char dir); - - - -#endif diff --git a/src/main.c b/src/main.c deleted file mode 100644 index c9e468b..0000000 --- a/src/main.c +++ /dev/null @@ -1,240 +0,0 @@ -/* main.c */ - -#include "main.h" /* for world global */ -#include /* for atoi(), exit(), EXIT_FAILURE */ -#include /* for FILE typedef, F_OK */ -#include /* for noecho(), curs_set(), keypad(), raw() */ -#include /* for time() */ -#include /* for getopt(), optarg */ -#include /* for uint32_t */ -#include "windows.h" /* for structs WinMeta, Win, init_win_meta(), - * draw_all_wins() - */ -#include "readwrite.h" /* for try_fgetc(), read_uint32_bigendian(), - * write_uint32_bigendian(), try_fopen(), try_fclose(), - * try_fclose_unlink_rename(), try_fgetc_noeof(), - */ -#include "map_objects.h" /* for structs MapObj, init_map_object_defs(), - * add_map_objects(), get_player() - */ -#include "map.h" /* for struct Map, init_map() */ -#include "misc.h" /* for update_log(), save_game(), try_calloc(), load_game(), - * check_tempfile(), check_files_xor(), load_interface_conf(), - * rrand() - */ -#include "wincontrol.h" /* get_win_by_id(), get_winconf_by_win() */ -#include "rexit.h" /* for exit_game() */ -#include "command_db.h" /* for init_command_db(), is_command_id_shortdsc() */ -#include "control.h" /* for control_by_id(), player_control(), - * get_available_keycode_to_action() - */ -#include "map_object_actions.h" /* for init_map_object_actions() */ - - - -int main(int argc, char *argv[]) -{ - char * f_name = "main()"; - world.turn = 0; /* Turns to 1 when map and objects are initalized. */ - - /* Initialize commands and map object actions. */ - init_command_db(); - set_cleanup_flag(CLEANUP_COMMAND_DB); - init_map_object_actions(); - set_cleanup_flag(CLEANUP_MAP_OBJECT_ACTS); - - /* Check for corrupted savefile / recordfile savings. */ - char * recordfile = "record"; - char * savefile = "savefile"; - char * recordfile_tmp = "record_tmp"; - char * savefile_tmp = "savefile_tmp"; - check_files_xor(savefile, recordfile); - check_tempfile(recordfile_tmp); - check_tempfile(savefile_tmp); - check_tempfile("config/windows/Win_tmp_k"); - check_tempfile("config/windows/Win_tmp_m"); - check_tempfile("config/windows/Win_tmp_i"); - check_tempfile("config/windows/Win_tmp_l"); - check_tempfile("config/windows/toggle_order_tmp"); - - /* Read in startup options (i.e. replay option and replay start turn). */ - int opt; - uint32_t start_turn; - world.interactive = 1; - while ((opt = getopt(argc, argv, "s::")) != -1) - { - switch (opt) - { - case 's': - { - world.interactive = 0; - start_turn = 0; - if (optarg) - { - start_turn = atoi(optarg); - } - break; - } - default: - { - exit(EXIT_FAILURE); - } - } - } - - /* Initialize log and map object definitions. */ - world.score = 0; - world.log = try_calloc(1, sizeof(char), f_name); - set_cleanup_flag(CLEANUP_LOG); - update_log(" "); - init_map_object_defs("config/defs"); - set_cleanup_flag(CLEANUP_MAP_OBJECT_DEFS); - world.map_obj_count = 0; - - /* For interactive mode, try to load world state from savefile. */ - FILE * file; - if (1 == world.interactive && 0 == access(savefile, F_OK)) - { - load_game(); - set_cleanup_flag(CLEANUP_MAP_OBJECTS); - } - - /* For non-interactive mode, try to load world state from record file. */ - else - { - if (0 == world.interactive) - { - file = try_fopen(recordfile, "r", f_name); - world.seed = read_uint32_bigendian(file); - } - - /* For interactive-mode in newly started world, generate a start seed - * from the current time. - */ - else - { - world.seed = time(NULL); - file = try_fopen(recordfile_tmp, "w", f_name); - write_uint32_bigendian(world.seed, file); - try_fclose_unlink_rename(file, recordfile_tmp, recordfile, f_name); - } - world.mapseed = world.seed; - } - - /* Generate map from seed and, if newly generated world, start positions of - * actors. - */ - uint32_t restore_seed = world.seed; - world.seed = world.mapseed; - struct Map map = init_map(); - world.map = ↦ - set_cleanup_flag(CLEANUP_MAP); - if (0 == world.turn) - { - world.map_objs = NULL; - add_map_objects(0, 1); - add_map_objects(1, 1 + rrand() % 27); - add_map_objects(2, 1 + rrand() % 9); - add_map_objects(3, 1 + rrand() % 3); - add_map_objects(4, 1 + rrand() % 3); - add_map_objects(5, 1 + rrand() % 3); - set_cleanup_flag(CLEANUP_MAP_OBJECTS); - world.turn = 1; - } - world.seed = restore_seed; - - /* Initialize window system and windows. */ - init_win_meta(); - set_cleanup_flag(CLEANUP_NCURSES); - noecho(); - curs_set(0); - keypad(world.wmeta->screen, TRUE); - raw(); - load_interface_conf(); - set_cleanup_flag(CLEANUP_INTERFACE); - - /* Focus map on player. */ - struct MapObj * player = get_player(); - struct Win * win_map = get_win_by_id('m'); - win_map->center = player->pos; - - /* Initialize player's inventory selection index to start position. */ - world.inventory_sel = 0; - - /* Replay mode. */ - int key; - struct WinConf * wc; - if (0 == world.interactive) - { - int action = 0; - if (0 != start_turn) - { - while (world.turn != start_turn) - { - action = try_fgetc(file, f_name); - if (EOF == action) - { - break; - } - if ( is_command_id_shortdsc(action, "drop") - || is_command_id_shortdsc(action, "use")) - { - world.inventory_sel = try_fgetc_noeof(file, f_name); - } - player_control_by_id(action); - } - } - while (1) - { - draw_all_wins(); - key = getch(); - wc = get_winconf_by_win(world.wmeta->active); - if ( (1 == wc->view && wingeom_control(key)) - || (2 == wc->view && winkeyb_control(key))) - { - continue; - } - if ( EOF != action - && key == get_available_keycode_to_action("wait")) - { - action = try_fgetc(file, f_name); - if (EOF != action) - { - if ( is_command_id_shortdsc(action, "drop") - || is_command_id_shortdsc(action, "use")) - { - world.inventory_sel = try_fgetc_noeof(file, f_name); - } - player_control_by_id(action); - } - } - else if (meta_control(key)) - { - try_fclose(file, f_name); - exit_game(); - } - } - } - - /* Interactive mode. */ - else - { - while (1) - { - save_game(); - draw_all_wins(); - key = getch(); - wc = get_winconf_by_win(world.wmeta->active); - if ( (1 == wc->view && wingeom_control(key)) - || (2 == wc->view && winkeyb_control(key)) - || (0 != player->lifepoints && player_control_by_key(key))) - { - continue; - } - if (meta_control(key)) - { - exit_game(); - } - } - } -} diff --git a/src/main.h b/src/main.h deleted file mode 100644 index 1ade736..0000000 --- a/src/main.h +++ /dev/null @@ -1,45 +0,0 @@ -/* main.h - * - * Contains the World struct holding all game data together. - */ - -#ifndef MAIN_H -#define MAIN_H - -#include /* for uint32_t*/ -#include "keybindings.h" /* for KeyBiData struct */ -struct WinMeta; -struct WinConf; -struct Map; -struct MapObjDef; -struct MapObj; -struct MapObjAct; - - - -struct World -{ - char interactive; /* 1: playing; 0: record playback. */ - struct KeyBiData kb_global; /* Global keybindings. */ - struct KeyBiData kb_wingeom; /* Window geometry config keybindings. */ - struct KeyBiData kb_winkeys; /* Window keybinding config keybindings.*/ - uint32_t seed; /* Randomness seed. */ - uint32_t mapseed; /* Initial randomness seed used for map.*/ - uint32_t turn; /* Current game turn. */ - uint16_t score; /* Player's score. */ - char * log; /* Pointer to the game log string. */ - struct Map * map; /* Pointer to the game map cells. */ - struct CommandDB * cmd_db; /* Pointer to the command database. */ - struct WinMeta * wmeta; /* Pointer to window manager's WinMeta. */ - struct WinConf * winconfs; /* Pointer to windows' configurations. */ - char * winconf_ids; /* Pointer to string of Winconfs' ids. */ - uint8_t map_obj_count; /* Counts map objects generated so far. */ - struct MapObjDef * map_obj_defs; /* Map object type definitions chain. */ - struct MapObj * map_objs; /* Pointer to map objects chain start. */ - uint8_t inventory_sel; /* Player's inventory selection index. */ - struct MapObjAct * map_obj_acts; /* Pointer to map object actions chain. */ -} world; - - - -#endif diff --git a/src/map.c b/src/map.c deleted file mode 100644 index 3b3f34e..0000000 --- a/src/map.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "map.h" -#include /* for uint8_t, uint16_t, uint32_t */ -#include "misc.h" /* for try_malloc(), center_offset(), rrand() */ -#include "map_objects.h" /* for get_player() */ -#include "yx_uint16.h" /* for yx_uint16, dir enums */ -#include "windows.h" /* for struct Win */ -#include "main.h" /* for world global */ -#include "wincontrol.h" /* for get_win_by_id() */ - - - -extern struct Map init_map() -{ - char * f_name = "init_map()"; - struct Map map; - map.size.x = 64; - map.size.y = 64; - uint32_t size = map.size.x * map.size.y; - map.cells = try_malloc(size, f_name); - uint16_t y, x; - for (y = 0; y < map.size.y; y++) - { - for (x = 0; x < map.size.x; map.cells[(y * map.size.x) + x] = '~', x++); - } - map.cells[size / 2 + (map.size.x / 2)] = '.'; - uint32_t curpos; - while (1) - { - y = rrand() % map.size.y; - x = rrand() % map.size.x; - curpos = y * map.size.x + x; - if ('~' == map.cells[curpos] - && ((curpos >= map.size.x && '.' == map.cells[curpos - map.size.x]) - || (curpos < map.size.x * (map.size.y-1) - && '.' == map.cells[curpos + map.size.x]) - || (curpos > 0 && curpos % map.size.x != 0 - && '.' == map.cells[curpos-1]) - || (curpos < (map.size.x * map.size.y) - && (curpos+1) % map.size.x != 0 - && '.' == map.cells[curpos+1]))) - { - if (y == 0 || y == map.size.y - 1 || x == 0 || x == map.size.x - 1) - { - break; - } - map.cells[y * map.size.x + x] = '.'; - } - } - return map; -} - - - -extern void map_scroll(char d) -{ - struct Win * win = get_win_by_id('m'); - uint16_t offset; - if (('N' == d || 'S' == d) && world.map->size.y > win->framesize.y) - { - offset = center_offset(win->center.y, - world.map->size.y, win->framesize.y); - win->center.y = offset + (win->framesize.y / 2); - if ('S' == d && win->center.y < world.map->size.y - 1) - { - win->center.y++; - return; - } - win->center.y = win->center.y - ('N' == d && win->center.y > 0); - } - else if (('W' == d || 'E' == d) && world.map->size.x > win->framesize.x) - { - offset = center_offset(win->center.x, - world.map->size.x, win->framesize.x); - win->center.x = offset + (win->framesize.x / 2); - if ('E' == d && win->center.x < world.map->size.x - 1) - { - win->center.x++; - return; - } - win->center.x = win->center.x - ('W' == d && win->center.x > 0); - } -} - - - -extern void map_center() -{ - struct MapObj * player = get_player(); - struct Win * win_map = get_win_by_id('m'); - win_map->center = player->pos; -} - - - -extern uint8_t is_passable(struct Map * map, struct yx_uint16 pos) -{ - uint8_t passable = 0; - if (0 <= pos.x && pos.x < map->size.x && 0 <= pos.y && pos.y < map->size.y) - { - passable = (('.' == map->cells[pos.y * map->size.x + pos.x])); - } - return passable; -} diff --git a/src/map.h b/src/map.h deleted file mode 100644 index 8fb21ee..0000000 --- a/src/map.h +++ /dev/null @@ -1,44 +0,0 @@ -/* map.h - * - * Struct for the game map and routines to create and scroll on it. - */ - -#ifndef MAP_H -#define MAP_H - -#include "yx_uint16.h" /* for yx_uint16 and dir enums */ -struct Win; - - - -struct Map -{ - struct yx_uint16 size; /* map's height/width in number of cells */ - char * cells; /* sequence of bytes encoding map cells */ -}; - - - -/* Initialize island map as 64 x 64 "~" cells representing water and "." cells - * representing land. The shape of the island is generated randomly by starting - * with a sea containing one land cell in the middle and then going into a cycle - * of repeatedly selecting a random cell on the map and transforming it into a - * land cell if it is horizontally or vertically neighbor to one; the cycle ends - * when a land cell is due to be created right at the border of the map. - */ -extern struct Map init_map(); - -/* Try changing map window's focus into directino "d" (north = "N" etc.). */ -extern void map_scroll(char d); - -/* Center map window on player. */ -extern void map_center(); - -/* Check if coordinate pos on (or beyond) map is accessible to map object - * movement. - */ -extern uint8_t is_passable(struct Map * map, struct yx_uint16 pos); - - - -#endif diff --git a/src/map_object_actions.c b/src/map_object_actions.c deleted file mode 100644 index 5ba99c0..0000000 --- a/src/map_object_actions.c +++ /dev/null @@ -1,354 +0,0 @@ -/* map_object_actions.c */ - -#include "map_object_actions.h" -#include /* for uint8_t, uint16_t */ -#include /* for strlen(), strcmp() */ -#include "yx_uint16.h" /* for yx_uint16 struct, mv_yx_in_dir(), - * yx_uint16_cmp() - */ -#include "map_objects.h" /* for MapObj, MapObjDef structs, get_player(), - * set_object_position(), own_map_object() - */ -#include "misc.h" /* for update_log(), try_malloc() */ -#include "map.h" /* for is_passable() */ -#include "main.h" /* for world global */ -#include "readwrite.h" /* for try_fopen(), try_fclose(), textfile_sizes() */ -#include "rexit.h" /* for exit_err() */ - - - -/* If "name" fits "moa"->name, set "moa"->func to "func". */ -static uint8_t try_func_name(struct MapObjAct * moa, - char * name, void (* func) (struct MapObj *)); - -/* One actor "wounds" another actor, decrementing his lifepoints and, if they - * reach zero in the process, killing it. Generates appropriate log message. - */ -static void actor_hits_actor(struct MapObj * hitter, struct MapObj * hitted); - -/* Bonus stuff to actor_*() to happen if actor==player. Mostly writing of log - * messages; _pick and _drop also decrement world.inventory_sel by 1 if >0. - */ -static void playerbonus_wait(); -static void playerbonus_move(char d, uint8_t passable); -static void playerbonus_drop(uint8_t owns_none); -static void playerbonus_pick(uint8_t picked); -static void playerbonus_use(uint8_t no_object, uint8_t wrong_object); - - - -static uint8_t try_func_name(struct MapObjAct * moa, - char * name, void (* func) (struct MapObj *)) -{ - if (0 == strcmp(moa->name, name)) - { - moa->func = func; - return 1; - } - return 0; -} - - - -static void actor_hits_actor(struct MapObj * hitter, struct MapObj * hitted) -{ - struct MapObjDef * mod_hitter = get_map_object_def(hitter->type); - struct MapObjDef * mod_hitted = get_map_object_def(hitted->type); - struct MapObj * player = get_player(); - char * msg1 = "You"; - char * msg2 = "wound"; - char * msg3 = "you"; - if (player != hitter) - { - msg1 = mod_hitter->name; - msg2 = "wounds"; - } - if (player != hitted) - { - msg3 = mod_hitted->name; - } - uint8_t len = 1 + strlen(msg1) + 1 + strlen(msg2) + 1 + strlen(msg3) + 2; - char msg[len]; - sprintf(msg, "\n%s %s %s.", msg1, msg2, msg3); - update_log(msg); - hitted->lifepoints--; - if (0 == hitted->lifepoints) - { - hitted->type = mod_hitted->corpse_id; - if (player == hitted) - { - update_log(" You die."); - return; - } - update_log(" It dies."); - if (player == hitter) - { - world.score = world.score + mod_hitted->lifepoints; - } - } -} - - - -static void playerbonus_wait() -{ - update_log("\nYou wait."); -} - - - -static void playerbonus_move(char d, uint8_t passable) -{ - char * dsc_dir = "north"; - if ('E' == d) - { - dsc_dir = "east" ; - } - else if ('S' == d) - { - dsc_dir = "south"; - } - else if ('W' == d) - { - dsc_dir = "west" ; - } - char * dsc_move = "You move "; - if (0 == passable) - { - dsc_move = "You fail to move "; - } - char msg[strlen(dsc_move) + strlen (dsc_dir) + 3]; - sprintf(msg, "\n%s%s.", dsc_move, dsc_dir); - update_log(msg); -} - - - -static void playerbonus_drop(uint8_t owns_none) -{ - if (0 != owns_none) - { - update_log("\nYou try to drop an object, but you own none."); - return; - } - update_log("\nYou drop an object."); - world.inventory_sel = world.inventory_sel - (0 < world.inventory_sel); -} - - - -static void playerbonus_pick(uint8_t picked) -{ - if (picked) - { - update_log("\nYou pick up an object."); - return; - } - update_log("\nYou try to pick up an object, but there is none."); -} - - - -static void playerbonus_use(uint8_t no_object, uint8_t wrong_object) -{ - if (no_object) - { - update_log("\nYou try to use an object, but you own none."); - return; - } - else if (wrong_object) - { - update_log("\nYou try to use this object, but fail."); - return; - } - update_log("\nYou consume MAGIC MEAT."); - world.inventory_sel = world.inventory_sel - (0 < world.inventory_sel); -} - - - -extern void init_map_object_actions() -{ - char * f_name = "init_map_object_actions()"; - char * path = "config/map_object_actions"; - FILE * file = try_fopen(path, "r", f_name); - uint16_t linemax = textfile_sizes(file, NULL); - char line[linemax + 1]; - struct MapObjAct ** moa_ptr_ptr = &world.map_obj_acts; - char * delim = " "; - while (fgets(line, linemax + 1, file)) - { - if ('\n' == line[0] || 0 == line[0]) - { - break; - } - struct MapObjAct * moa = try_malloc(sizeof(struct MapObjAct), f_name); - moa->id = atoi(strtok(line, delim)); - moa->effort = atoi(strtok(NULL, delim)); - char * funcname = strtok(NULL, "\n"); - uint8_t len_name = strlen(funcname) + 1; - moa->name = try_malloc(len_name, f_name); - memcpy(moa->name, funcname, len_name); - if (!( try_func_name(moa, "move", actor_move) - || try_func_name(moa, "pick_up", actor_pick) - || try_func_name(moa, "drop", actor_drop) - || try_func_name(moa, "use", actor_use))) - { - moa->func = actor_wait; - } - moa->next = NULL; - * moa_ptr_ptr = moa; - moa_ptr_ptr = &moa->next; - } - try_fclose(file, f_name); -} - - - -extern void free_map_object_actions(struct MapObjAct * moa) -{ - if (NULL == moa) - { - return; - } - free(moa->name); - free_map_object_actions(moa->next); - free(moa); -} - - - -extern uint8_t get_moa_id_by_name(char * name) -{ - struct MapObjAct * moa = world.map_obj_acts; - while (NULL != moa) - { - if (0 == strcmp(moa->name, name)) - { - break; - } - moa = moa->next; - } - exit_err(NULL == moa, "get_moa_id_name() did not find map object action."); - return moa->id; -} - - - -extern void actor_wait(struct MapObj * mo) -{ - if (mo == get_player()) - { - playerbonus_wait(); - } -} - - - -extern void actor_move(struct MapObj * mo) -{ - char d = mo->arg; - struct yx_uint16 target = mv_yx_in_dir(d, mo->pos); - struct MapObj * other_mo; - for (other_mo = world.map_objs; other_mo != 0; other_mo = other_mo->next) - { - if (0 == other_mo->lifepoints || other_mo == mo) - { - continue; - } - if (yx_uint16_cmp(&target, &other_mo->pos)) - { - actor_hits_actor(mo, other_mo); - return; - } - } - uint8_t passable = is_passable(world.map, target); - if (passable) - { - set_object_position(mo, target); - } - if (mo == get_player()) - { - playerbonus_move(d, passable); - } -} - - - -extern void actor_drop(struct MapObj * mo) -{ - uint8_t owns_none = (NULL == mo->owns); - if (!owns_none) - { - uint8_t select = mo->arg; - struct MapObj * owned = mo->owns; - uint8_t i = 0; - for (; i != select; i++, owned = owned->next); - own_map_object(&world.map_objs, &mo->owns, owned->id); - } - if (mo == get_player()) - { - playerbonus_drop(owns_none); - } -} - - - -extern void actor_pick(struct MapObj * mo) -{ - struct MapObj * picked; - for (picked = world.map_objs; NULL != picked; picked = picked->next) - { - if (picked != mo && yx_uint16_cmp(&picked->pos, &mo->pos)) - { - break; - } - } - if (NULL != picked) - { - own_map_object(&mo->owns, &world.map_objs, picked->id); - set_object_position(picked, mo->pos); - } - if (mo == get_player()) - { - playerbonus_pick(NULL != picked); - } -} - - - -extern void actor_use(struct MapObj * mo) -{ - uint8_t wrong_object = 1; - uint8_t no_object = (NULL == mo->owns); - if (!no_object) - { - uint8_t select = mo->arg; - uint8_t i = 0; - struct MapObj * selected = mo->owns; - for (; i != select; i++, selected = selected->next); - struct MapObjDef * mod = get_map_object_def(selected->type); - if (!strcmp("MAGIC MEAT", mod->name)) - { - wrong_object = 0; - struct MapObj * next = selected->next; - free(selected); - if (0 < select) - { - select--; - selected = mo->owns; - for (i = 0; i != select; i++, selected = selected->next); - selected->next = next; - } - else - { - mo->owns = next; - } - mo->lifepoints++; - } - } - if (mo == get_player()) - { - playerbonus_use(no_object, wrong_object); - } -} diff --git a/src/map_object_actions.h b/src/map_object_actions.h deleted file mode 100644 index 0d594d3..0000000 --- a/src/map_object_actions.h +++ /dev/null @@ -1,59 +0,0 @@ -/* map_object_actions.h - * - * Actions that can be performed my map objects / "actors". Note that apart - * from the consequences described below, each action may also trigger log - * messages and other minor stuff if the actor is equal to the player. - */ - -#ifndef MAP_OBJECT_ACTIONS_H -#define MAP_OBJECT_ACTIONS_H - -#include /* for uint8_t */ -struct MapObj; - - - -struct MapObjAct -{ - struct MapObjAct * next; - uint8_t id; /* unique id of map object action */ - char * name; /* human-readable identifier */ - uint8_t effort; /* how many turns the action takes */ - void (* func) (struct MapObj *); /* function called after .effort turns */ -}; - - - -/* Init MapObjAct chain at world.map_obj_acts from config/map_object_actions. */ -extern void init_map_object_actions(); - -/* Free MapObjAct * chain starting at "moa". */ -extern void free_map_object_actions(struct MapObjAct * moa); - -/* Return world.map_obj_acts MapObjAct.id for "name". */ -extern uint8_t get_moa_id_by_name(char * name); - -/* Actor "mo" does nothing. */ -extern void actor_wait(struct MapObj * mo); - -/* Actor "mo" tries to move one step in direction described by char mo->arg - * (where east is 'E', north 'N') etc. Move either succeeds, or another actor is - * encountered and hit (which leads ot its lifepoint decreasing by one and - * eventually death), or the move fails due to an impassable target square. - */ -extern void actor_move(struct MapObj * mo); - -/* Actor "mo" tries to drop from inventory object indexed by number mo->args. */ -extern void actor_drop(struct MapObj * mo); - -/* Actor "mo" tries to pick up object from ground into its inventory. */ -extern void actor_pick(struct MapObj * mo); - -/* Actor "mo" tries to use inventory object indexed by number mo->args. - * (Currently the only valid use is consuming "MAGIC MEAT".) - */ -extern void actor_use(struct MapObj * mo); - - - -#endif diff --git a/src/map_objects.c b/src/map_objects.c deleted file mode 100644 index 1fb4e68..0000000 --- a/src/map_objects.c +++ /dev/null @@ -1,284 +0,0 @@ -/* map_objects.c */ - -#include "map_objects.h" -#include /* for free(), atoi() */ -#include /* for uint8_t, uint16_t */ -#include /* for FILE typedef */ -#include /* for strlen(), memcpy(), strtok() */ -#include "readwrite.h" /* for textfile_sizes(), try_fopen(), try_fclose(), - * try_fgets() - */ -#include "misc.h" /* for try_malloc(), find_passable_pos() */ -#include "main.h" /* for world global */ -#include "rexit.h" /* for exit_err() */ -#include "yx_uint16.h" /* for yx_uint16 struct, yx_uint16_cmp() */ - - - -/* Write representation of "mo" and all of the map objects it owns to "file". */ -static void write_map_object(FILE * file, struct MapObj * mo); - -/* Return pointer to map object of "id" in chain starting at "ptr". */ -static struct MapObj * get_map_object(struct MapObj * ptr, uint8_t id); - - - -static void write_map_object(FILE * file, struct MapObj * mo) -{ - char * f_name = "write_map_object()"; - struct MapObj * mo_ptr = mo->owns; - uint8_t i = 0; - for (; NULL != mo_ptr; mo_ptr = mo_ptr->next, i++); - uint8_t size = 3+1 + 3+1 + 3+1 + 5+1 + 5 + ((1+3)*i) + 1 + 1; - char line[size]; - sprintf(line, "%d %d %d %d %d %d %d %d", - mo->id, mo->type, mo->lifepoints, mo->pos.y, mo->pos.x, - mo->command, mo->arg, mo->progress); - for (mo_ptr = mo->owns; NULL != mo_ptr; mo_ptr = mo_ptr->next) - { - sprintf(line + strlen(line), " %d", mo_ptr->id); - } - line[strlen(line) + 1] = '\0'; - line[strlen(line)] = '\n'; - try_fwrite(line, strlen(line), 1, file, f_name); - for (mo_ptr = mo->owns; NULL != mo_ptr; mo_ptr = mo_ptr->next) - { - write_map_object(file, mo_ptr); - } -} - - - -static struct MapObj * get_map_object(struct MapObj * ptr, uint8_t id) -{ - while (1) - { - if (NULL == ptr || id == ptr->id) - { - return ptr; - } - struct MapObj * owned_object = get_map_object(ptr->owns, id); - if (NULL != owned_object) - { - return ptr; - } - ptr = ptr->next; - } -} - - - -extern void init_map_object_defs(char * filename) -{ - char * f_name = "init_map_object_defs()"; - FILE * file = try_fopen(filename, "r", f_name); - uint16_t linemax = textfile_sizes(file, NULL); - struct MapObjDef ** last_mod_ptr_ptr = &world.map_obj_defs; - char * delim = " "; - char line[linemax + 1]; - while (try_fgets(line, linemax + 1, file, f_name)) - { - struct MapObjDef * mod = try_malloc(sizeof(struct MapObjDef), f_name); - mod->next = NULL; - mod->id = atoi(strtok(line, delim)); - mod->corpse_id = atoi(strtok(NULL, delim)); - mod->char_on_map = * strtok(NULL, delim); - mod->lifepoints = atoi(strtok(NULL, delim)); - char * name = strtok(NULL, "\n"); - mod->name = try_malloc(strlen(name) + 1, f_name); - memcpy(mod->name, name, strlen(name) + 1); - * last_mod_ptr_ptr = mod; - last_mod_ptr_ptr = &mod->next; - } - try_fclose(file, f_name); -} - - - -extern void free_map_object_defs(struct MapObjDef * mod_start) -{ - if (NULL == mod_start) - { - return; - } - free_map_object_defs(mod_start->next); - free(mod_start->name); - free(mod_start); -} - - - -extern void write_map_objects(FILE * file) -{ - struct MapObj * mo = world.map_objs; - while (NULL != mo) - { - write_map_object(file, mo); - mo = mo->next; - } -} - - - -extern void read_map_objects(FILE * file, char * line, int linemax) -{ - char * f_name = "read_map_objects()"; - struct MapObj ** mo_ptr_ptr = &world.map_objs; - char * delim = " "; - fpos_t pos; - exit_err(-1 == fgetpos(file, &pos), f_name); - while (try_fgets(line, linemax + 1, file, f_name)) - { - struct MapObj * mo = try_malloc(sizeof(struct MapObj), f_name); - mo->next = NULL; - mo->id = atoi(strtok(line, delim)); - mo->type = atoi(strtok(NULL, delim)); - mo->lifepoints = atoi(strtok(NULL, delim)); - mo->pos.y = atoi(strtok(NULL, delim)); - mo->pos.x = atoi(strtok(NULL, delim)); - mo->command = atoi(strtok(NULL, delim));; - mo->arg = atoi(strtok(NULL, delim));; - mo->progress = atoi(strtok(NULL, delim));; - mo->owns = NULL; - * mo_ptr_ptr = mo; - mo_ptr_ptr = &mo->next; - } - exit_err(-1 == fsetpos(file, &pos), f_name); - while (try_fgets(line, linemax + 1, file, f_name)) - { - uint8_t id = atoi(strtok(line, delim)); - uint8_t i; - for (i = 0; i < 7; i++, strtok(NULL, delim)); - char * owned = strtok(NULL, "\n"); - if (NULL != owned) - { - struct MapObj * mo = get_map_object(world.map_objs, id); - char * owned_id = ""; - owned_id = strtok(owned, delim); - while (NULL != owned_id) - { - own_map_object(&mo->owns, &world.map_objs, atoi(owned_id)); - owned_id = strtok(NULL, delim); - } - } - } -} - - - -extern void add_map_object(uint8_t type) -{ - char * f_name = "add_map_object()"; - struct MapObjDef * mod = get_map_object_def(type); - struct MapObj * mo = try_malloc(sizeof(struct MapObj), f_name); - mo->id = world.map_obj_count++; - mo->type = mod->id; - mo->lifepoints = mod->lifepoints; - while (1) - { - struct yx_uint16 pos = find_passable_pos(world.map); - struct MapObj * mo_ptr; - uint8_t clear = 1; - for (mo_ptr = world.map_objs; mo_ptr != NULL; mo_ptr = mo_ptr->next) - { - if (yx_uint16_cmp(&pos, &mo_ptr->pos) && 0 != mo_ptr->lifepoints) - { - clear = 0; - break; - } - } - if (1 == clear) - { - mo->pos = pos; - break; - } - } - mo->progress = 0; - mo->command = 0; - mo->arg = 0; - mo->owns = NULL; - mo->next = NULL; - struct MapObj ** mo_ptr_ptr = &world.map_objs; - for (; NULL != * mo_ptr_ptr; mo_ptr_ptr = &(*mo_ptr_ptr)->next); - * mo_ptr_ptr = mo; -} - - - -extern void add_map_objects(uint8_t type, uint8_t n) -{ - uint8_t i; - for (i = 0; i < n; i++) - { - add_map_object(type); - } -} - - - -extern void free_map_objects(struct MapObj * mo_start) -{ - if (NULL == mo_start) - { - return; - } - free_map_objects(mo_start->owns); - free_map_objects(mo_start->next); - free(mo_start); -} - - - -extern void own_map_object(struct MapObj ** target, struct MapObj ** source, - uint8_t id) -{ - struct MapObj * mo; - if (id == (*source)->id) - { - mo = * source; - * source = mo->next; - } - else - { - struct MapObj * penult = * source; - while (1) - { - if (id == penult->next->id) - { - break; - } - penult = penult->next; - } - mo = penult->next; - penult->next = mo->next; - } - struct MapObj ** mo_ptr_ptr = target; - for (; NULL != * mo_ptr_ptr; mo_ptr_ptr = &(*mo_ptr_ptr)->next); - * mo_ptr_ptr = mo; - mo->next = NULL; -} - - - -extern struct MapObj * get_player() -{ - return get_map_object(world.map_objs, 0); -} - - - -extern struct MapObjDef * get_map_object_def(uint8_t id) -{ - struct MapObjDef * mod = world.map_obj_defs; - for (; id != mod->id; mod = mod->next); - return mod; -} - - - -extern void set_object_position(struct MapObj * mo, struct yx_uint16 pos) -{ - mo->pos = pos; - struct MapObj * owned = mo->owns; - for (; owned != NULL; set_object_position(owned, pos), owned = owned->next); -} diff --git a/src/map_objects.h b/src/map_objects.h deleted file mode 100644 index fd7a819..0000000 --- a/src/map_objects.h +++ /dev/null @@ -1,79 +0,0 @@ -/* map_objects.h - * - * Structs for objects on the map and their type definitions, and routines to - * initialize these and load and save them from/to files. - */ - -#ifndef MAP_OBJECTS_H -#define MAP_OBJECTS_H - -#include /* for FILE typedef */ -#include /* for uint8_t */ -#include "yx_uint16.h" /* for yx_uint16 coordinates */ - - - -struct MapObj -{ - struct MapObj * next; /* pointer to next one in map object chain */ - struct MapObj * owns; /* chain of map objects owned / in inventory */ - uint8_t id; /* individual map object's unique identifier */ - uint8_t type; /* ID of appropriate map object definition */ - uint8_t lifepoints; /* 0: object is inanimate; >0: hitpoints */ - struct yx_uint16 pos; /* coordinate on map */ - uint8_t command; /* map object's current action */ - uint8_t arg; /* optional field for .command argument */ - uint8_t progress; /* turns already passed to realize .command */ -}; - -struct MapObjDef -{ - struct MapObjDef * next; - uint8_t id; /* map object definition identifier / sets .type */ - uint8_t corpse_id; /* type to change map object into upon destruction */ - char char_on_map; /* map object symbol to appear on map */ - char * name; /* string to describe object in game log */ - uint8_t lifepoints; /* default start value for map object's .lifepoints */ -}; - - - -/* Initialize map object definitions chain from file at path "filename". */ -extern void init_map_object_defs(char * filename); - -/* Free map object definitions chain starting at "mod_start". */ -extern void free_map_object_defs(struct MapObjDef * mod_start); - -/* Write map objects chain to "file". */ -extern void write_map_objects(FILE * file); - -/* Read map objects chain from "file"; use "line" as char array for fgets() and - * expect line strings of max. "linemax" length to be read by it. - */ -extern void read_map_objects(FILE * file, char * line, int linemax); - -/* Add object(s) ("n": how many?) of "type" to map on random position(s). New - * animate objects are never placed in the same square with other animate ones. - */ -extern void add_map_object(uint8_t type); -extern void add_map_objects(uint8_t type, uint8_t n); - -/* Free map objects in map object chain starting at "mo_start. */ -extern void free_map_objects(struct MapObj * mo_start); - -/* Move object of "id" from "source" inventory to "target" inventory. */ -extern void own_map_object(struct MapObj ** target, struct MapObj ** source, - uint8_t id); - -/* Get pointer to the MapObj struct that represents the player. */ -extern struct MapObj * get_player(); - -/* Get pointer to the map object definition of identifier "def_id". */ -extern struct MapObjDef * get_map_object_def(uint8_t id); - -/* Move not only "mo" to "pos", but also all map objects owned by it. */ -extern void set_object_position(struct MapObj * mo, struct yx_uint16 pos); - - - -#endif diff --git a/src/misc.c b/src/misc.c deleted file mode 100644 index e6f0df1..0000000 --- a/src/misc.c +++ /dev/null @@ -1,334 +0,0 @@ -/* misc.c */ - -#include "misc.h" -#include /* for errno */ -#include /* for unlink(), acess() */ -#include /* for size_t, calloc(), free() */ -#include /* for strlen(), strcmp(), memcpy() */ -#include /* for uint8_t, uint16_t */ -#include "readwrite.h" /* for try_fopen(), try_fclose(), textfile_sizes(), - * try_fputc(), try_fgetc() - */ -#include "map_objects.h" /* for struct MapObj, get_player(), read_map_objects(), - * write_map_objects() - */ -#include "map_object_actions.h" /* for struct MapObjAct */ -#include "ai.h" /* for pretty_dumb_ai() */ -#include "map.h" /* for Map struct, is_passable() */ -#include "main.h" /* for world global */ -#include "yx_uint16.h" /* for yx_uint16 struct */ -#include "rexit.h" /* for exit_err(), exit_trouble() */ -#include "wincontrol.h" /* for init_winconfs(), init_wins(), free_winconfs(), - * sorted_wintoggle_and_activate() - */ -#include "windows.h" /* for suspend_win() */ -#include "command_db.h" /* for is_command_id_shortdsc() */ - - - -extern uint16_t rrand() -{ /* Constants as recommended by POSIX.1-2001 (see man page rand(3)). */ - world.seed = ((world.seed * 1103515245) + 12345) % 4294967296; - return (world.seed >> 16); /* Ignore less random least significant bits. */ -} - - - -extern void * try_malloc(size_t size, char * f) -{ - void * p = malloc(size); - exit_trouble(NULL == p, f, "malloc()"); - return p; -} - - - -extern void * try_calloc(size_t size1, size_t size2, char * f) -{ - void * p = calloc(size1, size2); - exit_trouble(NULL == p, f, "calloc()"); - return p; -} - - - -extern void check_files_xor(char * p1, char * p2) -{ - char * msg1 = "A file '"; - char * msg2 = "' exists, but no file '"; - char * msg3 = "'. If everything was in order, both or noe would exist. " - "The game won't start until this is corrected."; - uint16_t size = strlen(msg1) + strlen(p1) + strlen(msg2) + strlen(p2) - + strlen(msg3); - char msg[size]; - if (!access(p1, F_OK) && access(p2, F_OK)) - { - sprintf(msg, "%s%s%s%s%s", msg1, p1, msg2, p2, msg3); - errno = 0; - exit_err(1, msg); - } - else if (access(p1, F_OK) && !access(p2, F_OK)) - { - sprintf(msg, "%s%s%s%s%s", msg1, p2, msg2, p1, msg3); - errno = 0; - exit_err(1, msg); - } -} - - - -extern void check_tempfile(char * path) -{ - char * msg1 = "A file '"; - char * msg2 = "' exists, probably from a corrupted previous file saving " - "process. To avoid corruption of game files, the game won't " - "start until it is removed or renamed."; - uint16_t size = strlen(msg1) + strlen(path) + strlen(msg2); - char msg[size]; - sprintf(msg, "%s%s%s", msg1, path, msg2); - exit_err(!access(path, F_OK), msg); -} - - - -extern void save_interface_conf() -{ - save_keybindings("config/keybindings_global", &world.kb_global); - save_keybindings("config/keybindings_wingeom", &world.kb_wingeom); - save_keybindings("config/keybindings_winkeys", &world.kb_winkeys); - save_win_configs(); -} - - - -extern void load_interface_conf() -{ - init_keybindings("config/keybindings_global", &world.kb_global); - init_keybindings("config/keybindings_wingeom", &world.kb_wingeom); - init_keybindings("config/keybindings_winkeys", &world.kb_winkeys); - init_winconfs(); - init_wins(); - sorted_wintoggle_and_activate(); -} - - - -extern void unload_interface_conf() -{ - free_keybindings(world.kb_global.kbs); - free_keybindings(world.kb_wingeom.kbs); - free_keybindings(world.kb_winkeys.kbs); - while (0 != world.wmeta->active) - { - suspend_win(world.wmeta->active); - } - free_winconfs(); -} - - - -extern void reload_interface_conf() -{ - unload_interface_conf(); - load_interface_conf(); -} - - - -extern void update_log(char * text) -{ - char * f_name = "update_log()"; - static char * last_msg; /* TODO: valgrind is dissatisfied */ - if (0 == last_msg) /* with this calloc'd pointer not */ - { /* being freed. Rectify this? */ - last_msg = try_calloc(1, sizeof(char), f_name); - } - char * new_text; - uint16_t len_old = strlen(world.log); - if (0 == strcmp(last_msg, text)) - { - uint16_t len_whole = len_old + 1; - new_text = try_calloc(len_whole + 1, sizeof(char), f_name); - memcpy(new_text, world.log, len_old); - memcpy(new_text + len_old, ".", 1); - } - else - { - uint16_t len_new = strlen(text); - uint16_t len_whole = len_old + len_new + 1; - new_text = try_calloc(len_whole, sizeof(char), f_name); - memcpy(new_text, world.log, len_old); - memcpy(new_text + len_old, text, len_new); - last_msg = try_calloc(len_new + 1, sizeof(char), f_name); - memcpy(last_msg, text, len_new); - } - free(world.log); - world.log = new_text; -} - - - -extern uint16_t center_offset(uint16_t position, uint16_t mapsize, - uint16_t framesize) -{ - uint16_t offset = 0; - if (mapsize > framesize) - { - if (position > framesize / 2) - { - if (position < mapsize - (framesize / 2)) - { - offset = position - (framesize / 2); - } - else - { - offset = mapsize - framesize; - } - } - } - return offset; -} - - - -extern void turn_over(char action) -{ - char * f_name = "turn_over()"; - - char * recordfile_tmp = "record_tmp"; - char * recordfile = "record"; - if (1 == world.interactive) - { - FILE * file_old = try_fopen(recordfile, "r", f_name); - FILE * file_new = try_fopen(recordfile_tmp, "w", f_name); - int c = try_fgetc(file_old, f_name); - while (EOF != c) - { - try_fputc((uint8_t) c, file_new, f_name); - c = try_fgetc(file_old, f_name); - } - try_fclose(file_old, f_name); - try_fputc(action, file_new, f_name); - if ( is_command_id_shortdsc(action, "drop") - || is_command_id_shortdsc(action, "use")) - { - try_fputc(world.inventory_sel, file_new, f_name); - } - try_fclose_unlink_rename(file_new, recordfile_tmp, recordfile, f_name); - } - - struct MapObj * player = get_player(); - struct MapObj * map_object = player; - uint8_t first_round = 1; - while (0 < player->lifepoints) - { - if (NULL == map_object) - { - world.turn++; - map_object = world.map_objs; - } - if (0 < map_object->lifepoints) /* map_object is animate. */ - { - if (0 == first_round && 0 == map_object->progress) - { - if (map_object == player) - { - break; - } - pretty_dumb_ai(map_object); - } - first_round = 0; - map_object->progress++; - struct MapObjAct * moa = world.map_obj_acts; - while (moa->id != map_object->command) - { - moa = moa->next; - } - if (map_object->progress == moa->effort) - { - moa->func(map_object); - map_object->progress = 0; - } - } - map_object = map_object->next; - } -} - - - -extern void save_game() -{ - char * f_name = "save_game()"; - char * savefile_tmp = "savefile_tmp"; - char * savefile = "savefile"; - FILE * file = try_fopen(savefile_tmp, "w", f_name); - char line[12]; - sprintf(line, "%u\n", world.mapseed); - try_fwrite(line, strlen(line), 1, file, f_name); - sprintf(line, "%u\n", world.seed); - try_fwrite(line, strlen(line), 1, file, f_name); - sprintf(line, "%u\n", world.map_obj_count); - try_fwrite(line, strlen(line), 1, file, f_name); - sprintf(line, "%u\n", world.turn); - try_fwrite(line, strlen(line), 1, file, f_name); - sprintf(line, "%u\n", world.score); - try_fwrite(line, strlen(line), 1, file, f_name); - write_map_objects(file); - try_fclose_unlink_rename(file, savefile_tmp, savefile, f_name); -} - - - -extern void load_game() -{ - char * f_name = "load_game2()"; - char * filename = "savefile"; - FILE * file = try_fopen(filename, "r", f_name); - uint16_t linemax = textfile_sizes(file, NULL); - char line[linemax + 1]; - try_fgets(line, linemax + 1, file, f_name); - world.mapseed = atoi(line); - try_fgets(line, linemax + 1, file, f_name); - world.seed = atoi(line); - try_fgets(line, linemax + 1, file, f_name); - world.map_obj_count = atoi(line); - try_fgets(line, linemax + 1, file, f_name); - world.turn = atoi(line); - try_fgets(line, linemax + 1, file, f_name); - world.score = atoi(line); - read_map_objects(file, line, linemax); - try_fclose(file, f_name); -} - - - -extern struct yx_uint16 find_passable_pos(struct Map * map) -{ - struct yx_uint16 pos; - for (pos.y = pos.x = 0; 0 == is_passable(map, pos);) - { - pos.y = rrand() % map->size.y; - pos.x = rrand() % map->size.x; - } - return pos; -} - - - -extern void nav_inventory(char dir) -{ - if ('u' == dir) - { - world.inventory_sel = world.inventory_sel - (world.inventory_sel > 0); - return; - } - struct MapObj * player = get_player(); - struct MapObj * owned = player->owns; - if (NULL == owned) - { - return; - } - uint8_t n_owned = 0; - for (; NULL != owned->next; owned = owned->next, n_owned++); - world.inventory_sel = world.inventory_sel + (world.inventory_sel < n_owned); -} diff --git a/src/misc.h b/src/misc.h deleted file mode 100644 index 80bb1cd..0000000 --- a/src/misc.h +++ /dev/null @@ -1,61 +0,0 @@ -/* misc.h - * - * Miscellaneous routines that have not yet found a proper parent module. Having - * LOTS of stuff in here is a sure sign that better modularization is in order. - */ - -#ifndef MISC_H -#define MISC_H - -#include /* for size_t */ -#include /* for uint16_t */ -#include "yx_uint16.h" /* for yx_uint16 coordinates */ -struct Map; - - - -/* Return 16-bit number pseudo-randomly generated via Linear Congruential - * Generator algorithm with some proven constants. Use instead of rand() to - * ensure portability of the same pseudo-randomness across systems. - */ -extern uint16_t rrand(); - -/* Wrappers to malloc(), calloc() from function "f"; exit_trouble() on error. */ -extern void * try_malloc(size_t size, char * f); -extern void * try_calloc(size_t nmemb, size_t size, char * f); - -/* If one and only one of files at "p1", "p2" exists, fail with explanation. */ -extern void check_files_xor(char * p1, char * p2); - -/* Check if tempfile "path" exists, and if so, exit with explanation that. */ -extern void check_tempfile(char * path); - -/* Save / load / unload (free) / reload interface configuration data. */ -extern void save_interface_conf(); -extern void load_interface_conf(); -extern void unload_interface_conf(); -extern void reload_interface_conf(); - -/* Append "text" to game log, or a "." if "text" is the same as the last one. */ -extern void update_log(char * text); - -/* Return offset nto center map of "mapsize" on "position" in "framesize". */ -extern uint16_t center_offset(uint16_t position, - uint16_t mapsize, uint16_t framesize); - -/* Record "action" in record file, do all movements until player's next turn. */ -extern void turn_over(char action); - -/* Save or load current game data to / from file "savefile". */ -extern void save_game(); -extern void load_game(); - -/* Return random passable (as by is_passable()) position on "map". */ -extern struct yx_uint16 find_passable_pos(struct Map * map); - -/* Move world.inventory_sel up ("dir"="u") or down (else) as far as possible. */ -extern void nav_inventory(char dir); - - - -#endif diff --git a/src/readwrite.c b/src/readwrite.c deleted file mode 100644 index e6660ae..0000000 --- a/src/readwrite.c +++ /dev/null @@ -1,170 +0,0 @@ -/* readwrite.c */ - -#include "readwrite.h" -#include /* for size_t */ -#include /* for FILE typedef, fopen(), fgetc(), fputc(), fseek(), - * sprintf(), fwrite(), ferror() - */ -#include /* for uint8_t, uint16_t, uint32_t */ -#include /* for strlen() */ -#include /* for unlink() */ -#include "rexit.h" /* for exit_err(), exit_trouble() */ - - - -extern FILE * try_fopen(char * path, char * mode, char * f) -{ - char * msg1 = "Trouble in "; - char * msg2 = " with fopen() (mode '"; - char * msg3 = "') on path '"; - char * msg4 = "'."; - uint16_t size = strlen(msg1) + strlen(msg2) + strlen(msg3) + strlen(msg4) - + strlen(f) + strlen(path) + strlen(mode) + 1; - char msg[size]; - sprintf(msg, "%s%s%s%s%s%s%s", msg1, f, msg2, mode, msg3, path, msg4); - FILE * file_p = fopen(path, mode); - exit_err(NULL == file_p, msg); - return file_p; -} - - - -extern void try_fclose(FILE * file, char * f) -{ - exit_trouble(fclose(file), f, "fclose()"); -} - - - -extern void try_fwrite(void * ptr, size_t size, size_t nmemb, FILE * stream, - char * f) -{ - exit_trouble(0 == fwrite(ptr, size, nmemb, stream), f, "fwrite()"); -} - - - -extern void try_fputc(uint8_t c, FILE * file, char * f) -{ - exit_trouble(EOF == fputc(c, file), f, "fputc()"); -} - - - -extern char * try_fgets(char * line, int linemax, FILE * file, char * f) -{ - char * test = fgets(line, linemax, file); - exit_trouble(NULL == test && ferror(file), f, "fgets()"); - return test; -} - - - -extern int try_fgetc(FILE * file, char * f) -{ - int test = fgetc(file); - exit_trouble(EOF == test && ferror(file), f, "fgetc()"); - return test; -} - - - -extern uint8_t try_fgetc_noeof(FILE * file, char * f) -{ - char * f_name = "try_fgetc_noeof()"; - int test = try_fgetc(file, f_name); - exit_trouble(EOF == test, f, "fgetc() unexpectedly hitting EOF"); - return (uint8_t) test; -} - - - -extern void try_fclose_unlink_rename(FILE * file, char * p1, char * p2, - char * f) -{ - try_fclose(file, f); - char * msg1 = "Trouble in "; - char * msg4 = "'."; - if (!access(p2, F_OK)) - { - char * msg2 = " with unlink() on path '"; - uint16_t size = strlen(msg1) + strlen(msg2) + strlen(msg4) - + strlen(f) + strlen(p2) + 1; - char msg[size]; - sprintf(msg, "%s%s%s%s%s", msg1, f, msg2, p2, msg4); - exit_err(unlink(p2), msg); - } - char * msg2 = " with rename() from '"; - char * msg3 = "' to '"; - uint16_t size = strlen(msg1) + strlen(f) + strlen(msg2) + strlen(p1) - + strlen(msg3) + strlen(p2) + strlen(msg4) + 1; - char msg[size]; - sprintf(msg, "%s%s%s%s%s%s%s", msg1, f, msg2, p1, msg3, p2, msg4); - exit_err(rename(p1, p2), msg); -} - - - -extern uint16_t textfile_sizes(FILE * file, uint16_t * n_lines_p) -{ - char * f_name = "textfile_sizes()"; - int c = 0; - uint16_t c_count = 0; - uint16_t n_lines = 0; - uint16_t linemax = 0; - while (1) - { - c = try_fgetc(file, f_name); - if (EOF == c) - { - break; - } - c_count++; - if ('\n' == c) - { - if (c_count > linemax) - { - linemax = c_count; - } - c_count = 0; - if (n_lines_p) - { - n_lines++; - } - } - } - if (0 == linemax && 0 < c_count) /* Handle files that consist of only one */ - { /* line / lack newline chars. */ - linemax = c_count; - } - exit_trouble(-1 == fseek(file, 0, SEEK_SET), f_name, "fseek()"); - if (n_lines_p) - { - * n_lines_p = n_lines; - } - return linemax; -} - - - -extern uint32_t read_uint32_bigendian(FILE * file) -{ - char * f_name = "read_uint32_bigendian()"; - uint32_t x; - x = (uint32_t) try_fgetc_noeof(file, f_name) << 24; - x = x + ( (uint32_t) try_fgetc_noeof(file, f_name) << 16 ); - x = x + ( (uint32_t) try_fgetc_noeof(file, f_name) << 8 ); - x = x + (uint32_t) try_fgetc_noeof(file, f_name); - return x; -} - - - -extern void write_uint32_bigendian(uint32_t x, FILE * file) -{ - char * f_name = "write_uint32_bigendian()"; - try_fputc( x >> 24, file, f_name); - try_fputc( ( x >> 16 ) & 0xFF, file, f_name); - try_fputc( ( x >> 8 ) & 0xFF, file, f_name); - try_fputc( x & 0xFF, file, f_name); -} diff --git a/src/readwrite.h b/src/readwrite.h deleted file mode 100644 index 0dc47da..0000000 --- a/src/readwrite.h +++ /dev/null @@ -1,55 +0,0 @@ -/* readwrite.h: - * - * Routines for reading and writing files. - */ - -#ifndef READWRITE_H -#define READWRITE_H - -#include /* for FILE typedef */ -#include /* for uint8_t, uint16_t, uint32_t */ - - - -/* Wrappers to fopen(), fclose(), fgets() and fwrite() from function called "f", - * calling exit_err() upon error with appropriate error messages. - */ -extern FILE * try_fopen(char * path, char * mode, char * f); -extern void try_fclose(FILE * file, char * f); -extern void try_fwrite(void * ptr, size_t size, size_t nmemb, FILE * stream, - char * f); -extern void try_fputc(uint8_t c, FILE * file, char * f); - -/* Wrapper to calling fgets() from function called "f". The return code of - * fgets() is returned unless it is NULL *and* ferror() indicates that an error - * occured; otherwise end of file is assumed and NULL is returned properly. - */ -extern char * try_fgets(char * line, int size, FILE * file, char * f); - -/* Wrapper to calling fgetc() from function "f", treating either - * (try_fgetc_noeof()) all EOF returns as errors triggering exit_trouble(), or - * (try_fgetc()) only those accompanied by a positive ferror() result. - */ -extern int try_fgetc(FILE * file, char * f); -extern uint8_t try_fgetc_noeof(FILE * file, char * f); - -/* Wrapper to successive call of fclose() from function called "f" on "file", - * then unlink() on file at path "p2" if it exists, then rename() from path "p1" - * to "p2". Used for handling atomic saving of files via temp files. - */ -extern void try_fclose_unlink_rename(FILE * file, char * p1, char * p2, - char * f); - -/* Return largest line length from "file" the largest line length (including - * newline chars) and write the number of newline chars in "file" to the memory - * pointed to by "n_lines_p" if it is not passed as NULL. - */ -extern uint16_t textfile_sizes(FILE * file, uint16_t * n_lines_p); - -/* Read/write via try_(fgetc|fputc)() uint32 values with defined endianness. */ -extern uint32_t read_uint32_bigendian(FILE * file); -extern void write_uint32_bigendian(uint32_t x, FILE * file); - - - -#endif diff --git a/src/rexit.c b/src/rexit.c deleted file mode 100644 index f79eac7..0000000 --- a/src/rexit.c +++ /dev/null @@ -1,110 +0,0 @@ -/* rexit.c */ - -#include "rexit.h" -#include /* for exit(), free(), defines EXIT_SUCESS, EXIT_FAILURE */ -#include /* for printf(), perror(), sprintf() */ -#include /* for uint8_t */ -#include /* for strlen() */ -#include /* for errno */ -#include "main.h" /* for world global */ -#include "map.h" /* for Map struct ("free(world.map->cells)") */ -#include "command_db.h" /* for free_command_db() */ -#include "windows.h" /* for free_winmeta_and_endwin() */ -#include "map_objects.h" /* for free_map_objects, free_map_object_defs() */ -#include "misc.h" /* for unload_interface_conf() */ -#include "map_object_actions.h" /* for free_map_object_actions() */ - - - -/* The clean-up routine and the flag resource by which it decides what to do. */ -static uint32_t cleanup_flags = 0x0000; -static void cleanup(); - - - -static void cleanup() -{ - if (cleanup_flags & CLEANUP_LOG) - { - free(world.log); - } - if (cleanup_flags & CLEANUP_COMMAND_DB) - { - free_command_db(); - } - if (cleanup_flags & CLEANUP_MAP) - { - free(world.map->cells); - } - if (cleanup_flags & CLEANUP_MAP_OBJECTS) - { - free_map_objects(world.map_objs); - } - if (cleanup_flags & CLEANUP_MAP_OBJECT_DEFS) - { - free_map_object_defs(world.map_obj_defs); - } - if (cleanup_flags & CLEANUP_MAP_OBJECT_ACTS) - { - free_map_object_actions(world.map_obj_acts); - } - if (cleanup_flags & CLEANUP_INTERFACE) /* Only cleaning-up order */ - { /* dependency known so far: */ - unload_interface_conf(); /* unload_interface_conf() must */ - } /* come before */ - if (cleanup_flags & CLEANUP_NCURSES) /* free_winmeta_and_endwin() */ - { /* since it depends on world.wmeta */ - free_winmeta_and_endwin(); /* for closing all windows. */ - } -} - - - -extern void set_cleanup_flag(enum cleanup_flag flag) -{ - cleanup_flags = cleanup_flags | flag; -} - - - -extern void exit_game() -{ - cleanup(); - exit(EXIT_SUCCESS); -} - - - -extern void exit_err(uint8_t err, char * msg) -{ - if (0 == err) - { - return; - } - cleanup(); - if (NULL == msg) - { - msg = "Details unknown."; - } - printf("Aborted PlomRogue due to error. %s\nInternal error code: %d\n", - msg, err); - if (0 != errno) - { - perror("errno states"); - } - exit(EXIT_FAILURE); -} - - - -extern void exit_trouble(uint8_t err, char * parent, char * child) -{ - char * p1 = "Trouble in "; - char * p2 = " with "; - char * p3 = "."; - uint16_t size = strlen(p1) + strlen(parent) + strlen(p2) + strlen(child) - + strlen(p3) + 1; - char msg[size]; - sprintf(msg, "%s%s%s%s%s", p1, parent, p2, child, p3); - exit_err(err, msg); -} diff --git a/src/rexit.h b/src/rexit.h deleted file mode 100644 index 1c5c59d..0000000 --- a/src/rexit.h +++ /dev/null @@ -1,48 +0,0 @@ -/* rexit.h - * - * Routines to exit the game orderly or on error, with as much cleaning up as is - * possible in both cases. - */ - -#ifndef REXIT_H -#define REXIT_H - -#include /* for uint8_t */ - - - -/* set_cleanup_flag() sets any of the flags defined in cleanup_flag to announce - * the resources that need cleaning up upon program exit. It is to be called at - * the earliest moment possible after resource creation / initialization. - */ -enum cleanup_flag -{ - CLEANUP_LOG = 0x0001, - CLEANUP_COMMAND_DB = 0x0002, - CLEANUP_MAP = 0x0004, - CLEANUP_MAP_OBJECTS = 0x0008, - CLEANUP_MAP_OBJECT_DEFS = 0x0010, - CLEANUP_MAP_OBJECT_ACTS = 0x0020, - CLEANUP_INTERFACE = 0x0040, - CLEANUP_NCURSES = 0x0080 -}; -extern void set_cleanup_flag(enum cleanup_flag flag); - - - -/* Exit orderly, clean up in an order respecting resource dependencies. */ -extern void exit_game(); - -/* If "err" == 0, do nothing. Else, clean up and exit with an error message that - * consists, first, of "msg" or (if "msg" is a NULL pointer) a generic "Details - * unknown", secondly of "err" as the "internal error code", and thirdly of - * errno if it is non-zero. - */ -extern void exit_err(uint8_t err, char * msg); - -/* Do exit_err() with "msg" as: "Trouble in ".parent." with ".child."." */ -extern void exit_trouble(uint8_t err, char * parent, char * child); - - - -#endif diff --git a/src/server/ai.c b/src/server/ai.c new file mode 100644 index 0000000..96e7ac3 --- /dev/null +++ b/src/server/ai.c @@ -0,0 +1,115 @@ +/* src/server/ai.c */ + +#include "ai.h" +#include /* uint8_t, uint16_t */ +#include /* free() */ +#include /* strlen(), memset() */ +#include "../common/try_malloc.h" /* try_malloc() */ +#include "../common/yx_uint16.h" /* struct yx_uint16 */ +#include "map_object_actions.h" /* get_moa_id_by_name() */ +#include "map_objects.h" /* struct MapObj */ +#include "world.h" /* global world */ +#include "yx_uint16.h" /* yx_uint16_cmp(), mv_yx_in_dir() */ + + + +/* Change cardinal direction string ("NNE" etc.) of any length >1 pointed to by + * "path_ptr" one step clockwise ("NNE" -> "NEE", "NEE" -> "EEE" etc.). + */ +static void clockwise_path(char ** path_ptr); + +/* Return dir char (north = "N" etc.) to enemy nearest to "origin" (beeline). */ +static char nearest_enemy_dir(struct yx_uint16 origin); + + + +static void clockwise_path(char ** path_ptr) +{ + char * path = * path_ptr; + char old_char = path[0]; + char new_char = 'N'; + if ('N' == old_char) + { + new_char = 'E'; + } + else if ('E' == old_char) + { + new_char = 'S'; + } + else if ('S' == old_char) + { + new_char = 'W'; + } + uint8_t len = strlen(path); + uint8_t i = 0; + for (; i < len; i++) + { + uint8_t next_i = i + 1; + if (next_i == len || old_char != path[next_i]) + { + break; + } + } + path[i] = new_char; +} + + + +static char nearest_enemy_dir(struct yx_uint16 origin) +{ + char * f_name = "nearest_enemy_dir()"; + struct MapObj * mo; + char sel = 0; + uint16_t dist_max = world.map.size.y; + if (world.map.size.x > world.map.size.y) + { + dist_max = world.map.size.x; + } + uint8_t escape = 0; + uint8_t dist, j; + uint16_t i; + for (dist = 1; !escape && dist <= dist_max; dist++) + { + char * path = try_malloc(dist + 1, f_name); + memset(path, 'N', dist); + path[dist] = '\0'; + for (i = 0; !escape && i < (dist * 4); i++) + { + clockwise_path(&path); + struct yx_uint16 testpos = origin; + for (j = 0; j < dist; j++) + { + testpos = mv_yx_in_dir(path[j], testpos); + } + if (yx_uint16_cmp(&testpos, &origin) || + testpos.y > world.map.size.y || testpos.x > world.map.size.x) + { + continue; + } + for (mo = world.map_objs; mo != 0; mo = mo->next) + { + if (mo->lifepoints && 1 == yx_uint16_cmp(&testpos, &mo->pos)) + { + sel = path[0]; + escape = 1; + break; + } + } + } + free(path); + } + return sel; +} + + + +extern void pretty_dumb_ai(struct MapObj * mo) +{ + mo->command = get_moa_id_by_name("wait"); + char sel = nearest_enemy_dir(mo->pos); + if (0 != sel) + { + mo->command = get_moa_id_by_name("move"); + mo->arg = sel; + } +} diff --git a/src/server/ai.h b/src/server/ai.h new file mode 100644 index 0000000..7e5a145 --- /dev/null +++ b/src/server/ai.h @@ -0,0 +1,23 @@ +/* src/server/ai.h + * + * Pseudo AI for actor movement. + */ + +#ifndef AI_H +#define AI_H + +struct MapObj; + + + +/* Determine next non-player actor command / arguments by the actor's AI. + * + * The AI is pretty dumb so far. Actors basically try to move towards their + * nearest neighbor in a straight line, easily getting stuck behind obstacles or + * ending up in endless chase circles with each other. + */ +extern void pretty_dumb_ai(struct MapObj * mo); + + + +#endif diff --git a/src/server/cleanup.c b/src/server/cleanup.c new file mode 100644 index 0000000..4ad970c --- /dev/null +++ b/src/server/cleanup.c @@ -0,0 +1,49 @@ +/* src/server/cleanup.c */ + +#include "cleanup.h" +#include /* uint32_t */ +#include /* free() */ +#include /* unlink() */ +#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() +{ + free(world.queue); + free(world.log); + free(world.map.cells); + if (cleanup_flags & CLEANUP_OUTFILE) + { + unlink(world.path_out); + } + if (cleanup_flags & CLEANUP_MAP_OBJECTS) + { + free_map_objects(world.map_objs); + } + if (cleanup_flags & CLEANUP_MAP_OBJECT_DEFS) + { + free_map_object_defs(world.map_obj_defs); + } + if (cleanup_flags & CLEANUP_MAP_OBJECT_ACTS) + { + 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. */ +} + + +extern void set_cleanup_flag(enum cleanup_flag flag) +{ + cleanup_flags = cleanup_flags | flag; +} diff --git a/src/server/cleanup.h b/src/server/cleanup.h new file mode 100644 index 0000000..61abd71 --- /dev/null +++ b/src/server/cleanup.h @@ -0,0 +1,31 @@ +/* src/server/cleanup.h + * + * Stuff defining / performing the cleanup called by rexit.h's exit functions. + */ + +#ifndef CLEANUP_H +#define CLEANUP_H + + + +/* set_cleanup_flag() sets any of the flags defined in cleanup_flag to announce + * the resources that need cleaning up upon program exit. It is to be called at + * the earliest moment possible after resource creation / initialization. + */ +enum cleanup_flag +{ + CLEANUP_FIFO = 0x0001, + CLEANUP_OUTFILE = 0x0002, + CLEANUP_MAP_OBJECT_DEFS = 0x0004, + CLEANUP_MAP_OBJECTS = 0x0008, + CLEANUP_MAP_OBJECT_ACTS = 0x0010 +}; + +extern void set_cleanup_flag(enum cleanup_flag flag); + +/* Frees memory and unlinks some files. */ +extern void cleanup(); + + + +#endif diff --git a/src/server/init.c b/src/server/init.c new file mode 100644 index 0000000..f6b56a6 --- /dev/null +++ b/src/server/init.c @@ -0,0 +1,114 @@ +/* src/server/init.c */ + +#include "init.h" +#include /* uint32_t */ +#include /* exit(), free() */ +#include /* atoi() */ +#include /* time() */ +#include /* optarg, getopt(), access() */ +#include "../common/readwrite.h" /* try_fopen(), try_fclose(), textfile_sizes(), + * try_fgets() + */ +#include "../common/rexit.h" /* exit_err() */ +#include "cleanup.h" /* set_cleanup_flag() */ +#include "map_objects.h" /* free_map_objects(), add_map_objects() */ +#include "map.h" /* init_map() */ +#include "rrand.h" /* rrand() */ +#include "run.h" /* obey_msg(), io_loop() */ +#include "world.h" /* global world */ + + + +extern void obey_argv(int argc, char * argv[]) +{ + int opt; + while (-1 != (opt = getopt(argc, argv, "vs::"))) + { + if ('v' == opt) + { + world.is_verbose = 1; + } + else if ('s' == opt) + { + world.replay = 1; + if (optarg) + { + world.replay = atoi(optarg); + } + } + else + { + exit(EXIT_FAILURE); + } + } +} + + + +extern void remake_world(uint32_t seed) +{ + free(world.log); + world.log = NULL; + world.seed = seed; + world.map_obj_count = 0; + world.score = 0; + free(world.map.cells); + if (world.map_objs) + { + free_map_objects(world.map_objs); + } + world.last_update_turn = 0; + world.turn = 1; + init_map(); + add_map_objects(0, 1); + add_map_objects(1, 1 + rrand() % 27); + add_map_objects(2, 1 + rrand() % 9); + add_map_objects(3, 1 + rrand() % 3); + add_map_objects(4, 1 + rrand() % 3); + add_map_objects(5, 1 + rrand() % 3); + set_cleanup_flag(CLEANUP_MAP_OBJECTS); +} + + + +extern void run_game() +{ + char * f_name = "run_game()"; + if (!access(world.path_record, F_OK)) + { + FILE * file = try_fopen(world.path_record, "r", f_name); + uint32_t linemax = textfile_sizes(file, NULL); + 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()) + { + if (!end) + { + end = (NULL == try_fgets(line, linemax + 1, file, f_name)); + if (!end) + { + 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); + io_loop(); +} diff --git a/src/server/init.h b/src/server/init.h new file mode 100644 index 0000000..595f697 --- /dev/null +++ b/src/server/init.h @@ -0,0 +1,38 @@ +/* src/server/init.h + * + * Server, world and game state initialization. + */ + +#ifndef INIT_H +#define INIT_H + +#include /* uint32_t */ + + + +/* Parses command line arguments -v and -s into server configuration. */ +extern void obey_argv(int argc, char * argv[]); + +/* Dissolves old game world if it exists, and generates a new one from "seed". + * + * Map object (action) definitions read in from server config directory are not + * affected. 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. How many map objects of what type + * id are generated on the map is currently hard-coded. + */ +extern void remake_world(uint32_t seed); + +/* Create a game state from which to play or replay, then enter io_loop(). + * + * 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. + */ +extern void run_game(); + + + +#endif diff --git a/src/server/io.c b/src/server/io.c new file mode 100644 index 0000000..10fcd90 --- /dev/null +++ b/src/server/io.c @@ -0,0 +1,242 @@ +/* src/server/io.c */ + +#include "io.h" +#include /* global errno */ +#include /* open(), O_RDONLY, O_NONBLOCK */ +#include /* PIPE_BUF */ +#include /* size_t */ +#include /* uint8_t, uint16_t, uint32_t */ +#include /* define FILE, sprintf() */ +#include /* free() */ +#include /* strlen(), memset(), memcpy() */ +#include /* read(), close() */ +#include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(), + * try_fwrite(), try_fputc() + */ +#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() */ +#include "world.h" /* global world */ + + + +/* 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 + * start with \0 bytes, those are cut out, but NULL is returned instead of "". +*/ +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. + */ +static void read_fifo_into_queue(); + +/* Write to output file the world state as it is to be visible to clients. */ +static void update_out_file(); + +/* Write "value" to new \n-delimited line of "file". */ +static void write_value_as_line(uint32_t value, FILE * file); + +/* Write to "file" player's inventory, one item name per line. End in "%\n". */ +static void write_inventory(struct MapObj * player, FILE * file); + +/* Write to "file" game map, with map objects super-imposed. Write one row per + * \n-delimited line. Super-impose animated objects over inanimate objects. + */ +static void write_map(FILE * file); + + + +static char * get_message_from_queue() +{ + 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) + { + free(world.queue); + world.queue = NULL; + } + 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() +{ + 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) + { + bytes_read = read(fdesc_infile, buf, buf_size); + if (bytes_read > 0) + { + 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; + } + exit_trouble(-1 == bytes_read && errno != EAGAIN, "read()", f_name); + if (1 == read_state) + { + break; + } + } + exit_trouble(close(fdesc_infile), f_name, "close()"); +} + + + +static void update_out_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); + FILE * file = try_fopen(path_tmp, "w", f_name); + struct MapObj * player = get_player(); + write_value_as_line(world.turn, file); + write_value_as_line(world.score, file); + write_value_as_line(player->lifepoints, file); + write_inventory(player, file); + write_value_as_line(player->pos.y, file); + write_value_as_line(player->pos.x, file); + write_value_as_line(world.map.size.y, file); + write_value_as_line(world.map.size.x, file); + write_map(file); + if (world.log) + { + 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); +} + + + +static void write_value_as_line(uint32_t value, FILE * file) +{ + char * f_name = "write_value_as_line()"; + char write_buf[12]; /* Holds 10 digits of uint32_t maximum + \n + \0. */ + sprintf(write_buf, "%u\n", value); + try_fwrite(write_buf, strlen(write_buf), 1, file, f_name); +} + + + +static void write_inventory(struct MapObj * player, FILE * file) +{ + char * f_name = "write_inventory()"; + struct MapObj * owned = player->owns; + if (NULL == owned) + { + char * empty = "(none)\n"; + try_fwrite(empty, strlen(empty), 1, file, f_name); + } + else + { + uint8_t q; + for (q = 0; NULL != owned; q++) + { + struct MapObjDef * mod = get_map_object_def(owned->type); + try_fwrite(mod->name, strlen(mod->name), 1, file, f_name); + try_fputc('\n', file, f_name); + owned = owned->next; + } + } + try_fputc('%', file, f_name); + try_fputc('\n', file, f_name); +} + + + +static void write_map(FILE * file) +{ + char * f_name = "write_map()"; + uint32_t map_size = world.map.size.y * world.map.size.x; + char visible_map[map_size]; + memcpy(visible_map, world.map.cells, map_size); + struct MapObj * o; + struct MapObjDef * d; + char c; + uint8_t i; + for (i = 0; i < 2; i++) + { + for (o = world.map_objs; o != 0; o = o->next) + { + if (( (0 == i && 0 == o->lifepoints) + || (1 == i && 0 < o->lifepoints))) + { + d = get_map_object_def(o->type); + c = d->char_on_map; + visible_map[(o->pos.y * world.map.size.x) + o->pos.x] = c; + } + } + } + uint16_t x, y; + for (y = 0; y < world.map.size.y; y++) + { + for (x = 0; x < world.map.size.x; x++) + { + try_fputc(visible_map[(y * world.map.size.x) + x], file, f_name); + } + try_fputc('\n', file, f_name); + } +} + + + +extern char * io_round() +{ + char * f_name = "io_round()"; + if (0 < world.queue_size) + { + return get_message_from_queue(); + } + if (world.turn != world.last_update_turn) + { + update_out_file(); + world.last_update_turn = world.turn; + } + read_fifo_into_queue(); + if ('\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); + new_queue[world.queue_size] = '\0'; + world.queue_size++; + free(world.queue); + world.queue = new_queue; + } + return get_message_from_queue(); +} diff --git a/src/server/io.h b/src/server/io.h new file mode 100644 index 0000000..78cb068 --- /dev/null +++ b/src/server/io.h @@ -0,0 +1,30 @@ +/* io.h: + * + * Communication of the server with the outside world via input fifo and output + * file. + */ + +#ifndef IO_H +#define 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. + * + * 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 + * re-filled queue does not end in a \0 byte, a \0 byte is appended to it. + */ +extern char * io_round(); + + + +#endif diff --git a/src/server/main.c b/src/server/main.c new file mode 100644 index 0000000..6cf6b41 --- /dev/null +++ b/src/server/main.c @@ -0,0 +1,65 @@ +/* src/server/main.c */ + +#include /* global errno */ +#include /* printf() */ +#include /* exit() */ +#include /* mkfifo(), mkdir() */ +#include /* access() */ +#include "../common/rexit.h" /* exit_err, exit_trouble(), set_cleanup_func() */ +#include "cleanup.h" /* set_cleanup_flag(), cleanup() */ +#include "init.h" /* run_game(), obey_argv() */ +#include "map_object_actions.h" /* init_map_object_actions() */ +#include "map_objects.h" /* init_map_object_defs() */ +#include "run.h" /* obey_argv(), run_game() */ +#include "world.h" /* struct World */ + + + +struct World world; + + + +int main(int argc, char ** argv) +{ + char * f_name = "main()"; + + /* So error exits also go through the server's cleanup() function. */ + set_cleanup_func(cleanup); + + /* Init settings from command line / hard-coded values. Print start info. */ + obey_argv(argc, argv); + if (world.is_verbose) + { + char * printf_err = "Trouble in main() with printf()."; + int test = printf("Starting plomrogue-server.\n"); + exit_err(-1 == test, printf_err); + if (world.replay) + { + test = printf("Replay mode. Auto-replaying up to turn %d.\n", + world.replay); + exit_err(-1 == test, printf_err); + } + } + world.path_in = "server/in"; + world.path_out = "server/out"; + world.path_record = "record"; + world.tmp_suffix = "_tmp"; + + /* 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("confserver/defs"); + init_map_object_actions("confserver/map_object_actions"); + + /* Enter play or replay mode loops, then leave properly. */ + run_game(); + cleanup(); + exit(EXIT_SUCCESS); +} diff --git a/src/server/map.c b/src/server/map.c new file mode 100644 index 0000000..f46b03e --- /dev/null +++ b/src/server/map.c @@ -0,0 +1,66 @@ +/* src/server/map.c */ + +#include "map.h" +#include /* uint8_t, uint16_t, uint32_t */ +#include "../common/map.h" /* struct Map */ +#include "../common/try_malloc.h" /* try_malloc() */ +#include "../common/yx_uint16.h" /* struct yx_uint16 */ +#include "rrand.h" /* rrand() */ +#include "world.h" /* global world */ + + + +extern void init_map() +{ + char * f_name = "init_map()"; + world.map.size.x = 64; + world.map.size.y = 64; + uint32_t size = world.map.size.x * world.map.size.y; + world.map.cells = try_malloc(size, f_name); + uint16_t y, x; + for (y = 0; y < world.map.size.y; y++) + { + for (x = 0; + x < world.map.size.x; + world.map.cells[(y * world.map.size.x) + x] = '~', x++); + } + world.map.cells[size / 2 + (world.map.size.x / 2)] = '.'; + uint32_t curpos; + while (1) + { + y = rrand() % world.map.size.y; + x = rrand() % world.map.size.x; + curpos = y * world.map.size.x + x; + if ('~' == world.map.cells[curpos] + && ( ( curpos >= world.map.size.x + && '.' == world.map.cells[curpos - world.map.size.x]) + || ( curpos < world.map.size.x * (world.map.size.y-1) + && '.' == world.map.cells[curpos + world.map.size.x]) + || ( curpos > 0 && curpos % world.map.size.x != 0 + && '.' == world.map.cells[curpos-1]) + || ( curpos < (world.map.size.x * world.map.size.y) + && (curpos+1) % world.map.size.x != 0 + && '.' == world.map.cells[curpos+1]))) + { + if ( y == 0 || y == world.map.size.y - 1 || x == 0 + || x == world.map.size.x - 1) + { + break; + } + world.map.cells[y * world.map.size.x + x] = '.'; + } + } +} + + + +extern uint8_t is_passable(struct yx_uint16 pos) +{ + uint8_t passable = 0; + if ( 0 <= pos.x && pos.x < world.map.size.x + && 0 <= pos.y && pos.y < world.map.size.y) + { + passable = (('.' == world.map.cells[pos.y * world.map.size.x + pos.x])); + } + return passable; +} diff --git a/src/server/map.h b/src/server/map.h new file mode 100644 index 0000000..464f896 --- /dev/null +++ b/src/server/map.h @@ -0,0 +1,31 @@ +/* src/server/map.h + * + * Struct for the game map and routines to create and scroll on it. + */ + +#ifndef MAP_H_SERVER +#define MAP_H_SERVER + +#include /* uint8_t */ +#include "../common/map.h" /* struct Map */ +#include "../common/yx_uint16.h" /* yx_uint16 struct */ + + + +/* Initialize island map as 64 x 64 "~" cells representing water and "." cells + * representing land. The shape of the island is generated randomly by starting + * with a sea containing one land cell in the middle and then going into a cycle + * of repeatedly selecting a random cell on the map and transforming it into a + * land cell if it is horizontally or vertically neighbor to one; the cycle ends + * when a land cell is due to be created right at the border of the map. + */ +extern void init_map(); + +/* Check if coordinate "pos" on (or beyond) world.map is accessible to map + * object movement. + */ +extern uint8_t is_passable(struct yx_uint16 pos); + + + +#endif diff --git a/src/server/map_object_actions.c b/src/server/map_object_actions.c new file mode 100644 index 0000000..f7bdcb4 --- /dev/null +++ b/src/server/map_object_actions.c @@ -0,0 +1,402 @@ +/* src/server/map_object_actions.c */ + +#include "map_object_actions.h" +#include /* uint8_t, uint16_t */ +#include /* sprintf() */ +#include /* free(), atoi() */ +#include /* strlen(), strcmp(), memcpy(), strtok(), strncmp() */ +#include "../common/readwrite.h" /* textfile_sizes(), try_fopen(), try_fclose(), + * try_fgets() + */ +#include "../common/rexit.h" /* exit_err() */ +#include "../common/try_malloc.h" /* try_malloc() */ +#include "../common/yx_uint16.h" /* yx_uint16 struct */ +#include "cleanup.h" /* set_cleanup_flag() */ +#include "map_objects.h" /* structs MapObj, MapObjDef, get_player(), + * set_object_position(), own_map_object(), + * get_map_object_def() + */ +#include "map.h" /* is_passable() */ +#include "yx_uint16.h" /* mv_yx_in_dir(), yx_uint16_cmp() */ +#include "world.h" /* global world */ + + + +/* Append "text" to game log, or a "." if "text" is the same as the last one. */ +static void update_log(char * text); + +/* If "name" fits "moa"->name, set "moa"->func to "func". */ +static uint8_t try_func_name(struct MapObjAct * moa, + char * name, void (* func) (struct MapObj *)); + +/* One actor "wounds" another actor, decrementing his lifepoints and, if they + * reach zero in the process, killing it. Generates appropriate log message. + */ +static void actor_hits_actor(struct MapObj * hitter, struct MapObj * hitted); + +/* Bonus stuff to actor_*() to happen if actor==player. Mostly writing of log + * messages; _pick and _drop also decrement world.inventory_sel by 1 if >0. + */ +static void playerbonus_wait(); +static void playerbonus_move(char d, uint8_t passable); +static void playerbonus_drop(uint8_t owns_none); +static void playerbonus_pick(uint8_t picked); +static void playerbonus_use(uint8_t no_object, uint8_t wrong_object); + + + +static void update_log(char * text) +{ + char * f_name = "update_log()"; + uint16_t len_new = strlen(text); + uint16_t len_old = 0; + if (world.log) + { + len_old = strlen(world.log); + uint16_t last_nl = len_old - 1; + while (last_nl != 0) + { + if ('\n' == world.log[last_nl]) + { + break; + } + last_nl--; + } + uint16_t last_stop = len_old - 1; + while (last_stop != 0) + { + if ('.' == world.log[last_stop] && '.' != world.log[last_stop - 1]) + { + break; + } + last_stop--; + } + if ( (last_stop + 1) - last_nl == strlen(text) + && 0 == strncmp(world.log + last_nl, text, strlen(text))) + { + text = "."; + } + } + uint16_t len_whole = len_old + len_new + 1; + char * new_text = try_malloc(len_whole, f_name); + memcpy(new_text, world.log, len_old); + sprintf(new_text + len_old, "%s", text); + free(world.log); + world.log = new_text; +} + + + +static uint8_t try_func_name(struct MapObjAct * moa, + char * name, void (* func) (struct MapObj *)) +{ + if (0 == strcmp(moa->name, name)) + { + moa->func = func; + return 1; + } + return 0; +} + + + +static void actor_hits_actor(struct MapObj * hitter, struct MapObj * hitted) +{ + struct MapObjDef * mod_hitter = get_map_object_def(hitter->type); + struct MapObjDef * mod_hitted = get_map_object_def(hitted->type); + struct MapObj * player = get_player(); + char * msg1 = "You"; + char * msg2 = "wound"; + char * msg3 = "you"; + if (player != hitter) + { + msg1 = mod_hitter->name; + msg2 = "wounds"; + } + if (player != hitted) + { + msg3 = mod_hitted->name; + } + uint8_t len = 1 + strlen(msg1) + 1 + strlen(msg2) + 1 + strlen(msg3) + 2; + char msg[len]; + sprintf(msg, "\n%s %s %s.", msg1, msg2, msg3); + update_log(msg); + hitted->lifepoints--; + if (0 == hitted->lifepoints) + { + hitted->type = mod_hitted->corpse_id; + if (player == hitted) + { + update_log(" You die."); + return; + } + update_log(" It dies."); + if (player == hitter) + { + world.score = world.score + mod_hitted->lifepoints; + } + } +} + + + +static void playerbonus_wait() +{ + update_log("\nYou wait."); +} + + + +static void playerbonus_move(char d, uint8_t passable) +{ + char * dsc_dir = "north"; + if ('E' == d) + { + dsc_dir = "east" ; + } + else if ('S' == d) + { + dsc_dir = "south"; + } + else if ('W' == d) + { + dsc_dir = "west" ; + } + char * dsc_move = "You move "; + if (0 == passable) + { + dsc_move = "You fail to move "; + } + char msg[strlen(dsc_move) + strlen (dsc_dir) + 3]; + sprintf(msg, "\n%s%s.", dsc_move, dsc_dir); + update_log(msg); +} + + + +static void playerbonus_drop(uint8_t owns_none) +{ + if (0 != owns_none) + { + update_log("\nYou try to drop an object, but you own none."); + return; + } + update_log("\nYou drop an object."); +} + + + +static void playerbonus_pick(uint8_t picked) +{ + if (picked) + { + update_log("\nYou pick up an object."); + return; + } + update_log("\nYou try to pick up an object, but there is none."); +} + + + +static void playerbonus_use(uint8_t no_object, uint8_t wrong_object) +{ + if (no_object) + { + update_log("\nYou try to use an object, but you own none."); + return; + } + else if (wrong_object) + { + update_log("\nYou try to use this object, but fail."); + return; + } + update_log("\nYou consume MAGIC MEAT."); +} + + + +extern void init_map_object_actions(char * path) +{ + char * f_name = "init_map_object_actions()"; + FILE * file = try_fopen(path, "r", f_name); + uint16_t linemax = textfile_sizes(file, NULL); + char line[linemax + 1]; + struct MapObjAct ** moa_ptr_ptr = &world.map_obj_acts; + char * delim = " "; + while (try_fgets(line, linemax + 1, file, f_name)) + { + if ('\n' == line[0] || 0 == line[0]) + { + break; + } + struct MapObjAct * moa = try_malloc(sizeof(struct MapObjAct), f_name); + moa->id = atoi(strtok(line, delim)); + moa->effort = atoi(strtok(NULL, delim)); + char * funcname = strtok(NULL, "\n"); + uint8_t len_name = strlen(funcname) + 1; + moa->name = try_malloc(len_name, f_name); + memcpy(moa->name, funcname, len_name); + if (!( try_func_name(moa, "move", actor_move) + || try_func_name(moa, "pick_up", actor_pick) + || try_func_name(moa, "drop", actor_drop) + || try_func_name(moa, "use", actor_use))) + { + moa->func = actor_wait; + } + moa->next = NULL; + * moa_ptr_ptr = moa; + moa_ptr_ptr = &moa->next; + } + try_fclose(file, f_name); + set_cleanup_flag(CLEANUP_MAP_OBJECT_ACTS); +} + + + +extern void free_map_object_actions(struct MapObjAct * moa) +{ + if (NULL == moa) + { + return; + } + free(moa->name); + free_map_object_actions(moa->next); + free(moa); +} + + + +extern uint8_t get_moa_id_by_name(char * name) +{ + struct MapObjAct * moa = world.map_obj_acts; + while (NULL != moa) + { + if (0 == strcmp(moa->name, name)) + { + break; + } + moa = moa->next; + } + exit_err(NULL == moa, "get_moa_id_name() did not find map object action."); + return moa->id; +} + + + +extern void actor_wait(struct MapObj * mo) +{ + if (mo == get_player()) + { + playerbonus_wait(); + } +} + + + +extern void actor_move(struct MapObj * mo) +{ + char d = mo->arg; + struct yx_uint16 target = mv_yx_in_dir(d, mo->pos); + struct MapObj * other_mo; + for (other_mo = world.map_objs; other_mo != 0; other_mo = other_mo->next) + { + if (0 == other_mo->lifepoints || other_mo == mo) + { + continue; + } + if (yx_uint16_cmp(&target, &other_mo->pos)) + { + actor_hits_actor(mo, other_mo); + return; + } + } + uint8_t passable = is_passable(target); + if (passable) + { + set_object_position(mo, target); + } + if (mo == get_player()) + { + playerbonus_move(d, passable); + } +} + + + +extern void actor_drop(struct MapObj * mo) +{ + uint8_t owns_none = (NULL == mo->owns); + if (!owns_none) + { + uint8_t select = mo->arg; + struct MapObj * owned = mo->owns; + uint8_t i = 0; + for (; i != select; i++, owned = owned->next); + own_map_object(&world.map_objs, &mo->owns, owned->id); + } + if (mo == get_player()) + { + playerbonus_drop(owns_none); + } +} + + + +extern void actor_pick(struct MapObj * mo) +{ + struct MapObj * picked; + for (picked = world.map_objs; NULL != picked; picked = picked->next) + { + if (picked != mo && yx_uint16_cmp(&picked->pos, &mo->pos)) + { + break; + } + } + if (NULL != picked) + { + own_map_object(&mo->owns, &world.map_objs, picked->id); + set_object_position(picked, mo->pos); + } + if (mo == get_player()) + { + playerbonus_pick(NULL != picked); + } +} + + + +extern void actor_use(struct MapObj * mo) +{ + uint8_t wrong_object = 1; + uint8_t no_object = (NULL == mo->owns); + if (!no_object) + { + uint8_t select = mo->arg; + uint8_t i = 0; + struct MapObj * selected = mo->owns; + for (; i != select; i++, selected = selected->next); + struct MapObjDef * mod = get_map_object_def(selected->type); + if (!strcmp("MAGIC MEAT", mod->name)) + { + wrong_object = 0; + struct MapObj * next = selected->next; + free(selected); + if (0 < select) + { + select--; + selected = mo->owns; + for (i = 0; i != select; i++, selected = selected->next); + selected->next = next; + } + else + { + mo->owns = next; + } + mo->lifepoints++; + } + } + if (mo == get_player()) + { + playerbonus_use(no_object, wrong_object); + } +} diff --git a/src/server/map_object_actions.h b/src/server/map_object_actions.h new file mode 100644 index 0000000..ae015e6 --- /dev/null +++ b/src/server/map_object_actions.h @@ -0,0 +1,60 @@ +/* src/server/map_object_actions.h + * + * Actions that can be performed my map objects / "actors". Note that apart + * from the consequences described below, each action may also trigger log + * messages and other minor stuff if the actor is equal to the player. + */ + +#ifndef MAP_OBJECT_ACTIONS_H +#define MAP_OBJECT_ACTIONS_H + +#include /* uint8_t */ +struct MapObj; + + + +struct MapObjAct +{ + struct MapObjAct * next; + void (* func) (struct MapObj *); /* function called after .effort turns */ + char * name; /* human-readable identifier */ + uint8_t id; /* unique id of map object action */ + uint8_t effort; /* how many turns the action takes */ +}; + + + +/* Init MapObjAct chain at world.map_obj_acts from file at "path". */ +extern void init_map_object_actions(char * path); + +/* Free MapObjAct * chain starting at "moa". */ +extern void free_map_object_actions(struct MapObjAct * moa); + +/* Return world.map_obj_acts MapObjAct.id for "name". */ +extern uint8_t get_moa_id_by_name(char * name); + +/* Actor "mo" does nothing. */ +extern void actor_wait(struct MapObj * mo); + +/* Actor "mo" tries to move one step in direction described by char mo->arg + * (where east is 'E', north 'N') etc. Move either succeeds, or another actor is + * encountered and hit (which leads ot its lifepoint decreasing by one and + * eventually death), or the move fails due to an impassable target square. + */ +extern void actor_move(struct MapObj * mo); + +/* Actor "mo" tries to drop from inventory object indexed by number mo->args. */ +extern void actor_drop(struct MapObj * mo); + +/* Actor "mo" tries to pick up object from ground into its inventory. */ +extern void actor_pick(struct MapObj * mo); + +/* Actor "mo" tries to use inventory object indexed by number mo->args. + * (Currently the only valid use is consuming an item named "MAGIC MEAT", + * which increments the actors lifepoints.) + */ +extern void actor_use(struct MapObj * mo); + + + +#endif diff --git a/src/server/map_objects.c b/src/server/map_objects.c new file mode 100644 index 0000000..5b5bd97 --- /dev/null +++ b/src/server/map_objects.c @@ -0,0 +1,225 @@ +/* src/server/map_objects.c */ + +#include "map_objects.h" +#include /* FILE typedef */ +#include /* uint8_t, uint16_t */ +#include /* free(), atoi() */ +#include /* strlen(), memcpy(), strtok() */ +#include "../common/readwrite.h" /* try_fopen(), try_fclose(), try_fgets(), + * textfile_sizes() + */ +#include "../common/try_malloc.h" /* try_malloc() */ +#include "../common/yx_uint16.h" /* yx_uint16 struct */ +#include "cleanup.h" /* set_cleanup_flag() */ +#include "map.h" /* is_passable() */ +#include "rrand.h" /* rrand() */ +#include "world.h" /* global world */ +#include "yx_uint16.h" /* yx_uint16_cmp() */ + + + +/* Return pointer to map object of "id" in chain starting at "ptr". */ +static struct MapObj * get_map_object(struct MapObj * ptr, uint8_t id); + +/* Return random passable (as by is_passable()) position on world.map. */ +static struct yx_uint16 find_passable_pos(); + +/* Add object of "type" to map on random position. Don't place actor on actor.*/ +static void add_map_object(uint8_t type); + + + +static struct MapObj * get_map_object(struct MapObj * ptr, uint8_t id) +{ + while (1) + { + if (NULL == ptr || id == ptr->id) + { + return ptr; + } + struct MapObj * owned_object = get_map_object(ptr->owns, id); + if (NULL != owned_object) + { + return ptr; + } + ptr = ptr->next; + } +} + + + +static struct yx_uint16 find_passable_pos() // struct Map * map) +{ + struct yx_uint16 pos; + for (pos.y = pos.x = 0; 0 == is_passable(pos);) + { + pos.y = rrand() % world.map.size.y; + pos.x = rrand() % world.map.size.x; + } + return pos; +} + + + +static void add_map_object(uint8_t type) +{ + char * f_name = "add_map_object()"; + struct MapObjDef * mod = get_map_object_def(type); + struct MapObj * mo = try_malloc(sizeof(struct MapObj), f_name); + mo->id = world.map_obj_count++; + mo->type = mod->id; + mo->lifepoints = mod->lifepoints; + while (1) + { + struct yx_uint16 pos = find_passable_pos(world.map); + struct MapObj * mo_ptr; + uint8_t clear = 1; + for (mo_ptr = world.map_objs; mo_ptr != NULL; mo_ptr = mo_ptr->next) + { + if (yx_uint16_cmp(&pos, &mo_ptr->pos) && 0 != mo_ptr->lifepoints) + { + clear = 0; + break; + } + } + if (1 == clear) + { + mo->pos = pos; + break; + } + } + mo->progress = 0; + mo->command = 0; + mo->arg = 0; + mo->owns = NULL; + mo->next = NULL; + struct MapObj ** mo_ptr_ptr = &world.map_objs; + for (; NULL != * mo_ptr_ptr; mo_ptr_ptr = &(*mo_ptr_ptr)->next); + * mo_ptr_ptr = mo; +} + + + +extern void init_map_object_defs(char * filename) +{ + char * f_name = "init_map_object_defs()"; + FILE * file = try_fopen(filename, "r", f_name); + uint16_t linemax = textfile_sizes(file, NULL); + struct MapObjDef ** last_mod_ptr_ptr = &world.map_obj_defs; + char * delim = " "; + char line[linemax + 1]; + while (try_fgets(line, linemax + 1, file, f_name)) + { + struct MapObjDef * mod = try_malloc(sizeof(struct MapObjDef), f_name); + mod->next = NULL; + mod->id = atoi(strtok(line, delim)); + mod->corpse_id = atoi(strtok(NULL, delim)); + mod->char_on_map = * strtok(NULL, delim); + mod->lifepoints = atoi(strtok(NULL, delim)); + char * name = strtok(NULL, "\n"); + mod->name = try_malloc(strlen(name) + 1, f_name); + memcpy(mod->name, name, strlen(name) + 1); + * last_mod_ptr_ptr = mod; + last_mod_ptr_ptr = &mod->next; + } + try_fclose(file, f_name); + set_cleanup_flag(CLEANUP_MAP_OBJECT_DEFS); +} + + + +extern void free_map_object_defs(struct MapObjDef * mod_start) +{ + if (NULL == mod_start) + { + return; + } + free_map_object_defs(mod_start->next); + free(mod_start->name); + free(mod_start); + mod_start = NULL; +} + + + +extern void add_map_objects(uint8_t type, uint8_t n) +{ + uint8_t i; + for (i = 0; i < n; i++) + { + add_map_object(type); + } +} + + + +extern void free_map_objects(struct MapObj * mo_start) +{ + if (NULL == mo_start) + { + return; + } + free_map_objects(mo_start->owns); + free_map_objects(mo_start->next); + free(mo_start); + if (mo_start == world.map_objs) + { + world.map_objs = NULL; + } +} + + + +extern void own_map_object(struct MapObj ** target, struct MapObj ** source, + uint8_t id) +{ + struct MapObj * mo; + if (id == (*source)->id) + { + mo = * source; + * source = mo->next; + } + else + { + struct MapObj * penult = * source; + while (1) + { + if (id == penult->next->id) + { + break; + } + penult = penult->next; + } + mo = penult->next; + penult->next = mo->next; + } + struct MapObj ** mo_ptr_ptr = target; + for (; NULL != * mo_ptr_ptr; mo_ptr_ptr = &(*mo_ptr_ptr)->next); + * mo_ptr_ptr = mo; + mo->next = NULL; +} + + + +extern struct MapObj * get_player() +{ + return get_map_object(world.map_objs, 0); +} + + + +extern struct MapObjDef * get_map_object_def(uint8_t id) +{ + struct MapObjDef * mod = world.map_obj_defs; + for (; id != mod->id; mod = mod->next); + return mod; +} + + + +extern void set_object_position(struct MapObj * mo, struct yx_uint16 pos) +{ + mo->pos = pos; + struct MapObj * owned = mo->owns; + for (; owned != NULL; set_object_position(owned, pos), owned = owned->next); +} diff --git a/src/server/map_objects.h b/src/server/map_objects.h new file mode 100644 index 0000000..d5dc5b3 --- /dev/null +++ b/src/server/map_objects.h @@ -0,0 +1,69 @@ +/* src/server/map_objects.h + * + * Structs for objects on the map and their type definitions, and routines to + * initialize these and load and save them from/to files. + */ + +#ifndef MAP_OBJECTS_H +#define MAP_OBJECTS_H + +#include /* uint8_t */ +#include "../common/yx_uint16.h" /* yx_uint16 structs */ + + + +struct MapObj +{ + struct MapObj * next; /* pointer to next one in map object chain */ + struct MapObj * owns; /* chain of map objects owned / in inventory */ + struct yx_uint16 pos; /* coordinate on map */ + uint8_t id; /* individual map object's unique identifier */ + uint8_t type; /* ID of appropriate map object definition */ + uint8_t lifepoints; /* 0: object is inanimate; >0: hitpoints */ + uint8_t command; /* map object's current action */ + uint8_t arg; /* optional field for .command argument */ + uint8_t progress; /* turns already passed to realize .command */ +}; + +struct MapObjDef +{ + struct MapObjDef * next; + char char_on_map; /* map object symbol to appear on map */ + char * name; /* string to describe object in game log */ + uint8_t id; /* map object definition identifier / sets .type */ + uint8_t corpse_id; /* type to change map object into upon destruction */ + uint8_t lifepoints; /* default start value for map object's .lifepoints */ +}; + + + +/* Initialize map object definitions chain from file at path "filename". */ +extern void init_map_object_defs(char * filename); + +/* Free map object definitions chain starting at "mod_start". */ +extern void free_map_object_defs(struct MapObjDef * mod_start); + +/* Add object(s) ("n": how many?) of "type" to map on random position(s). New + * animate objects are never placed in the same square with other animate ones. + */ +extern void add_map_objects(uint8_t type, uint8_t n); + +/* Free map objects in map object chain starting at "mo_start. */ +extern void free_map_objects(struct MapObj * mo_start); + +/* Move object of "id" from "source" inventory to "target" inventory. */ +extern void own_map_object(struct MapObj ** target, struct MapObj ** source, + uint8_t id); + +/* Get pointer to the MapObj struct that represents the player. */ +extern struct MapObj * get_player(); + +/* Get pointer to the map object definition of identifier "def_id". */ +extern struct MapObjDef * get_map_object_def(uint8_t id); + +/* Move not only "mo" to "pos", but also all map objects owned by it. */ +extern void set_object_position(struct MapObj * mo, struct yx_uint16 pos); + + + +#endif diff --git a/src/server/rrand.c b/src/server/rrand.c new file mode 100644 index 0000000..8f8830f --- /dev/null +++ b/src/server/rrand.c @@ -0,0 +1,13 @@ +/* src/server/rrand.c */ + +#include "rrand.h" +#include /* uint16_t */ +#include "world.h" /* global world */ + + + +extern uint16_t rrand() +{ /* Constants as recommended by POSIX.1-2001 (see man page rand(3)). */ + world.seed = ((world.seed * 1103515245) + 12345) % 4294967296; + return (world.seed >> 16); /* Ignore less random least significant bits. */ +} diff --git a/src/server/rrand.h b/src/server/rrand.h new file mode 100644 index 0000000..42f3393 --- /dev/null +++ b/src/server/rrand.h @@ -0,0 +1,21 @@ +/* src/server/rrand.h + * + * Provides deterministic pseudo-randomness. + */ + +#ifndef RRAND_H +#define RRAND_H + +#include /* uint16_t */ + + + +/* Return 16-bit number pseudo-randomly generated via Linear Congruential + * Generator algorithm with some proven constants. Use instead of rand() to + * ensure portability of the same pseudo-randomness across systems. + */ +extern uint16_t rrand(); + + + +#endif diff --git a/src/server/run.c b/src/server/run.c new file mode 100644 index 0000000..3be073a --- /dev/null +++ b/src/server/run.c @@ -0,0 +1,162 @@ +/* src/server/run.c */ + +#include "run.h" +#include /* uint8_t, uint16_t, uint32_t */ +#include /* FILE, sprintf() */ +#include /* free() */ +#include /* strlen(), strncmp(), atoi() */ +#include /* access() */ +#include "../common/readwrite.h" /* try_fopen(), try_fcose(), try_fwrite(), + * try_fgets(), try_fclose_unlink_rename(), + * textfile_sizes(), try_fputc() + */ +#include "../common/rexit.h" /* exit_trouble() */ +#include "ai.h" /* pretty_dumb_ai() */ +#include "init.h" /* remake_world() */ +#include "io.h" /* io_round() */ +#include "map_object_actions.h" /* get_moa_id_by_name() */ +#include "map_objects.h" /* struct MapObj, get_player() */ +#include "world.h" /* global world */ + + + +/* 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 MapObj'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); + + + +static void turn_over() +{ + struct MapObj * player = get_player(); + struct MapObj * map_object = player; + uint16_t start_turn = world.turn; + uint8_t first_round = 1; + while ( 0 < player->lifepoints + || (0 == player->lifepoints && start_turn == world.turn)) + { + if (NULL == map_object) + { + world.turn++; + map_object = world.map_objs; + } + if (0 < map_object->lifepoints) + { + if (0 == first_round && 0 == map_object->progress) + { + if (map_object == player) + { + break; + } + pretty_dumb_ai(map_object); + } + first_round = 0; + map_object->progress++; + struct MapObjAct * moa = world.map_obj_acts; + while (moa->id != map_object->command) + { + moa = moa->next; + } + if (map_object->progress == moa->effort) + { + moa->func(map_object); + map_object->progress = 0; + } + } + map_object = map_object->next; + } +} + + + +static uint8_t apply_player_command(char * msg, char * command_name) +{ + if (!strncmp(msg, command_name, strlen(command_name))) + { + struct MapObj * player = get_player(); + player->arg = atoi(&(msg[strlen(command_name)])); + player->command = get_moa_id_by_name(command_name); + turn_over(); + return 1; + } + return 0; +} + + + +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_moa_id_by_name(); if id */ + || apply_player_command(msg, "drop") /* found, execute on it what's */ + || apply_player_command(msg, "use")); /* in apply_player_command(). */ + else + { + char * seed_command = "seed"; + if (!strncmp(msg, seed_command, strlen(seed_command))) + { + remake_world(atoi(&(msg[strlen(seed_command)]))); + } + } + 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_sizes(file_read, NULL); + 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); + } +} + + + +extern uint8_t io_loop() +{ + char * f_name = "io_loop()"; + while (1) + { + char * msg = io_round(); + if (NULL == msg) + { + continue; + } + if (world.is_verbose) + { + exit_trouble(-1 == printf("Input: %s\n", msg), f_name, "printf()"); + } + if (!strcmp("QUIT", msg)) + { + free(msg); + return 1; + } + if (world.replay) + { + free(msg); + return 0; + } + obey_msg(msg, 1); + free(msg); + } +} diff --git a/src/server/run.h b/src/server/run.h new file mode 100644 index 0000000..877d2fb --- /dev/null +++ b/src/server/run.h @@ -0,0 +1,26 @@ +/* src/server/run.h + * + * Process commands and act on them. Stuff that furthers the state of the game. + */ + +#ifndef RUN_H +#define RUN_H + +#include /* uint8_t */ + + + +/* 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. + */ +extern void obey_msg(char * msg, uint8_t do_record); + +/* 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(). + */ +extern uint8_t io_loop(); + + + +#endif diff --git a/src/server/world.h b/src/server/world.h new file mode 100644 index 0000000..56a5f28 --- /dev/null +++ b/src/server/world.h @@ -0,0 +1,43 @@ +/* src/server/world.h + * + * Contains the World struct holding all game data together. + */ + +#ifndef MAIN_H +#define MAIN_H + +#include /* uint8_t, uint16_t, uint32_t */ +#include "../common/map.h" /* struct Map */ +struct MapObjDef; +struct MapObjAct; +struct MapObj; + + + +struct World +{ + 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 * path_record; /* Record file from which to read the game history. */ + char * tmp_suffix; /* Appended to paths of files for their tmp versions. */ + char * queue; /* Stores un-processed messages received via input fifo. */ + 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. */ + uint16_t turn; /* Current game turn. */ + uint16_t last_update_turn; /* Last turn the .path_out file was updated. */ + uint16_t score; /* Player's score. */ + uint8_t is_verbose; /* Should server send debugging info to stdout? */ + uint8_t map_obj_count; /* Counts map objects generated so far. */ +}; + +extern struct World world; + + + +#endif diff --git a/src/server/yx_uint16.c b/src/server/yx_uint16.c new file mode 100644 index 0000000..d793000 --- /dev/null +++ b/src/server/yx_uint16.c @@ -0,0 +1,39 @@ +/* src/server/yx_uint16.c */ + +#include "yx_uint16.h" +#include /* uint8_t, UINT16_MAX */ +#include "../common/yx_uint16.h" /* yx_uint16 struct */ + + + +extern uint8_t yx_uint16_cmp(struct yx_uint16 * a, struct yx_uint16 * b) +{ + if (a->y == b->y && a->x == b->x) + { + return 1; + } + return 0; +} + + + +extern struct yx_uint16 mv_yx_in_dir(char d, struct yx_uint16 yx) +{ + if (d == 'N' && yx.y > 0) + { + yx.y--; + } + else if (d == 'E' && yx.x < UINT16_MAX) + { + yx.x++; + } + else if (d == 'S' && yx.y < UINT16_MAX) + { + yx.y++; + } + else if (d == 'W' && yx.x > 0) + { + yx.x--; + } + return yx; +} diff --git a/src/server/yx_uint16.h b/src/server/yx_uint16.h new file mode 100644 index 0000000..04d2573 --- /dev/null +++ b/src/server/yx_uint16.h @@ -0,0 +1,25 @@ +/* src/server/yx_uint16.h + * + * Routines for comparison and movement with yx_uin16 structs. + */ + +#ifndef YX_UINT16_H_SERVER +#define YX_UINT16_H_SERVER + +#include /* uint8_t */ +#include "../common/yx_uint16.h" /* yx_uint16 struct */ + + + +/* Return 1 if two yx_uint16 coordinates at "a" and "b" are equal, else 0. */ +extern uint8_t yx_uint16_cmp(struct yx_uint16 * a, struct yx_uint16 * b); + +/* Return yx_uint16 coordinate one step from "yx" in direction "dir" (east: 'E', + * west: 'W', north: 'N', south: 'S'). If "dir" is invalid or would wrap the + * move around the edge of a 2^16x2^16 cells field, "yx" remains unchanged. + */ +extern struct yx_uint16 mv_yx_in_dir(char dir, struct yx_uint16 yx); + + + +#endif diff --git a/src/wincontrol.c b/src/wincontrol.c deleted file mode 100644 index 74bd4a2..0000000 --- a/src/wincontrol.c +++ /dev/null @@ -1,566 +0,0 @@ -/* wincontrol.c */ - -#include "wincontrol.h" -#include /* for free() */ -#include /* for strlen(), strchr(), strstr() */ -#include /* for uint8_t, uint16_t */ -#include "windows.h" /* for suspend_win(), append_win(), reset_pad_offset(), - * resize_active_win(), init_win(), free_win(), struct Win - */ -#include "yx_uint16.h" /* for yx_uint16 struct */ -#include "main.h" /* for world global */ -#include "readwrite.h" /* for textfile_sizes(), try_fopen(), try_fclose(), - * try_fgets(), try_fclose_unlink_rename(), try_fwrite() - * try_fgetc_noeof() - */ -#include "rexit.h" /* for exit_err(), exit_trouble() */ -#include "draw_wins.h" /* for draw_win_map(), draw_win_info(), draw_win_log(), - * draw_win_available_keybindings(), - * draw_win_inventory(), draw_win_keybindings_global(), - * draw_win_keybindings_winconf_geometry(), - * draw_win_keybindings_winconf_keybindings(), - * draw_winconf_geometry(), draw_winconf_keybindings() - */ -#include "misc.h" /* for try_malloc() */ -#include "dirent.h" /* for opendir(), closedir(), readdir() */ -#include "errno.h" /* for errno */ -#include "keybindings.h" /* for KeyBinding struct, free_keybindings() */ - - - -/* Return string "prefix" + "id"; malloc()'s string, remember to call free()! */ -static char * string_prefixed_id(char * prefix, char id); - -/* Initialize Winconf of "id" from appropriate config file.*/ -static void init_winconf_from_file(char id, struct WinConf * winconf); - -/* Wrapper around init_win() called with values from Winconf of "id". */ -static void init_win_from_winconf(char id); - -/* Save title, draw function, size of window identified by "id" to conffile. */ -static void save_win_config(char id); - -/* Free data pointed to inside individual WinConf struct of "id". */ -static void free_winconf_data(char id); - -/* Write geometry of a window to its WinConf, as positive or negative values - * (dependent on state ofWinConf->height_type / WinConf->width_type). - */ -static void set_winconf_geometry(char id); - -/* Get WinConf by "id"; get id of WinConf mothering "win". */ -static struct WinConf * get_winconf_by_id(char id); - -/* Get (Win->draw) function identified by "c"; NULL if c not mapped to one. */ -static void * get_drawfunc_by_char(char c); - -/* Iterate over chars of world.winconf_ids array. Re-start after null byte. */ -static char get_next_winconf_id(); - - - -static char * string_prefixed_id(char * prefix, char id) -{ - uint8_t size = strlen(prefix) + 2; - char * path = try_malloc(size, "string_prefixed_id()"); - sprintf(path, "%s_", prefix); - path[size - 2] = id; - return path; -} - - - -static void init_winconf_from_file(char id, struct WinConf * winconf) -{ - /* Assign WinConf id to filename path, error message context, winconf->id.*/ - char * tmp = "init_winconf_from_file() on window id '_'"; - char * context = try_malloc(strlen(tmp) + 1, "init_winconf_from_file()"); - memcpy(context, tmp, strlen(tmp) + 1); - context[strlen(tmp) - 2] = id; - char * path = string_prefixed_id("config/windows/Win_", id); - winconf->id = id; - - /* Prepare reading in file line by line into "line" array. */ - FILE * file = try_fopen(path, "r", context); - free(path); - uint16_t linemax = textfile_sizes(file, NULL/*, context*/); - char line[linemax + 1]; - - /* Read/determine winconf->title, ->draw, ->height(_type),->width(_type). */ - try_fgets(line, linemax + 1, file, context); - winconf->title = try_malloc(strlen(line), context); - memcpy(winconf->title, line, strlen(line) - 1); /* Eliminate newline char */ - winconf->title[strlen(line) - 1] = '\0'; /* char at end of string. */ - try_fgets(line, linemax + 1, file, context); - winconf->draw = line[0]; - try_fgets(line, linemax + 1, file, context); - winconf->height = atoi(line); - winconf->height_type = (0 >= winconf->height); - try_fgets(line, linemax + 1, file, context); - winconf->width = atoi(line); - winconf->width_type = (0 >= winconf->width); - - /* Read in window-specific keybindings (winconf->kb). */ - char command[linemax + 1]; - char * cmdptr; - struct KeyBinding ** loc_last_ptr = &winconf->kb.kbs; - * loc_last_ptr = 0; - while (fgets(command, linemax + 1, file)) - { - if ('\n' == command[0] || 0 == command[0]) - { - break; - } - * loc_last_ptr = try_malloc(sizeof(struct KeyBinding), context); - struct KeyBinding * kb_p = * loc_last_ptr; - kb_p->next = 0; - kb_p->key = atoi(command); - cmdptr = strchr(command, ' ') + 1; - kb_p->name = try_malloc(strlen(cmdptr), context); - memcpy(kb_p->name, cmdptr, strlen(cmdptr) - 1); - kb_p->name[strlen(cmdptr) - 1] = '\0'; - loc_last_ptr = & kb_p->next; - } - - /* Init remaining values to zero and cleaning up. */ - winconf->view = 0; - winconf->kb.edit = 0; - winconf->kb.select = 0; - try_fclose(file, context); - free(context); -} - - - -static void init_win_from_winconf(char id) -{ - char * err = "get_drawfunc_by_char() returns NULL to init_win_from_file()."; - struct WinConf * winconf = get_winconf_by_id(id); - void * f = get_drawfunc_by_char(winconf->draw); - exit_err(NULL == f, err); - init_win(&winconf->win, winconf->title, winconf->height, winconf->width, f); -} - - - -static void save_win_config(char id) -{ - char * f_name = "save_win_config()"; - - /* Prepare atomic file saving. */ - char * path_tmp = string_prefixed_id("config/windows/Win_tmp_", id); - FILE * file = try_fopen(path_tmp, "w", f_name); - - /* Save, line by line, ->title, ->draw, ->height and ->width. */ - struct WinConf * wc = get_winconf_by_id(id); - uint8_t size = strlen(wc->title) + 2; - if (size < 7) /* Ensure that at least 5 + 2 char fit into line so that */ - { /* the digit representation of any uint16_t may be stored. */ - size = 7; - } - char line[size]; - sprintf(line, "%s\n", wc->title); - try_fwrite(line, sizeof(char), strlen(line), file, f_name); - sprintf(line, "%c\n", wc->draw); - try_fwrite(line, sizeof(char), strlen(line), file, f_name); - sprintf(line, "%d\n", wc->height); - try_fwrite(line, sizeof(char), strlen(line), file, f_name); - sprintf(line, "%d\n", wc->width); - try_fwrite(line, sizeof(char), strlen(line), file, f_name); - - /* Save window-specific keybindings (->kb.kbs). */ - uint16_t linemax = 0; - struct KeyBinding * kb_p = wc->kb.kbs; - while (0 != kb_p) - { - if (strlen(kb_p->name) > linemax) - { - linemax = strlen(kb_p->name); - } - kb_p = kb_p->next; - } - linemax = linemax + 6; /* + 6: + 3 digits + whitespace + \n + \0 */ - char kb_line[linemax]; - kb_p = wc->kb.kbs; - while (0 != kb_p) - { - sprintf(kb_line, "%d %s\n", kb_p->key, kb_p->name); - try_fwrite(kb_line, sizeof(char), strlen(kb_line), file, f_name); - kb_p = kb_p->next; - } - - /* Finish atomic file saving and clean up. */ - char * path = string_prefixed_id("config/windows/Win_", id); - try_fclose_unlink_rename(file, path_tmp, path, f_name); - free(path); - free(path_tmp); -} - - - -static void free_winconf_data(char id) -{ - struct WinConf * wc = get_winconf_by_id(id); - free(wc->title); - free_keybindings(wc->kb.kbs); - free_win(wc->win); -} - - - -static void set_winconf_geometry(char id) -{ - struct WinConf * wcp = get_winconf_by_id(id); - if (0 == wcp->height_type) - { - wcp->height = wcp->win->framesize.y; - } - else if (1 == wcp->height_type) - { - wcp->height = wcp->win->framesize.y - world.wmeta->padsize.y + 1; - } - if (0 == wcp->width_type) - { - wcp->width = wcp->win->framesize.x; - } - else if (1 == wcp->width_type) - { - wcp->width = wcp->win->framesize.x - world.wmeta->padsize.x; - } -} - - - -static struct WinConf * get_winconf_by_id(char id) -{ - uint8_t i = 0; - while (1) - { - if (id == world.winconfs[i].id) - { - return &world.winconfs[i]; - } - i++; - } -} - - - -static void * get_drawfunc_by_char(char c) -{ - if ('c' == c) - { - return draw_win_inventory; - } - else if ('i' == c) - { - return draw_win_info; - } - else if ('l' == c) - { - return draw_win_log; - } - else if ('k' == c) - { - return draw_win_available_keybindings; - } - else if ('m' == c) - { - return draw_win_map; - } - else if ('0' == c) - { - return draw_win_keybindings_global; - } - else if ('1' == c) - { - return draw_win_keybindings_winconf_geometry; - } - else if ('2' == c) - { - return draw_win_keybindings_winconf_keybindings; - } - return NULL; -} - - - -static char get_next_winconf_id() -{ - static uint8_t i = 0; - char c = world.winconf_ids[i]; - if (0 == c) - { - i = 0; - return c; - } - i++; - return c; -} - - - -extern struct WinConf * get_winconf_by_win(struct Win * win) -{ - uint8_t i = 0; - while (1) - { - if (win == world.winconfs[i].win) - { - return &world.winconfs[i]; - } - i++; - } -} - - - -extern struct Win * get_win_by_id(char id) -{ - struct WinConf * wc = get_winconf_by_id(id); - return wc->win; -} - - - -extern void init_winconfs() -{ - char * f_name = "init_winconfs()"; - - /* Fill world.winconf_ids with config/windows/Win_* filenames' end chars. */ - uint8_t max_wins = 255; /* Maximum number of window ids to store. */ - DIR * dp = opendir("config/windows"); - exit_trouble(NULL == dp, f_name, "opendir()"); - struct dirent * fn; - errno = 0; - char * winconf_ids = try_malloc(max_wins + 1, f_name); - uint8_t i = 0; - char id; - while (NULL != (fn = readdir(dp)) && i < max_wins) - { - if (5 == strlen(fn->d_name) && fn->d_name == strstr(fn->d_name, "Win_")) - { - id = fn->d_name[4]; - winconf_ids[i] = id; - i++; - } - } - winconf_ids[i] = '\0'; - exit_trouble(errno, f_name, "readdir()"); - exit_trouble(closedir(dp), f_name, "closedir()"); - world.winconf_ids = try_malloc(strlen(winconf_ids) + 1, f_name); - memcpy(world.winconf_ids, winconf_ids, strlen(winconf_ids) + 1); - free(winconf_ids); - - /* Initialize world.winconfs from Win_* files named in world.winconf_ids. */ - size_t size = strlen(world.winconf_ids) * sizeof(struct WinConf); - world.winconfs = try_malloc(size, f_name); - i = 0; - while (0 != (id = get_next_winconf_id())) - { - init_winconf_from_file(id, &world.winconfs[i]); - i++; - } -} - - - -extern void free_winconfs() -{ - char id; - while (0 != (id = get_next_winconf_id())) - { - free_winconf_data(id); - } - free(world.winconf_ids); - free(world.winconfs); -} - - - -extern void init_wins() -{ - char id; - while (0 != (id = get_next_winconf_id())) - { - init_win_from_winconf(id); - } -} - - - -extern void sorted_wintoggle_and_activate() -{ - char * f_name = "sorted_wintoggle_and_activate()"; - - /* Read from file order of windows to be toggled + active win selection. */ - char * path = "config/windows/toggle_order_and_active"; - FILE * file = try_fopen(path, "r", f_name); - uint16_t linemax = textfile_sizes(file, NULL); - char win_order[linemax + 1]; - try_fgets(win_order, linemax + 1, file, f_name); - uint8_t a = try_fgetc_noeof(file, f_name); - try_fclose(file, f_name); - - /* Toggle windows and set active window selection. */ - uint8_t i = 0; - for (; i < strlen(win_order) - 1; i++) - { - if (NULL == strchr(world.winconf_ids, win_order[i])) - { - continue; - } - toggle_window(win_order[i]); - if (a == (uint8_t) win_order[i]) - { - world.wmeta->active = get_win_by_id(win_order[i]); - } - } -} - - - -extern void save_win_configs() -{ - char * f_name = "save_win_configs()"; - - /* Save individual world.winconfs to their proper files. */ - uint8_t max_wins = 255; /* So many winconf ids fit into world.winconf_ids.*/ - char id; - while (0 != (id = get_next_winconf_id())) - { - save_win_config(id); - } - - /* Save order of windows to toggle on start / which to select as active. */ - char * path = "config/windows/toggle_order_and_active"; - char * path_tmp = "config/windows/toggle_order_and_active_tmp"; - FILE * file = try_fopen(path_tmp, "w", f_name); - char line[max_wins + 2]; - struct Win * w_p = world.wmeta->chain_start; - uint8_t i = 0; - while (0 != w_p && i < max_wins) - { - struct WinConf * wc = get_winconf_by_win(w_p); - line[i] = wc->id; - w_p = w_p->next; - i++; - } - line[i] = '\n'; - line[i + 1] = '\0'; - try_fwrite(line, sizeof(char), strlen(line), file, f_name); - if (0 != world.wmeta->active) - { - struct WinConf * wc = get_winconf_by_win(world.wmeta->active); - try_fputc(wc->id, file, f_name); - } - try_fclose_unlink_rename(file, path_tmp, path, f_name); -} - - - -extern void toggle_window(char id) -{ - struct Win * win = get_win_by_id(id); - if (0 == win->prev && world.wmeta->chain_start != win) /* Win struct is */ - { /* outside chain? */ - append_win(win); - } - else - { - suspend_win(win); - } -} - - - -extern void toggle_winconfig() -{ - struct Win * win = world.wmeta->active; - struct WinConf * wcp = get_winconf_by_win(win); - if (0 == wcp->view) - { - wcp->view = 1; - win->draw = draw_winconf_geometry; - wcp->center = win->center; - win->center.y = 0; - win->center.x = 0; - } - else if (1 == wcp->view) - { - wcp->view = 2; - win->draw = draw_winconf_keybindings; - win->center.x = 0; - } - else - { - wcp->view = 0; - win->draw = get_drawfunc_by_char(wcp->draw); - win->center = wcp->center; - } -} - - - -extern void toggle_win_size_type(char axis) -{ - struct Win * win = world.wmeta->active; - struct WinConf * wcp = get_winconf_by_win(win); - if ('y' == axis) - { - wcp->height_type = (0 == wcp->height_type); - set_winconf_geometry(wcp->id); - return; - } - wcp->width_type = ( 0 == wcp->width_type - && win->framesize.x <= world.wmeta->padsize.x); - set_winconf_geometry(wcp->id); -} - - - -extern void scroll_pad(char dir) -{ - if ('+' == dir) - { - reset_pad_offset(world.wmeta->pad_offset + 1); - } - else if ('-' == dir) - { - reset_pad_offset(world.wmeta->pad_offset - 1); - } -} - - - -extern void growshrink_active_window(char change) -{ - if (0 != world.wmeta->active) - { - struct yx_uint16 size = world.wmeta->active->framesize; - if (change == '-') - { - size.y--; - } - else if (change == '+') - { - size.y++; - } - else if (change == '_') - { - size.x--; - } - else if (change == '*') - { - size.x++; - } - resize_active_win(size); - struct WinConf * wcp = get_winconf_by_win(world.wmeta->active); - if ( 1 == wcp->width_type - && world.wmeta->active->framesize.x > world.wmeta->padsize.x) - { - wcp->width_type = 0; - } - set_winconf_geometry(wcp->id); - } -} diff --git a/src/wincontrol.h b/src/wincontrol.h deleted file mode 100644 index 7250aa6..0000000 --- a/src/wincontrol.h +++ /dev/null @@ -1,90 +0,0 @@ -/* wincontrol.h - * - * Routines that build on top of the windows library to provide a simple window - * management API to the game. Also helps managing window-specific keybindings. - */ - -#ifndef WINCONTROL_H -#define WINCONTROL_H - -#include /* for uint8_t, int16_t */ -#include "keybindings.h" /* for KeyBiData struct */ -#include "yx_uint16.h" /* for yx_uint16 struct */ -struct Win; - - - -/* Stores a window's configuration (like geometry, keybindings) and a pointer to - * the respective Win struct itself. - */ -struct WinConf -{ - char id; /* Unique identifier of WinConf, doubles aas identifier for .win */ - /* and the char following "Win_" in the respective conffile name.*/ - struct Win * win; /* Window / Win struct configured by this WinConf. */ - struct KeyBiData kb; /* Window-specific keybindings. */ - uint8_t view; /* 0: use .draw as Win.draw; 1/2: use draw_winconf()_(1/2). */ - int16_t height; /* Designated height to pass to init_win(). */ - int16_t width; /* Designated width to pass to init_win(). */ - uint8_t height_type; /* 0: read .height/.width as size in positive cells; */ - uint8_t width_type; /* 1: as negative diff in cells to the screen size. */ - char * title; /* Designated title to pass to init_win(). */ - char draw; /* Identifier of designated Win.draw; passed to init_win() */ - /* and reset after toggling Win.draw via toggle_winconf(). */ - struct yx_uint16 center; /* Designated Win.center; to be reset after */ -}; /* toggling Win.center via toggle_winconf(). */ - - - -/* Get WinConf fathering "win" / get Win of WinConf of "id". */ -extern struct WinConf * get_winconf_by_win(struct Win * win); -extern struct Win * get_win_by_id(char id); - -/* Create, initialize (from config files)/free world.winconfs and their Wins. */ -extern void init_winconfs(); -extern void free_winconfs(); -extern void init_wins(); - -/* Toggle windows in the order desribed by the 1st line of - * config/windows/toggle_order_and_active, where each char may fit a Winconf.id - * in world.winconfs. Silently ignore id chars not found there. The 1st char of - * the 2nd line of the same file determines which window (by its .id) to focus - * as active (but only if this window belongs to the ones just toggled). - */ -extern void sorted_wintoggle_and_activate(); - -/* Save world.winconfs, visible window chain and active window selection to the - * respective configuration files in config/windows/. - */ -extern void save_win_configs(); - -/* Toggle "window configuration" view for active window. Sets sensible - * Win.center values for the various configuration views (for winconf_geometry: - * y=0, x=0; for winconf_keys: x=0 (y is set by draw_winconf_keybindings()). - */ -extern void toggle_winconfig(); - -/* Toggle WinConf.(height/width)_type ("axis" = "y": height; else: width). Avoid - * positive diff to screen width (value would be wrongly read as a non-diff), - * width_type toggles to 1 only if world.wmeta->screen's width >= WinConf.width. - */ -extern void toggle_win_size_type(char axis); - -/* Toggle display of a window identified by "id". */ -extern void toggle_window(char id); - -/* Try scrolling virtual screen left ("dir" = "-") or right ("dir" = "+") to the - * degree allowed by the window manager's reset_pad_offset(). - */ -extern void scroll_pad(char dir); - -/* Try to grow or shrink the active window horizontally ("change" = "*"/"_") or - * vertically ("change = "+"/"-") by one cell size to the degree allowed by the - * window manager's resize_active_win(). If a new window width would surpass - * that of the terminal screen, set WinConf.width_type to 0. - */ -extern void growshrink_active_window(char change); - - - -#endif diff --git a/src/windows.c b/src/windows.c deleted file mode 100644 index 42f2859..0000000 --- a/src/windows.c +++ /dev/null @@ -1,659 +0,0 @@ -/* windows.c */ - -#include "windows.h" -#include /* for uint8_t, uint16_t, uint32_t, UINT16_MAX */ -#include /* for typedefs chtype, wresize(), getmaxx(), getmaxy(), - * delwin(), mvwaddch(), mvwaddstr(), newpad(), erase(), - * wnoutrefresh(), werase(), pnoutrefresh(), doupdate() - */ -#include /* for free() */ -#include /* for strlen(), strnlen(), memcpy() */ -#include "yx_uint16.h" /* for struct yx_uint16 */ -#include "misc.h" /* for center_offset(), try_malloc() */ -#include "main.h" /* for world global */ -#include "rexit.h" /* for exit_err() */ - - - -/* Make virtual screen just wide enough to contain all visible windows. */ -static void refit_pad(); - -/* Update geometry (sizes, positions) of window "w" and its successors in the - * window chain. Use place_win() for the positioning algorithm. - */ -static void update_wins(struct Win * w); -static void place_win(struct Win * w); - -/* Draw scroll hint (a line saying that there are "dist" more elements of "unit" - * further into the direction symbolized by "dir") into virtual screen, onto an - * appropriate edge of a window or the screen; the left/right edge if "dir" is - * "<"/">", or the top/bottom edge if it is "^"/"v". "start" be either the start - * coordinate of a window's frame, or .y=0, .x=wm->pad_offset for the virtual - * screen. winscroll_hint() and padscroll_hint() are wrappers to both cases. - */ -static void scroll_hint(struct yx_uint16 fsize, char dir, uint16_t dist, - char * unit, struct yx_uint16 start); -static void winscroll_hint(struct Win * w, char dir, uint16_t dist); -static void padscroll_hint(char dir, uint16_t dist); - -/* Draw contents of all windows in window chain from window "w" onwards. */ -static void draw_wins(struct Win * w); - -/* draw_win_borderlines() draws vertical/horizontal borders of window "w" sans - * corners into the virtual screen. It draws the top border line as the windows' - * title bar (highlighted if the window is selected as active). It is called - * recursively by draw_wins_borderlines() on all windows from "w" on. - * draw_wins_bordercorners() draws the border corners of "w" and its successors. - */ -static void draw_win_borderlines(struct Win * w); -static void draw_wins_borderlines(struct Win * w); -static void draw_wins_bordercorners(struct Win * w); - -/* Shift active window forwards / backwards in window chain. */ -static void shift_win_forward(); -static void shift_win_backward(); - - - -static void refit_pad() -{ - /* Determine rightmost window column. */ - uint32_t lastwcol = 0; - struct Win * wp = world.wmeta->chain_start; - while (wp != 0) - { - if ((uint32_t) wp->start.x + (uint32_t) wp->framesize.x > lastwcol + 1) - { - lastwcol = (uint32_t) wp->start.x + (uint32_t) wp->framesize.x - 1; - } - wp = wp->next; - } - - /* Only resize the pad if the rightmost window column has changed. */ - char * err_s = "refit_pad() extends virtual screen beyond legal sizes."; - char * err_m = "refit_pad() triggers memory alloc error via wresize()."; - if (getmaxx(world.wmeta->pad) + 1 != lastwcol) - { - uint8_t t = (lastwcol + 2 > UINT16_MAX); - exit_err(t, err_s); - t = wresize(world.wmeta->pad, getmaxy(world.wmeta->pad), lastwcol + 2); - exit_err(t, err_m); - } -} - - - -static void update_wins(struct Win * w) -{ - place_win(w); - refit_pad(); - if (0 != w->next) - { - update_wins(w->next); - } -} - - - -static void place_win(struct Win * w) -{ - /* If w is first window, it goes into the top left corner. */ - w->start.x = 0; - w->start.y = 1; /* Leave space for title bar. */ - if (0 != w->prev) - { - - /* If not, fit w's top left to top right of last top predecessor. */ - struct Win * w_top = w->prev; - while (w_top->start.y != 1) - { - w_top = w_top->prev; - } - w->start.x = w_top->start.x + w_top->framesize.x + 1; - - /* Fit w's top left to bottom left of its ->prev if enough space. */ - uint16_t w_prev_maxy = w->prev->start.y + w->prev->framesize.y; - if ( w->framesize.x <= w->prev->framesize.x - && w->framesize.y < world.wmeta->padsize.y - w_prev_maxy) - { - w->start.x = w->prev->start.x; - w->start.y = w_prev_maxy + 1; - } - - /* Failing that, try to fit w' top left to the top right of the last - * predecessor w_test 1) not followed by windows with a left corner - * further rightwards than its own 2) with enough space rightwards for w - * until the bottom right of w_thr directly throning over it 3) and with - * this same space extending far enough to the bottom for fitting in w. - */ - else - { - struct Win * w_test = w->prev; - struct Win * w_thr; - while (w_test != w_top) - { - w_thr = w_test->prev; - for (; w_test->start.y <= w_thr->start.y; w_thr = w_thr->prev); - uint16_t w_thr_bottom = w_thr->start.y + w_thr->framesize.y; - uint16_t free_width = (w_thr->start.x + w_thr->framesize.x) - - (w_test->start.x + w_test->framesize.x); - if ( w->framesize.y < world.wmeta->padsize.y - w_thr_bottom - && w->framesize.x < free_width) - { - w->start.x = w_test->start.x + w_test->framesize.x + 1; - w->start.y = w_thr_bottom + 1; - break; - } - w_test = w_thr; - } - } - } -} - - - -static void scroll_hint(struct yx_uint16 fsize, char dir, uint16_t dist, - char * unit, struct yx_uint16 start) -{ - /* Decide on alignment (vertical/horizontal?), thereby hint text space. */ - char * more = "more"; - uint16_t dsc_space = fsize.x; - if ('<' == dir || '>' == dir) - { - dsc_space = fsize.y; - } /* vv-- 10 = max strlen for uint16_t */ - char scrolldsc[1 + strlen(more) + 1 + 10 + 1 + strlen(unit) + 1 + 1]; - sprintf(scrolldsc, " %d %s %s ", dist, more, unit); - - /* Decide on offset of the description text inside the scroll hint line. */ - uint16_t dsc_offset = 1; - if (dsc_space > strlen(scrolldsc) + 1) - { - dsc_offset = (dsc_space - strlen(scrolldsc)) / 2; - } - - /* Draw scroll hint line as dir symbols bracketing description text. */ - uint16_t draw_offset = 0; - if ('>' == dir) - { - draw_offset = fsize.x - 1; - } - else if ('v' == dir) - { - draw_offset = fsize.y - 1; - } - uint16_t q = 0; - for (; q < dsc_space; q++) - { - chtype c = dir | A_REVERSE; - if (q >= dsc_offset && q < strlen(scrolldsc) + dsc_offset) - { - c = scrolldsc[q - dsc_offset] | A_REVERSE; - } - if ('<' == dir || '>' == dir) - { - mvwaddch(world.wmeta->pad, start.y + q, start.x + draw_offset, c); - continue; - } - mvwaddch(world.wmeta->pad, start.y + draw_offset, start.x + q, c); - } -} - - -static void padscroll_hint(char dir, uint16_t dist) -{ - struct yx_uint16 start; - start.y = 0; - start.x = world.wmeta->pad_offset; - scroll_hint(world.wmeta->padsize, dir, dist, "columns", start); -} - - - -static void winscroll_hint(struct Win * w, char dir, uint16_t dist) -{ - char * unit = "lines"; - if ('<' == dir || '>' == dir) - { - unit = "columns"; - } - struct yx_uint16 start = w->start; - scroll_hint(w->framesize, dir, dist, unit, start); -} - - - -static void draw_wins(struct Win * w) -{ - w->draw(w); - uint16_t size_y = w->winmapsize.y; - uint16_t size_x = w->winmapsize.x; - uint16_t offset_y = center_offset(w->center.y, size_y, w->framesize.y); - uint16_t offset_x = center_offset(w->center.x, size_x, w->framesize.x); - uint16_t y, x; - for (y = offset_y; y < w->framesize.y + offset_y && y < size_y; y++) - { - for (x = offset_x; x < w->framesize.x + offset_x && x < size_x; x++) - { - chtype ch = w->winmap[(y * w->winmapsize.x) + x]; - mvwaddch(world.wmeta->pad, w->start.y + (y - offset_y), - w->start.x + (x - offset_x), ch); - } - } - free(w->winmap); - w->winmap = NULL; - w->winmapsize.y = 0; - w->winmapsize.x = 0; - if (offset_y > 0) - { - winscroll_hint(w, '^', offset_y + 1); - } - if (size_y > offset_y + w->framesize.y) - { - winscroll_hint(w, 'v', size_y - ((offset_y + w->framesize.y) - 1)); - } - if (offset_x > 0) - { - winscroll_hint(w, '<', offset_x + 1); - } - if (size_x > offset_x + w->framesize.x) - { - winscroll_hint(w, '>', size_x - ((offset_x + w->framesize.x) - 1)); - } - if (0 != w->next) - { - return draw_wins(w->next); - } -} - - - -static void draw_win_borderlines(struct Win * w) -{ - /* Draw vertical and horizontal border lines. */ - uint16_t y, x; - for (y = w->start.y; y <= w->start.y + w->framesize.y; y++) - { - mvwaddch(world.wmeta->pad, y, w->start.x - 1, '|'); - mvwaddch(world.wmeta->pad, y, w->start.x + w->framesize.x, '|'); - } - for (x = w->start.x; x <= w->start.x + w->framesize.x; x++) - { - mvwaddch(world.wmeta->pad, w->start.y - 1, x, '-'); - mvwaddch(world.wmeta->pad, w->start.y + w->framesize.y, x, '-'); - } - - /* Draw as much as possible of the title into center of top border line. */ - char min_title_length_visible = 3; /* min. 1 char + 2 padding/decoration */ - if (w->framesize.x >= min_title_length_visible) - { - uint16_t title_offset = 0; - if (w->framesize.x > strlen(w->title) + 2) - { - title_offset = (w->framesize.x - (strlen(w->title) + 2)) / 2; - } /* +2 is for padding/decoration */ - uint16_t length_visible = strnlen(w->title, w->framesize.x - 2); - char title[length_visible + 3]; - char decoration = ' '; - if (w == world.wmeta->active) - { - decoration = '$'; - } - memcpy(title + 1, w->title, length_visible); - title[0] = title[length_visible + 1] = decoration; - title[length_visible + 2] = '\0'; - mvwaddstr(world.wmeta->pad, - w->start.y - 1, w->start.x + title_offset, title); - } -} - - - -static void draw_wins_borderlines(struct Win * w) -{ - draw_win_borderlines(w); - if (0 != w->next) - { - draw_wins_borderlines(w->next); - } -} - - - -static void draw_wins_bordercorners(struct Win * w) -{ - mvwaddch(world.wmeta->pad, w->start.y - 1, w->start.x - 1, '+'); - mvwaddch(world.wmeta->pad, w->start.y - 1, w->start.x + w->framesize.x,'+'); - mvwaddch(world.wmeta->pad, w->start.y + w->framesize.y, w->start.x - 1,'+'); - mvwaddch(world.wmeta->pad, w->start.y + w->framesize.y, - w->start.x + w->framesize.x, '+'); - if (0 != w->next) - { - draw_wins_bordercorners(w->next); - } -} - - - -static void shift_win_forward() -{ - if (world.wmeta->active == world.wmeta->chain_end) - { - world.wmeta->chain_end = world.wmeta->active->prev; - world.wmeta->chain_end->next = 0; - world.wmeta->active->next = world.wmeta->chain_start; - world.wmeta->active->next->prev = world.wmeta->active; - world.wmeta->chain_start = world.wmeta->active; - world.wmeta->chain_start->prev = 0; - } - else - { - struct Win * old_prev = world.wmeta->active->prev; - struct Win * old_next = world.wmeta->active->next; - if (world.wmeta->chain_end == world.wmeta->active->next) - { - world.wmeta->chain_end = world.wmeta->active; - world.wmeta->active->next = 0; - } - else - { - world.wmeta->active->next = old_next->next; - world.wmeta->active->next->prev = world.wmeta->active; - } - if (world.wmeta->chain_start == world.wmeta->active) - { - world.wmeta->chain_start = old_next; - } - else - { - old_prev->next = old_next; - } - old_next->prev = old_prev; - old_next->next = world.wmeta->active; - world.wmeta->active->prev = old_next; - } -} - - - -static void shift_win_backward() -{ - if (world.wmeta->active == world.wmeta->chain_start) - { - world.wmeta->chain_start = world.wmeta->active->next; - world.wmeta->chain_start->prev = 0; - world.wmeta->active->prev = world.wmeta->chain_end; - world.wmeta->active->prev->next = world.wmeta->active; - world.wmeta->chain_end = world.wmeta->active; - world.wmeta->chain_end->next = 0; - } - else - { - struct Win * old_prev = world.wmeta->active->prev; - struct Win * old_next = world.wmeta->active->next; - if (world.wmeta->chain_start == world.wmeta->active->prev) - { - world.wmeta->chain_start = world.wmeta->active; - world.wmeta->active->prev = 0; - } - else - { - world.wmeta->active->prev = old_prev->prev; - world.wmeta->active->prev->next = world.wmeta->active; - } - if (world.wmeta->chain_end == world.wmeta->active) - { - world.wmeta->chain_end = old_prev; - } - else - { - old_next->prev = old_prev; - } - old_prev->next = old_next; - old_prev->prev = world.wmeta->active; - world.wmeta->active->next = old_prev; - } -} - - - -extern void init_win_meta() -{ - char * f_name = "init_win_meta()"; - char * err_s = "init_win_meta() creates virtual screen beyond legal size."; - char * err_m = "init_win_meta() triggers memory alloc error via newpad()."; - world.wmeta = try_malloc(sizeof(struct WinMeta), f_name); - world.wmeta->screen = initscr(); - uint32_t maxy_test = getmaxy(world.wmeta->screen); - uint32_t maxx_test = getmaxx(world.wmeta->screen); - exit_err(maxy_test > UINT16_MAX || maxx_test > UINT16_MAX, err_s); - world.wmeta->padsize.y = maxy_test; - world.wmeta->padsize.x = maxx_test; - world.wmeta->chain_start = 0; - world.wmeta->chain_end = 0; - world.wmeta->pad_offset = 0; - world.wmeta->pad = newpad(world.wmeta->padsize.y, 1); - exit_err(NULL == world.wmeta->pad, err_m); - world.wmeta->active = 0; -} - - - -extern void init_win(struct Win ** wp, char * title, int16_t height, - int16_t width, void * func) -{ - char * f_name = "init_win()"; - struct Win * w = try_malloc(sizeof(struct Win), f_name); - w->prev = 0; - w->next = 0; - w->winmapsize.y = 0; - w->winmapsize.x = 0; - w->winmap = NULL; - w->title = try_malloc(strlen(title) + 1, f_name); - sprintf(w->title, "%s", title); - w->draw = func; - w->center.y = 0; - w->center.x = 0; - if (0 < width) - { - w->framesize.x = width; - } - else if (0 >= width) - { - w->framesize.x = world.wmeta->padsize.x + width; - } - if (0 < height && height <= world.wmeta->padsize.y - 1) - { - w->framesize.y = height; - } - else if (0 >= height && world.wmeta->padsize.y + (height - 1) > 0) - { - w->framesize.y = world.wmeta->padsize.y + (height - 1); - } - *wp = w; -} - - - -extern void free_winmeta_and_endwin() -{ - delwin(world.wmeta->pad); - free(world.wmeta); - endwin(); -} - - - -extern void free_win(struct Win * win) -{ - free(win->title); - free(win); -} - - - -extern void append_win(struct Win * w) -{ - if (0 != world.wmeta->chain_start) - { - w->prev = world.wmeta->chain_end; - world.wmeta->chain_end->next = w; - } - else - { - world.wmeta->active = w; - world.wmeta->chain_start = w; - } - world.wmeta->chain_end = w; - update_wins(w); -} - - - -extern void suspend_win(struct Win * w) -{ - if (world.wmeta->chain_start != w) - { - w->prev->next = w->next; - } - else - { - world.wmeta->chain_start = w->next; - } - uint8_t pad_refitted = 0; - if (world.wmeta->chain_end != w) - { - w->next->prev = w->prev; - if (world.wmeta->active == w) - { - world.wmeta->active = w->next; - } - update_wins(w->next); /* Positioning of successor windows may be */ - pad_refitted = 1; /* affected / need correction. Note that */ - } /* update_wins() already refits the pad, */ - else /* voiding later need for that. */ - { - world.wmeta->chain_end = w->prev; - if (world.wmeta->active == w) - { - world.wmeta->active = w->prev; - } - } - w->prev = 0; - w->next = 0; - if (0 == pad_refitted) - { - refit_pad(); - } -} - - - -extern void reset_pad_offset(uint16_t new_offset) -{ - if (new_offset >= 0 - && (new_offset < world.wmeta->pad_offset - || new_offset + world.wmeta->padsize.x < getmaxx(world.wmeta->pad))) - { - world.wmeta->pad_offset = new_offset; - } -} - - - -extern void resize_active_win(struct yx_uint16 size) -{ - if (0 != world.wmeta->active - && size.x > 0 && size.y > 0 && size.y < world.wmeta->padsize.y) - { - world.wmeta->active->framesize = size; - update_wins(world.wmeta->active); /* Positioning of following */ - } /* windows may be affected. */ -} - - - -extern void cycle_active_win(char dir) -{ - if (0 != world.wmeta->active) - { - if ('f' == dir) - { - if (world.wmeta->active->next != 0) - { - world.wmeta->active = world.wmeta->active->next; - } - else - { - world.wmeta->active = world.wmeta->chain_start; - } - } - else - { - if (world.wmeta->active->prev != 0) - { - world.wmeta->active = world.wmeta->active->prev; - } - else - { - world.wmeta->active = world.wmeta->chain_end; - } - } - } -} - - - -extern void shift_active_win(char dir) -{ - if ( 0 == world.wmeta->active /* No shifting with < 2 windows visible. */ - || world.wmeta->chain_start == world.wmeta->chain_end) - { - return; - } - if ('f' == dir) - { - shift_win_forward(); - update_wins(world.wmeta->chain_start); - return; - } - shift_win_backward(); - update_wins(world.wmeta->chain_start); -} - - - -extern void draw_all_wins() -{ - /* Empty everything before filling it a-new. */ - erase(); - wnoutrefresh(world.wmeta->screen); - werase(world.wmeta->pad); - if (world.wmeta->chain_start) - { - - /* Draw windows' borders first, then windows. */ - draw_wins_borderlines(world.wmeta->chain_start); - draw_wins_bordercorners(world.wmeta->chain_start); - draw_wins(world.wmeta->chain_start); - - /* Draw virtual screen scroll hints. */ - if (world.wmeta->pad_offset > 0) - { - padscroll_hint('<', world.wmeta->pad_offset + 1); - } - uint16_t size_x = getmaxx(world.wmeta->pad); - uint16_t right_edge = world.wmeta->pad_offset + world.wmeta->padsize.x; - if (right_edge < size_x - 1) - { - padscroll_hint('>', size_x - right_edge); - } - - /* Write pad segment to be shown on physical screen to screen buffer. */ - pnoutrefresh(world.wmeta->pad, 0, world.wmeta->pad_offset, 0, 0, - world.wmeta->padsize.y, world.wmeta->padsize.x - 1); - } - - /* Only at the end write accumulated changes to the physical screen. */ - doupdate(); -} diff --git a/src/windows.h b/src/windows.h deleted file mode 100644 index 8485321..0000000 --- a/src/windows.h +++ /dev/null @@ -1,132 +0,0 @@ -/* windows.h - * - * A tiled window manager for the terminal. - * - * It provides a virtual screen that can be scrolled horizontally and may carry - * any number of windows to be appeared, disappeared, resized and moved around. - * They have borders and a title bar and are positioned automatically. - * - * Windows can be any width between 1 and 2^16 cells. The virtual screen grows - * with them as needed -- but only horizontally and only up to 2^16 cells. Their - * height is limited by the height of the terminal screen (maximum 2^16 cells). - * - * Windows' positioning can be influenced only indirectly: by resizing them, and - * by shifting their relative position inside the (currently invisible) chain - * that the window manager treats their plurality as. The first window goes into - * the top left corner of the virtual screen. Further windows are fitted as - * left-aligned as possible below their (chain-wise) closest predecessor that - * thrones over enough space to contain them and that is open to the right. If - * that fails, they're fitted up-right to the window with the rightmost border. - * - * TODO: Think up a more intuitive window positioning algorithm. - */ - -#ifndef WINDOWS_H -#define WINDOWS_H - -#include /* for uint8_t, uint16_t, uint32_t */ -#include /* for the WINDOW and chtype typedefs */ -#include "yx_uint16.h" /* for yx_uint16 struct */ - - - -/* "Win" structs describe windows as frames located inside the virtual screen - * pad through which "winmaps" are visible, 2-dimensional maps of ncurses - * chtypes. If a winmap is bigger than its frame, scrolling hints will appear at - * the proper edges. Win structs are chained into a linked list of all the - * windows visible on the virtual screen and also contain pointers to what - * content is to be drawn inside the window, and by use of what method. - */ -struct Win -{ - struct Win * prev; /* chain pointers; if 0, they mark the start or end */ - struct Win * next; /* of the chain; if both are 0, Win is outside chain */ - struct yx_uint16 framesize; /* window frame size to see winmap through */ - struct yx_uint16 start; /* upper left corner of window in pad */ - struct yx_uint16 center; /* winmap cell to center frame on if smaller*/ - char * title; /* title to be used in window title bar */ - void (* draw) (struct Win *); /* function that draws/updates the winmap */ - chtype * winmap; /* sequence of cells, sorted into lines ... */ - struct yx_uint16 winmapsize; /* ... with these geometry infos */ -}; - -/* The window manager's parent struct WinMeta contains the virtual screen, - * relates it to the terminal screen and anchors the chain of visible windows. - */ -struct WinMeta -{ - WINDOW * screen; /* ncurses' pointer to the terminal screen */ - WINDOW * pad; /* ncurses pad of virtual screen */ - uint16_t pad_offset; /* number of cells view is moved to the right */ - struct yx_uint16 padsize; /* virtual screen size */ - struct Win * chain_start; /* first Win in chain; its _prev == 0 */ - struct Win * chain_end; /* last Win in chain; its _next == 0 */ - struct Win * active; /* Win highlighted/selected for manipulation */ -}; - - - -/* Initialize empty "wmeta" on terminal screen. All struct members initialize to - * 0, except for .screen, the newly created virtual screen .pad and its .padsize - * (height: that of the terminal screen; width: 1 cell). - */ -extern void init_win_meta(); - -/* Initialize a Win child "wp" of "wmeta" to "title", "height" and "width" and - * appoint "func"() as its .draw. Initialize other members to 0. - * - * Pass 0 for "width" to make the window as wide as the terminal screen. Pass 0 - * for "height" for the maximum allowed height: one cell smaller than that of - * the terminal screen. Pass negative values for either of them to make the - * window width/height so many cells smaller than what 0 would set. The maximum - * allowed height is also applied for positive height values that exceed it or - * negative values that would reduce the window height to less than 1 cell. - */ -extern void init_win(struct Win ** wp, char * title, int16_t height, - int16_t width, void * func); - -/* Free memory initialized Win/WinMeta structs; endwin() for the latter, too. */ -extern void free_winmeta_and_endwin(); -extern void free_win(struct Win * win); - -/* Append/suspend window "w" to/from chain of visible windows below "wmeta". - * Appended windows will become active. Suspended active windows will move the - * active window selection to their successor in the window chain or, failing - * that, their predecessor, or, failing that, to 0 (no window active). - */ -extern void append_win(struct Win * w); -extern void suspend_win(struct Win * w); - -/* Apply scrolling offset "new_offset" to virtual screen if it is equal/greater - * 0 and does not push the view (further) beyond the virtual screen's border. If - * the view is already beyond the virtual screen's border due to it having - * shrunk after suspension of windows, only allow screen scrolling leftwards. - */ -extern void reset_pad_offset(uint16_t new_offset); - -/* Apply "size" to the active window if it provides a minimum size of 1x1 cells - * and is in height at least one cell smaller than the screen's vertical height - * (to provide space for the title bar). Does nothing if no window is active. - */ -extern void resize_active_win(struct yx_uint16 size); - -/* Cycle active window selection forwards ("dir" == "f") or backwards (any - * other "dir"). Wrap around in the windows chain if start / end of it is met. - * Does nothing if no window is active. - */ -extern void cycle_active_win(char dir); - -/* Move active window forwards ("dir" == "f") or backwards (any other "dir") in - * the window chain. Wrap around in the window chain if start / end of it is - * met. Does nothing if no window is active. - */ -extern void shift_active_win(char dir); - -/* Draw virtual screen and its windows. Add scroll hints where edges of terminal - * screen hit non-edges inside the virtual screen. Then update terminal screen. - */ -extern void draw_all_wins(); - - - -#endif diff --git a/src/yx_uint16.c b/src/yx_uint16.c deleted file mode 100644 index c980693..0000000 --- a/src/yx_uint16.c +++ /dev/null @@ -1,38 +0,0 @@ -/* yx_uint16.c */ - -#include "yx_uint16.h" -#include /* for uint8_t, uint16_t */ - - - -extern uint8_t yx_uint16_cmp(struct yx_uint16 * a, struct yx_uint16 * b) -{ - if (a->y == b->y && a->x == b->x) - { - return 1; - } - return 0; -} - - - -extern struct yx_uint16 mv_yx_in_dir(char d, struct yx_uint16 yx) -{ - if (d == 'N' && yx.y > 0) - { - yx.y--; - } - else if (d == 'E' && yx.x < UINT16_MAX) - { - yx.x++; - } - else if (d == 'S' && yx.y < UINT16_MAX) - { - yx.y++; - } - else if (d == 'W' && yx.x > 0) - { - yx.x--; - } - return yx; -} diff --git a/src/yx_uint16.h b/src/yx_uint16.h deleted file mode 100644 index f6f90a3..0000000 --- a/src/yx_uint16.h +++ /dev/null @@ -1,34 +0,0 @@ -/* yx_uint16.h - * - * Structs and routines for coordinates and movement in 2-dimensional space - * (such as the ncurses screen and game maps). - */ - -#ifndef YX_UINT16_H -#define YX_UINT16_H - -#include /* for uint8_t, uint16_t */ - - - -/* Coordinates for maps of max. 65536x65536 cells. */ -struct yx_uint16 -{ - uint16_t y; - uint16_t x; -}; - - - -/* Return 1 if two yx_uint16 coordinates at "a" and "b" are equal, else 0. */ -extern uint8_t yx_uint16_cmp(struct yx_uint16 * a, struct yx_uint16 * b); - -/* Return yx_uint16 coordinate one step from "yx" in direction "dir" (east: 'E', - * west: 'W', north: 'N', south: 'S'). If "dir" is invalid or would wrap the - * move around the edge of a 2^16x2^16 cells field, "yx" remains unchanged. - */ -extern struct yx_uint16 mv_yx_in_dir(char dir, struct yx_uint16 yx); - - - -#endif