Imprive documentation, fix a typo, add the hacking guide.
[super-star-trek.git] / src / sst.py
1 #!/usr/bin/env python
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 ion how to modify (and how not to modify!) this code.
13 """
14 import os, sys, math, curses, time, readline, cPickle, random, copy, gettext
15
16 SSTDOC          = "/usr/share/doc/sst/sst.doc"
17 DOC_NAME        = "sst.doc"
18
19 def _(str): return gettext.gettext(str)
20
21 PHASEFAC        = 2.0
22 GALSIZE         = 8
23 NINHAB          = (GALSIZE * GALSIZE / 2)
24 MAXUNINHAB      = 10
25 PLNETMAX        = (NINHAB + MAXUNINHAB)
26 QUADSIZE        = 10
27 BASEMIN         = 2
28 BASEMAX         = (GALSIZE * GALSIZE / 12)
29 MAXKLGAME       = 127
30 MAXKLQUAD       = 9
31 FULLCREW        = 428   # BSD Trek was 387, that's wrong 
32 FOREVER         = 1e30
33 MAXBURST        = 3
34 MINCMDR         = 10
35
36 # How to represent features
37 IHR = 'R',
38 IHK = 'K',
39 IHC = 'C',
40 IHS = 'S',
41 IHSTAR = '*',
42 IHP = 'P',
43 IHW = '@',
44 IHB = 'B',
45 IHBLANK = ' ',
46 IHDOT = '.',
47 IHQUEST = '?',
48 IHE = 'E',
49 IHF = 'F',
50 IHT = 'T',
51 IHWEB = '#',
52 IHMATER0 = '-',
53 IHMATER1 = 'o',
54 IHMATER2 = '0'
55
56 class TrekError:
57     pass
58
59 class coord:
60     def __init__(self, x=None, y=None):
61         self.i = x
62         self.j = y
63     def valid_quadrant(self):
64         return self.i>=0 and self.i<GALSIZE and self.j>=0 and self.j<GALSIZE
65     def valid_sector(self):
66         return self.i>=0 and self.i<QUADSIZE and self.j>=0 and self.j<QUADSIZE
67     def invalidate(self):
68         self.i = self.j = None
69     def is_valid(self):
70         return self.i != None and self.j != None
71     def __eq__(self, other):
72         return other != None and self.i == other.i and self.j == other.j
73     def __ne__(self, other):
74         return other == None or self.i != other.i or self.j != other.j
75     def __add__(self, other):
76         return coord(self.i+other.i, self.j+other.j)
77     def __sub__(self, other):
78         return coord(self.i-other.i, self.j-other.j)
79     def __mul__(self, other):
80         return coord(self.i*other, self.j*other)
81     def __rmul__(self, other):
82         return coord(self.i*other, self.j*other)
83     def __div__(self, other):
84         return coord(self.i/other, self.j/other)
85     def __mod__(self, other):
86         return coord(self.i % other, self.j % other)
87     def __rdiv__(self, other):
88         return coord(self.i/other, self.j/other)
89     def roundtogrid(self):
90         return coord(int(round(self.i)), int(round(self.j)))
91     def distance(self, other=None):
92         if not other: other = coord(0, 0)
93         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
94     def bearing(self):
95         return 1.90985*math.atan2(self.j, self.i)
96     def sgn(self):
97         s = coord()
98         if self.i == 0:
99             s.i = 0
100         else:
101             s.i = self.i / abs(self.i)
102         if self.j == 0:
103             s.j = 0
104         else:
105             s.j = self.j / abs(self.j)
106         return s
107     def quadrant(self):
108         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
109         return self.roundtogrid() / QUADSIZE
110     def sector(self):
111         return self.roundtogrid() % QUADSIZE
112     def scatter(self):
113         s = coord()
114         s.i = self.i + randrange(-1, 2)
115         s.j = self.j + randrange(-1, 2)
116         return s
117     def __str__(self):
118         if self.i == None or self.j == None:
119             return "Nowhere"
120         return "%s - %s" % (self.i+1, self.j+1)
121     __repr__ = __str__
122
123 class planet:
124     def __init__(self):
125         self.name = None        # string-valued if inhabited
126         self.quadrant = coord() # quadrant located
127         self.pclass = None      # could be ""M", "N", "O", or "destroyed"
128         self.crystals = "absent"# could be "mined", "present", "absent"
129         self.known = "unknown"  # could be "unknown", "known", "shuttle_down"
130         self.inhabited = False  # is it inhabites?
131     def __str__(self):
132         return self.name
133
134 class quadrant:
135     def __init__(self):
136         self.stars = 0
137         self.planet = None
138         self.starbase = False
139         self.klingons = 0
140         self.romulans = 0
141         self.supernova = False
142         self.charted = False
143         self.status = "secure"  # Could be "secure", "distressed", "enslaved"
144
145 class page:
146     def __init__(self):
147         self.stars = None
148         self.starbase = None
149         self.klingons = None
150
151 def fill2d(size, fillfun):
152     "Fill an empty list in 2D."
153     lst = []
154     for i in range(size):
155         lst.append([]) 
156         for j in range(size):
157             lst[i].append(fillfun(i, j))
158     return lst
159
160 class snapshot:
161     def __init__(self):
162         self.snap = False       # snapshot taken
163         self.crew = 0           # crew complement
164         self.remkl = 0          # remaining klingons
165         self.nscrem = 0         # remaining super commanders
166         self.starkl = 0         # destroyed stars
167         self.basekl = 0         # destroyed bases
168         self.nromrem = 0        # Romulans remaining
169         self.nplankl = 0        # destroyed uninhabited planets
170         self.nworldkl = 0       # destroyed inhabited planets
171         self.planets = []       # Planet information
172         self.date = 0.0         # stardate
173         self.remres = 0         # remaining resources
174         self.remtime = 0        # remaining time
175         self.baseq = []         # Base quadrant coordinates
176         self.kcmdr = []         # Commander quadrant coordinates
177         self.kscmdr = coord()   # Supercommander quadrant coordinates
178         # the galaxy
179         self.galaxy = fill2d(GALSIZE, lambda i, j: quadrant())
180         # the starchart
181         self.chart = fill2d(GALSIZE, lambda i, j: page())
182
183 class event:
184     def __init__(self):
185         self.date = None        # A real number
186         self.quadrant = None    # A coord structure
187
188 # game options 
189 OPTION_ALL      = 0xffffffff
190 OPTION_TTY      = 0x00000001    # old interface 
191 OPTION_CURSES   = 0x00000002    # new interface 
192 OPTION_IOMODES  = 0x00000003    # cover both interfaces 
193 OPTION_PLANETS  = 0x00000004    # planets and mining 
194 OPTION_THOLIAN  = 0x00000008    # Tholians and their webs (UT 1979 version)
195 OPTION_THINGY   = 0x00000010    # Space Thingy can shoot back (Stas, 2005)
196 OPTION_PROBE    = 0x00000020    # deep-space probes (DECUS version, 1980)
197 OPTION_SHOWME   = 0x00000040    # bracket Enterprise in chart 
198 OPTION_RAMMING  = 0x00000080    # enemies may ram Enterprise (Almy)
199 OPTION_MVBADDY  = 0x00000100    # more enemies can move (Almy)
200 OPTION_BLKHOLE  = 0x00000200    # black hole may timewarp you (Stas, 2005) 
201 OPTION_BASE     = 0x00000400    # bases have good shields (Stas, 2005)
202 OPTION_WORLDS   = 0x00000800    # logic for inhabited worlds (ESR, 2006)
203 OPTION_AUTOSCAN = 0x00001000    # automatic LRSCAN before CHART (ESR, 2006)
204 OPTION_PLAIN    = 0x01000000    # user chose plain game 
205 OPTION_ALMY     = 0x02000000    # user chose Almy variant 
206
207 # Define devices 
208 DSRSENS = 0
209 DLRSENS = 1
210 DPHASER = 2
211 DPHOTON = 3
212 DLIFSUP = 4
213 DWARPEN = 5
214 DIMPULS = 6
215 DSHIELD = 7
216 DRADIO  = 0
217 DSHUTTL = 9
218 DCOMPTR = 10
219 DNAVSYS = 11
220 DTRANSP = 12
221 DSHCTRL = 13
222 DDRAY   = 14
223 DDSP    = 15
224 NDEVICES= 16    # Number of devices
225
226 SKILL_NONE      = 0
227 SKILL_NOVICE    = 1
228 SKILL_FAIR      = 2
229 SKILL_GOOD      = 3
230 SKILL_EXPERT    = 4
231 SKILL_EMERITUS  = 5
232
233 def damaged(dev):       return (game.damage[dev] != 0.0)
234 def communicating():    return not damaged(DRADIO) or game.condition=="docked"
235
236 # Define future events 
237 FSPY    = 0     # Spy event happens always (no future[] entry)
238                 # can cause SC to tractor beam Enterprise
239 FSNOVA  = 1     # Supernova
240 FTBEAM  = 2     # Commander tractor beams Enterprise
241 FSNAP   = 3     # Snapshot for time warp
242 FBATTAK = 4     # Commander attacks base
243 FCDBAS  = 5     # Commander destroys base
244 FSCMOVE = 6     # Supercommander moves (might attack base)
245 FSCDBAS = 7     # Supercommander destroys base
246 FDSPROB = 8     # Move deep space probe
247 FDISTR  = 9     # Emit distress call from an inhabited world 
248 FENSLV  = 10    # Inhabited word is enslaved */
249 FREPRO  = 11    # Klingons build a ship in an enslaved system
250 NEVENTS = 12
251
252 #
253 # abstract out the event handling -- underlying data structures will change
254 # when we implement stateful events
255
256 def findevent(evtype):  return game.future[evtype]
257
258 class enemy:
259     def __init__(self, type=None, loc=None, power=None):
260         self.type = type
261         self.kloc = coord()
262         if loc:
263             self.move(loc)
264         self.kpower = power     # enemy energy level
265         game.enemies.append(self)
266     def move(self, loc):
267         motion = (loc != self.kloc)
268         if self.kloc.i is not None and self.kloc.j is not None:
269             if motion:
270                 if self.type == IHT:
271                     game.quad[self.kloc.i][self.kloc.j] = IHWEB
272                 else:
273                     game.quad[self.kloc.i][self.kloc.j] = IHDOT
274         if loc:
275             self.kloc = copy.copy(loc)
276             game.quad[self.kloc.i][self.kloc.j] = self.type
277             self.kdist = self.kavgd = (game.sector - loc).distance()
278         else:
279             self.kloc = coord()
280             self.kdist = self.kavgd = None
281             game.enemies.remove(self)
282         return motion
283     def __repr__(self):
284         return "<%s,%s.%f>" % (self.type, self.kloc, self.kpower)       # For debugging
285
286 class gamestate:
287     def __init__(self):
288         self.options = None     # Game options
289         self.state = snapshot() # A snapshot structure
290         self.snapsht = snapshot()       # Last snapshot taken for time-travel purposes
291         self.quad = None        # contents of our quadrant
292         self.damage = [0.0] * NDEVICES  # damage encountered
293         self.future = []                # future events
294         for i in range(NEVENTS):
295             self.future.append(event())
296         self.passwd  = None;            # Self Destruct password
297         self.enemies = []
298         self.quadrant = None    # where we are in the large
299         self.sector = None      # where we are in the small
300         self.tholian = None     # Tholian enemy object
301         self.base = None        # position of base in current quadrant
302         self.battle = None      # base coordinates being attacked
303         self.plnet = None       # location of planet in quadrant
304         self.gamewon = False    # Finished!
305         self.ididit = False     # action taken -- allows enemy to attack
306         self.alive = False      # we are alive (not killed)
307         self.justin = False     # just entered quadrant
308         self.shldup = False     # shields are up
309         self.shldchg = False    # shield is changing (affects efficiency)
310         self.iscate = False     # super commander is here
311         self.ientesc = False    # attempted escape from supercommander
312         self.resting = False    # rest time
313         self.icraft = False     # Kirk in Galileo
314         self.landed = False     # party on planet (true), on ship (false)
315         self.alldone = False    # game is now finished
316         self.neutz = False      # Romulan Neutral Zone
317         self.isarmed = False    # probe is armed
318         self.inorbit = False    # orbiting a planet
319         self.imine = False      # mining
320         self.icrystl = False    # dilithium crystals aboard
321         self.iseenit = False    # seen base attack report
322         self.thawed = False     # thawed game
323         self.condition = None   # "green", "yellow", "red", "docked", "dead"
324         self.iscraft = None     # "onship", "offship", "removed"
325         self.skill = None       # Player skill level
326         self.inkling = 0        # initial number of klingons
327         self.inbase = 0         # initial number of bases
328         self.incom = 0          # initial number of commanders
329         self.inscom = 0         # initial number of commanders
330         self.inrom = 0          # initial number of commanders
331         self.instar = 0         # initial stars
332         self.intorps = 0        # initial/max torpedoes
333         self.torps = 0          # number of torpedoes
334         self.ship = 0           # ship type -- 'E' is Enterprise
335         self.abandoned = 0      # count of crew abandoned in space
336         self.length = 0         # length of game
337         self.klhere = 0         # klingons here
338         self.casual = 0         # causalties
339         self.nhelp = 0          # calls for help
340         self.nkinks = 0         # count of energy-barrier crossings
341         self.iplnet = None      # planet # in quadrant
342         self.inplan = 0         # initial planets
343         self.irhere = 0         # Romulans in quadrant
344         self.isatb = 0          # =1 if super commander is attacking base
345         self.tourn = None       # tournament number
346         self.nprobes = 0        # number of probes available
347         self.inresor = 0.0      # initial resources
348         self.intime = 0.0       # initial time
349         self.inenrg = 0.0       # initial/max energy
350         self.inshld = 0.0       # initial/max shield
351         self.inlsr = 0.0        # initial life support resources
352         self.indate = 0.0       # initial date
353         self.energy = 0.0       # energy level
354         self.shield = 0.0       # shield level
355         self.warpfac = 0.0      # warp speed
356         self.wfacsq = 0.0       # squared warp factor
357         self.lsupres = 0.0      # life support reserves
358         self.optime = 0.0       # time taken by current operation
359         self.docfac = 0.0       # repair factor when docking (constant?)
360         self.damfac = 0.0       # damage factor
361         self.lastchart = 0.0    # time star chart was last updated
362         self.cryprob = 0.0      # probability that crystal will work
363         self.probe = None       # object holding probe course info
364         self.height = 0.0       # height of orbit around planet
365     def recompute(self):
366         # Stas thinks this should be (C expression): 
367         # game.state.remkl + len(game.state.kcmdr) > 0 ?
368         #       game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
369         # He says the existing expression is prone to divide-by-zero errors
370         # after killing the last klingon when score is shown -- perhaps also
371         # if the only remaining klingon is SCOM.
372         game.state.remtime = game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr))
373
374 IHR = 'R'
375 IHK = 'K'
376 IHC = 'C'
377 IHS = 'S'
378 IHSTAR = '*'
379 IHP = 'P'
380 IHW = '@'
381 IHB = 'B'
382 IHBLANK = ' '
383 IHDOT = '.'
384 IHQUEST = '?'
385 IHE = 'E'
386 IHF = 'F'
387 IHT = 'T'
388 IHWEB = '#'
389 IHMATER0 = '-'
390 IHMATER1 = 'o'
391 IHMATER2 = '0'
392
393 FWON = 0
394 FDEPLETE = 1
395 FLIFESUP = 2
396 FNRG = 3
397 FBATTLE = 4
398 FNEG3 = 5
399 FNOVA = 6
400 FSNOVAED = 7
401 FABANDN = 8
402 FDILITHIUM = 9
403 FMATERIALIZE = 10
404 FPHASER = 11
405 FLOST = 12
406 FMINING = 13
407 FDPLANET = 14
408 FPNOVA = 15
409 FSSC = 16
410 FSTRACTOR = 17
411 FDRAY = 18
412 FTRIBBLE = 19
413 FHOLE = 20
414 FCREW = 21
415
416 def withprob(p):
417     v = random.random()
418     #logfp.write("# withprob(%s) -> %f (%s) at %s\n" % (p, v, v<p, traceback.extract_stack()[-2][1:]))
419     return v < p
420
421 def randrange(*args):
422     v = random.randrange(*args)
423     #logfp.write("# randrange%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
424     return v
425
426 def randreal(*args):
427     v = random.random()
428     if len(args) == 1:
429         v *= args[0]            # returns from [0, args[0])
430     elif len(args) == 2:
431         v = args[0] + v*(args[1]-args[0])       # returns from [args[0], args[1])
432     #logfp.write("# randreal%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
433     return v
434
435 # Code from ai.c begins here
436
437 def welcoming(iq):
438     "Would this quadrant welcome another Klingon?"
439     return iq.valid_quadrant() and \
440         not game.state.galaxy[iq.i][iq.j].supernova and \
441         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
442
443 def tryexit(enemy, look, irun):
444     "A bad guy attempts to bug out."
445     iq = coord()
446     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
447     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
448     if not welcoming(iq):
449         return False;
450     if enemy.type == IHR:
451         return False; # Romulans cannot escape! 
452     if not irun:
453         # avoid intruding on another commander's territory 
454         if enemy.type == IHC:
455             if iq in game.state.kcmdr:
456                 return False
457             # refuse to leave if currently attacking starbase 
458             if game.battle == game.quadrant:
459                 return False
460         # don't leave if over 1000 units of energy 
461         if enemy.kpower > 1000.0:
462             return False
463     # emit escape message and move out of quadrant.
464     # we know this if either short or long range sensors are working
465     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
466         game.condition == "docked":
467         prout(crmena(True, enemy.type, "sector", enemy.kloc) + \
468               (_(" escapes to Quadrant %s (and regains strength).") % q))
469     # handle local matters related to escape
470     enemy.move(None)
471     game.klhere -= 1
472     if game.condition != "docked":
473         newcnd()
474     # Handle global matters related to escape 
475     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
476     game.state.galaxy[iq.i][iq.j].klingons += 1
477     if enemy.type==IHS:
478         game.iscate = False
479         game.ientesc = False
480         game.isatb = 0
481         schedule(FSCMOVE, 0.2777)
482         unschedule(FSCDBAS)
483         game.state.kscmdr=iq
484     else:
485         for cmdr in game.state.kcmdr:
486             if cmdr == game.quadrant:
487                 game.state.kcmdr[n] = iq
488                 break
489     return True; # success 
490
491 # The bad-guy movement algorithm:
492
493 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
494 # If both are operating full strength, force is 1000. If both are damaged,
495 # force is -1000. Having shields down subtracts an additional 1000.
496
497 # 2. Enemy has forces equal to the energy of the attacker plus
498 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
499 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
500
501 # Attacker Initial energy levels (nominal):
502 # Klingon   Romulan   Commander   Super-Commander
503 # Novice    400        700        1200        
504 # Fair      425        750        1250
505 # Good      450        800        1300        1750
506 # Expert    475        850        1350        1875
507 # Emeritus  500        900        1400        2000
508 # VARIANCE   75        200         200         200
509
510 # Enemy vessels only move prior to their attack. In Novice - Good games
511 # only commanders move. In Expert games, all enemy vessels move if there
512 # is a commander present. In Emeritus games all enemy vessels move.
513
514 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
515 # forces are 1000 greater than Enterprise.
516
517 # Agressive action on average cuts the distance between the ship and
518 # the enemy to 1/4 the original.
519
520 # 4.  At lower energy advantage, movement units are proportional to the
521 # advantage with a 650 advantage being to hold ground, 800 to move forward
522 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
523
524 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
525 # retreat, especially at high skill levels.
526
527 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
528
529 def movebaddy(enemy):
530     "Tactical movement for the bad guys."
531     next = coord(); look = coord()
532     irun = False
533     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant) 
534     if game.skill >= SKILL_EXPERT:
535         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
536     else:
537         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
538     dist1 = enemy.kdist
539     mdist = int(dist1 + 0.5); # Nearest integer distance 
540     # If SC, check with spy to see if should hi-tail it 
541     if enemy.type==IHS and \
542         (enemy.kpower <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
543         irun = True
544         motion = -QUADSIZE
545     else:
546         # decide whether to advance, retreat, or hold position 
547         forces = enemy.kpower+100.0*len(game.enemies)+400*(nbaddys-1)
548         if not game.shldup:
549             forces += 1000; # Good for enemy if shield is down! 
550         if not damaged(DPHASER) or not damaged(DPHOTON):
551             if damaged(DPHASER): # phasers damaged 
552                 forces += 300.0
553             else:
554                 forces -= 0.2*(game.energy - 2500.0)
555             if damaged(DPHOTON): # photon torpedoes damaged 
556                 forces += 300.0
557             else:
558                 forces -= 50.0*game.torps
559         else:
560             # phasers and photon tubes both out! 
561             forces += 1000.0
562         motion = 0
563         if forces <= 1000.0 and game.condition != "docked": # Typical situation 
564             motion = ((forces + randreal(200))/150.0) - 5.0
565         else:
566             if forces > 1000.0: # Very strong -- move in for kill 
567                 motion = (1.0 - randreal())**2 * dist1 + 1.0
568             if game.condition=="docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
569                 motion -= game.skill*(2.0-randreal()**2)
570         if idebug:
571             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
572         # don't move if no motion 
573         if motion==0:
574             return
575         # Limit motion according to skill 
576         if abs(motion) > game.skill:
577             if motion < 0:
578                 motion = -game.skill
579             else:
580                 motion = game.skill
581     # calculate preferred number of steps 
582     nsteps = abs(int(motion))
583     if motion > 0 and nsteps > mdist:
584         nsteps = mdist; # don't overshoot 
585     if nsteps > QUADSIZE:
586         nsteps = QUADSIZE; # This shouldn't be necessary 
587     if nsteps < 1:
588         nsteps = 1; # This shouldn't be necessary 
589     if idebug:
590         proutn("NSTEPS = %d:" % nsteps)
591     # Compute preferred values of delta X and Y 
592     m = game.sector - enemy.kloc
593     if 2.0 * abs(m.i) < abs(m.j):
594         m.i = 0
595     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.kloc.i):
596         m.j = 0
597     m = (motion * m).sgn()
598     next = enemy.kloc
599     # main move loop 
600     for ll in range(nsteps):
601         if idebug:
602             proutn(" %d" % (ll+1))
603         # Check if preferred position available 
604         look = next + m
605         if m.i < 0:
606             krawli = 1
607         else:
608             krawli = -1
609         if m.j < 0:
610             krawlj = 1
611         else:
612             krawlj = -1
613         success = False
614         attempts = 0; # Settle mysterious hang problem 
615         while attempts < 20 and not success:
616             attempts += 1
617             if look.i < 0 or look.i >= QUADSIZE:
618                 if motion < 0 and tryexit(enemy, look, irun):
619                     return
620                 if krawli == m.i or m.j == 0:
621                     break
622                 look.i = next.i + krawli
623                 krawli = -krawli
624             elif look.j < 0 or look.j >= QUADSIZE:
625                 if motion < 0 and tryexit(enemy, look, irun):
626                     return
627                 if krawlj == m.j or m.i == 0:
628                     break
629                 look.j = next.j + krawlj
630                 krawlj = -krawlj
631             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != IHDOT:
632                 # See if enemy should ram ship 
633                 if game.quad[look.i][look.j] == game.ship and \
634                     (enemy.type == IHC or enemy.type == IHS):
635                     collision(rammed=True, enemy=enemy)
636                     return
637                 if krawli != m.i and m.j != 0:
638                     look.i = next.i + krawli
639                     krawli = -krawli
640                 elif krawlj != m.j and m.i != 0:
641                     look.j = next.j + krawlj
642                     krawlj = -krawlj
643                 else:
644                     break; # we have failed 
645             else:
646                 success = True
647         if success:
648             next = look
649             if idebug:
650                 proutn(`next`)
651         else:
652             break; # done early 
653     if idebug:
654         skip(1)
655     if enemy.move(next):
656         if not damaged(DSRSENS) or game.condition == "docked":
657             proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.kloc))
658             if enemy.kdist < dist1:
659                 proutn(_(" advances to "))
660             else:
661                 proutn(_(" retreats to "))
662             prout("Sector %s." % next)
663
664 def moveklings():
665     "Sequence Klingon tactical movement."
666     if idebug:
667         prout("== MOVCOM")
668     # Figure out which Klingon is the commander (or Supercommander)
669     # and do move
670     if game.quadrant in game.state.kcmdr:
671         for enemy in game.enemies:
672             if enemy.type == IHC:
673                 movebaddy(enemy)
674     if game.state.kscmdr==game.quadrant:
675         for enemy in game.enemies:
676             if enemy.type == IHS:
677                 movebaddy(enemy)
678                 break
679     # If skill level is high, move other Klingons and Romulans too!
680     # Move these last so they can base their actions on what the
681     # commander(s) do.
682     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
683         for enemy in game.enemies:
684             if enemy.type in (IHK, IHR):
685                 movebaddy(enemy)
686     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
687
688 def movescom(iq, avoid):
689     "Commander movement helper." 
690     # Avoid quadrants with bases if we want to avoid Enterprise 
691     if not welcoming(iq) or (avoid and iq in game.state.baseq):
692         return False
693     if game.justin and not game.iscate:
694         return False
695     # do the move 
696     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
697     game.state.kscmdr = iq
698     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
699     if game.state.kscmdr==game.quadrant:
700         # SC has scooted, Remove him from current quadrant 
701         game.iscate=False
702         game.isatb=0
703         game.ientesc = False
704         unschedule(FSCDBAS)
705         for enemy in game.enemies:
706             if enemy.type == IHS:
707                 break
708         enemy.move(None)
709         game.klhere -= 1
710         if game.condition != "docked":
711             newcnd()
712         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
713     # check for a helpful planet 
714     for i in range(game.inplan):
715         if game.state.planets[i].quadrant == game.state.kscmdr and \
716             game.state.planets[i].crystals == "present":
717             # destroy the planet 
718             game.state.planets[i].pclass = "destroyed"
719             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
720             if communicating():
721                 announce()
722                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
723                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
724                 prout(_("   by the Super-commander.\""))
725             break
726     return True; # looks good! 
727                         
728 def supercommander():
729     "Move the Super Commander." 
730     iq = coord(); sc = coord(); ibq = coord(); idelta = coord()
731     basetbl = []
732     if idebug:
733         prout("== SUPERCOMMANDER")
734     # Decide on being active or passive 
735     avoid = ((game.incom - len(game.state.kcmdr) + game.inkling - game.state.remkl)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
736             (game.state.date-game.indate) < 3.0)
737     if not game.iscate and avoid:
738         # compute move away from Enterprise 
739         idelta = game.state.kscmdr-game.quadrant
740         if idelta.distance() > 2.0:
741             # circulate in space 
742             idelta.i = game.state.kscmdr.j-game.quadrant.j
743             idelta.j = game.quadrant.i-game.state.kscmdr.i
744     else:
745         # compute distances to starbases 
746         if not game.state.baseq:
747             # nothing left to do 
748             unschedule(FSCMOVE)
749             return
750         sc = game.state.kscmdr
751         for base in game.state.baseq:
752             basetbl.append((i, (base - sc).distance()))
753         if game.state.baseq > 1:
754             basetbl.sort(lambda x, y: cmp(x[1]. y[1]))
755         # look for nearest base without a commander, no Enterprise, and
756         # without too many Klingons, and not already under attack. 
757         ifindit = iwhichb = 0
758         for (i2, base) in enumerate(game.state.baseq):
759             i = basetbl[i2][0]; # bug in original had it not finding nearest
760             if base==game.quadrant or base==game.battle or not welcoming(base):
761                 continue
762             # if there is a commander, and no other base is appropriate,
763             # we will take the one with the commander
764             for cmdr in game.state.kcmdr:
765                 if base == cmdr and ifindit != 2:
766                     ifindit = 2
767                     iwhichb = i
768                     break
769             else:       # no commander -- use this one 
770                 ifindit = 1
771                 iwhichb = i
772                 break
773         if ifindit==0:
774             return # Nothing suitable -- wait until next time
775         ibq = game.state.baseq[iwhichb]
776         # decide how to move toward base 
777         idelta = ibq - game.state.kscmdr
778     # Maximum movement is 1 quadrant in either or both axes 
779     idelta = idelta.sgn()
780     # try moving in both x and y directions
781     # there was what looked like a bug in the Almy C code here,
782     # but it might be this translation is just wrong.
783     iq = game.state.kscmdr + idelta
784     if not movescom(iq, avoid):
785         # failed -- try some other maneuvers 
786         if idelta.i==0 or idelta.j==0:
787             # attempt angle move 
788             if idelta.i != 0:
789                 iq.j = game.state.kscmdr.j + 1
790                 if not movescom(iq, avoid):
791                     iq.j = game.state.kscmdr.j - 1
792                     movescom(iq, avoid)
793             elif idelta.j != 0:
794                 iq.i = game.state.kscmdr.i + 1
795                 if not movescom(iq, avoid):
796                     iq.i = game.state.kscmdr.i - 1
797                     movescom(iq, avoid)
798         else:
799             # try moving just in x or y 
800             iq.j = game.state.kscmdr.j
801             if not movescom(iq, avoid):
802                 iq.j = game.state.kscmdr.j + idelta.j
803                 iq.i = game.state.kscmdr.i
804                 movescom(iq, avoid)
805     # check for a base 
806     if len(game.state.baseq) == 0:
807         unschedule(FSCMOVE)
808     else:
809         for ibq in game.state.baseq:
810             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
811                 # attack the base 
812                 if avoid:
813                     return # no, don't attack base! 
814                 game.iseenit = False
815                 game.isatb = 1
816                 schedule(FSCDBAS, randreal(1.0, 3.0))
817                 if is_scheduled(FCDBAS):
818                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
819                 if not communicating():
820                     return # no warning 
821                 game.iseenit = True
822                 announce()
823                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
824                       % game.state.kscmdr)
825                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
826                 proutn(_("   It can survive until stardate %d.\"") \
827                        % int(scheduled(FSCDBAS)))
828                 if not game.resting:
829                     return
830                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
831                 if ja() == False:
832                     return
833                 game.resting = False
834                 game.optime = 0.0; # actually finished 
835                 return
836     # Check for intelligence report 
837     if not idebug and \
838         (withprob(0.8) or \
839          (not communicating()) or \
840          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
841         return
842     announce()
843     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
844     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
845     return
846
847 def movetholian():
848     "Move the Tholian."
849     if not game.tholian or game.justin:
850         return
851     id = coord()
852     if game.tholian.kloc.i == 0 and game.tholian.kloc.j == 0:
853         id.i = 0; id.j = QUADSIZE-1
854     elif game.tholian.kloc.i == 0 and game.tholian.kloc.j == QUADSIZE-1:
855         id.i = QUADSIZE-1; id.j = QUADSIZE-1
856     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == QUADSIZE-1:
857         id.i = QUADSIZE-1; id.j = 0
858     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == 0:
859         id.i = 0; id.j = 0
860     else:
861         # something is wrong! 
862         game.tholian.move(None)
863         prout("***Internal error: Tholian in a bad spot.")
864         return
865     # do nothing if we are blocked 
866     if game.quad[id.i][id.j] not in (IHDOT, IHWEB):
867         return
868     here = copy.copy(game.tholian.kloc)
869     delta = (id - game.tholian.kloc).sgn()
870     # move in x axis 
871     while here.i != id.i:
872         here.i += delta.i
873         if game.quad[here.i][here.j]==IHDOT:
874             game.tholian.move(here)
875     # move in y axis 
876     while here.j != id.j:
877         here.j += delta.j
878         if game.quad[here.i][here.j]==IHDOT:
879             game.tholian.move(here)
880     # check to see if all holes plugged 
881     for i in range(QUADSIZE):
882         if game.quad[0][i]!=IHWEB and game.quad[0][i]!=IHT:
883             return
884         if game.quad[QUADSIZE-1][i]!=IHWEB and game.quad[QUADSIZE-1][i]!=IHT:
885             return
886         if game.quad[i][0]!=IHWEB and game.quad[i][0]!=IHT:
887             return
888         if game.quad[i][QUADSIZE-1]!=IHWEB and game.quad[i][QUADSIZE-1]!=IHT:
889             return
890     # All plugged up -- Tholian splits 
891     game.quad[game.tholian.kloc.i][game.tholian.kloc.j]=IHWEB
892     dropin(IHBLANK)
893     prout(crmena(True, IHT, "sector", game.tholian) + _(" completes web."))
894     game.tholian.move(None)
895     return
896
897 # Code from battle.c begins here
898
899 def doshield(shraise):
900     "Change shield status."
901     action = "NONE"
902     game.ididit = False
903     if shraise:
904         action = "SHUP"
905     else:
906         key = scanner.next()
907         if key == "IHALPHA":
908             if scanner.sees("transfer"):
909                 action = "NRG"
910             else:
911                 if damaged(DSHIELD):
912                     prout(_("Shields damaged and down."))
913                     return
914                 if scanner.sees("up"):
915                     action = "SHUP"
916                 elif scanner.sees("down"):
917                     action = "SHDN"
918         if action=="NONE":
919             proutn(_("Do you wish to change shield energy? "))
920             if ja() == True:
921                 proutn(_("Energy to transfer to shields- "))
922                 action = "NRG"
923             elif damaged(DSHIELD):
924                 prout(_("Shields damaged and down."))
925                 return
926             elif game.shldup:
927                 proutn(_("Shields are up. Do you want them down? "))
928                 if ja() == True:
929                     action = "SHDN"
930                 else:
931                     scanner.chew()
932                     return
933             else:
934                 proutn(_("Shields are down. Do you want them up? "))
935                 if ja() == True:
936                     action = "SHUP"
937                 else:
938                     scanner.chew()
939                     return    
940     if action == "SHUP": # raise shields 
941         if game.shldup:
942             prout(_("Shields already up."))
943             return
944         game.shldup = True
945         game.shldchg = True
946         if game.condition != "docked":
947             game.energy -= 50.0
948         prout(_("Shields raised."))
949         if game.energy <= 0:
950             skip(1)
951             prout(_("Shields raising uses up last of energy."))
952             finish(FNRG)
953             return
954         game.ididit=True
955         return
956     elif action == "SHDN":
957         if not game.shldup:
958             prout(_("Shields already down."))
959             return
960         game.shldup=False
961         game.shldchg=True
962         prout(_("Shields lowered."))
963         game.ididit = True
964         return
965     elif action == "NRG":
966         while scanner.next() != "IHREAL":
967             scanner.chew()
968             proutn(_("Energy to transfer to shields- "))
969         scanner.chew()
970         if scanner.real == 0:
971             return
972         if scanner.real > game.energy:
973             prout(_("Insufficient ship energy."))
974             return
975         game.ididit = True
976         if game.shield+scanner.real >= game.inshld:
977             prout(_("Shield energy maximized."))
978             if game.shield+scanner.real > game.inshld:
979                 prout(_("Excess energy requested returned to ship energy"))
980             game.energy -= game.inshld-game.shield
981             game.shield = game.inshld
982             return
983         if scanner.real < 0.0 and game.energy-scanner.real > game.inenrg:
984             # Prevent shield drain loophole 
985             skip(1)
986             prout(_("Engineering to bridge--"))
987             prout(_("  Scott here. Power circuit problem, Captain."))
988             prout(_("  I can't drain the shields."))
989             game.ididit = False
990             return
991         if game.shield+scanner.real < 0:
992             prout(_("All shield energy transferred to ship."))
993             game.energy += game.shield
994             game.shield = 0.0
995             return
996         proutn(_("Scotty- \""))
997         if scanner.real > 0:
998             prout(_("Transferring energy to shields.\""))
999         else:
1000             prout(_("Draining energy from shields.\""))
1001         game.shield += scanner.real
1002         game.energy -= scanner.real
1003         return
1004
1005 def randdevice():
1006     "Choose a device to damage, at random."
1007     # Quoth Eric Allman in the code of BSD-Trek:
1008     # "Under certain conditions you can get a critical hit.  This
1009     # sort of hit damages devices.  The probability that a given
1010     # device is damaged depends on the device.  Well protected
1011     # devices (such as the computer, which is in the core of the
1012     # ship and has considerable redundancy) almost never get
1013     # damaged, whereas devices which are exposed (such as the
1014     # warp engines) or which are particularly delicate (such as
1015     # the transporter) have a much higher probability of being
1016     # damaged."
1017     # 
1018     # This is one place where OPTION_PLAIN does not restore the
1019     # original behavior, which was equiprobable damage across
1020     # all devices.  If we wanted that, we'd return randrange(NDEVICES)
1021     # and have done with it.  Also, in the original game, DNAVYS
1022     # and DCOMPTR were the same device. 
1023     # 
1024     # Instead, we use a table of weights similar to the one from BSD Trek.
1025     # BSD doesn't have the shuttle, shield controller, death ray, or probes. 
1026     # We don't have a cloaking device.  The shuttle got the allocation
1027     # for the cloaking device, then we shaved a half-percent off
1028     # everything to have some weight to give DSHCTRL/DDRAY/DDSP.
1029     weights = (
1030         105,    # DSRSENS: short range scanners 10.5% 
1031         105,    # DLRSENS: long range scanners          10.5% 
1032         120,    # DPHASER: phasers                      12.0% 
1033         120,    # DPHOTON: photon torpedoes             12.0% 
1034         25,     # DLIFSUP: life support          2.5% 
1035         65,     # DWARPEN: warp drive                    6.5% 
1036         70,     # DIMPULS: impulse engines               6.5% 
1037         145,    # DSHIELD: deflector shields            14.5% 
1038         30,     # DRADIO:  subspace radio                3.0% 
1039         45,     # DSHUTTL: shuttle                       4.5% 
1040         15,     # DCOMPTR: computer                      1.5% 
1041         20,     # NAVCOMP: navigation system             2.0% 
1042         75,     # DTRANSP: transporter                   7.5% 
1043         20,     # DSHCTRL: high-speed shield controller 2.0% 
1044         10,     # DDRAY: death ray                       1.0% 
1045         30,     # DDSP: deep-space probes                3.0% 
1046     )
1047     idx = randrange(1000)       # weights must sum to 1000 
1048     sum = 0
1049     for (i, w) in enumerate(weights):
1050         sum += w
1051         if idx < sum:
1052             return i
1053     return None;        # we should never get here
1054
1055 def collision(rammed, enemy):
1056     "Collision handling fot rammong events."
1057     prouts(_("***RED ALERT!  RED ALERT!"))
1058     skip(1)
1059     prout(_("***COLLISION IMMINENT."))
1060     skip(2)
1061     proutn("***")
1062     proutn(crmshp())
1063     hardness = {IHR:1.5, IHC:2.0, IHS:2.5, IHT:0.5, IHQUEST:4.0}.get(enemy.type, 1.0)
1064     if rammed:
1065         proutn(_(" rammed by "))
1066     else:
1067         proutn(_(" rams "))
1068     proutn(crmena(False, enemy.type, "sector", enemy.kloc))
1069     if rammed:
1070         proutn(_(" (original position)"))
1071     skip(1)
1072     deadkl(enemy.kloc, enemy.type, game.sector)
1073     proutn("***" + crmship() + " heavily damaged.")
1074     icas = randrange(10, 30)
1075     prout(_("***Sickbay reports %d casualties"), icas)
1076     game.casual += icas
1077     game.state.crew -= icas
1078     # In the pre-SST2K version, all devices got equiprobably damaged,
1079     # which was silly.  Instead, pick up to half the devices at
1080     # random according to our weighting table,
1081     ncrits = randrange(NDEVICES/2)
1082     for m in range(ncrits):
1083         dev = randdevice()
1084         if game.damage[dev] < 0:
1085             continue
1086         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1087         # Damage for at least time of travel! 
1088         game.damage[dev] += game.optime + extradm
1089     game.shldup = False
1090     prout(_("***Shields are down."))
1091     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1092         announce()
1093         damagereport()
1094     else:
1095         finish(FWON)
1096     return
1097
1098 def torpedo(origin, bearing, dispersion, number, nburst):
1099     "Let a photon torpedo fly" 
1100     if not damaged(DSRSENS) or game.condition=="docked":
1101         setwnd(srscan_window)
1102     else: 
1103         setwnd(message_window)
1104     ac = bearing + 0.25*dispersion      # dispersion is a random variable
1105     bullseye = (15.0 - bearing)*0.5235988
1106     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin)) 
1107     bumpto = coord(0, 0)
1108     # Loop to move a single torpedo 
1109     setwnd(message_window)
1110     for step in range(1, QUADSIZE*2):
1111         if not track.next(): break
1112         w = track.sector()
1113         if not w.valid_sector():
1114             break
1115         iquad=game.quad[w.i][w.j]
1116         tracktorpedo(origin, w, step, number, nburst, iquad)
1117         if iquad==IHDOT:
1118             continue
1119         # hit something 
1120         if not damaged(DSRSENS) or game.condition == "docked":
1121             skip(1);    # start new line after text track 
1122         if iquad in (IHE, IHF): # Hit our ship 
1123             skip(1)
1124             prout(_("Torpedo hits %s.") % crmshp())
1125             hit = 700.0 + randreal(100) - \
1126                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1127             newcnd(); # we're blown out of dock 
1128             if game.landed or game.condition=="docked":
1129                 return hit # Cheat if on a planet 
1130             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1131             # is 143 degrees, which is almost exactly 4.8 clockface units
1132             displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
1133             displacement.next()
1134             bumpto = displacement.sector()
1135             if not bumpto.valid_sector():
1136                 return hit
1137             if game.quad[bumpto.i][bumpto.j]==IHBLANK:
1138                 finish(FHOLE)
1139                 return hit
1140             if game.quad[bumpto.i][bumpto.j]!=IHDOT:
1141                 # can't move into object 
1142                 return hit
1143             game.sector = bumpto
1144             proutn(crmshp())
1145             game.quad[w.i][w.j]=IHDOT
1146             game.quad[bumpto.i][bumpto.j]=iquad
1147             prout(_(" displaced by blast to Sector %s ") % bumpto)
1148             for enemy in game.enemies:
1149                 enemy.kdist = enemy.kavgd = (game.sector-enemy.kloc).distance()
1150             game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1151             return None
1152         elif iquad in (IHC, IHS, IHR, IHK): # Hit a regular enemy 
1153             # find the enemy 
1154             if iquad in (IHC, IHS) and withprob(0.05):
1155                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1156                 prout(_("   torpedo neutralized."))
1157                 return None
1158             for enemy in game.enemies:
1159                 if w == enemy.kloc:
1160                     break
1161             kp = math.fabs(enemy.kpower)
1162             h1 = 700.0 + randrange(100) - \
1163                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1164             h1 = math.fabs(h1)
1165             if kp < h1:
1166                 h1 = kp
1167             if enemy.kpower < 0:
1168                 enemy.kpower -= -h1
1169             else:
1170                 enemy.kpower -= h1
1171             if enemy.kpower == 0:
1172                 deadkl(w, iquad, w)
1173                 return None
1174             proutn(crmena(True, iquad, "sector", w))
1175             displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
1176             displacement.next()
1177             bumpto = displacement.sector()
1178             if not bumpto.valid_sector():
1179                 prout(_(" damaged but not destroyed."))
1180                 return
1181             if game.quad[bumpto.i][bumpto.j] == IHBLANK:
1182                 prout(_(" buffeted into black hole."))
1183                 deadkl(w, iquad, bumpto)
1184             if game.quad[bumpto.i][bumpto.j] != IHDOT:
1185                 prout(_(" damaged but not destroyed."))
1186             else:
1187                 prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1188                 enemy.kloc = bumpto
1189                 game.quad[w.i][w.j]=IHDOT
1190                 game.quad[bumpto.i][bumpto.j]=iquad
1191                 for enemy in game.enemies:
1192                     enemy.kdist = enemy.kavgd = (game.sector-enemy.kloc).distance()
1193                 game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1194             return None
1195         elif iquad == IHB: # Hit a base 
1196             skip(1)
1197             prout(_("***STARBASE DESTROYED.."))
1198             game.state.baseq = filter(lambda x: x != game.quadrant, game.state.baseq)
1199             game.quad[w.i][w.j]=IHDOT
1200             game.base.invalidate()
1201             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase -= 1
1202             game.state.chart[game.quadrant.i][game.quadrant.j].starbase -= 1
1203             game.state.basekl += 1
1204             newcnd()
1205             return None
1206         elif iquad == IHP: # Hit a planet 
1207             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1208             game.state.nplankl += 1
1209             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1210             game.iplnet.pclass = "destroyed"
1211             game.iplnet = None
1212             game.plnet.invalidate()
1213             game.quad[w.i][w.j] = IHDOT
1214             if game.landed:
1215                 # captain perishes on planet 
1216                 finish(FDPLANET)
1217             return None
1218         elif iquad == IHW: # Hit an inhabited world -- very bad! 
1219             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1220             game.state.nworldkl += 1
1221             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1222             game.iplnet.pclass = "destroyed"
1223             game.iplnet = None
1224             game.plnet.invalidate()
1225             game.quad[w.i][w.j] = IHDOT
1226             if game.landed:
1227                 # captain perishes on planet 
1228                 finish(FDPLANET)
1229             prout(_("The torpedo destroyed an inhabited planet."))
1230             return None
1231         elif iquad == IHSTAR: # Hit a star 
1232             if withprob(0.9):
1233                 nova(w)
1234             else:
1235                 prout(crmena(True, IHSTAR, "sector", w) + _(" unaffected by photon blast."))
1236             return None
1237         elif iquad == IHQUEST: # Hit a thingy 
1238             if not (game.options & OPTION_THINGY) or withprob(0.3):
1239                 skip(1)
1240                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1241                 skip(1)
1242                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1243                 skip(1)
1244                 proutn(_("Mr. Spock-"))
1245                 prouts(_("  \"Fascinating!\""))
1246                 skip(1)
1247                 deadkl(w, iquad, w)
1248             else:
1249                 # Stas Sergeev added the possibility that
1250                 # you can shove the Thingy and piss it off.
1251                 # It then becomes an enemy and may fire at you.
1252                 thing.angry = True
1253                 shoved = True
1254             return None
1255         elif iquad == IHBLANK: # Black hole 
1256             skip(1)
1257             prout(crmena(True, IHBLANK, "sector", w) + _(" swallows torpedo."))
1258             return None
1259         elif iquad == IHWEB: # hit the web 
1260             skip(1)
1261             prout(_("***Torpedo absorbed by Tholian web."))
1262             return None
1263         elif iquad == IHT:  # Hit a Tholian 
1264             h1 = 700.0 + randrange(100) - \
1265                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
1266             h1 = math.fabs(h1)
1267             if h1 >= 600:
1268                 game.quad[w.i][w.j] = IHDOT
1269                 deadkl(w, iquad, w)
1270                 game.tholian = None
1271                 return None
1272             skip(1)
1273             proutn(crmena(True, IHT, "sector", w))
1274             if withprob(0.05):
1275                 prout(_(" survives photon blast."))
1276                 return None
1277             prout(_(" disappears."))
1278             game.tholian.move(None)
1279             game.quad[w.i][w.j] = IHWEB
1280             dropin(IHBLANK)
1281             return None
1282         else: # Problem!
1283             skip(1)
1284             proutn("Don't know how to handle torpedo collision with ")
1285             proutn(crmena(True, iquad, "sector", w))
1286             skip(1)
1287             return None
1288         break
1289     skip(1)
1290     prout(_("Torpedo missed."))
1291     return None;
1292
1293 def fry(hit):
1294     "Critical-hit resolution." 
1295     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1296         return
1297     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1298     proutn(_("***CRITICAL HIT--"))
1299     # Select devices and cause damage
1300     cdam = []
1301     for loop1 in range(ncrit):
1302         while True:
1303             j = randdevice()
1304             # Cheat to prevent shuttle damage unless on ship 
1305             if not (game.damage[j]<0.0 or (j==DSHUTTL and game.iscraft != "onship")):
1306                 break
1307         cdam.append(j)
1308         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1309         game.damage[j] += extradm
1310     skipcount = 0
1311     for (i, j) in enumerate(cdam):
1312         proutn(device[j])
1313         if skipcount % 3 == 2 and i < len(cdam)-1:
1314             skip(1)
1315         skipcount += 1
1316         if i < len(cdam)-1:
1317             proutn(_(" and "))
1318     prout(_(" damaged."))
1319     if damaged(DSHIELD) and game.shldup:
1320         prout(_("***Shields knocked down."))
1321         game.shldup=False
1322
1323 def attack(torps_ok):
1324     # bad guy attacks us 
1325     # torps_ok == False forces use of phasers in an attack 
1326     # game could be over at this point, check
1327     if game.alldone:
1328         return
1329     attempt = False; ihurt = False;
1330     hitmax=0.0; hittot=0.0; chgfac=1.0
1331     where = "neither"
1332     if idebug:
1333         prout("=== ATTACK!")
1334     # Tholian gets to move before attacking 
1335     if game.tholian:
1336         movetholian()
1337     # if you have just entered the RNZ, you'll get a warning 
1338     if game.neutz: # The one chance not to be attacked 
1339         game.neutz = False
1340         return
1341     # commanders get a chance to tac-move towards you 
1342     if (((game.quadrant in game.state.kcmdr or game.state.kscmdr==game.quadrant) and not game.justin) or game.skill == SKILL_EMERITUS) and torps_ok:
1343         moveklings()
1344     # if no enemies remain after movement, we're done 
1345     if len(game.enemies)==0 or (len(game.enemies)==1 and thing == game.quadrant and not thing.angry):
1346         return
1347     # set up partial hits if attack happens during shield status change 
1348     pfac = 1.0/game.inshld
1349     if game.shldchg:
1350         chgfac = 0.25 + randreal(0.5)
1351     skip(1)
1352     # message verbosity control 
1353     if game.skill <= SKILL_FAIR:
1354         where = "sector"
1355     for enemy in game.enemies:
1356         if enemy.kpower < 0:
1357             continue;   # too weak to attack 
1358         # compute hit strength and diminish shield power 
1359         r = randreal()
1360         # Increase chance of photon torpedos if docked or enemy energy is low 
1361         if game.condition == "docked":
1362             r *= 0.25
1363         if enemy.kpower < 500:
1364             r *= 0.25; 
1365         if enemy.type==IHT or (enemy.type==IHQUEST and not thing.angry):
1366             continue
1367         # different enemies have different probabilities of throwing a torp 
1368         usephasers = not torps_ok or \
1369             (enemy.type == IHK and r > 0.0005) or \
1370             (enemy.type==IHC and r > 0.015) or \
1371             (enemy.type==IHR and r > 0.3) or \
1372             (enemy.type==IHS and r > 0.07) or \
1373             (enemy.type==IHQUEST and r > 0.05)
1374         if usephasers:      # Enemy uses phasers 
1375             if game.condition == "docked":
1376                 continue; # Don't waste the effort! 
1377             attempt = True; # Attempt to attack 
1378             dustfac = randreal(0.8, 0.85)
1379             hit = enemy.kpower*math.pow(dustfac,enemy.kavgd)
1380             enemy.kpower *= 0.75
1381         else: # Enemy uses photon torpedo 
1382             # We should be able to make the bearing() method work here
1383             course = 1.90985*math.atan2(game.sector.j-enemy.kloc.j, enemy.kloc.i-game.sector.i)
1384             hit = 0
1385             proutn(_("***TORPEDO INCOMING"))
1386             if not damaged(DSRSENS):
1387                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.kloc))
1388             attempt = True
1389             prout("  ")
1390             dispersion = (randreal()+randreal())*0.5 - 0.5
1391             dispersion += 0.002*enemy.kpower*dispersion
1392             hit = torpedo(enemy.kloc, course, dispersion, number=1, nburst=1)
1393             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1394                 finish(FWON); # Klingons did themselves in! 
1395             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1396                 return # Supernova or finished 
1397             if hit == None:
1398                 continue
1399         # incoming phaser or torpedo, shields may dissipate it 
1400         if game.shldup or game.shldchg or game.condition=="docked":
1401             # shields will take hits 
1402             propor = pfac * game.shield
1403             if game.condition =="docked":
1404                 propr *= 2.1
1405             if propor < 0.1:
1406                 propor = 0.1
1407             hitsh = propor*chgfac*hit+1.0
1408             absorb = 0.8*hitsh
1409             if absorb > game.shield:
1410                 absorb = game.shield
1411             game.shield -= absorb
1412             hit -= hitsh
1413             # taking a hit blasts us out of a starbase dock 
1414             if game.condition == "docked":
1415                 dock(False)
1416             # but the shields may take care of it 
1417             if propor > 0.1 and hit < 0.005*game.energy:
1418                 continue
1419         # hit from this opponent got through shields, so take damage 
1420         ihurt = True
1421         proutn(_("%d unit hit") % int(hit))
1422         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1423             proutn(_(" on the ") + crmshp())
1424         if not damaged(DSRSENS) and usephasers:
1425             prout(_(" from ") + crmena(False, enemy.type, where, enemy.kloc))
1426         skip(1)
1427         # Decide if hit is critical 
1428         if hit > hitmax:
1429             hitmax = hit
1430         hittot += hit
1431         fry(hit)
1432         game.energy -= hit
1433     if game.energy <= 0:
1434         # Returning home upon your shield, not with it... 
1435         finish(FBATTLE)
1436         return
1437     if not attempt and game.condition == "docked":
1438         prout(_("***Enemies decide against attacking your ship."))
1439     percent = 100.0*pfac*game.shield+0.5
1440     if not ihurt:
1441         # Shields fully protect ship 
1442         proutn(_("Enemy attack reduces shield strength to "))
1443     else:
1444         # Emit message if starship suffered hit(s) 
1445         skip(1)
1446         proutn(_("Energy left %2d    shields ") % int(game.energy))
1447         if game.shldup:
1448             proutn(_("up "))
1449         elif not damaged(DSHIELD):
1450             proutn(_("down "))
1451         else:
1452             proutn(_("damaged, "))
1453     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1454     # Check if anyone was hurt 
1455     if hitmax >= 200 or hittot >= 500:
1456         icas = randrange(int(hittot * 0.015))
1457         if icas >= 2:
1458             skip(1)
1459             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1460             prout(_("   in that last attack.\""))
1461             game.casual += icas
1462             game.state.crew -= icas
1463     # After attack, reset average distance to enemies 
1464     for enemy in game.enemies:
1465         enemy.kavgd = enemy.kdist
1466     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1467     return
1468                 
1469 def deadkl(w, type, mv):
1470     "Kill a Klingon, Tholian, Romulan, or Thingy." 
1471     # Added mv to allow enemy to "move" before dying 
1472     proutn(crmena(True, type, "sector", mv))
1473     # Decide what kind of enemy it is and update appropriately 
1474     if type == IHR:
1475         # Chalk up a Romulan 
1476         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1477         game.irhere -= 1
1478         game.state.nromrem -= 1
1479     elif type == IHT:
1480         # Killed a Tholian 
1481         game.tholian = None
1482     elif type == IHQUEST:
1483         # Killed a Thingy
1484         global thing
1485         thing = None
1486     else:
1487         # Killed some type of Klingon 
1488         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1489         game.klhere -= 1
1490         if type == IHC:
1491             game.state.kcmdr.remove(game.quadrant)
1492             unschedule(FTBEAM)
1493             if game.state.kcmdr:
1494                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1495             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1496                 unschedule(FCDBAS)    
1497         elif type ==  IHK:
1498             game.state.remkl -= 1
1499         elif type ==  IHS:
1500             game.state.nscrem -= 1
1501             game.state.kscmdr.invalidate()
1502             game.isatb = 0
1503             game.iscate = False
1504             unschedule(FSCMOVE)
1505             unschedule(FSCDBAS)
1506     # For each kind of enemy, finish message to player 
1507     prout(_(" destroyed."))
1508     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1509         return
1510     game.recompute()
1511     # Remove enemy ship from arrays describing local conditions
1512     for e in game.enemies:
1513         if e.kloc == w:
1514             e.move(None)
1515             break
1516     return
1517
1518 def targetcheck(w):
1519     "Return None if target is invalid, otherwise return a course angle."
1520     if not w.valid_sector():
1521         huh()
1522         return None
1523     delta = coord()
1524     # FIXME: C code this was translated from is wacky -- why the sign reversal?
1525     delta.j = (w.j - game.sector.j);
1526     delta.i = (game.sector.i - w.i);
1527     if delta == coord(0, 0):
1528         skip(1)
1529         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1530         prout(_("  I recommend an immediate review of"))
1531         prout(_("  the Captain's psychological profile.\""))
1532         scanner.chew()
1533         return None
1534     return delta.bearing()
1535
1536 def photon():
1537     "Launch photon torpedo."
1538     course = []
1539     game.ididit = False
1540     if damaged(DPHOTON):
1541         prout(_("Photon tubes damaged."))
1542         scanner.chew()
1543         return
1544     if game.torps == 0:
1545         prout(_("No torpedoes left."))
1546         scanner.chew()
1547         return
1548     # First, get torpedo count
1549     while True:
1550         scanner.next()
1551         if scanner.token == "IHALPHA":
1552             huh()
1553             return
1554         elif scanner.token == "IHEOL" or not scanner.waiting():
1555             prout(_("%d torpedoes left.") % game.torps)
1556             scanner.chew()
1557             proutn(_("Number of torpedoes to fire- "))
1558             continue    # Go back around to get a number
1559         else: # key == "IHREAL"
1560             n = scanner.int()
1561             if n <= 0: # abort command 
1562                 scanner.chew()
1563                 return
1564             if n > MAXBURST:
1565                 scanner.chew()
1566                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1567                 return
1568             if n > game.torps:
1569                 scanner.chew()  # User requested more torps than available
1570                 continue        # Go back around
1571             break       # All is good, go to next stage
1572     # Next, get targets
1573     target = []
1574     for i in range(n):
1575         key = scanner.next()
1576         if i==0 and key == "IHEOL":
1577             break;      # no coordinate waiting, we will try prompting 
1578         if i==1 and key == "IHEOL":
1579             # direct all torpedoes at one target 
1580             while i < n:
1581                 target.append(target[0])
1582                 course.append(course[0])
1583                 i += 1
1584             break
1585         scanner.push(scanner.token)
1586         target.append(scanner.getcoord())
1587         if target[-1] == None:
1588             return
1589         course.append(targetcheck(target[-1]))
1590         if course[-1] == None:
1591             return
1592     scanner.chew()
1593     if len(target) == 0:
1594         # prompt for each one 
1595         for i in range(n):
1596             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1597             scanner.chew()
1598             target.append(scanner.getcoord())
1599             if target[-1] == None:
1600                 return
1601             course.append(targetcheck(target[-1]))
1602             if course[-1] == None:
1603                 return
1604     game.ididit = True
1605     # Loop for moving <n> torpedoes 
1606     for i in range(n):
1607         if game.condition != "docked":
1608             game.torps -= 1
1609         dispersion = (randreal()+randreal())*0.5 -0.5
1610         if math.fabs(dispersion) >= 0.47:
1611             # misfire! 
1612             dispersion *= randreal(1.2, 2.2)
1613             if n > 0:
1614                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1615             else:
1616                 prouts(_("***TORPEDO MISFIRES."))
1617             skip(1)
1618             if i < n:
1619                 prout(_("  Remainder of burst aborted."))
1620             if withprob(0.2):
1621                 prout(_("***Photon tubes damaged by misfire."))
1622                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1623             break
1624         if game.shldup or game.condition == "docked":
1625             dispersion *= 1.0 + 0.0001*game.shield
1626         torpedo(game.sector, course[i], dispersion, number=i, nburst=n)
1627         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1628             return
1629     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1630         finish(FWON);
1631
1632 def overheat(rpow):
1633     "Check for phasers overheating."
1634     if rpow > 1500:
1635         checkburn = (rpow-1500.0)*0.00038
1636         if withprob(checkburn):
1637             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1638             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1639
1640 def checkshctrl(rpow):
1641     "Check shield control."
1642     skip(1)
1643     if withprob(0.998):
1644         prout(_("Shields lowered."))
1645         return False
1646     # Something bad has happened 
1647     prouts(_("***RED ALERT!  RED ALERT!"))
1648     skip(2)
1649     hit = rpow*game.shield/game.inshld
1650     game.energy -= rpow+hit*0.8
1651     game.shield -= hit*0.2
1652     if game.energy <= 0.0:
1653         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1654         skip(1)
1655         stars()
1656         finish(FPHASER)
1657         return True
1658     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1659     skip(2)
1660     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1661     icas = randrange(int(hit*0.012))
1662     skip(1)
1663     fry(0.8*hit)
1664     if icas:
1665         skip(1)
1666         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1667         prout(_("  %d casualties so far.\"") % icas)
1668         game.casual += icas
1669         game.state.crew -= icas
1670     skip(1)
1671     prout(_("Phaser energy dispersed by shields."))
1672     prout(_("Enemy unaffected."))
1673     overheat(rpow)
1674     return True;
1675
1676 def hittem(hits):
1677     "Register a phaser hit on Klingons and Romulans."
1678     nenhr2 = len(game.enemies); kk=0
1679     w = coord()
1680     skip(1)
1681     for (k, wham) in enumerate(hits):
1682         if wham==0:
1683             continue
1684         dustfac = randreal(0.9, 1.0)
1685         hit = wham*math.pow(dustfac,game.enemies[kk].kdist)
1686         kpini = game.enemies[kk].kpower
1687         kp = math.fabs(kpini)
1688         if PHASEFAC*hit < kp:
1689             kp = PHASEFAC*hit
1690         if game.enemies[kk].kpower < 0:
1691             game.enemies[kk].kpower -= -kp
1692         else:
1693             game.enemies[kk].kpower -= kp
1694         kpow = game.enemies[kk].kpower
1695         w = game.enemies[kk].kloc
1696         if hit > 0.005:
1697             if not damaged(DSRSENS):
1698                 boom(w)
1699             proutn(_("%d unit hit on ") % int(hit))
1700         else:
1701             proutn(_("Very small hit on "))
1702         ienm = game.quad[w.i][w.j]
1703         if ienm==IHQUEST:
1704             thing.angry = True
1705         proutn(crmena(False, ienm, "sector", w))
1706         skip(1)
1707         if kpow == 0:
1708             deadkl(w, ienm, w)
1709             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1710                 finish(FWON);           
1711             if game.alldone:
1712                 return
1713             kk -= 1     # don't do the increment
1714             continue
1715         else: # decide whether or not to emasculate klingon 
1716             if kpow>0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1717                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1718                 prout(_("   has just lost its firepower.\""))
1719                 game.enemies[kk].kpower = -kpow
1720         kk += 1
1721     return
1722
1723 def phasers():
1724     "Fire phasers at bad guys."
1725     hits = []
1726     kz = 0; k = 1; irec=0 # Cheating inhibitor 
1727     ifast = False; no = False; itarg = True; msgflag = True; rpow=0
1728     automode = "NOTSET"
1729     key=0
1730     skip(1)
1731     # SR sensors and Computer are needed for automode 
1732     if damaged(DSRSENS) or damaged(DCOMPTR):
1733         itarg = False
1734     if game.condition == "docked":
1735         prout(_("Phasers can't be fired through base shields."))
1736         scanner.chew()
1737         return
1738     if damaged(DPHASER):
1739         prout(_("Phaser control damaged."))
1740         scanner.chew()
1741         return
1742     if game.shldup:
1743         if damaged(DSHCTRL):
1744             prout(_("High speed shield control damaged."))
1745             scanner.chew()
1746             return
1747         if game.energy <= 200.0:
1748             prout(_("Insufficient energy to activate high-speed shield control."))
1749             scanner.chew()
1750             return
1751         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1752         ifast = True
1753     # Original code so convoluted, I re-did it all
1754     # (That was Tom Almy talking about the C code, I think -- ESR)
1755     while automode=="NOTSET":
1756         key=scanner.next()
1757         if key == "IHALPHA":
1758             if scanner.sees("manual"):
1759                 if len(game.enemies)==0:
1760                     prout(_("There is no enemy present to select."))
1761                     scanner.chew()
1762                     key = "IHEOL"
1763                     automode="AUTOMATIC"
1764                 else:
1765                     automode = "MANUAL"
1766                     key = scanner.next()
1767             elif scanner.sees("automatic"):
1768                 if (not itarg) and len(game.enemies) != 0:
1769                     automode = "FORCEMAN"
1770                 else:
1771                     if len(game.enemies)==0:
1772                         prout(_("Energy will be expended into space."))
1773                     automode = "AUTOMATIC"
1774                     key = scanner.next()
1775             elif scanner.sees("no"):
1776                 no = True
1777             else:
1778                 huh()
1779                 return
1780         elif key == "IHREAL":
1781             if len(game.enemies)==0:
1782                 prout(_("Energy will be expended into space."))
1783                 automode = "AUTOMATIC"
1784             elif not itarg:
1785                 automode = "FORCEMAN"
1786             else:
1787                 automode = "AUTOMATIC"
1788         else:
1789             # "IHEOL" 
1790             if len(game.enemies)==0:
1791                 prout(_("Energy will be expended into space."))
1792                 automode = "AUTOMATIC"
1793             elif not itarg:
1794                 automode = "FORCEMAN"
1795             else: 
1796                 proutn(_("Manual or automatic? "))
1797                 scanner.chew()
1798     avail = game.energy
1799     if ifast:
1800         avail -= 200.0
1801     if automode == "AUTOMATIC":
1802         if key == "IHALPHA" and scanner.sees("no"):
1803             no = True
1804             key = scanner.next()
1805         if key != "IHREAL" and len(game.enemies) != 0:
1806             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1807         irec=0
1808         while True:
1809             scanner.chew()
1810             if not kz:
1811                 for i in range(len(game.enemies)):
1812                     irec += math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1813             kz=1
1814             proutn(_("%d units required. ") % irec)
1815             scanner.chew()
1816             proutn(_("Units to fire= "))
1817             key = scanner.next()
1818             if key!="IHREAL":
1819                 return
1820             rpow = scanner.real
1821             if rpow > avail:
1822                 proutn(_("Energy available= %.2f") % avail)
1823                 skip(1)
1824                 key = "IHEOL"
1825             if not rpow > avail:
1826                 break
1827         if rpow<=0:
1828             # chicken out 
1829             scanner.chew()
1830             return
1831         key=scanner.next()
1832         if key == "IHALPHA" and scanner.sees("no"):
1833             no = True
1834         if ifast:
1835             game.energy -= 200; # Go and do it! 
1836             if checkshctrl(rpow):
1837                 return
1838         scanner.chew()
1839         game.energy -= rpow
1840         extra = rpow
1841         if len(game.enemies):
1842             extra = 0.0
1843             powrem = rpow
1844             for i in range(len(game.enemies)):
1845                 hits.append(0.0)
1846                 if powrem <= 0:
1847                     continue
1848                 hits[i] = math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))
1849                 over = randreal(1.01, 1.06) * hits[i]
1850                 temp = powrem
1851                 powrem -= hits[i] + over
1852                 if powrem <= 0 and temp < hits[i]:
1853                     hits[i] = temp
1854                 if powrem <= 0:
1855                     over = 0.0
1856                 extra += over
1857             if powrem > 0.0:
1858                 extra += powrem
1859             hittem(hits)
1860             game.ididit = True
1861         if extra > 0 and not game.alldone:
1862             if game.tholian:
1863                 proutn(_("*** Tholian web absorbs "))
1864                 if len(game.enemies)>0:
1865                     proutn(_("excess "))
1866                 prout(_("phaser energy."))
1867             else:
1868                 prout(_("%d expended on empty space.") % int(extra))
1869     elif automode == "FORCEMAN":
1870         scanner.chew()
1871         key = "IHEOL"
1872         if damaged(DCOMPTR):
1873             prout(_("Battle computer damaged, manual fire only."))
1874         else:
1875             skip(1)
1876             prouts(_("---WORKING---"))
1877             skip(1)
1878             prout(_("Short-range-sensors-damaged"))
1879             prout(_("Insufficient-data-for-automatic-phaser-fire"))
1880             prout(_("Manual-fire-must-be-used"))
1881             skip(1)
1882     elif automode == "MANUAL":
1883         rpow = 0.0
1884         for k in range(len(game.enemies)):
1885             aim = game.enemies[k].kloc
1886             ienm = game.quad[aim.i][aim.j]
1887             if msgflag:
1888                 proutn(_("Energy available= %.2f") % (avail-0.006))
1889                 skip(1)
1890                 msgflag = False
1891                 rpow = 0.0
1892             if damaged(DSRSENS) and \
1893                not game.sector.distance(aim)<2**0.5 and ienm in (IHC, IHS):
1894                 prout(cramen(ienm) + _(" can't be located without short range scan."))
1895                 scanner.chew()
1896                 key = "IHEOL"
1897                 hits[k] = 0; # prevent overflow -- thanks to Alexei Voitenko 
1898                 k += 1
1899                 continue
1900             if key == "IHEOL":
1901                 scanner.chew()
1902                 if itarg and k > kz:
1903                     irec=(abs(game.enemies[k].kpower)/(PHASEFAC*math.pow(0.9,game.enemies[k].kdist))) * randreal(1.01, 1.06) + 1.0
1904                 kz = k
1905                 proutn("(")
1906                 if not damaged(DCOMPTR):
1907                     proutn("%d" % irec)
1908                 else:
1909                     proutn("??")
1910                 proutn(")  ")
1911                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))                
1912                 key = scanner.next()
1913             if key == "IHALPHA" and scanner.sees("no"):
1914                 no = True
1915                 key = scanner.next()
1916                 continue
1917             if key == "IHALPHA":
1918                 huh()
1919                 return
1920             if key == "IHEOL":
1921                 if k==1: # Let me say I'm baffled by this 
1922                     msgflag = True
1923                 continue
1924             if scanner.real < 0:
1925                 # abort out 
1926                 scanner.chew()
1927                 return
1928             hits[k] = scanner.real
1929             rpow += scanner.real
1930             # If total requested is too much, inform and start over 
1931             if rpow > avail:
1932                 prout(_("Available energy exceeded -- try again."))
1933                 scanner.chew()
1934                 return
1935             key = scanner.next(); # scan for next value 
1936             k += 1
1937         if rpow == 0.0:
1938             # zero energy -- abort 
1939             scanner.chew()
1940             return
1941         if key == "IHALPHA" and scanner.sees("no"):
1942             no = True
1943         game.energy -= rpow
1944         scanner.chew()
1945         if ifast:
1946             game.energy -= 200.0
1947             if checkshctrl(rpow):
1948                 return
1949         hittem(hits)
1950         game.ididit = True
1951      # Say shield raised or malfunction, if necessary 
1952     if game.alldone:
1953         return
1954     if ifast:
1955         skip(1)
1956         if no == 0:
1957             if withprob(0.01):
1958                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
1959                 prouts(_("         CLICK   CLICK   POP  . . ."))
1960                 prout(_(" No response, sir!"))
1961                 game.shldup = False
1962             else:
1963                 prout(_("Shields raised."))
1964         else:
1965             game.shldup = False
1966     overheat(rpow);
1967
1968 # Code from events,c begins here.
1969
1970 # This isn't a real event queue a la BSD Trek yet -- you can only have one 
1971 # event of each type active at any given time.  Mostly these means we can 
1972 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
1973 # BSD Trek, from which we swiped the idea, can have up to 5.
1974
1975 def unschedule(evtype):
1976     "Remove an event from the schedule."
1977     game.future[evtype].date = FOREVER
1978     return game.future[evtype]
1979
1980 def is_scheduled(evtype):
1981     "Is an event of specified type scheduled."
1982     return game.future[evtype].date != FOREVER
1983
1984 def scheduled(evtype):
1985     "When will this event happen?"
1986     return game.future[evtype].date
1987
1988 def schedule(evtype, offset):
1989     "Schedule an event of specified type."
1990     game.future[evtype].date = game.state.date + offset
1991     return game.future[evtype]
1992
1993 def postpone(evtype, offset):
1994     "Postpone a scheduled event."
1995     game.future[evtype].date += offset
1996
1997 def cancelrest():
1998     "Rest period is interrupted by event."
1999     if game.resting:
2000         skip(1)
2001         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2002         if ja() == True:
2003             game.resting = False
2004             game.optime = 0.0
2005             return True
2006     return False
2007
2008 def events():
2009     "Run through the event queue looking for things to do."
2010     i=0
2011     fintim = game.state.date + game.optime; yank=0
2012     ictbeam = False; istract = False
2013     w = coord(); hold = coord()
2014     ev = event(); ev2 = event()
2015
2016     def tractorbeam(yank):
2017         "Tractor-beaming cases merge here." 
2018         announce()
2019         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5) 
2020         skip(1)
2021         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2022         # If Kirk & Co. screwing around on planet, handle 
2023         atover(True) # atover(true) is Grab 
2024         if game.alldone:
2025             return
2026         if game.icraft: # Caught in Galileo? 
2027             finish(FSTRACTOR)
2028             return
2029         # Check to see if shuttle is aboard 
2030         if game.iscraft == "offship":
2031             skip(1)
2032             if withprob(0.5):
2033                 prout(_("Galileo, left on the planet surface, is captured"))
2034                 prout(_("by aliens and made into a flying McDonald's."))
2035                 game.damage[DSHUTTL] = -10
2036                 game.iscraft = "removed"
2037             else:
2038                 prout(_("Galileo, left on the planet surface, is well hidden."))
2039         if evcode == FSPY:
2040             game.quadrant = game.state.kscmdr
2041         else:
2042             game.quadrant = game.state.kcmdr[i]
2043         game.sector = randplace(QUADSIZE)
2044         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2045                % (game.quadrant, game.sector))
2046         if game.resting:
2047             prout(_("(Remainder of rest/repair period cancelled.)"))
2048             game.resting = False
2049         if not game.shldup:
2050             if not damaged(DSHIELD) and game.shield > 0:
2051                 doshield(shraise=True) # raise shields 
2052                 game.shldchg = False
2053             else:
2054                 prout(_("(Shields not currently useable.)"))
2055         newqad()
2056         # Adjust finish time to time of tractor beaming 
2057         fintim = game.state.date+game.optime
2058         attack(torps_ok=False)
2059         if not game.state.kcmdr:
2060             unschedule(FTBEAM)
2061         else: 
2062             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2063
2064     def destroybase():
2065         "Code merges here for any commander destroying a starbase." 
2066         # Not perfect, but will have to do 
2067         # Handle case where base is in same quadrant as starship 
2068         if game.battle == game.quadrant:
2069             game.state.chart[game.battle.i][game.battle.j].starbase = False
2070             game.quad[game.base.i][game.base.j] = IHDOT
2071             game.base.invalidate()
2072             newcnd()
2073             skip(1)
2074             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2075         elif game.state.baseq and communicating():
2076             # Get word via subspace radio 
2077             announce()
2078             skip(1)
2079             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2080             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2081             if game.isatb == 2: 
2082                 prout(_("the Klingon Super-Commander"))
2083             else:
2084                 prout(_("a Klingon Commander"))
2085             game.state.chart[game.battle.i][game.battle.j].starbase = False
2086         # Remove Starbase from galaxy 
2087         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2088         game.state.baseq = filter(lambda x: x != game.battle, game.state.baseq)
2089         if game.isatb == 2:
2090             # reinstate a commander's base attack 
2091             game.battle = hold
2092             game.isatb = 0
2093         else:
2094             game.battle.invalidate()
2095     if idebug:
2096         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2097         for i in range(1, NEVENTS):
2098             if   i == FSNOVA:  proutn("=== Supernova       ")
2099             elif i == FTBEAM:  proutn("=== T Beam          ")
2100             elif i == FSNAP:   proutn("=== Snapshot        ")
2101             elif i == FBATTAK: proutn("=== Base Attack     ")
2102             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2103             elif i == FSCMOVE: proutn("=== SC Move         ")
2104             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2105             elif i == FDSPROB: proutn("=== Probe Move      ")
2106             elif i == FDISTR:  proutn("=== Distress Call   ")
2107             elif i == FENSLV:  proutn("=== Enslavement     ")
2108             elif i == FREPRO:  proutn("=== Klingon Build   ")
2109             if is_scheduled(i):
2110                 prout("%.2f" % (scheduled(i)))
2111             else:
2112                 prout("never")
2113     radio_was_broken = damaged(DRADIO)
2114     hold.i = hold.j = 0
2115     while True:
2116         # Select earliest extraneous event, evcode==0 if no events 
2117         evcode = FSPY
2118         if game.alldone:
2119             return
2120         datemin = fintim
2121         for l in range(1, NEVENTS):
2122             if game.future[l].date < datemin:
2123                 evcode = l
2124                 if idebug:
2125                     prout("== Event %d fires" % evcode)
2126                 datemin = game.future[l].date
2127         xtime = datemin-game.state.date
2128         game.state.date = datemin
2129         # Decrement Federation resources and recompute remaining time 
2130         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2131         game.recompute()
2132         if game.state.remtime <=0:
2133             finish(FDEPLETE)
2134             return
2135         # Any crew left alive? 
2136         if game.state.crew <=0:
2137             finish(FCREW)
2138             return
2139         # Is life support adequate? 
2140         if damaged(DLIFSUP) and game.condition != "docked":
2141             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2142                 finish(FLIFESUP)
2143                 return
2144             game.lsupres -= xtime
2145             if game.damage[DLIFSUP] <= xtime:
2146                 game.lsupres = game.inlsr
2147         # Fix devices 
2148         repair = xtime
2149         if game.condition == "docked":
2150             repair /= game.docfac
2151         # Don't fix Deathray here 
2152         for l in range(NDEVICES):
2153             if game.damage[l] > 0.0 and l != DDRAY:
2154                 if game.damage[l]-repair > 0.0:
2155                     game.damage[l] -= repair
2156                 else:
2157                     game.damage[l] = 0.0
2158         # If radio repaired, update star chart and attack reports 
2159         if radio_was_broken and not damaged(DRADIO):
2160             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2161             prout(_("   surveillance reports are coming in."))
2162             skip(1)
2163             if not game.iseenit:
2164                 attackreport(False)
2165                 game.iseenit = True
2166             rechart()
2167             prout(_("   The star chart is now up to date.\""))
2168             skip(1)
2169         # Cause extraneous event EVCODE to occur 
2170         game.optime -= xtime
2171         if evcode == FSNOVA: # Supernova 
2172             announce()
2173             supernova(None)
2174             schedule(FSNOVA, expran(0.5*game.intime))
2175             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2176                 return
2177         elif evcode == FSPY: # Check with spy to see if SC should tractor beam 
2178             if game.state.nscrem == 0 or \
2179                 ictbeam or istract or \
2180                 game.condition=="docked" or game.isatb==1 or game.iscate:
2181                 return
2182             if game.ientesc or \
2183                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2184                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2185                 (damaged(DSHIELD) and \
2186                  (game.energy < 2500 or damaged(DPHASER)) and \
2187                  (game.torps < 5 or damaged(DPHOTON))):
2188                 # Tractor-beam her! 
2189                 istract = ictbeam = True
2190                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2191             else:
2192                 return
2193         elif evcode == FTBEAM: # Tractor beam 
2194             if not game.state.kcmdr:
2195                 unschedule(FTBEAM)
2196                 continue
2197             i = randrange(len(game.state.kcmdr))
2198             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2199             if istract or game.condition == "docked" or yank == 0:
2200                 # Drats! Have to reschedule 
2201                 schedule(FTBEAM, 
2202                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2203                 continue
2204             ictbeam = True
2205             tractorbeam(yank)
2206         elif evcode == FSNAP: # Snapshot of the universe (for time warp) 
2207             game.snapsht = copy.deepcopy(game.state)
2208             game.state.snap = True
2209             schedule(FSNAP, expran(0.5 * game.intime))
2210         elif evcode == FBATTAK: # Commander attacks starbase 
2211             if not game.state.kcmdr or not game.state.baseq:
2212                 # no can do 
2213                 unschedule(FBATTAK)
2214                 unschedule(FCDBAS)
2215                 continue
2216             try:
2217                 for ibq in game.state.baseq:
2218                    for cmdr in game.state.kcmdr: 
2219                        if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2220                            raise ibq
2221                 else:
2222                     # no match found -- try later 
2223                     schedule(FBATTAK, expran(0.3*game.intime))
2224                     unschedule(FCDBAS)
2225                     continue
2226             except coord:
2227                 pass
2228             # commander + starbase combination found -- launch attack 
2229             game.battle = ibq
2230             schedule(FCDBAS, randreal(1.0, 4.0))
2231             if game.isatb: # extra time if SC already attacking 
2232                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2233             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2234             game.iseenit = False
2235             if not communicating():
2236                 continue # No warning :-( 
2237             game.iseenit = True
2238             announce()
2239             skip(1)
2240             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2241             prout(_("   reports that it is under attack and that it can"))
2242             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2243             if cancelrest():
2244                 return
2245         elif evcode == FSCDBAS: # Supercommander destroys base 
2246             unschedule(FSCDBAS)
2247             game.isatb = 2
2248             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase: 
2249                 continue # WAS RETURN! 
2250             hold = game.battle
2251             game.battle = game.state.kscmdr
2252             destroybase()
2253         elif evcode == FCDBAS: # Commander succeeds in destroying base 
2254             if evcode==FCDBAS:
2255                 unschedule(FCDBAS)
2256                 if not game.state.baseq() \
2257                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2258                     game.battle.invalidate()
2259                     continue
2260                 # find the lucky pair 
2261                 for cmdr in game.state.kcmdr:
2262                     if cmdr == game.battle: 
2263                         break
2264                 else:
2265                     # No action to take after all 
2266                     continue
2267             destroybase()
2268         elif evcode == FSCMOVE: # Supercommander moves 
2269             schedule(FSCMOVE, 0.2777)
2270             if not game.ientesc and not istract and game.isatb != 1 and \
2271                    (not game.iscate or not game.justin): 
2272                 supercommander()
2273         elif evcode == FDSPROB: # Move deep space probe 
2274             schedule(FDSPROB, 0.01)
2275             if not game.probe.next():
2276                 if not game.probe.quadrant().valid_quadrant() or \
2277                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2278                     # Left galaxy or ran into supernova
2279                     if communicating():
2280                         announce()
2281                         skip(1)
2282                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2283                         if not game.probe.quadrant().valid_quadrant():
2284                             prout(_("has left the galaxy.\""))
2285                         else:
2286                             prout(_("is no longer transmitting.\""))
2287                     unschedule(FDSPROB)
2288                     continue
2289                 if communicating():
2290                     #announce()
2291                     skip(1)
2292                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2293             pdest = game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j]
2294             if communicating():
2295                 chp = game.state.chart[game.probe.quadrant().i][game.probe.quadrant().j]
2296                 chp.klingons = pdest.klingons
2297                 chp.starbase = pdest.starbase
2298                 chp.stars = pdest.stars
2299                 pdest.charted = True
2300             game.probe.moves -= 1 # One less to travel
2301             if game.probe.arrived() and game.isarmed and pdest.stars:
2302                 supernova(game.probe)           # fire in the hole!
2303                 unschedule(FDSPROB)
2304                 if game.state.galaxy[game.quadrant().i][game.quadrant().j].supernova: 
2305                     return
2306         elif evcode == FDISTR: # inhabited system issues distress call 
2307             unschedule(FDISTR)
2308             # try a whole bunch of times to find something suitable 
2309             for i in range(100):
2310                 # need a quadrant which is not the current one,
2311                 # which has some stars which are inhabited and
2312                 # not already under attack, which is not
2313                 # supernova'ed, and which has some Klingons in it
2314                 w = randplace(GALSIZE)
2315                 q = game.state.galaxy[w.i][w.j]
2316                 if not (game.quadrant == w or q.planet == None or \
2317                       not q.planet.inhabited or \
2318                       q.supernova or q.status!="secure" or q.klingons<=0):
2319                     break
2320             else:
2321                 # can't seem to find one; ignore this call 
2322                 if idebug:
2323                     prout("=== Couldn't find location for distress event.")
2324                 continue
2325             # got one!!  Schedule its enslavement 
2326             ev = schedule(FENSLV, expran(game.intime))
2327             ev.quadrant = w
2328             q.status = "distressed"
2329             # tell the captain about it if we can 
2330             if communicating():
2331                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2332                         % (q.planet, `w`))
2333                 prout(_("by a Klingon invasion fleet."))
2334                 if cancelrest():
2335                     return
2336         elif evcode == FENSLV:          # starsystem is enslaved 
2337             ev = unschedule(FENSLV)
2338             # see if current distress call still active 
2339             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2340             if q.klingons <= 0:
2341                 q.status = "secure"
2342                 continue
2343             q.status = "enslaved"
2344
2345             # play stork and schedule the first baby 
2346             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2347             ev2.quadrant = ev.quadrant
2348
2349             # report the disaster if we can 
2350             if communicating():
2351                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2352                         q.planet)
2353                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2354         elif evcode == FREPRO:          # Klingon reproduces 
2355             # If we ever switch to a real event queue, we'll need to
2356             # explicitly retrieve and restore the x and y.
2357             ev = schedule(FREPRO, expran(1.0 * game.intime))
2358             # see if current distress call still active 
2359             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2360             if q.klingons <= 0:
2361                 q.status = "secure"
2362                 continue
2363             if game.state.remkl >=MAXKLGAME:
2364                 continue                # full right now 
2365             # reproduce one Klingon 
2366             w = ev.quadrant
2367             m = coord()
2368             if game.klhere >= MAXKLQUAD:
2369                 try:
2370                     # this quadrant not ok, pick an adjacent one 
2371                     for m.i in range(w.i - 1, w.i + 2):
2372                         for m.j in range(w.j - 1, w.j + 2):
2373                             if not m.valid_quadrant():
2374                                 continue
2375                             q = game.state.galaxy[m.i][m.j]
2376                             # check for this quad ok (not full & no snova) 
2377                             if q.klingons >= MAXKLQUAD or q.supernova:
2378                                 continue
2379                             raise "FOUNDIT"
2380                     else:
2381                         continue        # search for eligible quadrant failed
2382                 except "FOUNDIT":
2383                     w = m
2384             # deliver the child 
2385             game.state.remkl += 1
2386             q.klingons += 1
2387             if game.quadrant == w:
2388                 game.klhere += 1
2389                 game.enemies.append(newkling())
2390             # recompute time left
2391             game.recompute()
2392             if communicating():
2393                 if game.quadrant == w:
2394                     prout(_("Spock- sensors indicate the Klingons have"))
2395                     prout(_("launched a warship from %s.") % q.planet)
2396                 else:
2397                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2398                     if q.planet != None:
2399                         proutn(_("near %s") % q.planet)
2400                     prout(_("in Quadrant %s.") % w)
2401                                 
2402 def wait():
2403     "Wait on events."
2404     game.ididit = False
2405     while True:
2406         key = scanner.next()
2407         if key  != "IHEOL":
2408             break
2409         proutn(_("How long? "))
2410     scanner.chew()
2411     if key != "IHREAL":
2412         huh()
2413         return
2414     origTime = delay = scanner.real
2415     if delay <= 0.0:
2416         return
2417     if delay >= game.state.remtime or len(game.enemies) != 0:
2418         proutn(_("Are you sure? "))
2419         if ja() == False:
2420             return
2421     # Alternate resting periods (events) with attacks 
2422     game.resting = True
2423     while True:
2424         if delay <= 0:
2425             game.resting = False
2426         if not game.resting:
2427             prout(_("%d stardates left.") % int(game.state.remtime))
2428             return
2429         temp = game.optime = delay
2430         if len(game.enemies):
2431             rtime = randreal(1.0, 2.0)
2432             if rtime < temp:
2433                 temp = rtime
2434             game.optime = temp
2435         if game.optime < delay:
2436             attack(torps_ok=False)
2437         if game.alldone:
2438             return
2439         events()
2440         game.ididit = True
2441         if game.alldone:
2442             return
2443         delay -= temp
2444         # Repair Deathray if long rest at starbase 
2445         if origTime-delay >= 9.99 and game.condition == "docked":
2446             game.damage[DDRAY] = 0.0
2447         # leave if quadrant supernovas
2448         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2449             break
2450     game.resting = False
2451     game.optime = 0
2452
2453 def nova(nov):
2454     "Star goes nova." 
2455     course = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2456     newc = coord(); neighbor = coord(); bump = coord(0, 0)
2457     if withprob(0.05):
2458         # Wow! We've supernova'ed 
2459         supernova(game.quadrant)
2460         return
2461     # handle initial nova 
2462     game.quad[nov.i][nov.j] = IHDOT
2463     prout(crmena(False, IHSTAR, "sector", nov) + _(" novas."))
2464     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2465     game.state.starkl += 1
2466     # Set up queue to recursively trigger adjacent stars 
2467     hits = [nov]
2468     kount = 0
2469     while hits:
2470         offset = coord()
2471         start = hits.pop()
2472         for offset.i in range(-1, 1+1):
2473             for offset.j in range(-1, 1+1):
2474                 if offset.j==0 and offset.i==0:
2475                     continue
2476                 neighbor = start + offset
2477                 if not neighbor.valid_sector():
2478                     continue
2479                 iquad = game.quad[neighbor.i][neighbor.j]
2480                 # Empty space ends reaction
2481                 if iquad in (IHDOT, IHQUEST, IHBLANK, IHT, IHWEB):
2482                     pass
2483                 elif iquad == IHSTAR: # Affect another star 
2484                     if withprob(0.05):
2485                         # This star supernovas 
2486                         supernova(game.quadrant)
2487                         return
2488                     else:
2489                         hits.append(neighbor)
2490                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2491                         game.state.starkl += 1
2492                         proutn(crmena(True, IHSTAR, "sector", neighbor))
2493                         prout(_(" novas."))
2494                         game.quad[neighbor.i][neighbor.j] = IHDOT
2495                         kount += 1
2496                 elif iquad in (IHP, IHW): # Destroy planet 
2497                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2498                     if iquad == IHP:
2499                         game.state.nplankl += 1
2500                     else:
2501                         game.state.worldkl += 1
2502                     prout(crmena(True, IHB, "sector", neighbor) + _(" destroyed."))
2503                     game.iplnet.pclass = "destroyed"
2504                     game.iplnet = None
2505                     game.plnet.invalidate()
2506                     if game.landed:
2507                         finish(FPNOVA)
2508                         return
2509                     game.quad[neighbor.i][neighbor.j] = IHDOT
2510                 elif iquad == IHB: # Destroy base 
2511                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2512                     game.state.baseq = filter(lambda x: x!= game.quadrant, game.state.baseq)
2513                     game.base.invalidate()
2514                     game.state.basekl += 1
2515                     newcnd()
2516                     prout(crmena(True, IHB, "sector", neighbor) + _(" destroyed."))
2517                     game.quad[neighbor.i][neighbor.j] = IHDOT
2518                 elif iquad in (IHE, IHF): # Buffet ship 
2519                     prout(_("***Starship buffeted by nova."))
2520                     if game.shldup:
2521                         if game.shield >= 2000.0:
2522                             game.shield -= 2000.0
2523                         else:
2524                             diff = 2000.0 - game.shield
2525                             game.energy -= diff
2526                             game.shield = 0.0
2527                             game.shldup = False
2528                             prout(_("***Shields knocked out."))
2529                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2530                     else:
2531                         game.energy -= 2000.0
2532                     if game.energy <= 0:
2533                         finish(FNOVA)
2534                         return
2535                     # add in course nova contributes to kicking starship
2536                     bump += (game.sector-hits[mm]).sgn()
2537                 elif iquad == IHK: # kill klingon 
2538                     deadkl(neighbor, iquad, neighbor)
2539                 elif iquad in (IHC,IHS,IHR): # Damage/destroy big enemies 
2540                     for ll in range(len(game.enemies)):
2541                         if game.enemies[ll].kloc == neighbor:
2542                             break
2543                     game.enemies[ll].kpower -= 800.0 # If firepower is lost, die 
2544                     if game.enemies[ll].kpower <= 0.0:
2545                         deadkl(neighbor, iquad, neighbor)
2546                         break
2547                     newc = neighbor + neighbor - hits[mm]
2548                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2549                     if not newc.valid_sector():
2550                         # can't leave quadrant 
2551                         skip(1)
2552                         break
2553                     iquad1 = game.quad[newc.i][newc.j]
2554                     if iquad1 == IHBLANK:
2555                         proutn(_(", blasted into ") + crmena(False, IHBLANK, "sector", newc))
2556                         skip(1)
2557                         deadkl(neighbor, iquad, newc)
2558                         break
2559                     if iquad1 != IHDOT:
2560                         # can't move into something else 
2561                         skip(1)
2562                         break
2563                     proutn(_(", buffeted to Sector %s") % newc)
2564                     game.quad[neighbor.i][neighbor.j] = IHDOT
2565                     game.quad[newc.i][newc.j] = iquad
2566                     game.enemies[ll].move(newc)
2567     # Starship affected by nova -- kick it away. 
2568     dist = kount*0.1
2569     direc = course[3*(bump.i+1)+bump.j+2]
2570     if direc == 0.0:
2571         dist = 0.0
2572     if dist == 0.0:
2573         return
2574     course = course(bearing=direc, distance=dist)
2575     game.optime = course.time(warp=4)
2576     skip(1)
2577     prout(_("Force of nova displaces starship."))
2578     imove(course, noattack=True)
2579     game.optime = course.time(warp=4)
2580     return
2581         
2582 def supernova(w):
2583     "Star goes supernova."
2584     num = 0; npdead = 0
2585     if w != None: 
2586         nq = copy.copy(w)
2587     else:
2588         # Scheduled supernova -- select star at random. 
2589         stars = 0
2590         nq = coord()
2591         for nq.i in range(GALSIZE):
2592             for nq.j in range(GALSIZE):
2593                 stars += game.state.galaxy[nq.i][nq.j].stars
2594         if stars == 0:
2595             return # nothing to supernova exists 
2596         num = randrange(stars) + 1
2597         for nq.i in range(GALSIZE):
2598             for nq.j in range(GALSIZE):
2599                 num -= game.state.galaxy[nq.i][nq.j].stars
2600                 if num <= 0:
2601                     break
2602             if num <=0:
2603                 break
2604         if idebug:
2605             proutn("=== Super nova here?")
2606             if ja() == True:
2607                 nq = game.quadrant
2608     if not nq == game.quadrant or game.justin:
2609         # it isn't here, or we just entered (treat as enroute) 
2610         if communicating():
2611             skip(1)
2612             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2613             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2614     else:
2615         ns = coord()
2616         # we are in the quadrant! 
2617         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2618         for ns.i in range(QUADSIZE):
2619             for ns.j in range(QUADSIZE):
2620                 if game.quad[ns.i][ns.j]==IHSTAR:
2621                     num -= 1
2622                     if num==0:
2623                         break
2624             if num==0:
2625                 break
2626         skip(1)
2627         prouts(_("***RED ALERT!  RED ALERT!"))
2628         skip(1)
2629         prout(_("***Incipient supernova detected at Sector %s") % ns)
2630         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2631             proutn(_("Emergency override attempts t"))
2632             prouts("***************")
2633             skip(1)
2634             stars()
2635             game.alldone = True
2636     # destroy any Klingons in supernovaed quadrant
2637     kldead = game.state.galaxy[nq.i][nq.j].klingons
2638     game.state.galaxy[nq.i][nq.j].klingons = 0
2639     if nq == game.state.kscmdr:
2640         # did in the Supercommander! 
2641         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2642         game.iscate = False
2643         unschedule(FSCMOVE)
2644         unschedule(FSCDBAS)
2645     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2646     comkills = len(game.state.kcmdr) - len(survivors)
2647     game.state.kcmdr = survivors
2648     kldead -= comkills
2649     if not game.state.kcmdr:
2650         unschedule(FTBEAM)
2651     game.state.remkl -= kldead
2652     # destroy Romulans and planets in supernovaed quadrant 
2653     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2654     game.state.galaxy[nq.i][nq.j].romulans = 0
2655     game.state.nromrem -= nrmdead
2656     # Destroy planets 
2657     for loop in range(game.inplan):
2658         if game.state.planets[loop].quadrant == nq:
2659             game.state.planets[loop].pclass = "destroyed"
2660             npdead += 1
2661     # Destroy any base in supernovaed quadrant
2662     game.state.baseq = filter(lambda x: x != nq, game.state.baseq)
2663     # If starship caused supernova, tally up destruction 
2664     if w != None:
2665         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2666         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2667         game.state.nplankl += npdead
2668     # mark supernova in galaxy and in star chart 
2669     if game.quadrant == nq or communicating():
2670         game.state.galaxy[nq.i][nq.j].supernova = True
2671     # If supernova destroys last Klingons give special message 
2672     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2673         skip(2)
2674         if w == None:
2675             prout(_("Lucky you!"))
2676         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2677         finish(FWON)
2678         return
2679     # if some Klingons remain, continue or die in supernova 
2680     if game.alldone:
2681         finish(FSNOVAED)
2682     return
2683
2684 # Code from finish.c ends here.
2685
2686 def selfdestruct():
2687     "Self-destruct maneuver. Finish with a BANG!" 
2688     scanner.chew()
2689     if damaged(DCOMPTR):
2690         prout(_("Computer damaged; cannot execute destruct sequence."))
2691         return
2692     prouts(_("---WORKING---")); skip(1)
2693     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2694     prouts("   10"); skip(1)
2695     prouts("       9"); skip(1)
2696     prouts("          8"); skip(1)
2697     prouts("             7"); skip(1)
2698     prouts("                6"); skip(1)
2699     skip(1)
2700     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2701     skip(1)
2702     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2703     skip(1)
2704     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2705     skip(1)
2706     scanner.next()
2707     scanner.chew()
2708     if game.passwd != scanner.token:
2709         prouts(_("PASSWORD-REJECTED;"))
2710         skip(1)
2711         prouts(_("CONTINUITY-EFFECTED"))
2712         skip(2)
2713         return
2714     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2715     prouts("                   5"); skip(1)
2716     prouts("                      4"); skip(1)
2717     prouts("                         3"); skip(1)
2718     prouts("                            2"); skip(1)
2719     prouts("                              1"); skip(1)
2720     if withprob(0.15):
2721         prouts(_("GOODBYE-CRUEL-WORLD"))
2722         skip(1)
2723     kaboom()
2724
2725 def kaboom():
2726     stars()
2727     if game.ship==IHE:
2728         prouts("***")
2729     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2730     skip(1)
2731     stars()
2732     skip(1)
2733     if len(game.enemies) != 0:
2734         whammo = 25.0 * game.energy
2735         l=1
2736         while l <= len(game.enemies):
2737             if game.enemies[l].kpower*game.enemies[l].kdist <= whammo: 
2738                 deadkl(game.enemies[l].kloc, game.quad[game.enemies[l].kloc.i][game.enemies[l].kloc.j], game.enemies[l].kloc)
2739             l += 1
2740     finish(FDILITHIUM)
2741                                 
2742 def killrate():
2743     "Compute our rate of kils over time."
2744     elapsed = game.state.date - game.indate
2745     if elapsed == 0:    # Avoid divide-by-zero error if calculated on turn 0
2746         return 0
2747     else:
2748         starting = (game.inkling + game.incom + game.inscom)
2749         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2750         return (starting - remaining)/elapsed
2751
2752 def badpoints():
2753     "Compute demerits."
2754     badpt = 5.0*game.state.starkl + \
2755             game.casual + \
2756             10.0*game.state.nplankl + \
2757             300*game.state.nworldkl + \
2758             45.0*game.nhelp +\
2759             100.0*game.state.basekl +\
2760             3.0*game.abandoned
2761     if game.ship == IHF:
2762         badpt += 100.0
2763     elif game.ship == None:
2764         badpt += 200.0
2765     return badpt
2766
2767 def finish(ifin):
2768     # end the game, with appropriate notfications 
2769     igotit = False
2770     game.alldone = True
2771     skip(3)
2772     prout(_("It is stardate %.1f.") % game.state.date)
2773     skip(1)
2774     if ifin == FWON: # Game has been won
2775         if game.state.nromrem != 0:
2776             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2777                   game.state.nromrem)
2778
2779         prout(_("You have smashed the Klingon invasion fleet and saved"))
2780         prout(_("the Federation."))
2781         game.gamewon = True
2782         if game.alive:
2783             badpt = badpoints()
2784             if badpt < 100.0:
2785                 badpt = 0.0     # Close enough!
2786             # killsPerDate >= RateMax
2787             if game.state.date-game.indate < 5.0 or \
2788                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2789                 skip(1)
2790                 prout(_("In fact, you have done so well that Starfleet Command"))
2791                 if game.skill == SKILL_NOVICE:
2792                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2793                 elif game.skill == SKILL_FAIR:
2794                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2795                 elif game.skill == SKILL_GOOD:
2796                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2797                 elif game.skill == SKILL_EXPERT:
2798                     prout(_("promotes you to Commodore Emeritus."))
2799                     skip(1)
2800                     prout(_("Now that you think you're really good, try playing"))
2801                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
2802                 elif game.skill == SKILL_EMERITUS:
2803                     skip(1)
2804                     proutn(_("Computer-  "))
2805                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
2806                     skip(2)
2807                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
2808                     skip(1)
2809                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2810                     skip(1)
2811                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2812                     skip(1)
2813                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2814                     skip(1)
2815                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
2816                     skip(2)
2817                     prout(_("Now you can retire and write your own Star Trek game!"))
2818                     skip(1)
2819                 elif game.skill >= SKILL_EXPERT:
2820                     if game.thawed and not idebug:
2821                         prout(_("You cannot get a citation, so..."))
2822                     else:
2823                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
2824                         scanner.chew()
2825                         if ja() == True:
2826                             igotit = True
2827             # Only grant long life if alive (original didn't!)
2828             skip(1)
2829             prout(_("LIVE LONG AND PROSPER."))
2830         score()
2831         if igotit:
2832             plaque()        
2833         return
2834     elif ifin == FDEPLETE: # Federation Resources Depleted
2835         prout(_("Your time has run out and the Federation has been"))
2836         prout(_("conquered.  Your starship is now Klingon property,"))
2837         prout(_("and you are put on trial as a war criminal.  On the"))
2838         proutn(_("basis of your record, you are "))
2839         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
2840             prout(_("acquitted."))
2841             skip(1)
2842             prout(_("LIVE LONG AND PROSPER."))
2843         else:
2844             prout(_("found guilty and"))
2845             prout(_("sentenced to death by slow torture."))
2846             game.alive = False
2847         score()
2848         return
2849     elif ifin == FLIFESUP:
2850         prout(_("Your life support reserves have run out, and"))
2851         prout(_("you die of thirst, starvation, and asphyxiation."))
2852         prout(_("Your starship is a derelict in space."))
2853     elif ifin == FNRG:
2854         prout(_("Your energy supply is exhausted."))
2855         skip(1)
2856         prout(_("Your starship is a derelict in space."))
2857     elif ifin == FBATTLE:
2858         prout(_("The %s has been destroyed in battle.") % crmshp())
2859         skip(1)
2860         prout(_("Dulce et decorum est pro patria mori."))
2861     elif ifin == FNEG3:
2862         prout(_("You have made three attempts to cross the negative energy"))
2863         prout(_("barrier which surrounds the galaxy."))
2864         skip(1)
2865         prout(_("Your navigation is abominable."))
2866         score()
2867     elif ifin == FNOVA:
2868         prout(_("Your starship has been destroyed by a nova."))
2869         prout(_("That was a great shot."))
2870         skip(1)
2871     elif ifin == FSNOVAED:
2872         prout(_("The %s has been fried by a supernova.") % crmshp())
2873         prout(_("...Not even cinders remain..."))
2874     elif ifin == FABANDN:
2875         prout(_("You have been captured by the Klingons. If you still"))
2876         prout(_("had a starbase to be returned to, you would have been"))
2877         prout(_("repatriated and given another chance. Since you have"))
2878         prout(_("no starbases, you will be mercilessly tortured to death."))
2879     elif ifin == FDILITHIUM:
2880         prout(_("Your starship is now an expanding cloud of subatomic particles"))
2881     elif ifin == FMATERIALIZE:
2882         prout(_("Starbase was unable to re-materialize your starship."))
2883         prout(_("Sic transit gloria mundi"))
2884     elif ifin == FPHASER:
2885         prout(_("The %s has been cremated by its own phasers.") % crmshp())
2886     elif ifin == FLOST:
2887         prout(_("You and your landing party have been"))
2888         prout(_("converted to energy, disipating through space."))
2889     elif ifin == FMINING:
2890         prout(_("You are left with your landing party on"))
2891         prout(_("a wild jungle planet inhabited by primitive cannibals."))
2892         skip(1)
2893         prout(_("They are very fond of \"Captain Kirk\" soup."))
2894         skip(1)
2895         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2896     elif ifin == FDPLANET:
2897         prout(_("You and your mining party perish."))
2898         skip(1)
2899         prout(_("That was a great shot."))
2900         skip(1)
2901     elif ifin == FSSC:
2902         prout(_("The Galileo is instantly annihilated by the supernova."))
2903         prout(_("You and your mining party are atomized."))
2904         skip(1)
2905         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2906         prout(_("joins the Romulans, wreaking terror on the Federation."))
2907     elif ifin == FPNOVA:
2908         prout(_("You and your mining party are atomized."))
2909         skip(1)
2910         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2911         prout(_("joins the Romulans, wreaking terror on the Federation."))
2912     elif ifin == FSTRACTOR:
2913         prout(_("The shuttle craft Galileo is also caught,"))
2914         prout(_("and breaks up under the strain."))
2915         skip(1)
2916         prout(_("Your debris is scattered for millions of miles."))
2917         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2918     elif ifin == FDRAY:
2919         prout(_("The mutants attack and kill Spock."))
2920         prout(_("Your ship is captured by Klingons, and"))
2921         prout(_("your crew is put on display in a Klingon zoo."))
2922     elif ifin == FTRIBBLE:
2923         prout(_("Tribbles consume all remaining water,"))
2924         prout(_("food, and oxygen on your ship."))
2925         skip(1)
2926         prout(_("You die of thirst, starvation, and asphyxiation."))
2927         prout(_("Your starship is a derelict in space."))
2928     elif ifin == FHOLE:
2929         prout(_("Your ship is drawn to the center of the black hole."))
2930         prout(_("You are crushed into extremely dense matter."))
2931     elif ifin == FCREW:
2932         prout(_("Your last crew member has died."))
2933     if game.ship == IHF:
2934         game.ship = None
2935     elif game.ship == IHE:
2936         game.ship = IHF
2937     game.alive = False
2938     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
2939         goodies = game.state.remres/game.inresor
2940         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
2941         if goodies/baddies >= randreal(1.0, 1.5):
2942             prout(_("As a result of your actions, a treaty with the Klingon"))
2943             prout(_("Empire has been signed. The terms of the treaty are"))
2944             if goodies/baddies >= randreal(3.0):
2945                 prout(_("favorable to the Federation."))
2946                 skip(1)
2947                 prout(_("Congratulations!"))
2948             else:
2949                 prout(_("highly unfavorable to the Federation."))
2950         else:
2951             prout(_("The Federation will be destroyed."))
2952     else:
2953         prout(_("Since you took the last Klingon with you, you are a"))
2954         prout(_("martyr and a hero. Someday maybe they'll erect a"))
2955         prout(_("statue in your memory. Rest in peace, and try not"))
2956         prout(_("to think about pigeons."))
2957         game.gamewon = True
2958     score()
2959
2960 def score():
2961     "Compute player's score."
2962     timused = game.state.date - game.indate
2963     iskill = game.skill
2964     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
2965         timused = 5.0
2966     perdate = killrate()
2967     ithperd = 500*perdate + 0.5
2968     iwon = 0
2969     if game.gamewon:
2970         iwon = 100*game.skill
2971     if game.ship == IHE: 
2972         klship = 0
2973     elif game.ship == IHF: 
2974         klship = 1
2975     else:
2976         klship = 2
2977     if not game.gamewon:
2978         game.state.nromrem = 0 # None captured if no win
2979     iscore = 10*(game.inkling - game.state.remkl) \
2980              + 50*(game.incom - len(game.state.kcmdr)) \
2981              + ithperd + iwon \
2982              + 20*(game.inrom - game.state.nromrem) \
2983              + 200*(game.inscom - game.state.nscrem) \
2984              - game.state.nromrem \
2985              - badpoints()
2986     if not game.alive:
2987         iscore -= 200
2988     skip(2)
2989     prout(_("Your score --"))
2990     if game.inrom - game.state.nromrem:
2991         prout(_("%6d Romulans destroyed                 %5d") %
2992               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
2993     if game.state.nromrem:
2994         prout(_("%6d Romulans captured                  %5d") %
2995               (game.state.nromrem, game.state.nromrem))
2996     if game.inkling - game.state.remkl:
2997         prout(_("%6d ordinary Klingons destroyed        %5d") %
2998               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
2999     if game.incom - len(game.state.kcmdr):
3000         prout(_("%6d Klingon commanders destroyed       %5d") %
3001               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3002     if game.inscom - game.state.nscrem:
3003         prout(_("%6d Super-Commander destroyed          %5d") %
3004               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3005     if ithperd:
3006         prout(_("%6.2f Klingons per stardate              %5d") %
3007               (perdate, ithperd))
3008     if game.state.starkl:
3009         prout(_("%6d stars destroyed by your action     %5d") %
3010               (game.state.starkl, -5*game.state.starkl))
3011     if game.state.nplankl:
3012         prout(_("%6d planets destroyed by your action   %5d") %
3013               (game.state.nplankl, -10*game.state.nplankl))
3014     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3015         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3016               (game.state.nplankl, -300*game.state.nworldkl))
3017     if game.state.basekl:
3018         prout(_("%6d bases destroyed by your action     %5d") %
3019               (game.state.basekl, -100*game.state.basekl))
3020     if game.nhelp:
3021         prout(_("%6d calls for help from starbase       %5d") %
3022               (game.nhelp, -45*game.nhelp))
3023     if game.casual:
3024         prout(_("%6d casualties incurred                %5d") %
3025               (game.casual, -game.casual))
3026     if game.abandoned:
3027         prout(_("%6d crew abandoned in space            %5d") %
3028               (game.abandoned, -3*game.abandoned))
3029     if klship:
3030         prout(_("%6d ship(s) lost or destroyed          %5d") %
3031               (klship, -100*klship))
3032     if not game.alive:
3033         prout(_("Penalty for getting yourself killed        -200"))
3034     if game.gamewon:
3035         proutn(_("Bonus for winning "))
3036         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3037         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3038         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3039         elif game.skill ==  SKILL_EXPERT:       proutn(_("Expert game  "))
3040         elif game.skill ==  SKILL_EMERITUS:     proutn(_("Emeritus game"))
3041         prout("           %5d" % iwon)
3042     skip(1)
3043     prout(_("TOTAL SCORE                               %5d") % iscore)
3044
3045 def plaque():
3046     "Emit winner's commemmorative plaque." 
3047     skip(2)
3048     while True:
3049         proutn(_("File or device name for your plaque: "))
3050         winner = cgetline()
3051         try:
3052             fp = open(winner, "w")
3053             break
3054         except IOError:
3055             prout(_("Invalid name."))
3056
3057     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3058     winner = cgetline()
3059     # The 38 below must be 64 for 132-column paper 
3060     nskip = 38 - len(winner)/2
3061     fp.write("\n\n\n\n")
3062     # --------DRAW ENTERPRISE PICTURE. 
3063     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3064     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3065     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3066     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3067     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3068     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3069     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3070     fp.write("                                      EEE           E          E            E  E\n")
3071     fp.write("                                                       E         E          E  E\n")
3072     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3073     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3074     fp.write("                                                    :E    :                 EEEE       E\n")
3075     fp.write("                                                   .-E   -:-----                       E\n")
3076     fp.write("                                                    :E    :                            E\n")
3077     fp.write("                                                      EE  :                    EEEEEEEE\n")
3078     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3079     fp.write("\n\n\n")
3080     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3081     fp.write("\n\n\n\n")
3082     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3083     fp.write("\n")
3084     fp.write(_("                                                Starfleet Command bestows to you\n"))
3085     fp.write("\n")
3086     fp.write("%*s%s\n\n" % (nskip, "", winner))
3087     fp.write(_("                                                           the rank of\n\n"))
3088     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3089     fp.write("                                                          ")
3090     if game.skill ==  SKILL_EXPERT:
3091         fp.write(_(" Expert level\n\n"))
3092     elif game.skill == SKILL_EMERITUS:
3093         fp.write(_("Emeritus level\n\n"))
3094     else:
3095         fp.write(_(" Cheat level\n\n"))
3096     timestring = time.ctime()
3097     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3098                     (timestring+4, timestring+20, timestring+11))
3099     fp.write(_("                                                        Your score:  %d\n\n") % iscore)
3100     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % perdate)
3101     fp.close()
3102
3103 # Code from io.c begins here
3104
3105 rows = linecount = 0    # for paging 
3106 stdscr = None
3107 replayfp = None
3108 fullscreen_window = None
3109 srscan_window     = None
3110 report_window     = None
3111 status_window     = None
3112 lrscan_window     = None
3113 message_window    = None
3114 prompt_window     = None
3115 curwnd = None
3116
3117 def iostart():
3118     global stdscr, rows
3119     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3120     gettext.textdomain("sst")
3121     if not (game.options & OPTION_CURSES):
3122         ln_env = os.getenv("LINES")
3123         if ln_env:
3124             rows = ln_env
3125         else:
3126             rows = 25
3127     else:
3128         stdscr = curses.initscr()
3129         stdscr.keypad(True)
3130         curses.nonl()
3131         curses.cbreak()
3132         global fullscreen_window, srscan_window, report_window, status_window
3133         global lrscan_window, message_window, prompt_window
3134         (rows, columns)   = stdscr.getmaxyx()
3135         fullscreen_window = stdscr
3136         srscan_window     = curses.newwin(12, 25, 0,       0)
3137         report_window     = curses.newwin(11, 0,  1,       25)
3138         status_window     = curses.newwin(10, 0,  1,       39)
3139         lrscan_window     = curses.newwin(5,  0,  0,       64) 
3140         message_window    = curses.newwin(0,  0,  12,      0)
3141         prompt_window     = curses.newwin(1,  0,  rows-2,  0) 
3142         message_window.scrollok(True)
3143         setwnd(fullscreen_window)
3144
3145 def ioend():
3146     "Wrap up I/O."
3147     if game.options & OPTION_CURSES:
3148         stdscr.keypad(False)
3149         curses.echo()
3150         curses.nocbreak()
3151         curses.endwin()
3152
3153 def waitfor():
3154     "Wait for user action -- OK to do nothing if on a TTY"
3155     if game.options & OPTION_CURSES:
3156         stdscr.getch()
3157
3158 def announce():
3159     skip(1)
3160     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3161     skip(1)
3162
3163 def pause_game():
3164     if game.skill > SKILL_FAIR:
3165         prompt = _("[CONTINUE?]")
3166     else:
3167         prompt = _("[PRESS ENTER TO CONTINUE]")
3168
3169     if game.options & OPTION_CURSES:
3170         drawmaps(0)
3171         setwnd(prompt_window)
3172         prompt_window.clear()
3173         prompt_window.addstr(prompt)
3174         prompt_window.getstr()
3175         prompt_window.clear()
3176         prompt_window.refresh()
3177         setwnd(message_window)
3178     else:
3179         global linecount
3180         sys.stdout.write('\n')
3181         proutn(prompt)
3182         raw_input()
3183         for j in range(rows):
3184             sys.stdout.write('\n')
3185         linecount = 0
3186
3187 def skip(i):
3188     "Skip i lines.  Pause game if this would cause a scrolling event."
3189     for dummy in range(i):
3190         if game.options & OPTION_CURSES:
3191             (y, x) = curwnd.getyx()
3192             (my, mx) = curwnd.getmaxyx()
3193             if curwnd == message_window and y >= my - 3:
3194                 pause_game()
3195                 clrscr()
3196             else:
3197                 try:
3198                     curwnd.move(y+1, 0)
3199                 except curses.error:
3200                     pass
3201         else:
3202             global linecount
3203             linecount += 1
3204             if rows and linecount >= rows:
3205                 pause_game()
3206             else:
3207                 sys.stdout.write('\n')
3208
3209 def proutn(line):
3210     "Utter a line with no following line feed."
3211     if game.options & OPTION_CURSES:
3212         curwnd.addstr(line)
3213         curwnd.refresh()
3214     else:
3215         sys.stdout.write(line)
3216         sys.stdout.flush()
3217
3218 def prout(line):
3219     proutn(line)
3220     skip(1)
3221
3222 def prouts(line):
3223     "Emit slowly!" 
3224     for c in line:
3225         if not replayfp or replayfp.closed:     # Don't slow down replays
3226             time.sleep(0.03)
3227         proutn(c)
3228         if game.options & OPTION_CURSES:
3229             curwnd.refresh()
3230         else:
3231             sys.stdout.flush()
3232     if not replayfp or replayfp.closed:
3233         time.sleep(0.03)
3234
3235 def cgetline():
3236     "Get a line of input."
3237     if game.options & OPTION_CURSES:
3238         line = curwnd.getstr() + "\n"
3239         curwnd.refresh()
3240     else:
3241         if replayfp and not replayfp.closed:
3242             while True:
3243                 line = replayfp.readline()
3244                 proutn(line)
3245                 if line == '':
3246                     prout("*** Replay finished")
3247                     replayfp.close()
3248                     break
3249                 elif line[0] != "#":
3250                     break
3251         else:
3252             line = raw_input() + "\n"
3253     if logfp:
3254         logfp.write(line)
3255     return line
3256
3257 def setwnd(wnd):
3258     "Change windows -- OK for this to be a no-op in tty mode."
3259     global curwnd
3260     if game.options & OPTION_CURSES:
3261         curwnd = wnd
3262         curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
3263
3264 def clreol():
3265     "Clear to end of line -- can be a no-op in tty mode" 
3266     if game.options & OPTION_CURSES:
3267         curwnd.clrtoeol()
3268         curwnd.refresh()
3269
3270 def clrscr():
3271     "Clear screen -- can be a no-op in tty mode."
3272     global linecount
3273     if game.options & OPTION_CURSES:
3274        curwnd.clear()
3275        curwnd.move(0, 0)
3276        curwnd.refresh()
3277     linecount = 0
3278     
3279 def highvideo():
3280     "Set highlight video, if this is reasonable."
3281     if game.options & OPTION_CURSES:
3282         curwnd.attron(curses.A_REVERSE)
3283  
3284 #
3285 # Things past this point have policy implications.
3286
3287
3288 def drawmaps(mode):
3289     "Hook to be called after moving to redraw maps."
3290     if game.options & OPTION_CURSES:
3291         if mode == 1:
3292             sensor()
3293         setwnd(srscan_window)
3294         curwnd.move(0, 0)
3295         srscan()
3296         if mode != 2:
3297             setwnd(status_window)
3298             status_window.clear()
3299             status_window.move(0, 0)