Checkpoint Python version before trying to move to zero-origin indexing.
[super-star-trek.git] / src / sst.py
index 5b41777bf0d5dc83cdcb995c6405ead793a7c399..14a1f27429c2885fd9174a7a6aa8ba10cd30f72a 100644 (file)
@@ -1,4 +1,5 @@
-'''
+#!/usr/bin/env python
+"""
 sst.py =-- Super Star Trek in Python
 
 This code is a Python translation of a C translation of a FORTRAN original.
@@ -171,7 +172,7 @@ 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).
-'''
+"""
 import os, sys, math, curses, time, atexit, readline, cPickle, random, getopt
 
 SSTDOC         = "/usr/share/doc/sst/sst.doc"
@@ -186,6 +187,7 @@ NINHAB      = (GALSIZE * GALSIZE / 2)
 MAXUNINHAB     = 10
 PLNETMAX       = (NINHAB + MAXUNINHAB)
 QUADSIZE       = 10
+BASEMIN                = 2
 BASEMAX        = (GALSIZE * GALSIZE / 12)
 MAXKLGAME      = 127
 MAXKLQUAD      = 9
@@ -226,7 +228,7 @@ IHREAL = 0.0
 IHALPHA = " "
 
 class coord:
-    def __init(self, x=None, y=None):
+    def __init__(self, x=None, y=None):
         self.x = x
         self.y = y
     def invalidate(self):
@@ -234,7 +236,7 @@ class coord:
     def is_valid(self):
         return self.x != None and self.y != None
     def __eq__(self, other):
-        return self.x == other.y and self.x == other.y
+        return other != None and self.x == other.y and self.x == other.y
     def __add__(self, other):
         return coord(self.x+self.x, self.y+self.y)
     def __sub__(self, other):
@@ -246,22 +248,22 @@ class coord:
     def __hash__(self):
         return hash((x, y))
     def __str__(self):
-        return "%d - %d" % (self.x, self.y)
+        return "%s - %s" % (self.x, self.y)
 
 class planet:
-    def __init(self):
+    def __init__(self):
         self.name = None       # string-valued if inhabited
         self.w = coord()       # quadrant located
         self.pclass = None     # could be ""M", "N", "O", or "destroyed"
-        self.crystals = None   # could be "mined", "present", "absent"
-        self.known = None      # could be "unknown", "known", "shuttle_down"
-        self.inhabited         # is it inhabites?
+        self.crystals = "absent"# could be "mined", "present", "absent"
+        self.known = "unknown" # could be "unknown", "known", "shuttle_down"
+        self.inhabited = False # is it inhabites?
     def __str__(self):
         return self.name
 
 NOPLANET = None
 class quadrant:
-    def __init(self):
+    def __init__(self):
         self.stars = None
         self.planet = None
        self.starbase = None
@@ -272,28 +274,37 @@ class quadrant:
         self.status = None     # Could be "secure", "distressed", "enslaved"
 
 class page:
-    def __init(self):
+    def __init__(self):
        self.stars = None
        self.starbase = None
        self.klingons = None
 
+def fill2d(size, fillfun):
+    "Fill an empty list in 2D."
+    lst = []
+    for i in range(size+1):
+        lst.append([]) 
+        for j in range(size+1):
+            lst[i][j] = fillfun(i, j)
+    return lst
+
 class snapshot:
-    def __init(self):
+    def __init__(self):
         self.snap = False      # snapshot taken
-        self.crew = None       # crew complement
-       self.remkl = None       # remaining klingons
-       self.remcom = None      # remaining commanders
-       self.nscrem = None      # remaining super commanders
-       self.rembase = None     # remaining bases
-       self.starkl = None      # destroyed stars
-       self.basekl = None      # destroyed bases
-       self.nromrem = None     # Romulans remaining
-       self.nplankl = None     # destroyed uninhabited planets
-       self.nworldkl = None    # destroyed inhabited planets
+        self.crew = 0          # crew complement
+       self.remkl = 0          # remaining klingons
+       self.remcom = 0         # remaining commanders
+       self.nscrem = 0         # remaining super commanders
+       self.rembase = 0        # remaining bases
+       self.starkl = 0         # destroyed stars
+       self.basekl = 0         # destroyed bases
+       self.nromrem = 0        # Romulans remaining
+       self.nplankl = 0        # destroyed uninhabited planets
+       self.nworldkl = 0       # destroyed inhabited planets
         self.planets = []      # Planet information
-        self.date = None       # stardate
-       self.remres = None      # remaining resources
-       self.remtime = None     # remaining time
+        self.date = 0.0        # stardate
+       self.remres = 0         # remaining resources
+       self.remtime = 0        # remaining time
         self.baseq = []        # Base quadrant coordinates
         for i in range(BASEMAX+1):
             self.baseq.append(coord())
@@ -301,16 +312,10 @@ class snapshot:
         for i in range(QUADSIZE+1):
             self.kcmdr.append(coord())
        self.kscmdr = coord()   # Supercommander quadrant coordinates
-        self.galaxy = []       # The Galaxy (subscript 0 not used)
-        for i in range(GALSIZE+1):
-            self.chart.append([])
-            for j in range(GALSIZE+1):
-                self.galaxy[i].append(quadrant())
-       self.chart = []         # the starchart (subscript 0 not used)
-        for i in range(GALSIZE+1):
-            self.chart.append([])
-            for j in range(GALSIZE+1):
-                self.chart[i].append(page())
+        # the galaxy (subscript 0 not used)
+        self.galaxy = fill2d(GALSIZE, lambda i, j: quadrant())
+        # the starchart (subscript 0 not used)
+       self.chart = fill2d(GALSIZE, lambda i, j: page())
 
 class event:
     def __init__(self):
@@ -389,14 +394,14 @@ def findevent(evtype):    return game.future[evtype]
 class gamestate:
     def __init__(self):
         self.options = None    # Game options
-        self.state = None      # A snapshot structure
-        self.snapsht = None    # Last snapshot taken for time-travel purposes
-        self.quad = [[IHDOT * (QUADSIZE+1)] * (QUADSIZE+1)]    # contents of our quadrant
-        self.kpower = [[0 * (QUADSIZE+1)] * (QUADSIZE+1)]      # enemy energy levels
-        self.kdist = [[0 * (QUADSIZE+1)] * (QUADSIZE+1)]       # enemy distances
-        self.kavgd = [[0 * (QUADSIZE+1)] * (QUADSIZE+1)]       # average distances
+        self.state = snapshot()        # A snapshot structure
+        self.snapsht = snapshot()      # Last snapshot taken for time-travel purposes
+        self.quad = fill2d(QUADSIZE, lambda i, j: IHDOT)       # contents of our quadrant
+        self.kpower = fill2d(QUADSIZE, lambda i, j: 0.0)       # enemy energy levels
+        self.kdist = fill2d(QUADSIZE, lambda i, j: 0.0)                # enemy distances
+        self.kavgd = fill2d(QUADSIZE, lambda i, j: 0.0)        # average distances
         self.damage = [0.0] * NDEVICES # damage encountered
-        self.future = [0.0] * NEVENTS  # future events
+        self.future = []               # future events
         for i in range(NEVENTS):
             self.future.append(event())
         self.passwd  = None;           # Self Destruct password
@@ -2270,7 +2275,7 @@ def scheduled(evtype):
     return game.future[evtype].date
 
 def schedule(evtype, offset):
-    # schedule an event of specified type 
+    # schedule an event of specified type
     game.future[evtype].date = game.state.date + offset
     return game.future[evtype]
 
@@ -3477,6 +3482,7 @@ def plaque():
 
 rows = linecount = 0   # for paging 
 stdscr = None
+replayfp = None
 fullscreen_window = None
 srscan_window     = None
 report_window     = None
@@ -3484,6 +3490,7 @@ status_window     = None
 lrscan_window     = None
 message_window    = None
 prompt_window     = None
+curwnd = None
 
 def outro():
     "wrap up, either normally or due to signal"
@@ -3494,12 +3501,12 @@ def outro():
        #resetterm()
        #echo()
        curses.endwin()
-       stdout.write('\n')
+       sys.stdout.write('\n')
     if logfp:
        logfp.close()
 
 def iostart():
-    global stdscr
+    global stdscr, rows
     #setlocale(LC_ALL, "")
     #bindtextdomain(PACKAGE, LOCALEDIR)
     #textdomain(PACKAGE)
@@ -3572,16 +3579,16 @@ def pause_game():
         setwnd(message_window)
     else:
         global linecount
-        stdout.write('\n')
+        sys.stdout.write('\n')
         proutn(prompt)
         raw_input()
         for j in range(0, rows):
-            stdout.write('\n')
+            sys.stdout.write('\n')
         linecount = 0
 
 def skip(i):
     "Skip i lines.  Pause game if this would cause a scrolling event."
-    while dummy in range(i):
+    for dummy in range(i):
        if game.options & OPTION_CURSES:
             (y, x) = curwnd.getyx()
             (my, mx) = curwnd.getmaxyx()
@@ -3593,10 +3600,10 @@ def skip(i):
        else:
             global linecount
            linecount += 1
-           if linecount >= rows:
+           if rows and linecount >= rows:
                pause_game()
            else:
-               stdout.write('\n')
+               sys.stdout.write('\n')
 
 def proutn(line):
     "Utter a line with no following line feed."
@@ -3604,7 +3611,8 @@ def proutn(line):
        curwnd.addstr(line)
        curwnd.refresh()
     else:
-       stdout.write(line)
+       sys.stdout.write(line)
+        sys.stdout.flush()
 
 def prout(line):
     proutn(line)
@@ -3630,12 +3638,14 @@ def cgetline():
        if replayfp and not replayfp.closed:
            line = replayfp.readline()
        else:
-           sys.stdin.readline()
+           line = raw_input()
     if logfp:
        logfp.write(line)
+    return line
 
 def setwnd(wnd):
-    "Change windows -- OK for this to be a no-op in tty mode." 
+    "Change windows -- OK for this to be a no-op in tty mode."
+    global curwnd
     if game.options & OPTION_CURSES:
         curwnd = wnd
         curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
@@ -5890,7 +5900,6 @@ device = (
 
 def setup(needprompt):
     # prepare to play, set up cosmos 
-    intj, krem, klumper
     w = coord()
 
     #  Decide how many of everything
@@ -5915,7 +5924,7 @@ def setup(needprompt):
     for i in range(0, NDEVICES): 
        game.damage[i] = 0.0
     # Set up assorted game parameters
-    invalidate(game.battle)
+    game.battle = coord()
     game.state.date = game.indate = 100.0*int(31.0*random.random()+20.0)
     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
@@ -5972,9 +5981,9 @@ def setup(needprompt):
            contflag = False
             # C version: for (j = i-1; j > 0; j--)
             # so it did them in the opposite order.
-            for j in range(i):
-               # Improved placement algorithm to spread out bases 
-               distq = w.distance(baseq[j])
+            for j in range(1, i):
+               # Improved placement algorithm to spread out bases
+               distq = w.distance(game.state.baseq[j])
                if distq < 6.0*(BASEMAX+1-game.inbase) and random.random() < 0.75:
                    contflag = True
                    if idebug:
@@ -6034,13 +6043,13 @@ def setup(needprompt):
             new.name = systnames[i]
            new.inhabited = True
        else:
-           new.pclass = ("M", "N", "O")[random.random()*3.0]
+           new.pclass = ("M", "N", "O")[random.randint(0, 2)]
             if random.random()*1.5:            # 1 in 3 chance of crystals
                 new.crystals = "present"
            new.known = "unknown"
            new.inhabited = False
        game.state.galaxy[w.x][w.y].planet = new
-        game.state.plnets.append(new)
+        game.state.planets.append(new)
     # Locate Romulans
     for i in range(1, game.state.nromrem+1):
        w = randplace(GALSIZE)
@@ -6049,15 +6058,14 @@ def setup(needprompt):
     if game.state.nscrem > 0:
         while True:
             w = randplace(GALSIZE)
-            if not game.state.galaxy[w.x][w.y].supernova and game.state.galaxy[w.x][w.y].klingons <= MXKLQUAD:
+            if not game.state.galaxy[w.x][w.y].supernova and game.state.galaxy[w.x][w.y].klingons <= MAXKLQUAD:
                 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!)
-    if thing.x != -1:
+    global thing
+    if thing == None:
        thing = randplace(GALSIZE)
-    else:
-       invalidate(thing)
     skip(2)
     game.state.snap = False
     if game.skill == SKILL_NOVICE:
@@ -6097,7 +6105,8 @@ def setup(needprompt):
        attack(False)
 
 def choose(needprompt):
-    # choose your game type 
+    # choose your game type
+    global thing
     while True:
        game.tourn = 0
        game.thawed = False
@@ -6115,7 +6124,6 @@ def choose(needprompt):
                chew()
                continue # We don't want a blank entry
            game.tourn = int(aaitem)
-           thing.x = -1
            random.seed(aaitem)
            break
         if isit("saved") or isit("frozen"):
@@ -6131,9 +6139,7 @@ def choose(needprompt):
            return True
         if isit("regular"):
            break
-       proutn(_("What is \""))
-       proutn(citem)
-       prout("\"?")
+       proutn(_("What is \"%s\"?"), citem)
        chew()
     while game.length==0 or game.skill==SKILL_NONE:
        if scan() == IHALPHA:
@@ -6187,7 +6193,7 @@ def choose(needprompt):
 
     # Use parameters to generate initial values of things
     game.damfac = 0.5 * game.skill
-    game.state.rembase = 2.0 + random.random()*(BASEMAX-2.0)
+    game.state.rembase = random.randint(BASEMIN, BASEMAX)
     game.inbase = game.state.rembase
     game.inplan = 0
     if game.options & OPTION_PLANETS:
@@ -6241,10 +6247,8 @@ def newqad(shutup):
     # set up a new state of quadrant, for when we enter or re-enter it 
     w = coord()
     game.justin = True
-    invalidate(game.base)
     game.klhere = 0
     game.comhere = False
-    invalidate(game.plnet)
     game.ishere = False
     game.irhere = 0
     game.iplnet = 0
@@ -6261,10 +6265,6 @@ def newqad(shutup):
        # Attempt to escape Super-commander, so tbeam back!
        game.iscate = False
        game.ientesc = True
-    # Clear quadrant
-    for i in range(1, QUADSIZE+1):
-       for j in range(1, QUADSIZE+1):
-           game.quad[i][j] = IHDOT
     q = game.state.galaxy[game.quadrant.x][game.quadrant.y]
     # cope with supernova
     if q.supernova:
@@ -6283,7 +6283,7 @@ def newqad(shutup):
            w = newkling(i)
        # If we need a commander, promote a Klingon
        for i in range(1, game.state.remcom+1):
-           if same(game.state.kcmdr[i], game.quadrant):
+           if game.state.kcmdr[i] == game.quadrant:
                break
                        
        if i <= game.state.remcom:
@@ -6432,7 +6432,7 @@ def setpassword():
     else:
         game.passwd = ""
         for i in range(3):
-           game.passwd[i] += chr(97+int(random.random()*25))
+           game.passwd += chr(97+int(random.random()*25))
 
 # Code from sst.c begins here
 
@@ -6773,37 +6773,37 @@ def randplace(size):
     return w
 
 def chew():
+    # Demand input for next scan
     global inqueue
-    inqueue = []
+    inqueue = None
 
 def chew2():
     # return IHEOL next time 
     global inqueue
-    inqueue = ["\n"]
+    inqueue = []
 
 def scan():
     # Get a token from the user
-    global inqueue
+    global inqueue, line, citem
     aaitem = 0.0
     citem = ''
 
     # Read a line if nothing here
-    if line == '\n':
-        line = ''
-        return IHEOL
-    elif line == '':
+    if inqueue == None:
        line = cgetline()
        if curwnd==prompt_window:
            clrscr()
            setwnd(message_window)
            clrscr()
-    # Skip leading white space
-    line = line.lstrip()
-    # Nothing left
-    if not line:
-       return IHEOL
-    else:
-        inqueue += line.split()
+        # Skip leading white space
+        line = line.lstrip()
+        if line:
+            inqueue = line.split()
+        else:
+            inqueue = []
+            return IHEOL
+    elif not inqueue:
+        return IHEOL
     # From here on in it's all looking at the queue
     citem = inqueue.pop(0)
     if citem == IHEOL:
@@ -6811,7 +6811,7 @@ def scan():
     try:
         aaitem = float(citem)
         return IHREAL
-    except ValuError:
+    except ValueError:
         pass
     # Treat as alpha
     citem = citem.lower()
@@ -6836,8 +6836,8 @@ def huh():
     prout(_("Beg your pardon, Captain?"))
 
 def isit(s):
-    # compares s to citem and returns true if it matches to the length of s 
-    return citem.startswith(s)
+    # compares s to citem and returns true if it matches to the length of s
+    return s.startswith(citem)
 
 def debugme():
     # access to the internals for debugging 
@@ -6929,20 +6929,24 @@ def debugme():
        atover(True)
 
 if __name__ == '__main__':
-    line = ""
+    global line, thing, game
+    game = citem = aaitem = inqueue = None
+    line = ''
     thing = coord()
     game = gamestate()
+    idebug = 0
 
     game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_SHOWME | OPTION_PLAIN | OPTION_ALMY)
-    if os.getenv("TERM"):
-       game.options |= OPTION_CURSES | OPTION_SHOWME
-    else:
-       game.options |= OPTION_TTY
+    # 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 = time.time()
     (options, arguments) = getopt.getopt(sys.argv[1:], "r:tx")
     for (switch, val) in options:
-        if switch == 'r':
+        if switch == '-r':
             try:
                 replayfp = open(optarg, "r")
             except IOError:
@@ -6957,10 +6961,10 @@ if __name__ == '__main__':
                os.exit(1)
            game.options |= OPTION_TTY
            game.options &=~ OPTION_CURSES
-       elif switch == 't':
+       elif switch == '-t':
            game.options |= OPTION_TTY
            game.options &=~ OPTION_CURSES
-       elif switch == 'x':
+       elif switch == '-x':
            idebug = True
        else:
            sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
@@ -6976,16 +6980,16 @@ if __name__ == '__main__':
     random.seed(seed)
 
     iostart()
-
-    for i in range(optind, argc):
-       line += sys.argv[i]
-       line += " "
+    if arguments:
+        inqueue = arguments
+    else:
+        inqueue = None
 
     while True: # Play a game 
        setwnd(fullscreen_window)
        clrscr()
        prelim()
-       setup(line[0] == '\0')
+       setup(needprompt=not line)
        if game.alldone:
            score()
            game.alldone = False