Dave Matuszek objected the the Thingy shooting back.
[super-star-trek.git] / sst
diff --git a/sst b/sst
index d7fa53a3583149321d13eb63d053ffd324cf97a6..cb64fd5cd2d14e429b82ff122eafb560cc9764d0 100755 (executable)
--- a/sst
+++ b/sst
@@ -88,6 +88,14 @@ class randomizer:
         #    logfp.write("#seed(%d)\n" % n)
         game.lcg_x = n % randomizer.LCG_M
 
+    @staticmethod
+    def getrngstate():
+        return game.lcg_x
+
+    @staticmethod
+    def setrngstate(n):
+        game.lcg_x = n
+
 GALSIZE        = 8             # Galaxy size in quadrants
 NINHAB         = (GALSIZE * GALSIZE // 2)      # Number of inhabited worlds
 MAXUNINHAB     = 10            # Maximum uninhabited worlds
@@ -203,15 +211,12 @@ class Coord:
         return "%s - %s" % (self.i+1, self.j+1)
     __repr__ = __str__
 
-class Thingy(Coord):
+class Thingy():
     "Do not anger the Space Thingy!"
     def __init__(self):
-        Coord.__init__(self)
-        self.angered = False
-    def angry(self):
-        self.angered = True
+        self.location = Coord()
     def at(self, q):
-        return (q.i, q.j) == (self.i, self.j)
+        return (q.i, q.j) == (self.location.i, self.location.j)
 
 class Planet:
     def __init__(self):
@@ -293,7 +298,6 @@ OPTION_CURSES     = 0x00000002        # new interface
 OPTION_IOMODES    = 0x00000003        # cover both interfaces
 OPTION_PLANETS    = 0x00000004        # planets and mining
 OPTION_THOLIAN    = 0x00000008        # Tholians and their webs (UT 1979 version)
-OPTION_THINGY     = 0x00000010        # Space Thingy can shoot back (Stas, 2005)
 OPTION_PROBE      = 0x00000020        # deep-space probes (DECUS version, 1980)
 OPTION_SHOWME     = 0x00000040        # bracket Enterprise in chart
 OPTION_RAMMING    = 0x00000080        # enemies may ram Enterprise (Almy)
@@ -315,8 +319,6 @@ option_names = {
     "TTY": OPTION_TTY,
     "IOMODES": OPTION_IOMODES,
     "PLANETS": OPTION_PLANETS,
-    "THOLIAN": OPTION_THOLIAN,
-    "THINGY": OPTION_THINGY,
     "PROBE": OPTION_PROBE,
     "SHOWME": OPTION_SHOWME,
     "RAMMING": OPTION_RAMMING,
@@ -680,7 +682,7 @@ def movebaddy(enemy):
             if game.condition == "docked" and (game.options & OPTION_BASE): # protected by base -- back off !
                 motion -= game.skill*(2.0-rnd.real()**2)
         if game.idebug:
-            proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
+            proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))     # pragma: no cover
         # don't move if no motion
         if motion == 0:
             return []
@@ -697,7 +699,7 @@ def movebaddy(enemy):
     nsteps = min(nsteps, QUADSIZE) # This shouldn't be necessary
     nsteps = max(nsteps, 1) # This shouldn't be necessary
     if game.idebug:
-        proutn("NSTEPS = %d:" % nsteps)
+        proutn("NSTEPS = %d:" % nsteps)        # pragma: no cover
     # Compute preferred values of delta X and Y
     m = game.sector - enemy.location
     if 2.0 * abs(m.i) < abs(m.j):
@@ -709,7 +711,7 @@ def movebaddy(enemy):
     # main move loop
     for ll in range(nsteps):
         if game.idebug:
-            proutn(" %d" % (ll+1))
+            proutn(" %d" % (ll+1))     # pragma: no cover
         # Check if preferred position available
         look = goto + m
         if m.i < 0:
@@ -757,11 +759,11 @@ def movebaddy(enemy):
         if success:
             goto = look
             if game.idebug:
-                proutn(repr(goto))
+                proutn(repr(goto))     # pragma: no cover
         else:
             break # done early
     if game.idebug:
-        skip(1)
+        skip(1)        # pragma: no cover
     # Enemy moved, but is still in sector
     return [(False, enemy, old_dist, goto)]
 
@@ -837,7 +839,7 @@ def supercommander():
     idelta = Coord()
     basetbl = []
     if game.idebug:
-        prout("== SUPERCOMMANDER")
+        prout("== SUPERCOMMANDER")     # pragma: no cover
     # Decide on being active or passive
     avoid = ((game.incom - len(game.state.kcmdr) + game.inkling - game.remkl())/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
             (game.state.date-game.indate) < 3.0)
@@ -1412,21 +1414,15 @@ def torpedo(origin, bearing, dispersion, number, nburst):
                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
             return None
         elif iquad == '?': # Hit a thingy
-            if not (game.options & OPTION_THINGY) or rnd.withprob(0.3):
-                skip(1)
-                prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
-                skip(1)
-                prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
-                skip(1)
-                proutn(_("Mr. Spock-"))
-                prouts(_("  \"Fascinating!\""))
-                skip(1)
-                deadkl(w, iquad, w)
-            else:
-                # Stas Sergeev added the possibility that
-                # you can shove the Thingy and piss it off.
-                # It then becomes an enemy and may fire at you.
-                thing.angry()
+            skip(1)
+            prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
+            skip(1)
+            prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
+            skip(1)
+            proutn(_("Mr. Spock-"))
+            prouts(_("  \"Fascinating!\""))
+            skip(1)
+            deadkl(w, iquad, w)
             return None
         elif iquad == ' ': # Black hole
             skip(1)
@@ -1545,7 +1541,7 @@ def attack(torps_ok):
                         prout("Sector %s." % goto)
         sortenemies()
     # if no enemies remain after movement, we're done
-    if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant) and not thing.angered):
+    if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant)):
         return
     # set up partial hits if attack happens during shield status change
     pfac = 1.0/game.inshld
@@ -1565,7 +1561,7 @@ def attack(torps_ok):
             r *= 0.25
         if enemy.power < 500:
             r *= 0.25
-        if enemy.type == 'T' or (enemy.type == '?' and not thing.angered):
+        if enemy.type in ('T', '?'):
             continue
         # different enemies have different probabilities of throwing a torp
         usephasers = not torps_ok or \
@@ -1908,8 +1904,6 @@ def hittem(hits):
         else:
             proutn(_("Very small hit on "))
         ienm = game.quad[w.i][w.j]
-        if ienm == '?':
-            thing.angry()
         proutn(crmena(False, ienm, "sector", w))
         skip(1)
         if kpow == 0:
@@ -2602,8 +2596,8 @@ def events():
                     break
             else:
                 # can't seem to find one; ignore this call
-                if game.idebug:                # pragma: no cover
-                    prout("=== Couldn't find location for distress event.")
+                if game.idebug:
+                    prout("=== Couldn't find location for distress event.")    # pragma: no cover
                 continue
             # got one!!  Schedule its enslavement
             ev = schedule(FENSLV, expran(game.intime))
@@ -3546,7 +3540,7 @@ def cgetline():
             while True:
                 linein = replayfp.readline()
                 proutn(linein)
-                if linein == '':
+                if linein == '':    # pragma: no cover
                     prout("*** Replay finished")
                     replayfp.close()
                     break
@@ -3672,7 +3666,7 @@ def drawmaps(mode):
             lrscan_window.move(0, 0)
             lrscan(silent=False)
 
-def put_srscan_sym(w, sym):
+def put_srscan_sym(w, sym):    # pragma: no cover
     "Emit symbol for short-range scan."
     srscan_window.move(w.i+1, w.j*2+2)
     srscan_window.addch(sym)
@@ -3707,7 +3701,7 @@ def warble():
 
 def tracktorpedo(w, step, i, n, iquad):
     "Torpedo-track animation."
-    if not game.options & OPTION_CURSES:       # pragma: no cover
+    if not game.options & OPTION_CURSES:
         if step == 1:
             if n != 1:
                 skip(1)
@@ -3718,7 +3712,7 @@ def tracktorpedo(w, step, i, n, iquad):
         elif step in {4, 9}:
             skip(1)
         proutn("%s   " % w)
-    else:
+    else:      # pragma: no cover
         if not damaged(DSRSENS) or game.condition=="docked":
             if i != 0 and step == 1:
                 drawmaps(2)
@@ -3838,7 +3832,7 @@ def imove(icourse=None, noattack=False):
                         return True
                 # This should not happen
                 prout(_("Which way did he go?")) # pragma: no cover
-                return False
+                return False # pragma: no cover
             elif iquad == ' ':
                 skip(1)
                 prouts(_("***RED ALERT!  RED ALERT!"))
@@ -4261,7 +4255,7 @@ def warp(wcourse, involuntary):
         # Decide if time warp will occur
         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > rnd.real():
             twarp = True
-        if game.idebug and game.warpfac==10 and not twarp:
+        if game.idebug and game.warpfac==10 and not twarp:                # pragma: no cover
             blooey = False
             proutn("=== Force time warp? ")
             if ja():
@@ -5363,7 +5357,7 @@ def sectscan(goodScan, i, j):
     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
         if game.quad[i][j] in ('E', 'F'):
             if game.iscloaked:
-                highvideo()
+                highvideo()    # pragma: no cover
             textcolor({"green":GREEN,
                        "yellow":YELLOW,
                        "red":RED,
@@ -5820,7 +5814,7 @@ def setup():
             game.state.galaxy[i][j].stars = k
     # Locate star bases in galaxy
     if game.idebug:
-        prout("=== Allocating %d bases" % game.inbase)
+        prout("=== Allocating %d bases" % game.inbase)                # pragma: no cover
     for i in range(game.inbase):
         while True:
             while True:
@@ -5836,15 +5830,15 @@ def setup():
                 if distq < 6.0*(BASEMAX+1-game.inbase) and rnd.withprob(0.75):
                     contflag = True
                     if game.idebug:
-                        prout("=== Abandoning base #%d at %s" % (i, w))
+                        prout("=== Abandoning base #%d at %s" % (i, w))                # pragma: no cover
                     break
                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
                     if game.idebug:
-                        prout("=== Saving base #%d, close to #%d" % (i, j))
+                        prout("=== Saving base #%d, close to #%d" % (i, j))                # pragma: no cover
             if not contflag:
                 break
         if game.idebug:
-            prout("=== Placing base #%d in quadrant %s" % (i, w))
+            prout("=== Placing base #%d in quadrant %s" % (i, w))                # pragma: no cover
         game.state.baseq.append(w)
         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
     # Position ordinary Klingon Battle Cruisers
@@ -5930,12 +5924,17 @@ def setup():
     # Place thing (in tournament game, we don't want one!)
     # New in SST2K: never place the Thing near a starbase.
     # This makes sense and avoids a special case in the old code.
-    global thing
     if game.tourn is None:
+        # Avoid distrubing the RNG chain.  This code
+        # was a late fix and we don't want to mess up
+        # all the regression tests.
+        state = randomizer.getrngstate()
         while True:
-            thing = randplace(GALSIZE)
-            if thing not in game.state.baseq:
+            thing.location = randplace(GALSIZE)
+            # Put it somewhere a starbase is not
+            if thing.location not in game.state.baseq:
                 break
+        randomizer.setrngstate(state)
     skip(2)
     game.state.snap = False
     if game.skill == SKILL_NOVICE:
@@ -5971,7 +5970,7 @@ def setup():
     clrscr()
     setwnd(message_window)
     newqad()
-    if len(game.enemies) - (thing == game.quadrant) - (game.tholian is not None):
+    if len(game.enemies) - (thing.location == game.quadrant) - (game.tholian is not None):
         game.shldup = True
     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
         attack(torps_ok=False)
@@ -5979,7 +5978,8 @@ def setup():
 def choose():
     "Choose your game type."
     while True:
-        game.tourn = game.length = 0
+        game.tourn = None
+        game.length = 0
         game.thawed = False
         game.skill = SKILL_NONE
         # Do not chew here, we want to use command-line tokens
@@ -5987,6 +5987,7 @@ def choose():
             proutn(_("Would you like a regular, tournament, or saved game? "))
         scanner.nexttok()
         if scanner.sees("tournament"):
+            game.tourn = 0
             while scanner.nexttok() == "IHEOL":
                 proutn(_("Type in tournament number-"))
             if scanner.real == 0:
@@ -6047,11 +6048,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 | OPTION_ALPHAMERIC)
+        game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | 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 | OPTION_ALPHAMERIC)
+        game.options &=~ (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
@@ -6174,7 +6175,7 @@ def newqad():
             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
     # Put in THING if needed
-    if thing == game.quadrant:
+    if thing.location == game.quadrant:
         Enemy(etype='?', loc=dropin(),
               power=rnd.real(6000,6500.0)+250.0*game.skill)
         if not damaged(DSRSENS):
@@ -6538,7 +6539,7 @@ def makemoves():
         if game.alldone:
             break
     if game.idebug:
-        prout("=== Ending")
+        prout("=== Ending")                # pragma: no cover
 
 def cramen(ch):
     "Emit the name of an enemy or feature."
@@ -6703,7 +6704,7 @@ def debugme():                # pragma: no cover
     proutn("Toggle debug flag? ")
     if ja():
         game.idebug = not game.idebug
-        if game.idebug:
+        if game.idebug:                # pragma: no cover
             prout("Debug output ON")
         else:
             prout("Debug output OFF")
@@ -6787,43 +6788,47 @@ if __name__ == '__main__':
         else:
             game.options |= OPTION_TTY
         seed = int(time.time())
-        (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
-        for (switch, val) in options:
-            if switch == '-r':
-                # pylint: disable=raise-missing-from
-                try:
-                    replayfp = open(val, "r")
-                except IOError:
-                    sys.stderr.write("sst: can't open replay file %s\n" % val)
-                    raise SystemExit(1)
-                # pylint: disable=raise-missing-from
-                try:
-                    line = replayfp.readline().strip()
-                    (leader, __, seed) = line.split()
-                    # pylint: disable=eval-used
-                    seed = eval(seed)
-                    line = replayfp.readline().strip()
-                    arguments += line.split()[2:]
-                except ValueError:                # pragma: no cover
-                    sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
+        try:
+            (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
+            for (switch, val) in options:
+                if switch == '-r':
+                    # pylint: disable=raise-missing-from
+                    try:
+                        replayfp = open(val, "r")
+                    except IOError:
+                        sys.stderr.write("sst: can't open replay file %s\n" % val)
+                        raise SystemExit(1)
+                    # pylint: disable=raise-missing-from
+                    try:
+                        line = replayfp.readline().strip()
+                        (leader, __, seed) = line.split()
+                        # pylint: disable=eval-used
+                        seed = eval(seed)
+                        line = replayfp.readline().strip()
+                        arguments += line.split()[2:]
+                    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':                # pragma: no cover
+                    seed = int(val)
+                elif switch == '-t':   # pragma: no cover
+                    game.options |= OPTION_TTY
+                    game.options &=~ OPTION_CURSES
+                elif switch == '-x':                # pragma: no cover
+                    game.idebug = True
+                elif switch == '-c':   # Enable curses debugging - undocumented
+                    game.cdebug = True
+                elif switch == '-V':                # pragma: no cover
+                    print("SST2K", version)
+                    raise SystemExit(0)
+                else:                # pragma: no cover
+                    sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
                     raise SystemExit(1)
-                game.options |= OPTION_TTY
-                game.options &=~ OPTION_CURSES
-            elif switch == '-s':                # pragma: no cover
-                seed = int(val)
-            elif switch == '-t':       # pragma: no cover
-                game.options |= OPTION_TTY
-                game.options &=~ OPTION_CURSES
-            elif switch == '-x':                # pragma: no cover
-                game.idebug = True
-            elif switch == '-c':       # Enable curses debugging - undocumented
-                game.cdebug = True
-            elif switch == '-V':                # pragma: no cover
-                print("SST2K", version)
-                raise SystemExit(0)
-            else:                # pragma: no cover
-                sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
-                raise SystemExit(1)
+        except getopt.GetoptError as err:
+            print(err)
+            raise SystemExit(1) from err
         # where to save the input in case of bugs
         if "TMPDIR" in os.environ:                # pragma: no cover
             tmpdir = os.environ['TMPDIR']