X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=sst;h=c11e661b431bcaa467dd2554fb5674cc5beda2bb;hb=fc8da2f955459e49dfa48ff42354243ef0d5d429;hp=94bc397b2c0f8e5df713a84398b8098e03c80dc6;hpb=53e4feec7d6e15846b75f6813a8ba49ebdc7d9fb;p=super-star-trek.git diff --git a/sst b/sst index 94bc397..c11e661 100755 --- a/sst +++ b/sst @@ -11,8 +11,7 @@ Stas Sergeev, and Eric S. Raymond. See the doc/HACKING file in the distribution for designers notes and advice on how to modify (and how not to modify!) this code. """ -from __future__ import print_function, division -# Runs under Python 2 an Python 3. Preserve this property! +# Copyright by Eric S. Raymond # SPDX-License-Identifier: BSD-2-clause # pylint: disable=line-too-long,superfluous-parens,too-many-lines,invalid-name,missing-function-docstring,missing-class-docstring,multiple-statements,too-many-branches,too-many-statements,too-many-locals,too-many-nested-blocks,too-many-return-statements,too-many-instance-attributes,global-statement,no-else-break,no-else-return,no-else-continue,too-few-public-methods,too-many-boolean-expressions,consider-using-f-string,consider-using-enumerate,consider-using-with,unspecified-encoding @@ -27,15 +26,9 @@ import codecs try: # pylint: disable=unused-import import readline -except ImportError: +except ImportError: # pragma: no cover pass -# Prevent lossage under Python 3 -try: - my_input = raw_input -except NameError: - my_input = input - version = "2.7" docpath = (".", "doc/", "/usr/share/doc/sst/") @@ -44,8 +37,8 @@ def _(st): return gettext.gettext(st) # Rolling our own LCG because Python changed its incompatibly in 3.2. -# Thus, we need to have our own to be 2/3 polyglot, which will also -# be helpful when we forwrard-port. +# Thus, we needed to have our own to be 2/3 polyglot; it will be +# helpful when and if we ever forward-port to another language. class randomizer: # LCG PRNG parameters tested against @@ -108,7 +101,7 @@ FOREVER = 1e30 # Time for the indefinite future MAXBURST = 3 # Max # of torps you can launch in one turn MINCMDR = 10 # Minimum number of Klingon commanders DOCKFAC = 0.25 # Repair faster when docked -PHASEFAC = 2.0 # Unclear what this is, it was in the C version +PHASEFAC = 2.0 # Phaser attenuation factor ALGERON = 2311 # Date of the Treaty of Algeron @@ -137,6 +130,9 @@ class TrekError(Exception): class JumpOut(Exception): pass +def letterize(n): + return chr(ord('a') + n - 1) + class Coord: def __init__(self, x=None, y=None): self.i = x # Row @@ -159,11 +155,11 @@ class Coord: return Coord(self.i*other, self.j*other) def __rmul__(self, other): return Coord(self.i*other, self.j*other) - def __div__(self, other): + def __div__(self, other): # pragma: no cover return Coord(self.i/other, self.j/other) - def __truediv__(self, other): + def __truediv__(self, other): # pragma: no cover return Coord(self.i/other, self.j/other) - def __floordiv__(self, other): + def __floordiv__(self, other): # pragma: no cover return Coord(self.i//other, self.j//other) def __mod__(self, other): return Coord(self.i % other, self.j % other) @@ -206,7 +202,9 @@ class Coord: return s def __str__(self): if self.i is None or self.j is None: - return "Nowhere" + return "Nowhere" # pragma: no cover + if (game.options & OPTION_ALPHAMERIC): + return letterize(self.i + 1) + str(self.j + 1) return "%s - %s" % (self.i+1, self.j+1) __repr__ = __str__ @@ -251,7 +249,7 @@ class Page: self.starbase = False self.klingons = None def __repr__(self): - return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars) + return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars) # pragma: no cover def fill2d(size, fillfun): "Fill an empty list in 2D." @@ -315,6 +313,30 @@ OPTION_PLAIN = 0x01000000 # user chose plain game OPTION_ALMY = 0x02000000 # user chose Almy variant OPTION_COLOR = 0x04000000 # enable color display (ESR, 2010) OPTION_DOTFILL = 0x08000000 # fix dotfill glitch in chart (ESR, 2019) +OPTION_ALPHAMERIC = 0x10000000 # Alpha Y coordinates (ESR, 2023) + +option_names = { + "ALL": OPTION_ALL, + "TTY": OPTION_TTY, + "IOMODES": OPTION_IOMODES, + "PLANETS": OPTION_PLANETS, + "THOLIAN": OPTION_THOLIAN, + "THINGY": OPTION_THINGY, + "PROBE": OPTION_PROBE, + "SHOWME": OPTION_SHOWME, + "RAMMING": OPTION_RAMMING, + "MVBADDY": OPTION_MVBADDY, + "BLKHOLE": OPTION_BLKHOLE, + "BASE": OPTION_BASE, + "WORLDS": OPTION_WORLDS, + "AUTOSCAN": OPTION_AUTOSCAN, + "CAPTURE": OPTION_CAPTURE, + "CLOAK": OPTION_CLOAK, + "PLAIN": OPTION_PLAIN, + "ALMY": OPTION_ALMY, + "COLOR": OPTION_COLOR, + "DOTFILL": OPTION_DOTFILL, + } # Define devices DSRSENS = 0 @@ -366,7 +388,7 @@ NEVENTS = 12 # Abstract out the event handling -- underlying data structures will change # when we implement stateful events -def findevent(evtype): +def findevent(evtype): # pragma: no cover return game.future[evtype] class Enemy: @@ -399,7 +421,7 @@ class Enemy: game.enemies.remove(self) return motion def __repr__(self): - return "<%s,%s.%f>" % (self.type, self.location, self.power) # For debugging + return "<%s,%s.%f>" % (self.type, self.location, self.power) # pragma: no cover class Gamestate: def __init__(self): @@ -751,7 +773,7 @@ def movebaddy(enemy): def moveklings(): "Sequence Klingon tactical movement." if game.idebug: - prout("== MOVCOM") + prout("== MOVCOM") # pragma: no cover # Figure out which Klingon is the commander (or Supercommander) # and do move tacmoves = [] @@ -1204,10 +1226,10 @@ def randdevice(): wsum += w if idx < wsum: return i - return None # we should never get here + return None # pragma: no cover def collision(rammed, enemy): - "Collision handling for rammong events." + "Collision handling for ramming events." prouts(_("***RED ALERT! RED ALERT!")) skip(1) prout(_("***COLLISION IMMINENT.")) @@ -1229,7 +1251,7 @@ def collision(rammed, enemy): prout(_("***Sickbay reports %d casualties") % icas) game.casual += icas game.state.crew -= icas - # In the pre-SST2K version, all devices got equiprobably damaged, + # In the pre-SST2K versions, all devices got equiprobably damaged, # which was silly. Instead, pick up to half the devices at # random according to our weighting table, ncrits = rnd.integer(NDEVICES//2) @@ -1348,7 +1370,7 @@ def torpedo(origin, bearing, dispersion, number, nburst): tenemy.kdist = tenemy.kavgd = (game.sector-tenemy.location).distance() sortenemies() break - else: + else: # pragma: no cover prout("Internal error, no enemy where expected!") raise SystemExit(1) return None @@ -1499,7 +1521,7 @@ def attack(torps_ok): chgfac = 1.0 where = "neither" if game.idebug: - prout("=== ATTACK!") + prout("=== ATTACK!") # pragma: no cover # Tholian gets to move before attacking if game.tholian: movetholian() @@ -2353,7 +2375,7 @@ def events(): game.isatb = 0 else: game.battle.invalidate() - if game.idebug: + if game.idebug: # pragma: no cover prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim)) for i in range(1, NEVENTS): if i == FSNOVA: proutn("=== Supernova ") @@ -2383,7 +2405,7 @@ def events(): if game.future[l].date < datemin: evcode = l if game.idebug: - prout("== Event %d fires" % evcode) + prout("== Event %d fires" % evcode) # pragma: no cover datemin = game.future[l].date xtime = datemin-game.state.date if game.iscloaked: @@ -2585,7 +2607,7 @@ def events(): break else: # can't seem to find one; ignore this call - if game.idebug: + if game.idebug: # pragma: no cover prout("=== Couldn't find location for distress event.") continue # got one!! Schedule its enslavement @@ -2871,7 +2893,7 @@ def supernova(w): break if num <=0: break - if game.idebug: + if game.idebug: # pragma: no cover proutn("=== Super nova here?") if ja(): nq = game.quadrant @@ -3150,13 +3172,14 @@ def finish(ifin): prout(_("Your starship is now an expanding cloud of subatomic particles")) elif ifin == FMATERIALIZE: prout(_("Starbase was unable to re-materialize your starship.")) - prout(_("Sic transit gloria mundi")) + prout(_("Sic transit gloria mundi.")) elif ifin == FPHASER: prout(_("The %s has been cremated by its own phasers.") % crmshp()) elif ifin == FLOST: prout(_("You and your landing party have been")) prout(_("converted to energy, dissipating through space.")) elif ifin == FMINING: + # This does not seem to be reachable from any code path. prout(_("You are left with your landing party on")) prout(_("a wild jungle planet inhabited by primitive cannibals.")) skip(1) @@ -3169,6 +3192,7 @@ def finish(ifin): prout(_("That was a great shot.")) skip(1) elif ifin == FSSC: + # This does not seem to be reachable from any code path. prout(_("The Galileo is instantly annihilated by the supernova.")) prout(_("You and your mining party are atomized.")) skip(1) @@ -3468,7 +3492,7 @@ def pause_game(): sys.stdout.write('\n') proutn(prompt) if not replayfp: - my_input() + input() sys.stdout.write('\n' * rows) linecount = 0 @@ -3540,7 +3564,7 @@ def cgetline(): break else: try: - linein = my_input() + "\n" + linein = input() + "\n" except EOFError: prout("") sys.exit(0) @@ -4009,39 +4033,35 @@ def getcourse(isprobe): scanner.chew() iprompt = True key = scanner.nexttok() - if key != "IHREAL": - huh() - raise TrekError - xi = int(round(scanner.real))-1 - key = scanner.nexttok() - if key != "IHREAL": - huh() + scanner.push(scanner.token) # Something IHREAL or IHALPHA awaits us + first = scanner.getcoord() + if first is None: raise TrekError - xj = int(round(scanner.real))-1 - key = scanner.nexttok() - if key == "IHREAL": - # both quadrant and sector specified - xk = int(round(scanner.real))-1 - key = scanner.nexttok() - if key != "IHREAL": - huh() + scanner.nexttok() + if scanner.type == "IHEOL": + second = None + else: + scanner.push(scanner.token) + second = scanner.getcoord() + if second is None: raise TrekError - xl = int(round(scanner.real))-1 - dquad.i = xi - dquad.j = xj - dsect.i = xk - dsect.j = xl + scanner.chew() + if second is not None: + dquad.i = first.i + dquad.j = first.j + dsect.i = second.i + dsect.j = second.j else: # only one pair of numbers was specified if isprobe: # only quadrant specified -- go to center of dest quad - dquad.i = xi - dquad.j = xj - dsect.j = dsect.i = 4 # preserves 1-origin behavior + dquad.i = first.i + dquad.j = first.j + dsect.j = dsect.i = (QUADSIZE/2)-1 # preserves 1-origin behavior else: # only sector specified - dsect.i = xi - dsect.j = xj + dsect.i = first.i + dsect.j = first.j itemp = "normal" if not dquad.valid_quadrant() or not dsect.valid_sector(): huh() @@ -5320,7 +5340,10 @@ def chart(): prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart))) prout(" 1 2 3 4 5 6 7 8") for i in range(GALSIZE): - proutn("%d |" % (i+1)) + if (game.options & OPTION_ALPHAMERIC): + proutn("%c |" % letterize(i+1)) + else: + proutn("%d |" % (i+1)) for j in range(GALSIZE): if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j: proutn("<") @@ -5459,7 +5482,10 @@ def srscan(): if game.condition != "docked": newcnd() for i in range(QUADSIZE): - proutn("%2d " % (i+1)) + if (game.options & OPTION_ALPHAMERIC): + proutn("%c " % letterize(i+1)) + else: + proutn("%2d " % (i+1)) for j in range(QUADSIZE): sectscan(goodScan, i, j) skip(1) @@ -5585,6 +5611,42 @@ def eta(): skip(1) return +# This is new in SST2K. + +def goptions(): + mode = scanner.nexttok() + if mode == "IHEOL": + active = [] + for k, v in option_names.items(): + if (v & game.options) and k != "ALL": + active.append(k) + active.sort() + prout(str(" ".join(active))) + elif scanner.token in {"set", "clear"}: + mode = scanner.token + changemask = 0 + while True: + scanner.nexttok() + if scanner.type == "IHEOL": + break + if scanner.token.upper() in option_names: + changemask |= option_names[scanner.token.upper()] + else: + prout(_("No such option as ") + scanner.token) + if mode == "set": + if (not (game.options & OPTION_CURSES)) and (changemask & OPTION_CURSES): + iostart() + game.options |= changemask + elif mode == "clear": + if (game.options & OPTION_CURSES) and (not (changemask & OPTION_CURSES)): + ioend() + game.options &=~ changemask + prout(_("Acknowledged, Captain.")) + else: + huh() + scanner.chew() + skip(1) + # Code from setup.c begins here def prelim(): @@ -5995,18 +6057,18 @@ def choose(): scanner.nexttok() if scanner.sees("plain"): # Approximates the UT FORTRAN version. - game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_CAPTURE | OPTION_CLOAK | OPTION_DOTFILL) + game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_CAPTURE | OPTION_CLOAK | OPTION_DOTFILL | OPTION_ALPHAMERIC) game.options |= OPTION_PLAIN elif scanner.sees("almy"): # Approximates Tom Almy's version. - game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_DOTFILL) + game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_DOTFILL | OPTION_ALPHAMERIC) game.options |= OPTION_ALMY elif scanner.sees("fancy") or scanner.sees("\n"): pass elif len(scanner.token): proutn(_("What is \"%s\"?") % scanner.token) setpassword() - if game.passwd == "debug": + if game.passwd == "debug": # pragma: no cover game.idebug = True prout("=== Debug mode enabled.") # Use parameters to generate initial values of things @@ -6181,6 +6243,7 @@ def setpassword(): proutn(_("Please type in a secret password- ")) scanner.nexttok() game.passwd = scanner.token + #game.passwd = getpass.getpass("Please type in a secret password- ") if game.passwd is not None: break else: @@ -6221,7 +6284,9 @@ commands = [ ("PROBE", OPTION_PROBE), ("SAVE", 0), ("FREEZE", 0), # Synonym for SAVE + ("OPTIONS", 0), ("ABANDON", 0), + # No abbreviations accepted after this point ("DESTRUCT", 0), ("DEATHRAY", 0), ("CAPTURE", OPTION_CAPTURE), @@ -6233,12 +6298,14 @@ commands = [ ("QUIT", 0), ("HELP", 0), ("SCORE", 0), - ("CURSES", 0), + ("CURSES", 0), ("", 0), ] -def listCommands(): +def listCommands(): # pragma: no cover "Generate a list of legal commands." + # Coverage-disabled because testing for this is fragile + # in the presence of changes in the command set. prout(_("LEGAL COMMANDS ARE:")) emitted = 0 for (key, opt) in commands: @@ -6339,7 +6406,7 @@ def makemoves(): if cmd == scanner.token.upper() or (not abandon_passed \ and cmd.startswith(scanner.token.upper())): break - if cmd == "": + if cmd == "": # pragma: no cover listCommands() continue elif opt and not (opt & game.options): @@ -6457,6 +6524,8 @@ def makemoves(): elif cmd == "CURSES": game.options |= (OPTION_CURSES | OPTION_COLOR) iostart() + elif cmd == "OPTIONS": + goptions() while True: if game.alldone: break # Game has ended @@ -6495,7 +6564,7 @@ def cramen(ch): elif ch == '#': s = _("Tholian web") elif ch == '?': s = _("Stranger") elif ch == '@': s = _("Inhabited World") - else: s = "Unknown??" + else: s = "Unknown??" # pragma: no cover return s def crmena(loud, enemy, loctype, w): @@ -6539,7 +6608,7 @@ class sstscanner: # Get a token from the user self.real = 0.0 self.token = '' - # Fill the token quue if nothing here + # Fill the token queue if nothing here while not self.inqueue: sline = cgetline() if curwnd==prompt_window: @@ -6587,6 +6656,15 @@ class sstscanner: def getcoord(self): s = Coord() self.nexttok() + if (game.options & OPTION_ALPHAMERIC): + try: + if (self.type == "IHALPHA") and (self.token[0] in "abcdefghij") and (self.token[1] in "0123456789"): + s.i = ord(self.token[0]) - ord("a") + s.j = int(self.token[1:])-1 + return s + except (TypeError, IndexError): + huh() + return None if self.type != "IHREAL": huh() return None @@ -6597,7 +6675,7 @@ class sstscanner: return None s.j = self.int()-1 return s - def __repr__(self): + def __repr__(self): # pragma: no cover return "" % (self.token, self.type, self.inqueue) def ja(): @@ -6618,7 +6696,7 @@ def huh(): skip(1) prout(_("Beg your pardon, Captain?")) -def debugme(): +def debugme(): # pragma: no cover "Access to the internals for debugging." proutn("Reset levels? ") if ja(): @@ -6720,7 +6798,6 @@ if __name__ == '__main__': game.options |= OPTION_TTY seed = int(time.time()) (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV") - replay = False for (switch, val) in options: if switch == '-r': # pylint: disable=raise-missing-from @@ -6737,40 +6814,39 @@ if __name__ == '__main__': seed = eval(seed) line = replayfp.readline().strip() arguments += line.split()[2:] - replay = True - except ValueError: + except ValueError: # pragma: no cover sys.stderr.write("sst: replay file %s is ill-formed\n"% val) raise SystemExit(1) game.options |= OPTION_TTY game.options &=~ OPTION_CURSES - elif switch == '-s': + elif switch == '-s': # pragma: no cover seed = int(val) elif switch == '-t': game.options |= OPTION_TTY game.options &=~ OPTION_CURSES - elif switch == '-x': + elif switch == '-x': # pragma: no cover game.idebug = True elif switch == '-c': # Enable curses debugging - undocumented game.cdebug = True - elif switch == '-V': + elif switch == '-V': # pragma: no cover print("SST2K", version) raise SystemExit(0) - else: + else: # pragma: no cover sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n") raise SystemExit(1) # where to save the input in case of bugs - if "TMPDIR" in os.environ: + if "TMPDIR" in os.environ: # pragma: no cover tmpdir = os.environ['TMPDIR'] else: tmpdir = "/tmp" try: logfp = open(os.path.join(tmpdir, "sst-input.log"), "w") - except IOError: + except IOError: # pragma: no cover sys.stderr.write("sst: warning, can't open logfile\n") sys.exit(1) if logfp: logfp.write("# seed %s\n" % seed) - logfp.write("# options %s\n" % " ".join(arguments)) + logfp.write("# arguments %s\n" % " ".join(arguments)) logfp.write("# SST2K version %s\n" % version) logfp.write("# recorded by %s@%s on %s\n" % \ (getpass.getuser(),socket.getfqdn(),time.ctime())) @@ -6790,10 +6866,11 @@ if __name__ == '__main__': game.alldone = False else: makemoves() - if replay: + if replayfp: break skip(1) - stars() + if (game.options & OPTION_TTY): + stars() skip(1) if game.tourn and game.alldone: proutn(_("Do you want your score recorded?")) @@ -6802,15 +6879,18 @@ if __name__ == '__main__': scanner.push("\n") freeze(False) scanner.chew() - proutn(_("Do you want to play again? ")) - if not ja(): + if (game.options & OPTION_TTY): + proutn(_("Do you want to play again? ")) + if not ja(): + break + else: break skip(1) prout(_("May the Great Bird of the Galaxy roost upon your home planet.")) finally: ioend() raise SystemExit(0) - except KeyboardInterrupt: + except KeyboardInterrupt: # pragma: no cover if logfp: logfp.close() print("")