Add self-destruct test.
[super-star-trek.git] / sst
diff --git a/sst b/sst
index 7cbccffa28dd4efe463357015b2200d01b692fc6..beff5c9cf497a2bc9d9ac35577a9bc4981833590 100755 (executable)
--- a/sst
+++ b/sst
@@ -11,15 +11,14 @@ 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
 
 # pylint: disable=multiple-imports
 import os, sys, math, curses, time, pickle, copy, gettext, getpass
-import getopt, socket, locale
+import getopt, socket, locale, getpass
 import codecs
 
 # This import only works on Unixes.  The intention is to enable
@@ -30,12 +29,6 @@ try:
 except ImportError:
     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
@@ -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
@@ -207,6 +203,8 @@ class Coord:
     def __str__(self):
         if self.i is None or self.j is None:
             return "Nowhere"
+        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__
 
@@ -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
@@ -3468,7 +3490,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 +3562,7 @@ def cgetline():
                     break
         else:
             try:
-                linein = my_input() + "\n"
+                linein = input() + "\n"
             except EOFError:
                 prout("")
                 sys.exit(0)
@@ -5316,7 +5338,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("<")
@@ -5455,7 +5480,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)
@@ -5581,6 +5609,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():
@@ -5991,11 +6055,11 @@ 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
@@ -6177,6 +6241,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:
@@ -6217,7 +6282,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),
@@ -6229,7 +6296,7 @@ commands = [
     ("QUIT",             0),
     ("HELP",             0),
     ("SCORE",            0),
-    ("CURSES",            0),
+    ("CURSES",           0),
     ("",                 0),
 ]
 
@@ -6453,6 +6520,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
@@ -6583,6 +6652,15 @@ class sstscanner:
     def getcoord(self):
         s = Coord()
         self.nexttok()
+        if (game.options & OPTION_ALPHAMERIC):
+            if (self.type == "IHALPHA") and (self.token[0] in "abcdefghij") and (self.token[1] in "0123456789"):
+                s.i = ord(self.token[0]) - ord("a")
+                try:
+                    s.j = int(self.token[-1:])-1
+                except TypeError:
+                    huh()
+                    return None
+                return s
         if self.type != "IHREAL":
             huh()
             return None
@@ -6716,7 +6794,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
@@ -6733,7 +6810,6 @@ if __name__ == '__main__':
                     seed = eval(seed)
                     line = replayfp.readline().strip()
                     arguments += line.split()[2:]
-                    replay = True
                 except ValueError:
                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
                     raise SystemExit(1)
@@ -6766,7 +6842,7 @@ if __name__ == '__main__':
             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()))
@@ -6786,10 +6862,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?"))
@@ -6798,8 +6875,11 @@ 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."))