Fix Tholian motion.
[super-star-trek.git] / src / sst.py
index eb440297d091e5b190d6dfa8d598b9c26c716c3f..dff69d5235b98837f1d9530aaf557042992c6db9 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
 
@@ -193,6 +200,7 @@ MAXKLGAME   = 127
 MAXKLQUAD      = 9
 FULLCREW       = 428   # BSD Trek was 387, that's wrong 
 FOREVER        = 1e30
+MAXBURST       = 3
 
 # These functions hide the difference between 0-origin and 1-origin addressing.
 def VALID_QUADRANT(x, y):      return ((x)>=0 and (x)<GALSIZE and (y)>=0 and (y)<GALSIZE)
@@ -240,7 +248,7 @@ class coord:
     def __add__(self, other):
         return coord(self.x+self.x, self.y+self.y)
     def __sub__(self, other):
-        return coord(self.x-self.x, self.y-self.y)
+        return coord(self.x-other.x, self.y-other.y)
     def distance(self, other):
         return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
     def sgn(self):
@@ -262,6 +270,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 +352,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,24 +416,49 @@ NEVENTS   = 12
 # 
 def findevent(evtype): return game.future[evtype]
 
+class enemy:
+    def __init__(self, type=None, loc=None, power=None):
+        self.type = type
+        self.kloc = coord()
+        if loc:
+            self.move(loc)
+        self.kpower = power    # enemy energy level
+        game.enemies.append(self)
+    def move(self, loc):
+        motion = (loc != self.kloc)
+        if self.kloc.x is not None and self.kloc.y is not None:
+            if motion:
+                if self.type == IHT:
+                    game.quad[self.kloc.x][self.kloc.y] = IHWEB
+                else:
+                    game.quad[self.kloc.x][self.kloc.y] = IHDOT
+        if loc:
+            self.kloc = copy.copy(loc)
+            game.quad[self.kloc.x][self.kloc.y] = self.type
+            self.kdist = self.kavgd = distance(game.sector, loc)
+        else:
+            self.kloc = coord()
+            self.kdist = self.kavgd = None
+            game.enemies.remove(self)
+        return motion
+    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.quad = None       # contents of our quadrant
         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
+        self.tholian = None    # Tholian enemy object
         self.base = None       # position of base in current quadrant
         self.battle = None     # base coordinates being attacked
         self.plnet = None      # location of planet in quadrant
@@ -468,10 +504,9 @@ class gamestate:
         self.nkinks = 0                # count of energy-barrier crossings
         self.iplnet = None     # planet # in quadrant
         self.inplan = 0                # initial planets
-        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
@@ -550,25 +585,6 @@ FTRIBBLE = 19
 FHOLE = 20
 FCREW = 21
 
-# From enumerated type 'COLORS'
-DEFAULT = 0
-BLACK = 1
-BLUE = 2
-GREEN = 3
-CYAN = 4
-RED = 5
-MAGENTA = 6
-BROWN = 7
-LIGHTGRAY = 8
-DARKGRAY = 9
-LIGHTBLUE = 10
-LIGHTGREEN = 11
-LIGHTCYAN = 12
-LIGHTRED = 13
-LIGHTMAGENTA = 14
-YELLOW = 15
-WHITE = 16
-
 # Log the results of pulling random numbers so we can check determinism.
 
 import traceback
@@ -594,7 +610,7 @@ def randreal(*args):
 
 # Code from ai.c begins here
 
-def tryexit(look, ienm, loccom, irun):
+def tryexit(enemy, look, irun):
     # a bad guy attempts to bug out 
     iq = coord()
     iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1
@@ -603,11 +619,11 @@ def tryexit(look, ienm, loccom, irun):
        game.state.galaxy[iq.x][iq.y].supernova or \
        game.state.galaxy[iq.x][iq.y].klingons > MAXKLQUAD-1:
        return False; # no can do -- neg energy, supernovae, or >MAXKLQUAD-1 Klingons 
-    if ienm == IHR:
+    if enemy.type == IHR:
        return False; # Romulans cannot escape! 
     if not irun:
        # avoid intruding on another commander's territory 
-       if ienm == IHC:
+       if enemy.type == IHC:
            for n in range(game.state.remcom):
                if game.state.kcmdr[n] == iq:
                    return False
@@ -615,28 +631,23 @@ 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 enemy.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])
+       game.condition == "docked":
+       crmena(True, enemy.type, "sector", enemy.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]
+    # handle local matters related to escape
+    enemy.move(None)
     game.klhere -= 1
-    game.nenhere -= 1
-    if game.condition != docked:
+    if game.condition != "docked":
        newcnd()
     # Handle global matters related to escape 
     game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons -= 1
     game.state.galaxy[iq.x][iq.y].klingons += 1
-    if ienm==IHS:
+    if enemy.type==IHS:
        game.ishere = False
        game.iscate = False
        game.ientesc = False
@@ -692,7 +703,7 @@ def tryexit(look, ienm, loccom, irun):
 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
 # 
 
-def movebaddy(com, loccom, ienm):
+def movebaddy(enemy):
     # tactical movement for the bad guys 
     next = coord(); look = coord()
     irun = False
@@ -701,18 +712,16 @@ def movebaddy(com, loccom, ienm):
        nbaddys = ((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0)
     else:
        nbaddys = game.comhere + game.ishere
-
-    dist1 = game.kdist[loccom]
+    dist1 = enemy.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))):
+    if enemy.type==IHS and \
+       (enemy.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 = enemy.kpower+100.0*len(game.enemies)+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 +757,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:
@@ -760,35 +769,33 @@ def movebaddy(com, loccom, ienm):
     if idebug:
        proutn("NSTEPS = %d:" % nsteps)
     # Compute preferred values of delta X and Y 
-    mx = game.sector.x - com.x
-    my = game.sector.y - com.y
-    if 2.0 * abs(mx) < abs(my):
-       mx = 0
-    if 2.0 * abs(my) < abs(game.sector.x-com.x):
-       my = 0
-    if mx != 0:
-        if mx*motion < 0:
-            mx = -1
+    m = game.sector - enemy.kloc
+    if 2.0 * abs(m.x) < abs(m.y):
+       m.x = 0
+    if 2.0 * abs(m.y) < abs(game.sector.x-enemy.kloc.x):
+       m.y = 0
+    if m.x != 0:
+        if m.x*motion < 0:
+            m.x = -1
         else:
-            mx = 1
-    if my != 0:
-        if my*motion < 0:
-            my = -1
+            m.x = 1
+    if m.y != 0:
+        if m.y*motion < 0:
+            m.y = -1
         else:
-            my = 1
-    next = com
+            m.y = 1
+    next = enemy.kloc
     # main move loop 
     for ll in range(nsteps):
        if idebug:
            proutn(" %d" % (ll+1))
        # Check if preferred position available 
-       look.x = next.x + mx
-       look.y = next.y + my
-        if mx < 0:
+       look = next + m
+        if m.x < 0:
             krawlx = 1
         else:
             krawlx = -1
-        if my < 0:
+        if m.y < 0:
             krawly = 1
         else:
             krawly = -1
@@ -797,29 +804,29 @@ def movebaddy(com, loccom, ienm):
        while attempts < 20 and not success:
             attempts += 1
            if look.x < 0 or look.x >= QUADSIZE:
-               if motion < 0 and tryexit(look, ienm, loccom, irun):
+               if motion < 0 and tryexit(enemy, look, irun):
                    return
-               if krawlx == mx or my == 0:
+               if krawlx == m.x or m.y == 0:
                    break
                look.x = next.x + krawlx
                krawlx = -krawlx
            elif look.y < 0 or look.y >= QUADSIZE:
-               if motion < 0 and tryexit(look, ienm, loccom, irun):
+               if motion < 0 and tryexit(enemy, look, irun):
                    return
-               if krawly == my or mx == 0:
+               if krawly == m.y or m.x == 0:
                    break
                look.y = next.y + krawly
                krawly = -krawly
            elif (game.options & OPTION_RAMMING) and game.quad[look.x][look.y] != IHDOT:
-               # See if we should ram ship 
+               # See if enemy should ram ship 
                if game.quad[look.x][look.y] == game.ship and \
-                   (ienm == IHC or ienm == IHS):
-                   ram(True, ienm, com)
+                   (enemy.type == IHC or enemy.type == IHS):
+                   collision(rammed=True, enemy=enemy)
                    return
-               if krawlx != mx and my != 0:
+               if krawlx != m.x and m.y != 0:
                    look.x = next.x + krawlx
                    krawlx = -krawlx
-               elif krawly != my and mx != 0:
+               elif krawly != m.y and m.x != 0:
                    look.y = next.y + krawly
                    krawly = -krawly
                else:
@@ -832,21 +839,14 @@ def movebaddy(com, loccom, ienm):
                proutn(`next`)
        else:
            break; # done early 
-       
     if idebug:
        skip(1)
-    # Put commander in place within same quadrant 
-    game.quad[com.x][com.y] = IHDOT
-    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)
-       if not damaged(DSRSENS) or game.condition == docked:
+    if enemy.move(next):
+       if not damaged(DSRSENS) or game.condition == "docked":
            proutn("***")
-           cramen(ienm)
-           proutn(_(" from Sector %s") % com)
-           if game.kdist[loccom] < dist1:
+           cramen(enemy.type)
+           proutn(_(" from Sector %s") % enemy.kloc)
+           if enemy.kdist < dist1:
                proutn(_(" advances to "))
            else:
                proutn(_(" retreats to "))
@@ -859,26 +859,22 @@ def moveklings():
     # Figure out which Klingon is the commander (or Supercommander)
     # and do move
     if game.comhere:
-       for i in range(game.nenhere):
-           w = game.ks[i]
-           if game.quad[w.x][w.y] == IHC:
-               movebaddy(w, i, IHC)
-               break
+        for enemy in game.enemies:
+           if enemy.type == IHC:
+               movebaddy(enemy)
     if game.ishere:
-       for i in range(game.nenhere):
-           w = game.ks[i]
-           if game.quad[w.x][w.y] == IHS:
-               movebaddy(w, i, IHS)
+        for enemy in game.enemies:
+           if enemy.type == IHS:
+               movebaddy(enemy)
                break
     # If skill level is high, move other Klingons and Romulans too!
     # Move these last so they can base their actions on what the
     # commander(s) do.
     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
-       for i in range(game.nenhere):
-           w = game.ks[i]
-           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();
+        for enemy in game.enemies:
+            if enemy.type in (IHK, IHR):
+               movebaddy(enemy)
+    game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
 
 def movescom(iq, avoid):
     # commander movement helper 
@@ -904,19 +900,14 @@ def movescom(iq, avoid):
        game.ishere = False
        game.ientesc = False
        unschedule(FSCDBAS)
-       for i in range(game.nenhere):
-           if game.quad[game.ks[i].x][game.ks[i].y] == IHS:
+       for enemy in game.enemies:
+           if enemy.type == 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]
+       enemy.move(None)
        game.klhere -= 1
-       game.nenhere -= 1
-       if game.condition!=docked:
+       if game.condition != "docked":
            newcnd()
-       sortklings()
+        game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
     # check for a helpful planet 
     for i in range(game.inplan):
        if game.state.planets[i].w == game.state.kscmdr and \
@@ -981,7 +972,7 @@ def supercommander():
                iwhichb = i
                break
        if ifindit==0:
-           return; # Nothing suitable -- wait until next time
+           return # Nothing suitable -- wait until next time
        ibq = game.state.baseq[iwhichb]
        # decide how to move toward base 
        idelta = ibq - game.state.kscmdr
@@ -1021,14 +1012,14 @@ def supercommander():
            if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
                # attack the base 
                if avoid:
-                   return; # no, don't attack base! 
+                   return # no, don't attack base! 
                game.iseenit = False
                game.isatb = 1
                schedule(FSCDBAS, randreal(1.0, 3.0))
                if is_scheduled(FCDBAS):
                    postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
                if not communicating():
-                   return; # no warning 
+                   return # no warning 
                game.iseenit = True
                announce()
                prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
@@ -1053,45 +1044,43 @@ def supercommander():
     announce()
     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
-    return;
+    return
 
 def movetholian():
-    # move the Tholian 
+    # move the Tholian
     if not game.tholian or game.justin:
        return
-    if game.tholian.x == 0 and game.tholian.y == 0:
-       idx = 0; idy = QUADSIZE-1
-    elif game.tholian.x == 0 and game.tholian.y == QUADSIZE-1:
-       idx = QUADSIZE-1; idy = QUADSIZE-1
-    elif game.tholian.x == QUADSIZE-1 and game.tholian.y == QUADSIZE-1:
-       idx = QUADSIZE-1; idy = 0
-    elif game.tholian.x == QUADSIZE-1 and game.tholian.y == 0:
-       idx = 0; idy = 0
+    id = coord()
+    if game.tholian.kloc.x == 0 and game.tholian.kloc.y == 0:
+       id.x = 0; id.y = QUADSIZE-1
+    elif game.tholian.kloc.x == 0 and game.tholian.kloc.y == QUADSIZE-1:
+       id.x = QUADSIZE-1; id.y = QUADSIZE-1
+    elif game.tholian.kloc.x == QUADSIZE-1 and game.tholian.kloc.y == QUADSIZE-1:
+       id.x = QUADSIZE-1; id.y = 0
+    elif game.tholian.kloc.x == QUADSIZE-1 and game.tholian.kloc.y == 0:
+       id.x = 0; id.y = 0
     else:
        # something is wrong! 
-       game.ithere = False
+       game.tholian.move(None)
+        prout("***Internal error: Tholian in a bad spot.")
        return
     # do nothing if we are blocked 
-    if game.quad[idx][idy]!= IHDOT and game.quad[idx][idy]!= IHWEB:
+    if game.quad[id.x][id.y] not in (IHDOT, IHWEB):
        return
-    game.quad[game.tholian.x][game.tholian.y] = IHWEB
-    if game.tholian.x != idx:
-       # move in x axis 
-       im = math.fabs(idx - game.tholian.x)*1.0/(idx - game.tholian.x)
-       while game.tholian.x != idx:
-           game.tholian.x += im
-           if game.quad[game.tholian.x][game.tholian.y]==IHDOT:
-               game.quad[game.tholian.x][game.tholian.y] = IHWEB
-    elif game.tholian.y != idy:
-       # move in y axis 
-       im = math.fabs(idy - game.tholian.y)*1.0/(idy - game.tholian.y)
-       while game.tholian.y != idy:
-           game.tholian.y += im
-           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
-
+    here = copy.copy(game.tholian.kloc)
+    delta = (id - game.tholian.kloc).sgn()
+    # move in x axis 
+    while here.x != id.x:
+        #print "Moving in X", delta
+        here.x += delta.x
+        if game.quad[here.kloc.x][here.y]==IHDOT:
+            game.tholian.move(here)
+    # move in y axis 
+    while here.y != id.y:
+        #print "Moving in Y", delta
+        here.y += delta.y
+        if game.quad[here.x][here.y]==IHDOT:
+            game.tholian.move(here)
     # check to see if all holes plugged 
     for i in range(QUADSIZE):
        if game.quad[0][i]!=IHWEB and game.quad[0][i]!=IHT:
@@ -1103,12 +1092,12 @@ def movetholian():
        if game.quad[i][QUADSIZE]!=IHWEB and game.quad[i][QUADSIZE]!=IHT:
            return
     # All plugged up -- Tholian splits 
-    game.quad[game.tholian.x][game.tholian.y]=IHWEB
+    game.quad[game.tholian.kloc.x][game.tholian.kloc.y]=IHWEB
     dropin(IHBLANK)
     crmena(True, IHT, "sector", game.tholian)
     prout(_(" completes web."))
-    game.tholian = None
-    game.nenhere -= 1
+    game.tholian.move(None)
+    print "Tholian movement ends"
     return
 
 # Code from battle.c begins here
@@ -1272,24 +1261,24 @@ def randdevice():
            return i
     return None;       # we should never get here
 
-def ram(ibumpd, ienm, w):
-    # make our ship ram something 
+def collision(rammed, enemy):
+    # collision handling
     prouts(_("***RED ALERT!  RED ALERT!"))
     skip(1)
     prout(_("***COLLISION IMMINENT."))
     skip(2)
     proutn("***")
     crmshp()
-    hardness = {IHR:1.5, IHC:2.0, IHS:2.5, IHT:0.5, IHQUEST:4.0}.get(ienm, 1.0)
-    if ibumpd:
+    hardness = {IHR:1.5, IHC:2.0, IHS:2.5, IHT:0.5, IHQUEST:4.0}.get(enemy.type, 1.0)
+    if rammed:
         proutn(_(" rammed by "))
     else:
         proutn(_(" rams "))
-    crmena(False, ienm, sector, w)
-    if ibumpd:
+    crmena(False, enemy.type, "sector", enemy.kloc)
+    if rammed:
        proutn(_(" (original position)"))
     skip(1)
-    deadkl(w, ienm, game.sector)
+    deadkl(enemy.kloc, enemy.type, game.sector)
     proutn("***")
     crmshp()
     prout(_(" heavily damaged."))
@@ -1317,18 +1306,18 @@ def ram(ibumpd, ienm, w):
        damagereport()
     else:
        finish(FWON)
-    return;
+    return
 
-def torpedo(course, r, incoming, i, n):
+def torpedo(course, dispersion, origin, number, nburst):
     # let a photon torpedo fly 
     iquad = 0
     shoved = False
-    ac = course + 0.25*r
+    ac = course + 0.25*dispersion
     angle = (15.0-ac)*0.5235988
     bullseye = (15.0 - course)*0.5235988
     deltax = -math.sin(angle);
     deltay = math.cos(angle);
-    x = incoming.x; y = incoming.y
+    x = origin.x; y = origin.y
     w = coord(); jw = coord()
     w.x = w.y = jw.x = jw.y = 0
     bigger = max(math.fabs(deltax), math.fabs(deltay))
@@ -1339,15 +1328,15 @@ def torpedo(course, r, incoming, i, n):
     else: 
        setwnd(message_window)
     # Loop to move a single torpedo 
-    for l in range(1, 15+1):
+    for step in range(1, 15+1):
        x += deltax
-       w.x = x + 0.5
+       w.x = int(x + 0.5)
        y += deltay
-       w.y = y + 0.5
+       w.y = int(y + 0.5)
        if not VALID_SECTOR(w.x, w.y):
            break
        iquad=game.quad[w.x][w.y]
-       tracktorpedo(w, l, i, n, iquad)
+       tracktorpedo(w, step, number, nburst, iquad)
        if iquad==IHDOT:
            continue
        # hit something 
@@ -1360,7 +1349,7 @@ def torpedo(course, r, incoming, i, n):
            crmshp()
            prout(".")
            hit = 700.0 + randreal(100) - \
-               1000.0 * distance(w, incoming) * math.fabs(math.sin(bullseye-angle))
+               1000.0 * distance(w, origin) * math.fabs(math.sin(bullseye-angle))
            newcnd(); # we're blown out of dock 
            # We may be displaced. 
            if game.landed or game.condition=="docked":
@@ -1371,8 +1360,8 @@ def torpedo(course, r, incoming, i, n):
                temp = math.fabs(math.cos(ang))
            xx = -math.sin(ang)/temp
            yy = math.cos(ang)/temp
-           jw.x=w.x+xx+0.5
-           jw.y=w.y+yy+0.5
+           jw.x = int(w.x+xx+0.5)
+           jw.y = int(w.y+yy+0.5)
            if not VALID_SECTOR(jw.x, jw.y):
                return hit
            if game.quad[jw.x][jw.y]==IHBLANK:
@@ -1386,26 +1375,26 @@ def torpedo(course, r, incoming, i, n):
            shoved = True
        elif iquad in (IHC, IHS): # Hit a commander 
            if withprob(0.05):
-               crmena(True, iquad, sector, w)
+               crmena(True, iquad, "sector", w)
                prout(_(" uses anti-photon device;"))
                prout(_("   torpedo neutralized."))
                return None
        elif iquad in (IHR, IHK): # Hit a regular enemy 
            # find the enemy 
-           for ll in range(game.nenhere):
-               if w == game.ks[ll]:
+            for enemy in game.enemies:
+               if w == game.enemies[ll].kloc:
                    break
-           kp = math.fabs(game.kpower[ll])
+           kp = math.fabs(e.kpower)
            h1 = 700.0 + randrange(100) - \
-               1000.0 * distance(w, incoming) * math.fabs(math.sin(bullseye-angle))
+               1000.0 * distance(w, origin) * 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 enemy.kpower < 0:
+                enemy.kpower -= -h1
             else:
-                game.kpower[ll] -= h1
-           if game.kpower[ll] == 0:
+                enemy.kpower -= h1
+           if enemy.kpower == 0:
                deadkl(w, iquad, w)
                return None
            crmena(True, iquad, "sector", w)
@@ -1416,8 +1405,8 @@ def torpedo(course, r, incoming, i, n):
                temp = math.fabs(math.cos(ang))
            xx = -math.sin(ang)/temp
            yy = math.cos(ang)/temp
-           jw.x=w.x+xx+0.5
-           jw.y=w.y+yy+0.5
+           jw.x = int(w.x+xx+0.5)
+           jw.y = int(w.y+yy+0.5)
            if not VALID_SECTOR(jw.x, jw.y):
                prout(_(" damaged but not destroyed."))
                return
@@ -1430,7 +1419,7 @@ def torpedo(course, r, incoming, i, n):
                prout(_(" damaged but not destroyed."))
                return None
            proutn(_(" damaged--"))
-           game.ks[ll] = jw
+           enemy.kloc = jw
            shoved = True
            break
        elif iquad == IHB: # Hit a base 
@@ -1449,7 +1438,7 @@ def torpedo(course, r, incoming, i, n):
            newcnd()
            return None
        elif iquad == IHP: # Hit a planet 
-           crmena(True, iquad, sector, w)
+           crmena(True, iquad, "sector", w)
            prout(_(" destroyed."))
            game.state.nplankl += 1
            game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = None
@@ -1462,7 +1451,7 @@ def torpedo(course, r, incoming, i, n):
                finish(FDPLANET)
            return None
        elif iquad == IHW: # Hit an inhabited world -- very bad! 
-           crmena(True, iquad, sector, w)
+           crmena(True, iquad, "sector", w)
            prout(_(" destroyed."))
            game.state.nworldkl += 1
            game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = None
@@ -1480,7 +1469,7 @@ def torpedo(course, r, incoming, i, n):
            if withprob(0.9):
                nova(w)
             else:
-                crmena(True, IHSTAR, sector, w)
+                crmena(True, IHSTAR, "sector", w)
                 prout(_(" unaffected by photon blast."))
            return None
        elif iquad == IHQUEST: # Hit a thingy 
@@ -1500,13 +1489,12 @@ def torpedo(course, r, incoming, i, n):
                # you can shove the Thingy and piss it off.
                # It then becomes an enemy and may fire at you.
                #
-                global iqengry
-               iqengry = True
+               thing.angry = True
                shoved = True
            return None
        elif iquad == IHBLANK: # Black hole 
            skip(1)
-           crmena(True, IHBLANK, sector, w)
+           crmena(True, IHBLANK, "sector", w)
            prout(_(" swallows torpedo."))
            return None
        elif iquad == IHWEB: # hit the web 
@@ -1515,28 +1503,27 @@ def torpedo(course, r, incoming, i, n):
            return None
        elif iquad == IHT:  # Hit a Tholian 
            h1 = 700.0 + randrange(100) - \
-               1000.0 * distance(w, incoming) * math.fabs(math.sin(bullseye-angle))
+               1000.0 * distance(w, origin) * math.fabs(math.sin(bullseye-angle))
            h1 = math.fabs(h1)
            if h1 >= 600:
                game.quad[w.x][w.y] = IHDOT
-               game.tholian = None
                deadkl(w, iquad, w)
+               game.tholian = None
                return None
            skip(1)
-           crmena(True, IHT, sector, w)
+           crmena(True, IHT, "sector", w)
            if withprob(0.05):
                prout(_(" survives photon blast."))
                return None
            prout(_(" disappears."))
+           game.tholian.move(None)
            game.quad[w.x][w.y] = IHWEB
-           game.tholian = None
-           game.nenhere -= 1
            dropin(IHBLANK)
            return None
         else: # Problem!
            skip(1)
            proutn("Don't know how to handle torpedo collision with ")
-           crmena(True, iquad, sector, w)
+           crmena(True, iquad, "sector", w)
            skip(1)
            return None
        break
@@ -1546,9 +1533,9 @@ def torpedo(course, r, incoming, i, n):
        game.quad[w.x][w.y]=IHDOT
        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])
-       sortklings()
+       for ll in range(len(game.enemies)):
+           game.enemies[ll].kdist = game.enemies[ll].kavgd = distance(game.sector,game.enemies[ll].kloc)
+        game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
        return None
     skip(1)
     prout(_("Torpedo missed."))
@@ -1556,8 +1543,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 +1558,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."))
@@ -1591,10 +1573,9 @@ def fry(hit):
 
 def attack(torps_ok):
     # bad guy attacks us 
-    # torps_ok == false forces use of phasers in an attack 
-    atackd = False; attempt = False; ihurt = False;
+    # torps_ok == False forces use of phasers in an attack 
+    attempt = False; ihurt = False;
     hitmax=0.0; hittot=0.0; chgfac=1.0
-    jay = coord()
     where = "neither"
     # game could be over at this point, check 
     if game.alldone:
@@ -1612,7 +1593,7 @@ def attack(torps_ok):
     if (((game.comhere or game.ishere) and not game.justin) or game.skill == SKILL_EMERITUS) and torps_ok:
        moveklings()
     # if no enemies remain after movement, we're done 
-    if game.nenhere==0 or (game.nenhere==1 and thing == game.quadrant and not iqengry):
+    if len(game.enemies)==0 or (len(game.enemies)==1 and thing == game.quadrant and not thing.angry):
        return
     # set up partial hits if attack happens during shield status change 
     pfac = 1.0/game.inshld
@@ -1622,50 +1603,48 @@ def attack(torps_ok):
     # message verbosity control 
     if game.skill <= SKILL_FAIR:
        where = "sector"
-    for loop in range(game.nenhere):
-       if game.kpower[loop] < 0:
+    for enemy in game.enemies:
+       if enemy.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 
+       # Increase chance of photon torpedos if docked or enemy energy is low 
        if game.condition == "docked":
            r *= 0.25
-       if game.kpower[loop] < 500:
+       if enemy.kpower < 500:
            r *= 0.25; 
-       jay = game.ks[loop]
-       iquad = game.quad[jay.x][jay.y]
-       if iquad==IHT or (iquad==IHQUEST and not iqengry):
+       if enemy.type==IHT or (enemy.type==IHQUEST and not thing.angry):
            continue
        # different enemies have different probabilities of throwing a torp 
        usephasers = not torps_ok or \
-           (iquad == IHK and r > 0.0005) or \
-           (iquad==IHC and r > 0.015) or \
-           (iquad==IHR and r > 0.3) or \
-           (iquad==IHS and r > 0.07) or \
-           (iquad==IHQUEST and r > 0.05)
+           (enemy.type == IHK and r > 0.0005) or \
+           (enemy.type==IHC and r > 0.015) or \
+           (enemy.type==IHR and r > 0.3) or \
+           (enemy.type==IHS and r > 0.07) or \
+           (enemy.type==IHQUEST and r > 0.05)
        if usephasers:      # Enemy uses phasers 
            if game.condition == "docked":
                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 = enemy.kpower*math.pow(dustfac,enemy.kavgd)
+           enemy.kpower *= 0.75
        else: # Enemy uses photon torpedo 
-           course = 1.90985*math.atan2(game.sector.y-jay.y, jay.x-game.sector.x)
+           course = 1.90985*math.atan2(game.sector.y-enemy.kloc.y, enemy.kloc.x-game.sector.x)
            hit = 0
            proutn(_("***TORPEDO INCOMING"))
            if not damaged(DSRSENS):
                proutn(_(" From "))
-               crmena(False, iquad, where, jay)
+               crmena(False, enemy.type, where, enemy.kloc)
            attempt = True
            prout("  ")
-           r = (randreal()+randreal())*0.5 - 0.5
-           r += 0.002*game.kpower[loop]*r
-           hit = torpedo(course, r, jay, 1, 1)
+           dispersion = (randreal()+randreal())*0.5 - 0.5
+           dispersion += 0.002*enemy.kpower*dispersion
+           hit = torpedo(course, dispersion, origin=enemy.kloc, number=1, nburst=1)
            if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
                finish(FWON); # Klingons did themselves in! 
            if game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova or game.alldone:
-               return; # Supernova or finished 
+               return # Supernova or finished 
            if hit == None:
                continue
        # incoming phaser or torpedo, shields may dissipate it 
@@ -1696,7 +1675,7 @@ def attack(torps_ok):
            crmshp()
        if not damaged(DSRSENS) and usephasers:
            proutn(_(" from "))
-           crmena(False, iquad, where, jay)
+           crmena(False, enemy.type, where, enemy.kloc)
        skip(1)
        # Decide if hit is critical 
        if hit > hitmax:
@@ -1710,8 +1689,6 @@ def attack(torps_ok):
        return
     if not attempt and game.condition == "docked":
        prout(_("***Enemies decide against attacking your ship."))
-    if not atackd:
-       return
     percent = 100.0*pfac*game.shield+0.5
     if not ihurt:
        # Shields fully protect ship 
@@ -1729,7 +1706,7 @@ def attack(torps_ok):
     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
     # Check if anyone was hurt 
     if hitmax >= 200 or hittot >= 500:
-       icas = randrange(hittot * 0.015)
+       icas = randrange(int(hittot * 0.015))
        if icas >= 2:
            skip(1)
            prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
@@ -1737,88 +1714,74 @@ def attack(torps_ok):
            game.casual += icas
            game.state.crew -= icas
     # After attack, reset average distance to enemies 
-    for loop in range(game.nenhere):
-       game.kavgd[loop] = game.kdist[loop]
-    sortklings()
-    return;
+    for enemy in game.enemies:
+       enemy.kavgd = enemy.kdist
+    game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+    return
                
 def deadkl(w, type, mv):
     # kill a Klingon, Tholian, Romulan, or Thingy 
     # Added mv to allow enemy to "move" before dying 
-    crmena(True, type, sector, mv)
+    crmena(True, type, "sector", mv)
     # Decide what kind of enemy it is and update appropriately 
     if type == IHR:
-       # chalk up a Romulan 
-       game.state.galaxy[game.quadrant.x][game.quadrant.y].romulans -= 1
-       game.irhere -= 1
-       game.state.nromrem -= 1
+        # chalk up a Romulan 
+        game.state.galaxy[game.quadrant.x][game.quadrant.y].romulans -= 1
+        game.irhere -= 1
+        game.state.nromrem -= 1
     elif type == IHT:
-       # Killed a Tholian 
-       game.tholian = None
+        # Killed a Tholian 
+        game.tholian = None
     elif type == IHQUEST:
-       # Killed a Thingy
-        global iqengry
-       iqengry = False
-       invalidate(thing)
+        # Killed a Thingy
+        global thing
+        thing = None
     else:
-       # Some type of a Klingon 
-       game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons -= 1
-       game.klhere -= 1
-       if type == IHC:
-           game.comhere = False
-           for i in range(game.state.remcom):
-               if game.state.kcmdr[i] == game.quadrant:
-                   break
-           game.state.kcmdr[i] = game.state.kcmdr[game.state.remcom]
-           game.state.kcmdr[game.state.remcom].x = 0
-           game.state.kcmdr[game.state.remcom].y = 0
-           game.state.remcom -= 1
-           unschedule(FTBEAM)
-           if game.state.remcom != 0:
-               schedule(FTBEAM, expran(1.0*game.incom/game.state.remcom))
-       elif type ==  IHK:
-           game.state.remkl -= 1
-       elif type ==  IHS:
-           game.state.nscrem -= 1
-           game.ishere = False
-           game.state.kscmdr.x = game.state.kscmdr.y = game.isatb = 0
-           game.iscate = False
-           unschedule(FSCMOVE)
-           unschedule(FSCDBAS)
-       else:
-           prout("*** Internal error, deadkl() called on %s\n" % type)
+        # Some type of a Klingon 
+        game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons -= 1
+        game.klhere -= 1
+        if type == IHC:
+            game.comhere = False
+            for i in range(game.state.remcom):
+                if game.state.kcmdr[i] == game.quadrant:
+                    break
+            game.state.kcmdr[i] = game.state.kcmdr[game.state.remcom]
+            game.state.kcmdr[game.state.remcom].x = 0
+            game.state.kcmdr[game.state.remcom].y = 0
+            game.state.remcom -= 1
+            unschedule(FTBEAM)
+            if game.state.remcom != 0:
+                schedule(FTBEAM, expran(1.0*game.incom/game.state.remcom))
+            if is_scheduled(FCDBAS) and game.battle == game.quadrant:
+                unschedule(FCDBAS)    
+        elif type ==  IHK:
+            game.state.remkl -= 1
+        elif type ==  IHS:
+            game.state.nscrem -= 1
+            game.ishere = False
+            game.state.kscmdr.x = game.state.kscmdr.y = game.isatb = 0
+            game.iscate = False
+            unschedule(FSCMOVE)
+            unschedule(FSCDBAS)
     # For each kind of enemy, finish message to player 
     prout(_(" destroyed."))
-    game.quad[w.x][w.y] = IHDOT
     if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
        return
     game.recompute()
-    # Remove enemy ship from arrays describing local conditions 
-    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:
-            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.nenhere -= 1
+    # Remove enemy ship from arrays describing local conditions
+    for e in game.enemies:
+       if e.kloc == w:
+            e.move(None)
            break
-        break
-    return;
+    return
 
-def targetcheck(x, y):
+def targetcheck(w):
     # Return None if target is invalid 
-    if not VALID_SECTOR(x, y):
+    if not VALID_SECTOR(w.x, w.y):
        huh()
        return None
-    deltx = 0.1*(y - game.sector.y)
-    delty = 0.1*(x - game.sector.x)
+    deltx = 0.1*(w.y - game.sector.y)
+    delty = 0.1*(w.x - game.sector.x)
     if deltx==0 and delty== 0:
        skip(1)
        prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
@@ -1829,7 +1792,8 @@ def targetcheck(x, y):
     return 1.90985932*math.atan2(deltx, delty)
 
 def photon():
-    # launch photon torpedo 
+    # launch photon torpedo
+    course = [0.0] * MAXBURST
     game.ididit = False
     if damaged(DPHOTON):
        prout(_("Photon tubes damaged."))
@@ -1846,63 +1810,66 @@ def photon():
            return
        elif key == IHEOL:
            prout(_("%d torpedoes left.") % game.torps)
+            chew()
            proutn(_("Number of torpedoes to fire- "))
            key = scan()
        else: # key == IHREAL  {
-           n = aaitem + 0.5
+           n = int(round(aaitem + 0.5))
            if n <= 0: # abort command 
                chew()
                return
-           if n > 3:
+           if n > MAXBURST:
                chew()
-               prout(_("Maximum of 3 torpedoes per burst."))
+               prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
                key = IHEOL
                return
            if n <= game.torps:
                break
            chew()
            key = IHEOL
-    for i in range(1, n+1):
+    targ = []
+    for i in range(MAXBURST):
+        targ.append(coord())
+    for i in range(n):
        key = scan()
-       if i==1 and key == IHEOL:
+       if i==0 and key == IHEOL:
            break;      # we will try prompting 
-       if i==2 and key == IHEOL:
+       if i==1 and key == IHEOL:
            # direct all torpedoes at one target 
            while i <= n:
-               targ[i][1] = targ[1][1]
-               targ[i][2] = targ[1][2]
-               course[i] = course[1]
+               targ[i] = targ[0]
+               course[i] = course[0]
                i += 1
            break
        if key != IHREAL:
            huh()
            return
-       targ[i][1] = aaitem
+       targ[i].x = aaitem
        key = scan()
        if key != IHREAL:
            huh()
            return
-       targ[i][2] = aaitem
-       course[i] = targetcheck(targ[i][1], targ[i][2])
+       targ[i].y = aaitem
+       course[i] = targetcheck(targ[i])
         if course[i] == None:
            return
     chew()
     if i == 1 and key == IHEOL:
        # prompt for each one 
-       for i in range(1, n+1):
-           proutn(_("Target sector for torpedo number %d- ") % i)
+       for i in range(n):
+           proutn(_("Target sector for torpedo number %d- ") % (i+1))
            key = scan()
            if key != IHREAL:
                huh()
                return
-           targ[i][1] = int(aaitem-0.5)
+           targ[i].x = int(aaitem-0.5)
            key = scan()
            if key != IHREAL:
                huh()
                return
-           targ[i][2] = int(aaitem-0.5)
+           targ[i].y = int(aaitem-0.5)
            chew()
-            course[i] = targetcheck(targ[i][1], targ[i][2])
+            course[i] = targetcheck(targ[i])
             if course[i] == None:
                 return
     game.ididit = True
@@ -1910,10 +1877,10 @@ def photon():
     for i in range(n):
        if game.condition != "docked":
            game.torps -= 1
-       r = (randreal()+randreal())*0.5 -0.5
-       if math.fabs(r) >= 0.47:
+       dispersion = (randreal()+randreal())*0.5 -0.5
+       if math.fabs(dispersion) >= 0.47:
            # misfire! 
-           r *= randreal(1.2, 2.2)
+           dispersion *= randreal(1.2, 2.2)
            if n > 0:
                prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
            else:
@@ -1926,8 +1893,8 @@ def photon():
                game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
            break
        if game.shldup or game.condition == "docked":
-           r *= 1.0 + 0.0001*game.shield
-       torpedo(course[i], r, game.sector, i, n)
+           dispersion *= 1.0 + 0.0001*game.shield
+       torpedo(course[i], dispersion, origin=game.sector, number=i, nburst=n)
        if game.alldone or game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova:
            return
     if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
@@ -1962,7 +1929,7 @@ def checkshctrl(rpow):
     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
     skip(2)
     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
-    icas = randrange(hit*0.012)
+    icas = randrange(int(hit*0.012))
     skip(1)
     fry(0.8*hit)
     if icas:
@@ -1978,26 +1945,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 = len(game.enemies); 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)
@@ -2006,8 +1972,7 @@ def hittem(hits):
            proutn(_("Very small hit on "))
        ienm = game.quad[w.x][w.y]
        if ienm==IHQUEST:
-            global iqengry
-           iqengry = True
+           thing.angry = True
        crmena(False, ienm, "sector", w)
        skip(1)
        if kpow == 0:
@@ -2016,14 +1981,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 
@@ -2033,7 +1999,7 @@ def phasers():
     automode = "NOTSET"
     key=0
     skip(1)
-    # SR sensors and Computer are needed fopr automode 
+    # SR sensors and Computer are needed for automode 
     if damaged(DSRSENS) or damaged(DCOMPTR):
        itarg = False
     if game.condition == "docked":
@@ -2061,7 +2027,7 @@ def phasers():
        key=scan()
        if key == IHALPHA:
            if isit("manual"):
-               if game.nenhere==0:
+               if len(game.enemies)==0:
                    prout(_("There is no enemy present to select."))
                    chew()
                    key = IHEOL
@@ -2070,10 +2036,10 @@ def phasers():
                    automode = "MANUAL"
                    key = scan()
            elif isit("automatic"):
-               if (not itarg) and game.nenhere != 0:
+               if (not itarg) and len(game.enemies) != 0:
                    automode = "FORCEMAN"
                else:
-                   if game.nenhere==0:
+                   if len(game.enemies)==0:
                        prout(_("Energy will be expended into space."))
                    automode = "AUTOMATIC"
                    key = scan()
@@ -2083,7 +2049,7 @@ def phasers():
                huh()
                return
        elif key == IHREAL:
-           if game.nenhere==0:
+           if len(game.enemies)==0:
                prout(_("Energy will be expended into space."))
                automode = "AUTOMATIC"
            elif not itarg:
@@ -2092,7 +2058,7 @@ def phasers():
                automode = "AUTOMATIC"
        else:
            # IHEOL 
-           if game.nenhere==0:
+           if len(game.enemies)==0:
                prout(_("Energy will be expended into space."))
                automode = "AUTOMATIC"
            elif not itarg:
@@ -2107,14 +2073,14 @@ def phasers():
        if key == IHALPHA and isit("no"):
            no = True
            key = scan()
-       if key != IHREAL and game.nenhere != 0:
+       if key != IHREAL and len(game.enemies) != 0:
            prout(_("Phasers locked on target. Energy available: %.2f")%avail)
        irec=0
         while True:
            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
+               for i in range(len(game.enemies)):
+                   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()
@@ -2143,14 +2109,14 @@ def phasers():
        chew()
        game.energy -= rpow
        extra = rpow
-       if game.nenhere:
+       if len(game.enemies):
            extra = 0.0
            powrem = rpow
-           for i in range(game.nenhere):
+           for i in range(len(game.enemies)):
                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
@@ -2166,7 +2132,7 @@ def phasers():
        if extra > 0 and not game.alldone:
            if game.tholian:
                proutn(_("*** Tholian web absorbs "))
-               if game.nenhere>0:
+               if len(game.enemies)>0:
                    proutn(_("excess "))
                prout(_("phaser energy."))
            else:
@@ -2186,16 +2152,16 @@ def phasers():
            skip(1)
     elif automode == "MANUAL":
        rpow = 0.0
-        for k in range(game.nenhere):
-           aim = game.ks[k]
+        for k in range(len(game.enemies)):
+           aim = game.enemies[k].kloc
            ienm = game.quad[aim.x][aim.y]
            if msgflag:
                proutn(_("Energy available= %.2f") % (avail-0.006))
                skip(1)
                msgflag = False
                rpow = 0.0
-           if damaged(DSRSENS) and not (abs(game.sector.x-aim.x) < 2 and abs(game.sector.y-aim.y) < 2) and \
-               (ienm == IHC or ienm == IHS):
+           if damaged(DSRSENS) and \
+               not game.sector.distance(aim)<2**0.5 and ienm in (IHC, IHS):
                cramen(ienm)
                prout(_(" can't be located without short range scan."))
                chew()
@@ -2206,7 +2172,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):
@@ -2215,7 +2181,7 @@ def phasers():
                    proutn("??")
                proutn(")  ")
                proutn(_("units to fire at "))
-               crmena(False, ienm, sector, aim)
+               crmena(False, ienm, "sector", aim)
                proutn("-  ")
                key = scan()
            if key == IHALPHA and isit("no"):
@@ -2366,7 +2332,7 @@ def events():
         newqad(False)
         # Adjust finish time to time of tractor beaming 
         fintim = game.state.date+game.optime
-        attack(False)
+        attack(torps_ok=False)
         if game.state.remcom <= 0:
             unschedule(FTBEAM)
         else: 
@@ -2554,10 +2520,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 +2677,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 
@@ -2741,7 +2706,7 @@ def wait():
     origTime = delay = aaitem
     if delay <= 0.0:
        return
-    if delay >= game.state.remtime or game.nenhere != 0:
+    if delay >= game.state.remtime or len(game.enemies) != 0:
        proutn(_("Are you sure? "))
        if ja() == False:
            return
@@ -2754,13 +2719,13 @@ def wait():
            prout(_("%d stardates left.") % int(game.state.remtime))
            return
        temp = game.optime = delay
-       if game.nenhere:
+       if len(game.enemies):
            rtime = randreal(1.0, 2.0)
            if rtime < temp:
                temp = rtime
            game.optime = temp
        if game.optime < delay:
-           attack(False)
+           attack(torps_ok=False)
        if game.alldone:
            return
        events()
@@ -2787,144 +2752,129 @@ def wait():
 def nova(nov):
     # star goes nova 
     course = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
-    newc = coord(); scratch = coord()
+    newc = coord(); neighbor = coord(); bump = coord(0, 0)
     if withprob(0.05):
        # Wow! We've supernova'ed 
        supernova(False, nov)
        return
     # handle initial nova 
     game.quad[nov.x][nov.y] = IHDOT
-    crmena(False, IHSTAR, sector, nov)
+    crmena(False, IHSTAR, "sector", nov)
     prout(_(" novas."))
     game.state.galaxy[game.quadrant.x][game.quadrant.y].stars -= 1
     game.state.starkl += 1
-       
-    # Set up stack to recursively trigger adjacent stars 
-    bot = top = top2 = 1
+    # Set up queue to recursively trigger adjacent stars 
+    hits = [nov]
     kount = 0
-    icx = icy = 0
-    hits[1][1] = nov.x
-    hits[1][2] = nov.y
-    while True:
-       for mm in range(bot, top+1): 
-           for nn in range(1, 3+1):  # nn,j represents coordinates around current 
-               for j in range(1, 3+1):
-                   if j==2 and nn== 2:
-                       continue
-                   scratch.x = hits[mm][1]+nn-2
-                   scratch.y = hits[mm][2]+j-2
-                   if not VALID_SECTOR(scratch.y, scratch.x):
-                       continue
-                   iquad = game.quad[scratch.x][scratch.y]
-                    # Empty space ends reaction
-                    if iquad in (IHDOT, IHQUEST, IHBLANK, IHT, IHWEB):
-                       break
-                   elif iquad == IHSTAR: # Affect another star 
-                       if wthprob(0.05):
-                           # This star supernovas 
-                           scratch = supernova(False)
-                           return
-                       top2 += 1
-                       hits[top2][1]=scratch.x
-                       hits[top2][2]=scratch.y
+    while hits:
+        offset = coord()
+        start = hits.pop()
+        for offset.x in range(-1, 1+1):
+            for offset.y in range(-1, 1+1):
+                if offset.y==0 and offset.x==0:
+                    continue
+                neighbor = start + offset
+                if not VALID_SECTOR(neighbor.y, neighbor.x):
+                    continue
+                iquad = game.quad[neighbor.x][neighbor.y]
+                # Empty space ends reaction
+                if iquad in (IHDOT, IHQUEST, IHBLANK, IHT, IHWEB):
+                    pass
+                elif iquad == IHSTAR: # Affect another star 
+                    if withprob(0.05):
+                        # This star supernovas 
+                        supernova(False)
+                        return
+                    else:
+                        hits.append(neighbor)
                        game.state.galaxy[game.quadrant.x][game.quadrant.y].stars -= 1
                        game.state.starkl += 1
-                       crmena(True, IHSTAR, sector, scratch)
+                       crmena(True, IHSTAR, "sector", neighbor)
                        prout(_(" novas."))
-                       game.quad[scratch.x][scratch.y] = IHDOT
-                   elif iquad == IHP: # Destroy planet 
-                       game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = None
-                       game.state.nplankl += 1
-                       crmena(True, IHP, sector, scratch)
-                       prout(_(" destroyed."))
-                       game.iplnet.pclass = "destroyed"
-                       game.iplnet = None
-                       invalidate(game.plnet)
-                       if game.landed:
-                           finish(FPNOVA)
-                           return
-                       game.quad[scratch.x][scratch.y] = IHDOT
-                   elif iquad == IHB: # Destroy base 
-                       game.state.galaxy[game.quadrant.x][game.quadrant.y].starbase = False
-                       for i in range(game.state.rembase):
-                           if game.state.baseq[i] == game.quadrant: 
-                               break
-                       game.state.baseq[i] = game.state.baseq[game.state.rembase]
-                       game.state.rembase -= 1
-                       invalidate(game.base)
-                       game.state.basekl += 1
-                       newcnd()
-                       crmena(True, IHB, sector, scratch)
-                       prout(_(" destroyed."))
-                       game.quad[scratch.x][scratch.y] = IHDOT
-                   elif iquad in (IHE, IHF): # Buffet ship 
-                       prout(_("***Starship buffeted by nova."))
-                       if game.shldup:
-                           if game.shield >= 2000.0:
-                               game.shield -= 2000.0
-                           else:
-                               diff = 2000.0 - game.shield
-                               game.energy -= diff
-                               game.shield = 0.0
-                               game.shldup = False
-                               prout(_("***Shields knocked out."))
-                               game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
-                       else:
-                           game.energy -= 2000.0
-                       if game.energy <= 0:
-                           finish(FNOVA)
-                           return
-                       # add in course nova contributes to kicking starship
-                       icx += game.sector.x-hits[mm][1]
-                       icy += game.sector.y-hits[mm][2]
-                       kount += 1
-                   elif iquad == IHK: # kill klingon 
-                       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:
-                               break
-                       game.kpower[ll] -= 800.0 # If firepower is lost, die 
-                       if game.kpower[ll] <= 0.0:
-                           deadkl(scratch, iquad, scratch)
-                           break
-                       newc.x = scratch.x + scratch.x - hits[mm][1]
-                       newc.y = scratch.y + scratch.y - hits[mm][2]
-                       crmena(True, iquad, sector, scratch)
-                       proutn(_(" damaged"))
-                       if not VALID_SECTOR(newc.x, newc.y):
-                           # can't leave quadrant 
-                           skip(1)
-                           break
-                       iquad1 = game.quad[newc.x][newc.y]
-                       if iquad1 == IHBLANK:
-                           proutn(_(", blasted into "))
-                           crmena(False, IHBLANK, sector, newc)
-                           skip(1)
-                           deadkl(scratch, iquad, newc)
-                           break
-                       if iquad1 != IHDOT:
-                           # can't move into something else 
-                           skip(1)
-                           break
-                       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)
-                       skip(1)
-       if top == top2: 
-           break
-       bot = top + 1
-       top = top2
-    if kount==0: 
-       return
-
+                        game.quad[neighbor.x][neighbor.y] = IHDOT
+                        kount += 1
+                elif iquad in (IHP, IHW): # Destroy planet 
+                    game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = None
+                    if iquad == IHP:
+                        game.state.nplankl += 1
+                    else:
+                        game.state.worldkl += 1
+                    crmena(True, iquad, "sector", neighbor)
+                    prout(_(" destroyed."))
+                    game.iplnet.pclass = "destroyed"
+                    game.iplnet = None
+                    invalidate(game.plnet)
+                    if game.landed:
+                        finish(FPNOVA)
+                        return
+                    game.quad[neighbor.x][neighbor.y] = IHDOT
+                elif iquad == IHB: # Destroy base 
+                    game.state.galaxy[game.quadrant.x][game.quadrant.y].starbase = False
+                    for i in range(game.state.rembase):
+                        if game.state.baseq[i] == game.quadrant: 
+                            break
+                    game.state.baseq[i] = game.state.baseq[game.state.rembase]
+                    game.state.rembase -= 1
+                    invalidate(game.base)
+                    game.state.basekl += 1
+                    newcnd()
+                    crmena(True, IHB, "sector", neighbor)
+                    prout(_(" destroyed."))
+                    game.quad[neighbor.x][neighbor.y] = IHDOT
+                elif iquad in (IHE, IHF): # Buffet ship 
+                    prout(_("***Starship buffeted by nova."))
+                    if game.shldup:
+                        if game.shield >= 2000.0:
+                            game.shield -= 2000.0
+                        else:
+                            diff = 2000.0 - game.shield
+                            game.energy -= diff
+                            game.shield = 0.0
+                            game.shldup = False
+                            prout(_("***Shields knocked out."))
+                            game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
+                    else:
+                        game.energy -= 2000.0
+                    if game.energy <= 0:
+                        finish(FNOVA)
+                        return
+                    # add in course nova contributes to kicking starship
+                    bump += (game.sector-hits[mm]).sgn()
+                elif iquad == IHK: # kill klingon 
+                    deadkl(neighbor, iquad, neighbor)
+                elif iquad in (IHC,IHS,IHR): # Damage/destroy big enemies 
+                    for ll in range(len(game.enemies)):
+                        if game.enemies[ll].kloc == neighbor:
+                            break
+                    game.enemies[ll].kpower -= 800.0 # If firepower is lost, die 
+                    if game.enemies[ll].kpower <= 0.0:
+                        deadkl(neighbor, iquad, neighbor)
+                        break
+                    newc = neighbor + neighbor - hits[mm]
+                    crmena(True, iquad, "sector", neighbor)
+                    proutn(_(" damaged"))
+                    if not VALID_SECTOR(newc.x, newc.y):
+                        # can't leave quadrant 
+                        skip(1)
+                        break
+                    iquad1 = game.quad[newc.x][newc.y]
+                    if iquad1 == IHBLANK:
+                        proutn(_(", blasted into "))
+                        crmena(False, IHBLANK, "sector", newc)
+                        skip(1)
+                        deadkl(neighbor, iquad, newc)
+                        break
+                    if iquad1 != IHDOT:
+                        # can't move into something else 
+                        skip(1)
+                        break
+                    proutn(_(", buffeted to Sector %s") % newc)
+                    game.quad[neighbor.x][neighbor.y] = IHDOT
+                    game.quad[newc.x][newc.y] = iquad
+                    game.enemies[ll].move(newc)
     # Starship affected by nova -- kick it away. 
     game.dist = kount*0.1
-    icx = sgn(icx)
-    icy = sgn(icy)
-    game.direc = course[3*(icx+1)+icy+2]
+    game.direc = course[3*(bump.x+1)+bump.y+2]
     if game.direc == 0.0:
        game.dist = 0.0
     if game.dist == 0.0:
@@ -3105,12 +3055,12 @@ def kaboom():
     skip(1)
     stars()
     skip(1)
-    if game.nenhere != 0:
+    if len(game.enemies) != 0:
        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])
+       while l <= len(game.enemies):
+           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)
                                
@@ -3232,7 +3182,7 @@ def finish(ifin):
     elif ifin == FBATTLE:
        proutn(_("The "))
        crmshp()
-       prout(_("has been destroyed in battle."))
+       prout(_(" has been destroyed in battle."))
        skip(1)
        prout(_("Dulce et decorum est pro patria mori."))
     elif ifin == FNEG3:
@@ -3548,6 +3498,7 @@ def iostart():
        #noecho()
         global fullscreen_window, srscan_window, report_window, status_window
         global lrscan_window, message_window, prompt_window
+        (rows, columns)   = stdscr.getmaxyx()
        fullscreen_window = stdscr
        srscan_window     = curses.newwin(12, 25, 0,       0)
        report_window     = curses.newwin(11, 0,  1,       25)
@@ -3559,8 +3510,16 @@ def iostart():
        setwnd(fullscreen_window)
        textcolor(DEFAULT)
 
+def textcolor(color):
+    "Set text foreground color.  Presently a stub."
+    pass       # FIXME
+
+def ioend():
+    "Wrap up I/O.  Presently a stub."
+    pass
+
 def waitfor():
-    "wait for user action -- OK to do nothing if on a TTY"
+    "Wait for user action -- OK to do nothing if on a TTY"
     if game.options & OPTION_CURSES:
        stsdcr.getch()
 
@@ -3631,13 +3590,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 +3609,7 @@ def cgetline():
        if replayfp and not replayfp.closed:
             while True:
                 line = replayfp.readline()
+                proutn(line)
                 if line == '':
                     prout("*** Replay finished")
                     replayfp.close()
@@ -3655,9 +3617,9 @@ def cgetline():
                 elif line[0] != "#":
                     break
        else:
-           line = raw_input("COMMAND> ")
+           line = raw_input() + "\n"
     if logfp:
-       logfp.write(line + "\n")
+       logfp.write(line)
     return line
 
 def setwnd(wnd):
@@ -3681,45 +3643,7 @@ def clrscr():
        curwnd.move(0, 0)
        curwnd.refresh()
     linecount = 0
-
-def textcolor(color):
-    "Set the current text color"
-    if game.options & OPTION_CURSES:
-       if color == DEFAULT: 
-           curwnd.attrset(0)
-       elif color == BLACK: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_BLACK))
-       elif color == BLUE: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_BLUE))
-       elif color == GREEN: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_GREEN))
-       elif color == CYAN: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_CYAN))
-       elif color == RED: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_RED))
-       elif color == MAGENTA: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_MAGENTA))
-       elif color == BROWN: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_YELLOW))
-       elif color == LIGHTGRAY: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_WHITE))
-       elif color == DARKGRAY: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_BLACK) | curses.A_BOLD)
-       elif color == LIGHTBLUE: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_BLUE) | curses.A_BOLD)
-       elif color == LIGHTGREEN: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_GREEN) | curses.A_BOLD)
-       elif color == LIGHTCYAN: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_CYAN) | curses.A_BOLD)
-       elif color == LIGHTRED: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_RED) | curses.A_BOLD)
-       elif color == LIGHTMAGENTA: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_MAGENTA) | curses.A_BOLD)
-       elif color == YELLOW: 
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_YELLOW) | curses.A_BOLD)
-       elif color == WHITE:
-           curwnd.attron(curses.COLOR_PAIR(curses.COLOR_WHITE) | curses.A_BOLD)
-
+    
 def highvideo():
     "Set highlight video, if this is reasonable."
     if game.options & OPTION_CURSES:
@@ -3748,7 +3672,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."
@@ -3783,27 +3707,27 @@ def warble():
        #nosound()
         pass
 
-def tracktorpedo(w, l, i, n, iquad):
+def tracktorpedo(w, step, i, n, iquad):
     "Torpedo-track animation." 
     if not game.options & OPTION_CURSES:
-       if l == 1:
+       if step == 1:
            if n != 1:
                skip(1)
                proutn(_("Track for torpedo number %d-  ") % i)
            else:
                skip(1)
                proutn(_("Torpedo track- "))
-       elif l==4 or l==9: 
+       elif step==4 or step==9: 
            skip(1)
-       proutn("%d - %d   " % (w.x, w.y))
+       proutn("%s   " % w)
     else:
        if not damaged(DSRSENS) or game.condition=="docked":
-           if i != 1 and l == 1:
+           if i != 0 and step == 1:
                drawmaps(2)
                time.sleep(0.4)
            if (iquad==IHDOT) or (iquad==IHBLANK):
                put_srscan_sym(w, '+')
-               #sound(l*10)
+               #sound(step*10)
                #time.sleep(0.1)
                #nosound()
                put_srscan_sym(w, iquad)
@@ -3816,7 +3740,7 @@ def tracktorpedo(w, l, i, n, iquad):
                curwnd.attroff(curses.A_REVERSE)
                put_srscan_sym(w, iquad)
        else:
-           proutn("%d - %d   " % (w.x, w.y))
+           proutn("%s   " % w)
 
 def makechart():
     "Display the current galaxy chart."
@@ -3851,16 +3775,16 @@ def imove(novapush):
     def no_quad_change():
         # No quadrant change -- compute new average enemy distances 
         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
-            sortklings()
+        if len(game.enemies):
+            for m in range(len(game.enemies)):
+                finald = distance(w, game.enemies[m].kloc)
+                game.enemies[m].kavgd = 0.5 * (finald+game.enemies[m].kdist)
+                game.enemies[m].kdist = finald
+            game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
             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]
+                attack(torps_ok=False)
+            for m in range(len(game.enemies)):
+                game.enemies[m].kavgd = game.enemies[m].kdist
         newcnd()
         drawmaps(0)
         setwnd(message_window)
@@ -3897,18 +3821,18 @@ def imove(novapush):
            if not VALID_SECTOR(w.x, w.y):
                # Leaving quadrant -- allow final enemy attack 
                # Don't do it if being pushed by Nova 
-               if game.nenhere != 0 and not novapush:
+               if len(game.enemies) != 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])
+                   for m in range(len(game.enemies)):
+                       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
                    # are present and your skill is good.
                    # 
                    if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova:
-                       attack(False)
+                       attack(torps_ok=False)
                    if game.alldone:
                        return
                # compute final position -- new quadrant and sector 
@@ -3958,7 +3882,7 @@ def imove(novapush):
                game.quad[game.sector.x][game.sector.y] = game.ship
                newqad(False)
                if game.skill>SKILL_NOVICE:
-                   attack(False)  
+                   attack(torps_ok=False)  
                return
            iquad = game.quad[w.x][w.y]
            if iquad != IHDOT:
@@ -3967,7 +3891,10 @@ def imove(novapush):
                game.dist = distance(game.sector, w) / (QUADSIZE * 1.0)
                 if iquad in (IHT, IHK, IHC, IHS, IHR, IHQUEST):
                    game.sector = w
-                   ram(False, iquad, game.sector)
+                    for enemy in game.enemies:
+                        if enemy.kloc == game.sector:
+                            break
+                   collision(rammed=False, enemy=enemy)
                    final = game.sector
                elif iquad == IHBLANK:
                    skip(1)
@@ -4673,13 +4600,13 @@ def mayday():
        proutn(_(" attempt to re-materialize "))
        crmshp()
        game.quad[ix][iy]=(IHMATER0,IHMATER1,IHMATER2)[m-1]
-       textcolor(RED)
+       textcolor("red")
        warble()
        if randreal() > probf:
            break
        prout(_("fails."))
        curses.delay_output(500)
-       textcolor(DEFAULT)
+       textcolor(None)
     if m > 3:
        game.quad[ix][iy]=IHQUEST
        game.alive = False
@@ -4688,9 +4615,9 @@ def mayday():
        finish(FMATERIALIZE)
        return
     game.quad[ix][iy]=game.ship
-    textcolor(GREEN)
+    textcolor("green")
     prout(_("succeeds."))
-    textcolor(DEFAULT)
+    textcolor(None)
     dock(False)
     skip(1)
     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
@@ -5178,7 +5105,7 @@ def deathray():
     if game.ship != IHE:
        prout(_("Ye Faerie Queene has no death ray."))
        return
-    if game.nenhere==0:
+    if len(game.enemies)==0:
        prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
        return
     if damaged(DDRAY):
@@ -5210,8 +5137,8 @@ def deathray():
     if r > dprob:
        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])
+       while len(game.enemies) > 0:
+           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 +5290,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 +5351,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":
@@ -5458,15 +5391,15 @@ def sectscan(goodScan, i, j):
     # light up an individual dot in a sector 
     if goodScan or (abs(i-game.sector.x)<= 1 and abs(j-game.sector.y) <= 1):
        if (game.quad[i][j]==IHMATER0) or (game.quad[i][j]==IHMATER1) or (game.quad[i][j]==IHMATER2) or (game.quad[i][j]==IHE) or (game.quad[i][j]==IHF):
-           if game.condition   == "red": textcolor(RED)
-           elif game.condition == "green": textcolor(GREEN)
-           elif game.condition == "yellow": textcolor(YELLOW)
-           elif game.condition == "docked": textcolor(CYAN)
-           elif game.condition == "dead": textcolor(BROWN)
+           if game.condition   == "red": textcolor("red")
+           elif game.condition == "green": textcolor("green")
+           elif game.condition == "yellow": textcolor("yellow")
+           elif game.condition == "docked": textcolor("cyan")
+           elif game.condition == "dead": textcolor("brown")
            if game.quad[i][j] != game.ship: 
                highvideo()
        proutn("%c " % game.quad[i][j])
-       textcolor(DEFAULT)
+       textcolor(None)
     else:
        proutn("- ")
 
@@ -5565,7 +5498,6 @@ def srscan():
            sectscan(goodScan, i, j)
        skip(1)
                        
-                       
 def eta():
     # use computer to get estimated time of arrival for a warp jump 
     w1 = coord(); w2 = coord()
@@ -5979,10 +5911,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:
@@ -6016,10 +5948,10 @@ def setup(needprompt):
        prout(_("  YOU'LL NEED IT."))
     waitfor()
     newqad(False)
-    if game.nenhere - (thing == game.quadrant) - (game.tholian != None):
+    if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
        game.shldup = True
     if game.neutz:     # bad luck to start in a Romulan Neutral Zone
-       attack(False)
+       attack(torps_ok=False)
 
 def choose(needprompt):
     # choose your game type
@@ -6031,7 +5963,7 @@ def choose(needprompt):
        game.length = 0
        if needprompt: # Can start with command line options 
            proutn(_("Would you like a regular, tournament, or saved game? "))
-       scan()
+        print "About to call scan()"
        if len(citem)==0: # Try again
            continue
         if isit("tournament"):
@@ -6134,14 +6066,15 @@ def choose(needprompt):
        game.inbase = game.state.rembase
     return False
 
-def dropin(iquad):
+def dropin(iquad=None):
     # drop a feature on a random dot in the current quadrant 
     w = coord()
     while True:
         w = randplace(QUADSIZE)
         if game.quad[w.x][w.y] == IHDOT:
             break
-    game.quad[w.x][w.y] = iquad
+    if iquad is not None:
+        game.quad[w.x][w.y] = iquad
     return w
 
 def newcnd():
@@ -6154,13 +6087,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(IHK, loc=dropin(), 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 
@@ -6171,14 +6100,13 @@ def newqad(shutup):
     game.ishere = False
     game.irhere = 0
     game.iplnet = 0
-    game.nenhere = 0
     game.neutz = False
     game.inorbit = False
     game.landed = False
     game.ientesc = False
-    global iqengry
-    iqengry = False
     game.iseenit = False
+    # Create a blank quadrant
+    game.quad = fill2d(QUADSIZE, lambda i, j: IHDOT)
     if game.iscate:
        # Attempt to escape Super-commander, so tbeam back!
        game.iscate = False
@@ -6189,35 +6117,31 @@ def newqad(shutup):
        return
     game.klhere = q.klingons
     game.irhere = q.romulans
-    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)
+            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
+    for i in range(game.klhere, len(game.enemies)):
+        enemy(IHR, loc=dropin(), 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)
@@ -6230,10 +6154,6 @@ def newqad(shutup):
            game.plnet = dropin(IHW)
     # Check for condition
     newcnd()
-    # And finally the 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 +6166,9 @@ 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)
-           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
+           enemy(type=IHQUEST, loc=dropin(),
+                      power=randreal(6000,6500.0)+250.0*game.skill)
            if not damaged(DSRSENS):
                skip(1)
                prout(_("Mr. Spock- \"Captain, this is most unusual."))
@@ -6264,18 +6178,14 @@ def newqad(shutup):
        if (game.skill < SKILL_GOOD and withprob(0.02)) or \
            (game.skill == SKILL_GOOD and withprob(0.05)) or \
             (game.skill > SKILL_GOOD and withprob(0.08)):
-            game.tholian = coord()
+            w = coord()
             while True:
-               game.tholian.x = withprob(0.5) * (QUADSIZE-1)
-               game.tholian.y = withprob(0.5) * (QUADSIZE-1)
-                if game.quad[game.tholian.x][game.tholian.y] == IHDOT:
+               w.x = withprob(0.5) * (QUADSIZE-1)
+               w.y = withprob(0.5) * (QUADSIZE-1)
+                if game.quad[w.x][w.y] == IHDOT:
                     break
-           game.quad[game.tholian.x][game.tholian.y] = IHT
-           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
+            game.tholian = enemy(type=IHT, loc=w,
+                                 power=randrange(100, 500) + 25.0*game.skill)
            # Reserve unoccupied corners 
            if game.quad[0][0]==IHDOT:
                game.quad[0][0] = 'X'
@@ -6285,7 +6195,10 @@ def newqad(shutup):
                game.quad[QUADSIZE-1][0] = 'X'
            if game.quad[QUADSIZE-1][QUADSIZE-1]==IHDOT:
                game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
-    sortklings()
+    game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+    # And finally the stars
+    for i in range(q.stars):
+       dropin(IHSTAR)
     # Put in a few black holes
     for i in range(1, 3+1):
        if withprob(0.5): 
@@ -6301,34 +6214,6 @@ def newqad(shutup):
        if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
            game.quad[QUADSIZE-1][QUADSIZE-1] = IHDOT
 
-def sortklings():
-    # sort Klingons by distance from us 
-    # The author liked bubble sort. So we will use it. :-(
-    if game.nenhere-(thing==game.quadrant)-(game.tholian!=None) < 2:
-       return
-    while True:
-       sw = False
-       for j in range(game.nenhere):
-           if game.kdist[j] > game.kdist[j+1]:
-               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
-        if not sw:
-            break
-
 def setpassword():
     # set the self-destruct password 
     if game.options & OPTION_PLAIN:
@@ -6475,6 +6360,7 @@ def makemoves():
            chew()
            setwnd(prompt_window)
            clrscr()
+           proutn("COMMAND> ")
            if scan() == IHEOL:
                if game.options & OPTION_CURSES:
                    makechart()
@@ -6500,7 +6386,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:
@@ -6519,7 +6405,7 @@ def makemoves():
        elif cmd == "DOCK":             # dock at starbase
            dock(True)
            if game.ididit:
-               attack(False)           
+               attack(torps_ok=False)          
        elif cmd == "DAMAGES":          # damage reports
            damagereport()
        elif cmd == "CHART":            # chart
@@ -6527,7 +6413,7 @@ def makemoves():
        elif cmd == "IMPULSE":          # impulse
            impulse()
        elif cmd == "REST":             # rest
-           os.wait()
+           wait()
            if game.ididit:
                hitme = True
        elif cmd == "WARP":             # warp
@@ -6604,7 +6490,7 @@ def makemoves():
                atover(False)
                continue
            if hitme and not game.justin:
-               attack(True)
+               attack(torps_ok=True)
                if game.alldone:
                    break
                if game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova:
@@ -6829,81 +6715,91 @@ 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
-            line = replayfp.readline().strip()
-            try:
-                (leader, key, seed) = line.split()
-                seed = eval(seed)
-                sys.stderr.write("sst2k: seed set to %s\n" % seed)
-            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)
-    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
+        game = citem = aaitem = inqueue = None
+        line = ''
+        thing = coord()
+        thing.angry = 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)
+        if arguments:
+            inqueue = arguments
+        else:
+            inqueue = None
+        try:
+            iostart()
+            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."))
+        finally:
+            ioend()
+        raise SystemExit, 0
+    except KeyboardInterrupt:
+        print""
+        pass