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