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