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
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/")
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
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
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
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)
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__
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."
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
# 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:
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):
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 = []
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."))
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)
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
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()
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 ")
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:
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
break
if num <=0:
break
- if game.idebug:
+ if game.idebug: # pragma: no cover
proutn("=== Super nova here?")
if ja():
nq = game.quadrant
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)
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)
sys.stdout.write('\n')
proutn(prompt)
if not replayfp:
- my_input()
+ input()
sys.stdout.write('\n' * rows)
linecount = 0
break
else:
try:
- linein = my_input() + "\n"
+ linein = input() + "\n"
except EOFError:
prout("")
sys.exit(0)
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("<")
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)
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():
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
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:
("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),
("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:
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):
elif cmd == "CURSES":
game.options |= (OPTION_CURSES | OPTION_COLOR)
iostart()
+ elif cmd == "OPTIONS":
+ goptions()
while True:
if game.alldone:
break # Game has ended
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):
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
return None
s.j = self.int()-1
return s
- def __repr__(self):
+ def __repr__(self): # pragma: no cover
return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
def ja():
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():
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
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()))
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?"))
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("")