Restore some display behavior.
[super-star-trek.git] / src / sst.py
index a83d6caaf8a53118ef0d039a7c6e61445936d2ad..33149f30dcd454bec93b83d6f344a993d5121dfd 100644 (file)
@@ -201,10 +201,6 @@ FOREVER    = 1e30
 MAXBURST       = 3
 MINCMDR        = 10
 
-# 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)
-def VALID_SECTOR(x, y):        return ((x)>=0 and (x)<QUADSIZE and (y)>=0 and (y)<QUADSIZE)
-
 # How to represent features
 IHR = 'R',
 IHK = 'K',
@@ -232,6 +228,10 @@ class coord:
     def __init__(self, x=None, y=None):
         self.i = x
         self.j = y
+    def valid_quadrant(self):
+        return self.i>=0 and self.i<GALSIZE and self.j>=0 and self.j<GALSIZE
+    def valid_sector(self):
+       return self.i>=0 and self.i<QUADSIZE and self.j>=0 and self.j<QUADSIZE
     def invalidate(self):
         self.i = self.j = None
     def is_valid(self):
@@ -602,7 +602,7 @@ def randreal(*args):
 
 def welcoming(iq):
     "Would this quadrant welcome another Klingon?"
-    return VALID_QUADRANT(iq.i,iq.j) and \
+    return iq.valid_quadrant() and \
        not game.state.galaxy[iq.i][iq.j].supernova and \
        game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
 
@@ -1261,63 +1261,63 @@ def collision(rammed, enemy):
        finish(FWON)
     return
 
-def torpedo(origin, course, dispersion, number, nburst):
+def torpedo(origin, bearing, dispersion, number, nburst):
     "Let a photon torpedo fly" 
     if not damaged(DSRSENS) or game.condition=="docked":
        setwnd(srscan_window)
     else: 
        setwnd(message_window)
-    shoved = False
-    ac = course + 0.25*dispersion
-    angle = (15.0-ac)*0.5235988
-    bullseye = (15.0 - course)*0.5235988
-    delta = coord(-math.sin(angle), math.cos(angle))          
-    bigger = max(abs(delta.i), abs(delta.j))
-    delta /= bigger
-    w = coord(0, 0); jw = coord(0, 0)
-    ungridded = copy.copy(origin)
+    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)
     # Loop to move a single torpedo 
+    setwnd(message_window)
     for step in range(1, QUADSIZE*2):
-       ungridded += delta
-       w = ungridded.roundtogrid()
-       if not VALID_SECTOR(w.i, w.j):
+       track.next()
+        w = track.sector()
+       if not w.valid_sector():
            break
        iquad=game.quad[w.i][w.j]
        tracktorpedo(origin, w, step, number, nburst, iquad)
        if iquad==IHDOT:
            continue
        # hit something 
-       setwnd(message_window)
-       if damaged(DSRSENS) and not game.condition=="docked":
+       if not damaged(DSRSENS) or game.condition == "docked":
            skip(1);    # start new line after text track 
        if iquad in (IHE, IHF): # Hit our ship 
            skip(1)
            prout(_("Torpedo hits %s.") % crmshp())
            hit = 700.0 + randreal(100) - \
-               1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
+               1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
            newcnd(); # we're blown out of dock 
-           # We may be displaced. 
            if game.landed or game.condition=="docked":
                return hit # Cheat if on a planet 
-           ang = angle + 2.5*(randreal()-0.5)
+           ang = track.angle + 2.5*(randreal()-0.5)
            temp = math.fabs(math.sin(ang))
            if math.fabs(math.cos(ang)) > temp:
                temp = math.fabs(math.cos(ang))
            xx = -math.sin(ang)/temp
            yy = math.cos(ang)/temp
-           jw.i = int(w.i+xx+0.5)
-           jw.j = int(w.j+yy+0.5)
-           if not VALID_SECTOR(jw.i, jw.j):
+           bumpto.i = int(w.i+xx+0.5)
+           bumpto.j = int(w.j+yy+0.5)
+           if not bumpto.valid_sector():
                return hit
-           if game.quad[jw.i][jw.j]==IHBLANK:
+           if game.quad[bumpto.i][bumpto.j]==IHBLANK:
                finish(FHOLE)
                return hit
-           if game.quad[jw.i][jw.j]!=IHDOT:
+           if game.quad[bumpto.i][bumpto.j]!=IHDOT:
                # can't move into object 
                return hit
-           game.sector = jw
+           game.sector = bumpto
            proutn(crmshp())
-           shoved = True
+            game.quad[w.i][w.j]=IHDOT
+            game.quad[bumpto.i][bumpto.j]=iquad
+            prout(_(" displaced by blast to Sector %s ") % bumpto)
+            for enemy in game.enemies:
+                enemy.kdist = enemy.kavgd = (game.sector-enemy.kloc).distance()
+            game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+            return None
        elif iquad in (IHC, IHS, IHR, IHK): # Hit a regular enemy 
            # find the enemy 
            if iquad in (IHC, IHS) and withprob(0.05):
@@ -1329,7 +1329,7 @@ def torpedo(origin, course, dispersion, number, nburst):
                    break
            kp = math.fabs(enemy.kpower)
            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 kp < h1:
                h1 = kp
@@ -1342,29 +1342,31 @@ def torpedo(origin, course, dispersion, number, nburst):
                return None
            proutn(crmena(True, iquad, "sector", w))
            # If enemy damaged but not destroyed, try to displace 
-           ang = angle + 2.5*(randreal()-0.5)
+           ang = track.angle + 2.5*(randreal()-0.5)
            temp = math.fabs(math.sin(ang))
            if math.fabs(math.cos(ang)) > temp:
                temp = math.fabs(math.cos(ang))
            xx = -math.sin(ang)/temp
            yy = math.cos(ang)/temp
-           jw.i = int(w.i+xx+0.5)
-           jw.j = int(w.j+yy+0.5)
-           if not VALID_SECTOR(jw.i, jw.j):
+           bumpto.i = int(w.i+xx+0.5)
+           bumpto.j = int(w.j+yy+0.5)
+            if not bumpto.valid_sector():
                prout(_(" damaged but not destroyed."))
                return
-           if game.quad[jw.i][jw.j]==IHBLANK:
+           if game.quad[bumpto.i][bumpto.j] == IHBLANK:
                prout(_(" buffeted into black hole."))
-               deadkl(w, iquad, jw)
-               return None
-           if game.quad[jw.i][jw.j]!=IHDOT:
-               # can't move into object 
+               deadkl(w, iquad, bumpto)
+           if game.quad[bumpto.i][bumpto.j] != IHDOT:
                prout(_(" damaged but not destroyed."))
-               return None
-           proutn(_(" damaged--"))
-           enemy.kloc = jw
-           shoved = True
-           break
+            else:
+                prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
+                enemy.kloc = bumpto
+                game.quad[w.i][w.j]=IHDOT
+                game.quad[bumpto.i][bumpto.j]=iquad
+                for enemy in game.enemies:
+                    enemy.kdist = enemy.kavgd = (game.sector-enemy.kloc).distance()
+                game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
+            return None
        elif iquad == IHB: # Hit a base 
            skip(1)
            prout(_("***STARBASE DESTROYED.."))
@@ -1460,16 +1462,6 @@ def torpedo(origin, course, dispersion, number, nburst):
            skip(1)
            return None
        break
-    if curwnd!=message_window:
-       setwnd(message_window)
-    if shoved:
-       game.quad[w.i][w.j]=IHDOT
-       game.quad[jw.i][jw.j]=iquad
-       prout(_(" displaced by blast to Sector %s ") % jw)
-       for ll in range(len(game.enemies)):
-           game.enemies[ll].kdist = game.enemies[ll].kavgd = (game.sector-game.enemies[ll].kloc).distance()
-        game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
-       return None
     skip(1)
     prout(_("Torpedo missed."))
     return None;
@@ -1701,7 +1693,7 @@ def deadkl(w, type, mv):
 
 def targetcheck(w):
     "Return None if target is invalid, otherwise return a course angle."
-    if not VALID_SECTOR(w.i, w.j):
+    if not w.valid_sector():
        huh()
        return None
     delta = coord()
@@ -2456,27 +2448,27 @@ def events():
                supercommander()
        elif evcode == FDSPROB: # Move deep space probe 
            schedule(FDSPROB, 0.01)
-            if game.probe.nextquad():
-               if not VALID_QUADRANT(game.probe.loc.i, game.probe.loc.j) or \
-                   game.state.galaxy[game.probe.loc.i][game.probe.loc.j].supernova:
+            if game.probe.next(grain=QUADSIZE):
+               if not game.probe.quadrant().valid_quadrant() or \
+                   game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
                    # Left galaxy or ran into supernova
                     if communicating():
                        announce()
                        skip(1)
                        proutn(_("Lt. Uhura-  \"The deep space probe "))
-                       if not VALID_QUADRANT(game.probe.loc.i, game.probe.loc.j):
-                           proutn(_("has left the galaxy.\""))
+                       if not game.probe.quadrant().valid_quadrant():
+                           prout(_("has left the galaxy.\""))
                        else:
-                           proutn(_("is no longer transmitting.\""))
+                           prout(_("is no longer transmitting.\""))
                    unschedule(FDSPROB)
                    continue
                 if communicating():
                    #announce()
                    skip(1)
-                   prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.loc)
-           pdest = game.state.galaxy[game.probe.loc.i][game.probe.loc.j]
+                   prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
+           pdest = game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j]
            if communicating():
-               chp = game.state.chart[game.probe.loc.i][game.probe.loc.j]
+               chp = game.state.chart[game.probe.quadrant().i][game.probe.quadrant().j]
                chp.klingons = pdest.klingons
                chp.starbase = pdest.starbase
                chp.stars = pdest.stars
@@ -2485,7 +2477,7 @@ def events():
            if game.probe.moves == 0 and game.isarmed and pdest.stars:
                supernova(game.probe)           # fire in the hole!
                unschedule(FDSPROB)
-               if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova: 
+               if game.state.galaxy[game.quadrant().i][game.quadrant().j].supernova: 
                    return
        elif evcode == FDISTR: # inhabited system issues distress call 
            unschedule(FDISTR)
@@ -2548,14 +2540,15 @@ def events():
                continue                # full right now 
            # reproduce one Klingon 
            w = ev.quadrant
+            m = coord()
            if game.klhere >= MAXKLQUAD:
                 try:
                     # this quadrant not ok, pick an adjacent one 
-                    for i in range(w.i - 1, w.i + 2):
-                        for j in range(w.j - 1, w.j + 2):
-                            if not VALID_QUADRANT(i, j):
+                    for m.i in range(w.i - 1, w.i + 2):
+                        for m.j in range(w.j - 1, w.j + 2):
+                            if not m.valid_quadrant():
                                 continue
-                            q = game.state.galaxy[w.i][w.j]
+                            q = game.state.galaxy[m.i][m.j]
                             # check for this quad ok (not full & no snova) 
                             if q.klingons >= MAXKLQUAD or q.supernova:
                                 continue
@@ -2563,7 +2556,7 @@ def events():
                     else:
                         continue       # search for eligible quadrant failed
                 except "FOUNDIT":
-                    w.i = i; w.j = j
+                    w = m
            # deliver the child 
            game.state.remkl += 1
            q.klingons += 1
@@ -2572,7 +2565,6 @@ def events():
                game.enemies.append(newkling())
            # recompute time left
             game.recompute()
-           # report the disaster if we can 
            if communicating():
                if game.quadrant == w:
                    prout(_("Spock- sensors indicate the Klingons have"))
@@ -2658,7 +2650,7 @@ def nova(nov):
                 if offset.j==0 and offset.i==0:
                     continue
                 neighbor = start + offset
-                if not VALID_SECTOR(neighbor.j, neighbor.i):
+                if not neighbor.valid_sector():
                     continue
                 iquad = game.quad[neighbor.i][neighbor.j]
                 # Empty space ends reaction
@@ -2730,7 +2722,7 @@ def nova(nov):
                         break
                     newc = neighbor + neighbor - hits[mm]
                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
-                    if not VALID_SECTOR(newc.i, newc.j):
+                    if not newc.valid_sector():
                         # can't leave quadrant 
                         skip(1)
                         break
@@ -2755,12 +2747,12 @@ def nova(nov):
        dist = 0.0
     if dist == 0.0:
        return
-    course = course(distance=dist, bearing=direc, warp=4)
-    game.optime = course.time()
+    course = course(bearing=direc, distance=dist)
+    game.optime = course.time(warp=4)
     skip(1)
     prout(_("Force of nova displaces starship."))
     imove(course, novapush=True)
-    game.optime = course.time()
+    game.optime = course.time(warp=4)
     return
        
 def supernova(w):
@@ -3529,7 +3521,7 @@ def tracktorpedo(origin, w, step, i, n, iquad):
        if step == 1:
            if n != 1:
                skip(1)
-               proutn(_("Track for %s torpedo number %d-  ") % (game.quad[origin.i][origin.j],i+1))
+               proutn(_("Track for torpedo number %d-  ") % (i+1))
            else:
                skip(1)
                proutn(_("Torpedo track- "))
@@ -3634,7 +3626,7 @@ def imove(course=None, novapush=False):
             y += deltay
            w.i = int(round(x))
            w.j = int(round(y))
-           if not VALID_SECTOR(w.i, w.j):
+           if not w.valid_sector():
                # Leaving quadrant -- allow final enemy attack 
                # Don't do it if being pushed by Nova 
                if len(game.enemies) != 0 and not novapush:
@@ -3794,6 +3786,14 @@ def dock(verbose):
 # because it involves giving x and y motions, yet the coordinates
 # are always displayed y - x, where +y is downward!
 
+def cartesian(loc1=None, loc2=None):
+    if loc1 is None:
+        return game.quadrant * QUADSIZE + game.sector
+    elif loc2 is None:
+        return game.quadrant * QUADSIZE + loc1
+    else:
+        return loc1 * QUADSIZE + loc2
+
 def getcourse(isprobe):
     "Get a course and distance from the user."
     key = 0
@@ -3842,6 +3842,7 @@ def getcourse(isprobe):
                prout(_("(Manual movement assumed.)"))
            navmode = "manual"
            break
+    delta = coord()
     if navmode == "automatic":
        while key == "IHEOL":
            if isprobe:
@@ -3885,7 +3886,7 @@ def getcourse(isprobe):
                dsect.i = xi
                dsect.j = xj
            itemp = "normal"
-       if not VALID_QUADRANT(dquad.i,dquad.j) or not VALID_SECTOR(dsect.i,dsect.j):
+       if not dquad.valid_quadrant() or not dsect.valid_sector():
            huh()
            raise TrekError
        skip(1)
@@ -3896,7 +3897,6 @@ def getcourse(isprobe):
            else:
                prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
         # the actual deltas get computed here
-        delta = coord()
        delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
        delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
     else: # manual 
@@ -3923,44 +3923,44 @@ def getcourse(isprobe):
        skip(1)
        prout(_("Helmsman Sulu- \"Aye, Sir.\""))
     scanner.chew()
-    return course(delta.distance(), delta.bearing(), isprobe=isprobe)
+    return course(bearing=delta.bearing(), distance=delta.distance())
 
 class course:
-    # Eventually, we want to consolidate all course compuation here,
-    # including for torpedos and the Enterprise.
-    def __init__(self, distance, bearing, warp=None, isprobe=False): 
-        # Course actually laid in -- thisis straight from the old getcd().
+    def __init__(self, bearing, distance, origin=None): 
         self.distance = distance
         self.bearing = bearing
-        self.warp = warp or game.warpfac
-        self.isprobe = isprobe
-        # This odd relic suggests that the bearing() code we inherited from
-        # FORTRAN is actually computing clockface directions.
+        # The bearing() code we inherited from FORTRAN is actually computing
+        # clockface directions!
         if self.bearing < 0.0:
             self.bearing += 12.0
-        # This code was moved from the probe() routine
-        if isprobe:
-            angle = ((15.0 - self.bearing) * 0.5235988)
-            self.increment = coord(-math.sin(angle), math.cos(angle))
-            bigger = max(abs(self.increment.i), abs(self.increment.j))
-            self.increment /= bigger
-            self.location = coord(game.quadrant.i*QUADSIZE + game.sector.i, 
-                               game.quadrant.j*QUADSIZE + game.sector.j)
-            self.loc = copy.copy(game.quadrant)
-            self.moves = 10.0*self.distance*bigger +0.5
-    def power(self):
-       return self.distance*(self.warp**3)*(game.shldup+1)
-    def time(self):
-        return 10.0*self.distance/self.warp**2
-    def nextquad(self):
-        "Next location on course, at quadrant granularity."
-        self.location += self.increment
-        newloc = (self.location / float(QUADSIZE)).trunctogrid()
-        if not newloc == self.loc:
-            self.loc = newloc
+        self.angle = ((15.0 - self.bearing) * 0.5235988)
+        if origin is None:
+            self.location = cartesian(game.quadrant, game.sector)
+        else:
+            self.location = cartesian(game.quadrant, origin)
+        self.increment = coord(-math.sin(self.angle), math.cos(self.angle))
+        bigger = max(abs(self.increment.i), abs(self.increment.j))
+        self.increment /= bigger
+        self.moves = 10*self.distance*bigger +0.5
+    def next(self, grain=1):
+        "Next step on course."
+        self.moves -=1
+        self.nextlocation = self.location + self.increment
+        oldloc = (self.location/grain).roundtogrid()
+        newloc = (self.nextlocation/grain).roundtogrid()
+        self.location = self.nextlocation
+        if newloc != oldloc:
             return True
         else:
             return False
+    def quadrant(self):
+        return (self.location / QUADSIZE).roundtogrid()
+    def sector(self):
+        return coord(int(round(self.location.i)) % QUADSIZE, int(round(self.location.j)) % QUADSIZE)
+    def power(self, warp):
+       return self.distance*(warp**3)*(game.shldup+1)
+    def time(self, warp):
+        return 10.0*self.distance/warp**2
 
 def impulse():
     "Move under impulse power."
@@ -4036,7 +4036,7 @@ def warp(course, involuntary):
        # 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.energy:
+       if course.power(game.warpfac) >= game.energy:
            # Insufficient power for trip 
            game.ididit = False
            skip(1)
@@ -4056,7 +4056,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.optime = course.time(game.warpfac)
        if game.optime >= 0.8*game.state.remtime:
            skip(1)
            prout(_("First Officer Spock- \"Captain, I compute that such"))
@@ -4104,7 +4104,7 @@ def warp(course, involuntary):
                ix = x + 0.5
                y += deltay
                iy = y +0.5
-               if not VALID_SECTOR(ix, iy):
+               if not coord(ix, iy).valid_sector():
                    break
                if game.quad[ix][iy] != IHDOT:
                    blooey = False
@@ -4113,10 +4113,10 @@ def warp(course, involuntary):
     imove(course, novapush=False)
     if game.alldone:
        return
-    game.energy -= course.power()
+    game.energy -= course.power(game.warpfac)
     if game.energy <= 0:
        finish(FNRG)
-    game.optime = course.time()
+    game.optime = course.time(game.warpfac)
     if twarp:
        timwrp()
     if blooey:
@@ -4227,7 +4227,7 @@ def atover(igrab):
        distreq = randreal(math.sqrt(2))
        if distreq < game.dist:
            dist = distreq
-        course = course(distance=dist, bearing=randreal(12))   # How dumb!
+        course = course(bearing=randreal(12), distance=dist)   # How dumb!
        game.optime = course.time()
        game.justin = False
        game.inorbit = False
@@ -4386,7 +4386,7 @@ def mayday():
     game.sector.invalidate()
     for m in range(1, 5+1):
         w = game.base.scatter() 
-       if VALID_SECTOR(w.i,w.j) and game.quad[w.i][w.j]==IHDOT:
+       if w.valid_sector() and game.quad[w.i][w.j]==IHDOT:
            # found one -- finish up 
             game.sector = w
            break
@@ -4489,7 +4489,7 @@ def abandon():
            game.quad[game.sector.i][game.sector.j] = IHDOT
            for l in range(QUADSIZE):
                game.sector = game.base.scatter()
-               if VALID_SECTOR(game.sector.i, game.sector.j) and \
+               if game.sector.valid_sector() and \
                        game.quad[game.sector.i][game.sector.j] == IHDOT:
                     break
            if l < QUADSIZE+1:
@@ -5085,7 +5085,7 @@ def lrscan(silent):
         if not silent:
             proutn(" ")
         for y in range(game.quadrant.j-1, game.quadrant.j+2):
-           if not VALID_QUADRANT(x, y):
+           if not coord(x, y).valid_quadrant():
                 if not silent:
                     proutn("  -1")
            else:
@@ -5104,7 +5104,6 @@ def damagereport():
     "Damage report."
     jdam = False
     scanner.chew()
-
     for i in range(NDEVICES):
        if damaged(i):
            if not jdam:
@@ -5311,7 +5310,7 @@ def eta():
            w2.j = 0
        else:
            w2.j=QUADSIZE-1
-    if not VALID_QUADRANT(w1.i, w1.j) or not VALID_SECTOR(w2.i, w2.j):
+    if not w1.valid_quadrant() or not w2.valid_sector():
        huh()
        return
     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+