From be432a9c64df2493576ef9149be136887b54a910 Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 00:28:45 -0600 Subject: [PATCH 1/8] Fixed build on modern GCC and map array bounds --- Makefile | 2 +- game.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8158eb7..f59f783 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ LIBS = -lncurses # You shouldn't have to modify anything below this line. # There's a dynamic format in the object-display routines; suppress the warning -CFLAGS = $(DEBUG) $(PROFILE) -Wall -Wno-format-security +CFLAGS = $(DEBUG) $(PROFILE) -Wall -Wno-format-security -fcommon FILES = \ attack.c \ diff --git a/game.c b/game.c index dd3d3eb..6d95c54 100644 --- a/game.c +++ b/game.c @@ -123,7 +123,7 @@ void make_map(void) { /* count the number of cells at each height */ for (i = 0; i <= MAX_HEIGHT; i++) height_count[i] = 0; - for (i = 0; i <= MAP_SIZE; i++) height_count[height[from][i]]++; + for (i = 0; i < MAP_SIZE; i++) height_count[height[from][i]]++; /* find the water line */ loc = MAX_HEIGHT; /* default to all water */ From d7ca2bbf2401c079ee4eda22c1d551826b797f92 Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 00:58:01 -0600 Subject: [PATCH 2/8] Update README with Raspberry Pi build instructions --- README | 106 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/README b/README index b631e3f..7873595 100644 --- a/README +++ b/README @@ -1,19 +1,95 @@ - README for vms-empire +# VMS Empire -VMS-Empire is a simulation of a full-scale war between two -emperors, the computer and you. Naturally, there is only -room for one, so the object of the game is to destroy -the other. The computer plays by the same rules that you -do. This game is the ancestor of all the multiplayer 4X -simulations out there, including Civilization and Master of Orion. +VMS Empire is a classic turn-based strategy game played in a text terminal. Units, cities, land, and water are represented using letters and ASCII characters. -This code originated under VMS but has been forward-ported to -modern ANSI C. It is known to run not only on Linux but -also under Windows and Mac OS X; all you need is a local -port of the curses library. +This fork includes small compatibility fixes that allow the game to compile cleanly on modern Linux systems, including Raspberry Pi OS. -The files HACKING and BUGS will be of interest if you intend -to modify this code. +## Changes in this fork + +* Added the GCC `-fcommon` compiler option to fix multiple-definition linker errors on modern GCC versions. +* Corrected two map-array loops in `game.c`. +* Changed: + +```c +i <= MAP_SIZE +``` + +to: + +```c +i < MAP_SIZE +``` + +This prevents the loops from accessing one element beyond the end of the arrays. + +## Requirements + +Install the compiler, development tools, Git, and ncurses library: + +```bash +sudo apt update +sudo apt install -y git build-essential libncurses-dev +``` + +## Download + +Clone this fork: + +```bash +git clone https://github.com/dgdimick/empire.git +cd empire +``` + +## Build + +Compile the game: + +```bash +make +``` + +The executable will be created as: + +```text +vms-empire +``` + +## Run + +Start the game with: + +```bash +./vms-empire +``` + +## Clean and rebuild + +To remove compiled files and rebuild from scratch: + +```bash +make clean +make +``` + +## Repository remotes + +This fork is maintained at: + +```text +https://github.com/dgdimick/empire +``` + +The original repository is: + +```text +https://github.com/slacy/empire +``` + +## Platform tested + +The changes in this fork were tested on Raspberry Pi OS using a Raspberry Pi. + +## License + +This fork retains the original project’s licensing and copyright terms. See the repository files for the original license information. -The ancestral code was by Chuck Simmons . Since -around 1990 it has been maintained by Eric S. Raymond From d81ac68fce4f86f445ab9aa857ad96cbd3148cfe Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 01:05:51 -0600 Subject: [PATCH 3/8] Update README to show to change line 126 in game.c from for (i = 0; i <= MAP_SIZE; i++.... to for (i = 0; i < MAP_SIZE; i++... --- README | 1 + 1 file changed, 1 insertion(+) diff --git a/README b/README index 7873595..e29522a 100644 --- a/README +++ b/README @@ -10,6 +10,7 @@ This fork includes small compatibility fixes that allow the game to compile clea * Corrected two map-array loops in `game.c`. * Changed: +In line 126 ```c i <= MAP_SIZE ``` From ba3a4c9ce89d9cb4feedc883d8a16e7a11a3ee40 Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 02:32:46 -0600 Subject: [PATCH 4/8] Add AI versus AI spectator mode with colored teams --- README | 20 +++++++++ compmove.c | 3 +- display.c | 23 +++++++++++ empire.c | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++ extern.h | 3 ++ game.c | 14 +++++-- main.c | 8 +++- 7 files changed, 181 insertions(+), 7 deletions(-) diff --git a/README b/README index e29522a..c662bd7 100644 --- a/README +++ b/README @@ -6,6 +6,8 @@ This fork includes small compatibility fixes that allow the game to compile clea ## Changes in this fork + +* Added AI-vers-AI support : "vms-empire -a -d 500" where 500 is the amount of time between AI turns * Added the GCC `-fcommon` compiler option to fix multiple-definition linker errors on modern GCC versions. * Corrected two map-array loops in `game.c`. * Changed: @@ -94,3 +96,21 @@ The changes in this fork were tested on Raspberry Pi OS using a Raspberry Pi. This fork retains the original project’s licensing and copyright terms. See the repository files for the original license information. + + +AI vs AI spectator mode +----------------------- + +This fork adds an unattended spectator mode where the existing computer +player controls both empires. Start it with: + + ./vms-empire -a + +Use the existing -d option to set the delay in milliseconds between map +updates. For example: + + ./vms-empire -a -d 500 + +The full battlefield is shown using the condensed map display. The USER-side AI +is shown in blue and the COMP-side AI is shown in red on color terminals. Press Ctrl-C +to stop an active game. At the end of a game, press any key to exit. diff --git a/compmove.c b/compmove.c index 1f24d88..83cd89a 100644 --- a/compmove.c +++ b/compmove.c @@ -1061,7 +1061,8 @@ void check_endgame(void) { for (p = comp_obj[ARMY]; p != NULL; p = p->piece_link.next) ncomp_army++; - if (ncomp_city < nuser_city / 3 && ncomp_army < nuser_army / 3) { + if (!ai_vs_ai && ncomp_city < nuser_city / 3 && + ncomp_army < nuser_army / 3) { clear_screen(); prompt("The computer acknowledges defeat. Do"); ksend("The computer acknowledges defeat."); diff --git a/display.c b/display.c index a3a6ea1..2110eed 100644 --- a/display.c +++ b/display.c @@ -430,6 +430,9 @@ void print_zoom_cell(view_map_t *vmap, int row, int col, int row_inc, int col_inc) { int r, c; char cell; +#ifdef A_COLOR + chtype attr = COLOR_PAIR(COLOR_WHITE); +#endif cell = ' '; for (r = row; r < row + row_inc; r++) @@ -438,8 +441,28 @@ void print_zoom_cell(view_map_t *vmap, int row, int col, int row_inc, strchr(zoom_list, cell)) cell = vmap[row_col_loc(r, c)].contents; +#ifdef A_COLOR + if (ai_vs_ai) { + /* In spectator mode, USER is the Blue AI and COMP is the Red AI. */ + if (cell == 'O' || strchr("TCBSDPFAZ", cell) != NULL) + attr = COLOR_PAIR(COLOR_BLUE) | A_BOLD; + else if (cell == 'X' || strchr("tcbsdpafz", cell) != NULL) + attr = COLOR_PAIR(COLOR_RED) | A_BOLD; + else if (cell == MAP_LAND || cell == '+') + attr = COLOR_PAIR(COLOR_GREEN); + else if (cell == MAP_SEA || cell == '.') + attr = COLOR_PAIR(COLOR_CYAN); + } + attron(attr); +#endif + (void)move(row / row_inc + NUMTOPS, col / col_inc); (void)addch((chtype)cell); + +#ifdef A_COLOR + attroff(attr); + attron(COLOR_PAIR(COLOR_WHITE)); +#endif } /* diff --git a/empire.c b/empire.c index 29d6f97..50f86ef 100644 --- a/empire.c +++ b/empire.c @@ -12,9 +12,14 @@ parser, and the simple commands. #include "empire.h" #include +#include #include "extern.h" void c_examine(void), c_movie(void); +static void ai_vs_ai_loop(void); +static void swap_sides(void); +static void reveal_spectator_map(void); +static void flip_view_symbols(view_map_t vmap[]); /* * 03a 01Apr88 aml .Hacked movement algorithms for computer. @@ -42,6 +47,8 @@ void empire(void) { if (!restore_game()) /* try to restore previous game */ init_game(); /* otherwise init a new game */ + if (ai_vs_ai) ai_vs_ai_loop(); + /* Command loop starts here. */ for (;;) { /* until user quits */ @@ -345,3 +352,113 @@ void c_movie(void) { } /* end */ + + +/* + * Run both empires with the existing computer player. The computer + * routines are written specifically for the COMP side, so the second + * half-turn is performed by swapping the two sides, calling comp_move(), + * and then swapping them back. + */ +static void ai_vs_ai_loop(void) { + for (;;) { + reveal_spectator_map(); + print_zoom(user_map); + topmsg(1, "AI vs AI spectator mode - round %ld", date); + topmsg(2, "Ctrl-C stops the game; -d controls the delay."); + delay(); + + comp_move(1); + if (win != no_win) break; + + swap_sides(); + comp_move(1); + swap_sides(); + if (win != no_win) break; + + save_game(); + } + + reveal_spectator_map(); + print_zoom(user_map); + topmsg(1, "AI vs AI game finished at round %ld.", date); + topmsg(2, "Press any key to exit."); + (void)get_chx(); + empend(); +} + +static void swap_sides(void) { + static view_map_t temp_map[MAP_SIZE]; + piece_info_t *temp_obj; + int temp_score; + int i; + + (void)memcpy(temp_map, user_map, sizeof(temp_map)); + (void)memcpy(user_map, comp_map, sizeof(temp_map)); + (void)memcpy(comp_map, temp_map, sizeof(temp_map)); + + /* Each view map encodes USER pieces/cities as uppercase/O and COMP + pieces/cities as lowercase/X. Since ownership is about to be + reversed, reverse those symbols too or the AI will mistake enemy + cities for its own. */ + flip_view_symbols(user_map); + flip_view_symbols(comp_map); + + for (i = 0; i < NUM_OBJECTS; i++) { + temp_obj = user_obj[i]; + user_obj[i] = comp_obj[i]; + comp_obj[i] = temp_obj; + } + + for (i = 0; i < LIST_SIZE; i++) { + if (object[i].owner == USER) + object[i].owner = COMP; + else if (object[i].owner == COMP) + object[i].owner = USER; + } + + for (i = 0; i < NUM_CITY; i++) { + if (city[i].owner == USER) + city[i].owner = COMP; + else if (city[i].owner == COMP) + city[i].owner = USER; + } + + temp_score = user_score; + user_score = comp_score; + comp_score = temp_score; +} + + +static void flip_view_symbols(view_map_t vmap[]) { + int i; + char c; + + for (i = 0; i < MAP_SIZE; i++) { + c = vmap[i].contents; + if (c == 'O') + vmap[i].contents = 'X'; + else if (c == 'X') + vmap[i].contents = 'O'; + else { + switch (c) { + case 'A': case 'F': case 'P': case 'S': case 'D': + case 'T': case 'C': case 'B': case 'Z': + vmap[i].contents = c - 'A' + 'a'; + break; + case 'a': case 'f': case 'p': case 's': case 'd': + case 't': case 'c': case 'b': case 'z': + vmap[i].contents = c - 'a' + 'A'; + break; + } + } + } +} + +static void reveal_spectator_map(void) { + int i; + + for (i = 0; i < MAP_SIZE; i++) { + if (map[i].on_board) update(user_map, i); + } +} diff --git a/extern.h b/extern.h index cdf5d41..43aa9fd 100644 --- a/extern.h +++ b/extern.h @@ -72,6 +72,7 @@ extern int user_lines; /* miscellaneous */ long date; /* number of game turns played */ bool automove; /* true iff user is in automove mode */ +bool ai_vs_ai; /* true iff both sides are computer controlled */ bool resigned; /* true iff computer resigned */ bool debug; /* true iff in debugging mode */ bool print_debug; /* true iff we print debugging stuff */ @@ -106,6 +107,7 @@ char *savefile; /* global routines */ void empire(void); +void empend(void); void attack(piece_info_t *att_obj, long loc); void comp_move(int nmoves); @@ -208,6 +210,7 @@ void embark(piece_info_t *ship, piece_info_t *obj); void disembark(piece_info_t *obj); void describe_obj(piece_info_t *obj); void scan(view_map_t vmap[], long loc); +void update(view_map_t vmap[], long loc); void scan_sat(view_map_t *vmap, long loc); void set_prod(city_info_t *cityp); diff --git a/game.c b/game.c index 6d95c54..fcf40c3 100644 --- a/game.c +++ b/game.c @@ -307,7 +307,10 @@ bool select_cities(void) { "Choose a difficulty level where 0 is easy and %d is hard: ", ncont * ncont - 1); - pair = get_range(jnkbuf, 0, ncont * ncont - 1); + if (ai_vs_ai) + pair = (ncont * ncont - 1) / 2; + else + pair = get_range(jnkbuf, 0, ncont * ncont - 1); comp_cont = pair_tab[pair].comp_cont; user_cont = pair_tab[pair].user_cont; @@ -319,8 +322,10 @@ bool select_cities(void) { userp = cont_tab[user_cont].cityp[useri]; } while (userp == compp); - topmsg(1, "Your city is at %d.", loc_disp(userp->loc)); - delay(); /* let user see output before we set_prod */ + if (!ai_vs_ai) { + topmsg(1, "Your city is at %d.", loc_disp(userp->loc)); + delay(); /* let user see output before we set_prod */ + } /* update city and map */ compp->owner = COMP; @@ -330,8 +335,9 @@ bool select_cities(void) { userp->owner = USER; userp->work = 0; + userp->prod = ARMY; scan(user_map, userp->loc); - set_prod(userp); + if (!ai_vs_ai) set_prod(userp); return (true); } diff --git a/main.c b/main.c index 6786995..8f4a852 100644 --- a/main.c +++ b/main.c @@ -29,7 +29,7 @@ main.c -- parse command line for empire #include "empire.h" #include "extern.h" -#define OPTFLAGS "w:s:d:S:f:" +#define OPTFLAGS "aw:s:d:S:f:" int main(argc, argv) int argc; char *argv[]; @@ -46,6 +46,7 @@ char *argv[]; dflg = 2000; Sflg = 10; savefile = "empsave.dat"; + ai_vs_ai = false; /* * extract command line options @@ -53,6 +54,9 @@ char *argv[]; while ((c = getopt(argc, argv, OPTFLAGS)) != EOF) { switch (c) { + case 'a': + ai_vs_ai = true; + break; case 'w': wflg = atoi(optarg); break; @@ -74,7 +78,7 @@ char *argv[]; } } if (errflg || (argc - optind) != 0) { - (void)printf("empire: usage: empire [-w water] [-s smooth] [-d delay]\n"); + (void)printf("empire: usage: empire [-a] [-w water] [-s smooth] [-d delay]\n"); exit(1); } From 7dee92ab8f9eb20c0e3230e8ded1fecd4c7deb03 Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 03:36:31 -0600 Subject: [PATCH 5/8] Add AI spectator views and update build cleanup --- Makefile | 2 +- README | 180 +++++++++++++++++++++++++++++++++++++++++++++---------- empire.c | 99 ++++++++++++++++++++++++++---- empire.h | 7 +++ extern.h | 1 + main.c | 20 ++++++- 6 files changed, 263 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index f59f783..8c3dbc0 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,7 @@ uninstall: clean: rm -f *.o TAGS vms-empire - rm -f *.6 *.html + rm -f *.html clobber: clean rm -f vms-empire vms-empire-*.tar* diff --git a/README b/README index c662bd7..a6eaaef 100644 --- a/README +++ b/README @@ -2,17 +2,20 @@ VMS Empire is a classic turn-based strategy game played in a text terminal. Units, cities, land, and water are represented using letters and ASCII characters. -This fork includes small compatibility fixes that allow the game to compile cleanly on modern Linux systems, including Raspberry Pi OS. +This fork includes compatibility fixes for modern Linux systems, including Raspberry Pi OS, along with an AI-versus-AI spectator mode. -## Changes in this fork +## Changes in This Fork - -* Added AI-vers-AI support : "vms-empire -a -d 500" where 500 is the amount of time between AI turns +* Added AI-versus-AI spectator mode. +* Added selectable Blue, Red, Shared, and Full spectator views. +* Added live view switching during AI matches. +* Added blue and red terminal colors for the two AI empires. +* Added an end-of-game prompt to keep or delete `empsave.dat` and `info_list.txt`. * Added the GCC `-fcommon` compiler option to fix multiple-definition linker errors on modern GCC versions. * Corrected two map-array loops in `game.c`. -* Changed: -In line 126 +The affected loops were changed from: + ```c i <= MAP_SIZE ``` @@ -23,7 +26,7 @@ to: i < MAP_SIZE ``` -This prevents the loops from accessing one element beyond the end of the arrays. +This prevents the loops from accessing one element beyond the end of the map arrays. ## Requirements @@ -57,15 +60,134 @@ The executable will be created as: vms-empire ``` -## Run +## Normal Game Mode -Start the game with: +Start the normal human-versus-computer game with: ```bash ./vms-empire ``` -## Clean and rebuild +The AI additions do not replace or disable normal gameplay. + +## AI-versus-AI Spectator Mode + +Start an unattended match where the computer controls both empires: + +```bash +./vms-empire -a +``` + +The `-a` option enables AI-versus-AI mode. + +Use the `-d` option to set the delay between map updates in milliseconds. + +Example with a 500-millisecond delay: + +```bash +./vms-empire -a -d 500 +``` + +Examples: + +```bash +./vms-empire -a -d 1000 +./vms-empire -a -d 500 +./vms-empire -a -d 100 +``` + +* `1000` = one second between updates +* `500` = half a second between updates +* `100` = fast playback + +The Blue AI is displayed in blue and the Red AI is displayed in red on terminals that support color. + +Press `Ctrl-C` to stop an active match. + +## AI Spectator Views + +The AI spectator mode supports four starting views. + +### Blue fog-of-war view + +Shows only territory and units known to the Blue AI: + +```bash +./vms-empire -a -v blue +``` + +### Red fog-of-war view + +Shows only territory and units known to the Red AI: + +```bash +./vms-empire -a -v red +``` + +### Shared fog-of-war view + +Shows anything discovered by either AI: + +```bash +./vms-empire -a -v shared +``` + +### Full spectator view + +Shows the entire battlefield: + +```bash +./vms-empire -a -v full +``` + +The view option can be combined with the delay option: + +```bash +./vms-empire -a -v shared -d 500 +``` + +If `-v` is omitted, the game starts in Shared view: + +```bash +./vms-empire -a -d 500 +``` + +## Change Views During a Match + +While an AI-versus-AI match is running, press: + +```text +B = Blue fog-of-war view +R = Red fog-of-war view +S = Shared fog-of-war view +F = Full spectator view +``` + +These commands only change what the spectator sees. They do not alter either AI’s private map or give either side additional information. + +## End-of-Game Save Prompt + +At the end of an AI-versus-AI match, the game asks: + +```text +Keep empsave.dat and info_list.txt? (Y/N) +``` + +Choose: + +```text +Y = Keep both files +N = Delete both files +``` + +The files are: + +* `empsave.dat` — the saved game state +* `info_list.txt` — the recorded move and game information + +The final game position is saved before the prompt appears. + +## Clean and Rebuild To remove compiled files and rebuild from scratch: @@ -74,7 +196,22 @@ make clean make ``` -## Repository remotes +A normal repository checkout should not contain compiled `.o` files or the `vms-empire` executable. + +## Suggested PuTTY Window Size + +The game map is 100 columns by 60 rows. + +For a full-size spectator display in PuTTY, use approximately: + +```text +Columns: 110 +Rows: 65 +``` + +Smaller terminals will use the condensed map display. + +## Repository Remotes This fork is maintained at: @@ -88,29 +225,10 @@ The original repository is: https://github.com/slacy/empire ``` -## Platform tested +## Platform Tested The changes in this fork were tested on Raspberry Pi OS using a Raspberry Pi. ## License This fork retains the original project’s licensing and copyright terms. See the repository files for the original license information. - - - -AI vs AI spectator mode ------------------------ - -This fork adds an unattended spectator mode where the existing computer -player controls both empires. Start it with: - - ./vms-empire -a - -Use the existing -d option to set the delay in milliseconds between map -updates. For example: - - ./vms-empire -a -d 500 - -The full battlefield is shown using the condensed map display. The USER-side AI -is shown in blue and the COMP-side AI is shown in red on color terminals. Press Ctrl-C -to stop an active game. At the end of a game, press any key to exit. diff --git a/empire.c b/empire.c index 50f86ef..e3e4954 100644 --- a/empire.c +++ b/empire.c @@ -11,6 +11,8 @@ parser, and the simple commands. */ #include "empire.h" +#include +#include #include #include #include "extern.h" @@ -18,8 +20,11 @@ parser, and the simple commands. void c_examine(void), c_movie(void); static void ai_vs_ai_loop(void); static void swap_sides(void); -static void reveal_spectator_map(void); +static void build_spectator_map(void); +static void ai_spectator_delay(void); +static const char *spectator_view_name(void); static void flip_view_symbols(view_map_t vmap[]); +static view_map_t spectator_map[MAP_SIZE]; /* * 03a 01Apr88 aml .Hacked movement algorithms for computer. @@ -362,11 +367,11 @@ void c_movie(void) { */ static void ai_vs_ai_loop(void) { for (;;) { - reveal_spectator_map(); - print_zoom(user_map); - topmsg(1, "AI vs AI spectator mode - round %ld", date); - topmsg(2, "Ctrl-C stops the game; -d controls the delay."); - delay(); + build_spectator_map(); + print_zoom(spectator_map); + topmsg(1, "AI vs AI - round %ld - %s view", date, spectator_view_name()); + topmsg(2, "B Blue R Red S Shared F Full Ctrl-C stops"); + ai_spectator_delay(); comp_move(1); if (win != no_win) break; @@ -379,11 +384,22 @@ static void ai_vs_ai_loop(void) { save_game(); } - reveal_spectator_map(); - print_zoom(user_map); + /* Save the final position before offering to keep or discard the + AI-match files. */ + save_game(); + + build_spectator_map(); + print_zoom(spectator_map); topmsg(1, "AI vs AI game finished at round %ld.", date); - topmsg(2, "Press any key to exit."); - (void)get_chx(); + topmsg(2, "Y keeps both files; N deletes both files."); + + if (!getyn("Keep empsave.dat and info_list.txt? (Y/N) ")) { + if (remove(savefile) != 0) + error("Could not delete %s.", savefile); + if (remove("info_list.txt") != 0) + error("Could not delete info_list.txt."); + } + empend(); } @@ -455,10 +471,69 @@ static void flip_view_symbols(view_map_t vmap[]) { } } -static void reveal_spectator_map(void) { +static const char *spectator_view_name(void) { + switch (spectator_view) { + case VIEW_BLUE: return "Blue fog"; + case VIEW_RED: return "Red fog"; + case VIEW_FULL: return "Full"; + default: return "Shared fog"; + } +} + +static void build_spectator_map(void) { int i; for (i = 0; i < MAP_SIZE; i++) { - if (map[i].on_board) update(user_map, i); + spectator_map[i].contents = ' '; + spectator_map[i].seen = false; + + if (spectator_view == VIEW_BLUE) { + spectator_map[i] = user_map[i]; + } + else if (spectator_view == VIEW_RED) { + spectator_map[i] = comp_map[i]; + } + else if (spectator_view == VIEW_FULL) { + if (map[i].on_board) update(spectator_map, i); + } + else { /* shared fog: show anything discovered by either AI */ + if (user_map[i].seen || comp_map[i].seen) { + spectator_map[i].seen = true; + if (map[i].on_board) + update(spectator_map, i); + else if (user_map[i].seen) + spectator_map[i].contents = user_map[i].contents; + else + spectator_map[i].contents = comp_map[i].contents; + } + } } } + +static void ai_spectator_delay(void) { + int remaining = delay_time; + int c; + int slice; + + (void)refresh(); + (void)nodelay(stdscr, TRUE); + + do { + c = getch(); + if (c != ERR) { + switch (toupper(c)) { + case 'B': spectator_view = VIEW_BLUE; break; + case 'R': spectator_view = VIEW_RED; break; + case 'S': spectator_view = VIEW_SHARED; break; + case 'F': spectator_view = VIEW_FULL; break; + } + } + + if (remaining <= 0) break; + slice = (remaining > 50) ? 50 : remaining; + (void)napms(slice); + remaining -= slice; + } while (remaining > 0); + + (void)nodelay(stdscr, FALSE); +} \ No newline at end of file diff --git a/empire.h b/empire.h index 5c22a60..b3b719b 100644 --- a/empire.h +++ b/empire.h @@ -218,6 +218,13 @@ typedef struct real_map { /* a cell of the actual map */ piece_info_t *objp; /* list of objects at this location */ } real_map_t; + +/* AI-versus-AI spectator view modes. */ +#define VIEW_BLUE 0 +#define VIEW_RED 1 +#define VIEW_SHARED 2 +#define VIEW_FULL 3 + typedef struct view_map { /* a cell of one player's world view */ char contents; /* MAP_LAND, MAP_SEA, MAP_CITY, 'A', 'a', etc */ long seen; /* date when last updated */ diff --git a/extern.h b/extern.h index 43aa9fd..1b9c1ed 100644 --- a/extern.h +++ b/extern.h @@ -73,6 +73,7 @@ extern int user_lines; long date; /* number of game turns played */ bool automove; /* true iff user is in automove mode */ bool ai_vs_ai; /* true iff both sides are computer controlled */ +int spectator_view; /* AI spectator view: blue, red, shared, or full */ bool resigned; /* true iff computer resigned */ bool debug; /* true iff in debugging mode */ bool print_debug; /* true iff we print debugging stuff */ diff --git a/main.c b/main.c index 8f4a852..b61ca92 100644 --- a/main.c +++ b/main.c @@ -25,11 +25,12 @@ main.c -- parse command line for empire #include #include +#include #include #include "empire.h" #include "extern.h" -#define OPTFLAGS "aw:s:d:S:f:" +#define OPTFLAGS "av:w:s:d:S:f:" int main(argc, argv) int argc; char *argv[]; @@ -47,6 +48,7 @@ char *argv[]; Sflg = 10; savefile = "empsave.dat"; ai_vs_ai = false; + spectator_view = VIEW_SHARED; /* * extract command line options @@ -57,6 +59,20 @@ char *argv[]; case 'a': ai_vs_ai = true; break; + case 'v': + if (strcmp(optarg, "blue") == 0) + spectator_view = VIEW_BLUE; + else if (strcmp(optarg, "red") == 0) + spectator_view = VIEW_RED; + else if (strcmp(optarg, "shared") == 0) + spectator_view = VIEW_SHARED; + else if (strcmp(optarg, "full") == 0) + spectator_view = VIEW_FULL; + else { + (void)printf("empire: -v must be blue, red, shared, or full.\n"); + exit(1); + } + break; case 'w': wflg = atoi(optarg); break; @@ -78,7 +94,7 @@ char *argv[]; } } if (errflg || (argc - optind) != 0) { - (void)printf("empire: usage: empire [-a] [-w water] [-s smooth] [-d delay]\n"); + (void)printf("empire: usage: empire [-a] [-v blue|red|shared|full] [-w water] [-s smooth] [-d delay]\n"); exit(1); } From 26d0fa90e2acdbc9575c841541fe9ae47b8c5120 Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 03:41:31 -0600 Subject: [PATCH 6/8] Rename README for Markdown rendering --- README => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.md (100%) diff --git a/README b/README.md similarity index 100% rename from README rename to README.md From 73e771b3c473937c1e3148c0170737ab098e62ac Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 04:07:33 -0600 Subject: [PATCH 7/8] Add startup menu and update AI spectator mode --- README.md | 188 ++++++++++++------------------------------------------ empire.c | 5 +- game.c | 2 +- main.c | 63 ++++++++++++++++++ 4 files changed, 108 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index a6eaaef..861bc49 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,15 @@ VMS Empire is a classic turn-based strategy game played in a text terminal. Units, cities, land, and water are represented using letters and ASCII characters. -This fork includes compatibility fixes for modern Linux systems, including Raspberry Pi OS, along with an AI-versus-AI spectator mode. +This fork includes small compatibility fixes that allow the game to compile cleanly on modern Linux systems, including Raspberry Pi OS. -## Changes in This Fork +## Changes in this fork -* Added AI-versus-AI spectator mode. -* Added selectable Blue, Red, Shared, and Full spectator views. -* Added live view switching during AI matches. -* Added blue and red terminal colors for the two AI empires. -* Added an end-of-game prompt to keep or delete `empsave.dat` and `info_list.txt`. * Added the GCC `-fcommon` compiler option to fix multiple-definition linker errors on modern GCC versions. * Corrected two map-array loops in `game.c`. +* Changed: -The affected loops were changed from: - +In line 126 ```c i <= MAP_SIZE ``` @@ -26,7 +21,7 @@ to: i < MAP_SIZE ``` -This prevents the loops from accessing one element beyond the end of the map arrays. +This prevents the loops from accessing one element beyond the end of the arrays. ## Requirements @@ -60,175 +55,74 @@ The executable will be created as: vms-empire ``` -## Normal Game Mode +## Run -Start the normal human-versus-computer game with: +Start the game with: ```bash ./vms-empire ``` -The AI additions do not replace or disable normal gameplay. - -## AI-versus-AI Spectator Mode - -Start an unattended match where the computer controls both empires: - -```bash -./vms-empire -a -``` - -The `-a` option enables AI-versus-AI mode. - -Use the `-d` option to set the delay between map updates in milliseconds. - -Example with a 500-millisecond delay: - -```bash -./vms-empire -a -d 500 -``` - -Examples: - -```bash -./vms-empire -a -d 1000 -./vms-empire -a -d 500 -./vms-empire -a -d 100 -``` - -* `1000` = one second between updates -* `500` = half a second between updates -* `100` = fast playback - -The Blue AI is displayed in blue and the Red AI is displayed in red on terminals that support color. - -Press `Ctrl-C` to stop an active match. - -## AI Spectator Views - -The AI spectator mode supports four starting views. - -### Blue fog-of-war view - -Shows only territory and units known to the Blue AI: - -```bash -./vms-empire -a -v blue -``` - -### Red fog-of-war view - -Shows only territory and units known to the Red AI: - -```bash -./vms-empire -a -v red -``` - -### Shared fog-of-war view - -Shows anything discovered by either AI: - -```bash -./vms-empire -a -v shared -``` - -### Full spectator view - -Shows the entire battlefield: - -```bash -./vms-empire -a -v full -``` - -The view option can be combined with the delay option: +## Clean and rebuild -```bash -./vms-empire -a -v shared -d 500 -``` - -If `-v` is omitted, the game starts in Shared view: +To remove compiled files and rebuild from scratch: ```bash -./vms-empire -a -d 500 -``` - -## Change Views During a Match - -While an AI-versus-AI match is running, press: - -```text -B = Blue fog-of-war view -R = Red fog-of-war view -S = Shared fog-of-war view -F = Full spectator view +make clean +make ``` -These commands only change what the spectator sees. They do not alter either AI’s private map or give either side additional information. +## Repository remotes -## End-of-Game Save Prompt - -At the end of an AI-versus-AI match, the game asks: +This fork is maintained at: ```text -Keep empsave.dat and info_list.txt? (Y/N) +https://github.com/dgdimick/empire ``` -Choose: +The original repository is: ```text -Y = Keep both files -N = Delete both files +https://github.com/slacy/empire ``` -The files are: - -* `empsave.dat` — the saved game state -* `info_list.txt` — the recorded move and game information - -The final game position is saved before the prompt appears. - -## Clean and Rebuild - -To remove compiled files and rebuild from scratch: - -```bash -make clean -make -``` +## Platform tested -A normal repository checkout should not contain compiled `.o` files or the `vms-empire` executable. +The changes in this fork were tested on Raspberry Pi OS using a Raspberry Pi. -## Suggested PuTTY Window Size +## License -The game map is 100 columns by 60 rows. +This fork retains the original project’s licensing and copyright terms. See the repository files for the original license information. -For a full-size spectator display in PuTTY, use approximately: -```text -Columns: 110 -Rows: 65 -``` -Smaller terminals will use the condensed map display. +AI vs AI spectator mode +----------------------- -## Repository Remotes +This fork adds an unattended spectator mode where the existing computer +player controls both empires. Start it with: -This fork is maintained at: + ./vms-empire -a -```text -https://github.com/dgdimick/empire -``` +Use the existing -d option to set the delay in milliseconds between map +updates. For example: -The original repository is: + ./vms-empire -a -d 500 -```text -https://github.com/slacy/empire -``` +The full battlefield is shown using the condensed map display. The USER-side AI +is shown in blue and the COMP-side AI is shown in red on color terminals. Press Ctrl-C +to stop an active game. At the end of a game, press any key to exit. -## Platform Tested -The changes in this fork were tested on Raspberry Pi OS using a Raspberry Pi. +AI spectator views +------------------ +Start an AI-versus-AI match in one of four views: -## License + ./vms-empire -a -v blue + ./vms-empire -a -v red + ./vms-empire -a -v shared + ./vms-empire -a -v full -This fork retains the original project’s licensing and copyright terms. See the repository files for the original license information. +If -v is omitted, shared fog-of-war is used. During a match, press B, R, +S, or F to switch immediately between Blue, Red, Shared, and Full views. +These display modes do not alter either AI's private fog-of-war map. diff --git a/empire.c b/empire.c index e3e4954..79386f7 100644 --- a/empire.c +++ b/empire.c @@ -45,8 +45,9 @@ void empire(void) { rndini(); /* init random number generator */ clear_screen(); /* nothing on screen */ - pos_str(7, 0, "EMPIRE, Version 5.00 site Amdahl 1-Apr-1988"); - pos_str(8, 0, "Detailed directions are in EMPIRE.DOC\n"); + pos_str(6, 0, "EMPIRE, Version 5.00 site Amdahl 1-Apr-1988"); + pos_str(7, 0, "AI Spectator Fork 1.0 - Denis Dimick, 2026"); + pos_str(8, 0, "Directions and build information are in README.md\n"); (void)redisplay(); if (!restore_game()) /* try to restore previous game */ diff --git a/game.c b/game.c index fcf40c3..cecceb1 100644 --- a/game.c +++ b/game.c @@ -547,7 +547,7 @@ int restore_game(void) { f = fopen(savefile, "r"); /* open for input */ if (f == NULL) { - perror("Cannot open saved game"); + topmsg(3, "No saved game found. Starting a new game."); return (false); } rbuf(map); diff --git a/main.c b/main.c index b61ca92..be9b56e 100644 --- a/main.c +++ b/main.c @@ -32,6 +32,64 @@ main.c -- parse command line for empire #define OPTFLAGS "av:w:s:d:S:f:" +static void startup_menu(int *delay_ms) { + char input[64]; + int choice; + + (void)printf("VMS Empire 5.00 - AI Spectator Fork 1.0\n\n"); + (void)printf("1. Normal game\n"); + (void)printf("2. AI vs AI - Shared view\n"); + (void)printf("3. AI vs AI - Blue view\n"); + (void)printf("4. AI vs AI - Red view\n"); + (void)printf("5. AI vs AI - Full view\n"); + (void)printf("6. Quit\n\n"); + (void)printf("Select mode [1]: "); + (void)fflush(stdout); + + if (fgets(input, sizeof(input), stdin) == NULL || input[0] == '\n') + choice = 1; + else + choice = atoi(input); + + switch (choice) { + case 1: + ai_vs_ai = false; + return; + case 2: + ai_vs_ai = true; + spectator_view = VIEW_SHARED; + break; + case 3: + ai_vs_ai = true; + spectator_view = VIEW_BLUE; + break; + case 4: + ai_vs_ai = true; + spectator_view = VIEW_RED; + break; + case 5: + ai_vs_ai = true; + spectator_view = VIEW_FULL; + break; + case 6: + exit(0); + default: + (void)printf("Invalid selection. Starting normal game.\n"); + ai_vs_ai = false; + return; + } + + (void)printf("Delay between updates in milliseconds [500]: "); + (void)fflush(stdout); + if (fgets(input, sizeof(input), stdin) != NULL && input[0] != '\n') { + int entered_delay = atoi(input); + if (entered_delay >= 0 && entered_delay <= 30000) + *delay_ms = entered_delay; + else + (void)printf("Invalid delay. Using 500 milliseconds.\n"); + } +} + int main(argc, argv) int argc; char *argv[]; { @@ -50,6 +108,11 @@ char *argv[]; ai_vs_ai = false; spectator_view = VIEW_SHARED; + if (argc == 1) { + dflg = 500; + startup_menu(&dflg); + } + /* * extract command line options */ From 162a6f880f5297466cf879f75945b144996bc0b3 Mon Sep 17 00:00:00 2001 From: dgdimick Date: Sun, 2 Aug 2026 04:22:48 -0600 Subject: [PATCH 8/8] Fix README command formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 861bc49..8184379 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ AI spectator views ------------------ Start an AI-versus-AI match in one of four views: +```bash ./vms-empire -a -v blue ./vms-empire -a -v red ./vms-empire -a -v shared