Minor fix to game load code.
[super-star-trek.git] / sst.py
diff --git a/sst.py b/sst.py
index a97268c6eea6b33bf4c492bd96e9245cec4f610a..691eff84dbb4e2f179528a3ecbce5becc9cc9795 100755 (executable)
--- a/sst.py
+++ b/sst.py
@@ -13,7 +13,7 @@ on how to modify (and how not to modify!) this code.
 """
 import os, sys, math, curses, time, readline, cPickle, random, copy, gettext, getpass
 
-version="2.0"
+version="2.1"
 
 docpath        = (".", "../doc", "/usr/share/doc/sst")
 
@@ -56,9 +56,9 @@ class TrekError(Exception):
     pass
 
 class JumpOut(Exception):
-    pass
+    pass 
 
-class coord:
+class Coord:
     def __init__(self, x=None, y=None):
         self.i = x
         self.j = y
@@ -75,28 +75,28 @@ class coord:
     def __ne__(self, other):
         return other == None or self.i != other.i or self.j != other.j
     def __add__(self, other):
-        return coord(self.i+other.i, self.j+other.j)
+        return Coord(self.i+other.i, self.j+other.j)
     def __sub__(self, other):
-        return coord(self.i-other.i, self.j-other.j)
+        return Coord(self.i-other.i, self.j-other.j)
     def __mul__(self, other):
-        return coord(self.i*other, self.j*other)
+        return Coord(self.i*other, self.j*other)
     def __rmul__(self, other):
-        return coord(self.i*other, self.j*other)
+        return Coord(self.i*other, self.j*other)
     def __div__(self, other):
-        return coord(self.i/other, self.j/other)
+        return Coord(self.i/other, self.j/other)
     def __mod__(self, other):
-        return coord(self.i % other, self.j % other)
+        return Coord(self.i % other, self.j % other)
     def __rdiv__(self, other):
-        return coord(self.i/other, self.j/other)
+        return Coord(self.i/other, self.j/other)
     def roundtogrid(self):
-        return coord(int(round(self.i)), int(round(self.j)))
+        return Coord(int(round(self.i)), int(round(self.j)))
     def distance(self, other=None):
-        if not other: other = coord(0, 0)
+        if not other: other = Coord(0, 0)
         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
     def bearing(self):
         return 1.90985*math.atan2(self.j, self.i)
     def sgn(self):
-        s = coord()
+        s = Coord()
         if self.i == 0:
             s.i = 0
         else:
@@ -112,7 +112,7 @@ class coord:
     def sector(self):
         return self.roundtogrid() % QUADSIZE
     def scatter(self):
-        s = coord()
+        s = Coord()
         s.i = self.i + randrange(-1, 2)
         s.j = self.j + randrange(-1, 2)
         return s
@@ -122,10 +122,10 @@ class coord:
         return "%s - %s" % (self.i+1, self.j+1)
     __repr__ = __str__
 
-class planet:
+class Planet:
     def __init__(self):
         self.name = None       # string-valued if inhabited
-        self.quadrant = coord()        # quadrant located
+        self.quadrant = Coord()        # quadrant located
         self.pclass = None     # could be ""M", "N", "O", or "destroyed"
         self.crystals = "absent"# could be "mined", "present", "absent"
         self.known = "unknown" # could be "unknown", "known", "shuttle_down"
@@ -133,7 +133,7 @@ class planet:
     def __str__(self):
         return self.name
 
-class quadrant:
+class Quadrant:
     def __init__(self):
         self.stars = 0
         self.planet = None
@@ -144,7 +144,7 @@ class quadrant:
        self.charted = False
         self.status = "secure" # Could be "secure", "distressed", "enslaved"
 
-class page:
+class Page:
     def __init__(self):
        self.stars = None
        self.starbase = None
@@ -159,7 +159,7 @@ def fill2d(size, fillfun):
             lst[i].append(fillfun(i, j))
     return lst
 
-class snapshot:
+class Snapshot:
     def __init__(self):
         self.snap = False      # snapshot taken
         self.crew = 0          # crew complement
@@ -176,13 +176,13 @@ class snapshot:
        self.remtime = 0        # remaining time
         self.baseq = []        # Base quadrant coordinates
         self.kcmdr = []        # Commander quadrant coordinates
-       self.kscmdr = coord()   # Supercommander quadrant coordinates
+       self.kscmdr = Coord()   # Supercommander quadrant coordinates
         # the galaxy
-        self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: quadrant())
+        self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
         # the starchart
-       self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: page())
+       self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
 
-class event:
+class Event:
     def __init__(self):
         self.date = None       # A real number
         self.quadrant = None   # A coord structure
@@ -256,10 +256,10 @@ NEVENTS   = 12
 # when we implement stateful events 
 def findevent(evtype): return game.future[evtype]
 
-class enemy:
+class Enemy:
     def __init__(self, type=None, loc=None, power=None):
         self.type = type
-        self.location = coord()
+        self.location = Coord()
         if loc:
             self.move(loc)
         self.power = power     # enemy energy level
@@ -277,23 +277,25 @@ class enemy:
             game.quad[self.location.i][self.location.j] = self.type
             self.kdist = self.kavgd = (game.sector - loc).distance()
         else:
-            self.location = coord()
+            self.location = Coord()
             self.kdist = self.kavgd = None
             game.enemies.remove(self)
         return motion
     def __repr__(self):
         return "<%s,%s.%f>" % (self.type, self.location, self.power)   # For debugging
 
-class gamestate:
+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.state = Snapshot()        # A snapshot structure
+        self.snapsht = Snapshot()      # Last snapshot taken for time-travel purposes
         self.quad = None       # contents of our quadrant
         self.damage = [0.0] * NDEVICES # damage encountered
-        self.future = []               # future events
-        for i_unused in range(NEVENTS):
-            self.future.append(event())
+        self.future = []       # future events
+        i = NEVENTS
+        while i > 0:
+            i -= 1
+            self.future.append(Event())
         self.passwd  = None;           # Self Destruct password
         self.enemies = []
         self.quadrant = None   # where we are in the large
@@ -361,6 +363,8 @@ class gamestate:
         self.cryprob = 0.0     # probability that crystal will work
         self.probe = None      # object holding probe course info
         self.height = 0.0      # height of orbit around planet
+        self.score = 0.0       # overall score
+        self.perdate = 0.0     # rate of kills
         self.idebug = False    # Debugging instrumentation enabled?
     def recompute(self):
         # Stas thinks this should be (C expression): 
@@ -418,7 +422,7 @@ def welcoming(iq):
 
 def tryexit(enemy, look, irun):
     "A bad guy attempts to bug out."
-    iq = coord()
+    iq = Coord()
     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
     if not welcoming(iq):
@@ -504,7 +508,7 @@ def tryexit(enemy, look, irun):
 
 def movebaddy(enemy):
     "Tactical movement for the bad guys."
-    goto = coord(); look = coord()
+    goto = Coord(); look = Coord()
     irun = False
     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant) 
     if game.skill >= SKILL_EXPERT:
@@ -659,7 +663,7 @@ def moveklings():
         for enemy in game.enemies:
             if enemy.type in ('K', 'R'):
                movebaddy(enemy)
-    game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+    sortenemies()
 
 def movescom(iq, avoid):
     "Commander movement helper." 
@@ -685,7 +689,7 @@ def movescom(iq, avoid):
        game.klhere -= 1
        if game.condition != "docked":
            newcnd()
-        game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+        sortenemies()
     # check for a helpful planet 
     for i in range(game.inplan):
        if game.state.planets[i].quadrant == game.state.kscmdr and \
@@ -703,7 +707,7 @@ def movescom(iq, avoid):
                        
 def supercommander():
     "Move the Super Commander." 
-    iq = coord(); sc = coord(); ibq = coord(); idelta = coord()
+    iq = Coord(); sc = Coord(); ibq = Coord(); idelta = Coord()
     basetbl = []
     if game.idebug:
        prout("== SUPERCOMMANDER")
@@ -724,7 +728,7 @@ def supercommander():
            unschedule(FSCMOVE)
            return
        sc = game.state.kscmdr
-        for base in game.state.baseq:
+        for (i, base) in enumerate(game.state.baseq):
            basetbl.append((i, (base - sc).distance()))
        if game.state.baseq > 1:
             basetbl.sort(lambda x, y: cmp(x[1], y[1]))
@@ -804,7 +808,7 @@ def supercommander():
                if not game.resting:
                    return
                prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
-               if ja() == False:
+               if not ja():
                    return
                game.resting = False
                game.optime = 0.0; # actually finished 
@@ -824,7 +828,7 @@ def movetholian():
     "Move the Tholian."
     if not game.tholian or game.justin:
        return
-    tid = coord()
+    tid = Coord()
     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
        tid.i = 0; tid.j = QUADSIZE-1
     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
@@ -911,7 +915,7 @@ def doshield(shraise):
                    action = "SHUP"
                else:
                    scanner.chew()
-                   return    
+                   return
     if action == "SHUP": # raise shields 
        if game.shldup:
            prout(_("Shields already up."))
@@ -1060,7 +1064,7 @@ def torpedo(origin, bearing, dispersion, number, nburst):
     ac = bearing + 0.25*dispersion     # dispersion is a random variable
     bullseye = (15.0 - bearing)*0.5235988
     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin)) 
-    bumpto = coord(0, 0)
+    bumpto = Coord(0, 0)
     # Loop to move a single torpedo 
     setwnd(message_window)
     for step in range(1, QUADSIZE*2):
@@ -1069,7 +1073,7 @@ def torpedo(origin, bearing, dispersion, number, nburst):
        if not w.valid_sector():
            break
        iquad=game.quad[w.i][w.j]
-       tracktorpedo(origin, w, step, number, nburst, iquad)
+       tracktorpedo(w, step, number, nburst, iquad)
        if iquad=='.':
            continue
        # hit something 
@@ -1103,7 +1107,7 @@ def torpedo(origin, bearing, dispersion, number, nburst):
             prout(_(" displaced by blast to Sector %s ") % bumpto)
             for enemy in game.enemies:
                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
-            game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+            sortenemies()
             return None
        elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy 
            # find the enemy 
@@ -1146,7 +1150,7 @@ def torpedo(origin, bearing, dispersion, number, nburst):
                 game.quad[bumpto.i][bumpto.j]=iquad
                 for enemy in game.enemies:
                     enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
-                game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+                sortenemies()
             return None
        elif iquad == 'B': # Hit a base 
            skip(1)
@@ -1217,7 +1221,7 @@ def torpedo(origin, bearing, dispersion, number, nburst):
            return None
        elif iquad == 'T':  # Hit a Tholian 
            h1 = 700.0 + randrange(100) - \
-               1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
+               1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
            h1 = math.fabs(h1)
            if h1 >= 600:
                game.quad[w.i][w.j] = '.'
@@ -1419,7 +1423,7 @@ def attack(torps_ok):
     # After attack, reset average distance to enemies 
     for enemy in game.enemies:
        enemy.kavgd = enemy.kdist
-    game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+    sortenemies()
     return
                
 def deadkl(w, type, mv):
@@ -1476,11 +1480,11 @@ def targetcheck(w):
     if not w.valid_sector():
        huh()
        return None
-    delta = coord()
+    delta = Coord()
     # FIXME: C code this was translated from is wacky -- why the sign reversal?
     delta.j = (w.j - game.sector.j);
     delta.i = (game.sector.i - w.i);
-    if delta == coord(0, 0):
+    if delta == Coord(0, 0):
        skip(1)
        prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
        prout(_("  I recommend an immediate review of"))
@@ -1632,7 +1636,7 @@ def checkshctrl(rpow):
 def hittem(hits):
     "Register a phaser hit on Klingons and Romulans."
     kk = 0
-    w = coord()
+    w = Coord()
     skip(1)
     for (k, wham) in enumerate(hits):
        if wham==0:
@@ -1966,8 +1970,8 @@ def events():
     i=0
     fintim = game.state.date + game.optime; yank=0
     ictbeam = False; istract = False
-    w = coord(); hold = coord()
-    ev = event(); ev2 = event()
+    w = Coord(); hold = Coord()
+    ev = Event(); ev2 = Event()
 
     def tractorbeam(yank):
         "Tractor-beaming cases merge here." 
@@ -2173,13 +2177,13 @@ def events():
                 for ibq in game.state.baseq:
                    for cmdr in game.state.kcmdr: 
                        if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
-                           raise ibq
+                           raise JumpOut
                 else:
                     # no match found -- try later 
                     schedule(FBATTAK, expran(0.3*game.intime))
                     unschedule(FCDBAS)
                     continue
-            except coord:
+            except JumpOut:
                 pass
            # commander + starbase combination found -- launch attack 
            game.battle = ibq
@@ -2320,7 +2324,7 @@ def events():
                continue                # full right now 
            # reproduce one Klingon 
            w = ev.quadrant
-            m = coord()
+            m = Coord()
            if game.klhere >= MAXKLQUAD:
                 try:
                     # this quadrant not ok, pick an adjacent one 
@@ -2409,7 +2413,7 @@ def wait():
 def nova(nov):
     "Star goes nova." 
     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
-    newc = coord(); neighbor = coord(); bump = coord(0, 0)
+    newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
     if withprob(0.05):
        # Wow! We've supernova'ed 
        supernova(game.quadrant)
@@ -2423,7 +2427,7 @@ def nova(nov):
     hits = [nov]
     kount = 0
     while hits:
-        offset = coord()
+        offset = Coord()
         start = hits.pop()
         for offset.i in range(-1, 1+1):
             for offset.j in range(-1, 1+1):
@@ -2489,7 +2493,7 @@ def nova(nov):
                         finish(FNOVA)
                         return
                     # add in course nova contributes to kicking starship
-                    bump += (game.sector-hits[mm]).sgn()
+                    bump += (game.sector-hits[-1]).sgn()
                 elif iquad == 'K': # kill klingon 
                     deadkl(neighbor, iquad, neighbor)
                 elif iquad in ('C','S','R'): # Damage/destroy big enemies 
@@ -2500,7 +2504,7 @@ def nova(nov):
                     if game.enemies[ll].power <= 0.0:
                         deadkl(neighbor, iquad, neighbor)
                         break
-                    newc = neighbor + neighbor - hits[mm]
+                    newc = neighbor + neighbor - hits[-1]
                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
                     if not newc.valid_sector():
                         # can't leave quadrant 
@@ -2528,7 +2532,7 @@ def nova(nov):
     if dist == 0.0:
        return
     scourse = course(bearing=direc, distance=dist)
-    game.optime = course.time(warp=4)
+    game.optime = scourse.time(warp=4)
     skip(1)
     prout(_("Force of nova displaces starship."))
     imove(scourse, noattack=True)
@@ -2543,7 +2547,7 @@ def supernova(w):
     else:
        # Scheduled supernova -- select star at random. 
        stars = 0
-        nq = coord()
+        nq = Coord()
        for nq.i in range(GALSIZE):
            for nq.j in range(GALSIZE):
                stars += game.state.galaxy[nq.i][nq.j].stars
@@ -2568,7 +2572,7 @@ def supernova(w):
            prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
            prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
     else:
-       ns = coord()
+       ns = Coord()
        # we are in the quadrant! 
        num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
        for ns.i in range(QUADSIZE):
@@ -2660,7 +2664,6 @@ def selfdestruct():
     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
     skip(1)
     scanner.next()
-    scanner.chew()
     if game.passwd != scanner.token:
        prouts(_("PASSWORD-REJECTED;"))
        skip(1)
@@ -2688,11 +2691,9 @@ def kaboom():
     skip(1)
     if len(game.enemies) != 0:
        whammo = 25.0 * game.energy
-       l=1
-       while l <= len(game.enemies):
+       for l in range(len(game.enemies)):
            if game.enemies[l].power*game.enemies[l].kdist <= whammo: 
                deadkl(game.enemies[l].location, game.quad[game.enemies[l].location.i][game.enemies[l].location.j], game.enemies[l].location)
-           l += 1
     finish(FDILITHIUM)
                                
 def killrate():
@@ -2916,11 +2917,10 @@ def finish(ifin):
 def score():
     "Compute player's score."
     timused = game.state.date - game.indate
-    iskill = game.skill
     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
        timused = 5.0
-    perdate = killrate()
-    ithperd = 500*perdate + 0.5
+    game.perdate = killrate()
+    ithperd = 500*game.perdate + 0.5
     iwon = 0
     if game.gamewon:
        iwon = 100*game.skill
@@ -2930,7 +2930,7 @@ def score():
        klship = 1
     else:
        klship = 2
-    iscore = 10*(game.inkling - game.state.remkl) \
+    game.score = 10*(game.inkling - game.state.remkl) \
              + 50*(game.incom - len(game.state.kcmdr)) \
              + ithperd + iwon \
              + 20*(game.inrom - game.state.nromrem) \
@@ -2938,7 +2938,7 @@ def score():
             - game.state.nromrem \
              - badpoints()
     if not game.alive:
-       iscore -= 200
+       game.score -= 200
     skip(2)
     prout(_("Your score --"))
     if game.inrom - game.state.nromrem:
@@ -2958,7 +2958,7 @@ def score():
              (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
     if ithperd:
        prout(_("%6.2f Klingons per stardate              %5d") %
-             (perdate, ithperd))
+             (game.perdate, ithperd))
     if game.state.starkl:
        prout(_("%6d stars destroyed by your action     %5d") %
              (game.state.starkl, -5*game.state.starkl))
@@ -2994,7 +2994,7 @@ def score():
        elif game.skill ==  SKILL_EMERITUS:     proutn(_("Emeritus game"))
        prout("           %5d" % iwon)
     skip(1)
-    prout(_("TOTAL SCORE                               %5d") % iscore)
+    prout(_("TOTAL SCORE                               %5d") % game.score)
 
 def plaque():
     "Emit winner's commemmorative plaque." 
@@ -3050,8 +3050,8 @@ def plaque():
     timestring = time.ctime()
     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
                     (timestring+4, timestring+20, timestring+11))
-    fp.write(_("                                                        Your score:  %d\n\n") % iscore)
-    fp.write(_("                                                    Klingons per stardate:  %.2f\n") % perdate)
+    fp.write(_("                                                        Your score:  %d\n\n") % game.score)
+    fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
     fp.close()
 
 # Code from io.c begins here
@@ -3151,24 +3151,18 @@ def pause_game():
         sys.stdout.write('\n')
         proutn(prompt)
         raw_input()
-        for j_unused in range(rows):
-            sys.stdout.write('\n')
+        sys.stdout.write('\n' * rows)
         linecount = 0
 
 def skip(i):
     "Skip i lines.  Pause game if this would cause a scrolling event."
     for dummy in range(i):
        if game.options & OPTION_CURSES:
-            (y, x) = curwnd.getyx()
-            (my, mx) = curwnd.getmaxyx()
-           if curwnd == message_window and y >= my - 2:
-               pause_game()
-               clrscr()
-           else:
-                try:
-                    curwnd.move(y+1, 0)
-                except curses.error:
-                    pass
+           (y, x) = curwnd.getyx()
+           try:
+               curwnd.move(y+1, 0)
+           except curses.error:
+               pass
        else:
             global linecount
            linecount += 1
@@ -3180,6 +3174,11 @@ def skip(i):
 def proutn(line):
     "Utter a line with no following line feed."
     if game.options & OPTION_CURSES:
+       (y, x) = curwnd.getyx()
+       (my, mx) = curwnd.getmaxyx()
+       if curwnd == message_window and y >= my - 2:
+           pause_game()
+           clrscr()
        curwnd.addstr(line)
        curwnd.refresh()
     else:
@@ -3346,7 +3345,7 @@ def warble():
        #nosound()
         pass
 
-def tracktorpedo(origin, w, step, i, n, iquad):
+def tracktorpedo(w, step, i, n, iquad):
     "Torpedo-track animation." 
     if not game.options & OPTION_CURSES:
        if step == 1:
@@ -3406,9 +3405,9 @@ def prstat(txt, data):
 
 # Code from moving.c begins here
 
-def imove(course=None, noattack=False):
+def imove(icourse=None, noattack=False):
     "Movement execution for warp, impulse, supernova, and tractor-beam events."
-    w = coord()
+    w = Coord()
 
     def newquadrant(noattack):
         # Leaving quadrant -- allow final enemy attack 
@@ -3429,17 +3428,17 @@ def imove(course=None, noattack=False):
         kinks = 0
         while True:
             kink = False
-            if course.final.i < 0:
-                course.final.i = -course.final.i
+            if icourse.final.i < 0:
+                icourse.final.i = -icourse.final.i
                 kink = True
-            if course.final.j < 0:
-                course.final.j = -course.final.j
+            if icourse.final.j < 0:
+                icourse.final.j = -icourse.final.j
                 kink = True
-            if course.final.i >= GALSIZE*QUADSIZE:
-                course.final.i = (GALSIZE*QUADSIZE*2) - course.final.i
+            if icourse.final.i >= GALSIZE*QUADSIZE:
+                icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
                 kink = True
-            if course.final.j >= GALSIZE*QUADSIZE:
-                course.final.j = (GALSIZE*QUADSIZE*2) - course.final.j
+            if icourse.final.j >= GALSIZE*QUADSIZE:
+                icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
                 kink = True
             if kink:
                 kinks += 1
@@ -3458,8 +3457,8 @@ def imove(course=None, noattack=False):
         # Compute final position in new quadrant 
         if trbeam: # Don't bother if we are to be beamed 
             return
-        game.quadrant = course.final.quadrant()
-        game.sector = course.final.sector()
+        game.quadrant = icourse.final.quadrant()
+        game.sector = icourse.final.sector()
         skip(1)
         prout(_("Entering Quadrant %s.") % game.quadrant)
         game.quad[game.sector.i][game.sector.j] = game.ship
@@ -3471,7 +3470,7 @@ def imove(course=None, noattack=False):
         iquad = game.quad[h.i][h.j]
         if iquad != '.':
             # object encountered in flight path 
-            stopegy = 50.0*course.distance/game.optime
+            stopegy = 50.0*icourse.distance/game.optime
             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
                 for enemy in game.enemies:
                     if enemy.location == game.sector:
@@ -3521,14 +3520,14 @@ def imove(course=None, noattack=False):
     if game.state.date+game.optime >= scheduled(FTBEAM):
        trbeam = True
        game.condition = "red"
-       course.distance = course.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
+       icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
        game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
     # Move out
     game.quad[game.sector.i][game.sector.j] = '.'
-    for m in range(course.moves):
-        course.next()
-        w = course.sector()
-        if course.origin.quadrant() != course.location.quadrant():
+    for m in range(icourse.moves):
+        icourse.next()
+        w = icourse.sector()
+        if icourse.origin.quadrant() != icourse.location.quadrant():
             newquadrant(noattack)
             break
         elif check_collision(w):
@@ -3543,7 +3542,7 @@ def imove(course=None, noattack=False):
             finald = (w-enemy.location).distance()
             enemy.kavgd = 0.5 * (finald + enemy.kdist)
             enemy.kdist = finald
-        game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+        sortenemies()
         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
             attack(torps_ok=False)
         for enemy in game.enemies:
@@ -3596,7 +3595,7 @@ def getcourse(isprobe):
     dquad = copy.copy(game.quadrant)
     navmode = "unspecified"
     itemp = "curt"
-    dsect = coord()
+    dsect = Coord()
     iprompt = False
     if game.landed and not isprobe:
        prout(_("Dummy! You can't leave standard orbit until you"))
@@ -3638,7 +3637,7 @@ def getcourse(isprobe):
                prout(_("(Manual movement assumed.)"))
            navmode = "manual"
            break
-    delta = coord()
+    delta = Coord()
     if navmode == "automatic":
        while key == "IHEOL":
            if isprobe:
@@ -3738,7 +3737,7 @@ class course:
             self.origin = cartesian(game.quadrant, game.sector)
         else:
             self.origin = cartesian(game.quadrant, origin)
-        self.increment = coord(-math.sin(self.angle), math.cos(self.angle))
+        self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
         bigger = max(abs(self.increment.i), abs(self.increment.j))
         self.increment /= bigger
         self.moves = int(round(10*self.distance*bigger))
@@ -3814,7 +3813,7 @@ def impulse():
        finish(FNRG)
     return
 
-def warp(course, involuntary):
+def warp(wcourse, involuntary):
     "ove under warp drive."
     blooey = False; twarp = False
     if not involuntary: # Not WARPX entry 
@@ -3831,21 +3830,21 @@ def warp(course, involuntary):
            prout(_("  is repaired, I can only give you warp 4.\""))
            return
                # Read in course and distance
-        if course==None:
+        if wcourse==None:
             try:
-                course = getcourse(isprobe=False)
+                wcourse = getcourse(isprobe=False)
             except TrekError:
                 return
        # Make sure starship has enough energy for the trip
         # Note: this formula is slightly different from the C version,
         # and lets you skate a bit closer to the edge.
-       if course.power(game.warpfac) >= game.energy:
+       if wcourse.power(game.warpfac) >= game.energy:
            # Insufficient power for trip 
            game.ididit = False
            skip(1)
            prout(_("Engineering to bridge--"))
-           if not game.shldup or 0.5*course.power(game.warpfac) > game.energy:
-               iwarp = (game.energy/(course.dist+0.05)) ** 0.333333333
+           if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
+               iwarp = (game.energy/(wcourse.dist+0.05)) ** 0.333333333
                if iwarp <= 0:
                    prout(_("We can't do it, Captain. We don't have enough energy."))
                else:
@@ -3859,7 +3858,7 @@ def warp(course, involuntary):
                prout(_("We haven't the energy to go that far with the shields up."))
            return                              
        # Make sure enough time is left for the trip 
-       game.optime = course.time(game.warpfac)
+       game.optime = wcourse.time(game.warpfac)
        if game.optime >= 0.8*game.state.remtime:
            skip(1)
            prout(_("First Officer Spock- \"Captain, I compute that such"))
@@ -3875,12 +3874,12 @@ def warp(course, involuntary):
     if game.warpfac > 6.0:
        # Decide if engine damage will occur
         # ESR: Seems wrong. Probability of damage goes *down* with distance? 
-       prob = course.distance*(6.0-game.warpfac)**2/66.666666666
+       prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
        if prob > randreal():
            blooey = True
-           course.distance = randreal(course.distance)
+           wcourse.distance = randreal(wcourse.distance)
        # Decide if time warp will occur 
-       if 0.5*course.distance*math.pow(7.0,game.warpfac-10.0) > randreal():
+       if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > randreal():
            twarp = True
        if game.idebug and game.warpfac==10 and not twarp:
            blooey = False
@@ -3889,24 +3888,26 @@ def warp(course, involuntary):
                twarp = True
        if blooey or twarp:
            # If time warp or engine damage, check path 
-           # If it is obstructed, don't do warp or damage 
-            for m_unused in range(course.moves):
-                course.next()
-                w = course.sector()
+           # If it is obstructed, don't do warp or damage
+            look = wcourse.moves
+            while look > 0:
+                look -= 1
+                wcourse.next()
+                w = wcourse.sector()
                 if not w.valid_sector():
                     break
                if game.quad[w.i][w.j] != '.':
                    blooey = False
                    twarp = False
-            course.reset()
+            wcourse.reset()
     # Activate Warp Engines and pay the cost 
-    imove(course, noattack=False)
+    imove(wcourse, noattack=False)
     if game.alldone:
        return
-    game.energy -= course.power(game.warpfac)
+    game.energy -= wcourse.power(game.warpfac)
     if game.energy <= 0:
        finish(FNRG)
-    game.optime = course.time(game.warpfac)
+    game.optime = wcourse.time(game.warpfac)
     if twarp:
        timwrp()
     if blooey:
@@ -4188,7 +4189,7 @@ def mayday():
        elif m == 2: proutn(_("2nd"))
        elif m == 3: proutn(_("3rd"))
        proutn(_(" attempt to re-materialize ") + crmshp())
-       game.quad[ix][iy]=('-','o','O')[m-1]
+       game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
         textcolor(RED)
        warble()
        if randreal() > probf:
@@ -4197,13 +4198,13 @@ def mayday():
         textcolor(DEFAULT)
        curses.delay_output(500)
     if m > 3:
-       game.quad[ix][iy]='?'
+       game.quad[game.sector.i][game.sector.j]='?'
        game.alive = False
        drawmaps(1)
        setwnd(message_window)
        finish(FMATERIALIZE)
        return
-    game.quad[ix][iy]=game.ship
+    game.quad[game.sector.i][game.sector.j]=game.ship
     textcolor(GREEN);
     prout(_("succeeds."))
     textcolor(DEFAULT);
@@ -4788,7 +4789,7 @@ def attackreport(curt):
 def report():
     # report on general game status 
     scanner.chew()
-    s1 = "" and game.thawed and _("thawed ")
+    s1 = (game.thawed and _("thawed ")) or ""
     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
     s3 = (None, _("novice"), _("fair"),
           _("good"), _("expert"), _("emeritus"))[game.skill]
@@ -4871,7 +4872,7 @@ def lrscan(silent):
         if not silent:
             proutn(" ")
         for y in range(game.quadrant.j-1, game.quadrant.j+2):
-           if not coord(x, y).valid_quadrant():
+           if not Coord(x, y).valid_quadrant():
                 if not silent:
                     proutn("  -1")
            else:
@@ -5060,7 +5061,7 @@ def srscan():
                
 def eta():
     "Use computer to get estimated time of arrival for a warp jump."
-    w1 = coord(); w2 = coord()
+    w1 = Coord(); w2 = Coord()
     prompt = False
     if damaged(DCOMPTR):
        prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
@@ -5202,7 +5203,6 @@ def freeze(boss):
     if key != "IHALPHA":
         huh()
         return
-    scanner.chew()
     if '.' not in scanner.token:
         scanner.token += ".trk"
     try:
@@ -5212,10 +5212,12 @@ def freeze(boss):
        return
     cPickle.dump(game, fp)
     fp.close()
+    scanner.chew()
 
 def thaw():
     "Retrieve saved game."
-    game.passwd[0] = '\0'
+    global game
+    game.passwd = None
     key = scanner.next()
     if key == "IHEOL":
        proutn(_("File name: "))
@@ -5223,7 +5225,6 @@ def thaw():
     if key != "IHALPHA":
        huh()
        return True
-    scanner.chew()
     if '.' not in scanner.token:
         scanner.token += ".trk"
     try:
@@ -5233,6 +5234,7 @@ def thaw():
        return
     game = cPickle.load(fp)
     fp.close()
+    scanner.chew()
     return False
 
 # I used <http://www.memory-alpha.org> to find planets
@@ -5308,7 +5310,7 @@ device = (
 
 def setup():
     "Prepare to play, set up cosmos."
-    w = coord()
+    w = Coord()
     #  Decide how many of everything
     if choose():
        return # frozen game
@@ -5328,15 +5330,26 @@ def setup():
     for i in range(NDEVICES): 
        game.damage[i] = 0.0
     # Set up assorted game parameters
-    game.battle = coord()
+    game.battle = Coord()
     game.state.date = game.indate = 100.0 * randreal(20, 51)
     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
     game.isatb = game.state.nplankl = 0
-    game.state.starkl = game.state.basekl = 0
+    game.state.starkl = game.state.basekl = game.state.nworldkl = 0
     game.iscraft = "onship"
     game.landed = False
     game.alive = True
+
+    # the galaxy
+    game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
+    # the starchart
+    game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
+
+    game.state.planets = []      # Planet information
+    game.state.baseq = []      # Base quadrant coordinates
+    game.state.kcmdr = []      # Commander quadrant coordinates
+    game.statekscmdr = Coord() # Supercommander quadrant coordinates
+
     # Starchart is functional but we've never seen it
     game.lastchart = FOREVER
     # Put stars in the galaxy
@@ -5406,7 +5419,7 @@ def setup():
             w = randplace(GALSIZE) 
             if game.state.galaxy[w.i][w.j].planet == None:
                 break
-        new = planet()
+        new = Planet()
        new.quadrant = w
         new.crystals = "absent"
        if (game.options & OPTION_WORLDS) and i < NINHAB:
@@ -5494,6 +5507,8 @@ def setup():
     if game.state.nscrem:
        prout(_("  YOU'LL NEED IT."))
     waitfor()
+    clrscr()
+    setwnd(message_window)
     newqad()
     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
        game.shldup = True
@@ -5506,8 +5521,9 @@ def choose():
        game.tourn = game.length = 0
        game.thawed = False
        game.skill = SKILL_NONE
-       if not scanner.inqueue: # Can start with command line options 
-           proutn(_("Would you like a regular, tournament, or saved game? "))
+       scanner.chew()
+#      if not scanner.inqueue: # Can start with command line options 
+       proutn(_("Would you like a regular, tournament, or saved game? "))
         scanner.next()
         if scanner.sees("tournament"):
            while scanner.next() == "IHEOL":
@@ -5533,7 +5549,7 @@ def choose():
            return True
         if scanner.sees("regular"):
            break
-       proutn(_("What is \"%s\"?") % scanner.token)
+       proutn(_("What is \"%s\"? ") % scanner.token)
        scanner.chew()
     while game.length==0 or game.skill==SKILL_NONE:
        if scanner.next() == "IHALPHA":
@@ -5627,7 +5643,11 @@ def newcnd():
 
 def newkling():
     "Drop new Klingon into current quadrant."
-    return enemy('K', loc=dropin(), power=randreal(300,450)+25.0*game.skill)
+    return Enemy('K', loc=dropin(), power=randreal(300,450)+25.0*game.skill)
+
+def sortenemies():
+    "Sort enemies by distance so 'nearest' is meaningful."
+    game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
 
 def newqad():
     "Set up a new state of quadrant, for when we enter or re-enter it."
@@ -5669,7 +5689,7 @@ def newqad():
            game.iscate = (game.state.remkl > 1)
     # Put in Romulans if needed
     for i in range(q.romulans):
-        enemy('R', loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
+        Enemy('R', 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('B')
@@ -5694,7 +5714,7 @@ def newqad():
            prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
     # Put in THING if needed
     if thing == game.quadrant:
-        enemy(type='?', loc=dropin(),
+        Enemy(type='?', loc=dropin(),
                   power=randreal(6000,6500.0)+250.0*game.skill)
         if not damaged(DSRSENS):
             skip(1)
@@ -5705,13 +5725,13 @@ def newqad():
        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)):
-            w = coord()
+            w = Coord()
             while True:
                w.i = withprob(0.5) * (QUADSIZE-1)
                w.j = withprob(0.5) * (QUADSIZE-1)
                 if game.quad[w.i][w.j] == '.':
                     break
-            game.tholian = enemy(type='T', loc=w,
+            game.tholian = Enemy(type='T', loc=w,
                                  power=randrange(100, 500) + 25.0*game.skill)
            # Reserve unoccupied corners 
            if game.quad[0][0]=='.':
@@ -5722,7 +5742,7 @@ def newqad():
                game.quad[QUADSIZE-1][0] = 'X'
            if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
                game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
-    game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+    sortenemies()
     # And finally the stars
     for i in range(q.stars):
        dropin('*')
@@ -5753,59 +5773,61 @@ def setpassword():
                break
     else:
         game.passwd = ""
-        for i_unused in range(3):
-           game.passwd += chr(ord('a')+randrange(26))
+        game.passwd += chr(ord('a')+randrange(26))
+        game.passwd += chr(ord('a')+randrange(26))
+        game.passwd += chr(ord('a')+randrange(26))
 
 # Code from sst.c begins here
 
-commands = {
-    "SRSCAN":          OPTION_TTY,
-    "STATUS":          OPTION_TTY,
-    "REQUEST":         OPTION_TTY,
-    "LRSCAN":          OPTION_TTY,
-    "PHASERS":         0,
-    "TORPEDO":         0,
-    "PHOTONS":         0,
-    "MOVE":            0,
-    "SHIELDS":         0,
-    "DOCK":            0,
-    "DAMAGES":         0,
-    "CHART":           0,
-    "IMPULSE":         0,
-    "REST":            0,
-    "WARP":            0,
-    "SCORE":           0,
-    "SENSORS":         OPTION_PLANETS,
-    "ORBIT":           OPTION_PLANETS,
-    "TRANSPORT":       OPTION_PLANETS,
-    "MINE":            OPTION_PLANETS,
-    "CRYSTALS":        OPTION_PLANETS,
-    "SHUTTLE":         OPTION_PLANETS,
-    "PLANETS":         OPTION_PLANETS,
-    "REPORT":          0,
-    "COMPUTER":        0,
-    "COMMANDS":        0,
-    "EMEXIT":          0,
-    "PROBE":           OPTION_PROBE,
-    "SAVE":            0,
-    "FREEZE":          0,      # Synonym for SAVE
-    "ABANDON":         0,
-    "DESTRUCT":        0,
-    "DEATHRAY":        0,
-    "DEBUG":           0,
-    "MAYDAY":          0,
-    "SOS":             0,      # Synonym for MAYDAY
-    "CALL":            0,      # Synonym for MAYDAY
-    "QUIT":            0,
-    "HELP":            0,
-}
+commands = [
+    ("SRSCAN",         OPTION_TTY),
+    ("STATUS",         OPTION_TTY),
+    ("REQUEST",        OPTION_TTY),
+    ("LRSCAN",         OPTION_TTY),
+    ("PHASERS",        0),
+    ("TORPEDO",        0),
+    ("PHOTONS",        0),
+    ("MOVE",           0),
+    ("SHIELDS",        0),
+    ("DOCK",           0),
+    ("DAMAGES",        0),
+    ("CHART",          0),
+    ("IMPULSE",        0),
+    ("REST",           0),
+    ("WARP",           0),
+    ("SCORE",          0),
+    ("SENSORS",        OPTION_PLANETS),
+    ("ORBIT",          OPTION_PLANETS),
+    ("TRANSPORT",      OPTION_PLANETS),
+    ("MINE",           OPTION_PLANETS),
+    ("CRYSTALS",       OPTION_PLANETS),
+    ("SHUTTLE",        OPTION_PLANETS),
+    ("PLANETS",        OPTION_PLANETS),
+    ("REPORT",         0),
+    ("COMPUTER",       0),
+    ("COMMANDS",       0),
+    ("EMEXIT",         0),
+    ("PROBE",          OPTION_PROBE),
+    ("SAVE",           0),
+    ("FREEZE",         0),     # Synonym for SAVE
+    ("ABANDON",        0),
+    ("DESTRUCT",       0),
+    ("DEATHRAY",       0),
+    ("DEBUG",          0),
+    ("MAYDAY",         0),
+    ("SOS",            0),     # Synonym for MAYDAY
+    ("CALL",           0),     # Synonym for MAYDAY
+    ("QUIT",           0),
+    ("HELP",           0),
+    ("",               0),
+]
 
 def listCommands():
     "Generate a list of legal commands."
     prout(_("LEGAL COMMANDS ARE:"))
     emitted = 0
-    for key in commands:
-       if not commands[key] or (commands[key] & game.options):
+    for (key, opt) in commands:
+       if not opt or (opt & game.options):
             proutn("%-12s " % key)
             emitted += 1
             if emitted % 5 == 4:
@@ -5823,7 +5845,8 @@ def helpme():
        setwnd(message_window)
        if key == "IHEOL":
            return
-        if scanner.token.upper() in commands or scanner.token == "ABBREV":
+       cmds = map(lambda x: x[0], commands)
+       if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
            break
        skip(1)
        listCommands()
@@ -5867,8 +5890,6 @@ def helpme():
 
 def makemoves():
     "Command-interpretation loop."
-    clrscr()
-    setwnd(message_window)
     while True:        # command loop 
        drawmaps(1)
         while True:    # get a command 
@@ -5888,16 +5909,19 @@ def makemoves():
            clrscr()
            setwnd(message_window)
            clrscr()
-            candidates = filter(lambda x: x.startswith(scanner.token.upper()),
-                                commands)
-            if len(candidates) == 1:
-                cmd = candidates[0]
-                break
-            elif candidates and not (game.options & OPTION_PLAIN):
-                prout("Commands with prefix '%s': %s" % (scanner.token, " ".join(candidates)))
-            else:
+           abandon_passed = False
+           for (cmd, opt) in commands:
+               # commands after ABANDON cannot be abbreviated
+               if cmd == "ABANDON":
+                   abandon_passed = True
+               if cmd == scanner.token.upper() or (not abandon_passed \
+                       and cmd.startswith(scanner.token.upper())):
+                   break;
+           if cmd == "":
                 listCommands()
                 continue
+            else:
+               break;
        if cmd == "SRSCAN":             # srscan
            srscan()
        elif cmd == "STATUS":           # status
@@ -5915,7 +5939,7 @@ def makemoves():
            if game.ididit:
                hitme = True
        elif cmd == "MOVE":             # move under warp
-           warp(course=None, involuntary=False)
+           warp(wcourse=None, involuntary=False)
        elif cmd == "SHIELDS":          # shields
            doshield(shraise=False)
            if game.ididit:
@@ -6065,7 +6089,7 @@ def expran(avrage):
 
 def randplace(size):
     "Choose a random location."
-    w = coord()
+    w = Coord()
     w.i = randrange(size) 
     w.j = randrange(size)
     return w
@@ -6126,7 +6150,7 @@ class sstscanner:
         # Round token value to nearest integer
         return int(round(scanner.real))
     def getcoord(self):
-        s = coord()
+        s = Coord()
         scanner.next()
        if scanner.type != "IHREAL":
            huh()
@@ -6190,8 +6214,8 @@ def debugme():
                game.damage[i] = 10.0
     proutn("Examine/change events? ")
     if ja():
-       ev = event()
-       w = coord()
+       ev = Event()
+       w = Coord()
         legends = {
             FSNOVA:  "Supernova       ",
             FTBEAM:  "T Beam          ",
@@ -6251,9 +6275,9 @@ if __name__ == '__main__':
     try:
         global line, thing, game
         game = None
-        thing = coord()
+        thing = Coord()
         thing.angry = False
-        game = gamestate()
+        game = Gamestate()
         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
         if os.getenv("TERM"):
             game.options |= OPTION_CURSES
@@ -6345,3 +6369,5 @@ if __name__ == '__main__':
         if logfp:
             logfp.close()
         print ""
+
+# End.