vms-empire-1.18/attack.c0000664000175000017500000001367414562167736013344 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* attack.c -- handle an attack between two pieces. We do everything from fighting it out between the pieces to notifying the user who won and killing off the losing object. Somewhere far above, our caller is responsible for actually removing the object from its list and actually updating the player's view of the world. Find object being attacked. If it is a city, attacker has 50% chance of taking city. If successful, give city to attacker. Otherwise kill attacking piece. Tell user who won. If attacking object is not a city, loop. On each iteration, select one piece to throw a blow. Damage the opponent by the strength of the blow thrower. Stop looping when one object has 0 or fewer hits. Kill off the dead object. Tell user who won and how many hits her piece has left, if any. */ #include "empire.h" #include "extern.h" void attack_city(piece_info_t *att_obj, loc_t loc) { city_info_t *cityp; int att_owner, city_owner; cityp = find_city(loc); ASSERT(cityp != NULL); att_owner = att_obj->owner; // cppcheck-suppress nullPointerRedundantCheck city_owner = cityp->owner; if (irand(2) == 0) { /* attack fails? */ if (att_owner == USER) { comment("The scum defending the city crushed your " "attacking blitzkrieger."); ksend("The scum defending the city crushed your " "attacking blitzkrieger.\n"); // kermyt } else if (city_owner == USER) { ksend("Your city at %d is under attack.\n", loc_disp(cityp->loc)); // kermyt comment("Your city at %d is under attack.", loc_disp(cityp->loc)); } kill_obj(att_obj, loc); } else { /* attack succeeded */ kill_city(cityp); cityp->owner = att_owner; kill_obj(att_obj, loc); if (att_owner == USER) { ksend("City at %d has been subjugated!\n", loc_disp(cityp->loc)); // kermyt error("City at %d has been subjugated!", loc_disp(cityp->loc)); extra( "Your army has been dispersed to enforce control."); ksend("Your army has been dispersed to enforce " "control.\n"); set_prod(cityp); } else if (city_owner == USER) { ksend("City at %d has been lost to the enemy!\n", loc_disp(cityp->loc)); // kermyt comment("City at %d has been lost to the enemy!", loc_disp(cityp->loc)); } } /* let city owner see all results */ if (city_owner != UNOWNED) { scan(MAP(city_owner), loc); } } /* Attack a piece other than a city. The piece could be anyone's. First we have to figure out what is being attacked. */ void attack_obj(piece_info_t *att_obj, loc_t loc) { void describe(piece_info_t *, piece_info_t *, loc_t); void survive(piece_info_t *, loc_t); piece_info_t *def_obj; /* defender */ int owner; def_obj = find_obj_at_loc(loc); ASSERT(def_obj != NULL); /* can't find object to attack? */ // cppcheck-suppress nullPointerRedundantCheck if (def_obj->type == SATELLITE) { return; /* can't attack a satellite */ } while (att_obj->hits > 0 && def_obj->hits > 0) { if (irand(2) == 0) /* defender hits? */ { att_obj->hits -= piece_attr[def_obj->type].strength; } else { def_obj->hits -= piece_attr[att_obj->type].strength; } } if (att_obj->hits > 0) { /* attacker won? */ describe(att_obj, def_obj, loc); owner = def_obj->owner; kill_obj(def_obj, loc); /* kill loser */ survive(att_obj, loc); /* move attacker */ } else { /* defender won */ describe(def_obj, att_obj, loc); owner = att_obj->owner; kill_obj(att_obj, loc); survive(def_obj, loc); } /* show results to first killed */ scan(MAP(owner), loc); } void attack(piece_info_t *att_obj, loc_t loc) { if (game.real_map[loc].contents == MAP_CITY) /* attacking a city? */ { attack_city(att_obj, loc); } else { attack_obj(att_obj, loc); /* attacking a piece */ } } /* Here we look to see if any cargo was killed in the attack. If a ships contents exceeds its capacity, some of the survivors fall overboard and drown. We also move the survivor to the given location. */ void survive(piece_info_t *obj, loc_t loc) { while (obj_capacity(obj) < obj->count) { kill_obj(obj->cargo, loc); } move_obj(obj, loc); } void describe(piece_info_t *win_obj, piece_info_t *lose_obj, loc_t loc) { char buf[STRSIZE]; char buf2[STRSIZE]; *buf = '\0'; *buf2 = '\0'; if (win_obj->owner != lose_obj->owner) { if (win_obj->owner == USER) { int diff; game.user_score += piece_attr[lose_obj->type].build_time; ksend("Enemy %s at %d destroyed.\n", piece_attr[lose_obj->type].name, loc_disp(loc)); // kermyt topmsg(1, "Enemy %s at %d destroyed.", piece_attr[lose_obj->type].name, loc_disp(loc)); ksend("Your %s has %d hits left\n", piece_attr[win_obj->type].name, win_obj->hits); // kermyt topmsg(2, "Your %s has %d hits left.", piece_attr[win_obj->type].name, win_obj->hits); diff = win_obj->count - obj_capacity(win_obj); if (diff > 0) { switch (win_obj->cargo->type) { case ARMY: ksend("%d armies fell overboard and " "drowned in the assault.\n", diff); // kermyt topmsg(3, "%d armies fell overboard and " "drowned in the assault.", diff); break; case FIGHTER: ksend("%d fighters fell overboard and " "were lost in the assult.\n", diff); // kermyt topmsg(3, "%d fighters fell overboard and " "were lost in the assault.", diff); break; } } } else { game.comp_score += piece_attr[lose_obj->type].build_time; ksend("Your %s at %d destroyed.\n", piece_attr[lose_obj->type].name, loc_disp(loc)); // kermyt topmsg(3, "Your %s at %d destroyed.", piece_attr[lose_obj->type].name, loc_disp(loc)); } set_need_delay(); } } /* end */ vms-empire-1.18/AUTHORS0000644000175000017500000000010412054531741012740 0ustar esresrChuck Simmons Eric S. Raymond vms-empire-1.18/BUGS0000644000175000017500000006770214551261516012400 0ustar esresr/* %W% %G% %U% - (c) SPDX-FileCopyrightText: Copyright 1987, 1988 Chuck Simmons */ /* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ (Notes tagged with ESR describe Eric S. Raymond's changes) Bugs ---- 1) The computer is allowed to leave armies in cities. Fixing this feature might be difficult. This feature also gives the computer a small advantage which slightly helps overcome the natural superiority of humans. 2) Once upon a time, if a fighter landed on a carrier, the user was never asked if she wanted to move it again. I don't know if this bug still exists. 3) Satellites are not completely implemented. When the user moves to a satellite, it should be allowed. The user should not be asked if she "really wants to attack her own piece". Enemy satelites should not cause the user's pieces to be awoken, since there is nothing the user can do. 4) If the computer has a damaged ship and is returning it to port, the user can block the ship with another piece. The computer will not attempt to move the damaged ship. The user can then sail up a transport to the city the damaged ship is heading toward, unblock the damaged ship, and as soon as the damaged ship enters port, take the city. Since ships in port are capturable, the user should now own one slightly damaged ship. 5) Currently, a fighter's range is not decremented if it is not moved during a turn. This could be called a feature, but I think I would really prefer that a fighter's range was decremented by at least one each turn. The computer does not take advantage of this bug. 6) Movement of armies in "attack" mode seems a little strange. 7) Maybe in "sentry" mode, an army should wake up and go into "attack" mode if an invader appears on the continent. In any event, there should be some mechanism to allow a user to specify that an army should sit on the shore and wait for either a transport to pass by, or for an invader to appear on the continent. 8) When setting a city function, the computer should prompt for the various pieces of input. Even I have a hard time setting functions for cities. Code Cleanup ------------ 1) The width and height of the map should be parameters to the program. Storage for the various data structures should be allocated dynamically instead of being stored in static tables. 2) Interrupts should be caught. When an interrupt is received, the user should be asked if she really wants to quit, and she should be warned if the game will not be saved. Performance Tuning ------------------ 1) 'vmap_cont' and 'vmap_mark_up_cont' would almost certainly be faster if we didn't recurse, but instead used something like the perimeter lists used in all the other map scanning algorithms. Since 'vmap_mark_up_cont' takes about 10% of the processing time, the performance improvements could be significant. 2) The real speed killer is 'expand_perimeter'. This routine is very generalized, and not all the generality is always needed. It might be better to write multiple non-general routines. I believe this routine accounts for roughly 20% to 30% of the processing time. If we take subroutines like 'strchr' and 'terrain_type' into account as well, this routine accounts for more like 50% of the processing. However, on a mainframe, the game plays sufficiently fast. ESR: I've done some tuning of expand_perimeter. Further performance tuning is hardly an issue at this point. Modern machines are *fast*. The above has been preserved as a historical note :-). Debugging Aids -------------- 1) My current debugging algorithm is to put the program into debug mode with the "++" command, and then put the program into movie mode with the "%" command. When I see something wrong, I hit the interrupt key and restart the program, so that I can use other debugging commands. It would be nice if this interface was better. (See comments above about catching interrupts.) 2) It would be nice if there were debugging commands which allowed me to examine the computer map and request information such as "What is this city producing? What objects are in this city? etc." This would be very much like the edit mode "?" command. Computer Algorithm Enhancements ------------------------------- 1) What improvements can be made to speed up the acquiring of territory by the computer? Note: As a person, I can acquire about 1/2 the board (and control 3/4 of the board) in about 150 turns. The current algorithm seems to be a little bit slower, but within the same order of magnitude. Observations: I've implemented an algorithm which keeps the computer from exploring every little square of the world it comes across. Building satellites seems to slow down the computer. The computer has an algorithm for unloading armies on interesting continents. A careful balance seems to be needed between the number of army producing cities and the number of tt producing cities. Currently, I build a tt as soon as poosible. On large continents, this is a drawback, as the tt is built before armies are ready to leave. To counter this effect, I attempted to build more armies in other cities. This causes an overproduction in armies after the first tt's fill up and head off to find something to dump on. Suggestions: Various minor improvements might be made in causing tt's to load one or two turns faster, and to unload one or two turns faster. Other improvements would prevent two tt's from heading to the same destination. This fix would probably go in 'transport_move' in 'compmove.c'. In this routine, for a loading transport, we would count the number of adjacent loading armies for the current cell, for each reachable cell that is one away, and for each reachable cell that is two away. If there were reachable loading armies, we would move the transport to that square. Otherwise we would use the current algorithm to find a destination for the transport and move the transport to that destination. For unloading transports, things are perhaps slightly more difficult. I think what needs to be done here is, if the transport cannot move along a shortest path towards the destination, then the transport should attempt to move to an adjacent square which minimizes either the row distance between the tt and the objective, or the column distance between the tt and the objective. For tie-breakers, the tt would move to the cell which touched the most land. Possibly I should describe the problems that the above to tweaks would fix. For loading armies, loading transports prefer moving to staying in place. Thus, they will sometimes move from a square that touches lots of loading armies, to a square that touches very few loading armies. This probably increases the load time by one or two turns. For unloading tt's, a tt will often be on a diagonal from a city that is an objective. Unloading armies will hop off one at a time because there is only one land square on a shortest path between the tt and the city. This particular case wastes about 4 moves. 2) The computer's algorithm is optimized for 70% water and a smoothness of 5. (More accurately, I developed the algorithm using these parameters for all of my testing and debugging.) Potentially the computer algorithm will be extremely poor for other parameter values. For example, the computer currently builds transports at the first possible opportunity. Transports would not be very useful on a world with only 10% water. (Still, there are checks to prevent ships from being built in inappropriate cities, so this may not be too much of a problem.) Potentially, the computer should use a more dynamic algorithm where it "notices" that certain kinds of pieces are going to be need in the near future and then builds these things. I've no ideas in this area for concrete algorithms, however. A plausible alternative would be for the computer to examine the parameters supplied by the user. If the user knows the parameters, why shouldn't the computer? 3) The computer should be willing to land a fighter on a carrier if the carrier can be reached in one turn. 4) The computer should "track" user ships. That is, whenever the computer sees a user ship, it should keep a list of possible locations where that ship could be. It should then make some attempt to find and destroy the ship. (See "Search and Destroy" under the user interface comments.) This code may be more trouble then its worth. Currently, it appears that the computer does a very good job of destroying user shipping. The reason for this is that there are good reasons for the user's ships to reappear where ships were previously seen. Also, the computer tends to amass great amounts of fire power when a ship has been seen, so the computer tends to bump into any other user ship that happens to be in the area. Also, the user is looking for the computer's ships, and is moving lots of ships into the sealanes that the computer tends to use. User Interface Enhancements --------------------------- 1) In edit mode, it would be nice if it was easier to move around the screen. (Mouse based approaches where the user points and clicks to say "move this piece to here" would be real nice.) One idea would be to allow the user to type in four digits that specify the square to move to; or to type in two digits where the first digit is the 10's row, and the second digit is the 10's column. (Thus, if the user typed "43", the cursor would be moved to location "4030".) 2) Small screens should not be redrawn so often. When moving pieces, we should move everything that is on the current screen (except for stuff that is real close to the edge of the screen, but not the edge of the board). If necessary, we might redraw the screen as the user moved off the screen. Or we could allow the user to explicitly tell us to redraw a new sector. If the screen was redrawn, we would then work on moving pieces that were displayed on the new screen. In general, we only want to redraw the screen if we absolutely have to. (This approach would also be real, real useful on terminals that are just a little bit smaller than the complete map. Using a terminal with something like 105 columns will be extremely disconcerting. The screen will be redrawn for what seems like no reason at all.) 3) It is extremely difficult to tell when a satellite has gone by, and when an enemy piece displayed on the screen is current, and when the piece is a ghost. One possibility would be to highlight enemy pieces that have just been seen. Another possibility would be for the user to type a "?" when the cursor is on top of any enemy piece, and the displayed information would be how long ago that piece was seen. (Also, see search and destroy tracking below.) 4) The user should be allowed to "zoom in" and "zoom out". Zooming in causes the playing board to be printed at four times the density of the previous display density. Thus, four squares would be drawn as a single square on the screen. Zooming out undoes the affects of zooming in. Actually, there should be multiple levels of zooming; 10 levels would probably more than suffice. This enhancement allows the user to easily get a big picture of what is going on. Most of the time, the user would be able to play with the board zoomed in. The user might sometimes need to zoom out to navigate a ship through a "river" or an army over an isthmus. Currently, there is a command to display a "zoomed" version of the map. This command prints a compact display of the map that fits on the screen. This is not quite the same as the above, because what I'm suggesting is that the user be allowed to make moves on a compact display of the map. 5) Search and Destroy. Here is an idea for a sizeable modification to the user interface. Basically, we want the computer to keep track of some information for the user. The information is possible locations of enemy ships. When the user sees a ship on the screen, the computer would start tracking the ship. (Tracking would be implemented as follows: The initial location of the ship would be stored in a perimeter list. On each round, the perimeter list would be expanded to encompass all cells which the ship could have reached. Cells that the user has seen this turn are removed from the perimeter list. After the user's turn, cells that the user has seen are removed from the list again. Problems arise when a tracked ship moves into unexplored territory.) Now the user should be able to give various commands. Commands would be things like: "Describe to me all ships you are tracking." This command would print a list of ships being tracked, the types, the number of possible locations for the ship, the time the ship was first seen, and any code used to represent that ship in tracking displays. Possibly, the likelihood that the ship has been destroyed would be displayed as well. "Display possible locations for a ship." This command would display the possible locations for a ship. Possible locations might be printed by highlighting the squares where the ship could be. "Stop tracking a ship." This command would delete information about a ship that the user assumed she had destroyed. Note that the computer will sometimes be able to tell that a tracked ship has been destroyed. The user might also give this command if a ship was presumed to have gotten itself lost. The computer might also stop tracking a ship if the number of possible locations for the ship gets large. To support the above, the user should be able to place fighters and ships in "Search and Destroy" mode. Here the user would specify a tracked ship that was to be searched for. The fighters and ships of the user would move in such a way as to minimize the size of the perimeter list for the tracked ship. 5) It has been suggested that all pieces at a location be moved at once instead of skipping around the screen in the order the pieces happen to be allocated. For example, the user is often asked what to do with each of the armies aboard a transport. If the user knows there are five armies aboard, she may start hitting the 's' key to put those armies to sleep. If the user isn't paying enough attention, she may discover that she has just put to sleep some piece on a few squares away. 6) When moving a ship or army toward a destination, or when moving a ship toward port to be repaired, the user will often want to avoid land that might contain enemy pieces. This functionality is partially implemented in that ships prefer to be at sea if there is nothing to explore. Pieces moving towards a destination also prefer to move so that they are in the middle of all the shortest paths to the destination. Despite this, it might be desirable to implement a movement mode whereby ships went out of there way to avoid the enemy. 7) It should be possible for the user to obtain information about pieces which are contained within another piece. For example, it would be nice to know the movement function of every ship contained in a city, or of every army on a transport. 8) The big problem... The user needs to type one hell of a lot of commands to control the movement of her pieces. I've implemented a lot of stuff to alleviate this problem, but lots more can be done. If we assume that everything is implemented as a "movement function", the following movement functions would be interesting: "load army" -- an army moves toward the nearest loading transport or transport producing city. Note that this is the same algorithm as that used by the computer. (For armies, there are really only three functions that are needed: "attack" (which is implemented) where the army explores a continent and attackes the enemy or unowned cities; "load" where armies leave a continent; and "unload" where armies leave a boat for some other continent. Also note that when I play the game, most of the commands that I give are commands to move the army to a place where a transport will pick up the army, commands to have the army wait for the transport and load, commands to put the army to sleep while it is being transported, and command to wake up and unload the armies.) "load transport" -- the transport is marked as "loading", and the transport moves toward the nearest loading army. "unload army" -- I'm not sure what this does, nor what "unload transport" does. Basically, I want some pseudo-intelligent mechanism for telling the computer not to ask me questions about armies on a transport until that transport reaches "something interesting". "load army" and "load transport" would be much easier to implement. Unloading is where intelligence and strategy can be important. "patrol" -- This causes a piece to move so as to decrease the likelihood that an enemy piece can sneak by. One method of implementing this would be for the piece to move toward the square two away that had been least recently seen. It might be desirable to constrain a patrolling piece to a relatively small area of the board. Note that the "Grope" command (or "explore" mode) is no longer of much use for armies. Possibly "attack" mode would be useful for ships and fighters. 9) One possibility for all of the above might be to allow the user to specify some algorithm that describes how pieces should move. I've thought along the lines of letting the user specify some sort of macro, and letting the user specify the arguments to one of the 'vmap_find_*obj' routines. This might require the ability for the user to place arbitrary labels on any square. Game Modifications ------------------ 1) Mobile repair platforms. Currently a damaged boat moves very slowly towards the nearest city. A floating platform that could move towards damaged boats might be useful. Also, this little baby would stay out of cities and so not be capturable. Hits would be 1; strength zero; and speed might be greater than 2. 2) Setting transports to have a strength of zero might be interesting. 3) Implementing the world as a torus instead of a rectangle might be better. This means having the map wrap at the edges instead of terminating at the edges. 4) Increase the "logistics" aspects of the game. What about oil, iron, food, etc? Oil might be used to determine the range of a boat or ship. Armies might starve without food. Iron might be necessary for building a ship. 5) One of my goals has been to define lots of highly specialized pieces so that each type of piece must be built and used in order to have an effective strategy. In the original game of empire, I eventually developed a strategy whereby the only pieces I would build were armies and transports. The computer basically couldn't beat me unless we both started on the same continent and it lucked out. The game also ended within one hundred turns. Now, eventually, I might decide that the current program also has the same faults (in which case I'll tweak the computer movement algorithms so that it plays differently). However, I have been making changes to increase the specialization of all the pieces. Obviously, armies must be built because they are the only pieces that can capture cities. Obviously, transports must be built (on worlds that have a reasonable amount of water) because that's the only way an army can be carried to another city. Since transports don't move all that fast, and since transports are fairly weak, they aren't good for much else (in theory). Beyond this... Patrol boats and fighters are very similar. Both are fast, both are quickly produced, both have low hits. I suspect that an effective strategy could be developed where one or the other of these pieces, but not necessarily both, were built. Patrol boats have an advantage in their unlimited range. This makes them useful for extremely long range exploration. Fighters have an advantage in their great speed. This makes them extremely useful for tracking and patrolling. Fighters also have a sufficient range that they can easily move across the board from one city to another without really needing carriers. Possibly, the range of fighters is too great. Possibly, the range of fighters should be 16. (For purposes of user friendliness, the range of fighters should be a multiple of the speed.) Now, carriers, destroyers, subs, and battleships are very similar. Carriers and battleships have the advantage that they can take a beating and then be reparied. Destroyers and subs have the advantage that lots of them can be produced which increases the amount of territory that can be seen at any time. Decreasing the range of fighters might increase the utility of carriers, but then the user would probably build more patrol boats instead. So, I guess I'm looking for more specialized pieces. Mobile repair platforms are one idea. Satellites are an attempt at a second idea, but this idea needs more refinement. Currently, the computer does just fine without building satellites (as near as I can tell). Maybe satellites would be more useful if they were faster or scanned a larger area. User Comments ------------- > empire is getting very good about asking me about all the troups on a transport, > etc. before going on to another piece, but its still not perfect. > > the computer still seems to be spending too much effort fighting, and not enough > exploring. i think i've got it beat, although i'm not sure yet. i was burning > transport after transport (of the computers) while he tried to take an island > from me. he finally succeeded, but i must have killed 8 transports in the > process (he got two of mine). > > early in the game, he had a real superiority with patrol boats, that was giving > me fits. but now i think i've got him, and am making it very hard for him to > move around. also, he still hasn't finished taking islands he's colonized-- > hopefully i'll be able to take some away from him. he should have consolidated > them long ago, and being harassing me. > > The satellite is fun, although i wish it would head into enemy territory > instead of back into mine. The first paragraph is a request that all pieces of a type in one position be moved before moving pieces of that type at another position. This fix should be combined with the needed fix to move all pieces on the screen before redrawing the screen. The second paragraph suggests to me that the computer should move lots of patrol boats and other support craft into an area to clear it out before moving in transports. The transports are too vulnerable, and the computer isn't defending them very well. Maybe what I mean here is that the computer should have a concept of "escorts". When moving a transport, a destroyer, sub, or at least a patrol boat should try and remain near by. Then if our transport gets destroyed by an enemy, at least there is some chance that we can kill off the attacker. Other problems probably exist in this area as well. Early in the game, the computer will see an unowned city and drop some armies on it. A little later the computer will notice that there is a user city on the same continent. Now, all the computer's transports go floating around that user city and dump armies on it. The computer has used massive amounts of resources to take a continent instead of exploring and sweeping up more easily defended continents. On the other hand, the computer is very "contentious". It's kind of fun to have the computer fighting so hard to keep me from taking its cities. Also, if I don't use the current strategy, there is a danger that the computer will not fight hard enough to keep the user from invading and taking a computer-owned continent. Colonization... The computer takes new continents very slowly. I don't know why. The computer should be producing armies in the first city it takes, and the computer will produce armies in new cities as long as it sees an unowned city to attack. Potentially, there is a problem in that the computer might not see an unowned city, even though there is lots of unexplored territory, and thus probably an unowned city, on the continent. The bigger problem seems to be that the computer is producing too many armies and not enough other stuff. In the particular game that these comments derived from, the computer had a massive continent that was smothered in armies. Instead of producing so many armies, the computer should have produced more fighters and non-transport ships. Tweaking the "ration?" arrays in compmove.c should make things better. Note that the user's strategy was to seek and destroy transports. The user would ignore other ships for relatively long periods of time for a chance to destroy one of the computer's transports. This strategy was quite effective; the user tended to be able to keep many more transports on the board than the computer. > planes aren't that useful even as they are--sure they're good for zooming > in and destroying the odd troop transport, but they're not that helpful for > exploration. i still suspect the optimal strategy is armies, transports, > and patrol boats, with a few planes. Later in the game planes become > useful because they can be gotten to the scene so quickly. If you want > to reduce them, how about reducing them to 24? Also, when a plane is > about to fly past the point of no return (return to anything) how about > a question, a la "troops can't walk on water, Sir?". as long as i can > override the objection, its not a problem. > oh, i think it would suffice to be able to launch satellites in a particular > direction. The first paragraph is a response to my suggestion that a fighter's range should be reduced to 16 so as to make patrol boats and carriers more useful. Maybe we should crank up the hits on the various objects. This would make attacks a little more deterministic. For example: armies: 2 armies 10 transports: 2 transports 10 fighters: 2 fighters 10 patrol boats: 2 patrol boats 10 submarines: 4 submarines 20 destoyers: 6 destroyers 30 carriers: 10 carriers 80 battleships: 12 battleships 100 But then we would have to worry about repairing armies? Or maybe the effectiveness of an army simply goes down when it doesn't have full hit points? This would also greatly increase the repair time of an object. Or maybe objects would be repaired two or 10 hits per turn. Other bugs... ------------- Possibly displayed messages should be of two types: Important messages and non-important messages. After an important message, the computer would delay for the full amount of delay time. After an unimportant message, it might not do anything. 1) The "m" and "n" commands should work in movement mode. They should also work when setting the function of a piece for a city. 2) Should there be a way to tell the program you are done attempting to move a fighter, and don't ask me about moves seven more times? 3) The program should use environment variables or command line arguments to supply the filenames for "empsave.dat" and "empmovie.dat". A command line argument would also be useful for telling the program how often the game should be saved. Actually, all command line arguments should have associated environment variables that can be used to set defaults. ESR: I've added a save-interval option, and another to set the savefile name. 4) When the user types "q" to quit, the program should exit immediately if the game has been saved. If the game hasn't been saved, the user should be told, and asked if she really wants to quit. 5) "Andrew Morrow" reported this in August 2002: I am playing the version of vms-empire that you claim in your READ-ME to maintain. I noticed a bug vms-empire that I think you should know. Sometimes the program exits in util.c/check_cargo() at the line ASSERT (q->owner == p->owner); At least one case where this can happen is when two opposing transports meet at sea. Somehow, an army can transfer to an enemy transport. I have not examined the exact line this happens on (maybe while attempting to attack the enemy transport or while executing its function to move a in particular direction?) but I have saved such a game, commented out this line, landed my transport (which has a computer army on board), and seen the computer army exit my transport. vms-empire-1.18/compmove.c0000664000175000017500000007704114562166302013704 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* compmove.c -- Make a move for the computer. For each move the user wants us to make, we do the following: 1) Handle city production; 2) Move computer's pieces; 3) Check to see if the game is over. */ #include "empire.h" #include "extern.h" #include static view_map_t emap[MAP_SIZE]; /* pruned explore map */ bool load_army(piece_info_t *obj); bool lake(loc_t loc); bool overproduced(city_info_t *cityp, int *city_count); bool nearby_load(piece_info_t *obj, loc_t loc); count_t nearby_count(loc_t loc); void move_objective(piece_info_t *obj, path_map_t pathmap[], loc_t new_loc, char *adj_list); void comp_set_prod(city_info_t *, int); void comp_set_needed(city_info_t *, int *, bool, bool); void comp_prod(city_info_t *, bool); void comp_move(int nmoves) { void do_cities(void), do_pieces(void), check_endgame(void); int i; piece_info_t *obj; /* Update our view of the world. */ for (i = 0; i < NUM_OBJECTS; i++) for (obj = game.comp_obj[i]; obj != NULL; obj = obj->piece_link.next) scan(game.comp_map, obj->loc); /* refresh comp's view of world */ for (i = 1; i <= nmoves; i++) { /* for each move we get... */ comment("Thinking..."); (void)memcpy(emap, game.comp_map, MAP_SIZE * sizeof(view_map_t)); vmap_prune_explore_locs(emap); do_cities(); /* handle city production */ do_pieces(); /* move pieces */ if (game.save_movie) save_movie_screen(); check_endgame(); /* see if game is over */ topini(); (void)redisplay(); } } /* Handle city production. First, we set production for new cities. Then we produce new pieces. After producing a piece, we will see if we should change our production. Our goals for city production are first, not to change production while something is in the process of being built. Second, we attempt to have enough city producing armies on a continent to counter any threat on the continent, and to adequately explore and control the continent. Third, we attempt to always have at least one transport producer. Fourth, we attempt to maintain a good ratio between the number of producers we have of each type of piece. Fifth, we never build carriers, as we don't have a good strategy for moving these. */ void do_cities(void) { int i; bool is_lake; for (i = 0; i < NUM_CITY; i++) /* new production */ if (game.city[i].owner == COMP) { scan(game.comp_map, game.city[i].loc); if (game.city[i].prod == NOPIECE) comp_prod(&game.city[i], lake(game.city[i].loc)); } for (i = 0; i < NUM_CITY; i++) /* produce and change */ if (game.city[i].owner == COMP) { is_lake = lake(game.city[i].loc); if (game.city[i].work++ >= (long)piece_attr[(int)game.city[i].prod] .build_time) { produce(&game.city[i]); comp_prod(&game.city[i], is_lake); } /* don't produce ships in lakes */ else if (game.city[i].prod > FIGHTER && game.city[i].prod != SATELLITE && is_lake) comp_prod(&game.city[i], is_lake); } } /* Define ratios for numbers of cities we want producing each object. Early in the game, we want to produce armies and transports for rapid expansion. After a while, we add fighters and pt boats for exploration. Later, we add ships of all kinds for control of the sea. */ // clang-format off /* A F P S D T C B Z*/ static int ratio1[NUM_OBJECTS] = { 60, 0, 10, 0, 0, 20, 0, 0, 0}; static int ratio2[NUM_OBJECTS] = { 90, 10, 10, 10, 10, 40, 0, 0, 0}; static int ratio3[NUM_OBJECTS] = {120, 20, 20, 10, 10, 60, 10, 10, 0}; static int ratio4[NUM_OBJECTS] = {150, 30, 30, 20, 20, 70, 10, 10, 0}; // clang-format on static int *ratio; /* Set city production if necessary. The algorithm below contains three parts: 1) Defend continents we own. 2) Produce a TT and a Satellite. 3) Meet the ratio requirements given above. */ void comp_prod(city_info_t *cityp, bool is_lake) { int city_count[NUM_OBJECTS]; /* # of cities producing each piece */ int cont_map[MAP_SIZE]; int total_cities; count_t i; int comp_ac; city_info_t *p; int need_count, interest; scan_counts_t counts; /* Make sure we have army producers for current continent. */ /* map out city's continent */ vmap_cont(cont_map, game.comp_map, cityp->loc, MAP_SEA); /* count items of interest on the continent */ counts = vmap_cont_scan(cont_map, game.comp_map); comp_ac = 0; /* no army producing computer cities */ for (i = 0; i < MAP_SIZE; i++) if (cont_map[i]) { /* for each cell of continent */ if (game.comp_map[i].contents == 'X') { p = find_city(i); ASSERT(p != NULL && p->owner == COMP); // cppcheck-suppress nullPointerRedundantCheck if (p->prod == ARMY) comp_ac += 1; } } /* see if anything of interest is on continent */ interest = (counts.unexplored || counts.user_cities || counts.user_objects[ARMY] || counts.unowned_cities); /* we want one more army producer than enemy has cities */ /* and one more if anything of interest on cont */ need_count = counts.user_cities - comp_ac + interest; if (counts.user_cities) need_count += 1; if (need_count > 0) { /* need an army producer? */ comp_set_prod(cityp, ARMY); return; } /* Produce armies in new cities if there is a city to attack. */ if (counts.user_cities && cityp->prod == NOPIECE) { comp_set_prod(cityp, ARMY); return; } /* Produce a TT and SAT if we don't have one. */ /* count # of cities producing each piece */ for (i = 0; i < NUM_OBJECTS; i++) city_count[i] = 0; total_cities = 0; for (i = 0; i < NUM_CITY; i++) if (game.city[i].owner == COMP && game.city[i].prod != NOPIECE) { city_count[(int)game.city[i].prod] += 1; total_cities += 1; } if (total_cities <= 10) ratio = ratio1; else if (total_cities <= 20) ratio = ratio2; else if (total_cities <= 30) ratio = ratio3; else ratio = ratio4; /* if we have one army producer, and this is it, return */ if (city_count[ARMY] == 1 && cityp->prod == ARMY) return; /* first available non-lake becomes a tt producer */ if (city_count[TRANSPORT] == 0) { if (!is_lake) { comp_set_prod(cityp, TRANSPORT); return; } /* if we have one army producer that is not on a lake, */ /* produce armies here instead */ if (city_count[ARMY] == 1) { for (i = 0; i < NUM_CITY; i++) if (game.city[i].owner == COMP && game.city[i].prod == ARMY) break; // It's mysterious why the following exclusion is // required... cppcheck-suppress arrayIndexOutOfBounds // cppcheck-suppress arrayIndexOutOfBounds if (!lake(game.city[i].loc)) { comp_set_prod(cityp, ARMY); return; } } } #if 0 /* Now we need a SATELLITE. */ if (cityp->prod == NOPIECE && city_count[SATELLITE] == 0) { comp_set_prod (cityp, SATELLITE); return; } if (cityp->prod == SATELLITE) return; /* "The satellites are out tonight." -- Lori Anderson */ #endif /* don't change prod from armies if something on continent */ if (cityp->prod == ARMY && interest) return; /* Produce armies in new cities if there is a city to attack. */ if (counts.unowned_cities && cityp->prod == NOPIECE) { comp_set_prod(cityp, ARMY); return; } /* Set production to item most needed. Continents with one city and nothing interesting may not produce armies. We set production for unset cities, and change production for cities that produce objects for which we have many city producers. Ship producers on lakes also get there production changed. */ interest = (counts.comp_cities != 1 || interest); if (cityp->prod == NOPIECE || (cityp->prod == ARMY && counts.comp_cities == 1) || overproduced(cityp, city_count) || (cityp->prod > FIGHTER && is_lake)) comp_set_needed(cityp, city_count, interest, is_lake); } /* Set production for a computer city to a given type. Don't reset production if it is already correct. */ void comp_set_prod(city_info_t *cityp, int type) { if (cityp->prod == type) return; pdebug("Changing city prod at %d from %d to %d\n", loc_disp(cityp->loc), cityp->prod, type); cityp->prod = type; cityp->work = -(piece_attr[type].build_time / 5); } /* See if a city is producing an object which is being overproduced. */ // cppcheck-suppress constParameter bool overproduced(city_info_t *cityp, int *city_count) { int i; for (i = 0; i < NUM_OBJECTS; i++) { /* return true if changing production would improve balance */ if (i != cityp->prod && ((city_count[(int)cityp->prod] - 1) * ratio[i] > (city_count[i] + 1) * ratio[(int)cityp->prod])) return true; } return false; } /* See if one type of production is needed more than another type. Return the most-needed type of production. */ int need_more(const int *city_count, int prod1, int prod2) { if (city_count[prod1] * ratio[prod2] <= city_count[prod2] * ratio[prod1]) return (prod1); else return (prod2); } /* Figure out the most needed type of production. We are passed a flag telling us if armies are ok to produce. */ void comp_set_needed(city_info_t *cityp, int *city_count, bool army_ok, bool is_lake) { int best_prod; int prod; if (!army_ok) city_count[ARMY] = INFINITY; if (is_lake) { /* choose fighter or army */ comp_set_prod(cityp, need_more(city_count, ARMY, FIGHTER)); return; } /* don't choose fighter */ city_count[FIGHTER] = INFINITY; best_prod = ARMY; /* default */ for (prod = 0; prod < NUM_OBJECTS; prod++) { best_prod = need_more(city_count, best_prod, prod); } comp_set_prod(cityp, best_prod); } /* See if a city is on a lake. We define a lake to be a body of water (where cities are considered to be water) that does not touch either an attackable city or unexplored territory. Be careful, because we use the 'emap'. This predicts whether unexplored territory will be land or water. The prediction should be helpful, because small bodies of water that enclose unexplored territory will appear as solid water. Big bodies of water should have unexplored territory on the edges. */ bool lake(loc_t loc) { int cont_map[MAP_SIZE]; scan_counts_t counts; vmap_cont(cont_map, emap, loc, MAP_LAND); /* game.real_map lake */ counts = vmap_cont_scan(cont_map, emap); return !(counts.unowned_cities || counts.user_cities || counts.unexplored); } /* Move all computer pieces. */ static view_map_t amap[MAP_SIZE]; /* temp view map */ static path_map_t path_map[MAP_SIZE]; void do_pieces(void) { void cpiece_move(piece_info_t *); int i; piece_info_t *obj, *next_obj; for (i = 0; i < NUM_OBJECTS; i++) { /* loop through obj lists */ for (obj = game.comp_obj[move_order[i]]; obj != NULL; obj = next_obj) { /* loop through objs in list */ next_obj = obj->piece_link.next; cpiece_move(obj); /* yup; move the object */ } } } /* Move a piece. We loop until all the moves of a piece are made. Within the loop, we find a direction to move that will take us closer to an objective. */ void cpiece_move(piece_info_t *obj) { void move1(piece_info_t *); bool changed_loc; int max_hits; if (obj->type == SATELLITE) { move_sat(obj); return; } obj->moved = 0; /* not moved yet */ changed_loc = false; /* not changed yet */ max_hits = piece_attr[obj->type].max_hits; if (obj->type == FIGHTER) { /* init fighter range */ city_info_t *cityp = find_city(obj->loc); if (cityp != NULL) obj->range = piece_attr[FIGHTER].range; } while (obj->moved < obj_moves(obj)) { loc_t saved_loc; saved_loc = obj->loc; /* remember starting location */ move1(obj); if (saved_loc != obj->loc) changed_loc = true; if (obj->type == FIGHTER && obj->hits > 0) { if (game.comp_map[obj->loc].contents == 'X') obj->moved = piece_attr[FIGHTER].speed; else if (obj->range <= 0) { pdebug("Fighter at %d crashed and burned\n", loc_disp(obj->loc)); ksend("Fighter at %d crashed and burned\n", loc_disp(obj->loc)); kill_obj(obj, obj->loc); /* crash & burn */ } } } /* if a boat is in port, damaged, and never moved, fix some damage */ if (obj->hits > 0 /* live piece? */ && !changed_loc /* object never changed location? */ && obj->type != ARMY && obj->type != FIGHTER /* it is a boat? */ && obj->hits != max_hits /* it is damaged? */ && game.comp_map[obj->loc].contents == 'X') /* it is in port? */ obj->hits++; /* fix some damage */ } /* Move a piece one square. */ void move1(piece_info_t *obj) { void army_move(piece_info_t *), transport_move(piece_info_t *); void fighter_move(piece_info_t *), ship_move(piece_info_t *); switch (obj->type) { case ARMY: army_move(obj); break; case TRANSPORT: transport_move(obj); break; case FIGHTER: fighter_move(obj); break; default: ship_move(obj); break; } } /* Move an army. This is a multi-step algorithm: 1) See if there is an object we can attack immediately. If so, attack it. 2) Look for the nearest land objective. 3) If we find an objective reachable by land, figure out how far away that objective is. Based on the found objective, also figure out how close a loadable tt must be to be of interest. If the objective is closer than the tt must be, head towards the objective. 4) Otherwise, look for the nearest loading tt (or tt producing city). If the nearest loading tt is farther than our land objective, head towards the land objective. 5) Otherwise, head for the tt. 6) If we still have no destination and we are in a city, attempt to leave the city. 7) Once we have a destination, find the best move toward that destination. (If there is no destination, sit around and wait.) */ void army_move(piece_info_t *obj) { loc_t move_away(view_map_t *, loc_t, char *); loc_t find_attack(loc_t, char *, char *); void make_army_load_map(piece_info_t *, view_map_t *, view_map_t *); void make_unload_map(view_map_t *, view_map_t *); void make_tt_load_map(view_map_t *, view_map_t *); void board_ship(piece_info_t *, path_map_t *, loc_t); loc_t new_loc; path_map_t path_map2[MAP_SIZE]; int cross_cost = 0; /* cost to enter water */ obj->func = 0; /* army doesn't want a tt */ if (vmap_at_sea(game.comp_map, obj->loc)) { /* army can't move? */ (void)load_army(obj); obj->moved = piece_attr[ARMY].speed; if (!obj->ship) obj->func = 1; /* load army on ship */ return; } if (obj->ship) /* is army on a transport? */ new_loc = find_attack(obj->loc, army_attack, "+*"); else new_loc = find_attack(obj->loc, army_attack, ".+*"); if (new_loc != obj->loc) { /* something to attack? */ attack(obj, new_loc); /* attack it */ if (game.real_map[new_loc].contents == MAP_SEA /* moved to ocean? */ && obj->hits > 0) { /* object still alive? */ kill_obj(obj, new_loc); scan(game.user_map, new_loc); /* rescan for user */ } return; } if (obj->ship) { if (obj->ship->func == 0) { if (!load_army(obj)) ABORT; /* load army on best ship */ return; /* armies stay on a loading ship */ } make_unload_map(amap, game.comp_map); new_loc = vmap_find_wlobj(path_map, amap, obj->loc, &tt_unload); move_objective(obj, path_map, new_loc, " "); return; } new_loc = vmap_find_lobj(path_map, game.comp_map, obj->loc, &army_fight); if (new_loc != obj->loc) { /* something interesting on land? */ switch (game.comp_map[new_loc].contents) { case 'A': case 'O': cross_cost = 60; /* high cost if enemy present */ break; case MAP_CITY: cross_cost = 30; /* medium cost for attackable city */ break; case ' ': cross_cost = 14; /* low cost for exploring */ break; default: ABORT; } cross_cost = path_map[new_loc].cost * 2 - cross_cost; } else cross_cost = INFINITY; if (new_loc == obj->loc || cross_cost > 0) { loc_t new_loc2; /* see if there is something interesting to load */ make_army_load_map(obj, amap, game.comp_map); new_loc2 = vmap_find_lwobj(path_map2, amap, obj->loc, &army_load, cross_cost); if (new_loc2 != obj->loc) { /* found something? */ board_ship(obj, path_map2, new_loc2); return; } } move_objective(obj, path_map, new_loc, " "); } /* Remove pruned explore locs from a view map. */ void unmark_explore_locs(view_map_t *xmap) { count_t i; for (i = 0; i < MAP_SIZE; i++) if (game.real_map[i].on_board && xmap[i].contents == ' ') xmap[i].contents = emap[i].contents; } /* Make a load map. We copy the view map and mark each loading transport and tt producing city with a '$'. */ void make_army_load_map(piece_info_t *obj, view_map_t *xmap, view_map_t *vmap) { piece_info_t *p; int i; (void)memcpy(xmap, vmap, sizeof(view_map_t) * MAP_SIZE); /* mark loading transports or cities building transports */ for (p = game.comp_obj[TRANSPORT]; p; p = p->piece_link.next) if (p->func == 0) /* loading tt? */ xmap[p->loc].contents = '$'; for (i = 0; i < NUM_CITY; i++) if (game.city[i].owner == COMP && game.city[i].prod == TRANSPORT) { if (nearby_load(obj, game.city[i].loc)) xmap[game.city[i].loc].contents = 'x'; /* army is nearby so it can load */ else if (nearby_count(game.city[i].loc) < piece_attr[TRANSPORT].capacity) xmap[game.city[i].loc].contents = 'x'; /* city needs armies */ } if (game.print_vmap == 'A') print_xzoom(xmap); } /* Return true if an army is considered near a location for loading. */ bool nearby_load(piece_info_t *obj, loc_t loc) { return obj->func == 1 && dist(obj->loc, loc) <= 2; } /* Return number of nearby armies. */ count_t nearby_count(loc_t loc) { piece_info_t *obj; int count; count = 0; for (obj = game.comp_obj[ARMY]; obj; obj = obj->piece_link.next) { if (nearby_load(obj, loc)) count += 1; } return count; } /* Make load map for a ship. */ void make_tt_load_map(view_map_t *xmap, view_map_t *vmap) { piece_info_t *p; (void)memcpy(xmap, vmap, sizeof(view_map_t) * MAP_SIZE); /* mark loading armies */ for (p = game.comp_obj[ARMY]; p; p = p->piece_link.next) if (p->func == 1) /* loading army? */ xmap[p->loc].contents = '$'; if (game.print_vmap == 'L') print_xzoom(xmap); } /* Make an unload map. We copy the view map. We then create a continent map. For each of our cities, we mark out the continent that city is on. Then, for each city that we don't own and which doesn't appear on our continent map, we set that square to a digit. We want to assign weights to each attackable city. Cities are more valuable if they are on a continent which has lots of cities. Cities are also valuable if either it will be easy for us to take over the continent, or if we need to defend that continent from an enemy. To implement the above, we assign numbers to each city as follows: a) if unowned_cities > user_cities && comp_cities == 0 set number to min (total_cities, 9) b) if comp_cities != 0 && user_cities != 0 set number to min (total_cities, 9) b) if enemy_cities == 1 && total_cities == 1, set number to 2. (( taking the sole enemy city on a continent is as good as getting a two city continent )) c) Any other attackable city is marked with a '0'. */ static int owncont_map[MAP_SIZE]; static int tcont_map[MAP_SIZE]; void make_unload_map(view_map_t *xmap, view_map_t *vmap) { count_t i; scan_counts_t counts; (void)memcpy(xmap, vmap, sizeof(view_map_t) * MAP_SIZE); unmark_explore_locs(xmap); for (i = 0; i < MAP_SIZE; i++) owncont_map[i] = 0; /* nothing marked */ for (i = 0; i < NUM_CITY; i++) if (game.city[i].owner == COMP) vmap_mark_up_cont(owncont_map, xmap, game.city[i].loc, MAP_SEA); for (i = 0; i < MAP_SIZE; i++) if (strchr("O*", vmap[i].contents)) { int total_cities; vmap_cont(tcont_map, xmap, i, MAP_SEA); /* game.real_map continent */ counts = vmap_cont_scan(tcont_map, xmap); total_cities = counts.unowned_cities + counts.user_cities + counts.comp_cities; if (total_cities > 9) total_cities = 0; if (counts.user_cities && counts.comp_cities) xmap[i].contents = '0' + total_cities; else if (counts.unowned_cities > counts.user_cities && counts.comp_cities == 0) xmap[i].contents = '0' + total_cities; else if (counts.user_cities == 1 && counts.comp_cities == 0) xmap[i].contents = '2'; else xmap[i].contents = '0'; } if (game.print_vmap == 'U') print_xzoom(xmap); } /* Load an army onto a ship. First look for an adjacent ship. If that doesn't work, move to the objective, trying to be close to the ocean. */ void board_ship(piece_info_t *obj, path_map_t *pmap, loc_t dest) { if (!load_army(obj)) { obj->func = 1; /* loading */ move_objective(obj, pmap, dest, "t."); } } /* Look for the most full, non-full transport at a location. Prefer switching to staying. If we switch, we force one of the ships to become more full. */ piece_info_t *find_best_tt(piece_info_t *best, loc_t loc) { piece_info_t *p; for (p = game.real_map[loc].objp; p != NULL; p = p->loc_link.next) if (p->type == TRANSPORT && obj_capacity(p) > p->count) { if (!best) best = p; else if (p->count >= best->count) best = p; } return best; } /* Load an army onto the most full non-full ship. */ bool load_army(piece_info_t *obj) { piece_info_t *p; int i; p = find_best_tt(obj->ship, obj->loc); /* look here first */ for (i = 0; i < 8; i++) { /* try surrounding squares */ loc_t x_loc = obj->loc + dir_offset[i]; if (game.real_map[x_loc].on_board) p = find_best_tt(p, x_loc); } if (!p) return false; /* no tt to be found */ if (p->loc == obj->loc) { /* stay in same place */ obj->moved = piece_attr[ARMY].speed; } else move_obj(obj, p->loc); /* move to square with ship */ if (p->ship != obj->ship) { /* reload army to new ship */ disembark(obj); embark(p, obj); } return true; } /* Return the first location we find adjacent to the current location of the correct terrain. */ loc_t move_away(view_map_t *vmap, loc_t loc, char *terrain) { int i; for (i = 0; i < 8; i++) { loc_t new_loc = loc + dir_offset[i]; if (game.real_map[new_loc].on_board && strchr(terrain, vmap[new_loc].contents)) return (new_loc); } return (loc); } /* Look to see if there is an adjacent object to attack. We are passed a location and a list of items we attack sorted in order of most valuable first. We look at each surrounding on board location. If there is an object we can attack, we return the location of the best of these. */ loc_t find_attack(loc_t loc, char *obj_list, char *terrain) { loc_t best_loc; int i, best_val; char *p; best_loc = loc; /* nothing found yet */ best_val = INFINITY; for (i = 0; i < 8; i++) { loc_t new_loc = loc + dir_offset[i]; if (game.real_map[new_loc].on_board /* can we move here? */ && strchr(terrain, game.real_map[new_loc].contents)) { p = strchr(obj_list, game.comp_map[new_loc].contents); if (p != NULL && p - obj_list < best_val) { best_val = p - obj_list; best_loc = new_loc; } } } return (best_loc); } /* Move a transport. There are two kinds of transports: loading and unloading. Loading transports move toward loading armies. Unloading transports move toward attackable cities on unowned continents. An empty transport is willing to attack adjacent enemy transports. Transports become 'loading' when empty, and 'unloading' when full. */ void transport_move(piece_info_t *obj) { loc_t new_loc; /* empty transports can attack */ if (obj->count == 0) { /* empty? */ obj->func = 0; /* transport is loading */ new_loc = find_attack(obj->loc, tt_attack, "."); if (new_loc != obj->loc) { /* something to attack? */ attack(obj, new_loc); /* attack it */ return; } } if (obj->count == obj_capacity(obj)) /* full? */ obj->func = 1; /* unloading */ if (obj->func == 0) { /* loading? */ make_tt_load_map(amap, game.comp_map); new_loc = vmap_find_wlobj(path_map, amap, obj->loc, &tt_load); if (new_loc == obj->loc) { /* nothing to load? */ (void)memcpy(amap, game.comp_map, MAP_SIZE * sizeof(view_map_t)); unmark_explore_locs(amap); if (game.print_vmap == 'S') print_xzoom(amap); new_loc = vmap_find_wobj(path_map, amap, obj->loc, &tt_explore); } move_objective(obj, path_map, new_loc, "a "); } else { make_unload_map(amap, game.comp_map); new_loc = vmap_find_wlobj(path_map, amap, obj->loc, &tt_unload); move_objective(obj, path_map, new_loc, " "); } } /* Move a fighter. 1) See if there is an object we can attack immediately. If so, attack it. 2) Otherwise, if fighter is low on fuel, move toward nearest city if there is one in range. 3) Otherwise, look for an objective. */ void fighter_move(piece_info_t *obj) { loc_t new_loc; new_loc = find_attack(obj->loc, fighter_attack, ".+"); if (new_loc != obj->loc) { /* something to attack? */ attack(obj, new_loc); /* attack it */ return; } /* return to base if low on fuel */ if (obj->range <= find_nearest_city(obj->loc, COMP, &new_loc) + 2) { if (new_loc != obj->loc) new_loc = vmap_find_dest(path_map, game.comp_map, obj->loc, new_loc, COMP, T_AIR); } else new_loc = obj->loc; if (new_loc == obj->loc) { /* no nearby city? */ new_loc = vmap_find_aobj(path_map, game.comp_map, obj->loc, &fighter_fight); } move_objective(obj, path_map, new_loc, " "); } /* Move a ship. Attack anything adjacent. If nothing adjacent, explore or look for something to attack. */ void ship_move(piece_info_t *obj) { loc_t new_loc; char *adj_list; if (obj->hits < piece_attr[obj->type].max_hits) { /* head to port */ if (game.comp_map[obj->loc].contents == 'X') { /* stay in port */ obj->moved = piece_attr[obj->type].speed; return; } new_loc = vmap_find_wobj(path_map, game.comp_map, obj->loc, &ship_repair); adj_list = "."; } else { new_loc = find_attack(obj->loc, ship_attack, "."); if (new_loc != obj->loc) { /* something to attack? */ attack(obj, new_loc); /* attack it */ return; } /* look for an objective */ (void)memcpy(amap, game.comp_map, MAP_SIZE * sizeof(view_map_t)); unmark_explore_locs(amap); if (game.print_vmap == 'S') print_xzoom(amap); new_loc = vmap_find_wobj(path_map, amap, obj->loc, &ship_fight); adj_list = ship_fight.objectives; } move_objective(obj, path_map, new_loc, adj_list); } /* Move to an objective. */ void move_objective(piece_info_t *obj, path_map_t pathmap[], loc_t new_loc, char *adj_list) { char *terrain; int d; bool reuse; /* true iff we should reuse old game.real_map */ loc_t old_loc; loc_t old_dest; if (new_loc == obj->loc) { obj->moved = piece_attr[obj->type].speed; obj->range -= 1; pdebug("No destination found for %d at %d; func=%d\n", obj->type, loc_disp(obj->loc), obj->func); return; } old_loc = obj->loc; /* remember where we are */ old_dest = new_loc; /* and where we're going */ d = dist(new_loc, obj->loc); reuse = true; /* try to reuse unless we learn otherwise */ if (game.comp_map[new_loc].contents == ' ' && d == 2) { /* are we exploring? */ vmap_mark_adjacent(pathmap, obj->loc); reuse = false; } else vmap_mark_path(pathmap, game.comp_map, new_loc); /* find routes to destination */ /* path terrain and move terrain may differ */ switch (obj->type) { case ARMY: terrain = "+"; break; case FIGHTER: terrain = "+.X"; break; default: terrain = ".X"; break; } new_loc = vmap_find_dir(pathmap, game.comp_map, obj->loc, terrain, adj_list); if (new_loc == obj->loc /* path is blocked? */ && (obj->type != ARMY || !obj->ship)) { /* don't unblock armies on a ship */ vmap_mark_near_path(pathmap, obj->loc); reuse = false; new_loc = vmap_find_dir(pathmap, game.comp_map, obj->loc, terrain, adj_list); } /* encourage army to leave city */ if (new_loc == obj->loc && game.real_map[obj->loc].cityp != NULL && obj->type == ARMY) { new_loc = move_away(game.comp_map, obj->loc, "+"); reuse = false; } if (new_loc == obj->loc) { obj->moved = piece_attr[obj->type].speed; if (obj->type == ARMY && obj->ship) ; else pdebug( "Cannot move %d at %d toward objective; func=%d\n", obj->type, loc_disp(obj->loc), obj->func); } else move_obj(obj, new_loc); /* Try to make more moves using same path map. */ if (reuse && obj->moved < obj_moves(obj) && obj->loc != old_dest) { char *attack_list; /* check for immediate attack */ switch (obj->type) { case FIGHTER: if (game.comp_map[old_dest].contents != 'X' /* watch fuel */ && obj->range <= piece_attr[FIGHTER].range / 2) return; attack_list = fighter_attack; terrain = "+."; break; case ARMY: attack_list = army_attack; if (obj->ship) terrain = "+*"; else terrain = "+.*"; break; case TRANSPORT: terrain = ".*"; if (obj->cargo) attack_list = tt_attack; else attack_list = "*O"; /* causes tt to wake up */ break; default: attack_list = ship_attack; terrain = "."; break; } if (find_attack(obj->loc, attack_list, terrain) != obj->loc) return; /* clear old path */ pathmap[old_loc].terrain = T_UNKNOWN; for (d = 0; d < 8; d++) { new_loc = old_loc + dir_offset[d]; pathmap[new_loc].terrain = T_UNKNOWN; } /* pathmap is already marked, but this should work */ move_objective(obj, pathmap, old_dest, adj_list); } } /* Check to see if the game is over. We count the number of cities owned by each side. If either side has no cities and no armies, then the game is over. If the computer has less than half as many cities and armies as the user, then the computer will give up. */ void check_endgame(void) { int nuser_city, ncomp_city; int nuser_army, ncomp_army; piece_info_t *p; int i; game.date += 1; /* one more turn has passed */ if (game.win != 0) return; /* we already know game is over */ nuser_city = 0; /* nothing counted yet */ ncomp_city = 0; nuser_army = 0; ncomp_army = 0; for (i = 0; i < NUM_CITY; i++) { if (game.city[i].owner == USER) nuser_city++; else if (game.city[i].owner == COMP) ncomp_city++; } for (p = game.user_obj[ARMY]; p != NULL; p = p->piece_link.next) nuser_army++; for (p = game.comp_obj[ARMY]; p != NULL; p = p->piece_link.next) ncomp_army++; if (ncomp_city < nuser_city / 3 && ncomp_army < nuser_army / 3) { clear_screen(); prompt("The computer acknowledges defeat. Do"); ksend("The computer acknowledges defeat."); error("you wish to smash the rest of the enemy?"); if (get_chx() != 'Y') empend(); announce( "\nThe enemy inadvertantly revealed its code used for"); announce( "\nreceiving battle information. You can display what"); announce("\nthey've learned with the ''E'' command."); game.resigned = true; game.win = ratio_win; game.automove = false; } else if (ncomp_city == 0 && ncomp_army == 0) { clear_screen(); announce("The enemy is incapable of defeating you.\n"); announce("You are free to rape the empire as you wish.\n"); announce( "There may be, however, remnants of the enemy fleet\n"); announce("to be routed out and destroyed.\n"); game.win = wipeout_win; game.automove = false; } else if (nuser_city == 0 && nuser_army == 0) { clear_screen(); announce("You have been rendered incapable of\n"); announce("defeating the rampaging enemy fascists! The\n"); announce("empire is lost. If you have any ships left, you\n"); announce("may attempt to harass enemy shipping."); game.win = 1; game.automove = false; } } /* end */ vms-empire-1.18/control0000664000175000017500000000174514562204012013303 0ustar esresr# This is not a real Debian control file # It's project metadata for the shipper tool # SPDX-FileCopyrightText: Copyright (C) Eric S. Raymond # SPDX-License-Identifier: GPL-2.0+ Package: vms-empire Description: The ancestor of all expand/explore/exploit/exterminate games. 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 was ancestral to all later expand/explore/exploit/exterminate games, including Civilization and Master of Orion. #XBS-Destinations: freshcode Homepage: http://www.catb.org/~esr/vms-empire XBS-HTML-Target: index.html XBS-Repository-URL: https://gitlab.com/esr/vms-empire XBS-OpenHub-URL: https://www.openhub.net/p/vms-empire #XBS-Project-Tags: Games/Entertainment XBS-Logo: vms-empire.png XBS-VC-Tag-Template: %(version)s XBS-Validate: make cppcheck vms-empire-1.18/COPYING0000664000175000017500000004311414551261072012736 0ustar esresr GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. vms-empire-1.18/data.c0000664000175000017500000001673114562170114012763 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* Static data. One of our hopes is that changing the types of pieces that exist in the game is mostly a matter of modifying this file. However, see also the help routine, empire.h, and empire.6. */ #include "empire.h" // clang-format off /* Piece attributes. Notice that the Transport is allowed only one hit. In the previous version of this game, the user could easily win simply by building armies and troop transports. We attempt to alleviate this problem by making transports far more fragile. We have also increased the range of a fighter from 20 to 30 so that fighters will be somewhat more useful. */ piece_attr_t piece_attr[] = { {'A', /* character for printing piece */ "army", /* name of piece */ "army", /* nickname */ "an army", /* name with preceding article */ "armies", /* plural */ "+", /* terrain */ 5, /* units to build */ 1, /* strength */ 1, /* max hits */ 1, /* movement */ 0, /* capacity */ INFINITY}, /* range */ /* For fighters, the range is set to an even multiple of the speed. This allows user to move fighter, say, two turns out and two turns back. */ {'F', "fighter", "fighter", "a fighter", "fighters", ".+", 10, 1, 1, 8, 0, 32}, {'P', "patrol boat", "patrol", "a patrol boat", "patrol boats", ".", 15, 1, 1, 4, 0, INFINITY}, {'D', "destroyer", "destroyer", "a destroyer", "destroyers", ".", 20, 1, 3, 2, 0, INFINITY}, {'S', "submarine", "submarine", "a submarine", "submarines", ".", 20, 3, 2, 2, 0, INFINITY}, {'T', "troop transport", "transport", "a troop transport", "troop transports", ".", 30, 1, 1, 2, 6, INFINITY}, {'C', "aircraft carrier", "carrier", "an aircraft carrier", "aircraft carriers", ".", 30, 1, 8, 2, 8, INFINITY}, {'B', "battleship", "battleship", "a battleship", "battleships", ".", 40, 2, 10, 2, 0, INFINITY}, {'Z', "satellite", "satellite", "a satellite", "satellites", ".+", 50, 0, 1, 10, 0, 500} }; /* Direction offsets. */ int dir_offset [] = {-MAP_WIDTH, /* north */ -MAP_WIDTH+1, /* northeast */ 1, /* east */ MAP_WIDTH+1, /* southeast */ MAP_WIDTH, /* south */ MAP_WIDTH-1, /* southwest */ -1, /* west */ -MAP_WIDTH-1}; /* northwest */ /* Names of movement functions. */ char *func_name[] = {"none", "random", "sentry", "fill", "land", "explore", "load", "attack", "load", "repair", "transport", "W", "E", "D", "C", "X", "Z", "A", "Q"}; /* The order in which pieces should be moved. */ int move_order[] = {SATELLITE, TRANSPORT, CARRIER, BATTLESHIP, PATROL, SUBMARINE, DESTROYER, ARMY, FIGHTER}; /* types of pieces, in declared order */ char type_chars[] = "AFPDSTCBZ"; /* Lists of attackable objects if object is adjacent to moving piece. */ char tt_attack[] = "T"; char army_attack[] = "O*TACFBSDP"; char fighter_attack[] = "TCFBSDPA"; char ship_attack[] = "TCBSDP"; /* Define various types of objectives */ move_info_t tt_explore = { /* water objectives */ COMP, /* home city */ " ", /* objectives */ {1} /* weights */ }; move_info_t tt_load = { /* land objectives */ COMP, "$", {1} }; /* Rationale for 'tt_unload': Any continent with four or more cities is extremely attractive, and we should grab it quickly. A continent with three cities is fairly attractive, but we are willing to go slightly out of our way to find a better continent. Similarily for two cities on a continent. At one city on a continent, things are looking fairly unattractive, and we are willing to go quite a bit out of our way to find a better continent. Cities marked with a '0' are on continents where we already have cities, and these cities will likely fall to our armies anyway, so we don't need to dump armies on them unless everything else is real far away. We would prefer to use unloading transports for taking cities instead of exploring, but we are willing to explore if interesting cities are too far away. It has been suggested that continents containing one city are not interesting. Unfortunately, most of the time what the computer sees is a single city on a continent next to lots of unexplored territory. So it dumps an army on the continent to explore the continent and the army ends up attacking the city anyway. So, we have decided we might as well send the tt to the city in the first place and increase the speed with which the computer unloads its tts. */ move_info_t tt_unload = { COMP, "9876543210 ", {1, 1, 1, 1, 1, 1, 11, 21, 41, 101, 61} }; /* '$' represents loading tt must be first 'x' represents tt producing city '0' represnets explorable territory */ move_info_t army_fight = { /* land objectives */ COMP, "O*TA ", {1, 1, 1, 1, 11} }; move_info_t army_load = { /* water objectives */ COMP, "$x", {1, W_TT_BUILD} }; move_info_t fighter_fight = { COMP, "TCFBSDPA ", {1, 1, 5, 5, 5, 5, 5, 5, 9} }; move_info_t ship_fight = { COMP, "TCBSDP ", {1, 1, 3, 3, 3, 3, 21} }; move_info_t ship_repair = { COMP, "X", {1} }; move_info_t user_army = { USER, " ", {1} }; move_info_t user_army_attack = { USER, "*Xa ", {1, 1, 1, 12} }; move_info_t user_fighter = { USER, " ", {1} }; move_info_t user_ship = { USER, " ", {1} }; move_info_t user_ship_repair = { USER, "O", {1} }; /* Various help texts. */ char *help_cmd[] = { "COMMAND MODE", "Auto: enter automove mode", "City: give city to computer", "Date: print round", "Examine: examine enemy map", "File: print map to file", "Give: give move to computer", "Help: display this text", "J: enter edit mode", "Move: make a move", "N: give N moves to computer", "Print: print a sector", "Quit: quit game", "Restore: restore game", "Save: save game", "Trace: save movie in empmovie.dat", "Watch: watch movie", "Zoom: display compressed map", ": redraw screen" }; int cmd_lines = 19; char *help_user[] = { "USER MODE", "QWE", "A D movement directions", "ZXC", ": sit", "Build: change city production", "Fill: set func to fill", "Grope: set func to explore", "Help: display this text", "I : set func to dir", "J: enter edit mode", "Kill: set func to awake", "Land: set func to land", "Out: leave automove mode", "Print: redraw screen", "Random: set func to random", "Sentry: set func to sentry", "Transport:set func to transport", "Upgrade: set func to repair", "V : set city func", "Y: set func to attack", ": redraw screen", "?: describe piece" }; int user_lines = 22; char *help_edit[] = { "EDIT MODE", "QWE", "A D movement directions", "ZXC", "Build: change city production", "Fill: set func to fill", "Grope: set func to explore", "Help: display this text", "I : set func to dir", "J: show all city's production", "Kill: set func to awake", "Land: set func to land", "Mark: mark piece", "N: set dest for marked piece", "Out: exit edit mode", "Print: print sector", "Random: set func to random", "Sentry: set func to sentry", "Upgrade: set func to repair", "V : set city func", "Y: set func to attack", ": redraw screen", "?: describe piece" }; int edit_lines = 22; // clang-format on vms-empire-1.18/display.c0000664000175000017500000004002114674360267013522 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* display.c -- This file contains routines for displaying sectors and moving the cursor about in a sector. We need to remember the following information: the current map portion displayed on the screen; whether the displayed portion is from the user's or the computer's point of view; */ #include "empire.h" #include "extern.h" #include #include static int whose_map = UNOWNED; /* user's or computer's point of view */ static int ref_row; /* map loc displayed in upper-left corner */ static int ref_col; static int save_sector; /* the currently displayed sector */ static int save_cursor; /* currently displayed cursor position */ static bool change_ok = true; /* true if new sector may be displayed */ static void show_loc(view_map_t vmap[], loc_t loc); static void disp_square(view_map_t *vp); static void disp_city_prod(loc_t t); static bool on_screen(loc_t loc); #ifdef A_COLOR void init_colors(void) { start_color(); init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK); init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK); init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK); init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK); init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK); init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK); init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK); init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK); attron(A_BOLD); /* otherwise we get gray for white */ keypad(stdscr, TRUE); } #endif /* A_COLOR */ /* Used for win announcements */ void announce(char *msg) { (void)addstr(msg); } /* * Map input character to direction offset. * Attempts to enable arrow and keypad keys. */ int direction(chtype c) { switch (c) { case 'w': case 'W': case KEY_UP: return 0; case 'e': case 'E': case KEY_A3: case KEY_PPAGE: return 1; case 'd': case 'D': case KEY_RIGHT: return 2; case 'c': case 'C': case KEY_C3: case KEY_NPAGE: return 3; case 'x': case 'X': case KEY_DOWN: return 4; case 'z': case 'Z': case KEY_C1: case KEY_END: return 5; case 'a': case 'A': case KEY_LEFT: return 6; case 'q': case 'Q': case KEY_A1: case KEY_HOME: return 7; default: return -1; } } /* This routine is called when the current display has been trashed and no sector is shown on the screen. */ void kill_display(void) { whose_map = UNOWNED; } /* This routine is called when a new sector may be displayed on the screen even if the location to be displayed is already on the screen. */ void sector_change(void) { change_ok = true; } /* Return the currently displayed user sector, if any. If a user sector is not displayed, return -1. */ int cur_sector(void) { if (whose_map != USER) { return (-1); } return (save_sector); } /* Return the current position of the cursor. If the user's map is not on the screen, we return -1. */ loc_t cur_cursor(void) { if (whose_map != USER) { return (-1); } return (save_cursor); } /* Display a location on the screen. We figure out the sector the location is in and display that sector. The cursor is left at the requested location. We redisplay the sector only if we either have been requested to redisplay the sector, or if the location is not on the screen. */ void display_loc(int whose, view_map_t vmap[], loc_t loc) /* whose is whose map to display; loc is location to display */ { if (change_ok || whose != whose_map || !on_screen(loc)) { print_sector(whose, vmap, loc_sector(loc)); } show_loc(vmap, loc); } /* Display a location iff the location is on the screen. */ void display_locx(int whose, view_map_t vmap[], loc_t loc) /* whose is whose map to display; loc is location to display */ { if (whose == whose_map && on_screen(loc)) { show_loc(vmap, loc); } } /* Display a location which exists on the screen. */ void show_loc(view_map_t vmap[], loc_t loc) { int r, c; r = loc_row(loc); c = loc_col(loc); (void)move(r - ref_row + NUMTOPS, c - ref_col); if (game.showprod && vmap[loc].contents == 'O') { disp_city_prod(loc); } else { disp_square(&vmap[loc]); } save_cursor = loc; /* remember cursor location */ (void)move(r - ref_row + NUMTOPS, c - ref_col); } /* Print a sector of the user's on the screen. If it is already displayed, we do nothing. Otherwise we redraw the screen. Someday, some intelligence in doing this might be interesting. We heavily depend on curses to update the screen in a reasonable fashion. If the desired sector is not displayed, we clear the screen. We then update the screen to reflect the current map. We heavily depend on curses to correctly optimize the redrawing of the screen. When redrawing the screen, we figure out where the center of the sector is in relation to the map. We then compute the screen coordinates where we want to display the center of the sector. We will remember the sector displayed, the map displayed, and the map location that appears in the upper-left corner of the screen. */ void print_sector(int whose, view_map_t vmap[], int sector) /* whose is USER or COMP, vmap is map to display, sector is sector to display */ { void display_screen(view_map_t[]); int first_row, first_col, last_row, last_col; int display_rows, display_cols; int r, c; save_sector = sector; /* remember last sector displayed */ change_ok = false; /* we are displaying a new sector */ display_rows = game.lines - NUMTOPS - 1; /* num lines to display */ display_cols = game.cols - NUMSIDES; /* compute row and column edges of sector */ first_row = sector_row(sector) * ROWS_PER_SECTOR; first_col = sector_col(sector) * COLS_PER_SECTOR; last_row = first_row + ROWS_PER_SECTOR - 1; last_col = first_col + COLS_PER_SECTOR - 1; if (!(whose == whose_map /* correct map is on screen? */ && ref_row <= first_row /* top row on screen? */ && ref_col <= first_col /* first col on screen? */ && ref_row + display_rows - 1 >= last_row /* bot row on screen? */ && ref_col + display_cols - 1 >= last_col)) { /* last col on screen? */ (void)clear(); /* erase current screen */ } /* figure out first row and col to print; subtract half the extra lines from the first line */ ref_row = first_row - (display_rows - ROWS_PER_SECTOR) / 2; ref_col = first_col - (display_cols - COLS_PER_SECTOR) / 2; /* try not to go past bottom of map */ if (ref_row + display_rows - 1 > MAP_HEIGHT - 1) { ref_row = MAP_HEIGHT - 1 - (display_rows - 1); } /* never go past top of map */ if (ref_row < 0) { ref_row = 0; } /* same with columns */ if (ref_col + display_cols - 1 > MAP_WIDTH - 1) { ref_col = MAP_WIDTH - 1 - (display_cols - 1); } if (ref_col < 0) { ref_col = 0; } whose_map = whose; /* remember whose map is displayed */ display_screen(vmap); /* print x-coordinates along bottom of screen */ for (c = ref_col; c < ref_col + display_cols && c < MAP_WIDTH; c++) { if (c % 10 == 0) { pos_str(game.lines - 1, c - ref_col, "%d", c); } } /* print y-coordinates along right of screen */ for (r = ref_row; r < ref_row + display_rows && r < MAP_HEIGHT; r++) { if (r % 2 == 0) { pos_str(r - ref_row + NUMTOPS, game.cols - NUMSIDES + 1, "%2d", r); } else { pos_str(r - ref_row + NUMTOPS, game.cols - NUMSIDES + 1, " "); } } /* print round number */ (void)sprintf(game.jnkbuf, "Sector %d Round %ld", sector, game.date); for (r = 0; game.jnkbuf[r] != '\0'; r++) { if (r + NUMTOPS >= MAP_HEIGHT) break; (void)move(r + NUMTOPS, game.cols - NUMSIDES + 4); (void)addch((chtype)game.jnkbuf[r]); } } /* Display the contents of a single map square. Fancy color hacks are done here. At the moment this is kind of bogus, because the color doesn't convey any extra information, it just looks pretty. */ static void disp_square(view_map_t *vp) { #ifdef A_COLOR chtype attr; switch (vp->contents) { case MAP_LAND: attr = COLOR_PAIR(COLOR_GREEN); break; case MAP_SEA: attr = COLOR_PAIR(COLOR_CYAN); break; case 'a': case 'f': case 'p': case 'd': case 'b': case 't': case 'c': case 's': case 'z': case 'X': attr = COLOR_PAIR(COLOR_RED); break; default: attr = COLOR_PAIR(COLOR_WHITE); break; } attron(attr); #endif /* A_COLOR */ (void)addch((chtype)vp->contents); #ifdef A_COLOR attroff(attr); attron(COLOR_PAIR(COLOR_WHITE)); #endif /* A_COLOR */ } /* Routine to show city production instead of boring O */ static void disp_city_prod(loc_t t) { chtype attr; city_info_t *cityp; cityp = find_city(t); ASSERT(cityp != NULL); attr = COLOR_PAIR(COLOR_CYAN); attron(attr); // cppcheck-suppress nullPointerRedundantCheck (void)addch((chtype)piece_attr[(int)cityp->prod].sname); attroff(attr); attron(COLOR_PAIR(COLOR_WHITE)); } /* Display the portion of the map that appears on the screen. */ void display_screen(view_map_t vmap[]) { int display_rows, display_cols; int r, c; loc_t t; display_rows = game.lines - NUMTOPS - 1; /* num lines to display */ display_cols = game.cols - NUMSIDES; for (r = ref_row; r < ref_row + display_rows && r < MAP_HEIGHT; r++) { for (c = ref_col; c < ref_col + display_cols && c < MAP_WIDTH; c++) { t = row_col_loc(r, c); (void)move(r - ref_row + NUMTOPS, c - ref_col); if (game.showprod && vmap[t].contents == 'O') { disp_city_prod(t); } else { disp_square(&vmap[t]); } } } } /* Move the cursor in a specified direction. We return true if the cursor remains in the currently displayed screen, otherwise false. We display the cursor on the screen, if possible. */ bool move_cursor(loc_t *cursor, int offset) /* cursor is current cursor position, offset is offset to add to cursor */ { loc_t t; int r, c; t = *cursor + offset; /* proposed location */ if (!game.real_map[t].on_board) { return (false); /* trying to move off map */ } if (!on_screen(t)) { return (false); /* loc is off screen */ } *cursor = t; /* update cursor position */ save_cursor = *cursor; r = loc_row(save_cursor); c = loc_col(save_cursor); (void)move(r - ref_row + NUMTOPS, c - ref_col); return (true); } /* See if a location is displayed on the screen. */ bool on_screen(loc_t loc) { int new_r, new_c; new_r = loc_row(loc); new_c = loc_col(loc); if (new_r < ref_row /* past top of screen */ || new_r - ref_row > game.lines - NUMTOPS - 1 /* past bot of screen? */ || new_c < ref_col /* past left edge of screen? */ || new_c - ref_col > game.cols - NUMSIDES) { /* past right edge of screen? */ return (false); } return (true); } /* Print a view map for debugging. */ void print_xzoom(view_map_t *vmap) { print_zoom(vmap); #if 0 prompt ("Hit a key: ",0,0,0,0,0,0,0,0); (void) get_chx (); /* wait for user */ #endif } /* Print a condensed version of the map. */ char zoom_list[] = "XO*tcbsdpfaTCBSDPFAzZ+. "; void print_zoom(view_map_t *vmap) { void print_zoom_cell(view_map_t *, int, int, int, int); int row_inc, col_inc; int r, c; kill_display(); row_inc = (MAP_HEIGHT + game.lines - NUMTOPS - 1) / (game.lines - NUMTOPS); col_inc = (MAP_WIDTH + game.cols - 1) / (game.cols - 1); for (r = 0; r < MAP_HEIGHT; r += row_inc) { for (c = 0; c < MAP_WIDTH; c += col_inc) { print_zoom_cell(vmap, r, c, row_inc, col_inc); } } pos_str(0, 0, "Round #%d", game.date); (void)refresh(); } /* Print a single cell in condensed format. */ void print_zoom_cell(view_map_t *vmap, int row, int col, int row_inc, int col_inc) { int r, c; char cell; cell = ' '; for (r = row; r < row + row_inc; r++) { for (c = col; c < col + col_inc; c++) { if (strchr(zoom_list, vmap[row_col_loc(r, c)].contents) < strchr(zoom_list, cell)) { cell = vmap[row_col_loc(r, c)].contents; } } } (void)move(row / row_inc + NUMTOPS, col / col_inc); (void)addch((chtype)cell); } /* Print a condensed version of a pathmap. */ void print_pzoom(char *s, path_map_t *pmap, view_map_t *vmap) { void print_pzoom_cell(path_map_t *, view_map_t *, int, int, int, int); int row_inc, col_inc; int r, c; kill_display(); row_inc = (MAP_HEIGHT + game.lines - NUMTOPS - 1) / (game.lines - NUMTOPS); col_inc = (MAP_WIDTH + game.cols - 1) / (game.cols - 1); for (r = 0; r < MAP_HEIGHT; r += row_inc) { for (c = 0; c < MAP_WIDTH; c += col_inc) { print_pzoom_cell(pmap, vmap, r, c, row_inc, col_inc); } } prompt(s, 0, 0, 0, 0, 0, 0, 0, 0); (void)get_chx(); /* wait for user */ (void)refresh(); } /* Print a single cell of a pathmap in condensed format. We average all squares in the cell and take the mod 10 value. Squares with a value of -1 are printed with '-', squares with a value of INFINITY/2 are printed with 'P', and squares with a value of INFINITY are printed with 'Z'. Squares with a value between P and Z are printed as U. */ void print_pzoom_cell(path_map_t *pmap, view_map_t *vmap, int row, int col, int row_inc, int col_inc) { int r, c; int sum, d; char cell; sum = 0; d = 0; /* number of squares in cell */ for (r = row; r < row + row_inc; r++) { for (c = col; c < col + col_inc; c++) { sum += pmap[row_col_loc(r, c)].cost; d += 1; } } // cppcheck-suppress zerodiv sum /= d; if (pmap[row_col_loc(row, col)].terrain == T_PATH) cell = '-'; else if (sum < 0) cell = '!'; else if (sum == INFINITY / 2) cell = 'P'; else if (sum == INFINITY) cell = ' '; else if (sum > INFINITY / 2) cell = 'U'; else { sum %= 36; if (sum < 10) { cell = sum + '0'; } else { cell = sum - 10 + 'a'; } } if (cell == ' ') print_zoom_cell(vmap, row, col, row_inc, col_inc); else { (void)move(row / row_inc + NUMTOPS, col / col_inc); (void)addch((chtype)cell); } } /* Display the score off in the corner of the screen. */ void display_score(void) { pos_str(1, game.cols - 12, " User Comp"); pos_str(2, game.cols - 12, "%5d %5d", game.user_score, game.comp_score); } /* Clear the end of a specified line starting at the specified column. */ void clreol(int linep, int colp) { (void)move(linep, colp); (void)clrtoeol(); } /* Initialize the terminal. */ void ttinit(void) { (void)initscr(); (void)noecho(); (void)crmode(); #ifdef A_COLOR init_colors(); #endif /* A_COLOR */ game.lines = LINES; game.cols = COLS; if (game.lines > MAP_HEIGHT + NUMTOPS + 1) { game.lines = MAP_HEIGHT + NUMTOPS + 1; } if (game.cols > MAP_WIDTH + NUMSIDES) { game.cols = MAP_WIDTH + NUMSIDES; } } /* Clear the screen. We must also kill information maintained about the display. */ void clear_screen(void) { (void)clear(); (void)refresh(); kill_display(); } /* Audible complaint. */ void complain(void) { (void)beep(); } /* Redraw the screen. */ void redisplay(void) { (void)refresh(); } void redraw(void) { (void)clearok(curscr, TRUE); (void)refresh(); } /* Wait a little bit to give user a chance to see a message. We refresh the screen and pause for a few milliseconds. */ void delay(void) { int t = game.delay_time; int i = 500; (void)refresh(); if (t > i) { (void)move(LINES - 1, 0); } for (; t > 0; t -= i) { (void)napms((t > i) ? i : t); /* pause a bit */ if (t > i) { addstr("*"); refresh(); } } } /* Clean up the display. This routine gets called as we leave the game. */ void close_disp(void) { (void)move(LINES - 1, 0); (void)clrtoeol(); (void)refresh(); (void)endwin(); } /* Position the cursor and output a string. */ void pos_str(int row, int col, char *str, ...) { va_list ap; char junkbuf[STRSIZE]; va_start(ap, str); (void)move(row, col); vsprintf(junkbuf, str, ap); (void)addstr(junkbuf); va_end(ap); } /* Print a single cell in condensed format. */ extern char zoom_list[]; void print_movie_cell(char *mbuf, int row, int col, int row_inc, int col_inc) { int r, c; char cell; cell = ' '; for (r = row; r < row + row_inc; r++) { for (c = col; c < col + col_inc; c++) { if (strchr(zoom_list, mbuf[row_col_loc(r, c)]) < strchr(zoom_list, cell)) { cell = mbuf[row_col_loc(r, c)]; } } } (void)move(row / row_inc + NUMTOPS, col / col_inc); (void)addch((chtype)cell); } /* end */ vms-empire-1.18/edit.c0000664000175000017500000002673414562175610013011 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* edit.c -- Routines to handle edit mode commands. */ #include "empire.h" #include "extern.h" #include #include #include #include void e_move(loc_t *path_start, loc_t loc); extern int get_piece_name(void); void edit(loc_t edit_cursor) { char e_cursor(loc_t *); void e_leave(void), e_print(loc_t *), e_random(loc_t); void e_stasis(loc_t), e_end(loc_t *, loc_t, int); void e_wake(loc_t), e_sleep(loc_t); void e_info(loc_t), e_prod(loc_t), e_help(void), e_explore(loc_t); void e_fill(loc_t), e_land(loc_t), e_transport(loc_t); void e_city_func(loc_t *, loc_t, int *); void e_attack(loc_t), e_repair(loc_t); loc_t path_start; int path_type; path_start = -1; /* not building a path yet */ game.showprod = false; comment("Edit mode..."); for (;;) { /* until user gives command to leave */ char e; display_loc_u(edit_cursor); /* position cursor */ e = e_cursor(&edit_cursor); /* handle cursor movement */ switch (e) { case 'B': /* change city production */ e_prod(edit_cursor); break; case 'F': /* fill */ e_fill(edit_cursor); break; case 'G': /* explore */ e_explore(edit_cursor); break; case 'H': /* help */ e_help(); break; case 'I': /* directional stasis */ e_stasis(edit_cursor); break; case 'J': /* Redraw map showing city production */ game.showprod = true; sector_change(); // Force redraw */ break; case 'K': /* wake up anything and everything */ e_wake(edit_cursor); break; case 'L': /* land plane */ e_land(edit_cursor); break; case 'M': /* start move to location */ path_type = NOPIECE; e_move(&path_start, edit_cursor); break; case 'N': /* end move to location */ e_end(&path_start, edit_cursor, path_type); break; case 'O': /* leave edit mode */ if (game.showprod) { game.showprod = false; sector_change(); // Force redraw */ } e_leave(); return; case 'P': /* print new sector */ e_print(&edit_cursor); break; case 'R': /* make piece move randomly */ e_random(edit_cursor); break; case 'S': /* sleep */ e_sleep(edit_cursor); break; case 'T': /* transport army */ e_transport(edit_cursor); break; case 'U': /* repair ship */ e_repair(edit_cursor); break; case 'V': /* set city function */ e_city_func(&path_start, edit_cursor, &path_type); break; case 'Y': /* set army func to attack */ e_attack(edit_cursor); break; case '?': /* request info */ e_info(edit_cursor); break; case '\014': /* control-L */ redraw(); break; default: /* bad command? */ huh(); break; } } } /* Get the next command. We handle cursor movement here. This routine is an attempt to make cursor movement reasonably fast. */ char e_cursor(loc_t *edit_cursor) { chtype e; /* set up terminal */ (void)crmode(); (void)refresh(); e = getch(); topini(); /* clear any error messages */ for (;;) { int p; p = direction(e); if (p == -1) { break; } if (!move_cursor(edit_cursor, dir_offset[p])) { (void)beep(); } (void)refresh(); e = getch(); } (void)nocrmode(); /* reset terminal */ return toupper(e); } /* Leave edit mode. */ void e_leave(void) { comment("Exiting edit mode."); } /* Print new sector. */ void e_print(loc_t *edit_cursor) { int sector; sector = get_range("New Sector? ", 0, NUM_SECTORS - 1); /* position cursor at center of sector */ *edit_cursor = sector_loc(sector); sector_change(); /* allow change of sector */ } /* Set the function of a piece. */ void e_set_func(loc_t loc, long func) { piece_info_t *obj; obj = find_obj_at_loc(loc); if (obj != NULL && obj->owner == USER) { obj->func = func; return; } huh(); /* no object here */ } /* Set the function of a city for some piece. */ void e_set_city_func(city_info_t *cityp, int type, long func) { ASSERT(cityp != NULL); // cppcheck-suppress nullPointerRedundantCheck cityp->func[type] = func; } /* Set a piece to move randomly. */ void e_random(loc_t loc) { e_set_func(loc, RANDOM); } void e_city_random(city_info_t *cityp, int type) { e_set_city_func(cityp, type, RANDOM); } /* Put a ship in fill mode. */ void e_fill(loc_t loc) { if (game.user_map[loc].contents == 'T' || game.user_map[loc].contents == 'C') { e_set_func(loc, FILL); } else { huh(); } } void e_city_fill(city_info_t *cityp, int type) { if (type == TRANSPORT || type == CARRIER) { e_set_city_func(cityp, type, FILL); } else { huh(); } } /* Set a piece to explore. */ void e_explore(loc_t loc) { e_set_func(loc, EXPLORE); } void e_city_explore(city_info_t *cityp, loc_t type) { e_set_city_func(cityp, type, EXPLORE); } /* Set a fighter to land. */ void e_land(loc_t loc) { if (game.user_map[loc].contents == 'F') { e_set_func(loc, LAND); } else { huh(); } } /* Set an army's function to TRANSPORT. */ void e_transport(loc_t loc) { if (game.user_map[loc].contents == 'A') { e_set_func(loc, WFTRANSPORT); } else { huh(); } } /* Set an army's function to ATTACK. */ void e_attack(loc_t loc) { if (game.user_map[loc].contents == 'A') { e_set_func(loc, ARMYATTACK); } else { huh(); } } void e_city_attack(city_info_t *cityp, int type) { if (type == ARMY) { e_set_city_func(cityp, type, ARMYATTACK); } else { huh(); } } /* Set a ship's function to REPAIR. */ void e_repair(loc_t loc) { if (strchr("PDSTBC", game.user_map[loc].contents)) { e_set_func(loc, REPAIR); } else { huh(); } } void e_city_repair(city_info_t *cityp, int type) { if (type == ARMY || type == FIGHTER || type == SATELLITE) { huh(); } else { e_set_city_func(cityp, type, REPAIR); } } /* Set object to move in a direction. */ static char dirs[] = "WEDCXZAQ"; void e_stasis(loc_t loc) { if (!isupper(game.user_map[loc].contents)) { huh(); /* no object here */ } else if (game.user_map[loc].contents == 'X') { huh(); } else { char e = get_chx(); /* get a direction */ char *p = strchr(dirs, e); if (p == NULL) { huh(); } else { e_set_func(loc, (long)(MOVE_N - (p - dirs))); } } } void e_city_stasis(city_info_t *cityp, int type) { char e; char *p; e = get_chx(); /* get a direction */ p = strchr(dirs, e); if (p == NULL) { huh(); } else { e_set_city_func(cityp, type, (long)(MOVE_N - (p - dirs))); } } /* Wake up anything and everything. */ void e_wake(loc_t loc) { city_info_t *cityp; piece_info_t *obj; cityp = find_city(loc); if (cityp != NULL) { int i; for (i = 0; i < NUM_OBJECTS; i++) { cityp->func[i] = NOFUNC; } } for (obj = game.real_map[loc].objp; obj != NULL; obj = obj->loc_link.next) { obj->func = NOFUNC; } } void e_city_wake(city_info_t *cityp, int type) { e_set_city_func(cityp, type, NOFUNC); } /* Set a city's function. We get the piece type to set, then the function itself. */ void e_city_func(loc_t *path_start, loc_t loc, int *path_type) { int type; char e; city_info_t *cityp; cityp = find_city(loc); if (!cityp || cityp->owner != USER) { huh(); return; } type = get_piece_name(); if (type == NOPIECE) { huh(); return; } e = get_chx(); switch (e) { case 'F': /* fill */ e_city_fill(cityp, type); break; case 'G': /* explore */ e_city_explore(cityp, type); break; case 'I': /* directional stasis */ e_city_stasis(cityp, type); break; case 'K': /* turn off function */ e_city_wake(cityp, type); break; case 'M': /* start move to location */ *path_type = type; e_move(path_start, loc); break; case 'R': /* make piece move randomly */ e_city_random(cityp, type); break; case 'U': /* repair ship */ e_city_repair(cityp, type); break; case 'Y': /* set army func to attack */ e_city_attack(cityp, type); break; default: /* bad command? */ huh(); break; } } /* Beginning of move to location. */ void e_move(loc_t *path_start, loc_t loc) { if (!isupper(game.user_map[loc].contents)) { huh(); /* nothing there? */ } else if (game.user_map[loc].contents == 'X') { huh(); /* enemy city? */ } else { *path_start = loc; } } /* End of move to location. */ void e_end(loc_t *path_start, loc_t loc, int path_type) { if (*path_start == -1) { huh(); /* no path started? */ } else if (path_type == NOPIECE) { e_set_func(*path_start, loc); } else { city_info_t *cityp = find_city(*path_start); ASSERT(cityp != NULL); e_set_city_func(cityp, path_type, loc); } *path_start = -1; /* remember no path in progress */ } /* Put a piece to sleep. */ void e_sleep(loc_t loc) { if (game.user_map[loc].contents == 'O') { huh(); /* can't sleep a city */ } else { e_set_func(loc, SENTRY); } } /* Print out information about a piece. */ void e_info(loc_t edit_cursor) { void e_city_info(loc_t), e_piece_info(loc_t edit_cursor, char ab); char ab; ab = game.user_map[edit_cursor].contents; if (ab == 'O') { e_city_info(edit_cursor); } else if (ab == 'X' && game.debug) { e_city_info(edit_cursor); } else if ((ab >= 'A') && (ab <= 'T')) { e_piece_info(edit_cursor, ab); } else if ((ab >= 'a') && (ab <= 't') && (game.debug)) { e_piece_info(edit_cursor, ab); } else { huh(); } } /* Print info about a piece. */ void e_piece_info(loc_t edit_cursor, char ab) { piece_info_t *obj; int type; char *p; ab = toupper(ab); p = strchr(type_chars, ab); type = p - type_chars; obj = find_obj(type, edit_cursor); ASSERT(obj != NULL); describe_obj(obj); } /* Display info on a city. */ void e_city_info(loc_t edit_cursor) { piece_info_t *obj; city_info_t *cityp; int f, s; char func_buf[STRSIZE]; char temp_buf[STRSIZE]; char junk_buf2[STRSIZE]; error(""); /* clear line */ f = 0; /* no fighters counted yet */ for (obj = game.real_map[edit_cursor].objp; obj != NULL; obj = obj->loc_link.next) { if (obj->type == FIGHTER) { f++; } } s = 0; /* no ships counted yet */ for (obj = game.real_map[edit_cursor].objp; obj != NULL; obj = obj->loc_link.next) { if (obj->type >= DESTROYER) { s++; } } if (f == 1 && s == 1) { (void)sprintf(game.jnkbuf, "1 fighter landed, 1 ship docked"); } else if (f == 1) { (void)sprintf(game.jnkbuf, "1 fighter landed, %d ships docked", s); } else if (s == 1) { (void)sprintf(game.jnkbuf, "%d fighters landed, 1 ship docked", f); } else { (void)sprintf(game.jnkbuf, "%d fighters landed, %d ships docked", f, s); } cityp = find_city(edit_cursor); ASSERT(cityp != NULL); *func_buf = 0; /* nothing in buffer */ for (s = 0; s < NUM_OBJECTS; s++) { /* for each piece */ // cppcheck-suppress nullPointerRedundantCheck if (cityp->func[s] < 0) { (void)sprintf(temp_buf, "%c:%s; ", piece_attr[s].sname, func_name[FUNCI(cityp->func[s])]); } else { (void)sprintf(temp_buf, "%c: %d;", piece_attr[s].sname, loc_disp(cityp->func[s])); } (void)strcat(func_buf, temp_buf); } (void)sprintf( junk_buf2, "City at location %d will complete %s on round %ld", loc_disp(cityp->loc), piece_attr[(int)cityp->prod].article, game.date + piece_attr[(int)cityp->prod].build_time - cityp->work); info(junk_buf2, game.jnkbuf, func_buf); } /* Change city production. */ void e_prod(loc_t loc) { city_info_t *cityp; cityp = find_city(loc); if (cityp == NULL) { huh(); /* no city? */ } else { set_prod(cityp); } } /* get help */ void e_help(void) { help(help_edit, edit_lines); prompt("Press any key to continue: "); (void)get_chx(); } /* end */ vms-empire-1.18/empire.60000664000175000017500000006716314562177341013274 0ustar esresr'\" t .\" Title: Empire .\" Author: [see the "AUTHORS" section] .\" Generator: DocBook XSL Stylesheets vsnapshot .\" Date: 02/11/2024 .\" Manual: Games .\" Source: empire .\" Language: English .\" .TH "EMPIRE" "6" "02/11/2024" "empire" "Games" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" empire \- the wargame of the century .SH "SYNOPSIS" .HP \w'\fBempire\fR\ 'u \fBempire\fR [\-w\ \fIwater\fR] [\-s\ \fIsmooth\fR] [\-d\ \fIdelay\fR] [\-S\ \fIsave\-interval\fR] [\-f\ \fIsavefile\fR] .SH "DESCRIPTION" .PP 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\&. .PP \fB\-w\fR\fIwater\fR .RS 4 This option controls the amount of water on the map\&. This is given as the percentage of the map which should be water\&. The default is 70% water\&. \fIwater\fR must be an integer in the between 10 and 90 inclusive\&. .RE .PP \fB\-s\fR\fIsmooth\fR .RS 4 This controls the smoothness of the map\&. A low value will produce a highly chaotic map with lots of small islands or lakes\&. A high value will produce a map with a few scattered continents\&. Be forewarned that a high value will cause the program to take a long time to start up\&. The default value is 5\&. .RE .PP \fB\-d\fR\fIdelay\fR .RS 4 This option controls the length of time the computer will delay after printing informational messages at the top of the screen\&. \fIdelay\fR is specified in milliseconds\&. The default value is 2000 which allows the user two seconds to read a message\&. .RE .SH "EXAMPLES" .PP empire \-w90 \-s2 .PP This produces a map with many islands\&. .PP empire \-w50 \-s0 .PP This produces a really strange map\&. These values are not recommended for the faint at heart\&. .PP empire \-w10 .PP This produces a map with lots of land and a few lakes\&. The computer will have a hard time on this sort of map as it will try and produce lots of troop transports, which are fairly useless\&. .PP There are two other option\&. .PP \fB\-S\fR\fIinterval\fR .RS 4 sets the \fIsave interval\fR for the game (default is 10)\&. Once per \fIinterval\fR turns the game state will be automatically saved after your move\&. It will be saved in any case when you change modes or do various special things from command mode, such as `M\*(Aq or `N\*(Aq\&. .RE .PP \fB\-f\fR\fIsavefile\fR .RS 4 Set the save file name (normally empsave\&.dat)\&. .RE .SH "INTRODUCTION" .PP Empire is a war game played between you and the computer\&. The world on which the game takes place is a square rectangle containing cities, land, and water\&. Cities are used to build armies, planes, and ships which can move across the world destroying enemy pieces, exploring, and capturing more cities\&. The objective of the game is to destroy all the enemy pieces, and capture all the cities\&. .PP The world is a rectangle 60 by 100 squares on a side\&. The world consists of sea (\&.), land (+), uncontrolled cities (*), computer\-controlled cities (X), and cities that you control (O)\&. .PP The world is displayed on the player\*(Aqs screen during movement\&. (On terminals with small screens, only a portion of the world is shown at any one time\&.) Each piece is represented by a unique character on the map\&. With a few exceptions, you can only have one piece on a given location\&. On the map, you are shown only the 8 squares adjacent to your units\&. This information is updated before and during each of your moves\&. The map displays the most recent information known\&. .PP The game starts by assigning you one city and the computer one city\&. Cities can produce new pieces\&. Every city that you own produces more pieces for you according to the cost of the desired piece\&. The typical play of the game is to issue the Automove command until you decide to do something special\&. During movement in each round, the player is prompted to move each piece that does not otherwise have an assigned function\&. .PP Map coordinates are 4\-digit numbers\&. The first two digits are the row, the second two digits are the column\&. .SH "PIECES" .PP The pieces are as follows: .TS allbox center tab(:); l c c r r r c l c c r r r c l c c r r r c l c c r r r c l c c r r r c l c c r r r c l c c r r r c l c c r r r c l c c r r r c l c c r r r c. T{ Piece T}:T{ You T}:T{ Enemy T}:T{ Moves T}:T{ Hits T}:T{ Str T}:T{ Cost T} T{ Army T}:T{ A T}:T{ a T}:T{ 1 T}:T{ 1 T}:T{ 1 T}:T{ 5(6) T} T{ Fighter T}:T{ F T}:T{ f T}:T{ 8 T}:T{ 1 T}:T{ 1 T}:T{ 10(12) T} T{ Patrol Boat T}:T{ P T}:T{ p T}:T{ 4 T}:T{ 1 T}:T{ 1 T}:T{ 15(18) T} T{ Destroyer T}:T{ D T}:T{ d T}:T{ 2 T}:T{ 3 T}:T{ 1 T}:T{ 20(24) T} T{ Submarine T}:T{ S T}:T{ s T}:T{ 2 T}:T{ 2 T}:T{ 3 T}:T{ 20(24) T} T{ Troop Transport T}:T{ T T}:T{ t T}:T{ 2 T}:T{ 1 T}:T{ 1 T}:T{ 30(36) T} T{ Aircraft Carrier T}:T{ C T}:T{ c T}:T{ 2 T}:T{ 8 T}:T{ 1 T}:T{ 30(36) T} T{ Battleship T}:T{ B T}:T{ b T}:T{ 2 T}:T{ 10 T}:T{ 2 T}:T{ 40(48) T} T{ Satellite T}:T{ Z T}:T{ z T}:T{ 10 T}:T{ \-\- T}:T{ \-\- T}:T{ 50(60) T} .TE .sp 1 .PP The second column shows the map representation for your units\&. .PP The third shows the representations of enemy units\&. .PP Moves is the number of squares that the unit can move in a single round\&. .PP Hits is the amount of damage a unit can take before it is destroyed\&. .PP Strength is the amount of damage a unit can inflict upon an enemy during each round of an attack\&. .PP Cost is the number of rounds needed for a city to produce the piece\&. .PP The number in parenthesis is the cost for a city to produce the first unit\&. .PP Each piece has certain advantages associated with it that can make it useful\&. One of the primary strategic aspects of this game is deciding which pieces will be produced and in what quantities\&. .PP \fBArmies\fR can only move on land, and are the only piece that can move on land\&. Only armies can capture cities\&. This means that you must produce armies in order to win the game\&. Armies have a 50% chance of capturing a city when they attack\&. (Attacking one\*(Aqs own city results in the army\*(Aqs destruction\&. Armies that move onto the sea will drown\&. Armies can attack objects at sea, but even if they win, they will drown\&.) Armies can be carried by troop transports\&. If an army is moved onto a troop transport, then whenever the transport is moved, the army will be moved with the transport\&. You cannot attack any piece at sea while on a transport\&. .PP \fBFighters\fR move over both land and sea, and they move 8 squares per round\&. Their high speed and great mobility make fighters ideal for exploring\&. However, fighters must periodically land at user\-owned cities for refueling\&. A fighter can travel 32 squares without refueling\&. Fighters are also shot down if they attempt to fly over a city which is not owned by the user\&. .PP \fBPatrol boats\fR are fast but lightly armored\&. Therefore they are useful for patrolling ocean waters and exploring\&. In an attack against a stronger boat, however, patrol boats will suffer heavy casualties\&. .PP \fBDestroyers\fR are fairly heavily armored and reasonably quick to produce\&. Thus they are useful for destroying enemy transports which may be trying to spread the enemy across the face of the world\&. .PP When a \fBsubmarine\fR scores a hit, 3 hits are exacted instead of 1\&. Thus submarines can inflict heavy damage in a fight against heavily armored boats\&. Notice that healthy submarines will typically defeat healthy destroyers two\-thirds of the time\&. However, a submarine will defeat a fighter about two\-thirds of the time, while a destroyer will defeat a fighter three\-fourths of the time\&. .PP \fBTroop transports\fR are the only pieces that can carry armies\&. A maximum of six armies can be carried by a transport\&. On any world containing a reasonable amount of water, transports will be a critical resource in winning the game\&. Notice that the weakness of transports implies they need protection from stronger ships\&. .PP \fBAircraft carriers\fR are the only ships that can carry fighters\&. Carriers carry a maximum of the number of hits left of fighters\&. Fighters are refueled when they land on a carrier\&. .PP \fBBattleships\fR are similar to destroyers except that they are much stronger\&. .PP \fBSatellites\fR are only useful for reconnaissance\&. They can not be attacked\&. They are launched in a random diagonal orbit, and stay up for 50 turns\&. They can see one square farther than other objects\&. .PP All ships can move only on the sea\&. Ships can also dock in a user\-owned city\&. Docked ships have damage repaired at the rate of 1 hit per turn\&. Ships which have suffered a lot of damage will move more slowly\&. .PP Because of their ability to be repaired, ships with lots of hits such as Carriers and Battleships have an additional advantage\&. After suffering minor damage while destroying enemy shipping, these ships can sail back to port and be quickly repaired before the enemy has time to replenish her destroyed shipping\&. .PP The following table gives the probability that the piece listed on the side will defeat the piece listed at the top in a battle\&. (The table assumes that both pieces are undamaged\&.) .TS allbox center tab(:); c l c c c c l c c c c c l c c c c c l c c c c c l c c c c c l c c c c c. T{ \ \& T}:T{ AFPT T}:T{ D T}:T{ S T}:T{ C T}:T{ B T} T{ AFPT T}:T{ 50\&.0% T}:T{ 12\&.5% T}:T{ 25\&.0% T}:T{ 00\&.391% T}:T{ 00\&.0977% T} T{ D T}:T{ 87\&.5% T}:T{ 50\&.0% T}:T{ 25\&.0% T}:T{ 05\&.47% T}:T{ 00\&.537% T} T{ S T}:T{ 75\&.0% T}:T{ 75\&.0% T}:T{ 50\&.0% T}:T{ 31\&.3% T}:T{ 06\&.25% T} T{ C T}:T{ 99\&.6% T}:T{ 94\&.5% T}:T{ 68\&.7% T}:T{ 50\&.0% T}:T{ 04\&.61% T} T{ B T}:T{ 99\&.9% T}:T{ 99\&.5% T}:T{ 93\&.8% T}:T{ 95\&.4% T}:T{ 50\&.0% T} .TE .sp 1 .PP Notice, however, that when a ship has been damaged, the odds of being defeated can go up quite a bit\&. For example, a healthy submarine has a 25% chance of defeating a battleship that has had one hit of damage done to it, and a healthy submarine has a 50% chance of defeating a carrier which has suffered two hits of damage\&. .SH "MOVEMENT FUNCTIONS" .PP There are a variety of movement functions\&. The movement functions of pieces can be specified in user mode and edit mode\&. Cities can have movement functions set for each type of piece\&. When a movement function for a type of pieces is set for a city, then every time that type of piece appears in the city, the piece will acquire that movement function\&. Be forewarned that moving loaded transports or loaded carriers into a city can have undesirable side effects\&. .PP Normally, when a movement function has been specified, the piece will continue moving according to that function until one of the following happen: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} An enemy piece or unowned city appears next to the piece\&. In this case the piece will be completely awoken, unless its movement function has been set to a specific destination\&. Armies on ships and pieces inside cities will not be awoken if the enemy piece is gone by the time it is their turn to move\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} You explicitly awaken the piece\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} The piece can no longer move in accordance with its programmed function\&. In this case, the piece will awaken \fItemporarily\fR\&. You will be asked to move the piece at which time you may awaken it\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} The piece is a fighter which has just enough fuel (plus a small reserve) to get to the nearest city\&. In this case, the piece will awaken completely, unless its movement function has been set to a specific destination, or its movement function has been set to \fIland\fR\&. .RE .PP The rationale behind this complexity is that fighters must be awoken completely before they are out of range of a city to prevent one from accidentally forgetting to waken the fighter and then watching it fly off to its doom\&. However, it is presumed that when a path is set for the fighter, the fighter is not in danger of running out of fuel\&. .PP Pieces do not completely awaken when their function has been set to a destination because it is slightly time consuming to reset the destination, but very simple (one keystroke) to wake the piece\&. .PP The movement functions are: .PP \fBAttack\fR .RS 4 This function applies only to armies\&. When this function is set, the army will move toward the nearest enemy city, unowned city, or enemy army\&. This is useful when fighting off an invading enemy or taking over a new continent\&. When an army is set to this mode, it will also explore nearby territory\&. This tends to make the "grope" movement mode pretty useless\&. .RE .PP \fBAwake\fR .RS 4 When pieces are awake, you will be asked for the direction in which the piece should move on each turn\&. .RE .PP \fBFill\fR .RS 4 This function applies to carriers and transports\&. When this function is specified, these ships sleep until they have been filled with fighters or armies respectively\&. .RE .PP \fBGrope\fR .RS 4 This function causes a piece to explore\&. The piece heads toward the nearest unseen square of the map on each of its moves\&. Some attempt is made to explore in an optimal fashion\&. .RE .PP \fBLand\fR .RS 4 This function applies to fighters and causes the fighter to head toward the nearest transport or carrier\&. .RE .PP \fBRandom\fR .RS 4 This movement function causes a piece to move at random to an adjacent empty square\&. .RE .PP \fBSentry\fR .RS 4 This movement function puts a piece to sleep\&. The function of a city cannot be set to \*(Aqsleep\*(Aq\&. .RE .PP \fBTransport\fR .RS 4 This movement function only works on armies\&. The army sleeps until an unfull transport passes by, at which point the army wakes up and boards the transport\&. .RE .PP \fBUpgrade\fR .RS 4 This movement function only works with ships\&. The ship will move to the nearest owned city and remain there until it is repaired\&. .RE .PP \fB\fR .RS 4 Pieces can be set to move in a specified direction\&. .RE .PP \fB\fR .RS 4 Pieces can be set to move toward a specified square\&. In this movement mode, pieces take a shortest path toward the destination\&. Pieces moving in accordance with this function prefer diagonal moves that explore territory\&. Because of this, the movement of the piece may be non\-intuitive\&. .RE .PP As examples of how to use these movement functions, typically when I have a new city on a continent, I set the Army function of the city to \fIattack\fR\&. Whenever an army is produced, it merrily goes off on its way exploring the continent and moving towards unowned cities or enemy armies or cities\&. .PP I frequently set the ship functions for cities that are far from the front to automatically move ships towards the front\&. .PP When I have armies on a continent, but there is nothing to explore or attack, I move the army to the shore and use the \fItransport\fR function to have that army hop aboard the first passing transport\&. .SH "COMMANDS" .PP There are three command modes\&. The first of these is "command mode"\&. In this mode, you give commands that affect the game as a whole\&. In the second mode, "move mode", you give commands to move your pieces\&. The third mode is "edit mode", and in this mode you can edit the functions of your pieces and examine various portions of the map\&. .PP All commands are one character long\&. The full mnemonic names are listed below as a memorization aid\&. The mnemonics are somewhat contrived because there are so few characters in the English language\&. Too bad this program isn\*(Aqt written in Japanese, neh? .PP In all command modes, typing "H" will print out a screen of help information, and typing will redraw the screen\&. .SH "COMMAND MODE" .PP In command mode, the computer will prompt you for your orders\&. The following commands can be given at this time: .PP \fBAutomove\fR .RS 4 Enter automove mode\&. This command begins a new round of movement\&. You will remain in move mode after each of the computer\*(Aqs turns\&. (In move mode, the "O" command will return you to command mode after the computer finishes its next turn\&. .RE .PP \fBCity\fR .RS 4 Give the computer a random unowned city\&. This command is useful if you find that the computer is getting too easy to beat\&. .RE .PP \fBDate\fR .RS 4 The current round is displayed\&. .RE .PP \fBExamine\fR .RS 4 Examine the enemy\*(Aqs map\&. This command is only valid after the computer has resigned\&. .RE .PP \fBFile\fR .RS 4 Print a copy of the map to the specified file\&. .RE .PP \fBGive\fR .RS 4 This command gives the computer a free move\&. .RE .PP \fBJ\fR .RS 4 Enter edit mode where you can examine and change the functions associated with your pieces and cities\&. .RE .PP \fBMove\fR .RS 4 Enter move mode for a single round\&. .RE .PP \fBN\fR .RS 4 Give the computer the number of free moves you specify\&. .RE .PP \fBPrint\fR .RS 4 Display a sector on the screen\&. .RE .PP \fBQuit\fR .RS 4 Quit the game\&. .RE .PP \fBRestore\fR .RS 4 Restore the game from empsave\&.dat\&. .RE .PP \fBSave\fR .RS 4 Save the game in empsave\&.dat\&. .RE .PP \fBTrace\fR .RS 4 This command toggles a flag\&. When the flag is set, after each move, either yours or the computer\*(Aqs, a picture of the world is written out to the file \*(Aqempmovie\&.dat\*(Aq\&. \fBWatch out! This command produces lots of output\&.\fR .RE .PP \fBWatch\fR .RS 4 This command allows you to watch a saved movie\&. The movie is displayed in a condensed version so that it will fit on a single screen, so the output may be a little confusing\&. This command is only legal if the computer resigns\&. If you lose the game, you cannot replay a movie to learn the secrets of how the computer beat you\&. Nor can you replay a movie to find out the current positions of the computer\*(Aqs pieces\&. When replaying a movie, it is recommended that you use the \fB\-d\fR option to set the delay to around 2000 milliseconds or so\&. Otherwise the screen will be updated too quickly for you to really grasp what is going on\&. .RE .PP \fBZoom\fR .RS 4 Display a condensed version of the map on the screen\&. The user map is divided into small rectangles\&. Each rectangle is displayed as one square on the screen\&. If there is a city in a rectangle, then it is displayed\&. Otherwise enemy pieces are displayed, then user pieces, then land, then water, and then unexplored territory\&. When pieces are displayed, ships are preferred to fighters and armies\&. .RE .SH "MOVE MODE" .PP In move mode, the cursor will appear on the screen at the position of each piece that needs to be moved\&. You can then give commands to move the piece\&. Directions to move are specified by the following keys: .sp .if n \{\ .RS 4 .\} .nf \fI QWE A D ZXC \fR .fi .if n \{\ .RE .\} .PP The arrow and keypad keys on your terminal, if any, should also work\&. .PP These keys move in the direction of the key from S\&. The characters are not echoed and only 1 character is accepted, so there is no need for a \&. Hit the bar if you want the piece to stay put\&. .PP Other commands are: .PP \fBBuild\fR .RS 4 Change the production of a city\&. .RE .PP \fBFill\fR .RS 4 Set the function of a troop transport or aircraft carrier to \fIfill\fR\&. .RE .PP \fBGrope\fR .RS 4 Set the function of a piece to \fIgrope\fR\&. .RE .PP \fBI\fR\fIdir\fR .RS 4 Set the direction for a piece to move\&. .RE .PP \fBJ\fR .RS 4 Enter edit mode\&. .RE .PP \fBKill\fR .RS 4 Wake up the piece\&. If the piece is a transport or carrier, pieces on board will not be awoken\&. .RE .PP \fBLand\fR .RS 4 Set a fighter\*(Aqs function to \fIland\fR\&. .RE .PP \fBOut\fR .RS 4 Cancel automove mode\&. At the end of the round, you will be placed in command mode\&. .RE .PP \fBPrint\fR .RS 4 Redraw the screen\&. .RE .PP \fBRandom\fR .RS 4 Set a piece\*(Aqs function to \fIrandom\fR\&. .RE .PP \fBSentry\fR .RS 4 Set a piece\*(Aqs function to \fIsentry\fR\&. .RE .PP \fBTransport\fR .RS 4 Set an army\*(Aqs function to \fItransport\fR\&. .RE .PP \fBUpgrade\fR .RS 4 Set a ship\*(Aqs function to \fIupgrade\fR\&. .RE .PP \fBV\fR\fIpiece func\fR .RS 4 Set the city movement function for the specified piece to the specified function\&. For example, typing "VAY" would set the city movement function for armies to \fIattack\fR\&. Whenever an army is produced in the city (or whenever a loaded transport enters the city), the army\*(Aqs movement function would be set to \fIattack\fR\&. .RE .PP \fBY\fR .RS 4 Set an army\*(Aqs function to \fIattack\fR\&. .RE .PP \fB?\fR .RS 4 Display information about the piece\&. The function, hits left, range, and number of items on board are displayed\&. .RE .PP Attacking something is accomplished by moving onto the square of the unit you wish to attack\&. Hits are traded off at 50% probability of a hit landing on one or the other units until one unit is totally destroyed\&. There is only 1 possible winner\&. .PP You are "allowed" to do \fBfatal\fR things like attack your own cities or other pieces\&. If you try to make a fatal move, the computer will warn you and give you a chance to change your mind\&. .PP You cannot move onto the edge of the world\&. .SH "EDIT MODE" .PP In edit mode, you can move around the world and examine pieces or assign them new functions\&. To move the cursor around, use the standard direction keys\&. Other commands are: .PP \fBBuild\fR .RS 4 Change the production of the city under the cursor\&. The program will prompt for the new production, and you should respond with the key corresponding to the letter of the piece that you want produced\&. .RE .PP \fBFill\fR .RS 4 Set a transport\*(Aqs or carrier\*(Aqs function to \fIfill\fR\&. .RE .PP \fBGrope\fR .RS 4 Set a piece\*(Aqs function to \fIgrope\fR\&. .RE .PP \fBI\fR\fIdir\fR .RS 4 Set the function of a piece (or city) to the specified direction\&. .RE .PP \fBJ\fR\fIdir\fR .RS 4 Toggle displaying the production of all cities\&. .RE .PP \fBKill\fR .RS 4 Wake all pieces at the current location\&. If the location is a city, the fighter path will also be canceled\&. .RE .PP \fBMark\fR .RS 4 Select the piece or city at the current location\&. This command is used with the "N" command\&. .RE .PP \fBN\fR .RS 4 Set the destination of the piece previously selected with the "M" command to the current square\&. .RE .PP \fBOut\fR .RS 4 Exit edit mode\&. .RE .PP \fBPrint\fR\fIsector\fR .RS 4 Display a new sector of the map\&. The map is divided into ten sectors of size 20 by 70\&. Sector zero is in the upper\-left corner of the map\&. Sector four is in the lower\-left corner of the map\&. Sector five is in the upper\-right corner, and sector nine is in the lower\-right corner\&. .RE .PP \fBRandom\fR .RS 4 Set a piece to move randomly\&. .RE .PP \fBSentry\fR .RS 4 Put a piece to sleep\&. .RE .PP \fBTransport\fR .RS 4 Set an army\*(Aqs function to \fItransport\fR\&. .RE .PP \fBUpgrade\fR .RS 4 Set a ship\*(Aqs function to \fIupgrade\fR\&. .RE .PP \fBV\fR\fIpiece func\fR .RS 4 Set the city movement function for a piece\&. .RE .PP \fBY\fR .RS 4 Set an army\*(Aqs function to \fIattack\fR\&. .RE .PP \fB?\fR .RS 4 Display information about a piece or city\&. For a city, the production, time of completion of the next piece, movement functions, and the number of fighters and ships in the city are displayed\&. .RE .PP Note that you cannot directly affect anything inside a city with the editor\&. .SH "HINTS" .PP After you have played this game for a while, you will probably find that the computer is immensely easy to beat\&. Here are some ideas you can try that may make the game more interesting\&. .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Give the computer one or more extra cities before starting the game\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Try playing the game with a low smoothness value (try using the \-s2 or even \-s0 option)\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} When starting the game, the program will ask you what difficulty level you want\&. Here "difficulty level" is a misnomer\&. To compute a difficulty level, the program looks at each continent and counts the number of cities on the continents\&. A high "difficulty level" gives the computer a large continent with many cities, while the user gets a small continent with few cities\&. A low "difficulty level" has the opposite effect\&. It may be the case that the computer will play better when the "difficulty level" is low\&. The reason for this is that the computer is forced to move armies to multiple continents early in the game\&. .RE .SH "HISTORY" .PP According to \m[blue]\fBA Brief History of Empire\fR\m[]\&\s-2\u[1]\d\s+2, the ancestral game was written by Walter Bright sometime in the early 1970s while he was a student at Caltech\&. A copy leaked out of Caltech and was ported to DEC\*(Aqs VAX/VMS from the TOPS\-10/20 FORTRAN sources available sometime around fall 1979\&. Craig Leres found the source code on a DECUS tape in 1983 and added support for different terminal types\&. .PP Ed James got hold of the sources at Berkeley and converted portions of the code to C, mostly to use curses for the screen handling\&. He published his modified sources on the net in December 1986\&. Because this game ran on VMS machines for so long, it has been known as VMS Empire\&. .PP In 1987 Chuck Simmons at Amdahl reverse\-engineered the program and wrote a version completely in C\&. In doing so, he modified the computer strategy, the commands, the piece types, many of the piece attributes, and the algorithm for creating maps\&. .PP The various versions of this game were ancestral to later and better\-known 4X (expand/explore/exploit/exterminate) games, including Civilization (1990) and Master of Orion (1993)\&. .PP In 1994 Eric Raymond colorized the game\&. .SH "FILES" .PP \fIempsave\&.dat\fR .RS 4 holds a backup of the game\&. Whenever empire is run, it will reload any game in this file\&. .RE .PP \fIempmovie\&.dat\fR .RS 4 holds a history of the game so that the game can be replayed as a "movie"\&. .RE .SH "BUGS" .PP No doubt numerous\&. .PP The savefile format changed incompatibly after version 1\&.13\&. .PP Satellites are not completely implemented\&. You should be able to move to a square that contains a satellite, but the program won\*(Aqt let you\&. Enemy satellites should not cause your pieces to awaken\&. .SH "AUTHORS" .PP Original game by Walter Bright\&. Support for different terminal types added by Craig Leres\&. Curses support added by Ed James\&. C/Unix version written by Chuck Simmons\&. Colorization by Eric S\&. Raymond\&. Probability table corrected by Michael Self\&. .SH "COPYLEFT" .PP Copyright (C) 1987, 1988 Chuck Simmons .PP See the file COPYING, distributed with empire, for restriction and warranty information\&. .SH "NOTES" .IP " 1." 4 A Brief History of Empire .RS 4 \%http://www.classicempire.com/history.html .RE vms-empire-1.18/empire.c0000664000175000017500000001534314674350334013341 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* empire.c -- this file contains initialization code, the main command parser, and the simple commands. */ #include "empire.h" #include "extern.h" #include gamestate_t game; void c_examine(void), c_movie(void); /* * 03a 01Apr88 aml .Hacked movement algorithms for computer. * 02b 01Jun87 aml .First round of bug fixes. * 02a 01Jan87 aml .Translated to C. * 01b 27May85 cal .Fixed round number update bug. Made truename simple. * 01a 01Sep83 cal .Taken from a Decus tape */ void empire(void) { void do_command(char); void print_zoom(view_map_t *); char order; int turn = 0; ttinit(); /* init tty */ 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 on the manual page\n"); (void)redisplay(); if (!restore_game()) /* try to restore previous game */ { init_game(); /* otherwise init a new game */ } /* Command loop starts here. */ for (;;) { /* until user quits */ if (game.automove) { /* don't ask for cmd in auto mode */ user_move(); comp_move(1); if (++turn % game.save_interval == 0) { save_game(); } } else { prompt(""); /* blank top line */ redisplay(); prompt("Your orders? "); order = get_chx(); /* get a command */ do_command(order); } } } /* Execute a command. */ void do_command(char orders) { void c_debug(char order), c_quit(void), c_sector(void), c_map(void); void c_give(void); int ncycle; switch (orders) { case 'A': /* turn on auto move mode */ game.automove = true; error("Now in Auto-Mode"); user_move(); comp_move(1); save_game(); break; case 'C': /* give a city to the computer */ c_give(); break; case 'D': /* display round number */ error("Round #%d", game.date); break; case 'E': /* examine enemy map */ if (game.resigned) c_examine(); else huh(); /* illegal command */ break; case 'F': /* print map to file */ c_map(); break; case 'G': /* give one free enemy move */ comp_move(1); break; case 'H': /* help */ help(help_cmd, cmd_lines); break; case 'J': /* edit mode */ ncycle = cur_sector(); if (ncycle == -1) ncycle = 0; edit(sector_loc(ncycle)); break; case 'M': /* move */ user_move(); comp_move(1); save_game(); break; case 'N': /* give enemy free moves */ ncycle = getint("Number of free enemy moves: "); comp_move(ncycle); save_game(); break; case 'P': /* print a sector */ c_sector(); break; case '\026': /* some interrupt */ case 'Q': /* quit */ c_quit(); break; case 'R': /* restore game */ clear_screen(); restore_game(); break; case 'S': /* save game */ save_game(); break; case 'T': /* trace: toggle game.save_movie flag */ game.save_movie = !game.save_movie; if (game.save_movie) { comment("Saving movie screens to 'empmovie.dat'."); } else { comment("No longer saving movie screens."); } break; case 'W': /* watch movie */ if (game.resigned || game.debug) replay_movie(); else error("You cannot watch movie until computer resigns."); break; case 'Z': /* print compressed map */ print_zoom(game.user_map); break; case '\014': /* redraw the screen */ redraw(); break; case '+': { /* change debug state */ char e = get_chx(); if (e == '+') { game.debug = true; } else if (e == '-') { game.debug = false; } else { huh(); } } break; default: if (game.debug) { c_debug(orders); /* debug */ } else { huh(); /* illegal command */ } break; } } /* Give an unowned city (if any) to the computer. We make a list of unowned cities, choose one at random, and mark it as the computers. */ void c_give(void) { int unowned[NUM_CITY]; count_t i, count; count = 0; /* nothing in list yet */ for (i = 0; i < NUM_CITY; i++) { if (game.city[i].owner == UNOWNED) { unowned[count] = i; /* remember this city */ count += 1; } } if (count == 0) { error("There are no unowned cities."); ksend("There are no unowned cities."); return; } i = irand(count); i = unowned[i]; /* get city index */ game.city[i].owner = COMP; game.city[i].prod = NOPIECE; game.city[i].work = 0; scan(game.comp_map, game.city[i].loc); } /* Debugging commands should be implemented here. The order cannot be any legal command. */ void c_debug(char order) { char e; switch (order) { case '#': c_examine(); break; case '%': c_movie(); break; case '@': /* change trace state */ e = get_chx(); if (e == '+') { game.trace_pmap = true; } else if (e == '-') { game.trace_pmap = false; } else { huh(); } break; case '$': /* change game.print_debug state */ e = get_chx(); if (e == '+') { game.print_debug = true; } else if (e == '-') { game.print_debug = false; } else { huh(); } break; case '&': /* change game.print_vmap state */ game.print_vmap = get_chx(); break; default: huh(); break; } } /* The quit command. Make sure the user really wants to quit. */ void c_quit(void) { if (getyn("QUIT - Are you sure? ")) { empend(); } } /* Print a sector. Read the sector number from the user and print it. */ void c_sector(void) { int num; num = get_range("Sector number? ", 0, NUM_SECTORS - 1); print_sector_u(num); } /* Print the map to a file. We ask for a filename, attempt to open the file, and if successful, print out the user's information to the file. We print the map sideways to make it easier for the user to print out the map. */ void c_map(void) { FILE *f; int i, j; char line[MAP_HEIGHT + 2]; prompt("Filename? "); get_str(game.jnkbuf, STRSIZE); f = fopen(game.jnkbuf, "w"); if (f == NULL) { error("I can't open that file."); return; } for (i = 0; i < MAP_WIDTH; i++) { /* for each column */ for (j = MAP_HEIGHT - 1; j >= 0; j--) { /* for each row */ line[MAP_HEIGHT - 1 - j] = game.user_map[row_col_loc(j, i)].contents; } j = MAP_HEIGHT - 1; while (j >= 0 && line[j] == ' ') /* scan off trailing blanks */ { j -= 1; } line[++j] = '\n'; line[++j] = 0; /* trailing null */ (void)fputs(line, f); } (void)fclose(f); } /* Allow user to examine the computer's map. */ void c_examine(void) { int num; num = get_range("Sector number? ", 0, NUM_SECTORS - 1); print_sector_c(num); } /* We give the computer lots of free moves and Print a "zoomed" version of the computer's map. */ void c_movie(void) { for (;;) { comp_move(1); print_zoom(game.comp_map); save_game(); #ifdef PROFILE if (game.date == 125) empend(); #endif } } /* end */ vms-empire-1.18/empire.h0000664000175000017500000002711714561242527013347 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* empire.h -- type and constant declarations */ #include #ifndef NULL #define NULL 0 #endif typedef unsigned char uchar; typedef long loc_t; /* represent a board location in 4-digit form */ typedef long count_t; /* for iterating over or counting board locations */ #define ASSERT(x) \ if (!(x)) \ eassert("x", __FILE__, __LINE__); #define ABORT eassert("aborting", __FILE__, __LINE__) /* directions one can move */ #define NORTH 0 #define NORTHEAST 1 #define EAST 2 #define SOUTHEAST 3 #define SOUTH 4 #define SOUTHWEST 5 #define WEST 6 #define NORTHWEST 7 #define NUMTOPS 3 /* number of lines at top of screen for messages */ #define NUMSIDES 6 /* number of lines at side of screen */ /* * This used to be 80, and that was appropriate back when this game was * played on hardware terminals. Nowadays it's almost certain to be running * on a software terminal emulator that would have been considered * dizzyingly huge back in the day. Memory is cheap, we'll leave some * headroom. */ #define STRSIZE 400 /* number of characters in a string */ /* Information we maintain about cities. */ #define UNOWNED 0 #define USER 1 #define COMP 2 /* Piece types. */ #define ARMY 0 #define FIGHTER 1 #define PATROL 2 #define DESTROYER 3 #define SUBMARINE 4 #define TRANSPORT 5 #define CARRIER 6 #define BATTLESHIP 7 #define SATELLITE 8 #define NUM_OBJECTS 9 /* number of defined objects */ #define NOPIECE ((char)255) /* a 'null' piece */ #define LIST_SIZE 5000 /* max number of pieces on board */ typedef struct city_info { loc_t loc; /* location of city */ uchar owner; /* UNOWNED, USER, COMP */ long func[NUM_OBJECTS]; /* function for each object */ long work; /* units of work performed */ char prod; /* item being produced */ } city_info_t; /* Types of programmed movement. Use negative numbers for special functions, use positive numbers to move toward a specific location. */ #define NOFUNC -1 /* no programmed function */ #define RANDOM -2 /* move randomly */ #define SENTRY -3 /* sleep */ #define FILL -4 /* fill transport */ #define LAND -5 /* land fighter at city */ #define EXPLORE -6 /* piece explores nearby */ #define ARMYLOAD -7 /* army moves toward and boards a transport */ #define ARMYATTACK -8 /* army looks for city to attack */ #define TTLOAD -9 /* transport moves toward loading armies */ #define REPAIR -10 /* ship moves toward port */ #define WFTRANSPORT -11 /* army boards a transport */ #define MOVE_N -12 /* move north */ #define MOVE_NE -13 /* move northeast */ #define MOVE_E -14 /* move east */ #define MOVE_SE -15 /* move southeast */ #define MOVE_S -16 /* move south */ #define MOVE_SW -17 /* move southwest */ #define MOVE_W -18 /* move west */ #define MOVE_NW -19 /* move northwest */ /* Index to list of function names. */ #define FUNCI(x) (-(x)-1) /* Macro to convert a movement function into a direction. */ #define MOVE_DIR(a) (-(a) + MOVE_N) /* Information we maintain about each piece. */ typedef struct { /* ptrs for doubly linked list */ struct piece_info *next; /* pointer to next in list */ struct piece_info *prev; /* pointer to prev in list */ } link_t; typedef struct piece_info { link_t piece_link; /* linked list of pieces of this type */ link_t loc_link; /* linked list of pieces at a location */ link_t cargo_link; /* linked list of cargo pieces */ int owner; /* owner of piece */ int type; /* type of piece */ loc_t loc; /* location of piece */ long func; /* programmed type of movement */ short hits; /* hits left */ int moved; /* moves made */ struct piece_info *ship; /* pointer to containing ship */ struct piece_info *cargo; /* pointer to cargo list */ short count; /* count of items on board */ short range; /* current range (if applicable) */ } piece_info_t; /* Macros to link and unlink an object from a doubly linked list. */ #define LINK(head, obj, list) \ { \ obj->list.prev = NULL; \ obj->list.next = head; \ if (head) \ head->list.prev = obj; \ head = obj; \ } #define UNLINK(head, obj, list) \ { \ if (obj->list.next) \ obj->list.next->list.prev = obj->list.prev; \ if (obj->list.prev) \ obj->list.prev->list.next = obj->list.next; \ else \ head = obj->list.next; \ obj->list.next = \ NULL; /* encourage mem faults in buggy code */ \ obj->list.prev = NULL; \ } /* macros to set map and list of an object */ #define MAP(owner) ((owner) == USER ? game.user_map : game.comp_map) #define LIST(owner) ((owner) == USER ? game.user_obj : game.comp_obj) /* macro to step through adjacent cells */ #define FOR_ADJ(loc, new_loc, i) \ for (i = 0; (i < 8 ? new_loc = loc + dir_offset[i], 1 : 0); i++) #define FOR_ADJ_ON(loc, new_loc, i) \ FOR_ADJ(loc, new_loc, i) if (game.real_map[new_loc].on_board) /* We maintain attributes for each piece. Attributes are currently constant, but the way has been paved to allow user's to change attributes at the beginning of a game. */ #define INFINITY 10000000 /* a large number */ typedef struct piece_attr { char sname; /* eg 'C' */ char name[20]; /* eg "aircraft carrier" */ char nickname[20]; /* eg "carrier" */ char article[20]; /* eg "an aircraft carrier" */ char plural[20]; /* eg "aircraft carriers" */ char terrain[4]; /* terrain piece can pass over eg "." */ uchar build_time; /* time needed to build unit */ uchar strength; /* attack strength */ uchar max_hits; /* number of hits when completely repaired */ uchar speed; /* number of squares moved per turn */ uchar capacity; /* max objects that can be held */ long range; /* range of piece */ } piece_attr_t; /* There are 3 maps. 'map' describes the world as it actually exists; it tells whether each map cell is land, water or a city; it tells whether or not a square is on the board. 'user_map' describes the user's view of the world. 'comp_map' describes the computer's view of the world. As of the 1.7 version, MAP_WIDTH and MAP_HEIGHT are now free paramaters. You can change them and the code will adjust properly. */ #define MAP_WIDTH 100 #define MAP_HEIGHT 60 #define MAP_SIZE (MAP_WIDTH * MAP_HEIGHT) /* #define NUM_CITY 70 */ /* #define NUM_CITY (MAP_SIZE / 85) */ #define NUM_CITY ((100 * (MAP_WIDTH + MAP_HEIGHT)) / 228) typedef struct real_map { /* a cell of the actual map */ char contents; /* MAP_LAND, MAP_SEA, or MAP_CITY */ bool on_board; /* TRUE iff on the board */ city_info_t *cityp; /* ptr to city at this location */ piece_info_t *objp; /* list of objects at this location */ } real_map_t; 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 */ } view_map_t; /* Define information we maintain for a pathmap. */ typedef struct { int cost; /* total cost to get here */ int inc_cost; /* incremental cost to get here */ char terrain; /* T_LAND, T_WATER, T_UNKNOWN, T_PATH */ } path_map_t; #define T_UNKNOWN 0 #define T_PATH 1 #define T_LAND 2 #define T_WATER 4 #define T_AIR (T_LAND | T_WATER) /* A record for counts we obtain when scanning a continent. */ typedef struct { int user_cities; /* number of user cities on continent */ int user_objects[NUM_OBJECTS]; int comp_cities; int comp_objects[NUM_OBJECTS]; int size; /* size of continent in cells */ int unowned_cities; /* number of unowned cities */ int unexplored; /* unexplored territory */ } scan_counts_t; /* Define useful constants for accessing sectors. */ #define SECTOR_ROWS 5 /* number of vertical sectors */ #define SECTOR_COLS 2 /* number of horizontal sectors */ #define NUM_SECTORS (SECTOR_ROWS * SECTOR_COLS) /* total sectors */ #define ROWS_PER_SECTOR ((MAP_HEIGHT + SECTOR_ROWS - 1) / SECTOR_ROWS) #define COLS_PER_SECTOR ((MAP_WIDTH + SECTOR_COLS - 1) / SECTOR_COLS) /* Information we need for finding a path for moving a piece. */ typedef struct { char city_owner; /* char that represents home city */ char *objectives; /* list of objectives */ int weights[11]; /* weight of each objective */ } move_info_t; /* special weights */ #define W_TT_BUILD -1 /* special cost for city building a tt */ /* List of cells in the perimeter of our searching for a path. */ typedef struct { long len; /* number of items in list */ long list[MAP_SIZE]; /* list of locations */ } perimeter_t; enum win_t { no_win, wipeout_win, ratio_win }; #define MAP_LAND '+' #define MAP_SEA '.' #define MAP_CITY '*' typedef struct { /* user-supplied parameters */ int SMOOTH; /* number of times to smooth map */ int WATER_RATIO; /* percentage of map that is water */ int MIN_CITY_DIST; /* cities must be at least this far apart */ int delay_time; int save_interval; /* turns between autosaves */ /* the world */ real_map_t real_map[MAP_SIZE]; /* the way the world really looks */ view_map_t comp_map[MAP_SIZE]; /* computer's view of the world */ view_map_t user_map[MAP_SIZE]; /* user's view of the world */ city_info_t city[NUM_CITY]; /* city information */ /* miscellaneous */ long date; /* number of game turns played */ bool automove; /* true iff user is in automove mode */ bool resigned; /* true iff computer resigned */ bool debug; /* true iff in debugging mode */ bool print_debug; /* true iff we print debugging stuff */ char print_vmap; /* the map-printing mode */ bool trace_pmap; /* true if we are tracing pmaps */ int win; /* set when game is over - not a bool */ char jnkbuf[STRSIZE]; /* general purpose temporary buffer */ bool save_movie; /* true iff we should save movie screens */ int user_score; /* "score" for user and computer */ int comp_score; char *savefile; bool showprod; /* There is one array to hold all allocated objects no matter who owns them. Objects are allocated from the array and placed on a list corresponding to the type of object and its owner. */ piece_info_t *free_list; /* index to free items in object list */ piece_info_t *user_obj[NUM_OBJECTS]; /* indices to user lists */ piece_info_t *comp_obj[NUM_OBJECTS]; /* indices to computer lists */ piece_info_t object[LIST_SIZE]; /* object list */ /* Display information. */ int lines; /* lines on screen */ int cols; /* columns on screen */ } gamestate_t; /* end */ vms-empire-1.18/extern.h0000664000175000017500000001540214674360264013371 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* extern.h -- define global non-constant storage. */ #include // Needed for chtype extern gamestate_t game; /* constant data */ extern piece_attr_t piece_attr[]; extern int dir_offset[]; extern char *func_name[]; extern int move_order[]; extern char type_chars[]; extern char tt_attack[]; extern char army_attack[]; extern char fighter_attack[]; extern char ship_attack[]; extern move_info_t tt_load; extern move_info_t tt_explore; extern move_info_t tt_unload; extern move_info_t army_fight; extern move_info_t army_load; extern move_info_t fighter_fight; extern move_info_t ship_fight; extern move_info_t ship_repair; extern move_info_t user_army; extern move_info_t user_army_attack; extern move_info_t user_fighter; extern move_info_t user_ship; extern move_info_t user_ship_repair; extern char *help_cmd[]; extern char *help_edit[]; extern char *help_user[]; extern int cmd_lines; extern int edit_lines; extern int user_lines; /* Screen updating macros */ #define display_loc_u(loc) display_loc(USER, game.user_map, loc) #define display_loc_c(loc) display_loc(COMP, comp_map, loc) #define print_sector_u(sector) print_sector(USER, game.user_map, sector) #define print_sector_c(sector) print_sector(COMP, game.comp_map, sector) #define loc_row(loc) ((loc) / MAP_WIDTH) #define loc_col(loc) ((loc) % MAP_WIDTH) #define row_col_loc(row, col) ((long)((row)*MAP_WIDTH + (col))) #define sector_row(sector) ((sector) % SECTOR_ROWS) #define sector_col(sector) ((sector) / SECTOR_ROWS) #define row_col_sector(row, col) ((int)((col)*SECTOR_ROWS + (row))) #define loc_sector(loc) \ row_col_sector(loc_row(loc) / ROWS_PER_SECTOR, \ loc_col(loc) / COLS_PER_SECTOR) #define sector_loc(sector) \ row_col_loc( \ sector_row(sector) * ROWS_PER_SECTOR + ROWS_PER_SECTOR / 2, \ sector_col(sector) * COLS_PER_SECTOR + COLS_PER_SECTOR / 2) /* global routines */ void empire(void); void attack(piece_info_t *att_obj, long loc); void comp_move(int nmoves); void user_move(void); void edit(long edit_cursor); /* map routines */ void vmap_cont(int *cont_map, view_map_t *vmap, long loc, char bad_terrain); void rmap_cont(int *cont_map, long loc, char bad_terrain); void vmap_mark_up_cont(int *cont_map, view_map_t *vmap, long loc, char bad_terrain); scan_counts_t vmap_cont_scan(int *cont_map, view_map_t *vmap); scan_counts_t rmap_cont_scan(int *cont_map); bool map_cont_edge(const int *cont_map, long loc); long vmap_find_aobj(path_map_t path_map[], view_map_t *vmap, long loc, move_info_t *move_info); long vmap_find_wobj(path_map_t path_map[], view_map_t *vmap, long loc, move_info_t *move_info); long vmap_find_lobj(path_map_t path_map[], view_map_t *vmap, long loc, move_info_t *move_info); long vmap_find_lwobj(path_map_t path_map[], view_map_t *vmap, long loc, move_info_t *move_info, int beat_cost); long vmap_find_wlobj(path_map_t path_map[], view_map_t *vmap, long loc, move_info_t *move_info); long vmap_find_dest(path_map_t path_map[], view_map_t vmap[], long cur_loc, long dest_loc, int owner, int terrain); void vmap_prune_explore_locs(view_map_t *vmap); void vmap_mark_path(path_map_t *path_map, view_map_t *vmap, long dest); void vmap_mark_adjacent(path_map_t path_map[], long loc); void vmap_mark_near_path(path_map_t path_map[], long loc); long vmap_find_dir(path_map_t path_map[], view_map_t *vmap, long loc, char *terrain, char *adjchar); int vmap_count_adjacent(view_map_t *vmap, long loc, char *adj_char); bool vmap_shore(view_map_t *vmap, long loc); bool rmap_shore(long loc); bool vmap_at_sea(view_map_t *vmap, long loc); bool rmap_at_sea(long loc); /* display routines */ void announce(char *); void redisplay(void); void kill_display(void); void sector_change(void); int cur_sector(void); long cur_cursor(void); void display_loc(int whose, view_map_t vmap[], long loc); void display_locx(int whose, view_map_t vmap[], long loc); void print_sector(int whose, view_map_t vmap[], int sector); bool move_cursor(long *cursor, int offset); void print_zoom(view_map_t *vmap); void print_pzoom(char *s, path_map_t *pmap, view_map_t *vmap); void print_xzoom(view_map_t *vmap); void display_score(void); #ifdef A_COLOR void init_colors(void); #endif /* A_COLOR */ void redraw(void); void clear_screen(void); void complain(void); void delay(void); void close_disp(void); void pos_str(int row, int col, char *str, ...); int direction(chtype c); void init_game(void); /* game routines */ void save_game(void); int restore_game(void); void save_movie_screen(void); void replay_movie(void); void get_str(char *buf, int sizep); /* input routines */ void get_strq(char *buf, int sizep); char get_chx(void); int getint(char *message); char get_c(void); char get_cq(void); bool getyn(char *message); int get_range(char *message, int low, int high); void rndini(void); /* math routines */ long irand(long high); int dist(long a, long b); int isqrt(int n); int find_nearest_city(long loc, int owner, long *city_loc); city_info_t *find_city(long loc); /* object routines */ piece_info_t *find_obj(int type, long loc); piece_info_t *find_nfull(int type, long loc); long find_transport(int owner, long loc); piece_info_t *find_obj_at_loc(long loc); int obj_moves(piece_info_t *obj); int obj_capacity(piece_info_t *obj); void kill_obj(piece_info_t *obj, long loc); void kill_city(city_info_t *cityp); void produce(city_info_t *cityp); void move_obj(piece_info_t *obj, long new_loc); void move_sat(piece_info_t *obj); bool good_loc(piece_info_t *obj, long loc); 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 scan_sat(view_map_t *vmap, long loc); void set_prod(city_info_t *cityp); /* terminal routines */ void pdebug(char *s, ...); void topini(void); void clreol(int line, int colp); void topmsg(int line, char *fmt, ...); void prompt(char *fmt, ...); void error(char *fmt, ...); void info(char *a, char *b, char *c); void comment(char *fmt, ...); void extra(char *fmt, ...); void huh(void); void help(char **text, int nlines); void set_need_delay(void); void ksend(char *fmt, ...); /* utility routines */ void ttinit(void); void eassert(char *expression, char *file, int line); void empend(void); char upper(char c); void tupper(char *str); void check(void); int loc_disp(int loc); vms-empire-1.18/game.c0000664000175000017500000005551314674360014012770 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* game.c -- Routines to initialize, save, and restore a game. */ #include "empire.h" #include "extern.h" #include #include #include #include count_t remove_land(loc_t loc, count_t num_land); bool select_cities(void); bool find_next(loc_t *mapi); bool good_cont(loc_t mapi); bool xread(FILE *f, char *buf, int size); bool xwrite(FILE *f, char *buf, int size); void stat_display(char *mbuf, int round); /* Initialize a new game. Here we generate a new random map, put cities on the map, select cities for each opponent, and zero out the lists of pieces on the board. */ void init_game(void) { void make_map(void), place_cities(void); count_t i; kill_display(); /* nothing on screen */ game.automove = false; game.resigned = false; game.debug = false; game.print_debug = false; game.print_vmap = false; game.trace_pmap = false; game.save_movie = false; game.win = no_win; game.date = 0; /* no date yet */ game.user_score = 0; game.comp_score = 0; for (i = 0; i < MAP_SIZE; i++) { game.user_map[i].contents = ' '; /* nothing seen yet */ game.user_map[i].seen = 0; game.comp_map[i].contents = ' '; game.comp_map[i].seen = 0; } for (i = 0; i < NUM_OBJECTS; i++) { game.user_obj[i] = NULL; game.comp_obj[i] = NULL; } game.free_list = NULL; /* nothing free yet */ for (i = 0; i < LIST_SIZE; i++) { /* for each object */ piece_info_t *obj = &(game.object[i]); obj->hits = 0; /* mark object as dead */ obj->owner = UNOWNED; LINK(game.free_list, obj, piece_link); } make_map(); /* make land and water */ do { for (i = 0; i < MAP_SIZE; i++) { /* remove cities */ if (game.real_map[i].contents == MAP_CITY) { game.real_map[i].contents = MAP_LAND; /* land */ } } place_cities(); /* place cities on game.real_map */ } while (!select_cities()); /* choose a city for each player */ } /* Create a map. To do this, we first randomly assign heights to each map location. Then we smooth these heights. The more we smooth, the better the land and water will clump together. Then we decide how high the land will be. We attempt to choose enough land to meet some required percentage. There are two parameters to this algorithm: the amount we will smooth, and the ratio of land to water. The user can provide these numbers at program start up. */ #define MAX_HEIGHT 999 /* highest height */ /* these arrays give some compilers problems when they are automatic */ /* MAP_SIZE+1 to eleminate compiler warning. I think the loop is wrong and goes 1 too high, but safer to just increase here */ static int height[2][MAP_SIZE + 1]; static int height_count[MAX_HEIGHT + 1]; void make_map(void) { int from, to, k; count_t i, j, sum; loc_t loc; for (i = 0; i < MAP_SIZE; i++) { /* fill game.real_map with random sand */ height[0][i] = irand(MAX_HEIGHT); } from = 0; to = 1; for (i = 0; i < game.SMOOTH; i++) { /* smooth the game.real_map */ for (j = 0; j < MAP_SIZE; j++) { sum = height[from][j]; for (k = 0; k < 8; k++) { loc = j + dir_offset[k]; /* edges get smoothed in a wierd fashion */ if (loc < 0 || loc >= MAP_SIZE) { loc = j; } sum += height[from][loc]; } height[to][j] = sum / 9; } k = to; /* swap to and from */ to = from; from = k; } /* 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]]++; } /* find the water line */ loc = MAX_HEIGHT; /* default to all water */ sum = 0; for (i = 0; i <= MAX_HEIGHT; i++) { sum += height_count[i]; if (sum * 100 / MAP_SIZE > game.WATER_RATIO && sum >= NUM_CITY) { loc = i; /* this is last height that is water */ break; } } /* mark the land and water */ for (i = 0; i < MAP_SIZE; i++) { if (height[from][i] > loc) { game.real_map[i].contents = MAP_LAND; } else { game.real_map[i].contents = MAP_SEA; } game.real_map[i].objp = NULL; /* nothing in cell yet */ game.real_map[i].cityp = NULL; j = loc_col(i); k = loc_row(i); game.real_map[i].on_board = !(j == 0 || j == MAP_WIDTH - 1 || k == 0 || k == MAP_HEIGHT - 1); } } /* Randomly place cities on the land. There is a minimum distance that should exist between cities. We maintain a list of acceptable land cells on which a city may be placed. We randomly choose elements from this list until all the cities are placed. After each choice of a land cell for a city, we remove land cells which are too close to the city. */ /* avoid compiler problems with large automatic arrays */ static loc_t land[MAP_SIZE]; void place_cities(void) { count_t regen_land(count_t); count_t placed; count_t num_land; num_land = 0; /* nothing in land array yet */ placed = 0; /* nothing placed yet */ while (placed < NUM_CITY) { count_t i; loc_t loc; while (num_land == 0) { num_land = regen_land(placed); } i = irand(num_land - 1); /* select random piece of land */ loc = land[i]; game.city[placed].loc = loc; game.city[placed].owner = UNOWNED; game.city[placed].work = 0; game.city[placed].prod = NOPIECE; for (i = 0; i < NUM_OBJECTS; i++) { game.city[placed].func[i] = NOFUNC; /* no function */ } game.real_map[loc].contents = MAP_CITY; game.real_map[loc].cityp = &(game.city[placed]); placed++; /* Now remove any land too close to selected land. */ num_land = remove_land(loc, num_land); } } /* When we run out of available land, we recreate our land list. We put all land in the list, decrement the min_city_dist, and then remove any land which is too close to a city. */ count_t regen_land(count_t placed) { count_t num_land; count_t i; num_land = 0; for (i = 0; i < MAP_SIZE; i++) { if (game.real_map[i].on_board && game.real_map[i].contents == MAP_LAND) { land[num_land] = i; /* remember piece of land */ num_land++; /* remember number of pieces */ } } if (placed > 0) { /* don't decrement 1st time */ game.MIN_CITY_DIST -= 1; ASSERT(game.MIN_CITY_DIST >= 0); } for (i = 0; i < placed; i++) { /* for each placed city */ num_land = remove_land(game.city[i].loc, num_land); } return (num_land); } /* Remove land that is too close to a city. */ count_t remove_land(loc_t loc, count_t num_land) { count_t new, i; new = 0; /* nothing kept yet */ for (i = 0; i < num_land; i++) { if (dist(loc, land[i]) >= game.MIN_CITY_DIST) { land[new] = land[i]; new ++; } } return (new); } /* Here we select the cities for the user and the computer. Our choice of cities will be heavily dependent on the difficulty level the user desires. Our algorithm will not guarantee that either player will eventually be able to move armies to any continent on the map. There may be continents which are unreachable by sea. Consider the case of an island in a lake. If the lake has no shore cities, then there is no way for a boat to reach the island. Our hope is that there will be enough water on the map, or enough land, and that there will be enough cities, to make this case extremely rare. First we make a list of continents which contain at least two cities, one or more of which is on the coast. If there are no such continents, we return false, and our caller should decide again where cities should be placed on the map. While making this list, we will rank the continents. Our ranking is based on the thought that shore cities are better than inland cities, that any city is very important, and that the land area of a continent is mildly important. Usually, we expect every continent to have a different ranking. It will be unusual to have two continents with the same land area, the same number of shore cities, and the same number of inland cities. When this is not the case, the first city encountered will be given the highest rank. We then rank pairs of continents. We tell the user the number of different ranks, and ask the user what rank they want to use. This is how the user specifies the difficulty level. Using that pair, we have now decided on a continent for each player. We now choose a random city on each continent, making sure the cities are not the same. */ #define MAX_CONT 10 /* most continents we will allow */ typedef struct cont { /* a continent */ long value; /* value of continent */ int ncity; /* number of cities */ city_info_t *cityp[NUM_CITY]; /* pointer to city */ } cont_t; typedef struct pair { long value; /* value of pair for user */ int user_cont; /* index to user continent */ int comp_cont; /* index to computer continent */ } pair_t; static int marked[MAP_SIZE]; /* list of examine cells */ static int ncont; /* number of continents */ static cont_t cont_tab[MAX_CONT]; /* list of good continenets */ static int rank_tab[MAX_CONT]; /* indices to cont_tab in order of rank */ static pair_t pair_tab[MAX_CONT * MAX_CONT]; /* ranked pairs of continents */ bool select_cities(void) { void find_cont(void), make_pair(void); loc_t compi; city_info_t *compp, *userp; int comp_cont, user_cont; int pair; find_cont(); /* find and rank the continents */ if (ncont == 0) { return (false); /* there are no good continents */ } make_pair(); /* create list of ranked pairs */ (void)sprintf( game.jnkbuf, "Choose a difficulty level where 0 is easy and %d is hard: ", ncont * ncont - 1); pair = get_range(game.jnkbuf, 0, ncont * ncont - 1); comp_cont = pair_tab[pair].comp_cont; user_cont = pair_tab[pair].user_cont; compi = irand((long)cont_tab[comp_cont].ncity); compp = cont_tab[comp_cont].cityp[compi]; do { /* select different user city */ loc_t useri = irand((long)cont_tab[user_cont].ncity); 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 */ /* update city and map */ compp->owner = COMP; compp->prod = ARMY; compp->work = 0; scan(game.comp_map, compp->loc); userp->owner = USER; userp->work = 0; scan(game.user_map, userp->loc); set_prod(userp); return (true); } /* Find all continents with 2 cities or more, one of which must be a shore city. We rank the continents. */ void find_cont(void) { loc_t i; loc_t mapi; for (i = 0; i < MAP_SIZE; i++) { marked[i] = 0; /* nothing marked yet */ } ncont = 0; /* no continents found yet */ mapi = 0; while (ncont < MAX_CONT) { if (!find_next(&mapi)) { return; /* all found */ } } } /* Find the next continent and insert it in the rank table. If there are no more continents, we return false. */ bool find_next(loc_t *mapi) { count_t i; long val; for (;;) { if (*mapi >= MAP_SIZE) { return (false); } if (!game.real_map[*mapi].on_board || marked[*mapi] || game.real_map[*mapi].contents == MAP_SEA) { *mapi += 1; } else if (good_cont(*mapi)) { rank_tab[ncont] = ncont; /* insert cont in rank tab */ val = cont_tab[ncont].value; for (i = ncont; i > 0; i--) { /* bubble up new rank */ if (val > cont_tab[rank_tab[i - 1]].value) { rank_tab[i] = rank_tab[i - 1]; rank_tab[i - 1] = ncont; } else { break; } } ncont++; /* count continents */ return (true); } } } /* Map out the current continent. We mark every piece of land on the continent, count the cities, shore cities, and land area of the continent. If the continent contains 2 cities and a shore city, we set the value of the continent and return true. Otherwise we return false. */ static count_t ncity, nland, nshore; static void mark_cont(loc_t); bool good_cont(loc_t mapi) { long val; ncity = 0; /* nothing seen yet */ nland = 0; nshore = 0; mark_cont(mapi); if (nshore < 1 || ncity < 2) { return (false); } /* The first two cities, one of which must be a shore city, don't contribute to the value. Otherwise shore cities are worth 3/2 an inland city. A city is worth 1000 times as much as land area. */ if (ncity == nshore) { val = (nshore - 2) * 3; } else { val = (nshore - 1) * 3 + (ncity - nshore - 1) * 2; } val *= 1000; /* cities are worth a lot */ val += nland; cont_tab[ncont].value = val; cont_tab[ncont].ncity = ncity; return (true); } /* Mark a continent. This recursive algorithm marks the current square and counts it if it is land or city. If it is city, we also check to see if it is a shore city, and we install it in the list of cities for the continent. We then examine each surrounding cell. */ static void mark_cont(loc_t mapi) { int i; if (marked[mapi] || game.real_map[mapi].contents == MAP_SEA || !game.real_map[mapi].on_board) { return; } marked[mapi] = 1; /* mark this cell seen */ nland++; /* count land on continent */ if (game.real_map[mapi].contents == MAP_CITY) { /* a city? */ cont_tab[ncont].cityp[ncity] = game.real_map[mapi].cityp; ncity++; if (rmap_shore(mapi)) { nshore++; } } for (i = 0; i < 8; i++) { /* look at surrounding squares */ mark_cont(mapi + dir_offset[i]); } } /* Create a list of pairs of continents in a ranked order. The first element in the list is the pair which is easiest for the user to win with. Our ranking is simply based on the difference in value between the user's continent and the computer's continent. */ void make_pair(void) { int i, j, k, npair; long val; npair = 0; /* none yet */ for (i = 0; i < ncont; i++) { for (j = 0; j < ncont; j++) { /* loop through all continents */ val = cont_tab[i].value - cont_tab[j].value; pair_tab[npair].value = val; pair_tab[npair].user_cont = i; pair_tab[npair].comp_cont = j; for (k = npair; k > 0; k--) { /* bubble up new rank */ if (val > pair_tab[k - 1].value) { pair_tab[k] = pair_tab[k - 1]; pair_tab[k - 1].user_cont = i; pair_tab[k - 1].comp_cont = j; // Sort fix from Sarvottamananda pair_tab[k - 1].value = val; } else { break; } } npair++; /* count pairs */ } } } /* Save a game. We save the game in emp_save.dat. Someday we may want to ask the user for a file name. If we cannot save the game, we will tell the user why. */ /* macro to save typing; write an array, return if it fails */ #define wbuf(buf) \ if (!xwrite(f, (char *)buf, sizeof(buf))) \ return #define wval(val) \ if (!xwrite(f, (char *)&val, sizeof(val))) \ return #define SAVECOOKIE "EMPSAVE 1\n" /* increase digit when format changes */ static char buf[32]; void save_game(void) { FILE *f; /* file to save game in */ f = fopen(game.savefile, "w"); /* open for output */ if (f == NULL) { perror("Cannot save saved game"); return; } wbuf(SAVECOOKIE); wbuf(game.real_map); wbuf(game.comp_map); wbuf(game.user_map); wbuf(game.city); wbuf(game.object); wbuf(game.user_obj); wbuf(game.comp_obj); wval(game.free_list); wval(game.date); wval(game.automove); wval(game.resigned); wval(game.debug); wval(game.win); wval(game.save_movie); wval(game.user_score); wval(game.comp_score); (void)fclose(f); topmsg(3, "Game saved."); } /* Recover a saved game from emp_save.dat. We return true if we succeed, otherwise false. */ #define rbuf(buf) \ if (!xread(f, (char *)buf, sizeof(buf))) \ return (false); #define rval(val) \ if (!xread(f, (char *)&val, sizeof(val))) \ return (false); int restore_game(void) { void read_embark(piece_info_t *, int); FILE *f; /* file to save game in */ long i; piece_info_t **list; piece_info_t *obj; f = fopen(game.savefile, "r"); /* open for input */ if (f == NULL) { perror("Cannot open saved game"); return false; } if (fgets(buf, sizeof(buf), f) == NULL) { fclose(f); return false; } else if (strcmp(buf, SAVECOOKIE) != 0) { fclose(f); return false; } else if (fread(buf, 1, sizeof(char), f) != 1) { /* skip trailing nul after cookie */ fclose(f); return false; } rbuf(game.real_map); rbuf(game.comp_map); rbuf(game.user_map); rbuf(game.city); rbuf(game.object); rbuf(game.user_obj); rbuf(game.comp_obj); rval(game.free_list); rval(game.date); rval(game.automove); rval(game.resigned); rval(game.debug); rval(game.win); rval(game.save_movie); rval(game.user_score); rval(game.comp_score); /* Our pointers may not be valid because of source changes or other things. We recreate them. */ game.free_list = NULL; /* zero all ptrs */ for (i = 0; i < MAP_SIZE; i++) { game.real_map[i].cityp = NULL; game.real_map[i].objp = NULL; } for (i = 0; i < LIST_SIZE; i++) { game.object[i].loc_link.next = NULL; game.object[i].loc_link.prev = NULL; game.object[i].cargo_link.next = NULL; game.object[i].cargo_link.prev = NULL; game.object[i].piece_link.next = NULL; game.object[i].piece_link.prev = NULL; game.object[i].ship = NULL; game.object[i].cargo = NULL; } for (i = 0; i < NUM_OBJECTS; i++) { game.comp_obj[i] = NULL; game.user_obj[i] = NULL; } /* put cities on game.real_map */ for (i = 0; i < NUM_CITY; i++) { game.real_map[game.city[i].loc].cityp = &(game.city[i]); } /* put pieces in free list or on map and in object lists */ for (i = 0; i < LIST_SIZE; i++) { obj = &(game.object[i]); if (game.object[i].owner == UNOWNED || game.object[i].hits == 0) { LINK(game.free_list, obj, piece_link); } else { list = LIST(game.object[i].owner); LINK(list[game.object[i].type], obj, piece_link); LINK(game.real_map[game.object[i].loc].objp, obj, loc_link); } } /* Embark armies and fighters. */ read_embark(game.user_obj[TRANSPORT], ARMY); read_embark(game.user_obj[CARRIER], FIGHTER); read_embark(game.comp_obj[TRANSPORT], ARMY); read_embark(game.comp_obj[CARRIER], FIGHTER); (void)fclose(f); kill_display(); /* what we had is no longer good */ topmsg(3, "Game restored from save file."); return (true); } /* Embark cargo on a ship. We loop through the list of ships. We then loop through the pieces at the ship's location until the ship has the same amount of cargo it previously had. */ void read_embark(piece_info_t *list, int piece_type) { void inconsistent(void); piece_info_t *ship; piece_info_t *obj; for (ship = list; ship != NULL; ship = ship->piece_link.next) { int count = ship->count; /* get # of pieces we need */ if (count < 0) { inconsistent(); } ship->count = 0; /* nothing on board yet */ for (obj = game.real_map[ship->loc].objp; obj && count; obj = obj->loc_link.next) { if (obj->ship == NULL && obj->type == piece_type) { embark(ship, obj); count -= 1; } } if (count) { inconsistent(); } } } void inconsistent(void) { (void)printf("saved game is inconsistent. Please remove it.\n"); exit(1); } /* Write a buffer to a file. If we cannot write everything, return false. Also, tell the user why the write did not work if it didn't. */ bool xwrite(FILE *f, char *buf, int size) { int bytes; bytes = fwrite(buf, 1, size, f); if (bytes == -1) { perror("Write to save file failed"); return (false); } if (bytes != size) { perror("Cannot complete write to save file.\n"); return (false); } return (true); } /* Read a buffer from a file. If the read fails, we tell the user why and return false. */ bool xread(FILE *f, char *buf, int size) { int bytes; bytes = fread(buf, 1, size, f); if (bytes == -1) { perror("Read from save file failed"); return (false); } if (bytes != size) { perror("Saved file is too short.\n"); return (false); } return (true); } /* Save a movie screen. For each cell on the board, we write out the character that would appear on either the user's or the computer's screen. This information is appended to 'empmovie.dat'. */ extern char city_char[]; static char mapbuf[MAP_SIZE]; void save_movie_screen(void) { FILE *f; /* file to save game in */ count_t i; piece_info_t *p; f = fopen("empmovie.dat", "a"); /* open for append */ if (f == NULL) { perror("Cannot open empmovie.dat"); return; } for (i = 0; i < MAP_SIZE; i++) { if (game.real_map[i].cityp) { mapbuf[i] = city_char[game.real_map[i].cityp->owner]; } else { p = find_obj_at_loc(i); if (!p) { mapbuf[i] = game.real_map[i].contents; } else if (p->owner == USER) { mapbuf[i] = piece_attr[p->type].sname; } else { mapbuf[i] = tolower(piece_attr[p->type].sname); } } } wbuf(mapbuf); (void)fclose(f); } /* Replay a movie. We read each buffer from the file and print it using a zoomed display. */ void replay_movie(void) { void print_movie_cell(char *, int, int, int, int); FILE *f; /* file to save game in */ int r, c; int round; f = fopen("empmovie.dat", "r"); /* open for input */ if (f == NULL) { perror("Cannot open empmovie.dat"); return; } round = 0; clear_screen(); for (;;) { int row_inc, col_inc; if (fread((char *)mapbuf, 1, sizeof(mapbuf), f) != sizeof(mapbuf)) { break; } round += 1; stat_display(mapbuf, round); row_inc = (MAP_HEIGHT + game.lines - NUMTOPS - 1) / (game.lines - NUMTOPS); col_inc = (MAP_WIDTH + game.cols - 1) / (game.cols - 1); for (r = 0; r < MAP_HEIGHT; r += row_inc) { for (c = 0; c < MAP_WIDTH; c += col_inc) { print_movie_cell(mapbuf, r, c, row_inc, col_inc); } } (void)redisplay(); delay(); } (void)fclose(f); } /* Display statistics about the game. At the top of the screen we print: nn O nn A nn F nn P nn D nn S nn T nn C nn B nn Z xxxxx nn X nn a nn f nn p nn d nn s nn t nn c nn b nn z xxxxx There may be objects in cities and boats that aren't displayed. The "xxxxx" field is the cumulative cost of building the hardware. */ /* in declared order, with city first */ static char *pieces = "OAFPDSTCBZXafpdstcbz"; void stat_display(char *mbuf, int round) { count_t i; int counts[2 * NUM_OBJECTS + 2]; int user_cost, comp_cost; (void)memset((char *)counts, '\0', sizeof(counts)); for (i = 0; i < MAP_SIZE; i++) { char *p = strchr(pieces, mbuf[i]); if (p) { counts[p - pieces] += 1; } } user_cost = 0; for (i = 1; i <= NUM_OBJECTS; i++) { user_cost += counts[i] * piece_attr[i - 1].build_time; } comp_cost = 0; for (i = NUM_OBJECTS + 2; i <= 2 * NUM_OBJECTS + 1; i++) { comp_cost += counts[i] * piece_attr[i - NUM_OBJECTS - 2].build_time; } for (i = 0; i < NUM_OBJECTS + 1; i++) { pos_str(1, (int)i * 6, "%2d %c ", counts[i], pieces[i]); pos_str(2, (int)i * 6, "%2d %c ", counts[i + NUM_OBJECTS + 1], pieces[i + NUM_OBJECTS + 1]); } pos_str(1, (int)i * 6, "%5d", user_cost); pos_str(2, (int)i * 6, "%5d", comp_cost); pos_str(0, 0, "Round %3d", (round + 1) / 2); } /* end */ vms-empire-1.18/HACKING0000664000175000017500000000633614551261713012701 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ C Empire Sources (now called vms-empire). A few notes on how to debug and modify this code. This used to be part of the README. Organization: empire.h -- definitions of data structures extern.h -- definitions of global variables data.c -- constant data main.c -- option parsing empire.c -- main program loop and outermost command handler usermove.c -- move the user's pieces compmove.c -- move the computer's pieces edit.c -- handle the user's edit mode commands game.c -- saving, restoring, and initializing the game board display.c -- update the screen term.c -- deal with information area of screen math.c -- mathematical routines object.c -- routines for manipulating objects attack.c -- handle attacks between pieces map.c -- find paths for moving pieces util.c -- miscellaneous routines, especially I/O. Debugging notes: From command mode, there are two special commands that can be used to turn debugging mode on or off. "++" turns debugging mode on. "+-" turns debugging mode off. When debugging mode is turned on, the following commands are available: "#" -- display a sector of the computer's map. "%" -- enter "movie" mode. The computer continuously makes moves, and the computer's map is shown on the screen. This is useful for debugging the algorithm used by the computer when it makes a move. Don't confuse this with saving a movie and replaying it. "@" -- enable/disable "trace pathmap" mode. If this command is followed by a "+", trace pathmap mode is enabled. If this command is followed by a "-", trace pathmap mode is disabled. In this mode, every time a "pathmap" is created, it is displayed. This is useful for debugging the subroutines that search for an optimal path along which to move a piece. "$" -- enable/disable "print_debug". This command is also followed by either a "+" or "-". In this mode, various messages will be printed out at times which may indicate that something is being done non-optimally. "&" -- enable/disable "print_vmap". This command is followed by a char that specifies the type of vmap to be displayed. Values are "a" -- army load maps "l" -- transport load maps "u" -- transport unload maps "s" -- ship maps "i" -- pruned explore map Any other character disables the printing of vmaps. The program will not provide any prompts for the debugging commands. If you make a mistake, the computer just beeps. You can also replay a saved movie with the normal "W" command when debugging mode is turned on. Also, the -DDEBUG flag can be turned on to cause consistency checking to be performed frequently on the internal database. This consistency checking is fairly exhaustive and checks for all sorts of screwed up pointers. My measurements suggest that consistency checking causes the program to run half as fast. vms-empire-1.18/main.c0000664000175000017500000000452514674357337013016 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* main.c -- parse command line for empire options: -w water: percentage of map that is water. Must be in the range 10..90. Default is 70. -s smooth: amount of smoothing performed to generate map. Must be a nonnegative integer. Default is 5. -d delay: number of milliseconds to delay between output. default is 2000 (2 seconds). -S saveinterval: sets turn interval between saves. default is 10 */ #include "empire.h" #include "extern.h" #include #include #include #define OPTFLAGS "w:s:d:S:f:" int main(int argc, char *argv[]) { int c; extern char *optarg; extern int optind; int errflg = 0; int wflg, sflg, dflg, Sflg; int land; wflg = 70; /* set defaults */ sflg = 5; dflg = 2000; Sflg = 10; game.savefile = "empsave.dat"; /* * extract command line options */ while ((c = getopt(argc, argv, OPTFLAGS)) != EOF) { switch (c) { case 'w': wflg = atoi(optarg); break; case 's': sflg = atoi(optarg); break; case 'd': dflg = atoi(optarg); break; case 'S': Sflg = atoi(optarg); break; case 'f': game.savefile = optarg; break; case '?': /* illegal option? */ errflg++; break; } } if (errflg || (argc - optind) != 0) { (void)printf("empire: usage: empire [-w water] [-s smooth] [-d " "delay]\n"); exit(1); } if (wflg < 10 || wflg > 90) { (void)printf( "empire: -w argument must be in the range 0..90.\n"); exit(1); } if (sflg < 0) { (void)printf( "empire: -s argument must be greater or equal to zero.\n"); exit(1); } if (dflg < 0 || dflg > 30000) { (void)printf( "empire: -d argument must be in the range 0..30000.\n"); exit(1); } game.SMOOTH = sflg; game.WATER_RATIO = wflg; game.delay_time = dflg; game.save_interval = Sflg; /* compute min distance between cities */ land = MAP_SIZE * (100 - game.WATER_RATIO) / 100; /* available land */ land /= NUM_CITY; /* land per city */ game.MIN_CITY_DIST = isqrt(land); /* distance between cities */ empire(); /* call main routine */ return (0); } vms-empire-1.18/Makefile0000664000175000017500000000664314562204012013342 0ustar esresr# # SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons # SPDX-License-Identifier: GPL-2.0+ # # See the file COPYING, distributed with empire, for restriction # and warranty information. VERS=$(shell sed -n MANIFEST @(cd ..; ln -s vms-empire vms-empire-$(VERS)) (cd ..; tar -czf vms-empire/vms-empire-$(VERS).tar.gz `cat vms-empire/MANIFEST`) @(cd ..; rm vms-empire-$(VERS)) dist: vms-empire-$(VERS).tar.gz release: vms-empire-$(VERS).tar.gz vms-empire.html shipper version=$(VERS) | sh -e -x refresh: vms-empire.html shipper -N -w version=$(VERS) | sh -e -x vms-empire-1.18/map.c0000664000175000017500000010120414562176733012632 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* map.c This file contains routines for playing around with view maps, real maps, path_maps, and cont_maps. */ #include "empire.h" #include "extern.h" #include #define SWAP(a, b) \ { \ perimeter_t *x; \ x = a; \ a = b; \ b = x; \ } static void expand_perimeter(path_map_t *, view_map_t *, move_info_t *, perimeter_t *, int, int, int, int, perimeter_t *, perimeter_t *); static void expand_prune(view_map_t *, path_map_t *, loc_t, int, perimeter_t *, int *); static int objective_cost(view_map_t *, move_info_t *, loc_t, int); static int terrain_type(path_map_t *, view_map_t *, move_info_t *, loc_t, loc_t); static void start_perimeter(path_map_t *, perimeter_t *, loc_t, int); static void add_cell(path_map_t *, loc_t, perimeter_t *, int, int, int); static int vmap_count_path(path_map_t *, loc_t); static perimeter_t p1; /* perimeter list for use as needed */ static perimeter_t p2; static perimeter_t p3; static perimeter_t p4; static int best_cost; /* cost and location of best objective */ static loc_t best_loc; /* Map out a continent. We are given a location on the continent. We mark each square that is part of the continent and unexplored territory adjacent to the continent. By adjusting the value of 'bad_terrain', this routine can map either continents of land, or lakes. */ void vmap_cont(int *cont_map, view_map_t *vmap, loc_t loc, char bad_terrain) { (void)memset((char *)cont_map, '\0', MAP_SIZE * sizeof(int)); vmap_mark_up_cont(cont_map, vmap, loc, bad_terrain); } /* Mark all squares of a continent and the squares that are adjacent to the continent which are on the board. Our passed location is known to be either on the continent or adjacent to the continent. */ void vmap_mark_up_cont(int *cont_map, view_map_t *vmap, loc_t loc, char bad_terrain) { int i, j; loc_t new_loc; char this_terrain; perimeter_t *from, *to; from = &p1; to = &p2; from->len = 1; /* init perimeter */ from->list[0] = loc; cont_map[loc] = 1; /* loc is on continent */ while (from->len) { to->len = 0; /* nothing in new perimeter yet */ for (i = 0; i < from->len; i++) { /* expand perimeter */ FOR_ADJ_ON(from->list[i], new_loc, j) if (!cont_map[new_loc]) { /* mark, but don't expand, unexplored territory */ if (vmap[new_loc].contents == ' ') { cont_map[new_loc] = 1; } else { if (vmap[new_loc].contents == MAP_LAND) { this_terrain = MAP_LAND; } else if (vmap[new_loc].contents == MAP_SEA) { this_terrain = MAP_SEA; } else { this_terrain = game.real_map[new_loc] .contents; } if (this_terrain != bad_terrain) { /* on continent? */ cont_map[new_loc] = 1; to->list[to->len] = new_loc; to->len += 1; } } } } SWAP(from, to); } } /* Map out a continent. We are given a location on the continent. We mark each square that is part of the continent. By adjusting the value of 'bad_terrain', this routine can map either continents of land, or lakes. */ static void rmap_mark_up_cont(int *cont_map, loc_t loc, char bad_terrain); void rmap_cont(int *cont_map, loc_t loc, char bad_terrain) { (void)memset((char *)cont_map, '\0', MAP_SIZE * sizeof(int)); rmap_mark_up_cont(cont_map, loc, bad_terrain); } /* Mark all squares of a continent and the squares that are adjacent to the continent which are on the board. Our passed location is known to be either on the continent or adjacent to the continent. Someday this should be tweaked to use perimeter lists. */ static void rmap_mark_up_cont(int *cont_map, loc_t loc, char bad_terrain) { int i; loc_t new_loc; if (!game.real_map[loc].on_board) { return; /* off board */ } if (cont_map[loc]) { return; /* already marked */ } if (game.real_map[loc].contents == bad_terrain) { return; /* off continent */ } cont_map[loc] = 1; /* on continent */ FOR_ADJ(loc, new_loc, i) rmap_mark_up_cont(cont_map, new_loc, bad_terrain); } /* Scan a continent recording items of interest on the continent. This could be done as we mark up the continent. */ #define COUNT(c, item) \ case c: \ item += 1; \ break scan_counts_t vmap_cont_scan(int *cont_map, view_map_t *vmap) { scan_counts_t counts; count_t i; (void)memset((char *)&counts, '\0', sizeof(scan_counts_t)); for (i = 0; i < MAP_SIZE; i++) { if (cont_map[i]) { /* cell on continent? */ counts.size += 1; switch (vmap[i].contents) { COUNT(' ', counts.unexplored); COUNT('O', counts.user_cities); COUNT('A', counts.user_objects[ARMY]); COUNT('F', counts.user_objects[FIGHTER]); COUNT('P', counts.user_objects[PATROL]); COUNT('D', counts.user_objects[DESTROYER]); COUNT('S', counts.user_objects[SUBMARINE]); COUNT('T', counts.user_objects[TRANSPORT]); COUNT('C', counts.user_objects[CARRIER]); COUNT('B', counts.user_objects[BATTLESHIP]); COUNT('X', counts.comp_cities); COUNT('a', counts.comp_objects[ARMY]); COUNT('f', counts.comp_objects[FIGHTER]); COUNT('p', counts.comp_objects[PATROL]); COUNT('d', counts.comp_objects[DESTROYER]); COUNT('s', counts.comp_objects[SUBMARINE]); COUNT('t', counts.comp_objects[TRANSPORT]); COUNT('c', counts.comp_objects[CARRIER]); COUNT('b', counts.comp_objects[BATTLESHIP]); COUNT(MAP_CITY, counts.unowned_cities); case MAP_LAND: break; case MAP_SEA: break; default: /* check for city underneath */ if (game.real_map[i].contents == MAP_CITY) { switch (game.real_map[i].cityp->owner) { COUNT(USER, counts.user_cities); COUNT(COMP, counts.comp_cities); COUNT(UNOWNED, counts.unowned_cities); } } } } } return counts; } /* Scan a real map as above. Only the 'size' and 'unowned_cities' fields are valid. */ scan_counts_t rmap_cont_scan(int *cont_map) { scan_counts_t counts; count_t i; (void)memset((char *)&counts, '\0', sizeof(scan_counts_t)); for (i = 0; i < MAP_SIZE; i++) { if (cont_map[i]) { /* cell on continent? */ counts.size += 1; if (game.real_map[i].contents == MAP_CITY) { counts.unowned_cities += 1; } } } return counts; } /* Return true if a location is on the edge of a continent. */ bool map_cont_edge(const int *cont_map, loc_t loc) { loc_t i, j; if (!cont_map[loc]) { return false; /* not on continent */ } FOR_ADJ(loc, j, i) if (!cont_map[j]) { return true; /* edge of continent */ } return false; } /* Find the nearest objective for a piece. This routine actually does some real work. This code represents my fourth rewrite of the algorithm. This algorithm is central to the strategy used by the computer. Given a view_map, we create a path_map. On the path_map, we record the distance from a location to the nearest objective. We are given information about what the interesting objectives are, and how interesting each objective is. We use a breadth first search to find the nearest objective. We maintain something called a "perimeter list". This list initially contains a list of squares that we can reach in 'n' moves. On each pass through our loop, we add all squares that are adjacent to the perimeter list and which lie outside the perimeter to our list. (The loop is only slightly more complicated for armies and transports.) When our perimeter list becomes empty, or when the distance to the current perimeter is at least as large as the weighted distance to the best objective, we return the location of the best objective found. The 'cost' field in a path_map must be INFINITY if the cell lies outside of the current perimeter. The cost for cells that lie on or within the current perimeter doesn't matter, except that the information must be consistent with the needs of 'vmap_mark_path'. */ /* Find an objective over a single type of terrain. */ loc_t vmap_find_xobj(path_map_t path_map[], view_map_t *vmap, loc_t loc, move_info_t *move_info, int start, int expand) { perimeter_t *from; perimeter_t *to; int cur_cost; from = &p1; to = &p2; start_perimeter(path_map, from, loc, start); cur_cost = 0; /* cost to reach current perimeter */ for (;;) { to->len = 0; /* nothing in perim yet */ expand_perimeter(path_map, vmap, move_info, from, expand, cur_cost, 1, 1, to, to); if (game.trace_pmap) { print_pzoom("After xobj loop:", path_map, vmap); } cur_cost += 1; if (to->len == 0 || best_cost <= cur_cost) { return best_loc; } SWAP(from, to); } } /* Find an objective for a piece that crosses land and water. */ loc_t vmap_find_aobj(path_map_t path_map[], view_map_t *vmap, loc_t loc, move_info_t *move_info) { return vmap_find_xobj(path_map, vmap, loc, move_info, T_LAND, T_AIR); } /* Find an objective for a piece that crosses only water. */ loc_t vmap_find_wobj(path_map_t path_map[], view_map_t *vmap, loc_t loc, move_info_t *move_info) { return vmap_find_xobj(path_map, vmap, loc, move_info, T_WATER, T_WATER); } /* Find an objective for a piece that crosses only land. */ loc_t vmap_find_lobj(path_map_t path_map[], view_map_t *vmap, loc_t loc, move_info_t *move_info) { return vmap_find_xobj(path_map, vmap, loc, move_info, T_LAND, T_LAND); } /* Find an objective moving from land to water. This is mildly complicated. It costs 2 to move on land and one to move on water. To handle this, we expand our current perimeter by one cell, where land can be expanded to either land or water, and water is only expanded to water. Then we expand any water one more cell. We have different objectives depending on whether the objective is being approached from the land or the water. */ loc_t vmap_find_lwobj(path_map_t path_map[], view_map_t *vmap, loc_t loc, move_info_t *move_info, int beat_cost) { perimeter_t *cur_land; perimeter_t *cur_water; perimeter_t *new_land; perimeter_t *new_water; int cur_cost; cur_land = &p1; cur_water = &p2; new_water = &p3; new_land = &p4; start_perimeter(path_map, cur_land, loc, T_LAND); cur_water->len = 0; best_cost = beat_cost; /* we can do this well */ cur_cost = 0; /* cost to reach current perimeter */ for (;;) { /* expand current perimeter one cell */ new_water->len = 0; new_land->len = 0; expand_perimeter(path_map, vmap, move_info, cur_water, T_WATER, cur_cost, 1, 1, new_water, NULL); expand_perimeter(path_map, vmap, move_info, cur_land, T_AIR, cur_cost, 1, 2, new_water, new_land); /* expand new water one cell */ cur_water->len = 0; expand_perimeter(path_map, vmap, move_info, new_water, T_WATER, cur_cost + 1, 1, 1, cur_water, NULL); if (game.trace_pmap) { print_pzoom("After lwobj loop:", path_map, vmap); } cur_cost += 2; if ((cur_water->len == 0 && new_land->len == 0) || (best_cost <= cur_cost)) { return best_loc; } SWAP(cur_land, new_land); } } #ifdef __UNUSED__ /* Return the cost to reach the adjacent cell of the correct type with the lowest cost. */ static int best_adj(path_map_t *pmap, loc_t loc, int type) { int i; loc_t new_loc; int best; best = INFINITY; FOR_ADJ(loc, new_loc, i) if (pmap[new_loc].terrain == type && pmap[new_loc].cost < best) { best = pmap[new_loc].cost; } return best; } #endif /* Find an objective moving from water to land. Here, we expand water to either land or water. We expand land only to land. We cheat ever so slightly, but this cheating accurately reflects the mechanics o moving. The first time we expand water we can expand to land or water (army moving off tt or tt moving on water), but the second time, we only expand water (tt taking its second move). */ loc_t vmap_find_wlobj(path_map_t path_map[], view_map_t *vmap, loc_t loc, move_info_t *move_info) { perimeter_t *cur_land; perimeter_t *cur_water; perimeter_t *new_land; perimeter_t *new_water; int cur_cost; cur_land = &p1; cur_water = &p2; new_water = &p3; new_land = &p4; start_perimeter(path_map, cur_water, loc, T_WATER); cur_land->len = 0; cur_cost = 0; /* cost to reach current perimeter */ for (;;) { /* expand current perimeter one cell */ new_water->len = 0; new_land->len = 0; expand_perimeter(path_map, vmap, move_info, cur_water, T_AIR, cur_cost, 1, 2, new_water, new_land); expand_perimeter(path_map, vmap, move_info, cur_land, T_LAND, cur_cost, 1, 2, NULL, new_land); /* expand new water one cell to water */ cur_water->len = 0; expand_perimeter(path_map, vmap, move_info, new_water, T_WATER, cur_cost + 1, 1, 1, cur_water, NULL); if (game.trace_pmap) { print_pzoom("After wlobj loop:", path_map, vmap); } cur_cost += 2; if ((cur_water->len == 0 && new_land->len == 0) || (best_cost <= cur_cost)) { return best_loc; } SWAP(cur_land, new_land); } } /* Initialize the perimeter searching. This routine was taking a significant amount of the program time (10%) doing the initialization of the path map. We now use an external constant and 'memcpy'. */ static path_map_t pmap_init[MAP_SIZE]; static bool init_done = false; static void start_perimeter(path_map_t *pmap, perimeter_t *perim, loc_t loc, int terrain) { /* zap the path map */ if (!init_done) { int i; init_done = true; for (i = 0; i < MAP_SIZE; i++) { pmap_init[i].cost = INFINITY; /* everything lies outside perim */ pmap_init[i].terrain = T_UNKNOWN; } } (void)memcpy((char *)pmap, (char *)pmap_init, sizeof(pmap_init)); /* put first location in perimeter */ pmap[loc].cost = 0; pmap[loc].inc_cost = 0; pmap[loc].terrain = terrain; perim->len = 1; perim->list[0] = loc; best_cost = INFINITY; /* no best yet */ best_loc = loc; /* if nothing found, result is current loc */ } /* Expand the perimeter. Note that 'waterp' and 'landp' may be the same. For each cell of the current perimeter, we examine each cell adjacent to that cell which lies outside of the current perimeter. If the adjacent cell is an objective, we update best_cost and best_loc. If the adjacent cell is of the correct type, we turn place the adjacent cell in either the new water perimeter or the new land perimeter. We set the cost to reach the current perimeter. */ static void expand_perimeter(path_map_t *pmap, view_map_t *vmap, move_info_t *move_info, perimeter_t *curp, int type, int cur_cost, int inc_wcost, int inc_lcost, perimeter_t *waterp, perimeter_t *landp) /* pmap = path map to up1date */ /* move_info = objectives and weights */ /* curp = perimeter to expand */ /* type = type of terrain to expand */ /* cur_cost = cost to reach cells on perimeter */ /* inc_wcost = cost to enter new water cells */ /* inc_lcost = cost to enter new land cells */ /* waterp = pointer to new water perimeter */ /* landp = pointer to new land perimeter */ { register long i; register int j; loc_t new_loc; int obj_cost; register int new_type; for (i = 0; i < curp->len; i++) { /* for each perimeter cell... */ FOR_ADJ_ON(curp->list[i], new_loc, j) { /* for each adjacent cell... */ register path_map_t *pm = pmap + new_loc; if (pm->cost == INFINITY) { new_type = terrain_type(pmap, vmap, move_info, curp->list[i], new_loc); if (new_type == T_LAND && (type & T_LAND)) { add_cell(pmap, new_loc, landp, new_type, cur_cost, inc_lcost); } else if (new_type == T_WATER && (type & T_WATER)) { add_cell(pmap, new_loc, waterp, new_type, cur_cost, inc_wcost); } else if (new_type == T_UNKNOWN) { /* unreachable cell? */ pm->terrain = new_type; pm->cost = cur_cost + INFINITY / 2; pm->inc_cost = INFINITY / 2; } if (pmap[new_loc].cost != INFINITY) { /* did we expand? */ obj_cost = objective_cost( vmap, move_info, new_loc, cur_cost); if (obj_cost < best_cost) { best_cost = obj_cost; best_loc = new_loc; if (new_type == T_UNKNOWN) { pm->cost = cur_cost + 2; pm->inc_cost = 2; } } } } } } } /* Add a cell to a perimeter list. */ static void add_cell(path_map_t *pmap, loc_t new_loc, perimeter_t *perim, int terrain, int cur_cost, int inc_cost) { register path_map_t *pm = &pmap[new_loc]; ASSERT(pm != NULL); // cppcheck-suppress nullPointerRedundantCheck pm->terrain = terrain; // cppcheck-suppress nullPointerRedundantCheck pm->inc_cost = inc_cost; // cppcheck-suppress nullPointerRedundantCheck pm->cost = cur_cost + inc_cost; ASSERT(perim != NULL); // cppcheck-suppress nullPointerRedundantCheck perim->list[perim->len] = new_loc; // cppcheck-suppress nullPointerRedundantCheck perim->len += 1; } /* Compute the cost to move to an objective. */ static int objective_cost(view_map_t *vmap, move_info_t *move_info, loc_t loc, int base_cost) { char *p; int w; city_info_t *cityp; p = strchr(move_info->objectives, vmap[loc].contents); if (!p) { return INFINITY; } w = move_info->weights[p - move_info->objectives]; if (w >= 0) { return w + base_cost; } switch (w) { case W_TT_BUILD: /* handle special case of moving to tt building city */ cityp = find_city(loc); if (!cityp) { return base_cost + 2; /* tt is already here */ } if (cityp->prod != TRANSPORT) { return base_cost + 2; /* just finished a tt */ } /* compute time to wait for tt to be built */ w = piece_attr[TRANSPORT].build_time - cityp->work; w *= 2; /* had to cross land to get here */ if (w < base_cost + 2) { w = base_cost + 2; } return w; default: ABORT; /* NOTREACHED */ return -1; } } /* Return the type of terrain at a vmap location. */ static int terrain_type(path_map_t *pmap, view_map_t *vmap, move_info_t *move_info, loc_t from_loc, loc_t to_loc) { if (vmap[to_loc].contents == MAP_LAND) { return T_LAND; } if (vmap[to_loc].contents == MAP_SEA) { return T_WATER; } if (vmap[to_loc].contents == '%') { return T_UNKNOWN; /* magic objective */ } if (vmap[to_loc].contents == ' ') { return pmap[from_loc].terrain; } switch (game.real_map[to_loc].contents) { case MAP_SEA: return T_WATER; case MAP_LAND: return T_LAND; case MAP_CITY: if (game.real_map[to_loc].cityp->owner == move_info->city_owner) { return T_WATER; } else { return T_UNKNOWN; /* cannot cross */ } } ABORT; /*NOTREACHED*/ return -1; } /* Prune unexplored territory. We take a view map and we modify it so that unexplored territory that is adjacent to a lot of land or a lot of water is marked as being either that land or water. So basically, we are making a predicition about what we expect for land and water. We iterate this algorithm until either the next iteration would remove all unexplored territory, or there is nothing more about which we can make an assumption. First, we use a pathmap to save the number of adjacent land and water cells for each unexplored cell. Cells which have adjacent explored territory are placed in a perimeter list. We also count the number of cells that are not unexplored. We now take this perimeter list and make high-probability predictions. Then we round things off by making one pass of medium probability predictions. Then we make multiple passes extending our predictions. We stop if at any point all remaining unexplored cells are in a perimeter list, or if no predictions were made during one of the final passes. Unlike other algorithms, here we deal with "off board" locations. So be careful. */ void vmap_prune_explore_locs(view_map_t *vmap) { path_map_t pmap[MAP_SIZE]; perimeter_t *from, *to; int explored; loc_t loc, new_loc; count_t i; long copied; (void)memset(pmap, '\0', sizeof(pmap)); from = &p1; to = &p2; from->len = 0; explored = 0; /* build initial path map and perimeter list */ for (loc = 0; loc < MAP_SIZE; loc++) { if (vmap[loc].contents != ' ') { explored += 1; } else { /* add unexplored cell to perim */ FOR_ADJ(loc, new_loc, i) { if (new_loc < 0 || new_loc >= MAP_SIZE) { ; /* ignore off map */ } else if (vmap[new_loc].contents == ' ') { ; /* ignore adjacent unexplored */ } else if (game.real_map[new_loc].contents != MAP_SEA) { pmap[loc].cost += 1; /* count land */ } else { pmap[loc].inc_cost += 1; /* count water */ } } if (pmap[loc].cost || pmap[loc].inc_cost) { from->list[from->len] = loc; from->len += 1; } } } if (game.print_vmap == 'I') { print_xzoom(vmap); } for (;;) { /* do high probability predictions */ if (from->len + explored == MAP_SIZE) { return; } to->len = 0; copied = 0; for (i = 0; i < from->len; i++) { loc = from->list[i]; if (pmap[loc].cost >= 5) { expand_prune(vmap, pmap, loc, T_LAND, to, &explored); } else if (pmap[loc].inc_cost >= 5) { expand_prune(vmap, pmap, loc, T_WATER, to, &explored); } else if ((loc < MAP_WIDTH || loc >= MAP_SIZE - MAP_WIDTH) && pmap[loc].cost >= 3) { expand_prune(vmap, pmap, loc, T_LAND, to, &explored); } else if ((loc < MAP_WIDTH || loc >= MAP_SIZE - MAP_WIDTH) && pmap[loc].inc_cost >= 3) { expand_prune(vmap, pmap, loc, T_WATER, to, &explored); } else if ((loc == 0 || loc == MAP_SIZE - 1) && pmap[loc].cost >= 2) { expand_prune(vmap, pmap, loc, T_LAND, to, &explored); } else if ((loc == 0 || loc == MAP_SIZE - 1) && pmap[loc].inc_cost >= 2) { expand_prune(vmap, pmap, loc, T_WATER, to, &explored); } else { /* copy perimeter cell */ to->list[to->len] = loc; to->len += 1; copied += 1; } } if (copied == from->len) { break; /* nothing expanded */ } SWAP(from, to); } if (game.print_vmap == 'I') { print_xzoom(vmap); } /* one pass for medium probability predictions */ if (from->len + explored == MAP_SIZE) { return; } to->len = 0; for (i = 0; i < from->len; i++) { loc = from->list[i]; if (pmap[loc].cost > pmap[loc].inc_cost) { expand_prune(vmap, pmap, loc, T_LAND, to, &explored); } else if (pmap[loc].cost < pmap[loc].inc_cost) { expand_prune(vmap, pmap, loc, T_WATER, to, &explored); } else { /* copy perimeter cell */ to->list[to->len] = loc; to->len += 1; } } SWAP(from, to); if (game.print_vmap == 'I') { print_xzoom(vmap); } /* multiple low probability passes */ for (;;) { /* return if very little left to explore */ if (from->len + explored >= MAP_SIZE - MAP_HEIGHT) { if (game.print_vmap == 'I') { print_xzoom(vmap); } return; } to->len = 0; copied = 0; for (i = 0; i < from->len; i++) { loc = from->list[i]; if (pmap[loc].cost >= 4 && pmap[loc].inc_cost < 4) { expand_prune(vmap, pmap, loc, T_LAND, to, &explored); } else if (pmap[loc].inc_cost >= 4 && pmap[loc].cost < 4) { expand_prune(vmap, pmap, loc, T_WATER, to, &explored); } else if ((loc < MAP_WIDTH || loc >= MAP_SIZE - MAP_WIDTH) && pmap[loc].cost > pmap[loc].inc_cost) { expand_prune(vmap, pmap, loc, T_LAND, to, &explored); } else if ((loc < MAP_WIDTH || loc >= MAP_SIZE - MAP_WIDTH) && pmap[loc].inc_cost > pmap[loc].cost) { expand_prune(vmap, pmap, loc, T_WATER, to, &explored); } else { /* copy perimeter cell */ to->list[to->len] = loc; to->len += 1; copied += 1; } } if (copied == from->len) { break; /* nothing expanded */ } SWAP(from, to); } if (game.print_vmap == 'I') { print_xzoom(vmap); } } /* Expand an unexplored cell. We increment the land or water count of each neighbor. Any neighbor that acquires a non-zero count is added to the 'to' perimiter list. The count of explored territory is incremented. Careful: 'loc' may be "off board". */ static void expand_prune(view_map_t *vmap, path_map_t *pmap, loc_t loc, int type, perimeter_t *to, int *explored) { int i; loc_t new_loc; *explored += 1; if (type == T_LAND) { vmap[loc].contents = MAP_LAND; } else { vmap[loc].contents = MAP_SEA; } FOR_ADJ(loc, new_loc, i) if (new_loc >= 0 && new_loc < MAP_SIZE && vmap[new_loc].contents == ' ') { if (!pmap[new_loc].cost && !pmap[new_loc].inc_cost) { to->list[to->len] = new_loc; to->len += 1; } if (type == T_LAND) { pmap[new_loc].cost += 1; } else { pmap[new_loc].inc_cost += 1; } } } /* Find the shortest path from the current location to the destination which passes over valid terrain. We return the destination if a path exists. Otherwise we return the origin. This is similar to 'find_objective' except that we know our destination. */ loc_t vmap_find_dest(path_map_t path_map[], view_map_t vmap[], loc_t cur_loc, loc_t dest_loc, int owner, int terrain) /* cur_loc = current location of piece */ /* dest_loc = destination of piece */ /* owner = owner of piece being moved */ /* terrain = terrain we can cross */ { perimeter_t *from; perimeter_t *to; int cur_cost; int start_terrain; move_info_t move_info; char old_contents; old_contents = vmap[dest_loc].contents; vmap[dest_loc].contents = '%'; /* mark objective */ move_info.city_owner = owner; move_info.objectives = "%"; move_info.weights[0] = 1; from = &p1; to = &p2; if (terrain == T_AIR) { start_terrain = T_LAND; } else { start_terrain = terrain; } start_perimeter(path_map, from, cur_loc, start_terrain); cur_cost = 0; /* cost to reach current perimeter */ for (;;) { to->len = 0; /* nothing in perim yet */ expand_perimeter(path_map, vmap, &move_info, from, terrain, cur_cost, 1, 1, to, to); cur_cost += 1; if (to->len == 0 || best_cost <= cur_cost) { vmap[dest_loc].contents = old_contents; return best_loc; } SWAP(from, to); } } /* Starting with the destination, we recursively back track toward the source marking all cells which are on a shortest path between the start and the destination. To do this, we know the distance from the destination to the start. The destination is on a path. We then find the cells adjacent to the destination and nearest to the source and place them on the path. If we know square P is on the path, then S is on the path if S is adjacent to P, the cost to reach S is less than the cost to reach P, and the cost to move from S to P is the difference in cost between S and P. Someday, this routine should probably use perimeter lists as well. */ void vmap_mark_path(path_map_t *path_map, view_map_t *vmap, loc_t dest) { int n; loc_t new_dest; if (path_map[dest].cost == 0) { return; /* reached end of path */ } if (path_map[dest].terrain == T_PATH) { return; /* already marked */ } path_map[dest].terrain = T_PATH; /* this square is on path */ /* loop to mark adjacent squares on shortest path */ FOR_ADJ(dest, new_dest, n) if (path_map[new_dest].cost == path_map[dest].cost - path_map[dest].inc_cost) { vmap_mark_path(path_map, vmap, new_dest); } } /* Create a marked path map. We mark those squares adjacent to the starting location which are on the board. 'find_dir' must be invoked to decide which squares are actually valid. */ void vmap_mark_adjacent(path_map_t path_map[], loc_t loc) { int i; loc_t new_loc; FOR_ADJ_ON(loc, new_loc, i) path_map[new_loc].terrain = T_PATH; } /* Modify a marked path map. We mark those squares adjacent to the starting location which are on the board and which are adjacent to a location on the existing shortest path. */ void vmap_mark_near_path(path_map_t path_map[], loc_t loc) { int i, j; loc_t new_loc, xloc; int hit_loc[8]; (void)memset((char *)hit_loc, '\0', sizeof(int) * 8); FOR_ADJ_ON(loc, new_loc, i) { FOR_ADJ_ON(new_loc, xloc, j) if (xloc != loc && path_map[xloc].terrain == T_PATH) { hit_loc[i] = 1; break; } } for (i = 0; i < 8; i++) { if (hit_loc[i]) { path_map[loc + dir_offset[i]].terrain = T_PATH; } } } /* Look at each neighbor of 'loc'. Select the first marked cell which is on a short path to the desired destination, and which holds a valid terrain. Note that while this terrain is matched against a 'vmap', it differs slightly from terrains used above. This terrain is the terrain to which we can move immediately, and does not include terrain for which we would have to wait for another piece to move off of. We prefer diagonal moves, and we try to have as many squares as possible containing something in 'adj_char'. For tie-breaking, we prefer moving to cells that are adjacent to as many other squares on the path. This should have a few benefits: 1) Fighters are less likely to be blocked from reaching a city because they stay in the center of the path and increase the number of options for subsequent moves. 2) Transports will approach a city so that as many armies as possible can hop off the tt on one turn to take a valid path toward the city. 3) User pieces will move more intuitively by staying in the center of the best path. */ static int order[] = {NORTHWEST, NORTHEAST, SOUTHWEST, SOUTHEAST, WEST, EAST, NORTH, SOUTH}; loc_t vmap_find_dir(path_map_t path_map[], view_map_t *vmap, loc_t loc, char *terrain, char *adj_char) { int i, count, bestcount; loc_t bestloc; int path_count, bestpath; char *p; if (game.trace_pmap) { print_pzoom("Before vmap_find_dir:", path_map, vmap); } bestcount = -INFINITY; /* no best yet */ bestpath = -1; bestloc = loc; for (i = 0; i < 8; i++) { /* for each adjacent square */ loc_t new_loc = loc + dir_offset[order[i]]; if (path_map[new_loc].terrain == T_PATH) { /* which is on path */ p = strchr(terrain, vmap[new_loc].contents); if (p != NULL) { /* desirable square? */ count = vmap_count_adjacent(vmap, new_loc, adj_char); path_count = vmap_count_path(path_map, new_loc); /* remember best location */ if (count > bestcount || (count == bestcount && path_count > bestpath)) { bestcount = count; bestpath = path_count; bestloc = new_loc; } } } } return (bestloc); } /* Count the number of adjacent squares of interest. Squares are weighted so that the first in the list is the most interesting. */ int vmap_count_adjacent(view_map_t *vmap, loc_t loc, char *adj_char) { int i, count; loc_t new_loc; char *p; int len; len = strlen(adj_char); count = 0; FOR_ADJ_ON(loc, new_loc, i) { p = strchr(adj_char, vmap[new_loc].contents); if (p) { count += 8 * (len - (p - adj_char)); } } return (count); } /* Count the number of adjacent cells that are on the path. */ int vmap_count_path(path_map_t *pmap, loc_t loc) { int i, count; loc_t new_loc; count = 0; FOR_ADJ_ON(loc, new_loc, i) if (pmap[new_loc].terrain == T_PATH) { count += 1; } return (count); } /* See if a location is on the shore. We return true if a surrounding cell contains water and is on the board. */ bool rmap_shore(loc_t loc) { loc_t i, j; FOR_ADJ_ON(loc, j, i) if (game.real_map[j].contents == MAP_SEA) { return (true); } return (false); } bool vmap_shore(view_map_t *vmap, loc_t loc) { loc_t i, j; FOR_ADJ_ON(loc, j, i) if (vmap[j].contents != ' ' && vmap[j].contents != MAP_LAND && game.real_map[j].contents == MAP_SEA) { return (true); } return (false); } /* Return true if a location is surrounded by ocean. Off board locations which cannot be moved to are treated as ocean. */ bool vmap_at_sea(view_map_t *vmap, loc_t loc) { loc_t i, j; if (game.real_map[loc].contents != MAP_SEA) { return (false); } FOR_ADJ_ON(loc, j, i) if (vmap[j].contents == ' ' || vmap[j].contents == MAP_LAND || game.real_map[j].contents != MAP_SEA) { return (false); } return (true); } bool rmap_at_sea(loc_t loc) { loc_t i, j; if (game.real_map[loc].contents != MAP_SEA) { return (false); } FOR_ADJ_ON(loc, j, i) { if (game.real_map[j].contents != MAP_SEA) { return (false); } } return (true); } /* end */ vms-empire-1.18/math.c0000664000175000017500000000375214562174272013013 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* math.c -- various mathematical routines. This file contains routines used to create random integers. The initialization routine 'rndini' should be called at program startup. The flavors of random integers that can be generated are: irand(n) -- returns a random integer in the range 0..n-1 rndint(a,b) -- returns a random integer in the range a..b Other routines include: dist (a, b) -- returns the straight-line distance between two locations. */ #include "empire.h" #include "extern.h" #include #include void rndini(void) { srand((unsigned)(time(0) & 0xFFFF)); } long irand(long high) { if (high < 2) { return (0); } return (rand() % high); } #ifdef __UNUSED__ int rndint(int minp, int maxp) { int size; size = maxp - minp + 1; return ((rand() % size) + minp); } #endif /* Return the distance between two locations. This is simply the max of the absolute differnce between the x and y coordinates. */ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define ABS(a) ((a) < 0 ? -(a) : (a)) int dist(loc_t a, loc_t b) { int ax, ay, bx, by; ax = loc_row(a); ay = loc_col(a); bx = loc_row(b); by = loc_col(b); return (MAX(ABS(ax - bx), ABS(ay - by))); } /* Find the square root of an integer. We actually return the floor of the square root using Newton's method. */ int isqrt(int n) { int guess; ASSERT(n >= 0); /* can't take sqrt of negative number */ if (n <= 1) return (n); /* do easy cases and avoid div by zero */ guess = 2; /* gotta start somewhere */ guess = (guess + n / guess) / 2; guess = (guess + n / guess) / 2; guess = (guess + n / guess) / 2; guess = (guess + n / guess) / 2; guess = (guess + n / guess) / 2; if (guess * guess > n) { guess -= 1; /* take floor */ } return (guess); } vms-empire-1.18/NEWS0000664000175000017500000001475614677575516012440 0ustar esresr vms-empire news 1.18: 2024-10-03:: Port changes for GCC 10. 1.17: 2024-02-11:: Remove unnecessary delays. Fix a defective sort in the fame AI. 1.16: 2021-02-25 20:52:1Z Minor documentation fixes and code cleanup. 1.15: 2020-01-26 01:20:45Z Fix for infinite-fighter-tange bug, New 'J' command to show city production in editing mode. Rehosted on GitLab. 1.14: 2016-01-05 06:49:17Z Add an identifying magic cookie and version to save files. Full ANSI prototypes for private functions as well as public. Markup fix for probability table. 1.13: 2014-12-14T17:06:26Z Comment on entry to and exit from edit mode. Cleaned up packaging and install/uninstall productions. 1.12: 2014-05-20T09:03:09Z Incorporated Dennis Pinckard's fix for a Mac OS/X port bug. Added desktop file. 1.11: 2013-12-31T17:31:04Z Clean up code to modern C, apply cppcheck. Fix code typo reported as Debian bug #593434 1.10: 2013-08-22T23:47:21Z Fix core dump when a satellite bounced off the world edge. Full ANSIfication of function prototypes (which fixed the sat bug - the code appears to have been tickling some obscure bug in the compilation of function calls with K&R-style prototypes). 1.9: 2012-02-16T22:59:22Z Drop RPM packaging. Documentation cleanups. 1.8: 2010-10-21T12:47:33Z Joe Wells's patch to enable different-sized worlds. Minor fixes to map functions by Andrew Morrow. More on the history of the game. Enable arrow keys in edit mode. License changed from Chuck Simmons's cosmetically tweaked clone of the GPL to GPL proper, so the classifier systems that forge and archive sites use will be less confused. 1.7: 2004-09-27T21:53:41Z Documentation fixes. The old empire.doc file has been replaced by the man page. 1.6: 2003-12-29T11:11:53Z Source RPMs can be built by non-root user. 1.5: 2003-12-29T11:06:21Z Cleanup release with new build machinery. 1.4: 2002-08-01T20:04:09Z Packaging tweaks. RPM spec fixes. READ.ME broken up into READ.ME and NEWS. Obsolete bug notes removed. Option -f to set savefile added. I'd like to move the documentation to XML, but can't yet because db2man doesn't render tables. 1.3: 2002-04-19T10:25:43Z Walt Stoneburner's cleanup patch addressing all compiler errors and warnings, both in debug and in production mode; works with GCC v3.0.3. 1.2: 2000-07-28T16:04:26Z The victory-odds table in previous versions was seriously buggy. I folded in corrections from Michael Self. I also took changes from James T. Jordan , who wrote some speedups, added ANSI prototypes, and cleaned up code. 1.1: 1995-12-06T14:02:31 Packaging tweaks. 1.0: 1994-12-01T17:15:43Z I colorized and speed-tuned this and added a save-interval option. The rest of this history is Chuck Simmons's original notes. Because there are so many other Empire games out there now (all descended from this one!), I've reverted to the VMS Empire name. -- Eric S. Raymond History Apparently, this game was originally written outside of Digital, probably at a university. The game was ported to DEC's VAX/VMS from the TOPS-10/20 FORTRAN sources available around fall 1979. Ed James got hold of the sources at Berkeley and converted portions of the code to C, mostly to use curses for the screen handling. He published his modified sources on the net in December 1986. Because this game ran on VMS machines for so long, a previous version is known as VMS Empire. In early 1987 I reverse engineered the program and wrote a version completely written in C. In doing this, I used lots of structures and defined constants, and I attempted to make the code flexible and easy to modify. The algorithms used in this C version are completely new, the names of the commands have been changed to be more mnemonic, and new commands have been implemented. Only the format of the display is the same. I suspect that many of my changes are slower and less intelligently implemented than the originals. Also, I have not implemented some of the original functionality. However, my hope is that the commented C sources I have written will prove far easier to modify and enhance than the original FORTRAN sources. If you make changes for the better, by all means send Ed James and I a copy. The basic game has been heavily modified. I've changed the types of objects built, modified the parameters on others, and added lots of new kinds of movement functions. Read the man page for a complete description. The file 'bugs' contains lots of ideas for enhancements, and describes the bugs I haven't been able to find. Final Notes Unfortunately, I have a rather powerful mainframe at my disposal which is somewhere between 10 and 40 times as fast as a 68020 based computer. This means I can afford to use extremely inefficient algorithms. I suspect that running this program on a smaller machine, such as a Sun workstation or Vax will not be overly rewarding. In particular, the computer will take a very long time to move its pieces, and it may not be desirable to save the game after every move. (You mean your system doesn't write out 1/2 megabyte files in a few milliseconds?) This second problem is easily fixed, but I don't yet have any good ideas for fixing the first problem. The size of a saved file can be easily tuned by reducing the LIST_SIZE constant in empire.h. The only current simple tweak for making the computer move faster is to reduce the size of a map. Chuck Simmons amdahl!chuck Ed James edjames@ic.berkeley.edu ucbvax!edjames The low-end PCs of 2002 are more powerful than Chuck's 1987 mainframe. Performance tuning isn't the issue it once was. :-) My changes enable color on machines with terminfo color support, for a dramatic improvement in appearance and readability of the display. Color support, if present, will be auto-detected at compilation time. They also implement and document a `save-interval' option, addressing one of the misfeatures noted in the bugs file. I've also tweaked the sources so they compile clean under GCC -- they assumed the older K&R model of forward reference, causing many warning references. Finally, I've sped up expand_perimeter by cutting down on the number of array references it has to compute. This eliminates several multiplies from the inner loop, and is a technique that could be applied much more widely in the code. If efficiency matters that much to you, maybe you need to get outside more. Eric S. Raymond esr@thyrsus.com (home page: http://www.catb.org/~esr/) vms-empire-1.18/object.c0000664000175000017500000003424414562167174013333 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* object.c -- routines for manipulating objects. */ #include "empire.h" #include "extern.h" #include #include #include extern int get_piece_name(void); /* Find the nearest city to a location. Return the location of the city and the estimated cost to reach the city. Distances are computed as straight-line distances. */ int find_nearest_city(loc_t loc, int owner, loc_t *city_loc) { loc_t best_loc; long best_dist; long new_dist, i; best_dist = INFINITY; best_loc = loc; for (i = 0; i < NUM_CITY; i++) if (game.city[i].owner == owner) { new_dist = dist(loc, game.city[i].loc); if (new_dist < best_dist) { best_dist = new_dist; best_loc = game.city[i].loc; } } *city_loc = best_loc; return best_dist; } /* Given the location of a city, return the index of that city. */ city_info_t *find_city(loc_t loc) { return (game.real_map[loc].cityp); } /* Return the number of moves an object gets to make. This amount is based on the damage the object has suffered and the number of moves it normally gets to make. The object always gets to make at least one move, assuming it is not dead. Damaged objects move at a fraction of their normal speed. An object which has lost half of its hits moves at half-speed, for example. */ int obj_moves(piece_info_t *obj) { return (piece_attr[obj->type].speed * obj->hits + piece_attr[obj->type].max_hits - 1) /* round up */ / piece_attr[obj->type].max_hits; } /* Figure out the capacity for an object. */ int obj_capacity(piece_info_t *obj) { return (piece_attr[obj->type].capacity * obj->hits + piece_attr[obj->type].max_hits - 1) /* round up */ / piece_attr[obj->type].max_hits; } /* Search for an object of a given type at a location. We scan the list of objects at the given location for one of the given type. */ piece_info_t *find_obj(int type, loc_t loc) { piece_info_t *p; for (p = game.real_map[loc].objp; p != NULL; p = p->loc_link.next) if (p->type == type) return (p); return (NULL); } /* Find a non-full item of the appropriate type at the given location. */ piece_info_t *find_nfull(int type, loc_t loc) { piece_info_t *p; for (p = game.real_map[loc].objp; p != NULL; p = p->loc_link.next) if (p->type == type) { if (obj_capacity(p) > p->count) return (p); } return (NULL); } /* Look around a location for an unfull transport. Return the location of the transport if there is one. */ loc_t find_transport(int owner, loc_t loc) { int i; for (i = 0; i < 8; i++) { /* look around */ loc_t new_loc = loc + dir_offset[i]; piece_info_t *t = find_nfull(TRANSPORT, new_loc); if (t != NULL && t->owner == owner) return (new_loc); } return (loc); /* no tt found */ } /* Search a list of objects at a location for any kind of object. We prefer transports and carriers to other objects. */ piece_info_t *find_obj_at_loc(loc_t loc) { piece_info_t *p, *best; best = game.real_map[loc].objp; if (best == NULL) return (NULL); /* nothing here */ for (p = best->loc_link.next; p != NULL; p = p->loc_link.next) if (p->type > best->type && p->type != SATELLITE) best = p; return (best); } /* If an object is on a ship, remove it from that ship. */ void disembark(piece_info_t *obj) { if (obj->ship) { UNLINK(obj->ship->cargo, obj, cargo_link); obj->ship->count -= 1; obj->ship = NULL; } } /* Move an object onto a ship. */ void embark(piece_info_t *ship, piece_info_t *obj) { obj->ship = ship; LINK(ship->cargo, obj, cargo_link); ship->count += 1; } /* Kill an object. We scan around the piece and free it. If there is anything in the object, it is killed as well. */ void kill_obj(piece_info_t *obj, loc_t loc) { void kill_one(piece_info_t **, piece_info_t *); piece_info_t **list; view_map_t *vmap; vmap = MAP(obj->owner); list = LIST(obj->owner); while (obj->cargo != NULL) /* kill contents */ kill_one(list, obj->cargo); kill_one(list, obj); scan(vmap, loc); /* scan around new location */ } /* kill an object without scanning */ void kill_one(piece_info_t **list, piece_info_t *obj) { UNLINK(list[obj->type], obj, piece_link); /* unlink obj from all lists */ UNLINK(game.real_map[obj->loc].objp, obj, loc_link); disembark(obj); LINK(game.free_list, obj, piece_link); /* return object to free list */ obj->hits = 0; /* let all know this object is dead */ obj->moved = piece_attr[obj->type].speed; /* object has moved */ } /* Kill a city. We kill off all objects in the city and set its type to unowned. We scan around the city's location. */ void kill_city(city_info_t *cityp) { piece_info_t *p; piece_info_t *next_p; piece_info_t **list; /* change ownership of hardware at this location; but not satellites */ for (p = game.real_map[cityp->loc].objp; p; p = next_p) { next_p = p->loc_link.next; if (p->type == ARMY) kill_obj(p, cityp->loc); else if (p->type != SATELLITE) { if (p->type == TRANSPORT) { list = LIST(p->owner); while (p->cargo != NULL) /* kill contents */ kill_one(list, p->cargo); } list = LIST(p->owner); UNLINK(list[p->type], p, piece_link); p->owner = (p->owner == USER ? COMP : USER); list = LIST(p->owner); LINK(list[p->type], p, piece_link); p->func = NOFUNC; } } if (cityp->owner != UNOWNED) { view_map_t *vmap = MAP(cityp->owner); int i; cityp->owner = UNOWNED; cityp->work = 0; cityp->prod = NOPIECE; for (i = 0; i < NUM_OBJECTS; i++) cityp->func[i] = NOFUNC; scan(vmap, cityp->loc); } } /* Produce an item for a city. */ static int sat_dir[4] = {MOVE_NW, MOVE_SW, MOVE_NE, MOVE_SE}; void produce(city_info_t *cityp) { piece_info_t **list; piece_info_t *new; list = LIST(cityp->owner); cityp->work -= piece_attr[(int)cityp->prod].build_time; ASSERT(game.free_list); /* can we allocate? */ new = game.free_list; UNLINK(game.free_list, new, piece_link); LINK(list[(int)cityp->prod], new, piece_link); LINK(game.real_map[cityp->loc].objp, new, loc_link); new->cargo_link.next = NULL; new->cargo_link.prev = NULL; new->loc = cityp->loc; new->func = NOFUNC; new->hits = piece_attr[(int)cityp->prod].max_hits; new->owner = cityp->owner; new->type = cityp->prod; new->moved = 0; new->cargo = NULL; new->ship = NULL; new->count = 0; new->range = piece_attr[(int)cityp->prod].range; if (new->type == SATELLITE) { /* set random move direction */ new->func = sat_dir[irand(4)]; } } /* Move an object to a location. We mark the object moved, we move the object to the new square, and we scan around the object. We also do lots of little maintenance like updating the range of an object, keeping track of the number of pieces on a boat, etc. */ void move_obj(piece_info_t *obj, loc_t new_loc) { view_map_t *vmap; loc_t old_loc; piece_info_t *p; ASSERT(obj->hits); vmap = MAP(obj->owner); old_loc = obj->loc; /* save original location */ obj->moved += 1; obj->loc = new_loc; obj->range--; disembark(obj); /* remove object from any ship */ UNLINK(game.real_map[old_loc].objp, obj, loc_link); LINK(game.real_map[new_loc].objp, obj, loc_link); /* move any objects contained in object */ for (p = obj->cargo; p != NULL; p = p->cargo_link.next) { p->loc = new_loc; UNLINK(game.real_map[old_loc].objp, p, loc_link); LINK(game.real_map[new_loc].objp, p, loc_link); } switch (obj->type) { /* board new ship */ case FIGHTER: if (game.real_map[obj->loc].cityp == NULL) { /* not in a city? */ p = find_nfull(CARRIER, obj->loc); if (p != NULL) embark(p, obj); } break; case ARMY: p = find_nfull(TRANSPORT, obj->loc); if (p != NULL) embark(p, obj); break; } if (obj->type == SATELLITE) scan_sat(vmap, obj->loc); scan(vmap, obj->loc); } /* Move a satellite. It moves according to the preset direction. Satellites bounce off the edge of the board. We start off with some preliminary routines. */ /* Return next direction for a sattellite to travel. */ static loc_t bounce(loc_t loc, loc_t dir1, loc_t dir2, loc_t dir3) { int new_loc; new_loc = loc + dir_offset[MOVE_DIR(dir1)]; if (game.real_map[new_loc].on_board) return dir1; new_loc = loc + dir_offset[MOVE_DIR(dir2)]; if (game.real_map[new_loc].on_board) return dir2; return dir3; } /* Move a satellite one square. */ static void move_sat1(piece_info_t *obj) { int dir; loc_t new_loc; dir = MOVE_DIR(obj->func); new_loc = obj->loc + dir_offset[dir]; if (!game.real_map[new_loc].on_board) { switch (obj->func) { case MOVE_NE: obj->func = bounce(obj->loc, MOVE_NW, MOVE_SE, MOVE_SW); break; case MOVE_NW: obj->func = bounce(obj->loc, MOVE_NE, MOVE_SW, MOVE_SE); break; case MOVE_SE: obj->func = bounce(obj->loc, MOVE_SW, MOVE_NE, MOVE_NW); break; case MOVE_SW: obj->func = bounce(obj->loc, MOVE_SE, MOVE_NW, MOVE_NE); break; default: ABORT; } dir = MOVE_DIR(obj->func); new_loc = obj->loc + dir_offset[dir]; } move_obj(obj, new_loc); } /* Now move the satellite all of its squares. Satellite burns iff it's range reaches zero. */ void move_sat(piece_info_t *obj) { obj->moved = 0; while (obj->moved < obj_moves(obj)) { move_sat1(obj); if (obj->range <= 0) { if (obj->owner == USER) comment("Satellite at %d crashed and burned.", loc_disp(obj->loc)); ksend("Satellite at %d crashed and burned.", loc_disp(obj->loc)); kill_obj(obj, obj->loc); } } } /* Return true if a piece can move to a specified location. We are passed the object and the location. The location must be on the board, and the player's view map must have an appropriate terrain type for the location. Boats may move into port, armies may move onto transports, and fighters may move onto cities or carriers. */ bool good_loc(piece_info_t *obj, loc_t loc) { view_map_t *vmap; piece_info_t *p; if (!game.real_map[loc].on_board) return (false); vmap = MAP(obj->owner); if (strchr(piece_attr[obj->type].terrain, vmap[loc].contents) != NULL) return (true); /* armies can move into unfull transports */ if (obj->type == ARMY) { p = find_nfull(TRANSPORT, loc); return (p != NULL && p->owner == obj->owner); } /* ships and fighters can move into cities */ if (game.real_map[loc].cityp && game.real_map[loc].cityp->owner == obj->owner) return (true); /* fighters can move onto unfull carriers */ if (obj->type == FIGHTER) { p = find_nfull(CARRIER, loc); return (p != NULL && p->owner == obj->owner); } return (false); } void describe_obj(piece_info_t *obj) { char func[STRSIZE]; char other[STRSIZE]; ASSERT(obj != NULL); // cppcheck-suppress nullPointerRedundantCheck if (obj->func >= 0) (void)sprintf(func, "%d", loc_disp(obj->func)); else (void)sprintf(func, func_name[FUNCI(obj->func)]); other[0] = 0; switch (obj->type) { /* set other information */ case FIGHTER: (void)sprintf(other, "; range = %d", obj->range); break; case TRANSPORT: (void)sprintf(other, "; armies = %d", obj->count); break; case CARRIER: (void)sprintf(other, "; fighters = %d", obj->count); break; } prompt("%s at %d: moves = %d; hits = %d; func = %s%s", piece_attr[obj->type].name, loc_disp(obj->loc), obj_moves(obj) - obj->moved, obj->hits, func, other); } /* Scan around a location to update a player's view of the world. For each surrounding cell, we remember the date the cell was examined, and the contents of the cell. Notice how we carefully update the cell to first reflect land, water, or city, then army or fighter, then boat, and finally city owner. This guarantees that the object we want to display will appear on top. */ void scan(view_map_t vmap[], loc_t loc) { void update(view_map_t[], loc_t), check(void); int i; #ifdef DEBUG check(); /* perform a consistency check */ #endif ASSERT(game.real_map[loc].on_board); /* passed loc must be on board */ for (i = 0; i < 8; i++) { /* for each surrounding cell */ loc_t xloc = loc + dir_offset[i]; update(vmap, xloc); } update(vmap, loc); /* update current location as well */ } /* Scan a portion of the board for a satellite. */ void scan_sat(view_map_t vmap[], loc_t loc) { int i; ASSERT(game.real_map[loc].on_board); for (i = 0; i < 8; i++) { /* for each surrounding cell */ loc_t xloc = loc + 2 * dir_offset[i]; if (xloc >= 0 && xloc < MAP_SIZE && game.real_map[xloc].on_board) scan(vmap, xloc); } scan(vmap, loc); } /* Update a location. We set the date seen, the land type, object contents starting with armies, then fighters, then boats, and the city type. */ char city_char[] = {MAP_CITY, 'O', 'X'}; void update(view_map_t vmap[], loc_t loc) { vmap[loc].seen = game.date; if (game.real_map[loc].cityp) /* is there a city here? */ vmap[loc].contents = city_char[game.real_map[loc].cityp->owner]; else { piece_info_t *p = find_obj_at_loc(loc); if (p == NULL) /* nothing here? */ vmap[loc].contents = game.real_map[loc].contents; else if (p->owner == USER) vmap[loc].contents = piece_attr[p->type].sname; else vmap[loc].contents = tolower(piece_attr[p->type].sname); } if (vmap == game.comp_map) display_locx(COMP, game.comp_map, loc); else if (vmap == game.user_map) display_locx(USER, game.user_map, loc); } /* Set the production for a city. We make sure the city is displayed on the screen, and we ask the user for the new production. We keep asking until we get a valid answer. */ void set_prod(city_info_t *cityp) { ASSERT(cityp != NULL); // cppcheck-suppress nullPointerRedundantCheck scan(game.user_map, cityp->loc); // cppcheck-suppress nullPointerRedundantCheck display_loc_u(cityp->loc); for (;;) { int i; prompt("What do you want the city at %d to produce? ", // cppcheck-suppress nullPointerRedundantCheck loc_disp(cityp->loc)); i = get_piece_name(); if (i == NOPIECE) error("I don't know how to build those."); else { cityp->prod = i; cityp->work = -(piece_attr[i].build_time / 5); return; } } } /* Get the name of a type of object. */ int get_piece_name(void) { char c; int i; c = get_chx(); /* get the answer */ for (i = 0; i < NUM_OBJECTS; i++) if (piece_attr[i].sname == c) { return i; } return NOPIECE; } /* end */ vms-empire-1.18/README0000644000175000017500000000144612341351662012563 0ustar esresr README for 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. 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. The files HACKING and BUGS will be of interest if you intend to modify this code. The ancestral code was by Chuck Simmons . Since around 1990 it has been maintained by Eric S. Raymond vms-empire-1.18/term.c0000664000175000017500000001654214562175610013027 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* term.c -- this file contains various routines used to control the user communications area of the terminal. This area consists of the top 3 lines of the terminal where messages are displayed to the user and input is acquired from the user. There are two types of output in this area. One type is interactive output. This consists of a prompt line and an error message line. The other type of output is informational output. The user must be given time to read informational output. Whenever input is received, the top three lines are cleared and the screen refreshed as the user has had time to read these lines. We also clear the 'need_delay' flag, saying that the user has read the information on the screen. When information is to be displayed, if the 'need_delay' flag is set, we refresh the screen and pause momentarily to give the user a chance to read the lines. The new information is then displayed, and the 'need_delay' flag is set. */ #include #include #include #include #include #include #include "empire.h" #include "extern.h" static bool need_delay; static FILE *my_stream; /* Here are routines that handle printing to the top few lines of the screen. 'topini' should be called at initialization, and whenever we finish printing information to the screen. */ void topini(void) { info("", "", ""); } /* Write a message to one of the top lines. */ static void vtopmsg(int line, const char *fmt, va_list varglist) /* assemble command in printf(3) style, print to a top line */ { char junkbuf[STRSIZE]; if (line < 1 || line > NUMTOPS) { line = 1; } (void)move(line - 1, 0); vsnprintf(junkbuf, sizeof(junkbuf), fmt, varglist); (void)addstr(junkbuf); (void)clrtoeol(); } void topmsg(int line, char *fmt, ...) { va_list ap; va_start(ap, fmt); vtopmsg(line, fmt, ap); va_end(ap); } /* Print a prompt on the first message line. */ void prompt(char *fmt, ...) { va_list ap; va_start(ap, fmt); vtopmsg(1, fmt, ap); va_end(ap); } /* Print an error message on the second message line. */ void error(char *fmt, ...) { va_list ap; va_start(ap, fmt); vtopmsg(2, fmt, ap); va_end(ap); } /* Print out extra information. */ void extra(char *fmt, ...) { va_list ap; va_start(ap, fmt); vtopmsg(3, fmt, ap); va_end(ap); } /* Print out a generic error message. */ void huh(void) { error("Type H for Help."); } /* Display information on the screen. If the 'need_delay' flag is set, we force a delay, then print the information. After we print the information, we set the need_delay flag. */ void info(char *a, char *b, char *c) { if (need_delay) { delay(); } topmsg(1, a); topmsg(2, b); topmsg(3, c); need_delay = (strlen(a) || strlen(b) || strlen(c)); } void set_need_delay(void) { need_delay = true; } void comment(char *fmt, ...) { va_list ap; va_start(ap, fmt); if (need_delay) { delay(); } topmsg(1, ""); topmsg(2, ""); vtopmsg(3, fmt, ap); need_delay = (fmt != 0); va_end(ap); } void pdebug(char *fmt, ...) { va_list ap; if (!game.print_debug) { return; } va_start(ap, fmt); if (need_delay) { delay(); } topmsg(1, ""); topmsg(2, ""); vtopmsg(3, fmt, ap); need_delay = (fmt != 0); va_end(ap); } /* kermyt begin */ void vksend(const char *fmt, va_list varglist) { if (!(my_stream = fopen("info_list.txt", "a"))) { error("Cannot open info_list.txt"); return; } vfprintf(my_stream, fmt, varglist); fclose(my_stream); return; } void ksend(char *fmt, ...) { va_list ap; va_start(ap, fmt); vksend(fmt, ap); va_end(ap); } /* kermyt end */ /* Get a string from the user, echoing characters all the while. */ void get_str(char *buf, int sizep) { (void)echo(); get_strq(buf, sizep); (void)noecho(); } /* Get a string from the user, ignoring the current echo mode. */ void get_strq(char *buf, int sizep) { (void)nocrmode(); (void)refresh(); (void)getnstr(buf, sizep); need_delay = false; info("", "", ""); (void)crmode(); } /* Get a character from the user and convert it to uppercase. */ char get_chx(void) { char c; c = get_cq(); if (islower(c)) { return (toupper(c)); } else { return (c); } } /* Input an integer from the user. */ int getint(char *message) { char buf[STRSIZE]; char *p; for (;;) { /* until we get a legal number */ prompt(message, 0, 0, 0, 0, 0, 0, 0, 0); get_str(buf, sizeof(buf)); for (p = buf; *p; p++) { if (*p < '0' || *p > '9') { error("Please enter an integer.", 0, 0, 0, 0, 0, 0, 0, 0); break; } } if (*p == 0) { /* no error yet? */ if (p - buf > 7) /* too many digits? */ { error("Please enter a small integer.", 0, 0, 0, 0, 0, 0, 0, 0); } else { return (atoi(buf)); } } } } /* Input a character from the user with echoing. */ char get_c(void) { char c; /* one char and a null */ (void)echo(); c = get_cq(); (void)noecho(); return (c); } /* Input a character quietly. */ char get_cq(void) { char c; (void)crmode(); (void)refresh(); c = getch(); topini(); /* clear information lines */ (void)nocrmode(); return (c); } /* Input a yes or no response from the user. We loop until we get a valid response. We return true iff the user replies 'y'. */ bool getyn(char *message) { for (;;) { char c; prompt(message, 0, 0, 0, 0, 0, 0, 0, 0); c = get_chx(); if (c == 'Y') { return (true); } if (c == 'N') { return (false); } error("Please answer Y or N.", 0, 0, 0, 0, 0, 0, 0, 0); } } /* Input an integer in a range. */ int get_range(char *message, int low, int high) { for (;;) { int result = getint(message); if (result >= low && result <= high) { return (result); } error("Please enter an integer in the range %d..%d.", low, high); } } /* Print a screen of help information. */ void help(char **text, int nlines) { int i, r, c; int text_lines; text_lines = (nlines + 1) / 2; /* lines of text */ clear_screen(); pos_str(NUMTOPS, 1, text[0]); /* mode */ pos_str(NUMTOPS, 41, "See empire(6) for more information."); for (i = 1; i < nlines; i++) { if (i > text_lines) { pos_str(i - text_lines + NUMTOPS + 1, 41, text[i]); } else { pos_str(i + NUMTOPS + 1, 1, text[i]); } } pos_str(text_lines + NUMTOPS + 2, 1, "--Piece---Yours-Enemy-Moves-Hits-Cost"); pos_str(text_lines + NUMTOPS + 2, 41, "--Piece---Yours-Enemy-Moves-Hits-Cost"); for (i = 0; i < NUM_OBJECTS; i++) { if (i >= (NUM_OBJECTS + 1) / 2) { r = i - (NUM_OBJECTS + 1) / 2; c = 41; } else { r = i; c = 1; } pos_str(r + text_lines + NUMTOPS + 3, c, "%-12s%c %c%6d%5d%6d", piece_attr[i].nickname, piece_attr[i].sname, tolower(piece_attr[i].sname), piece_attr[i].speed, piece_attr[i].max_hits, piece_attr[i].build_time, 0, 0); // FLAG } (void)refresh(); } #define COL_DIGITS \ ((MAP_WIDTH <= 100) ? 2 : ((MAP_WIDTH <= 1000 ? 3 : (1 / 0)))) int loc_disp(int loc) { int row = loc / MAP_WIDTH; int nrow = row; int col = loc % MAP_WIDTH; ASSERT(loc == (row * MAP_WIDTH) + col); int i; // cppcheck-suppress zerodiv for (i = COL_DIGITS; i > 0; i--) { nrow *= 10; } move(LINES - 1, 0); return nrow + col; } /* end */ vms-empire-1.18/usermove.c0000664000175000017500000006653614562175610013735 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* usermove.c -- Let the user move her troops. */ #include "empire.h" #include "extern.h" #include #include #include void fatal(piece_info_t *obj, loc_t loc, char *message, char *response); void move_to_dest(piece_info_t *obj, loc_t dest); void move_army_to_city(piece_info_t *obj, loc_t city_loc); bool awake(piece_info_t *obj); extern int get_piece_name(void); void user_move(void) { void piece_move(piece_info_t *); int i, j, sec_start; piece_info_t *obj, *next_obj; int prod; /* First we loop through objects to update the user's view of the world and perform any other necessary processing. We would like to have the world view up to date before asking the user any questions. This means that we should also scan through all cities before possibly asking the user what to produce in each city. */ for (i = 0; i < NUM_OBJECTS; i++) { for (obj = game.user_obj[i]; obj != NULL; obj = obj->piece_link.next) { obj->moved = 0; /* nothing moved yet */ scan(game.user_map, obj->loc); /* refresh user's view of world */ } } /* produce new hardware */ for (i = 0; i < NUM_CITY; i++) { if (game.city[i].owner == USER) { scan(game.user_map, game.city[i].loc); prod = game.city[i].prod; if (prod == NOPIECE) { /* need production? */ set_prod(&(game.city[i])); /* ask user what to produce */ } else if (game.city[i].work++ >= (long)piece_attr[prod].build_time) { /* kermyt begin */ ksend("%s has been completed at city %d.\n", piece_attr[prod].article, loc_disp(game.city[i].loc)); /* kermyt end */ comment("%s has been completed at city %d.\n", piece_attr[prod].article, loc_disp(game.city[i].loc)); produce(&game.city[i]); /* produce should set object.moved to 0 */ } } } /* move all satellites */ for (obj = game.user_obj[SATELLITE]; obj != NULL; obj = next_obj) { next_obj = obj->piece_link.next; move_sat(obj); } sec_start = cur_sector(); /* get currently displayed sector */ if (sec_start == -1) { sec_start = 0; } /* loop through sectors, moving every piece in the sector */ for (i = sec_start; i < sec_start + NUM_SECTORS; i++) { int sec = i % NUM_SECTORS; sector_change(); /* allow screen to be redrawn */ for (j = 0; j < NUM_OBJECTS; j++) { /* loop through obj lists */ for (obj = game.user_obj[move_order[j]]; obj != NULL; obj = next_obj) { /* loop through objs in list */ next_obj = obj->piece_link.next; if (!obj->moved) { /* object not moved yet? */ if (loc_sector(obj->loc) == sec) { /* object in sector? */ piece_move( obj); /* yup; move the object */ } } } } if (cur_sector() == sec) { /* is sector displayed? */ print_sector_u(sec); /* make screen up-to-date */ redisplay(); /* show it to the user */ } } if (game.save_movie) { save_movie_screen(); } } /* Move a piece. We loop until all the moves of a piece are made. Within the loop, we first awaken the piece if it is adjacent to an enemy piece. Then we attempt to handle any preprogrammed function for the piece. If the piece has not moved after this, we ask the user what to do. */ void piece_move(piece_info_t *obj) { void move_random(piece_info_t *), move_fill(piece_info_t *); void move_land(piece_info_t *), move_explore(piece_info_t *); void move_path(piece_info_t *), move_dir(piece_info_t *); void move_armyload(piece_info_t *), ask_user(piece_info_t *); void move_armyattack(piece_info_t *), move_ttload(piece_info_t *); void move_repair(piece_info_t *), move_transport(piece_info_t *); bool changed_loc; int speed, max_hits; bool need_input; city_info_t *cityp; /* set func for piece if on city */ cityp = find_city(obj->loc); if (cityp != NULL) { if (cityp->func[obj->type] != NOFUNC) { obj->func = cityp->func[obj->type]; } } changed_loc = false; /* not changed yet */ speed = piece_attr[obj->type].speed; max_hits = piece_attr[obj->type].max_hits; need_input = false; /* don't require user input yet */ while (obj->moved < obj_moves(obj)) { int saved_moves = obj->moved; /* save moves made */ loc_t saved_loc = obj->loc; /* remember starting location */ if (awake(obj) || need_input) { /* need user input? */ ask_user(obj); topini(); /* clear info lines */ display_loc_u(obj->loc); /* let user see result */ (void)redisplay(); need_input = false; /* we got it */ } if (obj->moved == saved_moves) { /* user set function? */ switch (obj->func) { /* handle preprogrammed function */ case NOFUNC: break; case RANDOM: move_random(obj); break; case SENTRY: obj->moved = speed; break; case FILL: move_fill(obj); break; case LAND: move_land(obj); break; case EXPLORE: move_explore(obj); break; case ARMYLOAD: move_armyload(obj); break; case ARMYATTACK: move_armyattack(obj); break; case TTLOAD: move_ttload(obj); break; case REPAIR: move_repair(obj); break; case WFTRANSPORT: move_transport(obj); break; case MOVE_N: case MOVE_NE: case MOVE_E: case MOVE_SE: case MOVE_S: case MOVE_SW: case MOVE_W: case MOVE_NW: move_dir(obj); break; default: move_path(obj); break; } } if (obj->moved == saved_moves) { need_input = true; } /* handle fighters specially. If in a city or carrier, turn is over and reset range to max. Otherwise, if range = 0, fighter crashes and burns and turn is over. */ if (obj->type == FIGHTER && obj->hits > 0) { if ((game.user_map[obj->loc].contents == 'O' || game.user_map[obj->loc].contents == 'C') && obj->moved > 0) { obj->range = piece_attr[FIGHTER].range; obj->moved = speed; obj->func = NOFUNC; comment("Landing confirmed."); } /* range can be less than zero if fighter survives many attacks. It loses 1 unit of range on every attack it survives. */ else if (obj->range <= 0) { comment("Fighter at %d crashed and burned.", loc_disp(obj->loc)); kill_obj(obj, obj->loc); } } if (saved_loc != obj->loc) { changed_loc = true; } } /* if a boat is in port, damaged, and never moved, fix some damage */ if (obj->hits > 0 /* still alive? */ && !changed_loc /* object never changed location? */ && obj->type != ARMY && obj->type != FIGHTER /* it is a boat? */ && obj->hits < max_hits /* it is damaged? */ && game.user_map[obj->loc].contents == 'O') { /* it is in port? */ obj->hits++; /* fix some damage */ } } /* Move a piece at random. We create a list of empty squares to which the piece can move. If there are none, we do nothing, otherwise we move the piece to a random adjacent square. */ void move_random(piece_info_t *obj) { loc_t loc_list[8]; int i, nloc; nloc = 0; for (i = 0; i < 8; i++) { loc_t loc = obj->loc + dir_offset[i]; if (good_loc(obj, loc)) { loc_list[nloc] = loc; /* remember this location */ nloc++; /* count locations we can move to */ } } if (nloc == 0) { return; /* no legal move */ } i = irand((long)nloc - 1); /* choose random direction */ move_obj(obj, loc_list[i]); /* move the piece */ } /* Have a piece explore. We look for the nearest unexplored territory which the piece can reach and have to piece move toward the territory. */ void move_explore(piece_info_t *obj) { path_map_t path_map[MAP_SIZE]; loc_t loc; char *terrain; switch (obj->type) { case ARMY: loc = vmap_find_lobj(path_map, game.user_map, obj->loc, &user_army); terrain = "+"; break; case FIGHTER: loc = vmap_find_aobj(path_map, game.user_map, obj->loc, &user_fighter); terrain = "+.O"; break; default: loc = vmap_find_wobj(path_map, game.user_map, obj->loc, &user_ship); terrain = ".O"; break; } if (loc == obj->loc) { return; /* nothing to explore */ } if (game.user_map[loc].contents == ' ' && path_map[loc].cost == 2) { vmap_mark_adjacent(path_map, obj->loc); } else { vmap_mark_path(path_map, game.user_map, loc); } loc = vmap_find_dir(path_map, game.user_map, obj->loc, terrain, " "); if (loc != obj->loc) { move_obj(obj, loc); } } /* Move an army onto a transport when it arrives. We scan around the army to find a non-full transport. If one is present, we move the army to the transport and waken the army. */ void move_transport(piece_info_t *obj) { loc_t loc; /* look for an adjacent transport */ loc = find_transport(USER, obj->loc); if (loc != obj->loc) { move_obj(obj, loc); obj->func = NOFUNC; } else { obj->moved = piece_attr[obj->type].speed; } } /* Move an army toward the nearest loading transport. If there is an adjacent transport, move the army onto the transport, and awaken the army. */ static view_map_t amap[MAP_SIZE]; void move_armyload(piece_info_t *obj) { loc_t loc; piece_info_t *p; ABORT; /* look for an adjacent transport */ loc = find_transport(USER, obj->loc); if (loc != obj->loc) { move_obj(obj, loc); obj->func = NOFUNC; } else { /* look for nearest non-full transport */ int i; (void)memcpy(amap, game.user_map, sizeof(view_map_t) * MAP_SIZE); /* mark loading transports or cities building transports */ for (p = game.user_obj[TRANSPORT]; p; p = p->piece_link.next) { if (p->count < obj_capacity(p)) { /* not full? */ amap[p->loc].contents = '$'; } } for (i = 0; i < NUM_CITY; i++) { if (game.city[i].owner == USER && game.city[i].prod == TRANSPORT) { amap[game.city[i].loc].contents = '$'; } } } } /* Move an army toward an attackable city or enemy army. */ void move_armyattack(piece_info_t *obj) { path_map_t path_map[MAP_SIZE]; loc_t loc; ASSERT(obj->type == ARMY); loc = vmap_find_lobj(path_map, game.user_map, obj->loc, &user_army_attack); if (loc == obj->loc) { return; /* nothing to attack */ } vmap_mark_path(path_map, game.user_map, loc); loc = vmap_find_dir(path_map, game.user_map, obj->loc, "+", "X*a"); if (loc != obj->loc) { move_obj(obj, loc); } } void move_ttload(piece_info_t *obj) { ABORT; } /* Move a ship toward port. If the ship is healthy, wake it up. */ void move_repair(piece_info_t *obj) { path_map_t path_map[MAP_SIZE]; loc_t loc; ASSERT(obj->type > FIGHTER); if (obj->hits == piece_attr[obj->type].max_hits) { obj->func = NOFUNC; return; } if (game.user_map[obj->loc].contents == 'O') { /* it is in port? */ obj->moved += 1; return; } loc = vmap_find_wobj(path_map, game.user_map, obj->loc, &user_ship_repair); if (loc == obj->loc) { return; /* no reachable city */ } vmap_mark_path(path_map, game.user_map, loc); /* try to be next to ocean to avoid enemy pieces */ loc = vmap_find_dir(path_map, game.user_map, obj->loc, ".O", "."); if (loc != obj->loc) { move_obj(obj, loc); } } /* Here we have a transport or carrier waiting to be filled. If the object is not full, we set the move count to its maximum value. Otherwise we awaken the object. */ void move_fill(piece_info_t *obj) { if (obj->count == obj_capacity(obj)) { /* full? */ obj->func = NOFUNC; /* awaken full boat */ } else { obj->moved = piece_attr[obj->type].speed; } } /* Here we have a piece that wants to land at the nearest carrier or owned city. We scan through the lists of cities and carriers looking for the closest one. We then move toward that item's location. The nearest landing field must be within the object's range. */ void move_land(piece_info_t *obj) { long best_dist; loc_t best_loc; piece_info_t *p; best_dist = find_nearest_city(obj->loc, USER, &best_loc); for (p = game.user_obj[CARRIER]; p != NULL; p = p->piece_link.next) { long new_dist = dist(obj->loc, p->loc); if (new_dist < best_dist) { best_dist = new_dist; best_loc = p->loc; } } if (best_dist == 0) { obj->moved += 1; /* fighter is on a city */ } else if (best_dist <= obj->range) { move_to_dest(obj, best_loc); } else { obj->func = NOFUNC; /* can't reach city or carrier */ } } /* Move a piece in the specified direction if possible. If the object is a fighter which has travelled for half its range, we wake it up. */ void move_dir(piece_info_t *obj) { loc_t loc; int dir; dir = MOVE_DIR(obj->func); loc = obj->loc + dir_offset[dir]; if (good_loc(obj, loc)) { move_obj(obj, loc); } } /* Move a piece toward a specified destination if possible. For each direction, we see if moving in that direction would bring us closer to our destination, and if there is nothing in the way. If so, we move in the first direction we find. */ void move_path(piece_info_t *obj) { if (obj->loc == obj->func) { obj->func = NOFUNC; } else { move_to_dest(obj, obj->func); } } /* Move a piece toward a specific destination. We first map out the paths to the destination, if we can't get there, we return. Then we mark the paths to the destination. Then we choose a move. */ void move_to_dest(piece_info_t *obj, loc_t dest) { path_map_t path_map[MAP_SIZE]; int fterrain; char *mterrain; loc_t new_loc; switch (obj->type) { case ARMY: fterrain = T_LAND; mterrain = "+"; break; case FIGHTER: fterrain = T_AIR; mterrain = "+.O"; break; default: fterrain = T_WATER; mterrain = ".O"; break; } new_loc = vmap_find_dest(path_map, game.user_map, obj->loc, dest, USER, fterrain); if (new_loc == obj->loc) { return; /* can't get there */ } vmap_mark_path(path_map, game.user_map, dest); new_loc = vmap_find_dir(path_map, game.user_map, obj->loc, mterrain, " ."); if (new_loc == obj->loc) { return; /* can't move ahead */ } ASSERT(good_loc(obj, new_loc)); move_obj(obj, new_loc); /* everything looks good */ } /* Ask the user to move her piece. */ void ask_user(piece_info_t *obj) { void user_skip(piece_info_t *), user_fill(piece_info_t *); void user_dir(piece_info_t *, int), user_set_dir(piece_info_t *); void user_wake(piece_info_t *), user_set_city_func(piece_info_t *); void user_cancel_auto(void), user_redraw(void); void user_random(piece_info_t *), user_land(piece_info_t *); void user_sentry(piece_info_t *), user_help(void); void reset_func(piece_info_t *), user_explore(piece_info_t *); void user_build(piece_info_t *), user_transport(piece_info_t *); void user_armyattack(piece_info_t *), user_repair(piece_info_t *); for (;;) { char c; display_loc_u(obj->loc); /* display piece to move */ describe_obj(obj); /* describe object to be moved */ display_score(); /* show current score */ display_loc_u(obj->loc); /* reposition cursor */ c = get_chx(); /* get command from user (no echo) */ switch (c) { case 'Q': user_dir(obj, NORTHWEST); return; case 'W': user_dir(obj, NORTH); return; case 'E': user_dir(obj, NORTHEAST); return; case 'D': user_dir(obj, EAST); return; case 'C': user_dir(obj, SOUTHEAST); return; case 'X': user_dir(obj, SOUTH); return; case 'Z': user_dir(obj, SOUTHWEST); return; case 'A': user_dir(obj, WEST); return; case 'J': edit(obj->loc); reset_func(obj); return; case 'V': user_set_city_func(obj); reset_func(obj); return; case ' ': user_skip(obj); return; case 'F': user_fill(obj); return; case 'I': user_set_dir(obj); return; case 'R': user_random(obj); return; case 'S': user_sentry(obj); return; case 'L': user_land(obj); return; case 'G': user_explore(obj); return; case 'T': user_transport(obj); return; case 'U': user_repair(obj); return; case 'Y': user_armyattack(obj); return; case 'B': user_build(obj); break; case 'H': user_help(); break; case 'K': user_wake(obj); break; case 'O': user_cancel_auto(); break; case '\014': case 'P': user_redraw(); break; case '?': describe_obj(obj); break; default: complain(); } } } /* Here, if the passed object is on a city, we assign the city's function to the object. However, we then awaken the object if necessary because the user only changed the city function, and did not tell us what to do with the object. */ void reset_func(piece_info_t *obj) { city_info_t *cityp; cityp = find_city(obj->loc); if (cityp != NULL) { if (cityp->func[obj->type] != NOFUNC) { obj->func = cityp->func[obj->type]; (void)awake(obj); } } } /* Increment the number of moves a piece has used. If the piece is an army and the army is in a city, move the army to the city. */ void user_skip(piece_info_t *obj) { if (obj->type == ARMY && game.user_map[obj->loc].contents == 'O') { move_army_to_city(obj, obj->loc); } else { obj->moved++; } } /* Put an object in FILL mode. The object must be either a transport or carrier. If not, we beep at the user. */ void user_fill(piece_info_t *obj) { if (obj->type != TRANSPORT && obj->type != CARRIER) { complain(); } else { obj->func = FILL; } } /* Print out help information. */ void user_help(void) { help(help_user, user_lines); prompt("Press any key to continue: "); (void)get_chx(); } /* Set an object's function to move in a certain direction. */ void user_set_dir(piece_info_t *obj) { char c; c = get_chx(); switch (c) { case 'Q': obj->func = MOVE_NW; break; case 'W': obj->func = MOVE_N; break; case 'E': obj->func = MOVE_NE; break; case 'D': obj->func = MOVE_E; break; case 'C': obj->func = MOVE_SE; break; case 'X': obj->func = MOVE_S; break; case 'Z': obj->func = MOVE_SW; break; case 'A': obj->func = MOVE_W; break; default: complain(); break; } } /* Wake up the current piece. */ void user_wake(piece_info_t *obj) { obj->func = NOFUNC; } /* Set the piece's func to random. */ void user_random(piece_info_t *obj) { obj->func = RANDOM; } /* Set a piece's function to sentry. */ void user_sentry(piece_info_t *obj) { obj->func = SENTRY; } /* Set a fighter's function to land at the nearest city. */ void user_land(piece_info_t *obj) { if (obj->type != FIGHTER) { complain(); } else { obj->func = LAND; } } /* Set the piece's func to explore. */ void user_explore(piece_info_t *obj) { obj->func = EXPLORE; } /* Set an army's function to WFTRANSPORT. */ void user_transport(piece_info_t *obj) { if (obj->type != ARMY) { complain(); } else { obj->func = WFTRANSPORT; } } /* Set an army's function to ARMYATTACK. */ void user_armyattack(piece_info_t *obj) { if (obj->type != ARMY) { complain(); } else { obj->func = ARMYATTACK; } } /* Set a ship's function to REPAIR. */ void user_repair(piece_info_t *obj) { if (obj->type == ARMY || obj->type == FIGHTER) { complain(); } else { obj->func = REPAIR; } } /* Set a city's function. */ void user_set_city_func(piece_info_t *obj) { void e_city_fill(city_info_t *, int); void e_city_explore(city_info_t *, int); void e_city_stasis(city_info_t *, int); void e_city_wake(city_info_t *, int); void e_city_random(city_info_t *, int); void e_city_repair(city_info_t *, int); void e_city_attack(city_info_t *, int); int type; char e; city_info_t *cityp; cityp = find_city(obj->loc); if (!cityp || cityp->owner != USER) { complain(); return; } type = get_piece_name(); if (type == NOPIECE) { complain(); return; } e = get_chx(); switch (e) { case 'F': /* fill */ e_city_fill(cityp, type); break; case 'G': /* explore */ e_city_explore(cityp, type); break; case 'I': /* directional stasis */ e_city_stasis(cityp, type); break; case 'K': /* turn off function */ e_city_wake(cityp, type); break; case 'R': /* make piece move randomly */ e_city_random(cityp, type); break; case 'U': /* repair ship */ e_city_repair(cityp, type); break; case 'Y': /* set army func to attack */ e_city_attack(cityp, type); break; default: /* bad command? */ complain(); break; } } /* Change a city's production. */ void user_build(piece_info_t *obj) { city_info_t *cityp; if (game.user_map[obj->loc].contents != 'O') { /* no user city here? */ complain(); return; } cityp = find_city(obj->loc); ASSERT(cityp != NULL); set_prod(cityp); } /* Move a piece in the direction specified by the user. This routine handles attacking objects. */ void user_dir(piece_info_t *obj, int dir) { void user_dir_army(piece_info_t *, loc_t); void user_dir_fighter(piece_info_t *, loc_t); void user_dir_ship(piece_info_t *, loc_t); loc_t loc; loc = obj->loc + dir_offset[dir]; if (good_loc(obj, loc)) { move_obj(obj, loc); return; } if (!game.real_map[loc].on_board) { error("You cannot move to the edge of the world."); delay(); return; } switch (obj->type) { case ARMY: user_dir_army(obj, loc); break; case FIGHTER: user_dir_fighter(obj, loc); break; default: user_dir_ship(obj, loc); break; } } /* We have an army that wants to attack something or move onto some unreasonable terrain. We check for errors, question the user if necessary, and attack if necessary. */ void user_dir_army(piece_info_t *obj, loc_t loc) { if (game.user_map[loc].contents == 'O') { /* attacking own city */ move_army_to_city(obj, loc); } else if (game.user_map[loc].contents == 'T') { /* transport full? */ fatal(obj, loc, "Sorry, sir. There is no more room on the transport. " "Do you insist? ", "Your army jumped into the briny and drowned."); } else if (game.real_map[loc].contents == MAP_SEA) { /* going for a swim? */ bool enemy_killed = false; if (!getyn(/* thanks to Craig Hansen for this next message */ "Troops can't walk on water, sir. Do you really " "want to go to sea? ")) { return; } if (game.user_map[obj->loc].contents == 'T') { comment("Your army jumped into the briny and drowned."); ksend("Your army jumped into the briny and drowned.\n"); } else if (game.user_map[loc].contents == MAP_SEA) { comment("Your army marched dutifully into the sea and " "drowned."); ksend("Your army marched dutifully into the sea and " "drowned.\n"); } else { /* attack something at sea */ enemy_killed = islower(game.user_map[loc].contents); attack(obj, loc); if (obj->hits > 0) /* ship won? */ { comment("Your army regretfully drowns after " "its successful assault."); ksend("Your army regretfully drowns after its " "successful assault."); } } if (obj->hits > 0) { kill_obj(obj, loc); if (enemy_killed) { scan(game.comp_map, loc); } } } else if (isupper(game.user_map[loc].contents) && game.user_map[loc].contents != 'X') { /* attacking self */ if (!getyn("Sir, those are our men! Do you really want to " "attack them? ")) { return; } attack(obj, loc); } else { attack(obj, loc); } } /* Here we have a fighter wanting to attack something. There are only three cases: attacking a city, attacking ourself, attacking the enemy. */ void user_dir_fighter(piece_info_t *obj, loc_t loc) { if (game.real_map[loc].contents == MAP_CITY) { fatal(obj, loc, "That's never worked before, sir. Do you really want to " "try? ", "Your fighter was shot down."); } else if (isupper(game.user_map[loc].contents)) { if (!getyn("Sir, those are our men! " "Do you really want to attack them? ")) { return; } attack(obj, loc); } else { attack(obj, loc); } } /* Here we have a ship attacking something, or trying to move on shore. Our cases are: moving ashore (and subcases), attacking a city, attacking self, attacking enemy. */ void user_dir_ship(piece_info_t *obj, loc_t loc) { if (game.real_map[loc].contents == MAP_CITY) { (void)sprintf(game.jnkbuf, "Your %s broke up on shore.", piece_attr[obj->type].name); fatal(obj, loc, "That's never worked before, sir. Do you really want to " "try? ", game.jnkbuf); } else if (game.real_map[loc].contents == MAP_LAND) { /* moving ashore? */ bool enemy_killed = false; if (!getyn("Ships need sea to float, sir. Do you really want " "to go ashore? ")) { return; } if (game.user_map[loc].contents == MAP_LAND) { comment("Your %s broke up on shore.", piece_attr[obj->type].name); ksend("Your %s broke up on shore.", piece_attr[obj->type].name); } else { /* attack something on shore */ enemy_killed = islower(game.user_map[loc].contents); attack(obj, loc); if (obj->hits > 0) /* ship won? */ { comment("Your %s breaks up after its " "successful assault.", piece_attr[obj->type].name); ksend("Your %s breaks up after its successful " "assault.", piece_attr[obj->type].name); } } if (obj->hits > 0) { kill_obj(obj, loc); if (enemy_killed) { scan(game.comp_map, loc); } } } else if (isupper(game.user_map[loc].contents)) { /* attacking self */ if (!getyn("Sir, those are our men! Do you really want to " "attack them? ")) { return; } attack(obj, loc); } else { attack(obj, loc); } } /* Here a user wants to move an army to a city. If the city contains a non-full transport, we make the move. Otherwise we ask the user if she really wants to attack the city. */ void move_army_to_city(piece_info_t *obj, loc_t city_loc) { piece_info_t *tt; tt = find_nfull(TRANSPORT, city_loc); if (tt != NULL) { move_obj(obj, city_loc); } else { fatal(obj, city_loc, "That's our city, sir! Do you really want to attack the " "garrison? ", "Your rebel army was liquidated."); } } /* Cancel game.automove mode. */ void user_cancel_auto(void) { if (!game.automove) { comment("Not in auto mode!"); } else { game.automove = false; comment("Auto mode cancelled."); } } /* Redraw the screen. */ void user_redraw(void) { redraw(); } /* Awaken an object if it needs to be. Normally, objects are awakened when they are next to an enemy object or an unowned city. Armies on troop transports are not awakened if they are surrounded by sea. We return true if the object is now awake. Objects are never completely awoken here if their function is a destination. But we will return true if we want the user to have control. */ bool awake(piece_info_t *obj) { int i; long t; if (obj->type == ARMY && vmap_at_sea(game.user_map, obj->loc)) { obj->moved = piece_attr[ARMY].range; return (false); } if (obj->func == NOFUNC) { return (true); /* object is awake */ } if (obj->type == FIGHTER /* wake fighters */ && obj->func != LAND /* that aren't returning to base */ && obj->func < 0 /* and which don't have a path */ && obj->range <= find_nearest_city(obj->loc, USER, &t) + 2) { obj->func = NOFUNC; /* wake piece */ return (true); } for (i = 0; i < 8; i++) { /* for each surrounding cell */ char c = game.user_map[obj->loc + dir_offset[i]].contents; if (islower(c) || c == MAP_CITY || c == 'X') { if (obj->func < 0) { obj->func = NOFUNC; /* awaken */ } return (true); } } return (false); } /* Question the user about a fatal move. If the user responds 'y', print out the response and kill the object. */ void fatal(piece_info_t *obj, loc_t loc, char *message, char *response) { if (getyn(message)) { comment(response); kill_obj(obj, loc); } } /* end */ vms-empire-1.18/util.c0000664000175000017500000001341214562175610013026 0ustar esresr/* * SPDX-FileCopyrightText: Copyright (C) 1987, 1988 Chuck Simmons * SPDX-License-Identifier: GPL-2.0+ * * See the file COPYING, distributed with empire, for restriction * and warranty information. */ /* util.c -- various utility routines. */ #include "empire.h" #include "extern.h" #include /* Ugh...shouldn't be needed here */ #include #include #include #include #include /* Report a bug. */ void eassert(char *expression, char *file, int line) { char buf[STRSIZE]; (void)move(game.lines, 0); close_disp(); (void)sprintf(buf, "assert failed: file %s line %d: %s", file, line, expression); kill(getpid(), SIGSEGV); /* core dump */ } /* End the game by cleaning up the display. */ void empend(void) { close_disp(); exit(0); } /* Here is a little routine to perform consistency checking on the database. I'm finding that my database is becoming inconsistent, and I've no idea why. Possibly this will help. We perform the following functions: 1) Make sure no list contains loops. 2) Make sure every object is in either the free list with 0 hits, or it is in the correct object list and a location list with non-zero hits, and an appropriate owner. 3) Make sure every city is on the map. 4) Make sure every object is in the correct location and that objects on the map have non-zero hits. 5) Make sure every object in a cargo list has a ship pointer. 6) Make sure every object with a ship pointer is in that ship's cargo list. */ static bool in_free[LIST_SIZE]; /* true if object in free list */ static bool in_obj[LIST_SIZE]; /* true if object in obj list */ static bool in_loc[LIST_SIZE]; /* true if object in a loc list */ static bool in_cargo[LIST_SIZE]; /* true if object in a cargo list */ void check(void) { void check_cargo(piece_info_t *, int); void check_obj(piece_info_t **, int); void check_obj_cargo(piece_info_t **); long i, j; piece_info_t *p; /* nothing in any list yet */ for (i = 0; i < LIST_SIZE; i++) { in_free[i] = 0; in_obj[i] = 0; in_loc[i] = 0; in_cargo[i] = 0; } /* Mark all objects in free list. Make sure objects in free list have zero hits. */ for (p = game.free_list; p != NULL; p = p->piece_link.next) { i = p - game.object; ASSERT(!in_free[i]); in_free[i] = 1; ASSERT(p->hits == 0); if (p->piece_link.prev) { ASSERT(p->piece_link.prev->piece_link.next == p); } } /* Mark all objects in the map. Check that cities are in corect location. Check that objects are in correct location, have a good owner, and good hits. */ for (i = 0; i < MAP_SIZE; i++) { if (game.real_map[i].cityp) { ASSERT(game.real_map[i].cityp->loc == i); } for (p = game.real_map[i].objp; p != NULL; p = p->loc_link.next) { ASSERT(p->loc == i); ASSERT(p->hits > 0); ASSERT(p->owner == USER || p->owner == COMP); j = p - game.object; ASSERT(!in_loc[j]); in_loc[j] = 1; if (p->loc_link.prev) { ASSERT(p->loc_link.prev->loc_link.next == p); } } } /* make sure all cities are on map */ for (i = 0; i < NUM_CITY; i++) { ASSERT(game.real_map[game.city[i].loc].cityp == &(game.city[i])); } /* Scan object lists. */ check_obj(game.comp_obj, COMP); check_obj(game.user_obj, USER); /* Scan cargo lists. */ check_cargo(game.user_obj[TRANSPORT], ARMY); check_cargo(game.comp_obj[TRANSPORT], ARMY); check_cargo(game.user_obj[CARRIER], FIGHTER); check_cargo(game.comp_obj[CARRIER], FIGHTER); /* Make sure all objects with ship pointers are in cargo. */ check_obj_cargo(game.comp_obj); check_obj_cargo(game.user_obj); /* Make sure every object is either free or in loc and obj list. */ for (i = 0; i < LIST_SIZE; i++) { ASSERT(in_free[i] != (in_loc[i] && in_obj[i])); } } /* Check object lists. We look for: 1) Loops and bad prev pointers. 2) Dead objects. 3) Invalid types. 4) Invalid owners. */ // cppcheck-suppress constParameter void check_obj(piece_info_t **list, int owner) { long i, j; piece_info_t *p; for (i = 0; i < NUM_OBJECTS; i++) { for (p = list[i]; p != NULL; p = p->piece_link.next) { ASSERT(p->owner == owner); ASSERT(p->type == i); ASSERT(p->hits > 0); j = p - game.object; ASSERT(!in_obj[j]); in_obj[j] = 1; if (p->piece_link.prev) { ASSERT(p->piece_link.prev->piece_link.next == p); } } } } /* Check cargo lists. We assume object lists are valid. as we will place bits in the 'in_cargo' array that are used by 'check_obj'. Check for: 1) Number of items in list is same as cargo count. 2) Type of cargo is correct. 3) Location of cargo matches location of ship. 4) Ship pointer of cargo points to correct ship. 5) There are no loops in cargo list and prev ptrs are correct. 6) All cargo is alive. */ void check_cargo(piece_info_t *list, int cargo_type) { piece_info_t *p, *q; long j; for (p = list; p != NULL; p = p->piece_link.next) { long count = 0; for (q = p->cargo; q != NULL; q = q->cargo_link.next) { count += 1; /* count items in list */ ASSERT(q->type == cargo_type); ASSERT(q->owner == p->owner); ASSERT(q->hits > 0); ASSERT(q->ship == p); ASSERT(q->loc == p->loc); j = q - game.object; ASSERT(!in_cargo[j]); in_cargo[j] = 1; if (p->cargo_link.prev) { ASSERT(p->cargo_link.prev->cargo_link.next == p); } } ASSERT(count == p->count); } } /* Scan through object lists making sure every object with a ship pointer appears in a cargo list. We assume object and cargo lists are valid. */ // cppcheck-suppress constParameter void check_obj_cargo(piece_info_t **list) { piece_info_t *p; long i; for (i = 0; i < NUM_OBJECTS; i++) { for (p = list[i]; p != NULL; p = p->piece_link.next) { if (p->ship) { ASSERT(in_cargo[p - game.object]); } } } } /* end */ vms-empire-1.18/vms-empire.desktop0000600000175000017500000000032012443403777015350 0ustar esresr[Desktop Entry] Name=VMS Empire GenericName=VMS Empire Comment=Simulation of a full-scale war between two emperors Icon=vms-empire Exec=vms-empire Type=Application Categories=Game;StrategyGame; Terminal=true vms-empire-1.18/vms-empire.png0000644000175000017500000001505412443404051014467 0ustar esresrPNG  IHDR00WbKGD pHYs  tIME  IDAThލUywg99g(!!(P*T!SڷECJ-jmAZ, )L$L'rwXyOu_y> A>.>pddf/|)ęN7];ͷӽ"ֱfg_Oc\-y[::ԒR! H5[2O3=x^dxgOOw w?F{>mèS?ohhτc?͊״TͲP$cnL*X.Uȸ<p.EDH vټt RuF$9{rz<74$9JkI\XƿpÕze?ưv3q4h$(J gP Fh!9x(B`nɾGN`7Z),) ɻ 1W-pn֡:uBNo?Ӣ `n`A_|YF!Z8! +nrb`_!"椢 )YǕmY=E2|0i.|[/j׳.Ox*m݋˪] ]y)BHF) #TDhjAEIdP*dhDPA>EBXhQQ('bB7B础nzdžRDV_i&OǷ5s[v|5&(RuB[1Q.cn)G[tc21F')c*mCGG e Oђ*R  2Umw7_|}q rdaZ;~*u[x{t {Xf=lp b5X'~U_AI b'Ҹ$ 2p 8eh*Ihm D 4mx ~{c}'6-8-lh̚T1I2]S &v">IMc ybM ^9cPƈg-ɸ@=%.o:eٙe`Zgh%c)gO%D5ngRLt<@Gٚ#9rE/wwn555SA=ߑ($Mhbqe'FњPxJ+O4"NJh9+TgSgl . v[󱧲#k{YzZw{\r +ԶG5[8v^9.p')E!j"m>r-LS!'o*EETCBs L.╉5 K'22;>_l9vݓ7+o󻽭]?bssO6g/l׿^.h* 48*%[H+*QE'5ML"_k#Zif}.|k~_h&nM.5'7~ r͋Jۛ4cێyZO,}vjNʳ1NB $Մd #p!z?t]^OsFVn=>7cTG! &WHO{wo?ͥGo=}SH++Z5\ O:)' *]Vq8飭(S ~Nva4'gp<mz(K\zvEjG\ԋ9'?Zu῜;g\hz[2}~Jq\j-eib6KhlD^C Nɶ9V3&  l[7ǶSZ6ѕIƟ_g9{lz8}[^999Uq]]XIȽwYOJwř)ͯ*:K>&5B ˤLƺm9&VB3tYi.]ʡ^ϸ伩O#<'N_qb;6ŏR}a\͗17g~M"bS@7SI7^.3:)'w9\= )} vAFGFw.ٔY2'hu,xU= zx` 9{*pߠοj{+NTk?_zq7WrKL*]' `QOՈg}: MuTA:awl3),ߤ&JY6'?9?ts_Xy[BU?:Ⱦ N6X@ΉDg# /QdYT9Jݢ~0޳ǘN^(If18{s''}U 2epu;Osٱ*}t~I+b|Lo:>cӹ9;Ozw &T^dhZS拉T["h,8T#3dJ"I4:q0 FBb)4C:2~LcF鍘jbwL&zj$dgU;a6oǶ|F$>z7DZʑӚN ֈr38S^ iq N`g/$|fzBl-GG:T&39*]mŒFLAOJ`u3Oc7'F$$bVa#8_&r"Yg|PJ/aZiDnZf\>:j뢡3a:JtDQWut^JZ (J9  K0#`~ȋ]gN܎,22MC &${3sL}`/dXt 'y5'cJ,7( c1fL22FMEajZQJ4Rr0:7 ; E|F~Or=u竉89y P.P4*djS9ޒ&jY?5A;L;on'vj&Ji-O^@/dp欯jeٗ|o @8-4{ahmVxsu%J9//&E:% `pbPQDsYc41tFdEq`v оm;PL (eo(uPT[ꢫ1kD>P#W?Z_;4QJ$bz DZcA5RJ6%)|O4cP^6auQ6}ty׾r}e-6Wljl̲cd?Y~|spˤ#u?V\kFEЉCOb&x UQX<)iNWN4PbhyyD3r(˨MGmQZyvB7RyY%0ɜJz9<^^ jߔw*ʷ(P2QS "1bE?p%eP*0ܢ0)]d4lg VފPAܮf2|W.;8#*yl]>^⪾GO޵e/|qQfPE$BApI!xHÑ3!Yb&K/HTG`:e$HpCۮX7SzNޱ)Q$IXp"՛ 5W.~V ʌ5?kwr*:"^?tJt&D\vfs2Z6"'ʪ) rl'ȦeW5ҺԨ4~z9|޹+\@ΐy3?6x|C}y'\mZnʺĹ CDt:5,Msxo_U7%}5&Hԓ[ب99Lo&\ Hh;m&g7Cs-/m= PQL&,]O]P@`[֎p7tJrV#'`;6}9b)OWwW%y<}}:X'(4QDR1X\4]g~k N3fU@c4СH_\7}r/k?S=]lE4#~c6#^}q%ݏ# /kP}uoNyG/F 5 -,/>;h| nz&᜼0qRE^:}0{rC2o4ޮ}luwc_Τ {012nӵhlR\). mvvY4N Ҿ'Ò9+']nB機}'')2s$?Ǯ-Wԙ|aq0-wJ_cNEt*_S|Iv 12;Bv5t6\w=秛J dDen`]7u袈Ysٴ`[ng(z'9=@?λu.<>QL/sDv~pū 3.'_"t.6! m7hڰ4)xߌ]t~qB˯|2rNx dIft;gXXIENDB`vms-empire-1.18/vms-empire.xml0000664000175000017500000012145014551321366014514 0ustar esresr Empire 6 March 29, 1990 empire empire Games empire the wargame of the century empire -w water -s smooth -d delay -S save-interval -f savefile DESCRIPTION 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. water This option controls the amount of water on the map. This is given as the percentage of the map which should be water. The default is 70% water. water must be an integer in the between 10 and 90 inclusive. smooth This controls the smoothness of the map. A low value will produce a highly chaotic map with lots of small islands or lakes. A high value will produce a map with a few scattered continents. Be forewarned that a high value will cause the program to take a long time to start up. The default value is 5. delay This option controls the length of time the computer will delay after printing informational messages at the top of the screen. delay is specified in milliseconds. The default value is 2000 which allows the user two seconds to read a message. EXAMPLES empire -w90 -s2 This produces a map with many islands. empire -w50 -s0 This produces a really strange map. These values are not recommended for the faint at heart. empire -w10 This produces a map with lots of land and a few lakes. The computer will have a hard time on this sort of map as it will try and produce lots of troop transports, which are fairly useless. There are two other option. interval sets the save interval for the game (default is 10). Once per interval turns the game state will be automatically saved after your move. It will be saved in any case when you change modes or do various special things from command mode, such as `M' or `N'. savefile Set the save file name (normally empsave.dat). INTRODUCTION Empire is a war game played between you and the computer. The world on which the game takes place is a square rectangle containing cities, land, and water. Cities are used to build armies, planes, and ships which can move across the world destroying enemy pieces, exploring, and capturing more cities. The objective of the game is to destroy all the enemy pieces, and capture all the cities. The world is a rectangle 60 by 100 squares on a side. The world consists of sea (.), land (+), uncontrolled cities (*), computer-controlled cities (X), and cities that you control (O). The world is displayed on the player's screen during movement. (On terminals with small screens, only a portion of the world is shown at any one time.) Each piece is represented by a unique character on the map. With a few exceptions, you can only have one piece on a given location. On the map, you are shown only the 8 squares adjacent to your units. This information is updated before and during each of your moves. The map displays the most recent information known. The game starts by assigning you one city and the computer one city. Cities can produce new pieces. Every city that you own produces more pieces for you according to the cost of the desired piece. The typical play of the game is to issue the Automove command until you decide to do something special. During movement in each round, the player is prompted to move each piece that does not otherwise have an assigned function. Map coordinates are 4-digit numbers. The first two digits are the row, the second two digits are the column. PIECES The pieces are as follows: Piece You Enemy Moves Hits Str Cost Army A a 1 1 1 5(6) Fighter F f 8 1 1 10(12) Patrol Boat P p 4 1 1 15(18) Destroyer D d 2 3 1 20(24) Submarine S s 2 2 3 20(24) Troop Transport T t 2 1 1 30(36) Aircraft Carrier C c 2 8 1 30(36) Battleship B b 2 10 2 40(48) Satellite Z z 10 -- -- 50(60) The second column shows the map representation for your units. The third shows the representations of enemy units. Moves is the number of squares that the unit can move in a single round. Hits is the amount of damage a unit can take before it is destroyed. Strength is the amount of damage a unit can inflict upon an enemy during each round of an attack. Cost is the number of rounds needed for a city to produce the piece. The number in parenthesis is the cost for a city to produce the first unit. Each piece has certain advantages associated with it that can make it useful. One of the primary strategic aspects of this game is deciding which pieces will be produced and in what quantities. Armies can only move on land, and are the only piece that can move on land. Only armies can capture cities. This means that you must produce armies in order to win the game. Armies have a 50% chance of capturing a city when they attack. (Attacking one's own city results in the army's destruction. Armies that move onto the sea will drown. Armies can attack objects at sea, but even if they win, they will drown.) Armies can be carried by troop transports. If an army is moved onto a troop transport, then whenever the transport is moved, the army will be moved with the transport. You cannot attack any piece at sea while on a transport. Fighters move over both land and sea, and they move 8 squares per round. Their high speed and great mobility make fighters ideal for exploring. However, fighters must periodically land at user-owned cities for refueling. A fighter can travel 32 squares without refueling. Fighters are also shot down if they attempt to fly over a city which is not owned by the user. Patrol boats are fast but lightly armored. Therefore they are useful for patrolling ocean waters and exploring. In an attack against a stronger boat, however, patrol boats will suffer heavy casualties. Destroyers are fairly heavily armored and reasonably quick to produce. Thus they are useful for destroying enemy transports which may be trying to spread the enemy across the face of the world. When a submarine scores a hit, 3 hits are exacted instead of 1. Thus submarines can inflict heavy damage in a fight against heavily armored boats. Notice that healthy submarines will typically defeat healthy destroyers two-thirds of the time. However, a submarine will defeat a fighter about two-thirds of the time, while a destroyer will defeat a fighter three-fourths of the time. Troop transports are the only pieces that can carry armies. A maximum of six armies can be carried by a transport. On any world containing a reasonable amount of water, transports will be a critical resource in winning the game. Notice that the weakness of transports implies they need protection from stronger ships. Aircraft carriers are the only ships that can carry fighters. Carriers carry a maximum of the number of hits left of fighters. Fighters are refueled when they land on a carrier. Battleships are similar to destroyers except that they are much stronger. Satellites are only useful for reconnaissance. They can not be attacked. They are launched in a random diagonal orbit, and stay up for 50 turns. They can see one square farther than other objects. All ships can move only on the sea. Ships can also dock in a user-owned city. Docked ships have damage repaired at the rate of 1 hit per turn. Ships which have suffered a lot of damage will move more slowly. Because of their ability to be repaired, ships with lots of hits such as Carriers and Battleships have an additional advantage. After suffering minor damage while destroying enemy shipping, these ships can sail back to port and be quickly repaired before the enemy has time to replenish her destroyed shipping. The following table gives the probability that the piece listed on the side will defeat the piece listed at the top in a battle. (The table assumes that both pieces are undamaged.) AFPT D S C B AFPT 50.0% 12.5% 25.0% 00.391% 00.0977% D 87.5% 50.0% 25.0% 05.47% 00.537% S 75.0% 75.0% 50.0% 31.3% 06.25% C 99.6% 94.5% 68.7% 50.0% 04.61% B 99.9% 99.5% 93.8% 95.4% 50.0% Notice, however, that when a ship has been damaged, the odds of being defeated can go up quite a bit. For example, a healthy submarine has a 25% chance of defeating a battleship that has had one hit of damage done to it, and a healthy submarine has a 50% chance of defeating a carrier which has suffered two hits of damage. MOVEMENT FUNCTIONS There are a variety of movement functions. The movement functions of pieces can be specified in user mode and edit mode. Cities can have movement functions set for each type of piece. When a movement function for a type of pieces is set for a city, then every time that type of piece appears in the city, the piece will acquire that movement function. Be forewarned that moving loaded transports or loaded carriers into a city can have undesirable side effects. Normally, when a movement function has been specified, the piece will continue moving according to that function until one of the following happen: An enemy piece or unowned city appears next to the piece. In this case the piece will be completely awoken, unless its movement function has been set to a specific destination. Armies on ships and pieces inside cities will not be awoken if the enemy piece is gone by the time it is their turn to move. You explicitly awaken the piece. The piece can no longer move in accordance with its programmed function. In this case, the piece will awaken temporarily. You will be asked to move the piece at which time you may awaken it. The piece is a fighter which has just enough fuel (plus a small reserve) to get to the nearest city. In this case, the piece will awaken completely, unless its movement function has been set to a specific destination, or its movement function has been set to land. The rationale behind this complexity is that fighters must be awoken completely before they are out of range of a city to prevent one from accidentally forgetting to waken the fighter and then watching it fly off to its doom. However, it is presumed that when a path is set for the fighter, the fighter is not in danger of running out of fuel. Pieces do not completely awaken when their function has been set to a destination because it is slightly time consuming to reset the destination, but very simple (one keystroke) to wake the piece. The movement functions are: Attack This function applies only to armies. When this function is set, the army will move toward the nearest enemy city, unowned city, or enemy army. This is useful when fighting off an invading enemy or taking over a new continent. When an army is set to this mode, it will also explore nearby territory. This tends to make the "grope" movement mode pretty useless. Awake When pieces are awake, you will be asked for the direction in which the piece should move on each turn. Fill This function applies to carriers and transports. When this function is specified, these ships sleep until they have been filled with fighters or armies respectively. Grope This function causes a piece to explore. The piece heads toward the nearest unseen square of the map on each of its moves. Some attempt is made to explore in an optimal fashion. Land This function applies to fighters and causes the fighter to head toward the nearest transport or carrier. Random This movement function causes a piece to move at random to an adjacent empty square. Sentry This movement function puts a piece to sleep. The function of a city cannot be set to 'sleep'. Transport This movement function only works on armies. The army sleeps until an unfull transport passes by, at which point the army wakes up and boards the transport. Upgrade This movement function only works with ships. The ship will move to the nearest owned city and remain there until it is repaired. <dir> Pieces can be set to move in a specified direction. <dest> Pieces can be set to move toward a specified square. In this movement mode, pieces take a shortest path toward the destination. Pieces moving in accordance with this function prefer diagonal moves that explore territory. Because of this, the movement of the piece may be non-intuitive. As examples of how to use these movement functions, typically when I have a new city on a continent, I set the Army function of the city to attack. Whenever an army is produced, it merrily goes off on its way exploring the continent and moving towards unowned cities or enemy armies or cities. I frequently set the ship functions for cities that are far from the front to automatically move ships towards the front. When I have armies on a continent, but there is nothing to explore or attack, I move the army to the shore and use the transport function to have that army hop aboard the first passing transport. COMMANDS There are three command modes. The first of these is "command mode". In this mode, you give commands that affect the game as a whole. In the second mode, "move mode", you give commands to move your pieces. The third mode is "edit mode", and in this mode you can edit the functions of your pieces and examine various portions of the map. All commands are one character long. The full mnemonic names are listed below as a memorization aid. The mnemonics are somewhat contrived because there are so few characters in the English language. Too bad this program isn't written in Japanese, neh? In all command modes, typing "H" will print out a screen of help information, and typing <ctrl-L> will redraw the screen. COMMAND MODE In command mode, the computer will prompt you for your orders. The following commands can be given at this time: Automove Enter automove mode. This command begins a new round of movement. You will remain in move mode after each of the computer's turns. (In move mode, the "O" command will return you to command mode after the computer finishes its next turn. City Give the computer a random unowned city. This command is useful if you find that the computer is getting too easy to beat. Date The current round is displayed. Examine Examine the enemy's map. This command is only valid after the computer has resigned. File Print a copy of the map to the specified file. Give This command gives the computer a free move. J Enter edit mode where you can examine and change the functions associated with your pieces and cities. Move Enter move mode for a single round. N Give the computer the number of free moves you specify. Print Display a sector on the screen. Quit Quit the game. Restore Restore the game from empsave.dat. Save Save the game in empsave.dat. Trace This command toggles a flag. When the flag is set, after each move, either yours or the computer's, a picture of the world is written out to the file 'empmovie.dat'. Watch out! This command produces lots of output. Watch This command allows you to watch a saved movie. The movie is displayed in a condensed version so that it will fit on a single screen, so the output may be a little confusing. This command is only legal if the computer resigns. If you lose the game, you cannot replay a movie to learn the secrets of how the computer beat you. Nor can you replay a movie to find out the current positions of the computer's pieces. When replaying a movie, it is recommended that you use the option to set the delay to around 2000 milliseconds or so. Otherwise the screen will be updated too quickly for you to really grasp what is going on. Zoom Display a condensed version of the map on the screen. The user map is divided into small rectangles. Each rectangle is displayed as one square on the screen. If there is a city in a rectangle, then it is displayed. Otherwise enemy pieces are displayed, then user pieces, then land, then water, and then unexplored territory. When pieces are displayed, ships are preferred to fighters and armies. MOVE MODE In move mode, the cursor will appear on the screen at the position of each piece that needs to be moved. You can then give commands to move the piece. Directions to move are specified by the following keys: QWE A D ZXC The arrow and keypad keys on your terminal, if any, should also work. These keys move in the direction of the key from S. The characters are not echoed and only 1 character is accepted, so there is no need for a <Return>. Hit the <Space> bar if you want the piece to stay put. Other commands are: Build Change the production of a city. Fill Set the function of a troop transport or aircraft carrier to fill. Grope Set the function of a piece to grope. Idir Set the direction for a piece to move. J Enter edit mode. Kill Wake up the piece. If the piece is a transport or carrier, pieces on board will not be awoken. Land Set a fighter's function to land. Out Cancel automove mode. At the end of the round, you will be placed in command mode. Print Redraw the screen. Random Set a piece's function to random. Sentry Set a piece's function to sentry. Transport Set an army's function to transport. Upgrade Set a ship's function to upgrade. Vpiece func Set the city movement function for the specified piece to the specified function. For example, typing "VAY" would set the city movement function for armies to attack. Whenever an army is produced in the city (or whenever a loaded transport enters the city), the army's movement function would be set to attack. Y Set an army's function to attack. ? Display information about the piece. The function, hits left, range, and number of items on board are displayed. Attacking something is accomplished by moving onto the square of the unit you wish to attack. Hits are traded off at 50% probability of a hit landing on one or the other units until one unit is totally destroyed. There is only 1 possible winner. You are "allowed" to do fatal things like attack your own cities or other pieces. If you try to make a fatal move, the computer will warn you and give you a chance to change your mind. You cannot move onto the edge of the world. EDIT MODE In edit mode, you can move around the world and examine pieces or assign them new functions. To move the cursor around, use the standard direction keys. Other commands are: Build Change the production of the city under the cursor. The program will prompt for the new production, and you should respond with the key corresponding to the letter of the piece that you want produced. Fill Set a transport's or carrier's function to fill. Grope Set a piece's function to grope. Idir Set the function of a piece (or city) to the specified direction. Jdir Toggle displaying the production of all cities. Kill Wake all pieces at the current location. If the location is a city, the fighter path will also be canceled. Mark Select the piece or city at the current location. This command is used with the "N" command. N Set the destination of the piece previously selected with the "M" command to the current square. Out Exit edit mode. Printsector Display a new sector of the map. The map is divided into ten sectors of size 20 by 70. Sector zero is in the upper-left corner of the map. Sector four is in the lower-left corner of the map. Sector five is in the upper-right corner, and sector nine is in the lower-right corner. Random Set a piece to move randomly. Sentry Put a piece to sleep. Transport Set an army's function to transport. Upgrade Set a ship's function to upgrade. Vpiece func Set the city movement function for a piece. Y Set an army's function to attack. ? Display information about a piece or city. For a city, the production, time of completion of the next piece, movement functions, and the number of fighters and ships in the city are displayed. Note that you cannot directly affect anything inside a city with the editor. HINTS After you have played this game for a while, you will probably find that the computer is immensely easy to beat. Here are some ideas you can try that may make the game more interesting. Give the computer one or more extra cities before starting the game. Try playing the game with a low smoothness value (try using the -s2 or even -s0 option). When starting the game, the program will ask you what difficulty level you want. Here "difficulty level" is a misnomer. To compute a difficulty level, the program looks at each continent and counts the number of cities on the continents. A high "difficulty level" gives the computer a large continent with many cities, while the user gets a small continent with few cities. A low "difficulty level" has the opposite effect. It may be the case that the computer will play better when the "difficulty level" is low. The reason for this is that the computer is forced to move armies to multiple continents early in the game. HISTORY According to A Brief History of Empire , the ancestral game was written by Walter Bright sometime in the early 1970s while he was a student at Caltech. A copy leaked out of Caltech and was ported to DEC's VAX/VMS from the TOPS-10/20 FORTRAN sources available sometime around fall 1979. Craig Leres found the source code on a DECUS tape in 1983 and added support for different terminal types. Ed James got hold of the sources at Berkeley and converted portions of the code to C, mostly to use curses for the screen handling. He published his modified sources on the net in December 1986. Because this game ran on VMS machines for so long, it has been known as VMS Empire. In 1987 Chuck Simmons at Amdahl reverse-engineered the program and wrote a version completely in C. In doing so, he modified the computer strategy, the commands, the piece types, many of the piece attributes, and the algorithm for creating maps. The various versions of this game were ancestral to later and better-known 4X (expand/explore/exploit/exterminate) games, including Civilization (1990) and Master of Orion (1993). In 1994 Eric Raymond colorized the game. FILES empsave.dat holds a backup of the game. Whenever empire is run, it will reload any game in this file. empmovie.dat holds a history of the game so that the game can be replayed as a "movie". BUGS No doubt numerous. The savefile format changed incompatibly after version 1.13. Satellites are not completely implemented. You should be able to move to a square that contains a satellite, but the program won't let you. Enemy satellites should not cause your pieces to awaken. AUTHORS Original game by Walter Bright. Support for different terminal types added by Craig Leres. Curses support added by Ed James. C/Unix version written by Chuck Simmons. Colorization by Eric S. Raymond. Probability table corrected by Michael Self. COPYLEFT Copyright (C) 1987, 1988 Chuck Simmons See the file COPYING, distributed with empire, for restriction and warranty information.