Ready to ship 2.2.
[super-star-trek.git] / sst.py
diff --git a/sst.py b/sst.py
index 9b35fd7e4a0c2ced7d9d32cfc247e929b6df0576..945017fed97d9a0c15ebc5e864c1b9ba92a8fbde 100755 (executable)
--- a/sst.py
+++ b/sst.py
@@ -12,6 +12,7 @@ See the doc/HACKING file in the distribution for designers notes and advice
 on how to modify (and how not to modify!) this code.
 """
 import os, sys, math, curses, time, pickle, random, copy, gettext, getpass
+import getopt, socket, locale
 
 # This import only works on Unixes.  The intention is to enable
 # Ctrl-P, Ctrl-N, and friends in Cmd.
@@ -20,9 +21,15 @@ try:
 except ImportError:
     pass
 
-version = "2.1"
+# Prevent lossage under Python 3
+try:
+    my_input = raw_input
+except NameError:
+    my_input = input
+
+version = "2.2"
 
-docpath        = (".", "../doc", "/usr/share/doc/sst")
+docpath        = (".", "doc/", "/usr/share/doc/sst/")
 
 def _(st):
     return gettext.gettext(st)
@@ -42,6 +49,9 @@ 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
 
+ALGERON                = 2311          # Date of the Treaty of Algeron
+
+
 DEFAULT      = -1
 BLACK        = 0
 BLUE         = 1
@@ -162,6 +172,9 @@ class Quadrant:
         self.supernova = False
         self.charted = False
         self.status = "secure"        # Could be "secure", "distressed", "enslaved"
+    def __str__(self):
+        return "<Quadrant: %(klingons)d>" % self.__dict__
+    __repr__ = __str__
 
 class Page:
     def __init__(self):
@@ -182,10 +195,9 @@ def fill2d(size, fillfun):
 
 class Snapshot:
     def __init__(self):
-        self.snap = False        # snapshot taken
+        self.snap = False       # snapshot taken
         self.crew = 0           # crew complement
-        self.remkl = 0          # remaining klingons
-        self.nscrem = 0                # remaining super commanders
+        self.nscrem = 0         # remaining super commanders
         self.starkl = 0         # destroyed stars
         self.basekl = 0         # destroyed bases
         self.nromrem = 0        # Romulans remaining
@@ -202,6 +214,10 @@ class Snapshot:
         self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
         # the starchart
         self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
+    def traverse(self):
+        for i in range(GALSIZE):
+            for j in range(GALSIZE):
+                yield (i, j, self.galaxy[i][j])
 
 class Event:
     def __init__(self):
@@ -211,22 +227,24 @@ class Event:
 # game options
 OPTION_ALL        = 0xffffffff
 OPTION_TTY        = 0x00000001        # old interface
-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)
-OPTION_MVBADDY        = 0x00000100        # more enemies can move (Almy)
-OPTION_BLKHOLE        = 0x00000200        # black hole may timewarp you (Stas, 2005)
-OPTION_BASE        = 0x00000400        # bases have good shields (Stas, 2005)
-OPTION_WORLDS        = 0x00000800        # logic for inhabited worlds (ESR, 2006)
-OPTION_AUTOSCAN        = 0x00001000        # automatic LRSCAN before CHART (ESR, 2006)
-OPTION_PLAIN        = 0x01000000        # user chose plain game
-OPTION_ALMY        = 0x02000000        # user chose Almy variant
-OPTION_COLOR    = 0x04000000        # enable color display (experimental, ESR, 2010)
+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)
+OPTION_MVBADDY    = 0x00000100        # more enemies can move (Almy)
+OPTION_BLKHOLE    = 0x00000200        # black hole may timewarp you (Stas, 2005)
+OPTION_BASE       = 0x00000400        # bases have good shields (Stas, 2005)
+OPTION_WORLDS     = 0x00000800        # logic for inhabited worlds (ESR, 2006)
+OPTION_AUTOSCAN   = 0x00001000        # automatic LRSCAN before CHART (ESR, 2006)
+OPTION_CAPTURE    = 0x00002000        # Enable BSD-Trek capture (Almy, 2013).
+OPTION_CLOAK      = 0x80004000        # Enable BSD-Trek capture (Almy, 2013).
+OPTION_PLAIN      = 0x01000000        # user chose plain game
+OPTION_ALMY       = 0x02000000        # user chose Almy variant
+OPTION_COLOR      = 0x04000000        # enable color display (ESR, 2010)
 
 # Define devices
 DSRSENS         = 0
@@ -237,22 +255,23 @@ DLIFSUP         = 4
 DWARPEN         = 5
 DIMPULS         = 6
 DSHIELD         = 7
-DRADIO         = 0
-DSHUTTL  = 9
-DCOMPTR  = 10
+DRADIO          = 8
+DSHUTTL         = 9
+DCOMPTR         = 10
 DNAVSYS         = 11
-DTRANSP  = 12
+DTRANSP         = 12
 DSHCTRL         = 13
-DDRAY         = 14
-DDSP         = 15
-NDEVICES = 16        # Number of devices
-
-SKILL_NONE        = 0
-SKILL_NOVICE        = 1
-SKILL_FAIR        = 2
-SKILL_GOOD        = 3
-SKILL_EXPERT        = 4
-SKILL_EMERITUS        = 5
+DDRAY           = 14
+DDSP            = 15
+DCLOAK          = 16
+NDEVICES        = 17        # Number of devices
+
+SKILL_NONE      = 0
+SKILL_NOVICE    = 1
+SKILL_FAIR      = 2
+SKILL_GOOD      = 3
+SKILL_EXPERT    = 4
+SKILL_EMERITUS  = 5
 
 def damaged(dev):
     return (game.damage[dev] != 0.0)
@@ -260,8 +279,8 @@ def communicating():
     return not damaged(DRADIO) or game.condition=="docked"
 
 # Define future events
-FSPY        = 0        # Spy event happens always (no future[] entry)
-                # can cause SC to tractor beam Enterprise
+FSPY    = 0        # Spy event happens always (no future[] entry)
+                   # can cause SC to tractor beam Enterprise
 FSNOVA  = 1        # Supernova
 FTBEAM  = 2        # Commander tractor beams Enterprise
 FSNAP   = 3        # Snapshot for time warp
@@ -270,10 +289,10 @@ FCDBAS  = 5        # Commander destroys base
 FSCMOVE = 6        # Supercommander moves (might attack base)
 FSCDBAS = 7        # Supercommander destroys base
 FDSPROB = 8        # Move deep space probe
-FDISTR        = 9        # Emit distress call from an inhabited world
-FENSLV        = 10        # Inhabited word is enslaved */
-FREPRO        = 11        # Klingons build a ship in an enslaved system
-NEVENTS        = 12
+FDISTR  = 9        # Emit distress call from an inhabited world
+FENSLV  = 10       # Inhabited word is enslaved */
+FREPRO  = 11       # Klingons build a ship in an enslaved system
+NEVENTS = 12
 
 # Abstract out the event handling -- underlying data structures will change
 # when we implement stateful events
@@ -305,7 +324,9 @@ class Enemy:
         else:
             self.location = Coord()
             self.kdist = self.kavgd = None
-            game.enemies.remove(self)
+            # Guard prevents failure on Tholian or thingy
+            if self in game.enemies:
+                game.enemies.remove(self)
         return motion
     def __repr__(self):
         return "<%s,%s.%f>" % (self.type, self.location, self.power)        # For debugging
@@ -393,14 +414,25 @@ class Gamestate:
         self.perdate = 0.0        # rate of kills
         self.idebug = False        # Debugging instrumentation enabled?
         self.statekscmdr = None # No SuperCommander coordinates yet.
+        self.brigcapacity = 400     # Enterprise brig capacity
+        self.brigfree = 400       # How many klingons can we put in the brig?
+        self.kcaptured = 0      # Total Klingons captured, for scoring.
+        self.iscloaked = False  # Cloaking device on?
+        self.ncviol = 0         # Algreon treaty violations
+        self.isviolreported = False # We have been warned
+    def remkl(self):
+        return sum([q.klingons for (_i, _j, q) in list(self.state.traverse())])
     def recompute(self):
         # Stas thinks this should be (C expression):
-        # game.state.remkl + len(game.state.kcmdr) > 0 ?
-        #        game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
+        # game.remkl() + len(game.state.kcmdr) > 0 ?
+        #        game.state.remres/(game.remkl() + 4*len(game.state.kcmdr)) : 99
         # He says the existing expression is prone to divide-by-zero errors
         # after killing the last klingon when score is shown -- perhaps also
         # if the only remaining klingon is SCOM.
-        self.state.remtime = self.state.remres/(self.state.remkl + 4*len(self.state.kcmdr))
+        self.state.remtime = self.state.remres/(self.remkl() + 4*len(self.state.kcmdr))
+    def unwon(self):
+        "Are there Klingons remaining?"
+        return self.remkl() + len(self.state.kcmdr) + self.state.nscrem
 
 FWON = 0
 FDEPLETE = 1
@@ -424,6 +456,7 @@ FDRAY = 18
 FTRIBBLE = 19
 FHOLE = 20
 FCREW = 21
+FCLOAK = 22
 
 def withprob(p):
     return random.random() < p
@@ -685,7 +718,7 @@ def moveklings():
     return tacmoves
 
 def movescom(iq, avoid):
-    "Commander movement helper."
+    "Supercommander movement helper."
     # Avoid quadrants with bases if we want to avoid Enterprise
     if not welcoming(iq) or (avoid and iq in game.state.baseq):
         return False
@@ -733,7 +766,7 @@ def supercommander():
     if game.idebug:
         prout("== SUPERCOMMANDER")
     # Decide on being active or passive
-    avoid = ((game.incom - len(game.state.kcmdr) + game.inkling - game.state.remkl)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
+    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)
     if not game.iscate and avoid:
         # compute move away from Enterprise
@@ -901,6 +934,86 @@ def movetholian():
 
 # Code from battle.c begins here
 
+def cloak():
+    "Change cloaking-device status."
+    if game.ship == 'F':
+        prout(_("Ye Faerie Queene hath no cloaking device."));
+        return
+
+    key = scanner.nexttok()
+
+    if key == "IHREAL":
+        huh()
+        return
+
+    action = None
+    if key == "IHALPHA":
+        if scanner.sees("on"):
+            if game.iscloaked:
+                prout(_("The cloaking device has already been switched on."))
+                return
+            action = "CLON"
+        elif scanner.sees("off"):
+            if not game.iscloaked:
+                prout(_("The cloaking device has already been switched off."))
+                return
+            action = "CLOFF"
+        else:
+            huh()
+            return
+    else:
+        if not game.iscloaked:
+            proutn(_("Switch cloaking device on? "))
+            if not ja():
+                return
+            action = "CLON"
+        else:
+            proutn(_("Switch cloaking device off? "))
+            if not ja():
+                return
+            action = "CLOFF"
+    if action == None:
+        return;
+
+    if action == "CLOFF":
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("Spock- \"Captain, the Treaty of Algeron is in effect.\n   Are you sure this is wise?\""))
+            if not ja():
+                return;
+        prout("Engineer Scott- \"Aye, Sir.\"");
+        game.iscloaked = False;
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
+            game.ncviol += 1
+            game.isviolreported = True
+
+            #if (neutz and game.state.date >= ALGERON) finish(FCLOAK);
+            return;
+
+    if action == "CLON":
+        if damaged(DCLOAK):
+            prout(_("Engineer Scott- \"The cloaking device is damaged, Sir.\""))
+            return;
+
+        if game.condition == "docked":
+            prout(_("You cannot cloak while docked."))
+
+       if game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("Spock- \"Captain, using the cloaking device is a violation"))
+            prout(_("  of the Treaty of Algeron. Considering the alternatives,"))
+            proutn(_("  are you sure this is wise? "))
+            if not ja():
+                return
+       prout(_("Engineer Scott- \"Cloaking device has engaging, Sir...\""))
+        attack(True)
+       prout(_("Engineer Scott- \"Cloaking device has engaged, Sir.\""))
+       game.iscloaked = True
+
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
+            game.ncviol += 1
+            game.isviolreported = True
+
 def doshield(shraise):
     "Change shield status."
     action = "NONE"
@@ -1010,22 +1123,23 @@ def doshield(shraise):
 def randdevice():
     "Choose a device to damage, at random."
     weights = (
-        105,        # DSRSENS: short range scanners        10.5%
-        105,        # DLRSENS: long range scanners                10.5%
-        120,        # DPHASER: phasers                        12.0%
-        120,        # DPHOTON: photon torpedoes                12.0%
-        25,        # DLIFSUP: life support                         2.5%
-        65,        # DWARPEN: warp drive                         6.5%
-        70,        # DIMPULS: impulse engines                 6.5%
-        145,        # DSHIELD: deflector shields                14.5%
-        30,        # DRADIO:  subspace radio                 3.0%
-        45,        # DSHUTTL: shuttle                         4.5%
-        15,        # DCOMPTR: computer                         1.5%
-        20,        # NAVCOMP: navigation system                 2.0%
-        75,        # DTRANSP: transporter                         7.5%
+        105,       # DSRSENS: short range scanners         10.5%
+        105,       # DLRSENS: long range scanners          10.5%
+        120,       # DPHASER: phasers                      12.0%
+        120,       # DPHOTON: photon torpedoes             12.0%
+        25,        # DLIFSUP: life support                  2.5%
+        65,        # DWARPEN: warp drive                    6.5%
+        70,        # DIMPULS: impulse engines               6.5%
+        135,       # DSHIELD: deflector shields            13.5%
+        30,        # DRADIO:  subspace radio                3.0%
+        45,        # DSHUTTL: shuttle                       4.5%
+        15,        # DCOMPTR: computer                      1.5%
+        20,        # NAVCOMP: navigation system             2.0%
+        75,        # DTRANSP: transporter                   7.5%
         20,        # DSHCTRL: high-speed shield controller  2.0%
-        10,        # DDRAY: death ray                         1.0%
-        30,        # DDSP: deep-space probes                 3.0%
+        10,        # DDRAY: death ray                       1.0%
+        30,        # DDSP: deep-space probes                3.0%
+        10,        # DCLOAK: the cloaking device            1.0
     )
     assert(sum(weights) == 1000)
     idx = randrange(1000)
@@ -1073,7 +1187,7 @@ def collision(rammed, enemy):
         game.damage[dev] += game.optime + extradm
     game.shldup = False
     prout(_("***Shields are down."))
-    if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
+    if game.unwon():
         announce()
         damagereport()
     else:
@@ -1292,7 +1406,7 @@ def fry(hit):
         while True:
             j = randdevice()
             # Cheat to prevent shuttle damage unless on ship
-            if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship")):
+            if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship") or (j == DCLOAK and game.ship != 'E' or j == DDRAY)):
                 break
         cdam.append(j)
         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
@@ -1310,10 +1424,15 @@ def fry(hit):
     if damaged(DSHIELD) and game.shldup:
         prout(_("***Shields knocked down."))
         game.shldup = False
+    if damaged(DCLOAK) and game.iscloaked:
+        prout(_("***Cloaking device rendered inoperative."))
+        game.iscloaked = False
 
 def attack(torps_ok):
     # bad guy attacks us
     # torps_ok == False forces use of phasers in an attack
+    if game.iscloaked:
+        return
     # game could be over at this point, check
     if game.alldone:
         return
@@ -1401,7 +1520,7 @@ def attack(torps_ok):
             dispersion = (randreal()+randreal())*0.5 - 0.5
             dispersion += 0.002*enemy.power*dispersion
             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
-            if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
+            if game.unwon() == 0:
                 finish(FWON) # Klingons did themselves in!
             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
                 return # Supernova or finished
@@ -1498,16 +1617,16 @@ def deadkl(w, etype, mv):
         # Killed some type of Klingon
         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
         game.klhere -= 1
-        if type == 'C':
+        if etype == 'C':
             game.state.kcmdr.remove(game.quadrant)
             unschedule(FTBEAM)
             if game.state.kcmdr:
                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
                 unschedule(FCDBAS)
-        elif type ==  'K':
-            game.state.remkl -= 1
-        elif type ==  'S':
+        elif etype ==  'K':
+            pass
+        elif etype ==  'S':
             game.state.nscrem -= 1
             game.state.kscmdr.invalidate()
             game.isatb = 0
@@ -1516,7 +1635,7 @@ def deadkl(w, etype, mv):
             unschedule(FSCDBAS)
     # For each kind of enemy, finish message to player
     prout(_(" destroyed."))
-    if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
+    if game.unwon() == 0:
         return
     game.recompute()
     # Remove enemy ship from arrays describing local conditions
@@ -1632,12 +1751,14 @@ def torps():
                 prout(_("***Photon tubes damaged by misfire."))
                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
             break
-        if game.shldup or game.condition == "docked":
+        if game.iscloaked:
+            dispersion *= 1.2
+        elif game.shldup or game.condition == "docked":
             dispersion *= 1.0 + 0.0001*game.shield
         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
             return
-    if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)<=0:
+    if game.unwon()<=0:
         finish(FWON)
 
 def overheat(rpow):
@@ -1717,11 +1838,10 @@ def hittem(hits):
         skip(1)
         if kpow == 0:
             deadkl(w, ienm, w)
-            if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
+            if game.unwon()==0:
                 finish(FWON)
             if game.alldone:
                 return
-            kk -= 1        # don't do the increment
             continue
         else: # decide whether or not to emasculate klingon
             if kpow > 0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
@@ -1982,7 +2102,69 @@ def phasers():
             game.shldup = False
     overheat(rpow)
 
-# Code from events,c begins here.
+
+def capture():
+    game.ididit = False # Nothing if we fail
+    game.optime = 0.0;
+
+    # Make sure there is room in the brig */
+    if game.brigfree == 0:
+        prout(_("Security reports the brig is already full."))
+        return;
+
+    if damaged(DRADIO):
+        prout(_("Uhura- \"We have no subspace radio communication, sir.\""))
+        return
+
+    if damaged(DTRANSP):
+        prout(_("Scotty- \"Transporter damaged, sir.\""))
+        return
+
+    # find out if there are any at all
+    if game.klhere < 1:
+        prout(_("Uhura- \"Getting no response, sir.\""))
+        return
+
+    # if there is more than one Klingon, find out which one */
+    #  Cruddy, just takes one at random.  Should ask the captain.
+    #  Nah, just select the weakest one since it is most likely to
+    #  surrender (Tom Almy mod)
+    klingons = [e for e in game.enemies if e.type == 'K']
+    weakest = sorted(klingons, key=lambda e: e.power)[0]
+    game.optime = 0.05         # This action will take some time
+    game.ididit = True #  So any others can strike back
+
+    # check out that Klingon
+    # The algorithm isn't that great and could use some more
+    # intelligent design
+    # x = 300 + 25*skill;
+    x = game.energy / (weakest.power * len(klingons))
+    #prout(_("Stats: energy = %s, kpower = %s, klingons = %s")
+    #      % (game.energy, weakest.power, len(klingons))) 
+    x *= 2.5  # would originally have been equivalent of 1.4,
+               # but we want command to work more often, more humanely */
+    #prout(_("Prob = %.4f" % x))
+    #  x = 100; // For testing, of course!
+    if x < randreal(100):
+        # guess what, he surrendered!!! */
+        prout(_("Klingon captain at %s surrenders.") % weakest.location)
+        i = randreal(200)
+        if i > 0:
+            prout(_("%d Klingons commit suicide rather than be taken captive.") % (200 - i))
+        if i > game.brigfree:
+            prout(_("%d Klingons die because there is no room for them in the brig.") % (i-brigfree))
+            i = game.brigfree
+        game.brigfree -= i
+        prout(_("%d captives taken") % i)
+        deadkl(weakest.location, weakest.type, game.sector)
+        if game.unwon()<=0:
+            finish(FWON)
+        return
+
+       # big surprise, he refuses to surrender */
+    prout(_("Fat chance, captain!"))
+
+# Code from events.c begins here.
 
 # This isn't a real event queue a la BSD Trek yet -- you can only have one
 # event of each type active at any given time.  Mostly these means we can
@@ -2146,9 +2328,14 @@ def events():
                     prout("== Event %d fires" % evcode)
                 datemin = game.future[l].date
         xtime = datemin-game.state.date
+        if game.iscloaked:
+            game.energy -= xtime*500.0
+            if game.energy <= 0:
+                finish(FNRG)
+                return
         game.state.date = datemin
         # Decrement Federation resources and recompute remaining time
-        game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
+        game.state.remres -= (game.remkl()+4*len(game.state.kcmdr))*xtime
         game.recompute()
         if game.state.remtime <= 0:
             finish(FDEPLETE)
@@ -2196,7 +2383,7 @@ def events():
             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
                 return
         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
-            if game.state.nscrem == 0 or \
+            if game.state.nscrem == 0 or game.iscloaked or \
                 ictbeam or istract or \
                 game.condition == "docked" or game.isatb == 1 or game.iscate:
                 return
@@ -2217,7 +2404,7 @@ def events():
                 continue
             i = randrange(len(game.state.kcmdr))
             yank = (game.state.kcmdr[i]-game.quadrant).distance()
-            if istract or game.condition == "docked" or yank == 0:
+            if istract or game.condition == "docked" or game.iscloaked or yank == 0:
                 # Drats! Have to reschedule
                 schedule(FTBEAM,
                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
@@ -2234,16 +2421,16 @@ def events():
                 unschedule(FBATTAK)
                 unschedule(FCDBAS)
                 continue
+            ibq = None # Force battle location to persist past loop
             try:
                 for ibq in game.state.baseq:
                     for cmdr in game.state.kcmdr:
                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
                             raise JumpOut
-                else:
-                    # no match found -- try later
-                    schedule(FBATTAK, expran(0.3*game.intime))
-                    unschedule(FCDBAS)
-                    continue
+                # no match found -- try later
+                schedule(FBATTAK, expran(0.3*game.intime))
+                unschedule(FCDBAS)
+                continue
             except JumpOut:
                 pass
             # commander + starbase combination found -- launch attack
@@ -2381,7 +2568,7 @@ def events():
             if q.klingons <= 0:
                 q.status = "secure"
                 continue
-            if game.state.remkl >= MAXKLGAME:
+            if game.remkl() >= MAXKLGAME:
                 continue                # full right now
             # reproduce one Klingon
             w = ev.quadrant
@@ -2398,12 +2585,11 @@ def events():
                             if q.klingons >= MAXKLQUAD or q.supernova:
                                 continue
                             raise JumpOut
-                    else:
-                        continue        # search for eligible quadrant failed
+                    # search for eligible quadrant failed
+                    continue
                 except JumpOut:
                     w = m
             # deliver the child
-            game.state.remkl += 1
             q.klingons += 1
             if game.quadrant == w:
                 game.klhere += 1
@@ -2428,7 +2614,7 @@ def wait():
         if key  != "IHEOL":
             break
         proutn(_("How long? "))
-    scanner.chew()
+        scanner.chew()
     if key != "IHREAL":
         huh()
         return
@@ -2558,33 +2744,37 @@ def nova(nov):
                 elif iquad == 'K': # kill klingon
                     deadkl(neighbor, iquad, neighbor)
                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
+                    target = None
                     for ll in range(len(game.enemies)):
                         if game.enemies[ll].location == neighbor:
+                            target = game.enemies[ll]
                             break
-                    game.enemies[ll].power -= 800.0 # If firepower is lost, die
-                    if game.enemies[ll].power <= 0.0:
-                        deadkl(neighbor, iquad, neighbor)
-                        break
-                    newc = neighbor + neighbor - hits[-1]
-                    proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
-                    if not newc.valid_sector():
-                        # can't leave quadrant
-                        skip(1)
-                        break
-                    iquad1 = game.quad[newc.i][newc.j]
-                    if iquad1 == ' ':
-                        proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
-                        skip(1)
-                        deadkl(neighbor, iquad, newc)
-                        break
-                    if iquad1 != '.':
-                        # can't move into something else
-                        skip(1)
-                        break
-                    proutn(_(", buffeted to Sector %s") % newc)
-                    game.quad[neighbor.i][neighbor.j] = '.'
-                    game.quad[newc.i][newc.j] = iquad
-                    game.enemies[ll].move(newc)
+                    if target is not None:
+                        target.power -= 800.0 # If firepower is lost, die
+                        if target.power <= 0.0:
+                            deadkl(neighbor, iquad, neighbor)
+                            continue   # neighbor loop
+                        # Else enemy gets flung by the blast wave
+                        newc = neighbor + neighbor - start
+                        proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
+                        if not newc.valid_sector():
+                            # can't leave quadrant
+                            skip(1)
+                            continue
+                        iquad1 = game.quad[newc.i][newc.j]
+                        if iquad1 == ' ':
+                            proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
+                            skip(1)
+                            deadkl(neighbor, iquad, newc)
+                            continue
+                        if iquad1 != '.':
+                            # can't move into something else
+                            skip(1)
+                            continue
+                        proutn(_(", buffeted to Sector %s") % newc)
+                        game.quad[neighbor.i][neighbor.j] = '.'
+                        game.quad[newc.i][newc.j] = iquad
+                        target.move(newc)
     # Starship affected by nova -- kick it away.
     dist = kount*0.1
     direc = ncourse[3*(bump.i+1)+bump.j+2]
@@ -2655,7 +2845,6 @@ def supernova(w):
             stars()
             game.alldone = True
     # destroy any Klingons in supernovaed quadrant
-    kldead = game.state.galaxy[nq.i][nq.j].klingons
     game.state.galaxy[nq.i][nq.j].klingons = 0
     if nq == game.state.kscmdr:
         # did in the Supercommander!
@@ -2666,10 +2855,8 @@ def supernova(w):
     survivors = filter(lambda w: w != nq, game.state.kcmdr)
     comkills = len(game.state.kcmdr) - len(survivors)
     game.state.kcmdr = survivors
-    kldead -= comkills
     if not game.state.kcmdr:
         unschedule(FTBEAM)
-    game.state.remkl -= kldead
     # destroy Romulans and planets in supernovaed quadrant
     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
     game.state.galaxy[nq.i][nq.j].romulans = 0
@@ -2690,7 +2877,7 @@ def supernova(w):
     if game.quadrant == nq or communicating():
         game.state.galaxy[nq.i][nq.j].supernova = True
     # If supernova destroys last Klingons give special message
-    if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
+    if game.unwon()==0 and not nq == game.quadrant:
         skip(2)
         if w is None:
             prout(_("Lucky you!"))
@@ -2764,7 +2951,7 @@ def killrate():
         return 0
     else:
         starting = (game.inkling + game.incom + game.inscom)
-        remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
+        remaining = game.unwon()
         return (starting - remaining)/elapsed
 
 def badpoints():
@@ -2775,7 +2962,8 @@ def badpoints():
             300*game.state.nworldkl + \
             45.0*game.nhelp +\
             100.0*game.state.basekl +\
-            3.0*game.abandoned
+            3.0*game.abandoned +\
+            100*game.ncviol
     if game.ship == 'F':
         badpt += 100.0
     elif game.ship is None:
@@ -2783,7 +2971,7 @@ def badpoints():
     return badpt
 
 def finish(ifin):
-    # end the game, with appropriate notfications
+    # end the game, with appropriate notifications
     igotit = False
     game.alldone = True
     skip(3)
@@ -2796,6 +2984,9 @@ def finish(ifin):
 
         prout(_("You have smashed the Klingon invasion fleet and saved"))
         prout(_("the Federation."))
+        if game.alive and game.brigcapacity-game.brigfree > 0:
+            game.kcaptured += game.brigcapacity-game.brigfree
+            prout(_("The %d captured Klingons are transferred to Star Fleet Command.") % (game.brigcapacity-game.brigfree))
         game.gamewon = True
         if game.alive:
             badpt = badpoints()
@@ -2854,7 +3045,7 @@ def finish(ifin):
         prout(_("conquered.  Your starship is now Klingon property,"))
         prout(_("and you are put on trial as a war criminal.  On the"))
         proutn(_("basis of your record, you are "))
-        if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
+        if game.unwon()*3.0 > (game.inkling + game.incom + game.inscom):
             prout(_("acquitted."))
             skip(1)
             prout(_("LIVE LONG AND PROSPER."))
@@ -2903,7 +3094,7 @@ def finish(ifin):
         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, disipating through space."))
+        prout(_("converted to energy, dissipating through space."))
     elif ifin == FMINING:
         prout(_("You are left with your landing party on"))
         prout(_("a wild jungle planet inhabited by primitive cannibals."))
@@ -2946,16 +3137,24 @@ def finish(ifin):
     elif ifin == FHOLE:
         prout(_("Your ship is drawn to the center of the black hole."))
         prout(_("You are crushed into extremely dense matter."))
+    elif ifin == FCLOAK:
+        game.ncviol += 1
+        prout(_("You have violated the Treaty of Algeron."))
+        prout(_("The Romulan Empire can never trust you again."))
     elif ifin == FCREW:
         prout(_("Your last crew member has died."))
+    if ifin != FWON and ifin != FCLOAK and game.iscloaked:
+        prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
+        prout(_("You may have missed some warning messages."))
+        skip(1)
     if game.ship == 'F':
         game.ship = None
     elif game.ship == 'E':
         game.ship = 'F'
     game.alive = False
-    if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
+    if game.unwon() != 0:
         goodies = game.state.remres/game.inresor
-        baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
+        baddies = (game.remkl() + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
         if goodies/baddies >= randreal(1.0, 1.5):
             prout(_("As a result of your actions, a treaty with the Klingon"))
             prout(_("Empire has been signed. The terms of the treaty are"))
@@ -2974,11 +3173,12 @@ def finish(ifin):
         prout(_("to think about pigeons."))
         game.gamewon = True
     score()
+    scanner.chew()     # Clean up leftovers
 
 def score():
     "Compute player's score."
     timused = game.state.date - game.indate
-    if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
+    if (timused == 0 or game.unwon() != 0) and timused < 5.0:
         timused = 5.0
     game.perdate = killrate()
     ithperd = 500*game.perdate + 0.5
@@ -2991,12 +3191,13 @@ def score():
         klship = 1
     else:
         klship = 2
-    game.score = 10*(game.inkling - game.state.remkl) \
+    game.score = 10*(game.inkling - game.remkl()) \
              + 50*(game.incom - len(game.state.kcmdr)) \
              + ithperd + iwon \
              + 20*(game.inrom - game.state.nromrem) \
              + 200*(game.inscom - game.state.nscrem) \
                  - game.state.nromrem \
+             + 3 * game.kcaptured \
              - badpoints()
     if not game.alive:
         game.score -= 200
@@ -3008,12 +3209,15 @@ def score():
     if game.state.nromrem and game.gamewon:
         prout(_("%6d Romulans captured                  %5d") %
               (game.state.nromrem, game.state.nromrem))
-    if game.inkling - game.state.remkl:
+    if game.inkling - game.remkl():
         prout(_("%6d ordinary Klingons destroyed        %5d") %
-              (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
+              (game.inkling - game.remkl(), 10*(game.inkling - game.remkl())))
     if game.incom - len(game.state.kcmdr):
         prout(_("%6d Klingon commanders destroyed       %5d") %
               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
+    if game.kcaptured:
+        prout(_("%d Klingons captured                   %5d") %
+              (game.kcaptured, 3 * game.kcaptured))
     if game.inscom - game.state.nscrem:
         prout(_("%6d Super-Commander destroyed          %5d") %
               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
@@ -3044,6 +3248,12 @@ def score():
     if klship:
         prout(_("%6d ship(s) lost or destroyed          %5d") %
               (klship, -100*klship))
+    if game.ncviol > 0:
+        if ncviol == 1:
+            prout(_("1 Treaty of Algeron violation          -100"))
+        else:
+            prout(_("%6d Treaty of Algeron violations       %5d\n") %
+                  (ncviol, -100*ncviol))
     if not game.alive:
         prout(_("Penalty for getting yourself killed        -200"))
     if game.gamewon:
@@ -3073,24 +3283,9 @@ def plaque():
     winner = cgetline()
     # The 38 below must be 64 for 132-column paper
     nskip = 38 - len(winner)/2
-    fp.write("\n\n\n\n")
-    # --------DRAW ENTERPRISE PICTURE.
-    fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
-    fp.write("                                      EEE                      E  : :                                         :  E\n" )
-    fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
-    fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
-    fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
-    fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
-    fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
-    fp.write("                                      EEE           E          E            E  E\n")
-    fp.write("                                                       E         E          E  E\n")
-    fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
-    fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
-    fp.write("                                                    :E    :                 EEEE       E\n")
-    fp.write("                                                   .-E   -:-----                       E\n")
-    fp.write("                                                    :E    :                            E\n")
-    fp.write("                                                      EE  :                    EEEEEEEE\n")
-    fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
+    # This is where the ASCII art picture was emitted.
+    # It got garbled somewhere in the chain of transmission to the Almy version.
+    # We should restore it if we can find old enough FORTRAN sources.
     fp.write("\n\n\n")
     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
     fp.write("\n\n\n\n")
@@ -3135,7 +3330,6 @@ def iostart():
     # for the older ones we probably need to set C locale, and python3
     # has no problems at all
     if sys.version_info[0] < 3:
-        import locale
         locale.setlocale(locale.LC_ALL, "")
     gettext.bindtextdomain("sst", "/usr/local/share/locale")
     gettext.textdomain("sst")
@@ -3212,7 +3406,7 @@ def pause_game():
         sys.stdout.write('\n')
         proutn(prompt)
         if not replayfp:
-            input()
+            my_input()
         sys.stdout.write('\n' * rows)
         linecount = 0
 
@@ -3242,8 +3436,8 @@ def proutn(proutntline):
             pause_game()
             clrscr()
         # Uncomment this to debug curses problems
-        if logfp:
-            logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
+        #if logfp:
+        #    logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
         curwnd.addstr(proutntline)
         curwnd.refresh()
     else:
@@ -3284,7 +3478,11 @@ def cgetline():
                 elif linein[0] != "#":
                     break
         else:
-            linein = eval(input()) + "\n"
+            try:
+                linein = my_input() + "\n"
+            except EOFError:
+                prout("")
+                sys.exit(0)
     if logfp:
         logfp.write(linein)
     return linein
@@ -3311,7 +3509,7 @@ def setwnd(wnd):
                 legend = "prompt"
             else:
                 legend = "unknown"
-            logfp.write("#curses: setwnd(%s)\n" % legend)
+            #logfp.write("#curses: setwnd(%s)\n" % legend)
         curwnd = wnd
         # Some curses implementations get confused when you try this.
         try:
@@ -3335,7 +3533,7 @@ def clrscr():
     linecount = 0
 
 def textcolor(color=DEFAULT):
-    if game.options & OPTION_COLOR:
+    if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
         if color == DEFAULT:
             curwnd.attrset(0)
         elif color ==  BLACK:
@@ -3372,7 +3570,7 @@ def textcolor(color=DEFAULT):
             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
 
 def highvideo():
-    if game.options & OPTION_COLOR:
+    if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
         curwnd.attron(curses.A_REVERSE)
 
 #
@@ -3409,7 +3607,7 @@ def put_srscan_sym(w, sym):
 def boom(w):
     "Enemy fall down, go boom."
     if game.options & OPTION_CURSES:
-        drawmaps(2)
+        drawmaps(0)
         setwnd(srscan_window)
         srscan_window.attron(curses.A_REVERSE)
         put_srscan_sym(w, game.quad[w.i][w.j])
@@ -3499,8 +3697,8 @@ def imove(icourse=None, noattack=False):
 
     def newquadrant(noattack):
         # Leaving quadrant -- allow final enemy attack
-        # Don't do it if being pushed by Nova
-        if len(game.enemies) != 0 and not noattack:
+        # Don't set up attack if being pushed by nova or cloaked
+        if len(game.enemies) != 0 and not noattack and not game.iscloaked:
             newcnd()
             for enemy in game.enemies:
                 finald = (w - enemy.location).distance()
@@ -3562,9 +3760,11 @@ def imove(icourse=None, noattack=False):
             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
                 for enemy in game.enemies:
                     if enemy.location == game.sector:
-                        break
-                collision(rammed=False, enemy=enemy)
-                return True
+                        collision(rammed=False, enemy=enemy)
+                        return True
+                # This should not happen
+                prout(_("Which way did he go?"))
+                return False
             elif iquad == ' ':
                 skip(1)
                 prouts(_("***RED ALERT!  RED ALERT!"))
@@ -3606,10 +3806,16 @@ def imove(icourse=None, noattack=False):
         game.inorbit = False
     # If tractor beam is to occur, don't move full distance
     if game.state.date+game.optime >= scheduled(FTBEAM):
-        trbeam = True
-        game.condition = "red"
-        icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
-        game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
+        if game.iscloaked:
+            # We can't be tractor beamed if cloaked,
+            # so move the event into the future
+            postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.kcmdr)))
+            pass
+        else:
+            trbeam = True
+            game.condition = "red"
+            icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
+            game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
     # Move out
     game.quad[game.sector.i][game.sector.j] = '.'
     for _m in range(icourse.moves):
@@ -3652,8 +3858,11 @@ def dock(verbose):
     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
         prout(crmshp() + _(" not adjacent to base."))
         return
+    if game.iscloaked:
+        prout(_("You cannot dock while cloaked."))
+        return
     game.condition = "docked"
-    if "verbose":
+    if verbose:
         prout(_("Docked."))
     game.ididit = True
     if game.energy < game.inenrg:
@@ -3662,7 +3871,11 @@ def dock(verbose):
     game.torps = game.intorps
     game.lsupres = game.inlsr
     game.state.crew = FULLCREW
-    if not damaged(DRADIO) and \
+    if game.brigcapacity-game.brigfree > 0:
+        prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
+        game.kcaptured += game.brigcapacity-game.brigfree
+        game.brigfree = game.brigcapacity
+    if communicating() and \
         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
         # get attack report from base
         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
@@ -3679,7 +3892,7 @@ def cartesian(loc1=None, loc2=None):
 
 def getcourse(isprobe):
     "Get a course and distance from the user."
-    key = 0
+    key = ""
     dquad = copy.copy(game.quadrant)
     navmode = "unspecified"
     itemp = "curt"
@@ -3789,15 +4002,20 @@ def getcourse(isprobe):
             iprompt = True
             key = scanner.nexttok()
         itemp = "verbose"
-        if key != "IHREAL":
+        if key == "IHREAL":
+            delta.j = scanner.real
+        else:
             huh()
             raise TrekError
-        delta.j = scanner.real
         key = scanner.nexttok()
-        if key != "IHREAL":
+        if key == "IHREAL":
+            delta.i = scanner.real
+        elif key == "IHEOL":
+            delta.i = 0
+            scanner.push("\n")
+        else:
             huh()
             raise TrekError
-        delta.i = scanner.real
     # Check for zero movement
     if delta.i == 0 and delta.j == 0:
         scanner.chew()
@@ -3821,16 +4039,14 @@ class course:
         if self.bearing < 0.0:
             self.bearing += 12.0
         self.angle = ((15.0 - self.bearing) * 0.5235988)
-        if origin is None:
-            self.origin = cartesian(game.quadrant, game.sector)
-        else:
-            self.origin = cartesian(game.quadrant, origin)
         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
         bigger = max(abs(self.increment.i), abs(self.increment.j))
         self.increment /= bigger
         self.moves = int(round(10*self.distance*bigger))
         self.reset()
         self.final = (self.location + self.moves*self.increment).roundtogrid()
+        self.location = self.origin
+        self.nextlocation = None
     def reset(self):
         self.location = self.origin
         self.step = 0
@@ -3906,6 +4122,11 @@ def warp(wcourse, involuntary):
     blooey = False; twarp = False
     if not involuntary: # Not WARPX entry
         game.ididit = False
+        if game.iscloaked:
+            scanner.chew()
+            skip(1)
+            prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
+            return
         if game.damage[DWARPEN] > 10.0:
             scanner.chew()
             skip(1)
@@ -4119,7 +4340,7 @@ def atover(igrab):
         # Repeat if another snova
         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
             break
-    if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
+    if game.unwon()==0:
         finish(FWON) # Snova killed remaining enemy.
 
 def timwrp():
@@ -4247,11 +4468,15 @@ def mayday():
         # There's one in this quadrant
         ddist = (game.base - game.sector).distance()
     else:
+        ibq = None     # Force base-quadrant game to persist past loop
         ddist = FOREVER
         for ibq in game.state.baseq:
             xdist = QUADSIZE * (ibq - game.quadrant).distance()
             if xdist < ddist:
                 ddist = xdist
+        if ibq is None:
+            prout(_("No starbases remain. You are alone in a hostile galaxy."))
+            return
         # Since starbase not in quadrant, set up new quadrant
         game.quadrant = ibq
         newqad()
@@ -4363,12 +4588,13 @@ def abandon():
         while True:
             # position next to base by trial and error
             game.quad[game.sector.i][game.sector.j] = '.'
+            l = QUADSIZE
             for l in range(QUADSIZE):
                 game.sector = game.base.scatter()
                 if game.sector.valid_sector() and \
                        game.quad[game.sector.i][game.sector.j] == '.':
                     break
-            if l < QUADSIZE+1:
+            if l < QUADSIZE:
                 break # found a spot
             game.sector.i=QUADSIZE/2
             game.sector.j=QUADSIZE/2
@@ -4394,6 +4620,7 @@ def abandon():
     game.lsupres=game.inlsr=3.0
     game.shldup=False
     game.warpfac=5.0
+    game.brigfree = game.brigcapacity = 300
     return
 
 # Code from planets.c begins here.
@@ -4795,7 +5022,7 @@ def deathray():
         while len(game.enemies) > 0:
             deadkl(game.enemies[1].location, game.quad[game.enemies[1].location.i][game.enemies[1].location.j],game.enemies[1].location)
         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
-        if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
+        if game.unwon() == 0:
             finish(FWON)
         if (game.options & OPTION_PLAIN) == 0:
             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
@@ -4887,11 +5114,11 @@ def report():
     if game.tourn:
         prout(_("This is tournament game %d.") % game.tourn)
     prout(_("Your secret password is \"%s\"") % game.passwd)
-    proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)),
+    proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
                                                       (game.inkling + game.incom + game.inscom)))
     if game.incom - len(game.state.kcmdr):
         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
-    elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
+    elif game.inkling - game.remkl() + (game.inscom - game.state.nscrem) > 0:
         prout(_(", but no Commanders."))
     else:
         prout(".")
@@ -4913,6 +5140,18 @@ def report():
         game.iseenit = True
     if game.casual:
         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
+    if game.brigcapacity != game.brigfree:
+        embriggened = brigcapacity-brigfree
+        if embriggened == 1:
+            prout(_("1 Klingon in brig"))
+        else:
+            prout(_("%d Klingons in brig.") %  embriggened)
+        if game.kcaptured == 0:
+            pass
+        elif game.kcaptured == 1:
+            prout(_("1 captured Klingon turned in to Starfleet."))
+        else:
+            prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
     if game.nhelp:
         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
     if game.ship == 'E':
@@ -4930,7 +5169,7 @@ def report():
             proutn(_("An armed deep space probe is in "))
         else:
             proutn(_("A deep space probe is in "))
-        prout("Quadrant %s." % game.probec)
+        prout("Quadrant %s." % game.probe.quadrant())
     if game.icrystl:
         if game.cryprob <= .05:
             prout(_("Dilithium crystals aboard ship... not yet used."))
@@ -4972,7 +5211,8 @@ def lrscan(silent):
                 if not silent and game.state.galaxy[x][y].supernova:
                     proutn(" ***")
                 elif not silent:
-                    proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
+                    cn = " %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars)
+                    proutn(((3 - len(cn)) * '.') + cn)
         if not silent:
             prout(" ")
 
@@ -5007,7 +5247,7 @@ def chart():
     scanner.chew()
     if (game.options & OPTION_AUTOSCAN):
         lrscan(silent=True)
-    if not damaged(DRADIO):
+    if communicating():
         rechart()
     if game.lastchart < game.state.date and game.condition == "docked":
         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
@@ -5043,13 +5283,22 @@ def chart():
 def sectscan(goodScan, i, j):
     "Light up an individual dot in a sector."
     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
-        textcolor({"green":GREEN,
-                   "yellow":YELLOW,
-                   "red":RED,
-                   "docked":CYAN,
-                   "dead":BROWN}[game.condition])
-        if game.quad[i][j] != game.ship:
-            highvideo()
+        if game.quad[i][j] in ('E', 'F'):
+            if game.iscloaked:
+                highvideo()
+            textcolor({"green":GREEN,
+                       "yellow":YELLOW,
+                       "red":RED,
+                       "docked":CYAN,
+                       "dead":BROWN}[game.condition])
+        else:
+            textcolor({'?':LIGHTMAGENTA,
+                       'K':LIGHTRED,
+                       'S':LIGHTRED,
+                       'C':LIGHTRED,
+                       'R':LIGHTRED,
+                       'T':LIGHTRED,
+                       }.get(game.quad[i][j], DEFAULT))
         proutn("%c " % game.quad[i][j])
         textcolor(DEFAULT)
     else:
@@ -5065,6 +5314,8 @@ def status(req=0):
             newcnd()
         prstat(_("Condition"), _("%s, %i DAMAGES") % \
                (game.condition.upper(), sum([x > 0 for x in game.damage])))
+        if game.iscloaked:
+            prout(_(", CLOAKED"))
     if not req or req == 3:
         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
     if not req or req == 4:
@@ -5096,8 +5347,7 @@ def status(req=0):
                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
         prstat(_("Shields"), s+data)
     if not req or req == 9:
-        prstat(_("Klingons Left"), "%d" \
-               % (game.state.remkl+len(game.state.kcmdr)+game.state.nscrem))
+        prstat(_("Klingons Left"), "%d" % game.unwon())
     if not req or req == 10:
         if game.options & OPTION_WORLDS:
             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
@@ -5133,7 +5383,7 @@ def srscan():
             prout(_("  [Using Base's sensors]"))
     else:
         prout(_("     Short-range scan"))
-    if goodScan and not damaged(DRADIO):
+    if goodScan and communicating():
         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
@@ -5394,6 +5644,7 @@ device = (
     _("Shield Control"), \
     _("Death Ray"), \
     _("D. S. Probe"), \
+    _("Cloaking Device"), \
 )
 
 def setup():
@@ -5485,7 +5736,7 @@ def setup():
         klumper = MAXKLQUAD
     while True:
         r = randreal()
-        klump = (1.0 - r*r)*klumper
+        klump = int((1.0 - r*r)*klumper)
         if klump > krem:
             klump = krem
         krem -= klump
@@ -5494,7 +5745,7 @@ def setup():
             if not game.state.galaxy[w.i][w.j].supernova and \
                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
                 break
-        game.state.galaxy[w.i][w.j].klingons += int(klump)
+        game.state.galaxy[w.i][w.j].klingons += klump
         if krem <= 0:
             break
     # Position Klingon Commander Ships
@@ -5615,9 +5866,9 @@ def choose():
         game.tourn = game.length = 0
         game.thawed = False
         game.skill = SKILL_NONE
-        scanner.chew()
-#        if not scanner.inqueue: # Can start with command line options
-        proutn(_("Would you like a regular, tournament, or saved game? "))
+        # Do not chew here, we want to use command-line tokens
+        if not scanner.inqueue: # Can start with command line options
+            proutn(_("Would you like a regular, tournament, or saved game? "))
         scanner.nexttok()
         if scanner.sees("tournament"):
             while scanner.nexttok() == "IHEOL":
@@ -5680,17 +5931,16 @@ 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)
+        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)
         game.options |= OPTION_PLAIN
     elif scanner.sees("almy"):
         # Approximates Tom Almy's version.
-        game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
+        game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR)
         game.options |= OPTION_ALMY
     elif scanner.sees("fancy") or scanner.sees("\n"):
         pass
     elif len(scanner.token):
         proutn(_("What is \"%s\"?") % scanner.token)
-    game.options &=~ OPTION_COLOR
     setpassword()
     if game.passwd == "debug":
         game.idebug = True
@@ -5703,11 +5953,11 @@ def choose():
         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
     if game.options & OPTION_WORLDS:
         game.inplan += int(NINHAB)
-    game.state.nromrem = game.inrom = randrange(2 *game.skill)
+    game.state.nromrem = game.inrom = randrange(2 * game.skill)
     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
     game.state.remtime = 7.0 * game.length
     game.intime = game.state.remtime
-    game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
+    game.inkling = int(2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15))
     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
     game.state.remres = (game.inkling+4*game.incom)*game.intime
     game.inresor = game.state.remres
@@ -5748,7 +5998,7 @@ def newqad():
     game.justin = True
     game.iplnet = None
     game.neutz = game.inorbit = game.landed = False
-    game.ientesc = game.iseenit = False
+    game.ientesc = game.iseenit = game.isviolreported = False
     # Create a blank quadrant
     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
     if game.iscate:
@@ -5780,7 +6030,7 @@ def newqad():
             e = game.enemies[0]
             game.quad[e.location.i][e.location.j] = 'S'
             e.power = randreal(1175.0,  1575.0) + 125.0*game.skill
-            game.iscate = (game.state.remkl > 1)
+            game.iscate = (game.remkl() > 1)
     # Put in Romulans if needed
     for _i in range(q.romulans):
         Enemy('R', loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
@@ -5854,6 +6104,9 @@ def newqad():
             game.quad[QUADSIZE-1][0] = '.'
         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
+    # This should guarantee that replay games don't lose info about the chart
+    if (game.options & OPTION_AUTOSCAN) or replayfp:
+        lrscan(silent=True)
 
 def setpassword():
     "Set the self-destruct password."
@@ -5889,7 +6142,6 @@ commands = [
     ("IMPULSE",          0),
     ("REST",             0),
     ("WARP",             0),
-    ("SCORE",            0),
     ("SENSORS",          OPTION_PLANETS),
     ("ORBIT",            OPTION_PLANETS),
     ("TRANSPORT",        OPTION_PLANETS),
@@ -5907,12 +6159,16 @@ commands = [
     ("ABANDON",          0),
     ("DESTRUCT",         0),
     ("DEATHRAY",         0),
+    ("CAPTURE",          OPTION_CAPTURE),
+    ("CLOAK",            OPTION_CLOAK),
     ("DEBUG",            0),
     ("MAYDAY",           0),
     ("SOS",              0),        # Synonym for MAYDAY
     ("CALL",             0),        # Synonym for MAYDAY
     ("QUIT",             0),
     ("HELP",             0),
+    ("SCORE",            0),
+    ("CURSES",            0),
     ("",                 0),
 ]
 
@@ -5984,6 +6240,11 @@ def helpme():
 
 def makemoves():
     "Command-interpretation loop."
+    def checkviol():
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
+            prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
+            game.ncviol += 1
+            game.isviolreported = True
     while True:         # command loop
         drawmaps(1)
         while True:        # get a command
@@ -6020,6 +6281,8 @@ def makemoves():
                 huh()
             else:
                 break
+        if game.options & OPTION_CURSES:
+            prout("COMMAND> %s" % cmd)
         if cmd == "SRSCAN":                # srscan
             srscan()
         elif cmd == "STATUS":                # status
@@ -6031,10 +6294,12 @@ def makemoves():
         elif cmd == "PHASERS":                # phasers
             phasers()
             if game.ididit:
+                checkviol()
                 hitme = True
         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
             torps()
             if game.ididit:
+                checkviol()
                 hitme = True
         elif cmd == "MOVE":                # move under warp
             warp(wcourse=None, involuntary=False)
@@ -6059,8 +6324,6 @@ def makemoves():
                 hitme = True
         elif cmd == "WARP":                # warp
             setwarp()
-        elif cmd == "SCORE":                # score
-            score()
         elif cmd == "SENSORS":                # sensors
             sensor()
         elif cmd == "ORBIT":                # orbit
@@ -6110,6 +6373,10 @@ def makemoves():
             deathray()
             if game.ididit:
                 hitme = True
+        elif cmd == "CAPTURE":
+            capture()
+        elif cmd == "CLOAK":
+            cloak()
         elif cmd == "DEBUGCMD":                # What do we want for debug???
             debugme()
         elif cmd == "MAYDAY":                # Call for help
@@ -6120,6 +6387,11 @@ def makemoves():
             game.alldone = True                # quit the game
         elif cmd == "HELP":
             helpme()                        # get help
+        elif cmd == "SCORE":
+            score()                         # see current score
+        elif cmd == "CURSES":
+            game.options |= (OPTION_CURSES | OPTION_COLOR)
+            iostart()
         while True:
             if game.alldone:
                 break                # Game has ended
@@ -6369,7 +6641,6 @@ def debugme():
         atover(True)
 
 if __name__ == '__main__':
-    import getopt, socket
     try:
         #global line, thing, game
         game = None