Collect three parallel arrays into an 'enemy' structure.
[super-star-trek.git] / src / sst.py
index 5f2c91813dbda28d9d1a7d737f9afd90e1b409bf..fb665de93505b70bac785dd26773c0439b87477f 100644 (file)
@@ -172,6 +172,13 @@ your score.  Docking at a starbase replenishes your crew.
 Also, the nav subsystem (enabling automatic course
 setting) can be damaged separately from the main computer (which
 handles weapons targeting, ETA calculation, and self-destruct).
+
+After these features were added, I translated this into Python and added
+more:
+
+9. A long-range scan is done silently whenever you call CHART; thus
+the LRSCAN command is no longer needed.  (Controlled by OPTION_AUTOSCAN
+and turned off if game type is "plain" or "almy".)
 """
 import os,sys,math,curses,time,atexit,readline,cPickle,random,getopt,copy
 
@@ -262,6 +269,8 @@ class coord:
     def __hash__(self):
         return hash((x, y))
     def __str__(self):
+        if self.x == None or self.y == None:
+            return "Nowhere"
         return "%s - %s" % (self.x+1, self.y+1)
     __repr__ = __str__
 
@@ -342,15 +351,16 @@ 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 
-OPTION_THINGY  = 0x00000010    # Space Thingy can shoot back 
-OPTION_PROBE   = 0x00000020    # deep-space probes 
+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 
-OPTION_MVBADDY = 0x00000100    # more enemies can move 
-OPTION_BLKHOLE = 0x00000200    # black hole may timewarp you 
-OPTION_BASE    = 0x00000400    # bases have good shields 
-OPTION_WORLDS  = 0x00000800    # logic for inhabited worlds 
+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 
 
@@ -405,21 +415,29 @@ NEVENTS   = 12
 # 
 def findevent(evtype): return game.future[evtype]
 
+class enemy:
+    def __init__(self, loc=None, power=None):
+        if loc:
+            self.kloc = loc
+        else:
+            self.kloc = coord()        # enemy sector location
+        self.kpower = power    # enemy energy levels
+        self.kdist = self.kavgd = distance(game.sector, e.kloc)
+    def __repr__(self):
+        return "<%s=%f>" % (self.kloc, self.kpower)    # For debugging
+
 class gamestate:
     def __init__(self):
         self.options = None    # Game options
         self.state = snapshot()        # A snapshot structure
         self.snapsht = snapshot()      # Last snapshot taken for time-travel purposes
         self.quad = fill2d(QUADSIZE, lambda i, j: IHDOT)       # contents of our quadrant
-        self.kpower = fill2d(QUADSIZE, lambda i, j: 0.0)       # enemy energy levels
-        self.kdist = fill2d(QUADSIZE, lambda i, j: 0.0)                # enemy distances
-        self.kavgd = fill2d(QUADSIZE, lambda i, j: 0.0)        # average distances
         self.damage = [0.0] * NDEVICES # damage encountered
         self.future = []               # future events
         for i in range(NEVENTS):
             self.future.append(event())
         self.passwd  = None;           # Self Destruct password
-        self.ks = fill2d(QUADSIZE, lambda i, j: coord())       # enemy sector locations
+        self.enemies = []
         self.quadrant = None   # where we are in the large
         self.sector = None     # where we are in the small
         self.tholian = None    # coordinates of Tholian
@@ -471,7 +489,7 @@ class gamestate:
         self.nenhere = 0       # number of enemies in quadrant
         self.irhere = 0                # Romulans in quadrant
         self.isatb = 0         # =1 if super commander is attacking base
-        self.tourn = 0         # tournament number
+        self.tourn = None      # tournament number
         self.proben = 0                # number of moves for probe
         self.nprobes = 0       # number of probes available
         self.inresor = 0.0     # initial resources
@@ -615,20 +633,20 @@ def tryexit(look, ienm, loccom, irun):
            if game.battle == game.quadrant:
                return False
        # don't leave if over 1000 units of energy 
-       if game.kpower[loccom] > 1000.0:
+       if game.enemies[loccom].kpower > 1000.0:
            return False
     # print escape message and move out of quadrant.
     # we know this if either short or long range sensors are working
     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
        game.condition == docked:
-       crmena(True, ienm, "sector", game.ks[loccom])
+       crmena(True, ienm, "sector", game.enemies[loccom].kloc)
        prout(_(" escapes to Quadrant %s (and regains strength).") % q)
     # handle local matters related to escape 
-    game.quad[game.ks[loccom].x][game.ks[loccom].y] = IHDOT
-    game.ks[loccom] = game.ks[game.nenhere]
-    game.kavgd[loccom] = game.kavgd[game.nenhere]
-    game.kpower[loccom] = game.kpower[game.nenhere]
-    game.kdist[loccom] = game.kdist[game.nenhere]
+    game.quad[game.enemies[loccom].kloc.x][game.enemies[loccom].kloc.y] = IHDOT
+    game.enemies[loccom].kloc = game.enemies[game.nenhere].kloc
+    game.enemies[loccom].kavgd = game.enemies[game.nenhere].kavgd
+    game.enemies[loccom].kpower = game.enemies[game.nenhere].kpower
+    game.enemies[loccom].kdist = game.enemies[game.nenhere].kdist
     game.klhere -= 1
     game.nenhere -= 1
     if game.condition != docked:
@@ -702,17 +720,17 @@ def movebaddy(com, loccom, ienm):
     else:
        nbaddys = game.comhere + game.ishere
 
-    dist1 = game.kdist[loccom]
+    dist1 = game.enemies[loccom].kdist
     mdist = int(dist1 + 0.5); # Nearest integer distance 
 
     # If SC, check with spy to see if should hi-tail it 
     if ienm==IHS and \
-       (game.kpower[loccom] <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
+       (game.enemies[loccom].kpower <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
        irun = True
        motion = -QUADSIZE
     else:
        # decide whether to advance, retreat, or hold position 
-       forces = game.kpower[loccom]+100.0*game.nenhere+400*(nbaddys-1)
+       forces = game.enemies[loccom].kpower+100.0*game.nenhere+400*(nbaddys-1)
        if not game.shldup:
            forces += 1000; # Good for enemy if shield is down! 
        if not damaged(DPHASER) or not damaged(DPHOTON):
@@ -748,9 +766,9 @@ def movebaddy(com, loccom, ienm):
                 motion = game.skill
     # calculate preferred number of steps 
     if motion < 0:
-        msteps = -motion
+        nsteps = -motion
     else:
-        msteps = motion
+        nsteps = motion
     if motion > 0 and nsteps > mdist:
        nsteps = mdist; # don't overshoot 
     if nsteps > QUADSIZE:
@@ -840,13 +858,13 @@ def movebaddy(com, loccom, ienm):
     game.quad[next.x][next.y] = ienm
     if next != com:
        # it moved 
-       game.ks[loccom] = next
-       game.kdist[loccom] = game.kavgd[loccom] = distance(game.sector, next)
+       game.enemies[loccom].kloc = next
+       game.enemies[loccom].kdist = game.enemies[loccom].kavgd = distance(game.sector, next)
        if not damaged(DSRSENS) or game.condition == docked:
            proutn("***")
            cramen(ienm)
            proutn(_(" from Sector %s") % com)
-           if game.kdist[loccom] < dist1:
+           if game.enemies[loccom].kdist < dist1:
                proutn(_(" advances to "))
            else:
                proutn(_(" retreats to "))
@@ -860,13 +878,13 @@ def moveklings():
     # and do move
     if game.comhere:
        for i in range(game.nenhere):
-           w = game.ks[i]
+           w = game.enemies[i].kloc
            if game.quad[w.x][w.y] == IHC:
                movebaddy(w, i, IHC)
                break
     if game.ishere:
        for i in range(game.nenhere):
-           w = game.ks[i]
+           w = game.enemies[i].kloc
            if game.quad[w.x][w.y] == IHS:
                movebaddy(w, i, IHS)
                break
@@ -875,7 +893,7 @@ def moveklings():
     # commander(s) do.
     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
        for i in range(game.nenhere):
-           w = game.ks[i]
+           w = game.enemies[i].kloc
            if game.quad[w.x][w.y] == IHK or game.quad[w.x][w.y] == IHR:
                movebaddy(w, i, game.quad[w.x][w.y])
     sortklings();
@@ -905,13 +923,13 @@ def movescom(iq, avoid):
        game.ientesc = False
        unschedule(FSCDBAS)
        for i in range(game.nenhere):
-           if game.quad[game.ks[i].x][game.ks[i].y] == IHS:
+           if game.quad[game.enemies[i].kloc.x][game.enemies[i].kloc.y] == IHS:
                break
-       game.quad[game.ks[i].x][game.ks[i].y] = IHDOT
-       game.ks[i] = game.ks[game.nenhere]
-       game.kdist[i] = game.kdist[game.nenhere]
-       game.kavgd[i] = game.kavgd[game.nenhere]
-       game.kpower[i] = game.kpower[game.nenhere]
+       game.quad[game.enemies[i].kloc.x][game.enemies[i].kloc.y] = IHDOT
+       game.enemies[i].kloc = game.enemies[game.nenhere].kloc
+       game.enemies[i].kdist = game.enemies[game.nenhere].kdist
+       game.enemies[i].kavgd = game.enemies[game.nenhere].kavgd
+       game.enemies[i].kpower = game.enemies[game.nenhere].kpower
        game.klhere -= 1
        game.nenhere -= 1
        if game.condition!=docked:
@@ -1090,7 +1108,7 @@ def movetholian():
            if game.quad[game.tholian.x][game.tholian.y]==IHDOT:
                game.quad[game.tholian.x][game.tholian.y] = IHWEB
     game.quad[game.tholian.x][game.tholian.y] = IHT
-    game.ks[game.nenhere] = game.tholian
+    game.enemies[game.nenhere].kloc = game.tholian
 
     # check to see if all holes plugged 
     for i in range(QUADSIZE):
@@ -1393,19 +1411,19 @@ def torpedo(course, r, incoming, i, n):
        elif iquad in (IHR, IHK): # Hit a regular enemy 
            # find the enemy 
            for ll in range(game.nenhere):
-               if w == game.ks[ll]:
+               if w == game.enemies[ll].kloc:
                    break
-           kp = math.fabs(game.kpower[ll])
+           kp = math.fabs(game.enemies[ll].kpower)
            h1 = 700.0 + randrange(100) - \
                1000.0 * distance(w, incoming) * math.fabs(math.sin(bullseye-angle))
            h1 = math.fabs(h1)
            if kp < h1:
                h1 = kp
-            if game.kpower[ll] < 0:
-                game.kpower[ll] -= -h1
+            if game.enemies[ll].kpower < 0:
+                game.enemies[ll].kpower -= -h1
             else:
-                game.kpower[ll] -= h1
-           if game.kpower[ll] == 0:
+                game.enemies[ll].kpower -= h1
+           if game.enemies[ll].kpower == 0:
                deadkl(w, iquad, w)
                return None
            crmena(True, iquad, "sector", w)
@@ -1430,7 +1448,7 @@ def torpedo(course, r, incoming, i, n):
                prout(_(" damaged but not destroyed."))
                return None
            proutn(_(" damaged--"))
-           game.ks[ll] = jw
+           game.enemies[ll].kloc = jw
            shoved = True
            break
        elif iquad == IHB: # Hit a base 
@@ -1547,7 +1565,7 @@ def torpedo(course, r, incoming, i, n):
        game.quad[jw.x][jw.y]=iquad
        prout(_(" displaced by blast to Sector %s ") % jw)
        for ll in range(game.nenhere):
-           game.kdist[ll] = game.kavgd[ll] = distance(game.sector,game.ks[ll])
+           game.enemies[ll].kdist = game.enemies[ll].kavgd = distance(game.sector,game.enemies[ll].kloc)
        sortklings()
        return None
     skip(1)
@@ -1556,8 +1574,6 @@ def torpedo(course, r, incoming, i, n):
 
 def fry(hit):
     # critical-hit resolution 
-    ktr=1
-    # a critical hit occured 
     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
        return
     ncrit = int(1.0 + hit/(500.0+randreal(100)))
@@ -1573,17 +1589,14 @@ def fry(hit):
        cdam.append(j)
        extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
        game.damage[j] += extradm
-       if loop1 > 0:
-            for loop2 in range(loop1):
-                if j == cdam[loop2]:
-                    break
-           if loop2 < loop1:
-               continue
-           ktr += 1
-           if ktr==3:
-               skip(1)
-           proutn(_(" and "))
+    skipcount = 0
+    for (i, j) in enumerate(cdam):
        proutn(device[j])
+        if skipcount % 3 == 2 and i < len(cdam)-1:
+            skip()
+        skipcount += 1
+        if i < len(cdam)-1:
+            proutn(_(" and "))
     prout(_(" damaged."))
     if damaged(DSHIELD) and game.shldup:
        prout(_("***Shields knocked down."))
@@ -1623,16 +1636,16 @@ def attack(torps_ok):
     if game.skill <= SKILL_FAIR:
        where = "sector"
     for loop in range(game.nenhere):
-       if game.kpower[loop] < 0:
+       if game.enemies[loop].kpower < 0:
            continue;   # too weak to attack 
        # compute hit strength and diminish shield power 
        r = randreal()
        # Increase chance of photon torpedos if docked or enemy energy low 
        if game.condition == "docked":
            r *= 0.25
-       if game.kpower[loop] < 500:
+       if game.enemies[loop].kpower < 500:
            r *= 0.25; 
-       jay = game.ks[loop]
+       jay = game.enemies[loop].kloc
        iquad = game.quad[jay.x][jay.y]
        if iquad==IHT or (iquad==IHQUEST and not iqengry):
            continue
@@ -1648,8 +1661,8 @@ def attack(torps_ok):
                continue; # Don't waste the effort! 
            attempt = True; # Attempt to attack 
            dustfac = 0.8 + randreal(0.5)
-           hit = game.kpower[loop]*math.pow(dustfac,game.kavgd[loop])
-           game.kpower[loop] *= 0.75
+           hit = game.enemies[loop].kpower*math.pow(dustfac,game.enemies[loop].kavgd)
+           game.enemies[loop].kpower *= 0.75
        else: # Enemy uses photon torpedo 
            course = 1.90985*math.atan2(game.sector.y-jay.y, jay.x-game.sector.x)
            hit = 0
@@ -1660,7 +1673,7 @@ def attack(torps_ok):
            attempt = True
            prout("  ")
            r = (randreal()+randreal())*0.5 - 0.5
-           r += 0.002*game.kpower[loop]*r
+           r += 0.002*game.enemies[loop].kpower*r
            hit = torpedo(course, r, jay, 1, 1)
            if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
                finish(FWON); # Klingons did themselves in! 
@@ -1738,7 +1751,7 @@ def attack(torps_ok):
            game.state.crew -= icas
     # After attack, reset average distance to enemies 
     for loop in range(game.nenhere):
-       game.kavgd[loop] = game.kdist[loop]
+       game.enemies[loop].kavgd = game.enemies[loop].kdist
     sortklings()
     return;
                
@@ -1797,16 +1810,16 @@ def deadkl(w, type, mv):
     if is_scheduled(FCDBAS) and game.battle == game.quadrant and type==IHC:
        unschedule(FCDBAS)
     for i in range(game.nenhere):
-       if game.ks[i] == w:
+       if game.enemies[i].kloc == w:
             for j in range(i, game.nenhere):
-                game.ks[j] = game.ks[j+1]
-                game.kpower[j] = game.kpower[j+1]
-                game.kavgd[j] = game.kdist[j] = game.kdist[j+1]
-            game.ks[game.nenhere].x = 0
-            game.ks[game.nenhere].y = 0
-            game.kdist[game.nenhere] = 0
-            game.kavgd[game.nenhere] = 0
-            game.kpower[game.nenhere] = 0
+                game.enemies[j].kloc = game.enemies[j+1].kloc
+                game.enemies[j].kpower = game.enemies[j+1].kpower
+                game.enemies[j].kavgd = game.enemies[j].kdist = game.enemies[j+1].kdist
+            game.enemies[game.nenhere].kloc.x = 0
+            game.enemies[game.nenhere].kloc.y = 0
+            game.enemies[game.nenhere].kdist = 0
+            game.enemies[game.nenhere].kavgd = 0
+            game.enemies[game.nenhere].kpower = 0
             game.nenhere -= 1
            break
         break
@@ -1978,26 +1991,25 @@ def checkshctrl(rpow):
     return True;
 
 def hittem(hits):
-    # register a phaser hit on Klingons and Romulans 
-    nenhr2 = game.nenhere; kk=1
+    # register a phaser hit on Klingons and Romulans
+    nenhr2 = game.nenhere; kk=0
     w = coord()
     skip(1)
-    for k in range(nenhr2):
-        wham = hits[k]
+    for (k, wham) in enumerate(hits):
        if wham==0:
            continue
        dustfac = randreal(0.9, 1.0)
-       hit = wham*math.pow(dustfac,game.kdist[kk])
-       kpini = game.kpower[kk]
+       hit = wham*math.pow(dustfac,game.enemies[kk].kdist)
+       kpini = game.enemies[kk].kpower
        kp = math.fabs(kpini)
        if PHASEFAC*hit < kp:
            kp = PHASEFAC*hit
-        if game.kpower[kk] < 0:
-            game.kpower[kk] -= -kp
+        if game.enemies[kk].kpower < 0:
+            game.enemies[kk].kpower -= -kp
         else:
-            game.kpower[kk] -= kp
-       kpow = game.kpower[kk]
-       w = game.ks[kk]
+            game.enemies[kk].kpower -= kp
+       kpow = game.enemies[kk].kpower
+       w = game.enemies[kk].kloc
        if hit > 0.005:
            if not damaged(DSRSENS):
                boom(w)
@@ -2016,14 +2028,15 @@ def hittem(hits):
                finish(FWON);           
            if game.alldone:
                return
-           kk -= 1; # don't do the increment 
+           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:
                prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
                prout(_("   has just lost its firepower.\""))
-               game.kpower[kk] = -kpow
+               game.enemies[kk].kpower = -kpow
         kk += 1
-    return;
+    return
 
 def phasers():
     # fire phasers 
@@ -2114,7 +2127,7 @@ def phasers():
            chew()
            if not kz:
                for i in range(game.nenhere):
-                   irec += math.fabs(game.kpower[i])/(PHASEFAC*math.pow(0.90,game.kdist[i]))*randreal(1.01, 1.06) + 1.0
+                   irec += math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
            kz=1
            proutn(_("%d units required. ") % irec)
            chew()
@@ -2150,7 +2163,7 @@ def phasers():
                hits.append(0.0)
                if powrem <= 0:
                    continue
-               hits[i] = math.fabs(game.kpower[i])/(PHASEFAC*math.pow(0.90,game.kdist[i]))
+               hits[i] = math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))
                over = randreal(1.01, 1.06) * hits[i]
                temp = powrem
                powrem -= hits[i] + over
@@ -2187,7 +2200,7 @@ def phasers():
     elif automode == "MANUAL":
        rpow = 0.0
         for k in range(game.nenhere):
-           aim = game.ks[k]
+           aim = game.enemies[k].kloc
            ienm = game.quad[aim.x][aim.y]
            if msgflag:
                proutn(_("Energy available= %.2f") % (avail-0.006))
@@ -2206,7 +2219,7 @@ def phasers():
            if key == IHEOL:
                chew()
                if itarg and k > kz:
-                   irec=(abs(game.kpower[k])/(PHASEFAC*math.pow(0.9,game.kdist[k]))) * randreal(1.01, 1.06) + 1.0
+                   irec=(abs(game.enemies[k].kpower)/(PHASEFAC*math.pow(0.9,game.enemies[k].kdist))) * randreal(1.01, 1.06) + 1.0
                kz = k
                proutn("(")
                if not damaged(DCOMPTR):
@@ -2554,10 +2567,9 @@ def events():
            game.iseenit = True
            announce()
            skip(1)
-           proutn(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
+           prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
            prout(_("   reports that it is under attack and that it can"))
-           proutn(_("   hold out only until stardate %d") % (int(scheduled(FCDBAS))))
-            prout(".\"")
+           prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
            if cancelrest():
                 return
        elif evcode == FSCDBAS: # Supercommander destroys base 
@@ -2712,7 +2724,7 @@ def events():
            q.klingons += 1
            if game.quadrant == w:
                 game.klhere += 1
-               newkling(game.klhere)
+               game.enemies.append(newkling())
            # recompute time left
             game.recompute()
            # report the disaster if we can 
@@ -2882,10 +2894,10 @@ def nova(nov):
                        deadkl(scratch,iquad, scratch)
                     elif iquad in (IHC,IHS,IHR): # Damage/destroy big enemies 
                        for ll in range(game.nenhere):
-                           if game.ks[ll] == scratch:
+                           if game.enemies[ll].kloc == scratch:
                                break
-                       game.kpower[ll] -= 800.0 # If firepower is lost, die 
-                       if game.kpower[ll] <= 0.0:
+                       game.enemies[ll].kpower -= 800.0 # If firepower is lost, die 
+                       if game.enemies[ll].kpower <= 0.0:
                            deadkl(scratch, iquad, scratch)
                            break
                        newc.x = scratch.x + scratch.x - hits[mm][1]
@@ -2910,8 +2922,8 @@ def nova(nov):
                        proutn(_(", buffeted to Sector %s") % newc)
                        game.quad[scratch.x][scratch.y] = IHDOT
                        game.quad[newc.x][newc.y] = iquad
-                       game.ks[ll] = newc
-                       game.kdist[ll] = game.kavgd[ll] = distance(game.sector, newc)
+                       game.enemies[ll].kloc = newc
+                       game.enemies[ll].kdist = game.enemies[ll].kavgd = distance(game.sector, newc)
                        skip(1)
        if top == top2: 
            break
@@ -3109,8 +3121,8 @@ def kaboom():
        whammo = 25.0 * game.energy
        l=1
        while l <= game.nenhere:
-           if game.kpower[l]*game.kdist[l] <= whammo: 
-               deadkl(game.ks[l], game.quad[game.ks[l].x][game.ks[l].y], game.ks[l])
+           if game.enemies[l].kpower*game.enemies[l].kdist <= whammo: 
+               deadkl(game.enemies[l].kloc, game.quad[game.enemies[l].kloc.x][game.enemies[l].kloc.y], game.enemies[l].kloc)
            l += 1
     finish(FDILITHIUM)
                                
@@ -3631,13 +3643,15 @@ def prout(line):
 def prouts(line):
     "print slowly!" 
     for c in line:
-       time.sleep(0.03)
+        if not replayfp or replayfp.closed:    # Don't slow down replays
+            time.sleep(0.03)
        proutn(c)
        if game.options & OPTION_CURSES:
            wrefresh(curwnd)
        else:
            sys.stdout.flush()
-    time.sleep(0.03)
+    if not replayfp or replayfp.closed:
+        time.sleep(0.03)
 
 def cgetline():
     "Get a line of input."
@@ -3648,6 +3662,7 @@ def cgetline():
        if replayfp and not replayfp.closed:
             while True:
                 line = replayfp.readline()
+                proutn(line)
                 if line == '':
                     prout("*** Replay finished")
                     replayfp.close()
@@ -3748,7 +3763,7 @@ def drawmaps(mode):
            setwnd(lrscan_window)
            lrscan_window.clear()
            lrscan_window.move(0, 0)
-           lrscan()
+           lrscan(silent=False)
 
 def put_srscan_sym(w, sym):
     "Emit symbol for short-range scan."
@@ -3853,14 +3868,14 @@ def imove(novapush):
         game.quad[game.sector.x][game.sector.y] = game.ship
         if game.nenhere:
             for m in range(game.nenhere):
-                finald = distance(w, game.ks[m])
-                game.kavgd[m] = 0.5 * (finald+game.kdist[m])
-                game.kdist[m] = finald
+                finald = distance(w, game.enemies[m].kloc)
+                game.enemies[m].kavgd = 0.5 * (finald+game.enemies[m].kdist)
+                game.enemies[m].kdist = finald
             sortklings()
             if not game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova:
                 attack(False)
             for m in range(game.nenhere):
-                game.kavgd[m] = game.kdist[m]
+                game.enemies[m].kavgd = game.enemies[m].kdist
         newcnd()
         drawmaps(0)
         setwnd(message_window)
@@ -3900,8 +3915,8 @@ def imove(novapush):
                if game.nenhere != 0 and not novapush:
                    newcnd()
                    for m in range(game.nenhere):
-                       finald = distance(w, game.ks[m])
-                       game.kavgd[m] = 0.5 * (finald + game.kdist[m])
+                       finald = distance(w, game.enemies[m].kloc)
+                       game.enemies[m].kavgd = 0.5 * (finald + game.enemies[m].kdist)
                    #
                    # Stas Sergeev added the condition
                    # that attacks only happen if Klingons
@@ -5211,7 +5226,7 @@ def deathray():
        prouts(_("Sulu- \"Captain!  It's working!\""))
        skip(2)
        while game.nenhere > 0:
-           deadkl(game.ks[1], game.quad[game.ks[1].x][game.ks[1].y],game.ks[1])
+           deadkl(game.enemies[1].kloc, game.quad[game.enemies[1].kloc.x][game.enemies[1].kloc.y],game.enemies[1].kloc)
        prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
        if (game.state.remkl + game.state.remcom + game.state.nscrem) == 0:
            finish(FWON)    
@@ -5363,30 +5378,34 @@ def report():
                   (i, (_("s"), "")[i==1]))
     skip(1)
        
-def lrscan():
+def lrscan(silent):
     # long-range sensor scan 
     if damaged(DLRSENS):
        # Now allow base's sensors if docked 
        if game.condition != "docked":
-           prout(_("LONG-RANGE SENSORS DAMAGED."))
+            if not silent:
+                prout(_("LONG-RANGE SENSORS DAMAGED."))
            return
-       prout(_("Starbase's long-range scan"))
-    else:
+        if not silent:
+            prout(_("Starbase's long-range scan"))
+    elif not silent:
        prout(_("Long-range scan"))
     for x in range(game.quadrant.x-1, game.quadrant.x+2):
-        proutn(" ")
+        if not silent:
+            proutn(" ")
         for y in range(game.quadrant.y-1, game.quadrant.y+2):
            if not VALID_QUADRANT(x, y):
-               proutn("  -1")
+                if not silent:
+                    proutn("  -1")
            else:
                if not damaged(DRADIO):
                    game.state.galaxy[x][y].charted = True
                game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
                game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
                game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
-               if game.state.galaxy[x][y].supernova: 
+               if not silent and game.state.galaxy[x][y].supernova: 
                    proutn(" ***")
-               else:
+               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))
        prout(" ")
 
@@ -5420,6 +5439,8 @@ def rechart():
 def chart():
     # display the star chart  
     chew()
+    if (game.options & OPTION_AUTOSCAN):
+        lrscan(silent=True)
     if not damaged(DRADIO):
        rechart()
     if game.lastchart < game.state.date and game.condition == "docked":
@@ -5979,10 +6000,10 @@ def setup(needprompt):
                 break
        game.state.kscmdr = w
        game.state.galaxy[w.x][w.y].klingons += 1
-    # Place thing (in tournament game, thingx == -1, don't want one!)
+    # Place thing (in tournament game, we don't want one!)
     global thing
-    if thing == None:
-       thing = randplace(GALSIZE)
+    if game.tourn is None:
+        thing = randplace(GALSIZE)
     skip(2)
     game.state.snap = False
     if game.skill == SKILL_NOVICE:
@@ -6154,13 +6175,9 @@ def newcnd():
     if not game.alive:
        game.condition="dead"
 
-def newkling(i):
-    # drop new Klingon into current quadrant 
-    pi = dropin(IHK)
-    game.ks[i] = pi
-    game.kdist[i] = game.kavgd[i] = distance(game.sector, pi)
-    game.kpower[i] = randreal(300, 450) + 25.0*game.skill
-    return pi
+def newkling():
+    # drop new Klingon into current quadrant
+    return enemy(loc=dropin(IHK), power=randreal(300, 450) + 25.0*game.skill)
 
 def newqad(shutup):
     # set up a new state of quadrant, for when we enter or re-enter it 
@@ -6192,32 +6209,30 @@ def newqad(shutup):
     game.nenhere = game.klhere + game.irhere
     # Position Starship
     game.quad[game.sector.x][game.sector.y] = game.ship
+    game.enemies = []
     if q.klingons:
-       w.x = w.y = 0   # quiet a gcc warning 
        # Position ordinary Klingons
        for i in range(game.klhere):
-           w = newkling(i)
+           game.enemies.append(newkling())
        # If we need a commander, promote a Klingon
        for i in range(game.state.remcom):
            if game.state.kcmdr[i] == game.quadrant:
-               break
-                       
-       if i <= game.state.remcom:
-           game.quad[w.x][w.y] = IHC
-           game.kpower[game.klhere] = randreal(950, 1350) + 50.0*game.skill
-           game.comhere = True
+                e = game.enemies[game.klhere-1]
+                game.quad[e.kloc.x][e.kloc.y] = IHC
+                e.kpower = randreal(950,1350) + 50.0*game.skill
+                game.comhere = True
+               break   
        # If we need a super-commander, promote a Klingon
        if game.quadrant == game.state.kscmdr:
-           game.quad[game.ks[0].x][game.ks[0].y] = IHS
-           game.kpower[0] = randreal(1175.0,  1575.0) + 125.0*game.skill
+            e = game.enemies[0]
+           game.quad[e.kloc.x][e.kloc.y] = IHS
+           e.kpower = randreal(1175.0,  1575.0) + 125.0*game.skill
            game.iscate = (game.state.remkl > 1)
            game.ishere = True
     # Put in Romulans if needed
     for i in range(game.klhere, game.nenhere):
-       w = dropin(IHR)
-       game.ks[i] = w
-       game.kdist[i] = game.kavgd[i] = distance(game.sector, w)
-       game.kpower[i] = randreal(400.0, 850.0) + 50.0*game.skill
+        game.enemies.append(enemy(loc=dropin(IHR),
+                                  power=randreal(400.0,850.0)+50.0*game.skill))
     # If quadrant needs a starbase, put it in
     if q.starbase:
        game.base = dropin(IHB)
@@ -6231,9 +6246,8 @@ def newqad(shutup):
     # Check for condition
     newcnd()
     # And finally the stars
-    for i in range(q.stars): 
+    for i in range(q.stars):
        dropin(IHSTAR)
-
     # Check for RNZ
     if game.irhere > 0 and game.klhere == 0:
        game.neutz = True
@@ -6246,15 +6260,12 @@ def newqad(shutup):
            prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
     if shutup==0:
        # Put in THING if needed
-        global thing
        if thing == game.quadrant:
-           w = dropin(IHQUEST)
-           thing = randplace(GALSIZE)
+           e = enemy(dropin(IHQUEST))
+           e.kdist = e.kavgd = distance(game.sector, e.kloc)
+           e.kpower = randreal(6000,6500.0)+250.0*game.skill
+            games.enemies.append(e)
            game.nenhere += 1
-           game.ks[game.nenhere] = w
-           game.kdist[game.nenhere] = game.kavgd[game.nenhere] = \
-               distance(game.sector, w)
-           game.kpower[game.nenhere] = randreal(6000,6500.0)+250.0*game.skill
            if not damaged(DSRSENS):
                skip(1)
                prout(_("Mr. Spock- \"Captain, this is most unusual."))
@@ -6271,11 +6282,9 @@ def newqad(shutup):
                 if game.quad[game.tholian.x][game.tholian.y] == IHDOT:
                     break
            game.quad[game.tholian.x][game.tholian.y] = IHT
+            game.enemies.append(loc=game.tholian,
+                                power=randrange(100, 500) + 25.0*game.skill)
            game.nenhere += 1
-           game.ks[game.nenhere] = game.tholian
-           game.kdist[game.nenhere] = game.kavgd[game.nenhere] = \
-               distance(game.sector, game.tholian)
-           game.kpower[game.nenhere] = randrange(100, 500) + 25.0*game.skill
            # Reserve unoccupied corners 
            if game.quad[0][0]==IHDOT:
                game.quad[0][0] = 'X'
@@ -6308,24 +6317,24 @@ def sortklings():
        return
     while True:
        sw = False
-       for j in range(game.nenhere):
-           if game.kdist[j] > game.kdist[j+1]:
+       for j in range(game.nenhere-1):
+           if game.enemies[j].kdist > game.enemies[j+1].kdist:
                sw = True
-               t = game.kdist[j]
-               game.kdist[j] = game.kdist[j+1]
-               game.kdist[j+1] = t
-               t = game.kavgd[j]
-               game.kavgd[j] = game.kavgd[j+1]
-               game.kavgd[j+1] = t
-               k = game.ks[j].x
-               game.ks[j].x = game.ks[j+1].x
-               game.ks[j+1].x = k
-               k = game.ks[j].y
-               game.ks[j].y = game.ks[j+1].y
-               game.ks[j+1].y = k
-               t = game.kpower[j]
-               game.kpower[j] = game.kpower[j+1]
-               game.kpower[j+1] = t
+               t = game.enemies[j].kdist
+               game.enemies[j].kdist = game.enemies[j+1].kdist
+               game.enemies[j+1].kdist = t
+               t = game.enemies[j].kavgd
+               game.enemies[j].kavgd = game.enemies[j+1].kavgd
+               game.enemies[j+1].kavgd = t
+               k = game.enemies[j].kloc.x
+               game.enemies[j].kloc.x = game.enemies[j+1].kloc.x
+               game.enemies[j+1].kloc.x = k
+               k = game.enemies[j].kloc.y
+               game.enemies[j].kloc.y = game.enemies[j+1].kloc.y
+               game.enemies[j+1].kloc.y = k
+               t = game.enemies[j].kpower
+               game.enemies[j].kpower = game.enemies[j+1].kpower
+               game.enemies[j+1].kpower = t
         if not sw:
             break
 
@@ -6501,7 +6510,7 @@ def makemoves():
        elif cmd == "REQUEST":          # status request 
            request()
        elif cmd == "LRSCAN":           # long range scan
-           lrscan()
+           lrscan(silent=False)
        elif cmd == "PHASERS":          # phasers
            phasers()
            if game.ididit:
@@ -6830,84 +6839,89 @@ def debugme():
        atover(True)
 
 if __name__ == '__main__':
-    global line, thing, game, idebug, iqengry
-    game = citem = aaitem = inqueue = None
-    line = ''
-    thing = coord()
-    iqengry = False
-    game = gamestate()
-    idebug = 0
-    game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_SHOWME | OPTION_PLAIN | OPTION_ALMY)
-    # Disable curses mode until the game logic is working.
-    #    if os.getenv("TERM"):
-    #  game.options |= OPTION_CURSES | OPTION_SHOWME
-    #    else:
-    game.options |= OPTION_TTY
-    seed = int(time.time())
-    (options, arguments) = getopt.getopt(sys.argv[1:], "r:tx")
-    for (switch, val) in options:
-        if switch == '-r':
-            try:
-                replayfp = open(val, "r")
-            except IOError:
-               sys.stderr.write("sst: can't open replay file %s\n" % val)
-               raise SystemExit, 1
-            try:
-                line = replayfp.readline().strip()
-                (leader, key, seed) = line.split()
-                seed = eval(seed)
-                sys.stderr.write("sst2k: seed set to %s\n" % seed)
-                line = replayfp.readline().strip()
-                arguments += line.split()[2:]
-            except ValueError:
-               sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
-               os.exit(1)
-           game.options |= OPTION_TTY
-           game.options &=~ OPTION_CURSES
-       elif switch == '-t':
-           game.options |= OPTION_TTY
-           game.options &=~ OPTION_CURSES
-       elif switch == '-x':
-           idebug = True
-       else:
-           sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
-           os.exit(0)
-    # where to save the input in case of bugs
     try:
-        logfp = open("/usr/tmp/sst-input.log", "w")
-    except IOError:
-        sys.stderr.write("sst: warning, can't open logfile\n")
-    if logfp:
-       logfp.write("# seed %s\n" % seed)
-       logfp.write("# options %s\n" % " ".join(arguments))
-    random.seed(seed)
-    iostart()
-    if arguments:
-        inqueue = arguments
-    else:
-        inqueue = None
-    while True: # Play a game 
-       setwnd(fullscreen_window)
-       clrscr()
-       prelim()
-       setup(needprompt=not inqueue)
-       if game.alldone:
-           score()
-           game.alldone = False
-       else:
-           makemoves()
-       skip(1)
-       stars()
-       skip(1)
-       if game.tourn and game.alldone:
-           proutn(_("Do you want your score recorded?"))
-           if ja() == True:
-               chew2()
-               freeze(False)
-        chew()
-       proutn(_("Do you want to play again? "))
-       if not ja():
-           break
-    skip(1)
-    prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
-    raise SystemExit, 0
+        global line, thing, game, idebug, iqengry
+        game = citem = aaitem = inqueue = None
+        line = ''
+        thing = coord()
+        iqengry = False
+        game = gamestate()
+        idebug = 0
+        game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
+        # Disable curses mode until the game logic is working.
+        #    if os.getenv("TERM"):
+        #      game.options |= OPTION_CURSES | OPTION_SHOWME
+        #    else:
+        game.options |= OPTION_TTY
+        seed = int(time.time())
+        (options, arguments) = getopt.getopt(sys.argv[1:], "r:tx")
+        for (switch, val) in options:
+            if switch == '-r':
+                try:
+                    replayfp = open(val, "r")
+                except IOError:
+                    sys.stderr.write("sst: can't open replay file %s\n" % val)
+                    raise SystemExit, 1
+                try:
+                    line = replayfp.readline().strip()
+                    (leader, key, seed) = line.split()
+                    seed = eval(seed)
+                    sys.stderr.write("sst2k: seed set to %s\n" % seed)
+                    line = replayfp.readline().strip()
+                    arguments += line.split()[2:]
+                except ValueError:
+                    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 == '-t':
+                game.options |= OPTION_TTY
+                game.options &=~ OPTION_CURSES
+            elif switch == '-x':
+                idebug = True
+            else:
+                sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
+                raise SystemExit, 1
+        # where to save the input in case of bugs
+        try:
+            logfp = open("/usr/tmp/sst-input.log", "w")
+        except IOError:
+            sys.stderr.write("sst: warning, can't open logfile\n")
+        if logfp:
+            logfp.write("# seed %s\n" % seed)
+            logfp.write("# options %s\n" % " ".join(arguments))
+        random.seed(seed)
+        iostart()
+        if arguments:
+            inqueue = arguments
+        else:
+            inqueue = None
+        while True: # Play a game 
+            setwnd(fullscreen_window)
+            clrscr()
+            prelim()
+            setup(needprompt=not inqueue)
+            if game.alldone:
+                score()
+                game.alldone = False
+            else:
+                makemoves()
+            skip(1)
+            stars()
+            skip(1)
+            if game.tourn and game.alldone:
+                proutn(_("Do you want your score recorded?"))
+                if ja() == True:
+                    chew2()
+                    freeze(False)
+            chew()
+            proutn(_("Do you want to play again? "))
+            if not ja():
+                break
+        skip(1)
+        prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
+        raise SystemExit, 0
+    except KeyboardInterrupt:
+        print""
+        pass
+