Get rid of obnoxious visible "have we paused?" state.
[super-star-trek.git] / src / sst.py
1 """
2 sst.py =-- Super Star Trek in Python
3
4 Control flow of this translation is pretty much identical to the C version
5 (and thus like the ancestral FORTRAN) but the data structures are
6 radically different -- the Python code makes heavy use of objects.
7
8 Note that the game.quad, game.snap.galaxy and game.snap.chart members
9 are not actually arrays but dictioaries indixed by coord tuples.  Be setting
10 the hash of a coord equal to the hash of a literal tuple containing its
11 coordinate data, we ensure these can be indexed both ways.
12
13 """
14 import math
15
16 PHASEFAC        = 2.0
17 GALSIZE         = 8
18 NINHAB          = GALSIZE * GALSIZE / 2
19 MAXUNINHAB      = 10
20 PLNETMAB        = NINHAB + MAXUNINHAB
21 QUADSIZE        = 10
22 BASEMAX         = 5
23 FULLCREW        = 428    # BSD Trek was 387, that's wrong
24 MAXKLGAME       = 127
25 MAXKLQUAD       = 9
26 FOREVER         = 1e30
27
28 # These macros hide the difference between 0-origin and 1-origin addressing.
29 # They're a step towards de-FORTRANizing the code.
30 def VALID_QUADRANT(x,y): ((x)>=1 and (x)<=GALSIZE and (y)>=1 and (y)<=GALSIZE)
31 def VALID_SECTOR(x, y): ((x)>=1 and (x)<=QUADSIZE and (y)>=1 and (y)<=QUADSIZE)
32
33 # These types have not been dealt with yet 
34 IHQUEST = '?',
35 IHWEB = '#',
36 IHMATER0 = '-',
37 IHMATER1 = 'o',
38 IHMATER2 = '0',
39
40 class coord:
41     def __init(self, x=None, y=None):
42         self.x = x
43         self.y = y
44     def invalidate(self):
45         self.x = self.y = None
46     def is_valid(self):
47         return self.x != None and self.y != None
48     def __eq__(self, other):
49         return self.x == other.y and self.x == other.y
50     def __add__(self, other):
51         return coord(self.x+self.x, self.y+self.y)
52     def __sub__(self, other):
53         return coord(self.x-self.x, self.y-self.y)
54     def distance(self, other):
55         return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
56     def sgn(self):
57         return coord(self.x / abs(x), self.y / abs(y));
58     def __hash__(self):
59         return hash((x, y))
60     def __str__(self):
61         return "%d - %d" % (self.x, self.y)
62
63 class feature:
64     "A feature in the current quadrant (ship, star, black hole, base, etc)." 
65     def __init__(self):
66         self.type = None        # name of feature type
67         self.sector = None      # sector location
68     def distance(self):
69         return self.sector.distance(game.sector)
70     def __str__(self):
71         "This will be overridden by subclasses."
72         return self.name[0]
73     def sectormove(self, dest):
74         "Move this feature within the current quadrant." 
75         if self.sector:
76             game.quad[self.sector] = None
77         game.quad[dest] = self
78         self.sector = dest
79
80 class ship(feature):
81     "A starship, frindly or enemy." 
82     def __init__(self, type, power):
83         feature.__init__(self)
84         self.type = type        # klingon, romulan, commander,
85                                 # supercommander, tholian,
86                                 # enterprise, faerie queene.
87         self.power = power      # power
88         if self.type in ("Klingon", "Commander", "Super-Commander"):
89             game.remkl += 1
90         elif self.type == "Romulan":
91             game.romrem += 1
92     def __del__(self):
93         if self.type in ("Klingon", "Commander", "Super-Commander"):
94             game.remkl -= 1
95         elif self.type == "Romulan":
96             game.romrem -= 1
97
98 class space(feature):
99     "Empty space.  Has no state, just knows how to identify iself."
100     def __str__(self):
101         return '*'
102
103 class star(feature):
104     "A star.  Has no state, just knows how to identify iself."
105     def __str__(self):
106         return '*'
107
108 class planet(feature):
109     "A planet.  May be inhabited or not, may hold dilithium crystals or not."
110     def __init(self):
111         feature.__init__(self)
112         self.name = None
113         self.crystals = None    # "absent", "present", or "mined"
114         self.inhabited = False
115         self.known = "unknown"  # Other values: "known" and "shuttle down"
116         game.state.planets.append(self)
117     def __del__(self):
118         game.state.planets.remove(self)
119     def __str__(self):
120         if self.inhabited:
121             return '@'
122         else:
123             return 'P'
124
125 class web(feature):
126     "A bit of Tholian web.  Has no state, just knows how to identify iself."
127     def __str__(self):
128         return '*'
129
130 class blackhole(feature):
131     "A black hole.  Has no hair, just knows how to identify iself."
132     def __str__(self):
133         return ' '
134
135 class starbase(feature):
136     "Starbases also have no features, just a location."
137     def __init(self, quadrant):
138         feature.__init__(self)
139         self.quadrant = quadrant
140         game.state.bases.append(self)
141     def __del__(self):
142         game.state.bases.remove(self)
143     def __str__(self):
144         return 'B'
145
146 class quadrant:
147     def __init__(self):
148         self.stars = None
149         self.planet = None
150         self.starbase = None
151         self.klingons = None
152         self.romulans = None
153         self.supernova = None
154         self.charted = None
155         self.status = "secure"  # Other valuues: "distressed", "enslaved"
156     def enemies(self):
157         "List enemies in this quadrant."
158         lst = []
159         for feature in self.quad.values:
160             if not isinstance(feature, ship):
161                 continue
162             if feature.name not in ("Enterprise", "Faerie Queene"):
163                 lst.append(feature)
164         return lst
165
166 class page:
167     "A chart page.  The starchart is a 2D array of these."
168     def __init__(self):
169         self.stars = None       # Will hold a number
170         self.starbase = None    # Will hold a bool
171         self.klingons = None    # Will hold a number
172
173 class snapshot:
174     "State of the universe.  The galaxy is a 2D array of these."
175     def __init__(self):
176         self.crew = None        # crew complement
177         self.remkl = None       # remaining klingons
178         self.remcom = None      # remaining commanders
179         self.nscrem = None      # remaining super commanders
180         self.starkl = None      # destroyed stars
181         self.basekl = None      # destroyed bases
182         self.nromrem = None     # Romulans remaining
183         self.nplankl = None     # destroyed uninhabited planets self.nworldkl = None    # destroyed inhabited planets
184         self.planets = [];      # List of planets known
185         self.date = None        # stardate
186         self.remres = None      # remaining resources
187         self. remtime = None    # remaining time
188         self.bases = []         # Base quadrant coordinates
189         self.kcmdr = []         # Commander quadrant coordinates
190         self.kscmdr = None      # Supercommander quadrant coordinates
191         self.galaxy = {}        # Dictionary of quadrant objects
192         self.chart = {}         # Dictionary of page objects
193
194 def damaged(dev):
195     return game.damage[dev] != 0.0
196
197 class event:
198     def __init__(self):
199         self.date = None        # The only mandatory attribute
200
201 class game:
202     def __init__(self):
203         self.options = []               # List of option strings
204         self.state = snapshot()         # State of the universe
205         self.snapsht = snapshot()       # For backwards timetravel
206         self.quad = {}                  # contents of our quadrant
207         self.kpower = {}                # enemy energy levels
208         self.kdist = {}                 # enemy distances
209         self.kavgd = {}                 # average distances
210         self.damage = {}                # damage encountered
211         self.future = []                # future events
212         self.passwd = None              # Self Destruct password
213         # Coordinate members start here
214         self.enemies = {}                       # enemy sector locations
215         self.quadrant = None            # where we are
216         self.sector = None
217         self.tholian = None             # coordinates of Tholian
218         self.base = None                # position of base in current quadrant
219         self.battle = None              # base coordinates being attacked
220         self.plnet = None               # location of planet in quadrant
221         self.probec = None              # current probe quadrant
222         # Flag members start here
223         self.gamewon = None             # Finished!
224         self.ididit = None              # action taken -- allows enemy to attack
225         self.alive = None               # we are alive (not killed)
226         self.justin = None              # just entered quadrant
227         self.shldup = None              # shields are up
228         self.shldchg = None             # shield changing (affects efficiency)
229         self.comhere = None             # commander here
230         self.ishere = None              # super-commander in quadrant
231         self.iscate = None              # super commander is here
232         self.ientesc = None             # attempted escape from supercommander
233         self.ithere = None              # Tholian is here 
234         self.resting = None             # rest time
235         self.icraft = None              # Kirk in Galileo
236         self.landed = None              # party on planet or on ship
237         self.alldone = None             # game is now finished
238         self.neutz = None               # Romulan Neutral Zone
239         self.isarmed = None             # probe is armed
240         self.inorbit = None             # orbiting a planet
241         self.imine = None               # mining
242         self.icrystl = None             # dilithium crystals aboard
243         self.iseenit = None             # seen base attack report
244         self.thawed = None              # thawed game
245         # String members start here
246         self.condition = None           # green, yellow, red, docked, dead,
247         self.iscraft = None             # onship, offship, removed
248         self.skill = None               # levels: none, novice, fair, good,
249                                         # expert, emeritus
250         # Integer nembers sart here
251         self.inkling = None             # initial number of klingons
252         self.inbase = None              # initial number of bases
253         self.incom = None               # initial number of commanders
254         self.inscom = None              # initial number of commanders
255         self.inrom = None               # initial number of commanders
256         self.instar = None              # initial stars
257         self.intorps = None             # initial/max torpedoes
258         self.torps = None               # number of torpedoes
259         self.ship = None                # ship type -- 'E' is Enterprise
260         self.abandoned = None           # count of crew abandoned in space
261         self.length = None              # length of game
262         self.klhere = None              # klingons here
263         self.casual = None              # causalties
264         self.nhelp = None               # calls for help
265         self.nkinks = None              # count of energy-barrier crossings
266         self.iplnet = None              # planet # in quadrant
267         self.inplan = None              # initial planets
268         self.irhere = None              # Romulans in quadrant
269         self.isatb = None               # =1 if super commander is attacking base
270         self.tourn = None               # tournament number
271         self.proben = None              # number of moves for probe
272         self.nprobes = None             # number of probes available
273         # Float members start here
274         self.inresor = None             # initial resources
275         self.intime = None              # initial time
276         self.inenrg = None              # initial/max energy
277         self.inshld = None              # initial/max shield
278         self.inlsr = None               # initial life support resources
279         self.indate = None              # initial date
280         self.energy = None              # energy level
281         self.shield = None              # shield level
282         self.warpfac = None             # warp speed
283         self.wfacsq = None              # squared warp factor
284         self.lsupres = None             # life support reserves
285         self.dist = None                # movement distance
286         self.direc = None               # movement direction
287         self.optime = None              # time taken by current operation
288         self.docfac = None              # repair factor when docking (constant?)
289         self.damfac = None              # damage factor
290         self.lastchart = None           # time star chart was last updated
291         self.cryprob = None             # probability that crystal will work
292         self.probex = None              # location of probe
293         self.probey = None              #
294         self.probeinx = None            # probe x,y increment
295         self.probeiny = None            #
296         self.height = None              # height of orbit around planet
297
298 def communicating():
299     "Are we in communication with Starfleet Command?"
300     return (not damaged("DRADIO")) or game.condition == "docked"
301
302 # Code corresponding to ai.c begins here
303
304 def tryexit(look, ship, irun):
305     # a bad guy attempts to bug out of the quadrant
306     iq = coord()
307     iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1
308     iq.y = game.quadrant.y+(look.y+(QUADSIZE-1))/QUADSIZE - 1
309     if not valid_quadrant(iq) or \
310         game.state.galaxy[iq].supernova or \
311         game.state.galaxy[iq].klingons > MAXKLQUAD-1:
312         return False;   # no can do -- neg energy, supernovae, or >MAXKLQUAD-1 Klingons
313     if ship.type == "Romulan":
314         return False    # Romulans cannot escape
315     if not irun:
316         # avoid intruding on another commander's territory
317         if ship.type == "Commander":
318             if iq in gamestate.kcmdr:
319                 return False
320             # refuse to leave if currently attacking starbase:
321             if game.battle == game.quadrant:
322                 return False;
323         # don't leave if over 1000 units of energy
324         if ship.power > 1000.0:
325             return false;
326     # Print escape message and move out of quadrant.
327     # We know this if either short or long range sensors are working
328     if not damaged("DSRSENS") or not damaged("DLRSENS") or game.condition=="docked":
329         crmena(True, "sector", ship)
330         prout(" escapes to quadrant %s (and regains strength)." % iq)
331     # handle local matters related to escape
332     game.quad[ship.location] = None;
333     if game.condition != "docked":
334         newcnd()
335     # Handle global matters related to escape
336     game.state.galaxy[game.quadrant].klingons -= 1
337     game.state.galaxy[iq].klingons += 1
338     if ship.type == "Super-Commander":
339         game.ishere = False
340         game.iscate = False
341         game.ientesc = False
342         game.isatb = 0
343         schedule("FSCMOVE", 0.2777)
344         unschedule("FSCDBAS")
345         game.state.kscmdr = iq
346     else:
347         for (n, cmdr) in enumerate(game.state.kcmdr):
348             if cmdr == game.quadrant:
349                 game.state.kcmdr[n] = iq
350                 break
351         game.comhere = False
352     return True         # successful exit
353
354 def sgn(n): n / abs(n)
355
356 '''
357 Algorithm for moving bad guys:
358
359  * Enterprise has "force" based on condition of phaser and photon torpedoes.
360  If both are operating full strength, force is 1000. If both are damaged,
361  force is -1000. Having shields down subtracts an additional 1000.
362
363  * Enemy has forces equal to the energy of the attacker plus
364  100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
365  346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
366
367  Attacker Initial energy levels (nominal):
368  Klingon   Romulan   Commander   Super-Commander
369  Novice    400        700        1200        
370  Fair      425        750        1250
371  Good      450        800        1300        1750
372  Expert    475        850        1350        1875
373  Emeritus  500        900        1400        2000
374  VARIANCE   75        200         200         200
375
376  Enemy vessels only move prior to their attack. In Novice - Good games
377  only commanders move. In Expert games, all enemy vessels move if there
378  is a commander present. In Emeritus games all enemy vessels move.
379
380  *  If Enterprise is not docked, an agressive action is taken if enemy
381  forces are 1000 greater than Enterprise.
382
383  Agressive action on average cuts the distance between the ship and
384  the enemy to 1/4 the original.
385
386  *  At lower energy advantage, movement units are proportional to the
387  advantage with a 650 advantage being to hold ground, 800 to move forward
388  1, 950 for two, 150 for back 4, etc. Variance of 100.
389
390  If docked, is reduced by roughly 1.75*game.skill, generally forcing a
391  retreat, especially at high skill levels.
392
393  *  Motion is limited to skill level, except for SC hi-tailing it out.
394 '''
395
396 def movebaddy(ship):
397     # tactical movement for the bad guys
398     bugout = False
399     # This should probably be just game.comhere + game.ishere
400     if game.skill >= SKILL_EXPERT:
401         nbaddys = int((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0)
402     else:
403         nbaddys = game.comhere + game.ishere
404     dist1 = ship.distance()
405     mdist = round(dist1 + 0.5)          # Nearest integer distance
406     # If SC, check with spy to see if should high-tail it
407     if ship.type == "Super-Commander" and \
408            (ship.power <= 500.0 or (game.condition==docked and not damaged("DPHOTON"))):
409         bugout = True;
410         motion = -QUADSIZE;
411     else:
412         # decide whether to advance, retreat, or hold position
413         forces = ship.power + 100.0*len(game.quad.enemies()) + 400*(nbaddys-1)
414         if not game.shldup:
415             forces += 1000.0            # Good for enemy if shield is down!
416         if not damaged("DPHASER") or not damaged("DPHOTON"):
417             if damaged(DPHASER):
418                 forces += 300.0
419             else:
420                 forces -= 0.2*(game.energy - 2500.0);
421             if damaged("DPHOTON"):
422                 forces += 300.0
423             else:
424                 forces -= 50.0*game.torps
425         else:
426             # phasers and photon tubes both out!
427             forces += 1000.0
428         motion = 0;
429         if forces <= 1000.0 and game.condition != "docked":     # Typical case
430             motion = ((forces+200.0*Rand())/150.0) - 5.0
431         else:
432             if forces > 1000.0: # Very strong -- move in for kill
433                 motion = (1.0-square(Rand()))*dist1 + 1.0
434             if game.condition == "docked" and "base" in game.options:
435                  # protected by base -- back off !
436                 motion -= game.skill * (2.0-Rand()**2)
437         if idebug:
438             proutn("=== MOTION = %1.2f, FORCES = %1.2f, ", motion, forces)
439         # don't move if no motion
440         if motion == 0:
441             return
442         # Limit motion according to skill
443         if abs(motion) > game.skill:
444             if motion < 0:
445                 motion = -game.kill
446             else:
447                 motion = game.skill
448     # calculate preferred number of steps
449     nsteps = abs(motion)
450     if motion > 0 and nsteps > mdist: # don't overshoot
451         nsteps = mdist
452     if nsteps > QUADSIZE: # This shouldn't be necessary
453         nsteps = QUADSIZE
454     if nsteps < 1:  # This shouldn't be necessary
455         nsteps = 1
456     if idebug:
457         proutn("NSTEPS = %d:", nsteps)
458     # Compute preferred values of delta X and Y
459     me = game.sector - com;
460     if 2.0 * abs(me.x) < abs(me.y):
461         me.x = 0;
462     if 2.0 * abs(me.y) < abs(game.sector.x-com.x):
463         me.y = 0;
464     if me.x != 0: me.x = sgn(me.x*motion)
465     if me.y != 0: me.y = sgn(me.y*motion)
466     next = com;
467     # main move loop
468     for ll in range(nsteps):
469         if idebug:
470             proutn(" %d", ll+1)
471         # Check if preferred position available
472         look = next + me
473         krawl = me.sgn()
474         success = False
475         attempts = 0 # Settle meysterious hang problem
476         while attempts < 20 and not success:
477             attempts += 1
478             if look.x < 1 or look.x > QUADSIZE:
479                 if motion < 0 and tryexit(look, ship, bugout):
480                     return
481                 if krawl.x == me.x or me.y == 0:
482                     break
483                 look.x = next.x + krawl.x
484                 krawl.x = -krawl.x
485             elif look.y < 1 or look.y > QUADSIZE:
486                 if motion < 0 and tryexit(look, ship, bugout):
487                     return
488                 if krawl.y == me.y or me.x == 0:
489                     break
490                 look.y = next.y + krawl.y
491                 krawl.y = -krawl.y
492             elif "ramming" in game.options and game.quad[look] != IHDOT:
493                 # See if we should ram ship
494                 if game.quad[look] == game.ship and ienm in (IHC, IHS):
495                     ram(true, ienm, com)
496                     return
497                 if krawl.x != me.x and me.y != 0:
498                     look.x = next.x + krawlx
499                     krawl.x = -krawl.x
500                 elif krawly != me.y and me.x != 0:
501                     look.y = next.y + krawly
502                     krawl.y = -krawl.y
503                 else:
504                     break # we have failed
505             else:
506                 success = True
507         if success:
508             next = look
509             if idebug:
510                 proutn(str(next))
511         else:
512             break # done early
513     if idebug:
514         prout("")
515     # Put ship in place within same quadrant
516     if next != ship.location:
517         # it moved
518         if not damaged("DSRSENS") or game.condition == "docked":
519             proutn("*** %s from sector %s" % (ship, ship.location))
520             if ship.distance() < dist1:
521                 prout(" advances to sector %s" % ship.location)
522             else:
523                 prout(" retreats to sector %s" % ship.location)
524         ship.sectormove(next)
525
526 def moveklings(): 
527     "Allow enemies to move."
528     for enemy in self.quad.enemies():
529         if enemy.type == "Commander":
530             movebaddy(enemy)
531             break
532     for enemy in self.quad.enemies():
533         if enemy.type == "Super-Commander":
534             movebaddy(enemy)
535             break
536     # Ff skill level is high, move other Klingons and Romulans too!
537     # Move these last so they can base their actions on what the
538     # commander(s) do.
539     if game.skill >= SKILL_EXPERT and "movebaddy" in game.options: 
540         for enemy in self.quad.enemies():
541             if enemy.type in ("Klingon", "Romulan"):
542                 movebaddy(enemy)
543                 break
544
545 def movescom(ship, avoid):
546     # commander movement helper
547     if game.state.kscmdr == game.quadrant or \
548         game.state.galaxy[iq].supernova or \
549         game.state.galaxy[iq].klingons > MAXKLQUAD-1: 
550         return True
551     if avoid:
552         # Avoid quadrants with bases if we want to avoid Enterprise
553         for base in game.state.starbases:
554             if base.location == ship.location:
555                 return True
556     if game.justin and not game.iscate:
557         return True
558     # Super-Commander has scooted, Remove him from current quadrant.
559     if game.state.kscmdr == game.quadrant:
560         game.iscate = False
561         game.isatb = 0
562         game.ientesc = False
563         unschedule("FSCDBAS")
564         if game.condition != "docked":
565             newcnd()
566         ship.sectormove(None)
567     # do the actual move
568     game.state.galaxy[game.state.kscmdr].klingons -= 1
569     game.state.kscmdr = iq
570     game.state.galaxy[game.state.kscmdr].klingons += 1
571     # check for a helpful planet in the destination quadrant
572     for planet in game.state.planets:
573         if planet.location == game.state.kscmdr and planet.crystals=="present":
574             # destroy the planet
575             del planet
576             if communicating():
577                     pause_game(True)
578                 prout("Lt. Uhura-  \"Captain, Starfleet Intelligence reports")
579                 proutn(_("   a planet in "))
580                 proutn(cramlc(quadrant, game.state.kscmdr))
581                 prout(" has been destroyed")
582                 prout("   by the Super-commander.\"")
583             break
584     return False # looks good!
585                         
586 def scom():
587     # move the Super Commander
588     if (idebug):
589         prout("== SCOM")
590
591     # Decide on being active or passive
592     passive = ((NKILLC+NKILLK)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) \
593                or (game.state.date-game.indate) < 3.0)
594     if not game.iscate and passive:
595         # coxmpute move away from Enterprise
596         delta = game.state.kscmdr - game.quadrant
597         if distance(game.state.kscmdr) > 2.0:
598             # circulate in space
599             delta.x = game.state.kscmdr.y-game.quadrant.y
600             delta.y = game.quadrant.x-game.state.kscmdr.x
601     else:
602         if len(game.state.bases):
603             unschedule("FSCMOVE")
604             return
605         sc = game.state.kscmdr
606         # compute distances to starbases
607         game.starbases.sort(lambda x, y: cmp(distance(x, game.quadrant), distance(y, game.quadrant)))
608         # look for nearest base without a commander, no Enterprise, and
609         # without too many Klingons, and not already under attack.
610         nearest = filter(game.starbases,
611                          lambda x: game.state.galaxy[x].supernova \
612                          and game.state.galaxy[x].klingons <= MAXKLQUAD-1)
613         if game.quadrant in nearest:
614             nearest.remove(game.quadrant)
615         if game.battle in nearest:
616             nearest.remove(game.battle)
617         # if there is a commander, and no other base is appropriate,
618         # we will take the one with the commander
619         nocmd = filter(lambda x: x.location not in game.state.kcmdr, nearest)
620         if len(nocmd):
621             nearest = nocmd
622         ibq = nearest[0]
623         if len(nearest) == 0:
624             return      # Nothing suitable -- wait until next time
625         # decide how to move toward base
626         delta = ibq - game.state.kscmdr
627     # maximum movement is 1 quadrant in either or both axis
628     delta = delta.sgn()
629     # try moving in both x and y directions
630     iq = game.state.kscmdr + delta
631     if movescom(iq, passive):
632         # failed -- try some other maneuvers
633         if delta.x==0 or delta.y==0:
634             # attempt angle move
635             if delta.x != 0:
636                 iq.y = game.state.kscmdr.y + 1
637                 if movescom(iq, passive):
638                     iq.y = game.state.kscmdr.y - 1
639                     movescom(iq, passive)
640             else:
641                 iq.x = game.state.kscmdr.x + 1
642                 if movescom(iq, passive):
643                     iq.x = game.state.kscmdr.x - 1
644                     movescom(iq, passive)
645         else:
646             # try moving just in x or y
647             iq.y = game.state.kscmdr.y
648             if movescom(iq, passive):
649                 iq.y = game.state.kscmdr.y + delta.y
650                 iq.x = game.state.kscmdr.x
651                 movescom(iq, passive)
652     # check for a base
653     if len(game.state.bases) == 0:
654         unschedule("FSCMOVE")
655     else:
656         for ibq in game.bases:
657             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
658                 # attack the base
659                 if passive:
660                     return # no, don't attack base!
661                 game.iseenit = false
662                 game.isatb = 1
663                 schedule("FSCDBAS", 1.0 +2.0*Rand())
664                 if is_scheduled("FCDBAS"):
665                     postpone("FSCDBAS", scheduled("FCDBAS")-game.state.date)
666                 if not communicating():
667                     return # no warning
668                 game.iseenit = True
669                 pause_game(true)
670                 proutn(_("Lt. Uhura-  \"Captain, the starbase in "))
671                 proutn(cramlc(quadrant, game.state.kscmdr))
672                 skip(1)
673                 prout("   reports that it is under attack from the Klingon Super-commander.")
674                 proutn("   It can survive until stardate %d.\"",
675                        int(scheduled(FSCDBAS)))
676                 if not game.resting:
677                     return
678                 prout("Mr. Spock-  \"Captain, shall we cancel the rest period?\"")
679                 if ja() == false:
680                     return
681                 game.resting = False
682                 game.optime = 0.0 # actually finished
683                 return
684     # Check for intelligence report
685     if (Rand() > 0.2 or not communicating() or
686         not game.state.galaxy[game.state.kscmdr].charted):
687         return
688     pause_game(true)
689     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
690     proutn(_("   the Super-commander is in "))
691     proutn(cramlc(quadrant, game.state.kscmdr))
692     prout(".\"")
693     return
694
695 def movetho(void):
696     "Move the Tholian (an instance of ship type pointed at by game.tholian)." 
697     if not game.tholian or game.justin:
698         return
699     next = coord()
700     if game.tholian.location.x == 1 and game.tholian.location.y == 1:
701         next.x = 1
702         next.y = QUADSIZE
703     elif game.tholian.location.x == 1 and game.tholian.location.y == QUADSIZE:
704         next.x = next.y = QUADSIZE
705     elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == QUADSIZE:
706         next.x = QUADSIZE
707         next.y = 1
708     elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == 1:
709         next.x = next.y = 1
710     else:
711         # something is wrong!
712         game.tholian = None
713         return
714     # Do nothing if we are blocked
715     if not (isinstance(game.quad[next], space) or isinstance(game.quad[next], web)):
716         return
717     # Now place some web
718     im = (next - game.tholian.location).sgn()
719     if game.tholian.x != next.x:
720         # move in x axis
721         while game.tholian.location.x != next.x:
722             game.tholian.location.x += im.x
723             if isinstance(game.quad[game.tholian.location], space):
724                 game.quad[game.tholian.location] = web()
725     elif game.tholian.y != next.y:
726         # move in y axis
727         while game.tholian.y != next.y:
728             game.tholian.y += im.y
729             if isinstance(game.quad[game.tholian.location], space):
730                 game.quad[game.tholian.location] = web()
731     # web is done, move ship
732     game.tholian.movesector(next)
733     # check to see if all holes plugged
734     for i in range(1, QUADSIZE+1):
735         if (not isinstance(game.quad[(1,i)],web)) and game.quad[(1,i)]!=game.tholian:
736             return
737         if (not isinstance(game.quad[(QUADSIZE,i)],web)) and game.quad[(QUADSIZE,i)]!=game.tholian:
738             return
739         if (not isinstance(game.quad[(i,1)],web)) and game.quad[(i,1)]!=game.tholian:
740             return
741         if (not isinstance(game.quad[(i.QUADSIZE)],web)) and game.quad[(i,QUADSIZE)]!=game.tholian:
742             return
743     # All plugged up -- Tholian splits
744     game.quad[game.tholian] = web()
745     ship.movesector(None)
746     crmena(True, IHT, sector, game.tholian)
747     prout(" completes web.")
748     game.tholian = None
749     return
750