Better wrapup on replays.
[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
6 original dating back to 1973.  Beautiful Python it is not.  But it
7 works.
8
9 Dave Matuszek says:
10
11 SRSCAN, MOVE, PHASERS, CALL, STATUS, IMPULSE, PHOTONS, ABANDON,
12 LRSCAN, WARP, SHIELDS, DESTRUCT, CHART, REST, DOCK, QUIT, and DAMAGE
13 were in the original non-"super" version of UT FORTRAN Star Trek.
14
15 Tholians were not in the original. Dave is dubious about their merits.
16 (They are now controlled by OPTION_THOLIAN and turned off if the game
17 type is "plain".)
18
19 Planets and dilithium crystals were not in the original.  Dave is OK
20 with this idea. (It's now controlled by OPTION_PLANETS and turned 
21 off if the game type is "plain".)
22
23 Dave says the bit about the Galileo getting turned into a
24 McDonald's is "consistant with our original vision".  (This has been
25 left permanently enabled, as it can only happen if OPTION_PLANETS
26 is on.)
27
28 Dave also says the Space Thingy should not be preserved across saved
29 games, so you can't prove to others that you've seen it.  He says it
30 shouldn't fire back, either.  It should do nothing except scream and
31 disappear when hit by photon torpedos.  It's OK that it may move
32 when attacked, but it didn't in the original.  (Whether the Thingy
33 can fire back is now controlled by OPTION_THINGY and turned off if the
34 game type is "plain" or "almy".  The no-save behavior has been restored.)
35
36 The Faerie Queen, black holes, and time warping were in the original.
37
38 Here are Tom Almy's changes:
39
40 In early 1997, I got the bright idea to look for references to
41 "Super Star Trek" on the World Wide Web. There weren't many hits,
42 but there was one that came up with 1979 Fortran sources! This
43 version had a few additional features that mine didn't have,
44 however mine had some feature it didn't have. So I merged its
45 features that I liked. I also took a peek at the DECUS version (a
46 port, less sources, to the PDP-10), and some other variations.
47
48 1, Compared to the original UT version, I've changed the "help"
49 command to "call" and the "terminate" command to "quit" to better
50 match user expectations. The DECUS version apparently made those
51 changes as well as changing "freeze" to "save". However I like
52 "freeze".  (Both "freeze" and "save" work in SST2K.)
53
54 2. The experimental deathray originally had only a 5% chance of
55 success, but could be used repeatedly. I guess after a couple
56 years of use, it was less "experimental" because the 1979
57 version had a 70% success rate. However it was prone to breaking
58 after use. I upgraded the deathray, but kept the original set of
59 failure modes (great humor!).  (Now controlled by OPTION_DEATHRAY
60 and turned off if game type is "plain".)
61
62 3. The 1979 version also mentions srscan and lrscan working when
63 docked (using the starbase's scanners), so I made some changes here
64 to do this (and indicating that fact to the player), and then realized
65 the base would have a subspace radio as well -- doing a Chart when docked
66 updates the star chart, and all radio reports will be heard. The Dock
67 command will also give a report if a base is under attack.
68
69 4. Tholian Web from the 1979 version.  (Now controlled by
70 OPTION_THOLIAN and turned off if game type is "plain".)
71
72 5. Enemies can ram the Enterprise. (Now controlled by OPTION_RAMMING
73 and turned off if game type is "plain".)
74
75 6. Regular Klingons and Romulans can move in Expert and Emeritus games. 
76 This code could use improvement. (Now controlled by OPTION_MVBADDY
77 and turned off if game type is "plain".)
78
79 7. The deep-space probe feature from the DECUS version.  (Now controlled
80 by OPTION_PROBE and turned off if game type is "plain").
81
82 8. 'emexit' command from the 1979 version.
83
84 9. Bugfix: Klingon commander movements are no longer reported if long-range 
85 sensors are damaged.
86
87 10. Bugfix: Better base positioning at startup (more spread out).
88 That made sense to add because most people abort games with 
89 bad base placement.
90
91 In June 2002, I fixed two known bugs and a documentation typo.
92 In June 2004 I fixed a number of bugs involving: 1) parsing invalid
93 numbers, 2) manual phasers when SR scan is damaged and commander is
94 present, 3) time warping into the future, 4) hang when moving
95 klingons in crowded quadrants.  (These fixes are in SST2K.)
96
97 Here are Stas Sergeev's changes:
98
99 1. The Space Thingy can be shoved, if you ram it, and can fire back if 
100 fired upon. (Now controlled by OPTION_THINGY and turned off if game 
101 type is "plain" or "almy".)
102
103 2. When you are docked, base covers you with an almost invincible shield. 
104 (A commander can still ram you, or a Romulan can destroy the base,
105 or a SCom can even succeed with direct attack IIRC, but this rarely 
106 happens.)  (Now controlled by OPTION_BASE and turned off if game 
107 type is "plain" or "almy".)
108
109 3. Ramming a black hole is no longer instant death.  There is a
110 chance you might get timewarped instead. (Now controlled by 
111 OPTION_BLKHOLE and turned off if game type is "plain" or "almy".)
112
113 4. The Tholian can be hit with phasers.
114
115 5. SCom can't escape from you if no more enemies remain 
116 (without this, chasing SCom can take an eternity).
117
118 6. Probe target you enter is now the destination quadrant. Before I don't 
119 remember what it was, but it was something I had difficulty using.
120
121 7. Secret password is now autogenerated.
122
123 8. "Plaque" is adjusted for A4 paper :-)
124
125 9. Phasers now tells you how much energy needed, but only if the computer 
126 is alive.
127
128 10. Planets are auto-scanned when you enter the quadrant.
129
130 11. Mining or using crystals in presense of enemy now yields an attack.
131 There are other minor adjustments to what yields an attack
132 and what does not.
133
134 12. "freeze" command reverts to "save", most people will understand this
135 better anyway. (SST2K recognizes both.)
136
137 13. Screen-oriented interface, with sensor scans always up.  (SST2K
138 supports both screen-oriented and TTY modes.)
139
140 Eric Raymond's changes:
141
142 Mainly, I translated this C code out of FORTRAN into C -- created #defines
143 for a lot of magic numbers and refactored the heck out of it.
144
145 1. "sos" and "call" becomes "mayday", "freeze" and "save" are both good.
146
147 2. Status report now indicates when dilithium crystals are on board.
148
149 3. Per Dave Matuszek's remarks, Thingy state is never saved across games.
150
151 4. Added game option selection so you can play a close (but not bug-for-
152 bug identical) approximation of older versions.
153
154 5. Half the quadrants now have inhabited planets, from which one 
155 cannot mine dilithium (there will still be the same additional number
156 of dilithium-bearing planets).  Torpedoing an inhabited world is *bad*.
157 There is BSD-Trek-like logic for Klingons to attack and enslave 
158 inhabited worlds, producing more ships (only is skill is 'good' or 
159 better). (Controlled by OPTION_WORLDS and turned off if game 
160 type is "plain" or "almy".)
161
162 6. User input is now logged so we can do regression testing.
163
164 7. More BSD-Trek features: You can now lose if your entire crew
165 dies in battle.  When abandoning ship in a game with inhabited
166 worlds enabled, they must have one in the quadrant to beam down
167 to; otherwise they die in space and this counts heavily against
168 your score.  Docking at a starbase replenishes your crew.
169
170 8. Still more BSD-Trek: we now have a weighted damage table.
171 Also, the nav subsystem (enabling automatic course
172 setting) can be damaged separately from the main computer (which
173 handles weapons targeting, ETA calculation, and self-destruct).
174
175 After these features were added, I translated this into Python and added
176 more:
177
178 9. A long-range scan is done silently whenever you call CHART; thus
179 the LRSCAN command is no longer needed.  (Controlled by OPTION_AUTOSCAN
180 and turned off if game type is "plain" or "almy".)
181 """
182 import os, sys, math, curses, time, readline, cPickle, random, copy
183
184 SSTDOC          = "/usr/share/doc/sst/sst.doc"
185 DOC_NAME        = "sst.doc"
186
187 # Stub to be replaced
188 def _(str): return str
189
190 PHASEFAC        = 2.0
191 GALSIZE         = 8
192 NINHAB          = (GALSIZE * GALSIZE / 2)
193 MAXUNINHAB      = 10
194 PLNETMAX        = (NINHAB + MAXUNINHAB)
195 QUADSIZE        = 10
196 BASEMIN         = 2
197 BASEMAX         = (GALSIZE * GALSIZE / 12)
198 MAXKLGAME       = 127
199 MAXKLQUAD       = 9
200 FULLCREW        = 428   # BSD Trek was 387, that's wrong 
201 FOREVER         = 1e30
202 MAXBURST        = 3
203 MINCMDR         = 10
204
205 # These functions hide the difference between 0-origin and 1-origin addressing.
206 def VALID_QUADRANT(x, y):       return ((x)>=0 and (x)<GALSIZE and (y)>=0 and (y)<GALSIZE)
207 def VALID_SECTOR(x, y): return ((x)>=0 and (x)<QUADSIZE and (y)>=0 and (y)<QUADSIZE)
208
209 # How to represent features
210 IHR = 'R',
211 IHK = 'K',
212 IHC = 'C',
213 IHS = 'S',
214 IHSTAR = '*',
215 IHP = 'P',
216 IHW = '@',
217 IHB = 'B',
218 IHBLANK = ' ',
219 IHDOT = '.',
220 IHQUEST = '?',
221 IHE = 'E',
222 IHF = 'F',
223 IHT = 'T',
224 IHWEB = '#',
225 IHMATER0 = '-',
226 IHMATER1 = 'o',
227 IHMATER2 = '0'
228
229 class coord:
230     def __init__(self, x=None, y=None):
231         self.i = x
232         self.j = y
233     def invalidate(self):
234         self.i = self.j = None
235     def is_valid(self):
236         return self.i != None and self.j != None
237     def __eq__(self, other):
238         return other != None and self.i == other.i and self.j == other.j
239     def __add__(self, other):
240         return coord(self.i+other.i, self.j+other.j)
241     def __sub__(self, other):
242         return coord(self.i-other.i, self.j-other.j)
243     def __mul__(self, other):
244         return coord(self.i*other, self.j*other)
245     def __rmul__(self, other):
246         return coord(self.i*other, self.j*other)
247     def __div__(self, other):
248         return coord(self.i/other, self.j/other)
249     def __rdiv__(self, other):
250         return coord(self.i/other, self.j/other)
251     def snaptogrid(self):
252         return coord(int(round(self.i)), int(round(self.j)))
253     def distance(self, other=None):
254         if not other: other = coord(0, 0)
255         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
256     def bearing(self, other=None):
257         if not other: other = coord(0, 0)
258         return 1.90985*math.atan2(self.j-other.j, self.i-other.i)
259     def sgn(self):
260         s = coord()
261         if self.i == 0:
262             s.i = 0
263         else:
264             s.i = self.i / abs(self.i)
265         if self.j == 0:
266             s.j = 0
267         else:
268             s.j = self.j / abs(self.j)
269         return s
270     def scatter(self):
271         s = coord()
272         s.i = self.i + randrange(-1, 2)
273         s.j = self.j + randrange(-1, 2)
274         return s
275     def __hash__(self):
276         return hash((x, y))
277     def __str__(self):
278         if self.i == None or self.j == None:
279             return "Nowhere"
280         return "%s - %s" % (self.i+1, self.j+1)
281     __repr__ = __str__
282
283 class planet:
284     def __init__(self):
285         self.name = None        # string-valued if inhabited
286         self.quadrant = coord() # quadrant located
287         self.pclass = None      # could be ""M", "N", "O", or "destroyed"
288         self.crystals = "absent"# could be "mined", "present", "absent"
289         self.known = "unknown"  # could be "unknown", "known", "shuttle_down"
290         self.inhabited = False  # is it inhabites?
291     def __str__(self):
292         return self.name
293
294 class quadrant:
295     def __init__(self):
296         self.stars = 0
297         self.planet = None
298         self.starbase = False
299         self.klingons = 0
300         self.romulans = 0
301         self.supernova = False
302         self.charted = False
303         self.status = "secure"  # Could be "secure", "distressed", "enslaved"
304
305 class page:
306     def __init__(self):
307         self.stars = None
308         self.starbase = None
309         self.klingons = None
310
311 def fill2d(size, fillfun):
312     "Fill an empty list in 2D."
313     lst = []
314     for i in range(size):
315         lst.append([]) 
316         for j in range(size):
317             lst[i].append(fillfun(i, j))
318     return lst
319
320 class snapshot:
321     def __init__(self):
322         self.snap = False       # snapshot taken
323         self.crew = 0           # crew complement
324         self.remkl = 0          # remaining klingons
325         self.nscrem = 0         # remaining super commanders
326         self.starkl = 0         # destroyed stars
327         self.basekl = 0         # destroyed bases
328         self.nromrem = 0        # Romulans remaining
329         self.nplankl = 0        # destroyed uninhabited planets
330         self.nworldkl = 0       # destroyed inhabited planets
331         self.planets = []       # Planet information
332         self.date = 0.0         # stardate
333         self.remres = 0         # remaining resources
334         self.remtime = 0        # remaining time
335         self.baseq = []         # Base quadrant coordinates
336         self.kcmdr = []         # Commander quadrant coordinates
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         motion = (loc != self.kloc)
428         if self.kloc.i is not None and self.kloc.j is not None:
429             if motion:
430                 if self.type == IHT:
431                     game.quad[self.kloc.i][self.kloc.j] = IHWEB
432                 else:
433                     game.quad[self.kloc.i][self.kloc.j] = IHDOT
434         if loc:
435             self.kloc = copy.copy(loc)
436             game.quad[self.kloc.i][self.kloc.j] = self.type
437             self.kdist = self.kavgd = (game.sector - loc).distance()
438         else:
439             self.kloc = coord()
440             self.kdist = self.kavgd = None
441             game.enemies.remove(self)
442         return motion
443     def __repr__(self):
444         return "<%s,%s.%f>" % (self.type, self.kloc, self.kpower)       # For debugging
445
446 class gamestate:
447     def __init__(self):
448         self.options = None     # Game options
449         self.state = snapshot() # A snapshot structure
450         self.snapsht = snapshot()       # Last snapshot taken for time-travel purposes
451         self.quad = None        # contents of our quadrant
452         self.damage = [0.0] * NDEVICES  # damage encountered
453         self.future = []                # future events
454         for i in range(NEVENTS):
455             self.future.append(event())
456         self.passwd  = None;            # Self Destruct password
457         self.enemies = []
458         self.quadrant = None    # where we are in the large
459         self.sector = None      # where we are in the small
460         self.tholian = None     # Tholian enemy object
461         self.base = None        # position of base in current quadrant
462         self.battle = None      # base coordinates being attacked
463         self.plnet = None       # location of planet in quadrant
464         self.probec = None      # current probe quadrant
465         self.gamewon = False    # Finished!
466         self.ididit = False     # action taken -- allows enemy to attack
467         self.alive = False      # we are alive (not killed)
468         self.justin = False     # just entered quadrant
469         self.shldup = False     # shields are up
470         self.shldchg = False    # shield is changing (affects efficiency)
471         self.iscate = False     # super commander is here
472         self.ientesc = False    # attempted escape from supercommander
473         self.resting = False    # rest time
474         self.icraft = False     # Kirk in Galileo
475         self.landed = False     # party on planet (true), on ship (false)
476         self.alldone = False    # game is now finished
477         self.neutz = False      # Romulan Neutral Zone
478         self.isarmed = False    # probe is armed
479         self.inorbit = False    # orbiting a planet
480         self.imine = False      # mining
481         self.icrystl = False    # dilithium crystals aboard
482         self.iseenit = False    # seen base attack report
483         self.thawed = False     # thawed game
484         self.condition = None   # "green", "yellow", "red", "docked", "dead"
485         self.iscraft = None     # "onship", "offship", "removed"
486         self.skill = None       # Player skill level
487         self.inkling = 0        # initial number of klingons
488         self.inbase = 0         # initial number of bases
489         self.incom = 0          # initial number of commanders
490         self.inscom = 0         # initial number of commanders
491         self.inrom = 0          # initial number of commanders
492         self.instar = 0         # initial stars
493         self.intorps = 0        # initial/max torpedoes
494         self.torps = 0          # number of torpedoes
495         self.ship = 0           # ship type -- 'E' is Enterprise
496         self.abandoned = 0      # count of crew abandoned in space
497         self.length = 0         # length of game
498         self.klhere = 0         # klingons here
499         self.casual = 0         # causalties
500         self.nhelp = 0          # calls for help
501         self.nkinks = 0         # count of energy-barrier crossings
502         self.iplnet = None      # planet # in quadrant
503         self.inplan = 0         # initial planets
504         self.irhere = 0         # Romulans in quadrant
505         self.isatb = 0          # =1 if super commander is attacking base
506         self.tourn = None       # tournament number
507         self.proben = 0         # number of moves for probe
508         self.nprobes = 0        # number of probes available
509         self.inresor = 0.0      # initial resources
510         self.intime = 0.0       # initial time
511         self.inenrg = 0.0       # initial/max energy
512         self.inshld = 0.0       # initial/max shield
513         self.inlsr = 0.0        # initial life support resources
514         self.indate = 0.0       # initial date
515         self.energy = 0.0       # energy level
516         self.shield = 0.0       # shield level
517         self.warpfac = 0.0      # warp speed
518         self.wfacsq = 0.0       # squared warp factor
519         self.lsupres = 0.0      # life support reserves
520         self.dist = 0.0         # movement distance
521         self.direc = 0.0        # movement direction
522         self.optime = 0.0       # time taken by current operation
523         self.docfac = 0.0       # repair factor when docking (constant?)
524         self.damfac = 0.0       # damage factor
525         self.lastchart = 0.0    # time star chart was last updated
526         self.cryprob = 0.0      # probability that crystal will work
527         self.probex = 0.0       # location of probe
528         self.probey = 0.0       #
529         self.probeinx = 0.0     # probe x,y increment
530         self.probeiny = 0.0     #
531         self.height = 0.0       # height of orbit around planet
532     def recompute(self):
533         # Stas thinks this should be (C expression): 
534         # game.state.remkl + len(game.state.kcmdr) > 0 ?
535         #       game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
536         # He says the existing expression is prone to divide-by-zero errors
537         # after killing the last klingon when score is shown -- perhaps also
538         # if the only remaining klingon is SCOM.
539         game.state.remtime = game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr))
540 # From enumerated type 'feature'
541 IHR = 'R'
542 IHK = 'K'
543 IHC = 'C'
544 IHS = 'S'
545 IHSTAR = '*'
546 IHP = 'P'
547 IHW = '@'
548 IHB = 'B'
549 IHBLANK = ' '
550 IHDOT = '.'
551 IHQUEST = '?'
552 IHE = 'E'
553 IHF = 'F'
554 IHT = 'T'
555 IHWEB = '#'
556 IHMATER0 = '-'
557 IHMATER1 = 'o'
558 IHMATER2 = '0'
559
560
561 # From enumerated type 'FINTYPE'
562 FWON = 0
563 FDEPLETE = 1
564 FLIFESUP = 2
565 FNRG = 3
566 FBATTLE = 4
567 FNEG3 = 5
568 FNOVA = 6
569 FSNOVAED = 7
570 FABANDN = 8
571 FDILITHIUM = 9
572 FMATERIALIZE = 10
573 FPHASER = 11
574 FLOST = 12
575 FMINING = 13
576 FDPLANET = 14
577 FPNOVA = 15
578 FSSC = 16
579 FSTRACTOR = 17
580 FDRAY = 18
581 FTRIBBLE = 19
582 FHOLE = 20
583 FCREW = 21
584
585 # Log the results of pulling random numbers so we can check determinism.
586
587 import traceback
588
589 def withprob(p):
590     v = random.random()
591     #logfp.write("# withprob(%s) -> %f (%s) at %s\n" % (p, v, v<p, traceback.extract_stack()[-2][1:]))
592     return v < p
593
594 def randrange(*args):
595     v = random.randrange(*args)
596     #logfp.write("# randrange%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
597     return v
598
599 def randreal(*args):
600     v = random.random()
601     if len(args) == 1:
602         v *= args[0]            # returns from [0, args[0])
603     elif len(args) == 2:
604         v = args[0] + v*(args[1]-args[0])       # returns from [args[0], args[1])
605     #logfp.write("# randreal%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
606     return v
607
608 # Code from ai.c begins here
609
610 def welcoming(iq):
611     "Would this quadrant welcome another Klingon?"
612     return VALID_QUADRANT(iq.i,iq.j) and \
613         not game.state.galaxy[iq.i][iq.j].supernova and \
614         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
615
616 def tryexit(enemy, look, irun):
617     "A bad guy attempts to bug out."
618     iq = coord()
619     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
620     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
621     if not welcoming(iq):
622         return False;
623     if enemy.type == IHR:
624         return False; # Romulans cannot escape! 
625     if not irun:
626         # avoid intruding on another commander's territory 
627         if enemy.type == IHC:
628             if iq in game.state.kcmdr:
629                 return False
630             # refuse to leave if currently attacking starbase 
631             if game.battle == game.quadrant:
632                 return False
633         # don't leave if over 1000 units of energy 
634         if enemy.kpower > 1000.0:
635             return False
636     # emit escape message and move out of quadrant.
637     # we know this if either short or long range sensors are working
638     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
639         game.condition == "docked":
640         prout(crmena(True, enemy.type, "sector", enemy.kloc) + \
641               (_(" escapes to Quadrant %s (and regains strength).") % q))
642     # handle local matters related to escape
643     enemy.move(None)
644     game.klhere -= 1
645     if game.condition != "docked":
646         newcnd()
647     # Handle global matters related to escape 
648     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
649     game.state.galaxy[iq.i][iq.j].klingons += 1
650     if enemy.type==IHS:
651         game.iscate = False
652         game.ientesc = False
653         game.isatb = 0
654         schedule(FSCMOVE, 0.2777)
655         unschedule(FSCDBAS)
656         game.state.kscmdr=iq
657     else:
658         for cmdr in game.state.kcmdr:
659             if cmdr == game.quadrant:
660                 game.state.kcmdr[n] = iq
661                 break
662     return True; # success 
663
664 #
665 # The bad-guy movement algorithm:
666
667 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
668 # If both are operating full strength, force is 1000. If both are damaged,
669 # force is -1000. Having shields down subtracts an additional 1000.
670
671 # 2. Enemy has forces equal to the energy of the attacker plus
672 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
673 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
674
675 # Attacker Initial energy levels (nominal):
676 # Klingon   Romulan   Commander   Super-Commander
677 # Novice    400        700        1200        
678 # Fair      425        750        1250
679 # Good      450        800        1300        1750
680 # Expert    475        850        1350        1875
681 # Emeritus  500        900        1400        2000
682 # VARIANCE   75        200         200         200
683
684 # Enemy vessels only move prior to their attack. In Novice - Good games
685 # only commanders move. In Expert games, all enemy vessels move if there
686 # is a commander present. In Emeritus games all enemy vessels move.
687
688 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
689 # forces are 1000 greater than Enterprise.
690
691 # Agressive action on average cuts the distance between the ship and
692 # the enemy to 1/4 the original.
693
694 # 4.  At lower energy advantage, movement units are proportional to the
695 # advantage with a 650 advantage being to hold ground, 800 to move forward
696 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
697
698 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
699 # retreat, especially at high skill levels.
700
701 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
702
703
704 def movebaddy(enemy):
705     "Tactical movement for the bad guys."
706     next = coord(); look = coord()
707     irun = False
708     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant) 
709     if game.skill >= SKILL_EXPERT:
710         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
711     else:
712         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
713     dist1 = enemy.kdist
714     mdist = int(dist1 + 0.5); # Nearest integer distance 
715     # If SC, check with spy to see if should hi-tail it 
716     if enemy.type==IHS and \
717         (enemy.kpower <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
718         irun = True
719         motion = -QUADSIZE
720     else:
721         # decide whether to advance, retreat, or hold position 
722         forces = enemy.kpower+100.0*len(game.enemies)+400*(nbaddys-1)
723         if not game.shldup:
724             forces += 1000; # Good for enemy if shield is down! 
725         if not damaged(DPHASER) or not damaged(DPHOTON):
726             if damaged(DPHASER): # phasers damaged 
727                 forces += 300.0
728             else:
729                 forces -= 0.2*(game.energy - 2500.0)
730             if damaged(DPHOTON): # photon torpedoes damaged 
731                 forces += 300.0
732             else:
733                 forces -= 50.0*game.torps
734         else:
735             # phasers and photon tubes both out! 
736             forces += 1000.0
737         motion = 0
738         if forces <= 1000.0 and game.condition != "docked": # Typical situation 
739             motion = ((forces + randreal(200))/150.0) - 5.0
740         else:
741             if forces > 1000.0: # Very strong -- move in for kill 
742                 motion = (1.0 - randreal())**2 * dist1 + 1.0
743             if game.condition=="docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
744                 motion -= game.skill*(2.0-randreal()**2)
745         if idebug:
746             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
747         # don't move if no motion 
748         if motion==0:
749             return
750         # Limit motion according to skill 
751         if abs(motion) > game.skill:
752             if motion < 0:
753                 motion = -game.skill
754             else:
755                 motion = game.skill
756     # calculate preferred number of steps 
757     nsteps = abs(int(motion))
758     if motion > 0 and nsteps > mdist:
759         nsteps = mdist; # don't overshoot 
760     if nsteps > QUADSIZE:
761         nsteps = QUADSIZE; # This shouldn't be necessary 
762     if nsteps < 1:
763         nsteps = 1; # This shouldn't be necessary 
764     if idebug:
765         proutn("NSTEPS = %d:" % nsteps)
766     # Compute preferred values of delta X and Y 
767     m = game.sector - enemy.kloc
768     if 2.0 * abs(m.i) < abs(m.j):
769         m.i = 0
770     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.kloc.i):
771         m.j = 0
772     m = (motion * m).sgn()
773     next = enemy.kloc
774     # main move loop 
775     for ll in range(nsteps):
776         if idebug:
777             proutn(" %d" % (ll+1))
778         # Check if preferred position available 
779         look = next + m
780         if m.i < 0:
781             krawli = 1
782         else:
783             krawli = -1
784         if m.j < 0:
785             krawlj = 1
786         else:
787             krawlj = -1
788         success = False
789         attempts = 0; # Settle mysterious hang problem 
790         while attempts < 20 and not success:
791             attempts += 1
792             if look.i < 0 or look.i >= QUADSIZE:
793                 if motion < 0 and tryexit(enemy, look, irun):
794                     return
795                 if krawli == m.i or m.j == 0:
796                     break
797                 look.i = next.i + krawli
798                 krawli = -krawli
799             elif look.j < 0 or look.j >= QUADSIZE:
800                 if motion < 0 and tryexit(enemy, look, irun):
801                     return
802                 if krawlj == m.j or m.i == 0:
803                     break
804                 look.j = next.j + krawlj
805                 krawlj = -krawlj
806             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != IHDOT:
807                 # See if enemy should ram ship 
808                 if game.quad[look.i][look.j] == game.ship and \
809                     (enemy.type == IHC or enemy.type == IHS):
810                     collision(rammed=True, enemy=enemy)
811                     return
812                 if krawli != m.i and m.j != 0:
813                     look.i = next.i + krawli
814                     krawli = -krawli
815                 elif krawlj != m.j and m.i != 0:
816                     look.j = next.j + krawlj
817                     krawlj = -krawlj
818                 else:
819                     break; # we have failed 
820             else:
821                 success = True
822         if success:
823             next = look
824             if idebug:
825                 proutn(`next`)
826         else:
827             break; # done early 
828     if idebug:
829         skip(1)
830     if enemy.move(next):
831         if not damaged(DSRSENS) or game.condition == "docked":
832             proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.kloc))
833             if enemy.kdist < dist1:
834                 proutn(_(" advances to "))
835             else:
836                 proutn(_(" retreats to "))
837             prout("Sector %s." % next)
838
839 def moveklings():
840     "Sequence Klingon tactical movement."
841     if idebug:
842         prout("== MOVCOM")
843     # Figure out which Klingon is the commander (or Supercommander)
844     # and do move
845     if game.quadrant in game.state.kcmdr:
846         for enemy in game.enemies:
847             if enemy.type == IHC:
848                 movebaddy(enemy)
849     if game.state.kscmdr==game.quadrant:
850         for enemy in game.enemies:
851             if enemy.type == IHS:
852                 movebaddy(enemy)
853                 break
854     # If skill level is high, move other Klingons and Romulans too!
855     # Move these last so they can base their actions on what the
856     # commander(s) do.
857     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
858         for enemy in game.enemies:
859             if enemy.type in (IHK, IHR):
860                 movebaddy(enemy)
861     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
862
863 def movescom(iq, avoid):
864     "Commander movement helper." 
865     # Avoid quadrants with bases if we want to avoid Enterprise 
866     if not welcoming(iq) or (avoid and iq in game.state.baseq):
867         return True
868     if game.justin and not game.iscate:
869         return True
870     # do the move 
871     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
872     game.state.kscmdr = iq
873     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
874     if game.state.kscmdr==game.quadrant:
875         # SC has scooted, Remove him from current quadrant 
876         game.iscate=False
877         game.isatb=0
878         game.ientesc = False
879         unschedule(FSCDBAS)
880         for enemy in game.enemies:
881             if enemy.type == IHS:
882                 break
883         enemy.move(None)
884         game.klhere -= 1
885         if game.condition != "docked":
886             newcnd()
887         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
888     # check for a helpful planet 
889     for i in range(game.inplan):
890         if game.state.planets[i].quadrant == game.state.kscmdr and \
891             game.state.planets[i].crystals == "present":
892             # destroy the planet 
893             game.state.planets[i].pclass = "destroyed"
894             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
895             if communicating():
896                 announce()
897                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
898                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
899                 prout(_("   by the Super-commander.\""))
900             break
901     return False; # looks good! 
902                         
903 def supercommander():
904     "Move the Super Commander." 
905     iq = coord(); sc = coord(); ibq = coord(); idelta = coord()
906     basetbl = []
907     if idebug:
908         prout("== SUPERCOMMANDER")
909     # Decide on being active or passive 
910     avoid = ((game.incom - len(game.state.kcmdr) + game.inkling - game.state.remkl)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
911             (game.state.date-game.indate) < 3.0)
912     if not game.iscate and avoid:
913         # compute move away from Enterprise 
914         idelta = game.state.kscmdr-game.quadrant
915         if math.sqrt(idelta.i*idelta.i+idelta.j*idelta.j) > 2.0:
916             # circulate in space 
917             idelta.i = game.state.kscmdr.j-game.quadrant.j
918             idelta.j = game.quadrant.i-game.state.kscmdr.i
919     else:
920         # compute distances to starbases 
921         if not game.state.baseq:
922             # nothing left to do 
923             unschedule(FSCMOVE)
924             return
925         sc = game.state.kscmdr
926         for base in game.state.baseq:
927             basetbl.append((i, (base - sc).distance()))
928         if game.state.baseq > 1:
929             basetbl.sort(lambda x, y: cmp(x[1]. y[1]))
930         # look for nearest base without a commander, no Enterprise, and
931         # without too many Klingons, and not already under attack. 
932         ifindit = iwhichb = 0
933         for (i2, base) in enumerate(game.state.baseq):
934             i = basetbl[i2][0]; # bug in original had it not finding nearest
935             if base==game.quadrant or base==game.battle or not welcoming(base):
936                 continue
937             # if there is a commander, and no other base is appropriate,
938             # we will take the one with the commander
939             for cmdr in game.state.kcmdr:
940                 if base == cmdr and ifindit != 2:
941                     ifindit = 2
942                     iwhichb = i
943                     break
944             else:       # no commander -- use this one 
945                 ifindit = 1
946                 iwhichb = i
947                 break
948         if ifindit==0:
949             return # Nothing suitable -- wait until next time
950         ibq = game.state.baseq[iwhichb]
951         # decide how to move toward base 
952         idelta = ibq - game.state.kscmdr
953     # Maximum movement is 1 quadrant in either or both axes 
954     idelta = idelta.sgn()
955     # try moving in both x and y directions
956     # there was what looked like a bug in the Almy C code here,
957     # but it might be this translation is just wrong.
958     iq = game.state.kscmdr + idelta
959     if movescom(iq, avoid):
960         # failed -- try some other maneuvers 
961         if idelta.i==0 or idelta.j==0:
962             # attempt angle move 
963             if idelta.i != 0:
964                 iq.j = game.state.kscmdr.j + 1
965                 if movescom(iq, avoid):
966                     iq.j = game.state.kscmdr.j - 1
967                     movescom(iq, avoid)
968             else:
969                 iq.i = game.state.kscmdr.i + 1
970                 if movescom(iq, avoid):
971                     iq.i = game.state.kscmdr.i - 1
972                     movescom(iq, avoid)
973         else:
974             # try moving just in x or y 
975             iq.j = game.state.kscmdr.j
976             if movescom(iq, avoid):
977                 iq.j = game.state.kscmdr.j + idelta.j
978                 iq.i = game.state.kscmdr.i
979                 movescom(iq, avoid)
980     # check for a base 
981     if len(game.state.baseq) == 0:
982         unschedule(FSCMOVE)
983     else:
984         for (i, ibq) in enumerate(game.state.baseq):
985             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
986                 # attack the base 
987                 if avoid:
988                     return # no, don't attack base! 
989                 game.iseenit = False
990                 game.isatb = 1
991                 schedule(FSCDBAS, randreal(1.0, 3.0))
992                 if is_scheduled(FCDBAS):
993                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
994                 if not communicating():
995                     return # no warning 
996                 game.iseenit = True
997                 announce()
998                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
999                       % game.state.kscmdr)
1000                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
1001                 proutn(_("   It can survive until stardate %d.\"") \
1002                        % int(scheduled(FSCDBAS)))
1003                 if not game.resting:
1004                     return
1005                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
1006                 if ja() == False:
1007                     return
1008                 game.resting = False
1009                 game.optime = 0.0; # actually finished 
1010                 return
1011     # Check for intelligence report 
1012     if not idebug and \
1013         (withprob(0.8) or \
1014          (not communicating()) or \
1015          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
1016         return
1017     announce()
1018     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
1019     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
1020     return
1021
1022 def movetholian():
1023     "Move the Tholian."
1024     if not game.tholian or game.justin:
1025         return
1026     id = coord()
1027     if game.tholian.kloc.i == 0 and game.tholian.kloc.j == 0:
1028         id.i = 0; id.j = QUADSIZE-1
1029     elif game.tholian.kloc.i == 0 and game.tholian.kloc.j == QUADSIZE-1:
1030         id.i = QUADSIZE-1; id.j = QUADSIZE-1
1031     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == QUADSIZE-1:
1032         id.i = QUADSIZE-1; id.j = 0
1033     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == 0:
1034         id.i = 0; id.j = 0
1035     else:
1036         # something is wrong! 
1037         game.tholian.move(None)
1038         prout("***Internal error: Tholian in a bad spot.")
1039         return
1040     # do nothing if we are blocked 
1041     if game.quad[id.i][id.j] not in (IHDOT, IHWEB):
1042         return
1043     here = copy.copy(game.tholian.kloc)
1044     delta = (id - game.tholian.kloc).sgn()
1045     # move in x axis 
1046     while here.i != id.i:
1047         here.i += delta.i
1048         if game.quad[here.i][here.j]==IHDOT:
1049             game.tholian.move(here)
1050     # move in y axis 
1051     while here.j != id.j:
1052         here.j += delta.j
1053         if game.quad[here.i][here.j]==IHDOT:
1054             game.tholian.move(here)
1055     # check to see if all holes plugged 
1056     for i in range(QUADSIZE):
1057         if game.quad[0][i]!=IHWEB and game.quad[0][i]!=IHT:
1058             return
1059         if game.quad[QUADSIZE-1][i]!=IHWEB and game.quad[QUADSIZE-1][i]!=IHT:
1060             return
1061         if game.quad[i][0]!=IHWEB and game.quad[i][0]!=IHT:
1062             return
1063         if game.quad[i][QUADSIZE-1]!=IHWEB and game.quad[i][QUADSIZE-1]!=IHT:
1064             return
1065     # All plugged up -- Tholian splits 
1066     game.quad[game.tholian.kloc.i][game.tholian.kloc.j]=IHWEB
1067     dropin(IHBLANK)
1068     prout(crmena(True, IHT, "sector", game.tholian) + _(" completes web."))
1069     game.tholian.move(None)
1070     return
1071
1072 # Code from battle.c begins here
1073
1074 def doshield(shraise):
1075     "Change shield status."
1076     action = "NONE"
1077     game.ididit = False
1078     if shraise:
1079         action = "SHUP"
1080     else:
1081         key = scanner.next()
1082         if key == "IHALPHA":
1083             if scanner.sees("transfer"):
1084                 action = "NRG"
1085             else:
1086                 if damaged(DSHIELD):
1087                     prout(_("Shields damaged and down."))
1088                     return
1089                 if scanner.sees("up"):
1090                     action = "SHUP"
1091                 elif scanner.sees("down"):
1092                     action = "SHDN"
1093         if action=="NONE":
1094             proutn(_("Do you wish to change shield energy? "))
1095             if ja() == True:
1096                 proutn(_("Energy to transfer to shields- "))
1097                 action = "NRG"
1098             elif damaged(DSHIELD):
1099                 prout(_("Shields damaged and down."))
1100                 return
1101             elif game.shldup:
1102                 proutn(_("Shields are up. Do you want them down? "))
1103                 if ja() == True:
1104                     action = "SHDN"
1105                 else:
1106                     scanner.chew()
1107                     return
1108             else:
1109                 proutn(_("Shields are down. Do you want them up? "))
1110                 if ja() == True:
1111                     action = "SHUP"
1112                 else:
1113                     scanner.chew()
1114                     return    
1115     if action == "SHUP": # raise shields 
1116         if game.shldup:
1117             prout(_("Shields already up."))
1118             return
1119         game.shldup = True
1120         game.shldchg = True
1121         if game.condition != "docked":
1122             game.energy -= 50.0
1123         prout(_("Shields raised."))
1124         if game.energy <= 0:
1125             skip(1)
1126             prout(_("Shields raising uses up last of energy."))
1127             finish(FNRG)
1128             return
1129         game.ididit=True
1130         return
1131     elif action == "SHDN":
1132         if not game.shldup:
1133             prout(_("Shields already down."))
1134             return
1135         game.shldup=False
1136         game.shldchg=True
1137         prout(_("Shields lowered."))
1138         game.ididit = True
1139         return
1140     elif action == "NRG":
1141         while scanner.next() != "IHREAL":
1142             scanner.chew()
1143             proutn(_("Energy to transfer to shields- "))
1144         scanner.chew()
1145         if scanner.real == 0:
1146             return
1147         if scanner.real > game.energy:
1148             prout(_("Insufficient ship energy."))
1149             return
1150         game.ididit = True
1151         if game.shield+scanner.real >= game.inshld:
1152             prout(_("Shield energy maximized."))
1153             if game.shield+scanner.real > game.inshld:
1154                 prout(_("Excess energy requested returned to ship energy"))
1155             game.energy -= game.inshld-game.shield
1156             game.shield = game.inshld
1157             return
1158         if scanner.real < 0.0 and game.energy-scanner.real > game.inenrg:
1159             # Prevent shield drain loophole 
1160             skip(1)
1161             prout(_("Engineering to bridge--"))
1162             prout(_("  Scott here. Power circuit problem, Captain."))
1163             prout(_("  I can't drain the shields."))
1164             game.ididit = False
1165             return
1166         if game.shield+scanner.real < 0:
1167             prout(_("All shield energy transferred to ship."))
1168             game.energy += game.shield
1169             game.shield = 0.0
1170             return
1171         proutn(_("Scotty- \""))
1172         if scanner.real > 0:
1173             prout(_("Transferring energy to shields.\""))
1174         else:
1175             prout(_("Draining energy from shields.\""))
1176         game.shield += scanner.real
1177         game.energy -= scanner.real
1178         return
1179
1180 def randdevice():
1181     "Choose a device to damage, at random."
1182     # Quoth Eric Allman in the code of BSD-Trek:
1183     # "Under certain conditions you can get a critical hit.  This
1184     # sort of hit damages devices.  The probability that a given
1185     # device is damaged depends on the device.  Well protected
1186     # devices (such as the computer, which is in the core of the
1187     # ship and has considerable redundancy) almost never get
1188     # damaged, whereas devices which are exposed (such as the
1189     # warp engines) or which are particularly delicate (such as
1190     # the transporter) have a much higher probability of being
1191     # damaged."
1192     # 
1193     # This is one place where OPTION_PLAIN does not restore the
1194     # original behavior, which was equiprobable damage across
1195     # all devices.  If we wanted that, we'd return randrange(NDEVICES)
1196     # and have done with it.  Also, in the original game, DNAVYS
1197     # and DCOMPTR were the same device. 
1198     # 
1199     # Instead, we use a table of weights similar to the one from BSD Trek.
1200     # BSD doesn't have the shuttle, shield controller, death ray, or probes. 
1201     # We don't have a cloaking device.  The shuttle got the allocation
1202     # for the cloaking device, then we shaved a half-percent off
1203     # everything to have some weight to give DSHCTRL/DDRAY/DDSP.
1204     weights = (
1205         105,    # DSRSENS: short range scanners 10.5% 
1206         105,    # DLRSENS: long range scanners          10.5% 
1207         120,    # DPHASER: phasers                      12.0% 
1208         120,    # DPHOTON: photon torpedoes             12.0% 
1209         25,     # DLIFSUP: life support          2.5% 
1210         65,     # DWARPEN: warp drive                    6.5% 
1211         70,     # DIMPULS: impulse engines               6.5% 
1212         145,    # DSHIELD: deflector shields            14.5% 
1213         30,     # DRADIO:  subspace radio                3.0% 
1214         45,     # DSHUTTL: shuttle                       4.5% 
1215         15,     # DCOMPTR: computer                      1.5% 
1216         20,     # NAVCOMP: navigation system             2.0% 
1217         75,     # DTRANSP: transporter                   7.5% 
1218         20,     # DSHCTRL: high-speed shield controller 2.0% 
1219         10,     # DDRAY: death ray                       1.0% 
1220         30,     # DDSP: deep-space probes                3.0% 
1221     )
1222     idx = randrange(1000)       # weights must sum to 1000 
1223     sum = 0
1224     for (i, w) in enumerate(weights):
1225         sum += w
1226         if idx < sum:
1227             return i
1228     return None;        # we should never get here
1229
1230 def collision(rammed, enemy):
1231     "Collision handling fot rammong events."
1232     prouts(_("***RED ALERT!  RED ALERT!"))
1233     skip(1)
1234     prout(_("***COLLISION IMMINENT."))
1235     skip(2)
1236     proutn("***")
1237     proutn(crmshp())
1238     hardness = {IHR:1.5, IHC:2.0, IHS:2.5, IHT:0.5, IHQUEST:4.0}.get(enemy.type, 1.0)
1239     if rammed:
1240         proutn(_(" rammed by "))
1241     else:
1242         proutn(_(" rams "))
1243     proutn(crmena(False, enemy.type, "sector", enemy.kloc))
1244     if rammed:
1245         proutn(_(" (original position)"))
1246     skip(1)
1247     deadkl(enemy.kloc, enemy.type, game.sector)
1248     proutn("***" + crmship() + " heavily damaged.")
1249     icas = randrange(10, 30)
1250     prout(_("***Sickbay reports %d casualties"), icas)
1251     game.casual += icas
1252     game.state.crew -= icas
1253     # In the pre-SST2K version, all devices got equiprobably damaged,
1254     # which was silly.  Instead, pick up to half the devices at
1255     # random according to our weighting table,
1256     ncrits = randrange(NDEVICES/2)
1257     for m in range(ncrits):
1258         dev = randdevice()
1259         if game.damage[dev] < 0:
1260             continue
1261         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1262         # Damage for at least time of travel! 
1263         game.damage[dev] += game.optime + extradm
1264     game.shldup = False
1265     prout(_("***Shields are down."))
1266     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1267         announce()
1268         damagereport()
1269     else:
1270         finish(FWON)
1271     return
1272
1273 def torpedo(origin, course, dispersion, number, nburst):
1274     "Let a photon torpedo fly" 
1275     if not damaged(DSRSENS) or game.condition=="docked":
1276         setwnd(srscan_window)
1277     else: 
1278         setwnd(message_window)
1279     shoved = False
1280     ac = course + 0.25*dispersion
1281     angle = (15.0-ac)*0.5235988
1282     bullseye = (15.0 - course)*0.5235988
1283     delta = coord(-math.sin(angle), math.cos(angle))          
1284     bigger = max(abs(delta.i), abs(delta.j))
1285     delta /= bigger
1286     w = coord(0, 0); jw = coord(0, 0)
1287     ungridded = copy.copy(origin)
1288     # Loop to move a single torpedo 
1289     for step in range(1, QUADSIZE*2):
1290         ungridded += delta
1291         w = ungridded.snaptogrid()
1292         if not VALID_SECTOR(w.i, w.j):
1293             break
1294         iquad=game.quad[w.i][w.j]
1295         tracktorpedo(origin, w, step, number, nburst, iquad)
1296         if iquad==IHDOT:
1297             continue
1298         # hit something 
1299         setwnd(message_window)
1300         if damaged(DSRSENS) and not game.condition=="docked":
1301             skip(1);    # start new line after text track 
1302         if iquad in (IHE, IHF): # Hit our ship 
1303             skip(1)
1304             prout(_("Torpedo hits %s.") % crmshp())
1305             hit = 700.0 + randreal(100) - \
1306                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
1307             newcnd(); # we're blown out of dock 
1308             # We may be displaced. 
1309             if game.landed or game.condition=="docked":
1310                 return hit # Cheat if on a planet 
1311             ang = angle + 2.5*(randreal()-0.5)
1312             temp = math.fabs(math.sin(ang))
1313             if math.fabs(math.cos(ang)) > temp:
1314                 temp = math.fabs(math.cos(ang))
1315             xx = -math.sin(ang)/temp
1316             yy = math.cos(ang)/temp
1317             jw.i = int(w.i+xx+0.5)
1318             jw.j = int(w.j+yy+0.5)
1319             if not VALID_SECTOR(jw.i, jw.j):
1320                 return hit
1321             if game.quad[jw.i][jw.j]==IHBLANK:
1322                 finish(FHOLE)
1323                 return hit
1324             if game.quad[jw.i][jw.j]!=IHDOT:
1325                 # can't move into object 
1326                 return hit
1327             game.sector = jw
1328             proutn(crmshp())
1329             shoved = True
1330         elif iquad in (IHC, IHS, IHR, IHK): # Hit a regular enemy 
1331             # find the enemy 
1332             if iquad in (IHC, IHS) and withprob(0.05):
1333                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1334                 prout(_("   torpedo neutralized."))
1335                 return None
1336             for enemy in game.enemies:
1337                 if w == enemy.kloc:
1338                     break
1339             kp = math.fabs(enemy.kpower)
1340             h1 = 700.0 + randrange(100) - \
1341                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
1342             h1 = math.fabs(h1)
1343             if kp < h1:
1344                 h1 = kp
1345             if enemy.kpower < 0:
1346                 enemy.kpower -= -h1
1347             else:
1348                 enemy.kpower -= h1
1349             if enemy.kpower == 0:
1350                 deadkl(w, iquad, w)
1351                 return None
1352             proutn(crmena(True, iquad, "sector", w))
1353             # If enemy damaged but not destroyed, try to displace 
1354             ang = angle + 2.5*(randreal()-0.5)
1355             temp = math.fabs(math.sin(ang))
1356             if math.fabs(math.cos(ang)) > temp:
1357                 temp = math.fabs(math.cos(ang))
1358             xx = -math.sin(ang)/temp
1359             yy = math.cos(ang)/temp
1360             jw.i = int(w.i+xx+0.5)
1361             jw.j = int(w.j+yy+0.5)
1362             if not VALID_SECTOR(jw.i, jw.j):
1363                 prout(_(" damaged but not destroyed."))
1364                 return
1365             if game.quad[jw.i][jw.j]==IHBLANK:
1366                 prout(_(" buffeted into black hole."))
1367                 deadkl(w, iquad, jw)
1368                 return None
1369             if game.quad[jw.i][jw.j]!=IHDOT:
1370                 # can't move into object 
1371                 prout(_(" damaged but not destroyed."))
1372                 return None
1373             proutn(_(" damaged--"))
1374             enemy.kloc = jw
1375             shoved = True
1376             break
1377         elif iquad == IHB: # Hit a base 
1378             skip(1)
1379             prout(_("***STARBASE DESTROYED.."))
1380             game.state.baseq = filter(lambda x: x != game.quadrant, game.state.baseq)
1381             game.quad[w.i][w.j]=IHDOT
1382             game.base.invalidate()
1383             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase -= 1
1384             game.state.chart[game.quadrant.i][game.quadrant.j].starbase -= 1
1385             game.state.basekl += 1
1386             newcnd()
1387             return None
1388         elif iquad == IHP: # Hit a planet 
1389             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1390             game.state.nplankl += 1
1391             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1392             game.iplnet.pclass = "destroyed"
1393             game.iplnet = None
1394             game.plnet.invalidate()
1395             game.quad[w.i][w.j] = IHDOT
1396             if game.landed:
1397                 # captain perishes on planet 
1398                 finish(FDPLANET)
1399             return None
1400         elif iquad == IHW: # Hit an inhabited world -- very bad! 
1401             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1402             game.state.nworldkl += 1
1403             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1404             game.iplnet.pclass = "destroyed"
1405             game.iplnet = None
1406             game.plnet.invalidate()
1407             game.quad[w.i][w.j] = IHDOT
1408             if game.landed:
1409                 # captain perishes on planet 
1410                 finish(FDPLANET)
1411             prout(_("You have just destroyed an inhabited planet."))
1412             prout(_("Celebratory rallies are being held on the Klingon homeworld."))
1413             return None
1414         elif iquad == IHSTAR: # Hit a star 
1415             if withprob(0.9):
1416                 nova(w)
1417             else:
1418                 prout(crmena(True, IHSTAR, "sector", w) + _(" unaffected by photon blast."))
1419             return None
1420         elif iquad == IHQUEST: # Hit a thingy 
1421             if not (game.options & OPTION_THINGY) or withprob(0.3):
1422                 skip(1)
1423                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1424                 skip(1)
1425                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1426                 skip(1)
1427                 proutn(_("Mr. Spock-"))
1428                 prouts(_("  \"Fascinating!\""))
1429                 skip(1)
1430                 deadkl(w, iquad, w)
1431             else:
1432                 # Stas Sergeev added the possibility that
1433                 # you can shove the Thingy and piss it off.
1434                 # It then becomes an enemy and may fire at you.
1435                 thing.angry = True
1436                 shoved = True
1437             return None
1438         elif iquad == IHBLANK: # Black hole 
1439             skip(1)
1440             prout(crmena(True, IHBLANK, "sector", w) + _(" swallows torpedo."))
1441             return None
1442         elif iquad == IHWEB: # hit the web 
1443             skip(1)
1444             prout(_("***Torpedo absorbed by Tholian web."))
1445             return None
1446         elif iquad == IHT:  # Hit a Tholian 
1447             h1 = 700.0 + randrange(100) - \
1448                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
1449             h1 = math.fabs(h1)
1450             if h1 >= 600:
1451                 game.quad[w.i][w.j] = IHDOT
1452                 deadkl(w, iquad, w)
1453                 game.tholian = None
1454                 return None
1455             skip(1)
1456             proutn(crmena(True, IHT, "sector", w))
1457             if withprob(0.05):
1458                 prout(_(" survives photon blast."))
1459                 return None
1460             prout(_(" disappears."))
1461             game.tholian.move(None)
1462             game.quad[w.i][w.j] = IHWEB
1463             dropin(IHBLANK)
1464             return None
1465         else: # Problem!
1466             skip(1)
1467             proutn("Don't know how to handle torpedo collision with ")
1468             proutn(crmena(True, iquad, "sector", w))
1469             skip(1)
1470             return None
1471         break
1472     if curwnd!=message_window:
1473         setwnd(message_window)
1474     if shoved:
1475         game.quad[w.i][w.j]=IHDOT
1476         game.quad[jw.i][jw.j]=iquad
1477         prout(_(" displaced by blast to Sector %s ") % jw)
1478         for ll in range(len(game.enemies)):
1479             game.enemies[ll].kdist = game.enemies[ll].kavgd = (game.sector-game.enemies[ll].kloc).distance()
1480         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1481         return None
1482     skip(1)
1483     prout(_("Torpedo missed."))
1484     return None;
1485
1486 def fry(hit):
1487     "Critical-hit resolution." 
1488     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1489         return
1490     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1491     proutn(_("***CRITICAL HIT--"))
1492     # Select devices and cause damage
1493     cdam = []
1494     for loop1 in range(ncrit):
1495         while True:
1496             j = randdevice()
1497             # Cheat to prevent shuttle damage unless on ship 
1498             if not (game.damage[j]<0.0 or (j==DSHUTTL and game.iscraft != "onship")):
1499                 break
1500         cdam.append(j)
1501         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1502         game.damage[j] += extradm
1503     skipcount = 0
1504     for (i, j) in enumerate(cdam):
1505         proutn(device[j])
1506         if skipcount % 3 == 2 and i < len(cdam)-1:
1507             skip(1)
1508         skipcount += 1
1509         if i < len(cdam)-1:
1510             proutn(_(" and "))
1511     prout(_(" damaged."))
1512     if damaged(DSHIELD) and game.shldup:
1513         prout(_("***Shields knocked down."))
1514         game.shldup=False
1515
1516 def attack(torps_ok):
1517     # bad guy attacks us 
1518     # torps_ok == False forces use of phasers in an attack 
1519     # game could be over at this point, check
1520     if game.alldone:
1521         return
1522     attempt = False; ihurt = False;
1523     hitmax=0.0; hittot=0.0; chgfac=1.0
1524     where = "neither"
1525     if idebug:
1526         prout("=== ATTACK!")
1527     # Tholian gets to move before attacking 
1528     if game.tholian:
1529         movetholian()
1530     # if you have just entered the RNZ, you'll get a warning 
1531     if game.neutz: # The one chance not to be attacked 
1532         game.neutz = False
1533         return
1534     # commanders get a chance to tac-move towards you 
1535     if (((game.quadrant in game.state.kcmdr or game.state.kscmdr==game.quadrant) and not game.justin) or game.skill == SKILL_EMERITUS) and torps_ok:
1536         moveklings()
1537     # if no enemies remain after movement, we're done 
1538     if len(game.enemies)==0 or (len(game.enemies)==1 and thing == game.quadrant and not thing.angry):
1539         return
1540     # set up partial hits if attack happens during shield status change 
1541     pfac = 1.0/game.inshld
1542     if game.shldchg:
1543         chgfac = 0.25 + randreal(0.5)
1544     skip(1)
1545     # message verbosity control 
1546     if game.skill <= SKILL_FAIR:
1547         where = "sector"
1548     for enemy in game.enemies:
1549         if enemy.kpower < 0:
1550             continue;   # too weak to attack 
1551         # compute hit strength and diminish shield power 
1552         r = randreal()
1553         # Increase chance of photon torpedos if docked or enemy energy is low 
1554         if game.condition == "docked":
1555             r *= 0.25
1556         if enemy.kpower < 500:
1557             r *= 0.25; 
1558         if enemy.type==IHT or (enemy.type==IHQUEST and not thing.angry):
1559             continue
1560         # different enemies have different probabilities of throwing a torp 
1561         usephasers = not torps_ok or \
1562             (enemy.type == IHK and r > 0.0005) or \
1563             (enemy.type==IHC and r > 0.015) or \
1564             (enemy.type==IHR and r > 0.3) or \
1565             (enemy.type==IHS and r > 0.07) or \
1566             (enemy.type==IHQUEST and r > 0.05)
1567         if usephasers:      # Enemy uses phasers 
1568             if game.condition == "docked":
1569                 continue; # Don't waste the effort! 
1570             attempt = True; # Attempt to attack 
1571             dustfac = randreal(0.8, 0.85)
1572             hit = enemy.kpower*math.pow(dustfac,enemy.kavgd)
1573             enemy.kpower *= 0.75
1574         else: # Enemy uses photon torpedo 
1575             # We should be able to make the bearing() method work here
1576             course = 1.90985*math.atan2(game.sector.j-enemy.kloc.j, enemy.kloc.i-game.sector.i)
1577             hit = 0
1578             proutn(_("***TORPEDO INCOMING"))
1579             if not damaged(DSRSENS):
1580                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.kloc))
1581             attempt = True
1582             prout("  ")
1583             dispersion = (randreal()+randreal())*0.5 - 0.5
1584             dispersion += 0.002*enemy.kpower*dispersion
1585             hit = torpedo(enemy.kloc, course, dispersion, number=1, nburst=1)
1586             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1587                 finish(FWON); # Klingons did themselves in! 
1588             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1589                 return # Supernova or finished 
1590             if hit == None:
1591                 continue
1592         # incoming phaser or torpedo, shields may dissipate it 
1593         if game.shldup or game.shldchg or game.condition=="docked":
1594             # shields will take hits 
1595             propor = pfac * game.shield
1596             if game.condition =="docked":
1597                 propr *= 2.1
1598             if propor < 0.1:
1599                 propor = 0.1
1600             hitsh = propor*chgfac*hit+1.0
1601             absorb = 0.8*hitsh
1602             if absorb > game.shield:
1603                 absorb = game.shield
1604             game.shield -= absorb
1605             hit -= hitsh
1606             # taking a hit blasts us out of a starbase dock 
1607             if game.condition == "docked":
1608                 dock(False)
1609             # but the shields may take care of it 
1610             if propor > 0.1 and hit < 0.005*game.energy:
1611                 continue
1612         # hit from this opponent got through shields, so take damage 
1613         ihurt = True
1614         proutn(_("%d unit hit") % int(hit))
1615         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1616             proutn(_(" on the ") + crmshp())
1617         if not damaged(DSRSENS) and usephasers:
1618             prout(_(" from ") + crmena(False, enemy.type, where, enemy.kloc))
1619         skip(1)
1620         # Decide if hit is critical 
1621         if hit > hitmax:
1622             hitmax = hit
1623         hittot += hit
1624         fry(hit)
1625         game.energy -= hit
1626     if game.energy <= 0:
1627         # Returning home upon your shield, not with it... 
1628         finish(FBATTLE)
1629         return
1630     if not attempt and game.condition == "docked":
1631         prout(_("***Enemies decide against attacking your ship."))
1632     percent = 100.0*pfac*game.shield+0.5
1633     if not ihurt:
1634         # Shields fully protect ship 
1635         proutn(_("Enemy attack reduces shield strength to "))
1636     else:
1637         # Emit message if starship suffered hit(s) 
1638         skip(1)
1639         proutn(_("Energy left %2d    shields ") % int(game.energy))
1640         if game.shldup:
1641             proutn(_("up "))
1642         elif not damaged(DSHIELD):
1643             proutn(_("down "))
1644         else:
1645             proutn(_("damaged, "))
1646     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1647     # Check if anyone was hurt 
1648     if hitmax >= 200 or hittot >= 500:
1649         icas = randrange(int(hittot * 0.015))
1650         if icas >= 2:
1651             skip(1)
1652             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1653             prout(_("   in that last attack.\""))
1654             game.casual += icas
1655             game.state.crew -= icas
1656     # After attack, reset average distance to enemies 
1657     for enemy in game.enemies:
1658         enemy.kavgd = enemy.kdist
1659     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1660     return
1661                 
1662 def deadkl(w, type, mv):
1663     "Kill a Klingon, Tholian, Romulan, or Thingy." 
1664     # Added mv to allow enemy to "move" before dying 
1665     proutn(crmena(True, type, "sector", mv))
1666     # Decide what kind of enemy it is and update appropriately 
1667     if type == IHR:
1668         # chalk up a Romulan 
1669         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1670         game.irhere -= 1
1671         game.state.nromrem -= 1
1672     elif type == IHT:
1673         # Killed a Tholian 
1674         game.tholian = None
1675     elif type == IHQUEST:
1676         # Killed a Thingy
1677         global thing
1678         thing = None
1679     else:
1680         # Some type of a Klingon 
1681         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1682         game.klhere -= 1
1683         if type == IHC:
1684             game.state.kcmdr.remove(game.quadrant)
1685             unschedule(FTBEAM)
1686             if game.state.kcmdr:
1687                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1688             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1689                 unschedule(FCDBAS)    
1690         elif type ==  IHK:
1691             game.state.remkl -= 1
1692         elif type ==  IHS:
1693             game.state.nscrem -= 1
1694             game.state.kscmdr.invalidate()
1695             game.isatb = 0
1696             game.iscate = False
1697             unschedule(FSCMOVE)
1698             unschedule(FSCDBAS)
1699     # For each kind of enemy, finish message to player 
1700     prout(_(" destroyed."))
1701     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1702         return
1703     game.recompute()
1704     # Remove enemy ship from arrays describing local conditions
1705     for e in game.enemies:
1706         if e.kloc == w:
1707             e.move(None)
1708             break
1709     return
1710
1711 def targetcheck(w):
1712     "Return None if target is invalid, otherwise return a course angle."
1713     if not VALID_SECTOR(w.i, w.j):
1714         huh()
1715         return None
1716     delta = coord()
1717     # FIXME: C code this was translated from is wacky -- why the sign reversal?
1718     delta.j = (w.j - game.sector.j);
1719     delta.i = (game.sector.i - w.i);
1720     if delta == coord(0, 0):
1721         skip(1)
1722         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1723         prout(_("  I recommend an immediate review of"))
1724         prout(_("  the Captain's psychological profile.\""))
1725         scanner.chew()
1726         return None
1727     return delta.bearing()
1728
1729 def photon():
1730     "Launch photon torpedo."
1731     course = []
1732     game.ididit = False
1733     if damaged(DPHOTON):
1734         prout(_("Photon tubes damaged."))
1735         scanner.chew()
1736         return
1737     if game.torps == 0:
1738         prout(_("No torpedoes left."))
1739         scanner.chew()
1740         return
1741     # First, get torpedo count
1742     while True:
1743         scanner.next()
1744         if scanner.token == "IHALPHA":
1745             huh()
1746             return
1747         elif scanner.token == "IHEOL" or not scanner.waiting():
1748             prout(_("%d torpedoes left.") % game.torps)
1749             scanner.chew()
1750             proutn(_("Number of torpedoes to fire- "))
1751             continue    # Go back around to get a number
1752         else: # key == "IHREAL"
1753             n = scanner.int()
1754             if n <= 0: # abort command 
1755                 scanner.chew()
1756                 return
1757             if n > MAXBURST:
1758                 scanner.chew()
1759                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1760                 return
1761             if n > game.torps:
1762                 scanner.chew()  # User requested more torps than available
1763                 continue        # Go back around
1764             break       # All is good, go to next stage
1765     # Next, get targets
1766     target = []
1767     for i in range(n):
1768         key = scanner.next()
1769         if i==0 and key == "IHEOL":
1770             break;      # no coordinate waiting, we will try prompting 
1771         if i==1 and key == "IHEOL":
1772             # direct all torpedoes at one target 
1773             while i < n:
1774                 target.append(target[0])
1775                 course.append(course[0])
1776                 i += 1
1777             break
1778         scanner.push(scanner.token)
1779         target.append(scanner.getcoord())
1780         if target[-1] == None:
1781             return
1782         course.append(targetcheck(target[-1]))
1783         if course[-1] == None:
1784             return
1785     scanner.chew()
1786     if len(target) == 0:
1787         # prompt for each one 
1788         for i in range(n):
1789             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1790             scanner.chew()
1791             target.append(scanner.getcoord())
1792             if target[-1] == None:
1793                 return
1794             course.append(targetcheck(target[-1]))
1795             if course[-1] == None:
1796                 return
1797     game.ididit = True
1798     # Loop for moving <n> torpedoes 
1799     for i in range(n):
1800         if game.condition != "docked":
1801             game.torps -= 1
1802         dispersion = (randreal()+randreal())*0.5 -0.5
1803         if math.fabs(dispersion) >= 0.47:
1804             # misfire! 
1805             dispersion *= randreal(1.2, 2.2)
1806             if n > 0:
1807                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1808             else:
1809                 prouts(_("***TORPEDO MISFIRES."))
1810             skip(1)
1811             if i < n:
1812                 prout(_("  Remainder of burst aborted."))
1813             if withprob(0.2):
1814                 prout(_("***Photon tubes damaged by misfire."))
1815                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1816             break
1817         if game.shldup or game.condition == "docked":
1818             dispersion *= 1.0 + 0.0001*game.shield
1819         torpedo(game.sector, course[i], dispersion, number=i, nburst=n)
1820         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1821             return
1822     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1823         finish(FWON);
1824
1825 def overheat(rpow):
1826     "Check for phasers overheating."
1827     if rpow > 1500:
1828         checkburn = (rpow-1500.0)*0.00038
1829         if withprob(checkburn):
1830             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1831             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1832
1833 def checkshctrl(rpow):
1834     "Check shield control."
1835     skip(1)
1836     if withprob(0.998):
1837         prout(_("Shields lowered."))
1838         return False
1839     # Something bad has happened 
1840     prouts(_("***RED ALERT!  RED ALERT!"))
1841     skip(2)
1842     hit = rpow*game.shield/game.inshld
1843     game.energy -= rpow+hit*0.8
1844     game.shield -= hit*0.2
1845     if game.energy <= 0.0:
1846         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1847         skip(1)
1848         stars()
1849         finish(FPHASER)
1850         return True
1851     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1852     skip(2)
1853     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1854     icas = randrange(int(hit*0.012))
1855     skip(1)
1856     fry(0.8*hit)
1857     if icas:
1858         skip(1)
1859         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1860         prout(_("  %d casualties so far.\"") % icas)
1861         game.casual += icas
1862         game.state.crew -= icas
1863     skip(1)
1864     prout(_("Phaser energy dispersed by shields."))
1865     prout(_("Enemy unaffected."))
1866     overheat(rpow)
1867     return True;
1868
1869 def hittem(hits):
1870     "Register a phaser hit on Klingons and Romulans."
1871     nenhr2 = len(game.enemies); kk=0
1872     w = coord()
1873     skip(1)
1874     for (k, wham) in enumerate(hits):
1875         if wham==0:
1876             continue
1877         dustfac = randreal(0.9, 1.0)
1878         hit = wham*math.pow(dustfac,game.enemies[kk].kdist)
1879         kpini = game.enemies[kk].kpower
1880         kp = math.fabs(kpini)
1881         if PHASEFAC*hit < kp:
1882             kp = PHASEFAC*hit
1883         if game.enemies[kk].kpower < 0:
1884             game.enemies[kk].kpower -= -kp
1885         else:
1886             game.enemies[kk].kpower -= kp
1887         kpow = game.enemies[kk].kpower
1888         w = game.enemies[kk].kloc
1889         if hit > 0.005:
1890             if not damaged(DSRSENS):
1891                 boom(w)
1892             proutn(_("%d unit hit on ") % int(hit))
1893         else:
1894             proutn(_("Very small hit on "))
1895         ienm = game.quad[w.i][w.j]
1896         if ienm==IHQUEST:
1897             thing.angry = True
1898         proutn(crmena(False, ienm, "sector", w))
1899         skip(1)
1900         if kpow == 0:
1901             deadkl(w, ienm, w)
1902             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1903                 finish(FWON);           
1904             if game.alldone:
1905                 return
1906             kk -= 1     # don't do the increment
1907             continue
1908         else: # decide whether or not to emasculate klingon 
1909             if kpow>0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1910                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1911                 prout(_("   has just lost its firepower.\""))
1912                 game.enemies[kk].kpower = -kpow
1913         kk += 1
1914     return
1915
1916 def phasers():
1917     "Fire phasers at bad guys."
1918     hits = []
1919     kz = 0; k = 1; irec=0 # Cheating inhibitor 
1920     ifast = False; no = False; itarg = True; msgflag = True; rpow=0
1921     automode = "NOTSET"
1922     key=0
1923     skip(1)
1924     # SR sensors and Computer are needed for automode 
1925     if damaged(DSRSENS) or damaged(DCOMPTR):
1926         itarg = False
1927     if game.condition == "docked":
1928         prout(_("Phasers can't be fired through base shields."))
1929         scanner.chew()
1930         return
1931     if damaged(DPHASER):
1932         prout(_("Phaser control damaged."))
1933         scanner.chew()
1934         return
1935     if game.shldup:
1936         if damaged(DSHCTRL):
1937             prout(_("High speed shield control damaged."))
1938             scanner.chew()
1939             return
1940         if game.energy <= 200.0:
1941             prout(_("Insufficient energy to activate high-speed shield control."))
1942             scanner.chew()
1943             return
1944         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1945         ifast = True
1946     # Original code so convoluted, I re-did it all
1947     # (That was Tom Almy talking about the C code, I think -- ESR)
1948     while automode=="NOTSET":
1949         key=scanner.next()
1950         if key == "IHALPHA":
1951             if scanner.sees("manual"):
1952                 if len(game.enemies)==0:
1953                     prout(_("There is no enemy present to select."))
1954                     scanner.chew()
1955                     key = "IHEOL"
1956                     automode="AUTOMATIC"
1957                 else:
1958                     automode = "MANUAL"
1959                     key = scanner.next()
1960             elif scanner.sees("automatic"):
1961                 if (not itarg) and len(game.enemies) != 0:
1962                     automode = "FORCEMAN"
1963                 else:
1964                     if len(game.enemies)==0:
1965                         prout(_("Energy will be expended into space."))
1966                     automode = "AUTOMATIC"
1967                     key = scanner.next()
1968             elif scanner.sees("no"):
1969                 no = True
1970             else:
1971                 huh()
1972                 return
1973         elif key == "IHREAL":
1974             if len(game.enemies)==0:
1975                 prout(_("Energy will be expended into space."))
1976                 automode = "AUTOMATIC"
1977             elif not itarg:
1978                 automode = "FORCEMAN"
1979             else:
1980                 automode = "AUTOMATIC"
1981         else:
1982             # "IHEOL" 
1983             if len(game.enemies)==0:
1984                 prout(_("Energy will be expended into space."))
1985                 automode = "AUTOMATIC"
1986             elif not itarg:
1987                 automode = "FORCEMAN"
1988             else: 
1989                 proutn(_("Manual or automatic? "))
1990                 scanner.chew()
1991     avail = game.energy
1992     if ifast:
1993         avail -= 200.0
1994     if automode == "AUTOMATIC":
1995         if key == "IHALPHA" and scanner.sees("no"):
1996             no = True
1997             key = scanner.next()
1998         if key != "IHREAL" and len(game.enemies) != 0:
1999             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
2000         irec=0
2001         while True:
2002             scanner.chew()
2003             if not kz:
2004                 for i in range(len(game.enemies)):
2005                     irec += math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
2006             kz=1
2007             proutn(_("%d units required. ") % irec)
2008             scanner.chew()
2009             proutn(_("Units to fire= "))
2010             key = scanner.next()
2011             if key!="IHREAL":
2012                 return
2013             rpow = scanner.real
2014             if rpow > avail:
2015                 proutn(_("Energy available= %.2f") % avail)
2016                 skip(1)
2017                 key = "IHEOL"
2018             if not rpow > avail:
2019                 break
2020         if rpow<=0:
2021             # chicken out 
2022             scanner.chew()
2023             return
2024         key=scanner.next()
2025         if key == "IHALPHA" and scanner.sees("no"):
2026             no = True
2027         if ifast:
2028             game.energy -= 200; # Go and do it! 
2029             if checkshctrl(rpow):
2030                 return
2031         scanner.chew()
2032         game.energy -= rpow
2033         extra = rpow
2034         if len(game.enemies):
2035             extra = 0.0
2036             powrem = rpow
2037             for i in range(len(game.enemies)):
2038                 hits.append(0.0)
2039                 if powrem <= 0:
2040                     continue
2041                 hits[i] = math.fabs(game.enemies[i].kpower)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))
2042                 over = randreal(1.01, 1.06) * hits[i]
2043                 temp = powrem
2044                 powrem -= hits[i] + over
2045                 if powrem <= 0 and temp < hits[i]:
2046                     hits[i] = temp
2047                 if powrem <= 0:
2048                     over = 0.0
2049                 extra += over
2050             if powrem > 0.0:
2051                 extra += powrem
2052             hittem(hits)
2053             game.ididit = True
2054         if extra > 0 and not game.alldone:
2055             if game.tholian:
2056                 proutn(_("*** Tholian web absorbs "))
2057                 if len(game.enemies)>0:
2058                     proutn(_("excess "))
2059                 prout(_("phaser energy."))
2060             else:
2061                 prout(_("%d expended on empty space.") % int(extra))
2062     elif automode == "FORCEMAN":
2063         scanner.chew()
2064         key = "IHEOL"
2065         if damaged(DCOMPTR):
2066             prout(_("Battle computer damaged, manual fire only."))
2067         else:
2068             skip(1)
2069             prouts(_("---WORKING---"))
2070             skip(1)
2071             prout(_("Short-range-sensors-damaged"))
2072             prout(_("Insufficient-data-for-automatic-phaser-fire"))
2073             prout(_("Manual-fire-must-be-used"))
2074             skip(1)
2075     elif automode == "MANUAL":
2076         rpow = 0.0
2077         for k in range(len(game.enemies)):
2078             aim = game.enemies[k].kloc
2079             ienm = game.quad[aim.i][aim.j]
2080             if msgflag:
2081                 proutn(_("Energy available= %.2f") % (avail-0.006))
2082                 skip(1)
2083                 msgflag = False
2084                 rpow = 0.0
2085             if damaged(DSRSENS) and \
2086                not game.sector.distance(aim)<2**0.5 and ienm in (IHC, IHS):
2087                 prout(cramen(ienm) + _(" can't be located without short range scan."))
2088                 scanner.chew()
2089                 key = "IHEOL"
2090                 hits[k] = 0; # prevent overflow -- thanks to Alexei Voitenko 
2091                 k += 1
2092                 continue
2093             if key == "IHEOL":
2094                 scanner.chew()
2095                 if itarg and k > kz:
2096                     irec=(abs(game.enemies[k].kpower)/(PHASEFAC*math.pow(0.9,game.enemies[k].kdist))) * randreal(1.01, 1.06) + 1.0
2097                 kz = k
2098                 proutn("(")
2099                 if not damaged(DCOMPTR):
2100                     proutn("%d" % irec)
2101                 else:
2102                     proutn("??")
2103                 proutn(")  ")
2104                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))                
2105                 key = scanner.next()
2106             if key == "IHALPHA" and scanner.sees("no"):
2107                 no = True
2108                 key = scanner.next()
2109                 continue
2110             if key == "IHALPHA":
2111                 huh()
2112                 return
2113             if key == "IHEOL":
2114                 if k==1: # Let me say I'm baffled by this 
2115                     msgflag = True
2116                 continue
2117             if scanner.real < 0:
2118                 # abort out 
2119                 scanner.chew()
2120                 return
2121             hits[k] = scanner.real
2122             rpow += scanner.real
2123             # If total requested is too much, inform and start over 
2124             if rpow > avail:
2125                 prout(_("Available energy exceeded -- try again."))
2126                 scanner.chew()
2127                 return
2128             key = scanner.next(); # scan for next value 
2129             k += 1
2130         if rpow == 0.0:
2131             # zero energy -- abort 
2132             scanner.chew()
2133             return
2134         if key == "IHALPHA" and scanner.sees("no"):
2135             no = True
2136         game.energy -= rpow
2137         scanner.chew()
2138         if ifast:
2139             game.energy -= 200.0
2140             if checkshctrl(rpow):
2141                 return
2142         hittem(hits)
2143         game.ididit = True
2144      # Say shield raised or malfunction, if necessary 
2145     if game.alldone:
2146         return
2147     if ifast:
2148         skip(1)
2149         if no == 0:
2150             if withprob(0.99):
2151                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2152                 prouts(_("         CLICK   CLICK   POP  . . ."))
2153                 prout(_(" No response, sir!"))
2154                 game.shldup = False
2155             else:
2156                 prout(_("Shields raised."))
2157         else:
2158             game.shldup = False
2159     overheat(rpow);
2160
2161 # Code from events,c begins here.
2162
2163 # This isn't a real event queue a la BSD Trek yet -- you can only have one 
2164 # event of each type active at any given time.  Mostly these means we can 
2165 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2166 # BSD Trek, from which we swiped the idea, can have up to 5.
2167
2168 def unschedule(evtype):
2169     "Remove an event from the schedule."
2170     game.future[evtype].date = FOREVER
2171     return game.future[evtype]
2172
2173 def is_scheduled(evtype):
2174     "Is an event of specified type scheduled."
2175     return game.future[evtype].date != FOREVER
2176
2177 def scheduled(evtype):
2178     "When will this event happen?"
2179     return game.future[evtype].date
2180
2181 def schedule(evtype, offset):
2182     "Schedule an event of specified type."
2183     game.future[evtype].date = game.state.date + offset
2184     return game.future[evtype]
2185
2186 def postpone(evtype, offset):
2187     "Postpone a scheduled event."
2188     game.future[evtype].date += offset
2189
2190 def cancelrest():
2191     "Rest period is interrupted by event."
2192     if game.resting:
2193         skip(1)
2194         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2195         if ja() == True:
2196             game.resting = False
2197             game.optime = 0.0
2198             return True
2199     return False
2200
2201 def events():
2202     "Run through the event queue looking for things to do."
2203     i=0
2204     fintim = game.state.date + game.optime; yank=0
2205     ictbeam = False; istract = False
2206     w = coord(); hold = coord()
2207     ev = event(); ev2 = event()
2208
2209     def tractorbeam(yank):
2210         "Tractor-beaming cases merge here." 
2211         announce()
2212         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5) 
2213         skip(1)
2214         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2215         # If Kirk & Co. screwing around on planet, handle 
2216         atover(True) # atover(true) is Grab 
2217         if game.alldone:
2218             return
2219         if game.icraft: # Caught in Galileo? 
2220             finish(FSTRACTOR)
2221             return
2222         # Check to see if shuttle is aboard 
2223         if game.iscraft == "offship":
2224             skip(1)
2225             if withprob(0.5):
2226                 prout(_("Galileo, left on the planet surface, is captured"))
2227                 prout(_("by aliens and made into a flying McDonald's."))
2228                 game.damage[DSHUTTL] = -10
2229                 game.iscraft = "removed"
2230             else:
2231                 prout(_("Galileo, left on the planet surface, is well hidden."))
2232         if evcode == FSPY:
2233             game.quadrant = game.state.kscmdr
2234         else:
2235             game.quadrant = game.state.kcmdr[i]
2236         game.sector = randplace(QUADSIZE)
2237         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2238                % (game.quadrant, game.sector))
2239         if game.resting:
2240             prout(_("(Remainder of rest/repair period cancelled.)"))
2241             game.resting = False
2242         if not game.shldup:
2243             if not damaged(DSHIELD) and game.shield > 0:
2244                 doshield(shraise=True) # raise shields 
2245                 game.shldchg = False
2246             else:
2247                 prout(_("(Shields not currently useable.)"))
2248         newqad()
2249         # Adjust finish time to time of tractor beaming 
2250         fintim = game.state.date+game.optime
2251         attack(torps_ok=False)
2252         if not game.state.kcmdr:
2253             unschedule(FTBEAM)
2254         else: 
2255             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2256
2257     def destroybase():
2258         "Code merges here for any commander destroying a starbase." 
2259         # Not perfect, but will have to do 
2260         # Handle case where base is in same quadrant as starship 
2261         if game.battle == game.quadrant:
2262             game.state.chart[game.battle.i][game.battle.j].starbase = False
2263             game.quad[game.base.i][game.base.j] = IHDOT
2264             game.base.invalidate()
2265             newcnd()
2266             skip(1)
2267             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2268         elif game.state.baseq and communicating():
2269             # Get word via subspace radio 
2270             announce()
2271             skip(1)
2272             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2273             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2274             if game.isatb == 2: 
2275                 prout(_("the Klingon Super-Commander"))
2276             else:
2277                 prout(_("a Klingon Commander"))
2278             game.state.chart[game.battle.i][game.battle.j].starbase = False
2279         # Remove Starbase from galaxy 
2280         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2281         game.state.baseq = filter(lambda x: x != game.battle, game.state.baseq)
2282         if game.isatb == 2:
2283             # reinstate a commander's base attack 
2284             game.battle = hold
2285             game.isatb = 0
2286         else:
2287             game.battle.invalidate()
2288     if idebug:
2289         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2290         for i in range(1, NEVENTS):
2291             if   i == FSNOVA:  proutn("=== Supernova       ")
2292             elif i == FTBEAM:  proutn("=== T Beam          ")
2293             elif i == FSNAP:   proutn("=== Snapshot        ")
2294             elif i == FBATTAK: proutn("=== Base Attack     ")
2295             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2296             elif i == FSCMOVE: proutn("=== SC Move         ")
2297             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2298             elif i == FDSPROB: proutn("=== Probe Move      ")
2299             elif i == FDISTR:  proutn("=== Distress Call   ")
2300             elif i == FENSLV:  proutn("=== Enslavement     ")
2301             elif i == FREPRO:  proutn("=== Klingon Build   ")
2302             if is_scheduled(i):
2303                 prout("%.2f" % (scheduled(i)))
2304             else:
2305                 prout("never")
2306     radio_was_broken = damaged(DRADIO)
2307     hold.i = hold.j = 0
2308     while True:
2309         # Select earliest extraneous event, evcode==0 if no events 
2310         evcode = FSPY
2311         if game.alldone:
2312             return
2313         datemin = fintim
2314         for l in range(1, NEVENTS):
2315             if game.future[l].date < datemin:
2316                 evcode = l
2317                 if idebug:
2318                     prout("== Event %d fires" % evcode)
2319                 datemin = game.future[l].date
2320         xtime = datemin-game.state.date
2321         game.state.date = datemin
2322         # Decrement Federation resources and recompute remaining time 
2323         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2324         game.recompute()
2325         if game.state.remtime <=0:
2326             finish(FDEPLETE)
2327             return
2328         # Any crew left alive? 
2329         if game.state.crew <=0:
2330             finish(FCREW)
2331             return
2332         # Is life support adequate? 
2333         if damaged(DLIFSUP) and game.condition != "docked":
2334             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2335                 finish(FLIFESUP)
2336                 return
2337             game.lsupres -= xtime
2338             if game.damage[DLIFSUP] <= xtime:
2339                 game.lsupres = game.inlsr
2340         # Fix devices 
2341         repair = xtime
2342         if game.condition == "docked":
2343             repair /= game.docfac
2344         # Don't fix Deathray here 
2345         for l in range(NDEVICES):
2346             if game.damage[l] > 0.0 and l != DDRAY:
2347                 if game.damage[l]-repair > 0.0:
2348                     game.damage[l] -= repair
2349                 else:
2350                     game.damage[l] = 0.0
2351         # If radio repaired, update star chart and attack reports 
2352         if radio_was_broken and not damaged(DRADIO):
2353             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2354             prout(_("   surveillance reports are coming in."))
2355             skip(1)
2356             if not game.iseenit:
2357                 attackreport(False)
2358                 game.iseenit = True
2359             rechart()
2360             prout(_("   The star chart is now up to date.\""))
2361             skip(1)
2362         # Cause extraneous event EVCODE to occur 
2363         game.optime -= xtime
2364         if evcode == FSNOVA: # Supernova 
2365             announce()
2366             supernova(None)
2367             schedule(FSNOVA, expran(0.5*game.intime))
2368             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2369                 return
2370         elif evcode == FSPY: # Check with spy to see if SC should tractor beam 
2371             if game.state.nscrem == 0 or \
2372                 ictbeam or istract or \
2373                 game.condition=="docked" or game.isatb==1 or game.iscate:
2374                 return
2375             if game.ientesc or \
2376                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2377                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2378                 (damaged(DSHIELD) and \
2379                  (game.energy < 2500 or damaged(DPHASER)) and \
2380                  (game.torps < 5 or damaged(DPHOTON))):
2381                 # Tractor-beam her! 
2382                 istract = ictbeam = True
2383                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2384             else:
2385                 return
2386         elif evcode == FTBEAM: # Tractor beam 
2387             if not game.state.kcmdr:
2388                 unschedule(FTBEAM)
2389                 continue
2390             i = randrange(len(game.state.kcmdr))
2391             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2392             if istract or game.condition == "docked" or yank == 0:
2393                 # Drats! Have to reschedule 
2394                 schedule(FTBEAM, 
2395                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2396                 continue
2397             ictbeam = True
2398             tractorbeam(yank)
2399         elif evcode == FSNAP: # Snapshot of the universe (for time warp) 
2400             game.snapsht = copy.deepcopy(game.state)
2401             game.state.snap = True
2402             schedule(FSNAP, expran(0.5 * game.intime))
2403         elif evcode == FBATTAK: # Commander attacks starbase 
2404             if not game.state.kcmdr or not game.state.baseq:
2405                 # no can do 
2406                 unschedule(FBATTAK)
2407                 unschedule(FCDBAS)
2408                 continue
2409             try:
2410                 for ibq in game.state.baseq:
2411                    for cmdr in game.state.kcmdr: 
2412                        if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2413                            raise ibq
2414                 else:
2415                     # no match found -- try later 
2416                     schedule(FBATTAK, expran(0.3*game.intime))
2417                     unschedule(FCDBAS)
2418                     continue
2419             except coord:
2420                 pass
2421             # commander + starbase combination found -- launch attack 
2422             game.battle = ibq
2423             schedule(FCDBAS, randreal(1.0, 4.0))
2424             if game.isatb: # extra time if SC already attacking 
2425                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2426             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2427             game.iseenit = False
2428             if not communicating():
2429                 continue # No warning :-( 
2430             game.iseenit = True
2431             announce()
2432             skip(1)
2433             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2434             prout(_("   reports that it is under attack and that it can"))
2435             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2436             if cancelrest():
2437                 return
2438         elif evcode == FSCDBAS: # Supercommander destroys base 
2439             unschedule(FSCDBAS)
2440             game.isatb = 2
2441             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase: 
2442                 continue # WAS RETURN! 
2443             hold = game.battle
2444             game.battle = game.state.kscmdr
2445             destroybase()
2446         elif evcode == FCDBAS: # Commander succeeds in destroying base 
2447             if evcode==FCDBAS:
2448                 unschedule(FCDBAS)
2449                 if not game.state.baseq() \
2450                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2451                     game.battle.invalidate()
2452                     continue
2453                 # find the lucky pair 
2454                 for cmdr in game.state.kcmdr:
2455                     if cmdr == game.battle: 
2456                         break
2457                 else:
2458                     # No action to take after all 
2459                     continue
2460             destroybase()
2461         elif evcode == FSCMOVE: # Supercommander moves 
2462             schedule(FSCMOVE, 0.2777)
2463             if not game.ientesc and not istract and game.isatb != 1 and \
2464                    (not game.iscate or not game.justin): 
2465                 supercommander()
2466         elif evcode == FDSPROB: # Move deep space probe 
2467             schedule(FDSPROB, 0.01)
2468             game.probex += game.probeinx
2469             game.probey += game.probeiny
2470             i = (int)(game.probex/QUADSIZE +0.05)
2471             j = (int)(game.probey/QUADSIZE + 0.05)
2472             if game.probec.i != i or game.probec.j != j:
2473                 game.probec.i = i
2474                 game.probec.j = j
2475                 if not VALID_QUADRANT(i, j) or \
2476                     game.state.galaxy[game.probec.i][game.probec.j].supernova:
2477                     # Left galaxy or ran into supernova
2478                     if comunicating():
2479                         announce()
2480                         skip(1)
2481                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2482                         if not VALID_QUADRANT(j, i):
2483                             proutn(_("has left the galaxy"))
2484                         else:
2485                             proutn(_("is no longer transmitting"))
2486                         prout(".\"")
2487                     unschedule(FDSPROB)
2488                     continue
2489                 if not communicating():
2490                     announce()
2491                     skip(1)
2492                     proutn(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probec)
2493             pdest = game.state.galaxy[game.probec.i][game.probec.j]
2494             # Update star chart if Radio is working or have access to radio
2495             if communicating():
2496                 chp = game.state.chart[game.probec.i][game.probec.j]
2497                 chp.klingons = pdest.klingons
2498                 chp.starbase = pdest.starbase
2499                 chp.stars = pdest.stars
2500                 pdest.charted = True
2501             game.proben -= 1 # One less to travel
2502             if game.proben == 0 and game.isarmed and pdest.stars:
2503                 # lets blow the sucker! 
2504                 supernova(game.probec)
2505                 unschedule(FDSPROB)
2506                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova: 
2507                     return
2508         elif evcode == FDISTR: # inhabited system issues distress call 
2509             unschedule(FDISTR)
2510             # try a whole bunch of times to find something suitable 
2511             for i in range(100):
2512                 # need a quadrant which is not the current one,
2513                 # which has some stars which are inhabited and
2514                 # not already under attack, which is not
2515                 # supernova'ed, and which has some Klingons in it
2516                 w = randplace(GALSIZE)
2517                 q = game.state.galaxy[w.i][w.j]
2518                 if not (game.quadrant == w or q.planet == None or \
2519                       not q.planet.inhabited or \
2520                       q.supernova or q.status!="secure" or q.klingons<=0):
2521                     break
2522             else:
2523                 # can't seem to find one; ignore this call 
2524                 if idebug:
2525                     prout("=== Couldn't find location for distress event.")
2526                 continue
2527             # got one!!  Schedule its enslavement 
2528             ev = schedule(FENSLV, expran(game.intime))
2529             ev.quadrant = w
2530             q.status = "distressed"
2531             # tell the captain about it if we can 
2532             if communicating():
2533                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2534                         % (q.planet, `w`))
2535                 prout(_("by a Klingon invasion fleet."))
2536                 if cancelrest():
2537                     return
2538         elif evcode == FENSLV:          # starsystem is enslaved 
2539             ev = unschedule(FENSLV)
2540             # see if current distress call still active 
2541             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2542             if q.klingons <= 0:
2543                 q.status = "secure"
2544                 continue
2545             q.status = "enslaved"
2546
2547             # play stork and schedule the first baby 
2548             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2549             ev2.quadrant = ev.quadrant
2550
2551             # report the disaster if we can 
2552             if communicating():
2553                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2554                         q.planet)
2555                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2556         elif evcode == FREPRO:          # Klingon reproduces 
2557             # If we ever switch to a real event queue, we'll need to
2558             # explicitly retrieve and restore the x and y.
2559             ev = schedule(FREPRO, expran(1.0 * game.intime))
2560             # see if current distress call still active 
2561             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2562             if q.klingons <= 0:
2563                 q.status = "secure"
2564                 continue
2565             if game.state.remkl >=MAXKLGAME:
2566                 continue                # full right now 
2567             # reproduce one Klingon 
2568             w = ev.quadrant
2569             if game.klhere >= MAXKLQUAD:
2570                 try:
2571                     # this quadrant not ok, pick an adjacent one 
2572                     for i in range(w.i - 1, w.i + 2):
2573                         for j in range(w.j - 1, w.j + 2):
2574                             if not VALID_QUADRANT(i, j):
2575                                 continue
2576                             q = game.state.galaxy[w.i][w.j]
2577                             # check for this quad ok (not full & no snova) 
2578                             if q.klingons >= MAXKLQUAD or q.supernova:
2579                                 continue
2580                             raise "FOUNDIT"
2581                     else:
2582                         continue        # search for eligible quadrant failed
2583                 except "FOUNDIT":
2584                     w.i = i; w.j = j
2585             # deliver the child 
2586             game.state.remkl += 1
2587             q.klingons += 1
2588             if game.quadrant == w:
2589                 game.klhere += 1
2590                 game.enemies.append(newkling())
2591             # recompute time left
2592             game.recompute()
2593             # report the disaster if we can 
2594             if communicating():
2595                 if game.quadrant == w:
2596                     prout(_("Spock- sensors indicate the Klingons have"))
2597                     prout(_("launched a warship from %s.") % q.planet)
2598                 else:
2599                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2600                     if q.planet != None:
2601                         proutn(_("near %s") % q.planet)
2602                     prout(_("in Quadrant %s.") % w)
2603                                 
2604 def wait():
2605     "Wait on events."
2606     game.ididit = False
2607     while True:
2608         key = scanner.next()
2609         if key  != "IHEOL":
2610             break
2611         proutn(_("How long? "))
2612     scanner.chew()
2613     if key != "IHREAL":
2614         huh()
2615         return
2616     origTime = delay = scanner.real
2617     if delay <= 0.0:
2618         return
2619     if delay >= game.state.remtime or len(game.enemies) != 0:
2620         proutn(_("Are you sure? "))
2621         if ja() == False:
2622             return
2623     # Alternate resting periods (events) with attacks 
2624     game.resting = True
2625     while True:
2626         if delay <= 0:
2627             game.resting = False
2628         if not game.resting:
2629             prout(_("%d stardates left.") % int(game.state.remtime))
2630             return
2631         temp = game.optime = delay
2632         if len(game.enemies):
2633             rtime = randreal(1.0, 2.0)
2634             if rtime < temp:
2635                 temp = rtime
2636             game.optime = temp
2637         if game.optime < delay:
2638             attack(torps_ok=False)
2639         if game.alldone:
2640             return
2641         events()
2642         game.ididit = True
2643         if game.alldone:
2644             return
2645         delay -= temp
2646         # Repair Deathray if long rest at starbase 
2647         if origTime-delay >= 9.99 and game.condition == "docked":
2648             game.damage[DDRAY] = 0.0
2649         # leave if quadrant supernovas
2650         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2651             break
2652     game.resting = False
2653     game.optime = 0
2654
2655 def nova(nov):
2656     "Star goes nova." 
2657     course = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2658     newc = coord(); neighbor = coord(); bump = coord(0, 0)
2659     if withprob(0.05):
2660         # Wow! We've supernova'ed 
2661         supernova(game.quadrant)
2662         return
2663     # handle initial nova 
2664     game.quad[nov.i][nov.j] = IHDOT
2665     prout(crmena(False, IHSTAR, "sector", nov) + _(" novas."))
2666     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2667     game.state.starkl += 1
2668     # Set up queue to recursively trigger adjacent stars 
2669     hits = [nov]
2670     kount = 0
2671     while hits:
2672         offset = coord()
2673         start = hits.pop()
2674         for offset.i in range(-1, 1+1):
2675             for offset.j in range(-1, 1+1):
2676                 if offset.j==0 and offset.i==0:
2677                     continue
2678                 neighbor = start + offset
2679                 if not VALID_SECTOR(neighbor.j, neighbor.i):
2680                     continue
2681                 iquad = game.quad[neighbor.i][neighbor.j]
2682                 # Empty space ends reaction
2683                 if iquad in (IHDOT, IHQUEST, IHBLANK, IHT, IHWEB):
2684                     pass
2685                 elif iquad == IHSTAR: # Affect another star 
2686                     if withprob(0.05):
2687                         # This star supernovas 
2688                         supernova(game.quadrant)
2689                         return
2690                     else:
2691                         hits.append(neighbor)
2692                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2693                         game.state.starkl += 1
2694                         proutn(crmena(True, IHSTAR, "sector", neighbor))
2695                         prout(_(" novas."))
2696                         game.quad[neighbor.i][neighbor.j] = IHDOT
2697                         kount += 1
2698                 elif iquad in (IHP, IHW): # Destroy planet 
2699                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2700                     if iquad == IHP:
2701                         game.state.nplankl += 1
2702                     else:
2703                         game.state.worldkl += 1
2704                     prout(crmena(True, IHB, "sector", neighbor) + _(" destroyed."))
2705                     game.iplnet.pclass = "destroyed"
2706                     game.iplnet = None
2707                     game.plnet.invalidate()
2708                     if game.landed:
2709                         finish(FPNOVA)
2710                         return
2711                     game.quad[neighbor.i][neighbor.j] = IHDOT
2712                 elif iquad == IHB: # Destroy base 
2713                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2714                     game.state.baseq = filter(lambda x: x!= game.quadrant, game.state.baseq)
2715                     game.base.invalidate()
2716                     game.state.basekl += 1
2717                     newcnd()
2718                     prout(crmena(True, IHB, "sector", neighbor) + _(" destroyed."))
2719                     game.quad[neighbor.i][neighbor.j] = IHDOT
2720                 elif iquad in (IHE, IHF): # Buffet ship 
2721                     prout(_("***Starship buffeted by nova."))
2722                     if game.shldup:
2723                         if game.shield >= 2000.0:
2724                             game.shield -= 2000.0
2725                         else:
2726                             diff = 2000.0 - game.shield
2727                             game.energy -= diff
2728                             game.shield = 0.0
2729                             game.shldup = False
2730                             prout(_("***Shields knocked out."))
2731                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2732                     else:
2733                         game.energy -= 2000.0
2734                     if game.energy <= 0:
2735                         finish(FNOVA)
2736                         return
2737                     # add in course nova contributes to kicking starship
2738                     bump += (game.sector-hits[mm]).sgn()
2739                 elif iquad == IHK: # kill klingon 
2740                     deadkl(neighbor, iquad, neighbor)
2741                 elif iquad in (IHC,IHS,IHR): # Damage/destroy big enemies 
2742                     for ll in range(len(game.enemies)):
2743                         if game.enemies[ll].kloc == neighbor:
2744                             break
2745                     game.enemies[ll].kpower -= 800.0 # If firepower is lost, die 
2746                     if game.enemies[ll].kpower <= 0.0:
2747                         deadkl(neighbor, iquad, neighbor)
2748                         break
2749                     newc = neighbor + neighbor - hits[mm]
2750                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2751                     if not VALID_SECTOR(newc.i, newc.j):
2752                         # can't leave quadrant 
2753                         skip(1)
2754                         break
2755                     iquad1 = game.quad[newc.i][newc.j]
2756                     if iquad1 == IHBLANK:
2757                         proutn(_(", blasted into ") + crmena(False, IHBLANK, "sector", newc))
2758                         skip(1)
2759                         deadkl(neighbor, iquad, newc)
2760                         break
2761                     if iquad1 != IHDOT:
2762                         # can't move into something else 
2763                         skip(1)
2764                         break
2765                     proutn(_(", buffeted to Sector %s") % newc)
2766                     game.quad[neighbor.i][neighbor.j] = IHDOT
2767                     game.quad[newc.i][newc.j] = iquad
2768                     game.enemies[ll].move(newc)
2769     # Starship affected by nova -- kick it away. 
2770     game.dist = kount*0.1
2771     game.direc = course[3*(bump.i+1)+bump.j+2]
2772     if game.direc == 0.0:
2773         game.dist = 0.0
2774     if game.dist == 0.0:
2775         return
2776     game.optime = 10.0*game.dist/16.0
2777     skip(1)
2778     prout(_("Force of nova displaces starship."))
2779     imove(novapush=True)
2780     game.optime = 10.0*game.dist/16.0
2781     return
2782         
2783 def supernova(w):
2784     "Star goes supernova."
2785     num = 0; npdead = 0
2786     if w != None: 
2787         nq = copy.copy(w)
2788     else:
2789         # Scheduled supernova -- select star at random. 
2790         stars = 0
2791         nq = coord()
2792         for nq.i in range(GALSIZE):
2793             for nq.j in range(GALSIZE):
2794                 stars += game.state.galaxy[nq.i][nq.j].stars
2795         if stars == 0:
2796             return # nothing to supernova exists 
2797         num = randrange(stars) + 1
2798         for nq.i in range(GALSIZE):
2799             for nq.j in range(GALSIZE):
2800                 num -= game.state.galaxy[nq.i][nq.j].stars
2801                 if num <= 0:
2802                     break
2803             if num <=0:
2804                 break
2805         if idebug:
2806             proutn("=== Super nova here?")
2807             if ja() == True:
2808                 nq = game.quadrant
2809     if not nq == game.quadrant or game.justin:
2810         # it isn't here, or we just entered (treat as enroute) 
2811         if communicating():
2812             skip(1)
2813             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2814             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2815     else:
2816         ns = coord()
2817         # we are in the quadrant! 
2818         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2819         for ns.i in range(QUADSIZE):
2820             for ns.j in range(QUADSIZE):
2821                 if game.quad[ns.i][ns.j]==IHSTAR:
2822                     num -= 1
2823                     if num==0:
2824                         break
2825             if num==0:
2826                 break
2827         skip(1)
2828         prouts(_("***RED ALERT!  RED ALERT!"))
2829         skip(1)
2830         prout(_("***Incipient supernova detected at Sector %s") % ns)
2831         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2832             proutn(_("Emergency override attempts t"))
2833             prouts("***************")
2834             skip(1)
2835             stars()
2836             game.alldone = True
2837     # destroy any Klingons in supernovaed quadrant
2838     kldead = game.state.galaxy[nq.i][nq.j].klingons
2839     game.state.galaxy[nq.i][nq.j].klingons = 0
2840     if nq == game.state.kscmdr:
2841         # did in the Supercommander! 
2842         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2843         game.iscate = False
2844         unschedule(FSCMOVE)
2845         unschedule(FSCDBAS)
2846     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2847     comkills = len(game.state.kcmdr) - len(survivors)
2848     game.state.kcmdr = survivors
2849     kldead -= comkills
2850     if not game.state.kcmdr:
2851         unschedule(FTBEAM)
2852     game.state.remkl -= kldead
2853     # destroy Romulans and planets in supernovaed quadrant 
2854     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2855     game.state.galaxy[nq.i][nq.j].romulans = 0
2856     game.state.nromrem -= nrmdead
2857     # Destroy planets 
2858     for loop in range(game.inplan):
2859         if game.state.planets[loop].quadrant == nq:
2860             game.state.planets[loop].pclass = "destroyed"
2861             npdead += 1
2862     # Destroy any base in supernovaed quadrant
2863     game.state.baseq = filter(lambda x: x != nq, game.state.baseq)
2864     # If starship caused supernova, tally up destruction 
2865     if w != None:
2866         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2867         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2868         game.state.nplankl += npdead
2869     # mark supernova in galaxy and in star chart 
2870     if game.quadrant == nq or communicating():
2871         game.state.galaxy[nq.i][nq.j].supernova = True
2872     # If supernova destroys last Klingons give special message 
2873     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2874         skip(2)
2875         if w == None:
2876             prout(_("Lucky you!"))
2877         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2878         finish(FWON)
2879         return
2880     # if some Klingons remain, continue or die in supernova 
2881     if game.alldone:
2882         finish(FSNOVAED)
2883     return
2884
2885 # Code from finish.c ends here.
2886
2887 def selfdestruct():
2888     "Self-destruct maneuver. Finish with a BANG!" 
2889     scanner.chew()
2890     if damaged(DCOMPTR):
2891         prout(_("Computer damaged; cannot execute destruct sequence."))
2892         return
2893     prouts(_("---WORKING---")); skip(1)
2894     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2895     prouts("   10"); skip(1)
2896     prouts("       9"); skip(1)
2897     prouts("          8"); skip(1)
2898     prouts("             7"); skip(1)
2899     prouts("                6"); skip(1)
2900     skip(1)
2901     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2902     skip(1)
2903     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2904     skip(1)
2905     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2906     skip(1)
2907     scanner.next()
2908     scanner.chew()
2909     if game.passwd != scanner.token:
2910         prouts(_("PASSWORD-REJECTED;"))
2911         skip(1)
2912         prouts(_("CONTINUITY-EFFECTED"))
2913         skip(2)
2914         return
2915     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2916     prouts("                   5"); skip(1)
2917     prouts("                      4"); skip(1)
2918     prouts("                         3"); skip(1)
2919     prouts("                            2"); skip(1)
2920     prouts("                              1"); skip(1)
2921     if withprob(0.15):
2922         prouts(_("GOODBYE-CRUEL-WORLD"))
2923         skip(1)
2924     kaboom()
2925
2926 def kaboom():
2927     stars()
2928     if game.ship==IHE:
2929         prouts("***")
2930     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2931     skip(1)
2932     stars()
2933     skip(1)
2934     if len(game.enemies) != 0:
2935         whammo = 25.0 * game.energy
2936         l=1
2937         while l <= len(game.enemies):
2938             if game.enemies[l].kpower*game.enemies[l].kdist <= whammo: 
2939                 deadkl(game.enemies[l].kloc, game.quad[game.enemies[l].kloc.i][game.enemies[l].kloc.j], game.enemies[l].kloc)
2940             l += 1
2941     finish(FDILITHIUM)
2942                                 
2943 def killrate():
2944     "Compute our rate of kils over time."
2945     elapsed = game.state.date - game.indate
2946     if elapsed == 0:    # Avoid divide-by-zero error if calculated on turn 0
2947         return 0
2948     else:
2949         starting = (game.inkling + game.incom + game.inscom)
2950         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2951         return (starting - remaining)/elapsed
2952
2953 def badpoints():
2954     "Compute demerits."
2955     badpt = 5.0*game.state.starkl + \
2956             game.casual + \
2957             10.0*game.state.nplankl + \
2958             300*game.state.nworldkl + \
2959             45.0*game.nhelp +\
2960             100.0*game.state.basekl +\
2961             3.0*game.abandoned
2962     if game.ship == IHF:
2963         badpt += 100.0
2964     elif game.ship == None:
2965         badpt += 200.0
2966     return badpt
2967
2968 def finish(ifin):
2969     # end the game, with appropriate notfications 
2970     igotit = False
2971     game.alldone = True
2972     skip(3)
2973     prout(_("It is stardate %.1f.") % game.state.date)
2974     skip(1)
2975     if ifin == FWON: # Game has been won
2976         if game.state.nromrem != 0:
2977             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2978                   game.state.nromrem)
2979
2980         prout(_("You have smashed the Klingon invasion fleet and saved"))
2981         prout(_("the Federation."))
2982         game.gamewon = True
2983         if game.alive:
2984             badpt = badpoints()
2985             if badpt < 100.0:
2986                 badpt = 0.0     # Close enough!
2987             # killsPerDate >= RateMax
2988             if game.state.date-game.indate < 5.0 or \
2989                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2990                 skip(1)
2991                 prout(_("In fact, you have done so well that Starfleet Command"))
2992                 if game.skill == SKILL_NOVICE:
2993                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2994                 elif game.skill == SKILL_FAIR:
2995                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2996                 elif game.skill == SKILL_GOOD:
2997                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2998                 elif game.skill == SKILL_EXPERT:
2999                     prout(_("promotes you to Commodore Emeritus."))
3000                     skip(1)
3001                     prout(_("Now that you think you're really good, try playing"))
3002                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
3003                 elif game.skill == SKILL_EMERITUS:
3004                     skip(1)
3005                     proutn(_("Computer-  "))
3006                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
3007                     skip(2)
3008                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
3009                     skip(1)
3010                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3011                     skip(1)
3012                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3013                     skip(1)
3014                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3015                     skip(1)
3016                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
3017                     skip(2)
3018                     prout(_("Now you can retire and write your own Star Trek game!"))
3019                     skip(1)
3020                 elif game.skill >= SKILL_EXPERT:
3021                     if game.thawed and not idebug:
3022                         prout(_("You cannot get a citation, so..."))
3023                     else:
3024                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
3025                         scanner.chew()
3026                         if ja() == True:
3027                             igotit = True
3028             # Only grant long life if alive (original didn't!)
3029             skip(1)
3030             prout(_("LIVE LONG AND PROSPER."))
3031         score()
3032         if igotit:
3033             plaque()        
3034         return
3035     elif ifin == FDEPLETE: # Federation Resources Depleted
3036         prout(_("Your time has run out and the Federation has been"))
3037         prout(_("conquered.  Your starship is now Klingon property,"))
3038         prout(_("and you are put on trial as a war criminal.  On the"))
3039         proutn(_("basis of your record, you are "))
3040         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
3041             prout(_("acquitted."))
3042             skip(1)
3043             prout(_("LIVE LONG AND PROSPER."))
3044         else:
3045             prout(_("found guilty and"))
3046             prout(_("sentenced to death by slow torture."))
3047             game.alive = False
3048         score()
3049         return
3050     elif ifin == FLIFESUP:
3051         prout(_("Your life support reserves have run out, and"))
3052         prout(_("you die of thirst, starvation, and asphyxiation."))
3053         prout(_("Your starship is a derelict in space."))
3054     elif ifin == FNRG:
3055         prout(_("Your energy supply is exhausted."))
3056         skip(1)
3057         prout(_("Your starship is a derelict in space."))
3058     elif ifin == FBATTLE:
3059         prout(_("The %s has been destroyed in battle.") % crmshp())
3060         skip(1)
3061         prout(_("Dulce et decorum est pro patria mori."))
3062     elif ifin == FNEG3:
3063         prout(_("You have made three attempts to cross the negative energy"))
3064         prout(_("barrier which surrounds the galaxy."))
3065         skip(1)
3066         prout(_("Your navigation is abominable."))
3067         score()
3068     elif ifin == FNOVA:
3069         prout(_("Your starship has been destroyed by a nova."))
3070         prout(_("That was a great shot."))
3071         skip(1)
3072     elif ifin == FSNOVAED:
3073         prout(_("The %s has been fried by a supernova.") % crmshp())
3074         prout(_("...Not even cinders remain..."))
3075     elif ifin == FABANDN:
3076         prout(_("You have been captured by the Klingons. If you still"))
3077         prout(_("had a starbase to be returned to, you would have been"))
3078         prout(_("repatriated and given another chance. Since you have"))
3079         prout(_("no starbases, you will be mercilessly tortured to death."))
3080     elif ifin == FDILITHIUM:
3081         prout(_("Your starship is now an expanding cloud of subatomic particles"))
3082     elif ifin == FMATERIALIZE:
3083         prout(_("Starbase was unable to re-materialize your starship."))
3084         prout(_("Sic transit gloria mundi"))
3085     elif ifin == FPHASER:
3086         prout(_("The %s has been cremated by its own phasers.") % crmshp())
3087     elif ifin == FLOST:
3088         prout(_("You and your landing party have been"))
3089         prout(_("converted to energy, disipating through space."))
3090     elif ifin == FMINING:
3091         prout(_("You are left with your landing party on"))
3092         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3093         skip(1)
3094         prout(_("They are very fond of \"Captain Kirk\" soup."))
3095         skip(1)
3096         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3097     elif ifin == FDPLANET:
3098         prout(_("You and your mining party perish."))
3099         skip(1)
3100         prout(_("That was a great shot."))
3101         skip(1)
3102     elif ifin == FSSC:
3103         prout(_("The Galileo is instantly annihilated by the supernova."))
3104         prout(_("You and your mining party are atomized."))
3105         skip(1)
3106         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3107         prout(_("joins the Romulans, wreaking terror on the Federation."))
3108     elif ifin == FPNOVA:
3109         prout(_("You and your mining party are atomized."))
3110         skip(1)
3111         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3112         prout(_("joins the Romulans, wreaking terror on the Federation."))
3113     elif ifin == FSTRACTOR:
3114         prout(_("The shuttle craft Galileo is also caught,"))
3115         prout(_("and breaks up under the strain."))
3116         skip(1)
3117         prout(_("Your debris is scattered for millions of miles."))
3118         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3119     elif ifin == FDRAY:
3120         prout(_("The mutants attack and kill Spock."))
3121         prout(_("Your ship is captured by Klingons, and"))
3122         prout(_("your crew is put on display in a Klingon zoo."))
3123     elif ifin == FTRIBBLE:
3124         prout(_("Tribbles consume all remaining water,"))
3125         prout(_("food, and oxygen on your ship."))
3126         skip(1)
3127         prout(_("You die of thirst, starvation, and asphyxiation."))
3128         prout(_("Your starship is a derelict in space."))
3129     elif ifin == FHOLE:
3130         prout(_("Your ship is drawn to the center of the black hole."))
3131         prout(_("You are crushed into extremely dense matter."))
3132     elif ifin == FCREW:
3133         prout(_("Your last crew member has died."))
3134     if game.ship == IHF:
3135         game.ship = None
3136     elif game.ship == IHE:
3137         game.ship = IHF
3138     game.alive = False
3139     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
3140         goodies = game.state.remres/game.inresor
3141         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3142         if goodies/baddies >= randreal(1.0, 1.5):
3143             prout(_("As a result of your actions, a treaty with the Klingon"))
3144             prout(_("Empire has been signed. The terms of the treaty are"))
3145             if goodies/baddies >= randreal(3.0):
3146                 prout(_("favorable to the Federation."))
3147                 skip(1)
3148                 prout(_("Congratulations!"))
3149             else:
3150                 prout(_("highly unfavorable to the Federation."))
3151         else:
3152             prout(_("The Federation will be destroyed."))
3153     else:
3154         prout(_("Since you took the last Klingon with you, you are a"))
3155         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3156         prout(_("statue in your memory. Rest in peace, and try not"))
3157         prout(_("to think about pigeons."))
3158         game.gamewon = True
3159     score()
3160
3161 def score():
3162     "Compute player's score."
3163     timused = game.state.date - game.indate
3164     iskill = game.skill
3165     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
3166         timused = 5.0
3167     perdate = killrate()
3168     ithperd = 500*perdate + 0.5
3169     iwon = 0
3170     if game.gamewon:
3171         iwon = 100*game.skill
3172     if game.ship == IHE: 
3173         klship = 0
3174     elif game.ship == IHF: 
3175         klship = 1
3176     else:
3177         klship = 2
3178     if not game.gamewon:
3179         game.state.nromrem = 0 # None captured if no win
3180     iscore = 10*(game.inkling - game.state.remkl) \
3181              + 50*(game.incom - len(game.state.kcmdr)) \
3182              + ithperd + iwon \
3183              + 20*(game.inrom - game.state.nromrem) \
3184              + 200*(game.inscom - game.state.nscrem) \
3185              - game.state.nromrem \
3186              - badpoints()
3187     if not game.alive:
3188         iscore -= 200
3189     skip(2)
3190     prout(_("Your score --"))
3191     if game.inrom - game.state.nromrem:
3192         prout(_("%6d Romulans destroyed                 %5d") %
3193               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3194     if game.state.nromrem:
3195         prout(_("%6d Romulans captured                  %5d") %
3196               (game.state.nromrem, game.state.nromrem))
3197     if game.inkling - game.state.remkl:
3198         prout(_("%6d ordinary Klingons destroyed        %5d") %
3199               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
3200     if game.incom - len(game.state.kcmdr):
3201         prout(_("%6d Klingon commanders destroyed       %5d") %
3202               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3203     if game.inscom - game.state.nscrem:
3204         prout(_("%6d Super-Commander destroyed          %5d") %
3205               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3206     if ithperd:
3207         prout(_("%6.2f Klingons per stardate              %5d") %
3208               (perdate, ithperd))
3209     if game.state.starkl:
3210         prout(_("%6d stars destroyed by your action     %5d") %
3211               (game.state.starkl, -5*game.state.starkl))
3212     if game.state.nplankl:
3213         prout(_("%6d planets destroyed by your action   %5d") %
3214               (game.state.nplankl, -10*game.state.nplankl))
3215     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3216         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3217               (game.state.nplankl, -300*game.state.nworldkl))
3218     if game.state.basekl:
3219         prout(_("%6d bases destroyed by your action     %5d") %
3220               (game.state.basekl, -100*game.state.basekl))
3221     if game.nhelp:
3222         prout(_("%6d calls for help from starbase       %5d") %
3223               (game.nhelp, -45*game.nhelp))
3224     if game.casual:
3225         prout(_("%6d casualties incurred                %5d") %
3226               (game.casual, -game.casual))
3227     if game.abandoned:
3228         prout(_("%6d crew abandoned in space            %5d") %
3229               (game.abandoned, -3*game.abandoned))
3230     if klship:
3231         prout(_("%6d ship(s) lost or destroyed          %5d") %
3232               (klship, -100*klship))
3233     if not game.alive:
3234         prout(_("Penalty for getting yourself killed        -200"))
3235     if game.gamewon:
3236         proutn(_("Bonus for winning "))
3237         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3238         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3239         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3240         elif game.skill ==  SKILL_EXPERT:       proutn(_("Expert game  "))
3241         elif game.skill ==  SKILL_EMERITUS:     proutn(_("Emeritus game"))
3242         prout("           %5d" % iwon)
3243     skip(1)
3244     prout(_("TOTAL SCORE                               %5d") % iscore)
3245
3246 def plaque():
3247     "Emit winner's commemmorative plaque." 
3248     skip(2)
3249     while True:
3250         proutn(_("File or device name for your plaque: "))
3251         winner = cgetline()
3252         try:
3253             fp = open(winner, "w")
3254             break
3255         except IOError:
3256             prout(_("Invalid name."))
3257
3258     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3259     winner = cgetline()
3260     # The 38 below must be 64 for 132-column paper 
3261     nskip = 38 - len(winner)/2
3262     fp.write("\n\n\n\n")
3263     # --------DRAW ENTERPRISE PICTURE. 
3264     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3265     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3266     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3267     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3268     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3269     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3270     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3271     fp.write("                                      EEE           E          E            E  E\n")
3272     fp.write("                                                       E         E          E  E\n")
3273     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3274     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3275     fp.write("                                                    :E    :                 EEEE       E\n")
3276     fp.write("                                                   .-E   -:-----                       E\n")
3277     fp.write("                                                    :E    :                            E\n")
3278     fp.write("                                                      EE  :                    EEEEEEEE\n")
3279     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3280     fp.write("\n\n\n")
3281     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3282     fp.write("\n\n\n\n")
3283     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3284     fp.write("\n")
3285     fp.write(_("                                                Starfleet Command bestows to you\n"))
3286     fp.write("\n")
3287     fp.write("%*s%s\n\n" % (nskip, "", winner))
3288     fp.write(_("                                                           the rank of\n\n"))
3289     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3290     fp.write("                                                          ")
3291     if game.skill ==  SKILL_EXPERT:
3292         fp.write(_(" Expert level\n\n"))
3293     elif game.skill == SKILL_EMERITUS:
3294         fp.write(_("Emeritus level\n\n"))
3295     else:
3296         fp.write(_(" Cheat level\n\n"))
3297     timestring = ctime()
3298     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3299                     (timestring+4, timestring+20, timestring+11))
3300     fp.write(_("                                                        Your score:  %d\n\n") % iscore)
3301     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % perdate)
3302     fp.close()
3303
3304 # Code from io.c begins here
3305
3306 rows = linecount = 0    # for paging 
3307 stdscr = None
3308 replayfp = None
3309 fullscreen_window = None
3310 srscan_window     = None
3311 report_window     = None
3312 status_window     = None
3313 lrscan_window     = None
3314 message_window    = None
3315 prompt_window     = None
3316 curwnd = None
3317
3318 def iostart():
3319     global stdscr, rows
3320     if not (game.options & OPTION_CURSES):
3321         ln_env = os.getenv("LINES")
3322         if ln_env:
3323             rows = ln_env
3324         else:
3325             rows = 25
3326     else:
3327         stdscr = curses.initscr()
3328         stdscr.keypad(True)
3329         curses.nonl()
3330         curses.cbreak()
3331         global fullscreen_window, srscan_window, report_window, status_window
3332         global lrscan_window, message_window, prompt_window
3333         (rows, columns)   = stdscr.getmaxyx()
3334         fullscreen_window = stdscr
3335         srscan_window     = curses.newwin(12, 25, 0,       0)
3336         report_window     = curses.newwin(11, 0,  1,       25)
3337         status_window     = curses.newwin(10, 0,  1,       39)
3338         lrscan_window     = curses.newwin(5,  0,  0,       64) 
3339         message_window    = curses.newwin(0,  0,  12,      0)
3340         prompt_window     = curses.newwin(1,  0,  rows-2,  0) 
3341         message_window.scrollok(True)
3342         setwnd(fullscreen_window)
3343
3344 def ioend():
3345     "Wrap up I/O."
3346     if game.options & OPTION_CURSES:
3347         stdscr.keypad(False)
3348         curses.echo()
3349         curses.nocbreak()
3350         curses.endwin()
3351
3352 def waitfor():
3353     "Wait for user action -- OK to do nothing if on a TTY"
3354     if game.options & OPTION_CURSES:
3355         stdscr.getch()
3356
3357 def announce():
3358     skip(1)
3359     prouts(_("[ANOUNCEMENT ARRIVING...]"))
3360     skip(1)
3361
3362 def pause_game():
3363     if game.skill > SKILL_FAIR:
3364         prompt = _("[CONTINUE?]")
3365     else:
3366         prompt = _("[PRESS ENTER TO CONTINUE]")
3367
3368     if game.options & OPTION_CURSES:
3369         drawmaps(0)
3370         setwnd(prompt_window)
3371         prompt_window.wclear()
3372         prompt_window.addstr(prompt)
3373         prompt_window.getstr()
3374         prompt_window.clear()
3375         prompt_window.refresh()
3376         setwnd(message_window)
3377     else:
3378         global linecount
3379         sys.stdout.write('\n')
3380         proutn(prompt)
3381         raw_input()
3382         for j in range(rows):
3383             sys.stdout.write('\n')
3384         linecount = 0
3385
3386 def skip(i):
3387     "Skip i lines.  Pause game if this would cause a scrolling event."
3388     for dummy in range(i):
3389         if game.options & OPTION_CURSES:
3390             (y, x) = curwnd.getyx()
3391             (my, mx) = curwnd.getmaxyx()
3392             if curwnd == message_window and y >= my - 3:
3393                 pause_game()
3394                 clrscr()
3395             else:
3396                 try:
3397                     curwnd.move(y+1, 0)
3398                 except curses.error:
3399                     pass
3400         else:
3401             global linecount
3402             linecount += 1
3403             if rows and linecount >= rows:
3404                 pause_game()
3405             else:
3406                 sys.stdout.write('\n')
3407
3408 def proutn(line):
3409     "Utter a line with no following line feed."
3410     if game.options & OPTION_CURSES:
3411         curwnd.addstr(line)
3412         curwnd.refresh()
3413     else:
3414         sys.stdout.write(line)
3415         sys.stdout.flush()
3416
3417 def prout(line):
3418     proutn(line)
3419     skip(1)
3420
3421 def prouts(line):
3422     "Emit slowly!" 
3423     for c in line:
3424         if not replayfp or replayfp.closed:     # Don't slow down replays
3425             time.sleep(0.03)
3426         proutn(c)
3427         if game.options & OPTION_CURSES:
3428             curwnd.refresh()
3429         else:
3430             sys.stdout.flush()
3431     if not replayfp or replayfp.closed:
3432         time.sleep(0.03)
3433
3434 def cgetline():
3435     "Get a line of input."
3436     if game.options & OPTION_CURSES:
3437         line = curwnd.getstr() + "\n"
3438         curwnd.refresh()
3439     else:
3440         if replayfp and not replayfp.closed:
3441             while True:
3442                 line = replayfp.readline()
3443                 proutn(line)
3444                 if line == '':
3445                     prout("*** Replay finished")
3446                     replayfp.close()
3447                     break
3448                 elif line[0] != "#":
3449                     break
3450         else:
3451             line = raw_input() + "\n"
3452     if logfp:
3453         logfp.write(line)
3454     return line
3455
3456 def setwnd(wnd):
3457     "Change windows -- OK for this to be a no-op in tty mode."
3458     global curwnd
3459     if game.options & OPTION_CURSES:
3460         curwnd = wnd
3461         curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
3462
3463 def clreol():
3464     "Clear to end of line -- can be a no-op in tty mode" 
3465     if game.options & OPTION_CURSES:
3466         wclrtoeol(curwnd)
3467         wrefresh(curwnd)
3468
3469 def clrscr():
3470     "Clear screen -- can be a no-op in tty mode."
3471     global linecount
3472     if game.options & OPTION_CURSES:
3473        curwnd.clear()
3474        curwnd.move(0, 0)
3475        curwnd.refresh()
3476     linecount = 0
3477     
3478 def highvideo():
3479     "Set highlight video, if this is reasonable."
3480     if game.options & OPTION_CURSES:
3481         curwnd.attron(curses.A_REVERSE)
3482  
3483 #
3484 # Things past this point have policy implications.
3485
3486
3487 def drawmaps(mode):
3488     "Hook to be called after moving to redraw maps."
3489     if game.options & OPTION_CURSES:
3490         if mode == 1:
3491             sensor()
3492         setwnd(srscan_window)
3493         curwnd.move(0, 0)
3494         srscan()
3495         if mode != 2:
3496             setwnd(status_window)
3497             status_window.clear()
3498             status_window.move(0, 0)
3499             setwnd(report_window)
3500             report_window.clear()
3501             report_window.move(0, 0)
3502             status()
3503             setwnd(lrscan_window)
3504             lrscan_window.clear()
3505             lrscan_window.move(0, 0)
3506             lrscan(silent=False)
3507
3508 def put_srscan_sym(w, sym):
3509     "Emit symbol for short-range scan."
3510     srscan_window.move(w.i+1, w.j*2+2)
3511     srscan_window.addch(sym)
3512     srscan_window.refresh()
3513
3514 def boom(w):
3515     "Enemy fall down, go boom."  
3516     if game.options & OPTION_CURSES:
3517         drawmaps(2)
3518         setwnd(srscan_window)
3519         srscan_window.attron(curses.A_REVERSE)
3520         put_srscan_sym(w, game.quad[w.i][w.j])
3521         #sound(500)
3522         #time.sleep(1.0)
3523         #nosound()
3524         srscan_window.attroff(curses.A_REVERSE)
3525         put_srscan_sym(w, game.quad[w.i][w.j])
3526         curses.delay_output(500)
3527         setwnd(message_window) 
3528
3529 def warble():
3530     "Sound and visual effects for teleportation."
3531     if game.options & OPTION_CURSES:
3532         drawmaps(2)
3533         setwnd(message_window)
3534         #sound(50)
3535     prouts("     . . . . .     ")
3536     if game.options & OPTION_CURSES:
3537         #curses.delay_output(1000)
3538         #nosound()
3539         pass
3540
3541 def tracktorpedo(origin, w, step, i, n, iquad):
3542     "Torpedo-track animation." 
3543     if not game.options & OPTION_CURSES:
3544         if step == 1:
3545             if n != 1:
3546                 skip(1)
3547                 proutn(_("Track for %s torpedo number %d-  ") % (game.quad[origin.i][origin.j],i+1))
3548             else:
3549                 skip(1)
3550                 proutn(_("Torpedo track- "))
3551         elif step==4 or step==9: 
3552             skip(1)
3553         proutn("%s   " % w)
3554     else:
3555         if not damaged(DSRSENS) or game.condition=="docked":
3556             if i != 0 and step == 1:
3557                 drawmaps(2)
3558                 time.sleep(0.4)
3559             if (iquad==IHDOT) or (iquad==IHBLANK):
3560                 put_srscan_sym(w, '+')
3561                 #sound(step*10)
3562                 #time.sleep(0.1)
3563                 #nosound()
3564                 put_srscan_sym(w, iquad)
3565             else:
3566                 curwnd.attron(curses.A_REVERSE)
3567                 put_srscan_sym(w, iquad)
3568                 #sound(500)
3569                 #time.sleep(1.0)
3570                 #nosound()
3571                 curwnd.attroff(curses.A_REVERSE)
3572                 put_srscan_sym(w, iquad)
3573         else:
3574             proutn("%s   " % w)
3575
3576 def makechart():
3577     "Display the current galaxy chart."
3578     if game.options & OPTION_CURSES:
3579         setwnd(message_window)
3580         message_window.clear()
3581     chart()
3582     if game.options & OPTION_TTY:
3583         skip(1)
3584
3585 NSYM    = 14
3586
3587 def prstat(txt, data):
3588     proutn(txt)
3589     if game.options & OPTION_CURSES:
3590         skip(1)
3591         setwnd(status_window)
3592     else:
3593         proutn(" " * (NSYM - len(txt)))
3594     proutn(data)
3595     skip(1)
3596     if game.options & OPTION_CURSES:
3597         setwnd(report_window)
3598
3599 # Code from moving.c begins here
3600
3601 def imove(novapush):
3602     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3603     w = coord(); final = coord()
3604     trbeam = False
3605
3606     def no_quad_change():
3607         # No quadrant change -- compute new average enemy distances 
3608         game.quad[game.sector.i][game.sector.j] = game.ship
3609         if game.enemies:
3610             for enemy in game.enemies:
3611                 finald = (w-enemy.kloc).distance()
3612                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3613                 enemy.kdist = finald
3614             game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
3615             if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3616                 attack(torps_ok=False)
3617             for enemy in game.enemies:
3618                 enemy.kavgd = enemy.kdist
3619         newcnd()
3620         drawmaps(0)
3621         setwnd(message_window)
3622     w.i = w.j = 0
3623     if game.inorbit:
3624         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3625         game.inorbit = False
3626     angle = ((15.0 - game.direc) * 0.5235988)
3627     deltax = -math.sin(angle)
3628     deltay = math.cos(angle)
3629     if math.fabs(deltax) > math.fabs(deltay):
3630         bigger = math.fabs(deltax)
3631     else:
3632         bigger = math.fabs(deltay)
3633     deltay /= bigger
3634     deltax /= bigger
3635     # If tractor beam is to occur, don't move full distance 
3636     if game.state.date+game.optime >= scheduled(FTBEAM):
3637         trbeam = True
3638         game.condition = "red"
3639         game.dist = game.dist*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3640         game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3641     # Move within the quadrant 
3642     game.quad[game.sector.i][game.sector.j] = IHDOT
3643     x = game.sector.i
3644     y = game.sector.j
3645     n = int(10.0*game.dist*bigger+0.5)
3646     if n > 0:
3647         for m in range(1, n+1):
3648             x += deltax
3649             y += deltay
3650             w.i = int(round(x))
3651             w.j = int(round(y))
3652             if not VALID_SECTOR(w.i, w.j):
3653                 # Leaving quadrant -- allow final enemy attack 
3654                 # Don't do it if being pushed by Nova 
3655                 if len(game.enemies) != 0 and not novapush:
3656                     newcnd()
3657                     for enemy in game.enemies:
3658                         finald = (w - enemy.kloc).distance()
3659                         enemy.kavgd = 0.5 * (finald + enemy.kdist)
3660                     #
3661                     # Stas Sergeev added the condition
3662                     # that attacks only happen if Klingons
3663                     # are present and your skill is good.
3664                     # 
3665                     if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3666                         attack(torps_ok=False)
3667                     if game.alldone:
3668                         return
3669                 # compute final position -- new quadrant and sector 
3670                 x = (QUADSIZE*game.quadrant.i)+game.sector.i
3671                 y = (QUADSIZE*game.quadrant.j)+game.sector.j
3672                 w.i = int(round(x+10.0*game.dist*bigger*deltax))
3673                 w.j = int(round(y+10.0*game.dist*bigger*deltay))
3674                 # check for edge of galaxy 
3675                 kinks = 0
3676                 while True:
3677                     kink = False
3678                     if w.i < 0:
3679                         w.i = -w.i
3680                         kink = True
3681                     if w.j < 0:
3682                         w.j = -w.j
3683                         kink = True
3684                     if w.i >= GALSIZE*QUADSIZE:
3685                         w.i = (GALSIZE*QUADSIZE*2) - w.i
3686                         kink = True
3687                     if w.j >= GALSIZE*QUADSIZE:
3688                         w.j = (GALSIZE*QUADSIZE*2) - w.j
3689                         kink = True
3690                     if kink:
3691                         kinks += 1
3692                     else:
3693                         break
3694                 if kinks:
3695                     game.nkinks += 1
3696                     if game.nkinks == 3:
3697                         # Three strikes -- you're out! 
3698                         finish(FNEG3)
3699                         return
3700                     skip(1)
3701                     prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3702                     prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3703                     prout(_("YOU WILL BE DESTROYED."))
3704                 # Compute final position in new quadrant 
3705                 if trbeam: # Don't bother if we are to be beamed 
3706                     return
3707                 game.quadrant.i = w.i/QUADSIZE
3708                 game.quadrant.j = w.j/QUADSIZE
3709                 game.sector.i = w.i - (QUADSIZE*game.quadrant.i)
3710                 game.sector.j = w.j - (QUADSIZE*game.quadrant.j)
3711                 skip(1)
3712                 prout(_("Entering Quadrant %s.") % game.quadrant)
3713                 game.quad[game.sector.i][game.sector.j] = game.ship
3714                 newqad()
3715                 if game.skill>SKILL_NOVICE:
3716                     attack(torps_ok=False)  
3717                 return
3718             iquad = game.quad[w.i][w.j]
3719             if iquad != IHDOT:
3720                 # object encountered in flight path 
3721                 stopegy = 50.0*game.dist/game.optime
3722                 game.dist = (game.sector - w).distance() / (QUADSIZE * 1.0)
3723                 if iquad in (IHT, IHK, IHC, IHS, IHR, IHQUEST):
3724                     game.sector = w
3725                     for enemy in game.enemies:
3726                         if enemy.kloc == game.sector:
3727                             break
3728                     collision(rammed=False, enemy=enemy)
3729                     final = game.sector
3730                 elif iquad == IHBLANK:
3731                     skip(1)
3732                     prouts(_("***RED ALERT!  RED ALERT!"))
3733                     skip(1)
3734                     proutn("***" + crmshp())
3735                     proutn(_(" pulled into black hole at Sector %s") % w)
3736                     #
3737                     # Getting pulled into a black hole was certain
3738                     # death in Almy's original.  Stas Sergeev added a
3739                     # possibility that you'll get timewarped instead.
3740                     # 
3741                     n=0
3742                     for m in range(NDEVICES):
3743                         if game.damage[m]>0: 
3744                             n += 1
3745                     probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3746                     if (game.options & OPTION_BLKHOLE) and withprob(1-probf): 
3747                         timwrp()
3748                     else: 
3749                         finish(FHOLE)
3750                     return
3751                 else:
3752                     # something else 
3753                     skip(1)
3754                     proutn(crmshp())
3755                     if iquad == IHWEB:
3756                         prout(_(" encounters Tholian web at %s;") % w)
3757                     else:
3758                         prout(_(" blocked by object at %s;") % w)
3759                     proutn(_("Emergency stop required "))
3760                     prout(_("%2d units of energy.") % int(stopegy))
3761                     game.energy -= stopegy
3762                     final.i = int(round(deltax))
3763                     final.j = int(round(deltay))
3764                     game.sector = final
3765                     if game.energy <= 0:
3766                         finish(FNRG)
3767                         return
3768                 # We're here!
3769                 no_quad_change()
3770                 return
3771         game.dist = (game.sector - w).distance() / (QUADSIZE * 1.0)
3772         game.sector = w
3773     final = game.sector
3774     no_quad_change()
3775     return
3776
3777 def dock(verbose):
3778     "Dock our ship at a starbase."
3779     scanner.chew()
3780     if game.condition == "docked" and verbose:
3781         prout(_("Already docked."))
3782         return
3783     if game.inorbit:
3784         prout(_("You must first leave standard orbit."))
3785         return
3786     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3787         prout(crmshp() + _(" not adjacent to base."))
3788         return
3789     game.condition = "docked"
3790     if "verbose":
3791         prout(_("Docked."))
3792     game.ididit = True
3793     if game.energy < game.inenrg:
3794         game.energy = game.inenrg
3795     game.shield = game.inshld
3796     game.torps = game.intorps
3797     game.lsupres = game.inlsr
3798     game.state.crew = FULLCREW
3799     if not damaged(DRADIO) and \
3800         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3801         # get attack report from base 
3802         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3803         attackreport(False)
3804         game.iseenit = True
3805  
3806 # This program originally required input in terms of a (clock)
3807 # direction and distance. Somewhere in history, it was changed to
3808 # cartesian coordinates. So we need to convert.  Probably
3809 # "manual" input should still be done this way -- it's a real
3810 # pain if the computer isn't working! Manual mode is still confusing
3811 # because it involves giving x and y motions, yet the coordinates
3812 # are always displayed y - x, where +y is downward!
3813
3814 def getcourse(isprobe, akey):
3815     "Get a course and distance from the user."
3816     key = 0
3817     dquad = copy.copy(game.quadrant)
3818     navmode = "unspecified"
3819     itemp = "curt"
3820     dsect = coord()
3821     iprompt = False
3822     if game.landed and not isprobe:
3823         prout(_("Dummy! You can't leave standard orbit until you"))
3824         proutn(_("are back aboard the ship."))
3825         scanner.chew()
3826         return False
3827     while navmode == "unspecified":
3828         if damaged(DNAVSYS):
3829             if isprobe:
3830                 prout(_("Computer damaged; manual navigation only"))
3831             else:
3832                 prout(_("Computer damaged; manual movement only"))
3833             scanner.chew()
3834             navmode = "manual"
3835             key = "IHEOL"
3836             break
3837         if isprobe and akey != -1:
3838             # For probe launch, use pre-scanned value first time 
3839             key = akey
3840             akey = -1
3841         else: 
3842             key = scanner.next()
3843         if key == "IHEOL":
3844             proutn(_("Manual or automatic- "))
3845             iprompt = True
3846             scanner.chew()
3847         elif key == "IHALPHA":
3848             if scanner.sees("manual"):
3849                 navmode = "manual"
3850                 key = scanner.next()
3851                 break
3852             elif scanner.sees("automatic"):
3853                 navmode = "automatic"
3854                 key = scanner.next()
3855                 break
3856             else:
3857                 huh()
3858                 scanner.chew()
3859                 return False
3860         else: # numeric 
3861             if isprobe:
3862                 prout(_("(Manual navigation assumed.)"))
3863             else:
3864                 prout(_("(Manual movement assumed.)"))
3865             navmode = "manual"
3866             break
3867     if navmode == "automatic":
3868         while key == "IHEOL":
3869             if isprobe:
3870                 proutn(_("Target quadrant or quadrant&sector- "))
3871             else:
3872                 proutn(_("Destination sector or quadrant&sector- "))
3873             scanner.chew()
3874             iprompt = True
3875             key = scanner.next()
3876         if key != "IHREAL":
3877             huh()
3878             return False
3879         xi = int(round(scanner.real))-1
3880         key = scanner.next()
3881         if key != "IHREAL":
3882             huh()
3883             return False
3884         xj = int(round(scanner.real))-1
3885         key = scanner.next()
3886         if key == "IHREAL":
3887             # both quadrant and sector specified 
3888             xk = int(round(scanner.real))-1
3889             key = scanner.next()
3890             if key != "IHREAL":
3891                 huh()
3892                 return False
3893             xl = int(round(scanner.real))-1
3894             dquad.i = xi
3895             dquad.j = xj
3896             dsect.i = xk
3897             dsect.j = xl
3898         else:
3899             # only one pair of numbers was specified
3900             if isprobe:
3901                 # only quadrant specified -- go to center of dest quad 
3902                 dquad.i = xi
3903                 dquad.j = xj
3904                 dsect.j = dsect.i = 4   # preserves 1-origin behavior
3905             else:
3906                 # only sector specified
3907                 dsect.i = xi
3908                 dsect.j = xj
3909             itemp = "normal"
3910         if not VALID_QUADRANT(dquad.i,dquad.j) or not VALID_SECTOR(dsect.i,dsect.j):
3911             huh()
3912             return False
3913         skip(1)
3914         if not isprobe:
3915             if itemp > "curt":
3916                 if iprompt:
3917                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
3918             else:
3919                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
3920         # the actual deltas get computed here
3921         delta = coord()
3922         delta.j = dquad.j-game.quadrant.j + 0.1*(dsect.j-game.sector.j)
3923         delta.i = game.quadrant.i-dquad.i + 0.1*(game.sector.i-dsect.i)
3924     else: # manual 
3925         while key == "IHEOL":
3926             proutn(_("X and Y displacements- "))
3927             scanner.chew()
3928             iprompt = True
3929             key = scanner.next()
3930         itemp = "verbose"
3931         if key != "IHREAL":
3932             huh()
3933             return False
3934         delta.j = scanner.real
3935         key = scanner.next()
3936         if key != "IHREAL":
3937             huh()
3938             return False
3939         delta.i = scanner.real
3940     # Check for zero movement 
3941     if delta.i == 0 and delta.j == 0:
3942         scanner.chew()
3943         return False
3944     if itemp == "verbose" and not isprobe:
3945         skip(1)
3946         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
3947     # Course actually laid in.
3948     game.dist = delta.distance()
3949     game.direc = delta.bearing()
3950     if game.direc < 0.0:
3951         game.direc += 12.0
3952     scanner.chew()
3953     return True
3954
3955 def impulse():
3956     "Move under impulse power."
3957     game.ididit = False
3958     if damaged(DIMPULS):
3959         scanner.chew()
3960         skip(1)
3961         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
3962         return
3963     if game.energy > 30.0:
3964         if not getcourse(isprobe=False, akey=0):
3965             return
3966         power = 20.0 + 100.0*game.dist
3967     else:
3968         power = 30.0
3969     if power >= game.energy:
3970         # Insufficient power for trip 
3971         skip(1)
3972         prout(_("First Officer Spock- \"Captain, the impulse engines"))
3973         prout(_("require 20.0 units to engage, plus 100.0 units per"))
3974         if game.energy > 30:
3975             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
3976                      int(0.01 * (game.energy-20.0)-0.05))
3977             prout(_(" quadrants.\""))
3978         else:
3979             prout(_("quadrant.  They are, therefore, useless.\""))
3980         scanner.chew()
3981         return
3982     # Make sure enough time is left for the trip 
3983     game.optime = game.dist/0.095
3984     if game.optime >= game.state.remtime:
3985         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
3986         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
3987         proutn(_("we dare spend the time?\" "))
3988         if ja() == False:
3989             return
3990     # Activate impulse engines and pay the cost 
3991     imove(novapush=False)
3992     game.ididit = True
3993     if game.alldone:
3994         return
3995     power = 20.0 + 100.0*game.dist
3996     game.energy -= power
3997     game.optime = game.dist/0.095
3998     if game.energy <= 0:
3999         finish(FNRG)
4000     return
4001
4002 def warp(timewarp):
4003     "ove under warp drive."
4004     blooey = False; twarp = False
4005     if not timewarp: # Not WARPX entry 
4006         game.ididit = False
4007         if game.damage[DWARPEN] > 10.0:
4008             scanner.chew()
4009             skip(1)
4010             prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4011             return
4012         if damaged(DWARPEN) and game.warpfac > 4.0:
4013             scanner.chew()
4014             skip(1)
4015             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4016             prout(_("  is repaired, I can only give you warp 4.\""))
4017             return
4018         # Read in course and distance 
4019         if not getcourse(isprobe=False, akey=0):
4020             return
4021         # Make sure starship has enough energy for the trip 
4022         power = (game.dist+0.05)*game.warpfac*game.warpfac*game.warpfac*(game.shldup+1)
4023         if power >= game.energy:
4024             # Insufficient power for trip 
4025             game.ididit = False
4026             skip(1)
4027             prout(_("Engineering to bridge--"))
4028             if not game.shldup or 0.5*power > game.energy:
4029                 iwarp = math.pow((game.energy/(game.dist+0.05)), 0.333333333)
4030                 if iwarp <= 0:
4031                     prout(_("We can't do it, Captain. We don't have enough energy."))
4032                 else:
4033                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4034                     if game.shldup:
4035                         prout(",")
4036                         prout(_("if you'll lower the shields."))
4037                     else:
4038                         prout(".")
4039             else:
4040                 prout(_("We haven't the energy to go that far with the shields up."))
4041             return
4042                                                 
4043         # Make sure enough time is left for the trip 
4044         game.optime = 10.0*game.dist/game.warpfac**2
4045         if game.optime >= 0.8*game.state.remtime:
4046             skip(1)
4047             prout(_("First Officer Spock- \"Captain, I compute that such"))
4048             proutn(_("  a trip would require approximately %2.0f") %
4049                    (100.0*game.optime/game.state.remtime))
4050             prout(_(" percent of our"))
4051             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4052             if ja() == False:
4053                 game.ididit = False
4054                 game.optime=0 
4055                 return
4056     # Entry WARPX 
4057     if game.warpfac > 6.0:
4058         # Decide if engine damage will occur
4059         # ESR: Seems wrong. Probability of damage goes *down* with distance? 
4060         prob = game.dist*(6.0-game.warpfac)**2/66.666666666
4061         if prob > randreal():
4062             blooey = True
4063             game.dist = randreal(game.dist)
4064         # Decide if time warp will occur 
4065         if 0.5*game.dist*math.pow(7.0,game.warpfac-10.0) > randreal():
4066             twarp = True
4067         if idebug and game.warpfac==10 and not twarp:
4068             blooey = False
4069             proutn("=== Force time warp? ")
4070             if ja() == True:
4071                 twarp = True
4072         if blooey or twarp:
4073             # If time warp or engine damage, check path 
4074             # If it is obstructed, don't do warp or damage 
4075             angle = ((15.0-game.direc)*0.5235998)
4076             deltax = -math.sin(angle)
4077             deltay = math.cos(angle)
4078             if math.fabs(deltax) > math.fabs(deltay):
4079                 bigger = math.fabs(deltax)
4080             else:
4081                 bigger = math.fabs(deltay)
4082             deltax /= bigger
4083             deltay /= bigger
4084             n = 10.0 * game.dist * bigger +0.5
4085             x = game.sector.i
4086             y = game.sector.j
4087             for l in range(1, n+1):
4088                 x += deltax
4089                 ix = x + 0.5
4090                 y += deltay
4091                 iy = y +0.5
4092                 if not VALID_SECTOR(ix, iy):
4093                     break
4094                 if game.quad[ix][iy] != IHDOT:
4095                     blooey = False
4096                     twarp = False
4097     # Activate Warp Engines and pay the cost 
4098     imove(novapush=False)
4099     if game.alldone:
4100         return
4101     game.energy -= game.dist*game.warpfac*game.warpfac*game.warpfac*(game.shldup+1)
4102     if game.energy <= 0:
4103         finish(FNRG)
4104     game.optime = 10.0*game.dist/game.warpfac**2
4105     if twarp:
4106         timwrp()
4107     if blooey:
4108         game.damage[DWARPEN] = game.damfac * randreal(1.0, 4.0)
4109         skip(1)
4110         prout(_("Engineering to bridge--"))
4111         prout(_("  Scott here.  The warp engines are damaged."))
4112         prout(_("  We'll have to reduce speed to warp 4."))
4113     game.ididit = True
4114     return
4115
4116 def setwarp():
4117     "Change the warp factor."
4118     while True:
4119         key=scanner.next()
4120         if key != "IHEOL":
4121             break
4122         scanner.chew()
4123         proutn(_("Warp factor- "))
4124     scanner.chew()
4125     if key != "IHREAL":
4126         huh()
4127         return
4128     if game.damage[DWARPEN] > 10.0:
4129         prout(_("Warp engines inoperative."))
4130         return
4131     if damaged(DWARPEN) and scanner.real > 4.0:
4132         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4133         prout(_("  but right now we can only go warp 4.\""))
4134         return
4135     if scanner.real > 10.0:
4136         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4137         return
4138     if scanner.real < 1.0:
4139         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4140         return
4141     oldfac = game.warpfac
4142     game.warpfac = scanner.real
4143     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4144         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4145                int(game.warpfac))
4146         return
4147     if game.warpfac < 8.00:
4148         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4149         return
4150     if game.warpfac == 10.0:
4151         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4152         return
4153     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4154     return
4155
4156 def atover(igrab):
4157     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4158     scanner.chew()
4159     # is captain on planet? 
4160     if game.landed:
4161         if damaged(DTRANSP):
4162             finish(FPNOVA)
4163             return
4164         prout(_("Scotty rushes to the transporter controls."))
4165         if game.shldup:
4166             prout(_("But with the shields up it's hopeless."))
4167             finish(FPNOVA)
4168         prouts(_("His desperate attempt to rescue you . . ."))
4169         if withprob(0.5):
4170             prout(_("fails."))
4171             finish(FPNOVA)
4172             return
4173         prout(_("SUCCEEDS!"))
4174         if game.imine:
4175             game.imine = False
4176             proutn(_("The crystals mined were "))
4177             if withprob(0.25):
4178                 prout(_("lost."))
4179             else:
4180                 prout(_("saved."))
4181                 game.icrystl = True
4182     if igrab:
4183         return
4184     # Check to see if captain in shuttle craft 
4185     if game.icraft:
4186         finish(FSTRACTOR)
4187     if game.alldone:
4188         return
4189     # Inform captain of attempt to reach safety 
4190     skip(1)
4191     while True:
4192         if game.justin:
4193             prouts(_("***RED ALERT!  RED ALERT!"))
4194             skip(1)
4195             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4196             prouts(_("   a supernova."))
4197             skip(2)
4198         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4199         skip(1)
4200         prout(_("safely out of quadrant."))
4201         if not damaged(DRADIO):
4202             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4203         # Try to use warp engines 
4204         if damaged(DWARPEN):
4205             skip(1)
4206             prout(_("Warp engines damaged."))
4207             finish(FSNOVAED)
4208             return
4209         game.warpfac = randreal(6.0, 8.0)
4210         prout(_("Warp factor set to %d") % int(game.warpfac))
4211         power = 0.75*game.energy
4212         game.dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4213         distreq = randreal(math.sqrt(2))
4214         if distreq < game.dist:
4215             game.dist = distreq
4216         game.optime = 10.0*game.dist/game.warpfac**2
4217         game.direc = randreal(12)       # How dumb! 
4218         game.justin = False
4219         game.inorbit = False
4220         warp(True)
4221         if not game.justin:
4222             # This is bad news, we didn't leave quadrant. 
4223             if game.alldone:
4224                 return
4225             skip(1)
4226             prout(_("Insufficient energy to leave quadrant."))
4227             finish(FSNOVAED)
4228             return
4229         # Repeat if another snova
4230         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4231             break
4232     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0: 
4233         finish(FWON) # Snova killed remaining enemy. 
4234
4235 def timwrp():
4236     "Let's do the time warp again."
4237     prout(_("***TIME WARP ENTERED."))
4238     if game.state.snap and withprob(0.5):
4239         # Go back in time 
4240         prout(_("You are traveling backwards in time %d stardates.") %
4241               int(game.state.date-game.snapsht.date))
4242         game.state = game.snapsht
4243         game.state.snap = False
4244         if len(game.state.kcmdr):
4245             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4246             schedule(FBATTAK, expran(0.3*game.intime))
4247         schedule(FSNOVA, expran(0.5*game.intime))
4248         # next snapshot will be sooner 
4249         schedule(FSNAP, expran(0.25*game.state.remtime))
4250                                 
4251         if game.state.nscrem:
4252             schedule(FSCMOVE, 0.2777)       
4253         game.isatb = 0
4254         unschedule(FCDBAS)
4255         unschedule(FSCDBAS)
4256         game.battle.invalidate()
4257
4258         # Make sure Galileo is consistant -- Snapshot may have been taken
4259         # when on planet, which would give us two Galileos! 
4260         gotit = False
4261         for l in range(game.inplan):
4262             if game.state.planets[l].known == "shuttle_down":
4263                 gotit = True
4264                 if game.iscraft == "onship" and game.ship==IHE:
4265                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4266                     game.iscraft = "offship"
4267         # Likewise, if in the original time the Galileo was abandoned, but
4268         # was on ship earlier, it would have vanished -- let's restore it.
4269         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4270             prout(_("Checkov-  \"Security reports the Galileo has reappeared in the dock!\""))
4271             game.iscraft = "onship"
4272         # There used to be code to do the actual reconstrction here,
4273         # but the starchart is now part of the snapshotted galaxy state.
4274         prout(_("Spock has reconstructed a correct star chart from memory"))
4275     else:
4276         # Go forward in time 
4277         game.optime = -0.5*game.intime*math.log(randreal())
4278         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4279         # cheat to make sure no tractor beams occur during time warp 
4280         postpone(FTBEAM, game.optime)
4281         game.damage[DRADIO] += game.optime
4282     newqad()
4283     events()    # Stas Sergeev added this -- do pending events 
4284
4285 def probe():
4286     "Launch deep-space probe." 
4287     # New code to launch a deep space probe 
4288     if game.nprobes == 0:
4289         scanner.chew()
4290         skip(1)
4291         if game.ship == IHE: 
4292             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4293         else:
4294             prout(_("Ye Faerie Queene has no deep space probes."))
4295         return
4296     if damaged(DDSP):
4297         scanner.chew()
4298         skip(1)
4299         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4300         return
4301     if is_scheduled(FDSPROB):
4302         scanner.chew()
4303         skip(1)
4304         if damaged(DRADIO) and game.condition != "docked":
4305             prout(_("Spock-  \"Records show the previous probe has not yet"))
4306             prout(_("   reached its destination.\""))
4307         else:
4308             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4309         return
4310     key = scanner.next()
4311     if key == "IHEOL":
4312         # slow mode, so let Kirk know how many probes there are left
4313         if game.nprobes == 1:
4314             prout(_("1 probe left."))
4315         else:
4316             prout(_("%d probes left") % game.nprobes)
4317         proutn(_("Are you sure you want to fire a probe? "))
4318         if ja() == False:
4319             return
4320     game.isarmed = False
4321     if key == "IHALPHA" and scanner.token == "armed":
4322         game.isarmed = True
4323         key = scanner.next()
4324     elif key == "IHEOL":
4325         proutn(_("Arm NOVAMAX warhead? "))
4326         game.isarmed = ja()
4327     if not getcourse(isprobe=True, akey=key):
4328         return
4329     game.nprobes -= 1
4330     angle = ((15.0 - game.direc) * 0.5235988)
4331     game.probeinx = -math.sin(angle)
4332     game.probeiny = math.cos(angle)
4333     if math.fabs(game.probeinx) > math.fabs(game.probeiny):
4334         bigger = math.fabs(game.probeinx)
4335     else:
4336         bigger = math.fabs(game.probeiny)
4337     game.probeiny /= bigger
4338     game.probeinx /= bigger
4339     game.proben = 10.0*game.dist*bigger +0.5
4340     game.probex = game.quadrant.i*QUADSIZE + game.sector.i - 1  # We will use better packing than original
4341     game.probey = game.quadrant.j*QUADSIZE + game.sector.j - 1
4342     game.probec = game.quadrant
4343     schedule(FDSPROB, 0.01) # Time to move one sector
4344     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4345     game.ididit = True
4346     return
4347
4348 def mayday():
4349     "Yell for help from nearest starbase."
4350     # There's more than one way to move in this game! 
4351     scanner.chew()
4352     # Test for conditions which prevent calling for help 
4353     if game.condition == "docked":
4354         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4355         return
4356     if damaged(DRADIO):
4357         prout(_("Subspace radio damaged."))
4358         return
4359     if not game.state.baseq:
4360         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4361         return
4362     if game.landed:
4363         prout(_("You must be aboard the %s.") % crmshp())
4364         return
4365     # OK -- call for help from nearest starbase 
4366     game.nhelp += 1
4367     if game.base.i!=0:
4368         # There's one in this quadrant 
4369         ddist = (game.base - game.sector).distance()
4370     else:
4371         ddist = FOREVER
4372         for ibq in game.state.baseq:
4373             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4374             if xdist < ddist:
4375                 ddist = xdist
4376         # Since starbase not in quadrant, set up new quadrant 
4377         game.quadrant = ibq
4378         newqad()
4379     # dematerialize starship 
4380     game.quad[game.sector.i][game.sector.j]=IHDOT
4381     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4382            % (game.quadrant, crmshp()))
4383     game.sector.invalidate()
4384     for m in range(1, 5+1):
4385         w = game.base.scatter() 
4386         if VALID_SECTOR(w.i,w.j) and game.quad[w.i][w.j]==IHDOT:
4387             # found one -- finish up 
4388             game.sector = w
4389             break
4390     if not game.sector.is_valid():
4391         prout(_("You have been lost in space..."))
4392         finish(FMATERIALIZE)
4393         return
4394     # Give starbase three chances to rematerialize starship 
4395     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4396     for m in range(1, 3+1):
4397         if m == 1: proutn(_("1st"))
4398         elif m == 2: proutn(_("2nd"))
4399         elif m == 3: proutn(_("3rd"))
4400         proutn(_(" attempt to re-materialize ") + crmshp())
4401         game.quad[ix][iy]=(IHMATER0,IHMATER1,IHMATER2)[m-1]
4402         #textcolor("red")
4403         warble()
4404         if randreal() > probf:
4405             break
4406         prout(_("fails."))
4407         curses.delay_output(500)
4408         #textcolor(None)
4409     if m > 3:
4410         game.quad[ix][iy]=IHQUEST
4411         game.alive = False
4412         drawmaps(1)
4413         setwnd(message_window)
4414         finish(FMATERIALIZE)
4415         return
4416     game.quad[ix][iy]=game.ship
4417     #textcolor("green")
4418     prout(_("succeeds."))
4419     #textcolor(None)
4420     dock(False)
4421     skip(1)
4422     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4423
4424 def abandon():
4425     "Abandon ship."
4426     scanner.chew()
4427     if game.condition=="docked":
4428         if game.ship!=IHE:
4429             prout(_("You cannot abandon Ye Faerie Queene."))
4430             return
4431     else:
4432         # Must take shuttle craft to exit 
4433         if game.damage[DSHUTTL]==-1:
4434             prout(_("Ye Faerie Queene has no shuttle craft."))
4435             return
4436         if game.damage[DSHUTTL]<0:
4437             prout(_("Shuttle craft now serving Big Macs."))
4438             return
4439         if game.damage[DSHUTTL]>0:
4440             prout(_("Shuttle craft damaged."))
4441             return
4442         if game.landed:
4443             prout(_("You must be aboard the ship."))
4444             return
4445         if game.iscraft != "onship":
4446             prout(_("Shuttle craft not currently available."))
4447             return
4448         # Emit abandon ship messages 
4449         skip(1)
4450         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4451         skip(1)
4452         prouts(_("***ALL HANDS ABANDON SHIP!"))
4453         skip(2)
4454         prout(_("Captain and crew escape in shuttle craft."))
4455         if not game.state.baseq:
4456             # Oops! no place to go... 
4457             finish(FABANDN)
4458             return
4459         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4460         # Dispose of crew 
4461         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4462             prout(_("Remainder of ship's complement beam down"))
4463             prout(_("to nearest habitable planet."))
4464         elif q.planet != None and not damaged(DTRANSP):
4465             prout(_("Remainder of ship's complement beam down to %s.") %
4466                     q.planet)
4467         else:
4468             prout(_("Entire crew of %d left to die in outer space.") %
4469                     game.state.crew)
4470             game.casual += game.state.crew
4471             game.abandoned += game.state.crew
4472         # If at least one base left, give 'em the Faerie Queene 
4473         skip(1)
4474         game.icrystl = False # crystals are lost 
4475         game.nprobes = 0 # No probes 
4476         prout(_("You are captured by Klingons and released to"))
4477         prout(_("the Federation in a prisoner-of-war exchange."))
4478         nb = randrange(len(game.state.baseq))
4479         # Set up quadrant and position FQ adjacient to base 
4480         if not game.quadrant == game.state.baseq[nb]:
4481             game.quadrant = game.state.baseq[nb]
4482             game.sector.i = game.sector.j = 5
4483             newqad()
4484         while True:
4485             # position next to base by trial and error 
4486             game.quad[game.sector.i][game.sector.j] = IHDOT
4487             for l in range(QUADSIZE):
4488                 game.sector = game.base.scatter()
4489                 if VALID_SECTOR(game.sector.i, game.sector.j) and \
4490                        game.quad[game.sector.i][game.sector.j] == IHDOT:
4491                     break
4492             if l < QUADSIZE+1:
4493                 break # found a spot 
4494             game.sector.i=QUADSIZE/2
4495             game.sector.j=QUADSIZE/2
4496             newqad()
4497     # Get new commission 
4498     game.quad[game.sector.i][game.sector.j] = game.ship = IHF
4499     game.state.crew = FULLCREW
4500     prout(_("Starfleet puts you in command of another ship,"))
4501     prout(_("the Faerie Queene, which is antiquated but,"))
4502     prout(_("still useable."))
4503     if game.icrystl:
4504         prout(_("The dilithium crystals have been moved."))
4505     game.imine = False
4506     game.iscraft = "offship" # Galileo disappears 
4507     # Resupply ship 
4508     game.condition="docked"
4509     for l in range(NDEVICES): 
4510         game.damage[l] = 0.0
4511     game.damage[DSHUTTL] = -1
4512     game.energy = game.inenrg = 3000.0
4513     game.shield = game.inshld = 1250.0
4514     game.torps = game.intorps = 6
4515     game.lsupres=game.inlsr=3.0
4516     game.shldup=False
4517     game.warpfac=5.0
4518     return
4519
4520 # Code from planets.c begins here.
4521
4522 def consumeTime():
4523     "Abort a lengthy operation if an event interrupts it." 
4524     game.ididit = True
4525     events()
4526     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin: 
4527         return True
4528     return False
4529
4530 def survey():
4531     "Report on (uninhabited) planets in the galaxy."
4532     iknow = False
4533     skip(1)
4534     scanner.chew()
4535     prout(_("Spock-  \"Planet report follows, Captain.\""))
4536     skip(1)
4537     for i in range(game.inplan):
4538         if game.state.planets[i].pclass == "destroyed":
4539             continue
4540         if (game.state.planets[i].known != "unknown" \
4541             and not game.state.planets[i].inhabited) \
4542             or idebug:
4543             iknow = True
4544             if idebug and game.state.planets[i].known=="unknown":
4545                 proutn("(Unknown) ")
4546             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4547             proutn(_("   class "))
4548             proutn(game.state.planets[i].pclass)
4549             proutn("   ")
4550             if game.state.planets[i].crystals != present:
4551                 proutn(_("no "))
4552             prout(_("dilithium crystals present."))
4553             if game.state.planets[i].known=="shuttle_down": 
4554                 prout(_("    Shuttle Craft Galileo on surface."))
4555     if not iknow:
4556         prout(_("No information available."))
4557
4558 def orbit():
4559     "Enter standard orbit." 
4560     skip(1)
4561     scanner.chew()
4562     if game.inorbit:
4563         prout(_("Already in standard orbit."))
4564         return
4565     if damaged(DWARPEN) and damaged(DIMPULS):
4566         prout(_("Both warp and impulse engines damaged."))
4567         return
4568     if not game.plnet.is_valid():
4569         prout("There is no planet in this sector.")
4570         return
4571     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4572         prout(crmshp() + _(" not adjacent to planet."))
4573         skip(1)
4574         return
4575     game.optime = randreal(0.02, 0.05)
4576     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4577     newcnd()
4578     if consumeTime():
4579         return
4580     game.height = randreal(1400, 8600)
4581     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4582     game.inorbit = True
4583     game.ididit = True
4584
4585 def sensor():
4586     "Examine planets in this quadrant."
4587     if damaged(DSRSENS):
4588         if game.options & OPTION_TTY:
4589             prout(_("Short range sensors damaged."))
4590         return
4591     if game.iplnet == None:
4592         if game.options & OPTION_TTY:
4593             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4594         return
4595     if game.iplnet.known == "unknown":
4596         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4597         skip(1)
4598         prout(_("         Planet at Sector %s is of class %s.") %
4599               (game.plnet, game.iplnet.pclass))
4600         if game.iplnet.known=="shuttle_down": 
4601             prout(_("         Sensors show Galileo still on surface."))
4602         proutn(_("         Readings indicate"))
4603         if game.iplnet.crystals != "present":
4604             proutn(_(" no"))
4605         prout(_(" dilithium crystals present.\""))
4606         if game.iplnet.known == "unknown":
4607             game.iplnet.known = "known"
4608     elif game.iplnet.inhabited:
4609         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4610         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4611
4612 def beam():
4613     "Use the transporter."
4614     nrgneed = 0
4615     scanner.chew()
4616     skip(1)
4617     if damaged(DTRANSP):
4618         prout(_("Transporter damaged."))
4619         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4620             skip(1)
4621             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4622             if ja() == True:
4623                 shuttle()
4624         return
4625     if not game.inorbit:
4626         prout(crmshp() + _(" not in standard orbit."))
4627         return
4628     if game.shldup:
4629         prout(_("Impossible to transport through shields."))
4630         return
4631     if game.iplnet.known=="unknown":
4632         prout(_("Spock-  \"Captain, we have no information on this planet"))
4633         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4634         prout(_("  you may not go down.\""))
4635         return
4636     if not game.landed and game.iplnet.crystals=="absent":
4637         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4638         prout(_("  exploring a planet with no dilithium crystals."))
4639         proutn(_("  Are you sure this is wise?\" "))
4640         if ja() == False:
4641             scanner.chew()
4642             return
4643     if not (game.options & OPTION_PLAIN):
4644         nrgneed = 50 * game.skill + game.height / 100.0
4645         if nrgneed > game.energy:
4646             prout(_("Engineering to bridge--"))
4647             prout(_("  Captain, we don't have enough energy for transportation."))
4648             return
4649         if not game.landed and nrgneed * 2 > game.energy:
4650             prout(_("Engineering to bridge--"))
4651             prout(_("  Captain, we have enough energy only to transport you down to"))
4652             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4653             if game.iplnet.known == "shuttle_down":
4654                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4655             proutn(_("  Are you sure this is wise?\" "))
4656             if ja() == False:
4657                 scanner.chew()
4658                 return
4659     if game.landed:
4660         # Coming from planet 
4661         if game.iplnet.known=="shuttle_down":
4662             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4663             if ja() == True:
4664                 scanner.chew()
4665                 return
4666             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4667         prout(_("Landing party assembled, ready to beam up."))
4668         skip(1)
4669         prout(_("Kirk whips out communicator..."))
4670         prouts(_("BEEP  BEEP  BEEP"))
4671         skip(2)
4672         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4673     else:
4674         # Going to planet 
4675         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4676         skip(1)
4677         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4678         skip(1)
4679         prout(_("Kirk-  \"Energize.\""))
4680     game.ididit = True
4681     skip(1)
4682     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4683     skip(2)
4684     if withprob(0.98):
4685         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4686         skip(2)
4687         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4688         finish(FLOST)
4689         return
4690     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4691     game.landed = not game.landed
4692     game.energy -= nrgneed
4693     skip(2)
4694     prout(_("Transport complete."))
4695     if game.landed and game.iplnet.known=="shuttle_down":
4696         prout(_("The shuttle craft Galileo is here!"))
4697     if not game.landed and game.imine:
4698         game.icrystl = True
4699         game.cryprob = 0.05
4700     game.imine = False
4701     return
4702
4703 def mine():
4704     "Strip-mine a world for dilithium."
4705     skip(1)
4706     scanner.chew()
4707     if not game.landed:
4708         prout(_("Mining party not on planet."))
4709         return
4710     if game.iplnet.crystals == "mined":
4711         prout(_("This planet has already been strip-mined for dilithium."))
4712         return
4713     elif game.iplnet.crystals == "absent":
4714         prout(_("No dilithium crystals on this planet."))
4715         return
4716     if game.imine:
4717         prout(_("You've already mined enough crystals for this trip."))
4718         return
4719     if game.icrystl and game.cryprob == 0.05:
4720         prout(_("With all those fresh crystals aboard the ") + crmshp())
4721         prout(_("there's no reason to mine more at this time."))
4722         return
4723     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4724     if consumeTime():
4725         return
4726     prout(_("Mining operation complete."))
4727     game.iplnet.crystals = "mined"
4728     game.imine = game.ididit = True
4729
4730 def usecrystals():
4731     "Use dilithium crystals."
4732     game.ididit = False
4733     skip(1)
4734     scanner.chew()
4735     if not game.icrystl:
4736         prout(_("No dilithium crystals available."))
4737         return
4738     if game.energy >= 1000:
4739         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4740         prout(_("  except when Condition Yellow exists."))
4741         return
4742     prout(_("Spock- \"Captain, I must warn you that loading"))
4743     prout(_("  raw dilithium crystals into the ship's power"))
4744     prout(_("  system may risk a severe explosion."))
4745     proutn(_("  Are you sure this is wise?\" "))
4746     if ja() == False:
4747         scanner.chew()
4748         return
4749     skip(1)
4750     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4751     prout(_("  Mr. Spock and I will try it.\""))
4752     skip(1)
4753     prout(_("Spock-  \"Crystals in place, Sir."))
4754     prout(_("  Ready to activate circuit.\""))
4755     skip(1)
4756     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4757     skip(1)
4758     if with(game.cryprob):
4759         prouts(_("  \"Activating now! - - No good!  It's***"))
4760         skip(2)
4761         prouts(_("***RED ALERT!  RED A*L********************************"))
4762         skip(1)
4763         stars()
4764         prouts(_("******************   KA-BOOM!!!!   *******************"))
4765         skip(1)
4766         kaboom()
4767         return
4768     game.energy += randreal(5000.0, 5500.0)
4769     prouts(_("  \"Activating now! - - "))
4770     prout(_("The instruments"))
4771     prout(_("   are going crazy, but I think it's"))
4772     prout(_("   going to work!!  Congratulations, Sir!\""))
4773     game.cryprob *= 2.0
4774     game.ididit = True
4775
4776 def shuttle():
4777     "Use shuttlecraft for planetary jaunt."
4778     scanner.chew()
4779     skip(1)
4780     if damaged(DSHUTTL):
4781         if game.damage[DSHUTTL] == -1.0:
4782             if game.inorbit and game.iplnet.known == "shuttle_down":
4783                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4784             else:
4785                 prout(_("Ye Faerie Queene had no shuttle craft."))
4786         elif game.damage[DSHUTTL] > 0:
4787             prout(_("The Galileo is damaged."))
4788         else: # game.damage[DSHUTTL] < 0  
4789             prout(_("Shuttle craft is now serving Big Macs."))
4790         return
4791     if not game.inorbit:
4792         prout(crmshp() + _(" not in standard orbit."))
4793         return
4794     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4795         prout(_("Shuttle craft not currently available."))
4796         return
4797     if not game.landed and game.iplnet.known=="shuttle_down":
4798         prout(_("You will have to beam down to retrieve the shuttle craft."))
4799         return
4800     if game.shldup or game.condition == "docked":
4801         prout(_("Shuttle craft cannot pass through shields."))
4802         return
4803     if game.iplnet.known=="unknown":
4804         prout(_("Spock-  \"Captain, we have no information on this planet"))
4805         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4806         prout(_("  you may not fly down.\""))
4807         return
4808     game.optime = 3.0e-5*game.height
4809     if game.optime >= 0.8*game.state.remtime:
4810         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4811         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4812                int(100*game.optime/game.state.remtime))
4813         prout(_("remaining time."))
4814         proutn(_("Are you sure this is wise?\" "))
4815         if ja() == False:
4816             game.optime = 0.0
4817             return
4818     if game.landed:
4819         # Kirk on planet 
4820         if game.iscraft == "onship":
4821             # Galileo on ship! 
4822             if not damaged(DTRANSP):
4823                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4824                 if ja() == True:
4825                     beam()
4826                     return
4827                 proutn(_("Shuttle crew"))
4828             else:
4829                 proutn(_("Rescue party"))
4830             prout(_(" boards Galileo and swoops toward planet surface."))
4831             game.iscraft = "offship"
4832             skip(1)
4833             if consumeTime():
4834                 return
4835             game.iplnet.known="shuttle_down"
4836             prout(_("Trip complete."))
4837             return
4838         else:
4839             # Ready to go back to ship 
4840             prout(_("You and your mining party board the"))
4841             prout(_("shuttle craft for the trip back to the Enterprise."))
4842             skip(1)
4843             prouts(_("The short hop begins . . ."))
4844             skip(1)
4845             game.iplnet.known="known"
4846             game.icraft = True
4847             skip(1)
4848             game.landed = False
4849             if consumeTime():
4850                 return
4851             game.iscraft = "onship"
4852             game.icraft = False
4853             if game.imine:
4854                 game.icrystl = True
4855                 game.cryprob = 0.05
4856             game.imine = False
4857             prout(_("Trip complete."))
4858             return
4859     else:
4860         # Kirk on ship and so is Galileo 
4861         prout(_("Mining party assembles in the hangar deck,"))
4862         prout(_("ready to board the shuttle craft \"Galileo\"."))
4863         skip(1)
4864         prouts(_("The hangar doors open; the trip begins."))
4865         skip(1)
4866         game.icraft = True
4867         game.iscraft = "offship"
4868         if consumeTime():
4869             return
4870         game.iplnet.known = "shuttle_down"
4871         game.landed = True
4872         game.icraft = False
4873         prout(_("Trip complete."))
4874         return
4875
4876 def deathray():
4877     "Use the big zapper."
4878     game.ididit = False
4879     skip(1)
4880     scanner.chew()
4881     if game.ship != IHE:
4882         prout(_("Ye Faerie Queene has no death ray."))
4883         return
4884     if len(game.enemies)==0:
4885         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
4886         return
4887     if damaged(DDRAY):
4888         prout(_("Death Ray is damaged."))
4889         return
4890     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
4891     prout(_("  is highly unpredictible.  Considering the alternatives,"))
4892     proutn(_("  are you sure this is wise?\" "))
4893     if ja() == False:
4894         return
4895     prout(_("Spock-  \"Acknowledged.\""))
4896     skip(1)
4897     game.ididit = True
4898     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
4899     skip(1)
4900     prout(_("Crew scrambles in emergency preparation."))
4901     prout(_("Spock and Scotty ready the death ray and"))
4902     prout(_("prepare to channel all ship's power to the device."))
4903     skip(1)
4904     prout(_("Spock-  \"Preparations complete, sir.\""))
4905     prout(_("Kirk-  \"Engage!\""))
4906     skip(1)
4907     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
4908     skip(1)
4909     dprob = 0.30
4910     if game.options & OPTION_PLAIN:
4911         dprob = 0.5
4912     r = randreal()
4913     if r > dprob:
4914         prouts(_("Sulu- \"Captain!  It's working!\""))
4915         skip(2)
4916         while len(game.enemies) > 0:
4917             deadkl(game.enemies[1].kloc, game.quad[game.enemies[1].kloc.i][game.enemies[1].kloc.j],game.enemies[1].kloc)
4918         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
4919         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
4920             finish(FWON)    
4921         if (game.options & OPTION_PLAIN) == 0:
4922             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
4923             if withprob(0.05):
4924                 prout(_("   is still operational.\""))
4925             else:
4926                 prout(_("   has been rendered nonfunctional.\""))
4927                 game.damage[DDRAY] = 39.95
4928         return
4929     r = randreal()      # Pick failure method 
4930     if r <= 0.30:
4931         prouts(_("Sulu- \"Captain!  It's working!\""))
4932         skip(1)
4933         prouts(_("***RED ALERT!  RED ALERT!"))
4934         skip(1)
4935         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
4936         skip(1)
4937         prouts(_("***RED ALERT!  RED A*L********************************"))
4938         skip(1)
4939         stars()
4940         prouts(_("******************   KA-BOOM!!!!   *******************"))
4941         skip(1)
4942         kaboom()
4943         return
4944     if r <= 0.55:
4945         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
4946         skip(1)
4947         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
4948         skip(1)
4949         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
4950         prout(_("  have apparently been transformed into strange mutations."))
4951         prout(_("  Vulcans do not seem to be affected."))
4952         skip(1)
4953         prout(_("Kirk-  \"Raauch!  Raauch!\""))
4954         finish(FDRAY)
4955         return
4956     if r <= 0.75:
4957         intj
4958         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
4959         skip(2)
4960         proutn(_("Spock-  \"I believe the word is"))
4961         prouts(_(" *ASTONISHING*"))
4962         prout(_(" Mr. Sulu."))
4963         for i in range(QUADSIZE):
4964             for j in range(QUADSIZE):
4965                 if game.quad[i][j] == IHDOT:
4966                     game.quad[i][j] = IHQUEST
4967         prout(_("  Captain, our quadrant is now infested with"))
4968         prouts(_(" - - - - - -  *THINGS*."))
4969         skip(1)
4970         prout(_("  I have no logical explanation.\""))
4971         return
4972     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
4973     skip(1)
4974     prout(_("Scotty-  \"There are so many tribbles down here"))
4975     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
4976     finish(FTRIBBLE)
4977     return
4978
4979 # Code from reports.c begins here
4980
4981 def attackreport(curt):
4982     "eport status of bases under attack."
4983     if not curt:
4984         if is_scheduled(FCDBAS):
4985             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
4986             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
4987         elif game.isatb == 1:
4988             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
4989             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
4990         else:
4991             prout(_("No Starbase is currently under attack."))
4992     else:
4993         if is_scheduled(FCDBAS):
4994             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
4995         if game.isatb:
4996             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
4997         clreol()
4998
4999 def report():
5000     # report on general game status 
5001     scanner.chew()
5002     s1 = "" and game.thawed and _("thawed ")
5003     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5004     s3 = (None, _("novice"). _("fair"),
5005           _("good"), _("expert"), _("emeritus"))[game.skill]
5006     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5007     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5008         prout(_("No plaque is allowed."))
5009     if game.tourn:
5010         prout(_("This is tournament game %d.") % game.tourn)
5011     prout(_("Your secret password is \"%s\"") % game.passwd)
5012     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)), 
5013            (game.inkling + game.incom + game.inscom)))
5014     if game.incom - len(game.state.kcmdr):
5015         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5016     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
5017         prout(_(", but no Commanders."))
5018     else:
5019         prout(".")
5020     if game.skill > SKILL_FAIR:
5021         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5022     if len(game.state.baseq) != game.inbase:
5023         proutn(_("There "))
5024         if game.inbase-len(game.state.baseq)==1:
5025             proutn(_("has been 1 base"))
5026         else:
5027             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5028         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5029     else:
5030         prout(_("There are %d bases.") % game.inbase)
5031     if communicating() or game.iseenit:
5032         # Don't report this if not seen and
5033         # either the radio is dead or not at base!
5034         attackreport(False)
5035         game.iseenit = True
5036     if game.casual: 
5037         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5038     if game.nhelp:
5039         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5040     if game.ship == IHE:
5041         proutn(_("You have "))
5042         if game.nprobes:
5043             proutn("%d" % (game.nprobes))
5044         else:
5045             proutn(_("no"))
5046         proutn(_(" deep space probe"))
5047         if game.nprobes!=1:
5048             proutn(_("s"))
5049         prout(".")
5050     if communicating() and is_scheduled(FDSPROB):
5051         if game.isarmed: 
5052             proutn(_("An armed deep space probe is in "))
5053         else:
5054             proutn(_("A deep space probe is in "))
5055         prout("Quadrant %s." % game.probec)
5056     if game.icrystl:
5057         if game.cryprob <= .05:
5058             prout(_("Dilithium crystals aboard ship... not yet used."))
5059         else:
5060             i=0
5061             ai = 0.05
5062             while game.cryprob > ai:
5063                 ai *= 2.0
5064                 i += 1
5065             prout(_("Dilithium crystals have been used %d time%s.") % \
5066                   (i, (_("s"), "")[i==1]))
5067     skip(1)
5068         
5069 def lrscan(silent):
5070     "Long-range sensor scan."
5071     if damaged(DLRSENS):
5072         # Now allow base's sensors if docked 
5073         if game.condition != "docked":
5074             if not silent:
5075                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5076             return
5077         if not silent:
5078             prout(_("Starbase's long-range scan"))
5079     elif not silent:
5080         prout(_("Long-range scan"))
5081     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5082         if not silent:
5083             proutn(" ")
5084         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5085             if not VALID_QUADRANT(x, y):
5086                 if not silent:
5087                     proutn("  -1")
5088             else:
5089                 if not damaged(DRADIO):
5090                     game.state.galaxy[x][y].charted = True
5091                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5092                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5093                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5094                 if not silent and game.state.galaxy[x][y].supernova: 
5095                     proutn(" ***")
5096                 elif not silent:
5097                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
5098         prout(" ")
5099
5100 def damagereport():
5101     "Damage report."
5102     jdam = False
5103     scanner.chew()
5104
5105     for i in range(NDEVICES):
5106         if damaged(i):
5107             if not jdam:
5108                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5109                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5110                 jdam = True
5111             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5112                                                game.damage[i]+0.05,
5113                                                game.docfac*game.damage[i]+0.005))
5114     if not jdam:
5115         prout(_("All devices functional."))
5116
5117 def rechart():
5118     "Update the chart in the Enterprise's computer from galaxy data."
5119     game.lastchart = game.state.date
5120     for i in range(GALSIZE):
5121         for j in range(GALSIZE):
5122             if game.state.galaxy[i][j].charted:
5123                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5124                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5125                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5126
5127 def chart():
5128     "Display the star chart."
5129     scanner.chew()
5130     if (game.options & OPTION_AUTOSCAN):
5131         lrscan(silent=True)
5132     if not damaged(DRADIO):
5133         rechart()
5134     if game.lastchart < game.state.date and game.condition == "docked":
5135         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5136         rechart()
5137     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5138     if game.state.date > game.lastchart:
5139         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5140     prout("      1    2    3    4    5    6    7    8")
5141     for i in range(GALSIZE):
5142         proutn("%d |" % (i+1))
5143         for j in range(GALSIZE):
5144             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5145                 proutn("<")
5146             else:
5147                 proutn(" ")
5148             if game.state.galaxy[i][j].supernova:
5149                 show = "***"
5150             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5151                 show = ".1."
5152             elif game.state.galaxy[i][j].charted:
5153                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5154             else:
5155                 show = "..."
5156             proutn(show)
5157             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5158                 proutn(">")
5159             else:
5160                 proutn(" ")
5161         proutn("  |")
5162         if i<GALSIZE:
5163             skip(1)
5164
5165 def sectscan(goodScan, i, j):
5166     "Light up an individual dot in a sector."
5167     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5168         if (game.quad[i][j]==IHMATER0) or (game.quad[i][j]==IHMATER1) or (game.quad[i][j]==IHMATER2) or (game.quad[i][j]==IHE) or (game.quad[i][j]==IHF):
5169             #if game.condition   == "red": textcolor("red")
5170             #elif game.condition == "green": textcolor("green")
5171             #elif game.condition == "yellow": textcolor("yellow")
5172             #elif game.condition == "docked": textcolor("cyan")
5173             #elif game.condition == "dead": textcolor("brown")
5174             if game.quad[i][j] != game.ship: 
5175                 highvideo()
5176         proutn("%c " % game.quad[i][j])
5177         #textcolor(None)
5178     else:
5179         proutn("- ")
5180
5181 def status(req=0):
5182     "Emit status report lines"
5183     if not req or req == 1:
5184         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5185                % (game.state.date, game.state.remtime))
5186     if not req or req == 2:
5187         if game.condition != "docked":
5188             newcnd()
5189         dam = 0
5190         for t in range(NDEVICES):
5191             if game.damage[t]>0: 
5192                 dam += 1
5193         prstat(_("Condition"), _("%s, %i DAMAGES") % (game.condition.upper(), dam))
5194     if not req or req == 3:
5195         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5196     if not req or req == 4:
5197         if damaged(DLIFSUP):
5198             if game.condition == "docked":
5199                 s = _("DAMAGED, Base provides")
5200             else:
5201                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5202         else:
5203             s = _("ACTIVE")
5204         prstat(_("Life Support"), s)
5205     if not req or req == 5:
5206         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5207     if not req or req == 6:
5208         extra = ""
5209         if game.icrystl and (game.options & OPTION_SHOWME):
5210             extra = _(" (have crystals)")
5211         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5212     if not req or req == 7:
5213         prstat(_("Torpedoes"), "%d" % (game.torps))
5214     if not req or req == 8:
5215         if damaged(DSHIELD):
5216             s = _("DAMAGED,")
5217         elif game.shldup:
5218             s = _("UP,")
5219         else:
5220             s = _("DOWN,")
5221         data = _(" %d%% %.1f units") \
5222                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5223         prstat(_("Shields"), s+data)
5224     if not req or req == 9:
5225         prstat(_("Klingons Left"), "%d" \
5226                % (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem))
5227     if not req or req == 10:
5228         if game.options & OPTION_WORLDS:
5229             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5230             if plnet and plnet.inhabited:
5231                 prstat(_("Major system"), plnet.name)
5232             else:
5233                 prout(_("Sector is uninhabited"))
5234     elif not req or req == 11:
5235         attackreport(not req)
5236
5237 def request():
5238     "Request specified status data, a historical relic from slow TTYs."
5239     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5240     while scanner.next() == "IHEOL":
5241         proutn(_("Information desired? "))
5242     scanner.chew()
5243     if scanner.token in requests:
5244         status(requests.index(scanner.token))
5245     else:
5246         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5247         prout(("  date, condition, position, lsupport, warpfactor,"))
5248         prout(("  energy, torpedoes, shields, klingons, system, time."))
5249                 
5250 def srscan():
5251     "Short-range scan." 
5252     goodScan=True
5253     if damaged(DSRSENS):
5254         # Allow base's sensors if docked 
5255         if game.condition != "docked":
5256             prout(_("   S.R. SENSORS DAMAGED!"))
5257             goodScan=False
5258         else:
5259             prout(_("  [Using Base's sensors]"))
5260     else:
5261         prout(_("     Short-range scan"))
5262     if goodScan and not damaged(DRADIO): 
5263         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5264         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5265         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5266         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5267     prout("    1 2 3 4 5 6 7 8 9 10")
5268     if game.condition != "docked":
5269         newcnd()
5270     for i in range(QUADSIZE):
5271         proutn("%2d  " % (i+1))
5272         for j in range(QUADSIZE):
5273             sectscan(goodScan, i, j)
5274         skip(1)
5275                 
5276 def eta():
5277     "Use computer to get estimated time of arrival for a warp jump."
5278     w1 = coord(); w2 = coord()
5279     prompt = False
5280     if damaged(DCOMPTR):
5281         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5282         skip(1)
5283         return
5284     if scanner.next() != "IHREAL":
5285         prompt = True
5286         scanner.chew()
5287         proutn(_("Destination quadrant and/or sector? "))
5288         if scanner.next()!="IHREAL":
5289             huh()
5290             return
5291     w1.j = int(scanner.real-0.5)
5292     if scanner.next() != "IHREAL":
5293         huh()
5294         return
5295     w1.i = int(scanner.real-0.5)
5296     if scanner.next() == "IHREAL":
5297         w2.j = int(scanner.real-0.5)
5298         if scanner.next() != "IHREAL":
5299             huh()
5300             return
5301         w2.i = int(scanner.real-0.5)
5302     else:
5303         if game.quadrant.j>w1.i:
5304             w2.i = 0
5305         else:
5306             w2.i=QUADSIZE-1
5307         if game.quadrant.i>w1.j:
5308             w2.j = 0
5309         else:
5310             w2.j=QUADSIZE-1
5311     if not VALID_QUADRANT(w1.i, w1.j) or not VALID_SECTOR(w2.i, w2.j):
5312         huh()
5313         return
5314     game.dist = math.sqrt((w1.j-game.quadrant.j+0.1*(w2.j-game.sector.j))**2+
5315                 (w1.i-game.quadrant.i+0.1*(w2.i-game.sector.i))**2)
5316     wfl = False
5317     if prompt:
5318         prout(_("Answer \"no\" if you don't know the value:"))
5319     while True:
5320         scanner.chew()
5321         proutn(_("Time or arrival date? "))
5322         if scanner.next()=="IHREAL":
5323             ttime = scanner.real
5324             if ttime > game.state.date:
5325                 ttime -= game.state.date # Actually a star date
5326             twarp=(math.floor(math.sqrt((10.0*game.dist)/ttime)*10.0)+1.0)/10.0
5327             if ttime <= 1e-10 or twarp > 10:
5328                 prout(_("We'll never make it, sir."))
5329                 scanner.chew()
5330                 return
5331             if twarp < 1.0:
5332                 twarp = 1.0
5333             break
5334         scanner.chew()
5335         proutn(_("Warp factor? "))
5336         if scanner.next()== "IHREAL":
5337             wfl = True
5338             twarp = scanner.real
5339             if twarp<1.0 or twarp > 10.0:
5340                 huh()
5341                 return
5342             break
5343         prout(_("Captain, certainly you can give me one of these."))
5344     while True:
5345         scanner.chew()
5346         ttime = (10.0*game.dist)/twarp**2
5347         tpower = game.dist*twarp*twarp*twarp*(game.shldup+1)
5348         if tpower >= game.energy:
5349             prout(_("Insufficient energy, sir."))
5350             if not game.shldup or tpower > game.energy*2.0:
5351                 if not wfl:
5352                     return
5353                 proutn(_("New warp factor to try? "))
5354                 if scanner.next() == "IHREAL":
5355                     wfl = True
5356                     twarp = scanner.real
5357                     if twarp<1.0 or twarp > 10.0:
5358                         huh()
5359                         return
5360                     continue
5361                 else:
5362                     scanner.chew()
5363                     skip(1)
5364                     return
5365             prout(_("But if you lower your shields,"))
5366             proutn(_("remaining"))
5367             tpower /= 2
5368         else:
5369             proutn(_("Remaining"))
5370         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5371         if wfl:
5372             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5373         elif twarp==1.0:
5374             prout(_("Any warp speed is adequate."))
5375         else:
5376             prout(_("Minimum warp needed is %.2f,") % (twarp))
5377             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5378         if game.state.remtime < ttime:
5379             prout(_("Unfortunately, the Federation will be destroyed by then."))
5380         if twarp > 6.0:
5381             prout(_("You'll be taking risks at that speed, Captain"))
5382         if (game.isatb==1 and game.state.kscmdr == w1 and \
5383              scheduled(FSCDBAS)< ttime+game.state.date) or \
5384             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5385             prout(_("The starbase there will be destroyed by then."))
5386         proutn(_("New warp factor to try? "))
5387         if scanner.next() == "IHREAL":
5388             wfl = True
5389             twarp = scanner.real
5390             if twarp<1.0 or twarp > 10.0:
5391                 huh()
5392                 return
5393         else:
5394             scanner.chew()
5395             skip(1)
5396             return
5397
5398 # Code from setup.c begins here
5399
5400 def prelim():
5401     "Issue a historically correct banner."
5402     skip(2)
5403     prout(_("-SUPER- STAR TREK"))
5404     skip(1)
5405 # From the FORTRAN original
5406 #    prout(_("Latest update-21 Sept 78"))
5407 #    skip(1)
5408
5409 def freeze(boss):
5410     "Save game."
5411     if boss:
5412         scanner.token = "emsave.trk"
5413     else:
5414         key = scanner.next()
5415         if key == "IHEOL":
5416             proutn(_("File name: "))
5417             key = scanner.next()
5418         if key != "IHALPHA":
5419             huh()
5420             return
5421         scanner.chew()
5422         if '.' not in scanner.token:
5423             scanner.token += ".trk"
5424     try:
5425         fp = open(scanner.token, "wb")
5426     except IOError:
5427         prout(_("Can't freeze game as file %s") % scanner.token)
5428         return
5429     cPickle.dump(game, fp)
5430     fp.close()
5431
5432 def thaw():
5433     "Retrieve saved game." 
5434     game.passwd[0] = '\0'
5435     key = scanner.next()
5436     if key == "IHEOL":
5437         proutn(_("File name: "))
5438         key = scanner.next()
5439     if key != "IHALPHA":
5440         huh()
5441         return True
5442     scanner.chew()
5443     if '.' not in scanner.token:
5444         scanner.token += ".trk"
5445     try:
5446         fp = open(scanner.token, "rb")
5447     except IOError:
5448         prout(_("Can't thaw game in %s") % scanner.token)
5449         return
5450     game = cPickle.load(fp)
5451     fp.close()
5452     return False
5453
5454 # I used <http://www.memory-alpha.org> to find planets
5455 # with references in ST:TOS.  Eath and the Alpha Centauri
5456 # Colony have been omitted.
5457
5458 # Some planets marked Class G and P here will be displayed as class M
5459 # because of the way planets are generated. This is a known bug.
5460 systnames = (
5461     # Federation Worlds 
5462     _("Andoria (Fesoan)"),      # several episodes 
5463     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel" 
5464     _("Vulcan (T'Khasi)"),      # many episodes 
5465     _("Medusa"),                # TOS: "Is There in Truth No Beauty?" 
5466     _("Argelius II (Nelphia)"),# TOS: "Wolf in the Fold" ("IV" in BSD) 
5467     _("Ardana"),                # TOS: "The Cloud Minders" 
5468     _("Catulla (Cendo-Prae)"),  # TOS: "The Way to Eden" 
5469     _("Gideon"),                # TOS: "The Mark of Gideon" 
5470     _("Aldebaran III"), # TOS: "The Deadly Years" 
5471     _("Alpha Majoris I"),       # TOS: "Wolf in the Fold" 
5472     _("Altair IV"),             # TOS: "Amok Time 
5473     _("Ariannus"),              # TOS: "Let That Be Your Last Battlefield" 
5474     _("Benecia"),               # TOS: "The Conscience of the King" 
5475     _("Beta Niobe I (Sarpeidon)"),      # TOS: "All Our Yesterdays" 
5476     _("Alpha Carinae II"),      # TOS: "The Ultimate Computer" 
5477     _("Capella IV (Kohath)"),   # TOS: "Friday's Child" (Class G) 
5478     _("Daran V"),               # TOS: "For the World is Hollow and I Have Touched the Sky" 
5479     _("Deneb II"),              # TOS: "Wolf in the Fold" ("IV" in BSD) 
5480     _("Eminiar VII"),           # TOS: "A Taste of Armageddon" 
5481     _("Gamma Canaris IV"),      # TOS: "Metamorphosis" 
5482     _("Gamma Tranguli VI (Vaalel)"),    # TOS: "The Apple" 
5483     _("Ingraham B"),            # TOS: "Operation: Annihilate" 
5484     _("Janus IV"),              # TOS: "The Devil in the Dark" 
5485     _("Makus III"),             # TOS: "The Galileo Seven" 
5486     _("Marcos XII"),            # TOS: "And the Children Shall Lead", 
5487     _("Omega IV"),              # TOS: "The Omega Glory" 
5488     _("Regulus V"),             # TOS: "Amok Time 
5489     _("Deneva"),                # TOS: "Operation -- Annihilate!" 
5490     # Worlds from BSD Trek 
5491     _("Rigel II"),              # TOS: "Shore Leave" ("III" in BSD) 
5492     _("Beta III"),              # TOS: "The Return of the Archons" 
5493     _("Triacus"),               # TOS: "And the Children Shall Lead", 
5494     _("Exo III"),               # TOS: "What Are Little Girls Made Of?" (Class P) 
5495 #       # Others 
5496 #    _("Hansen's Planet"),      # TOS: "The Galileo Seven" 
5497 #    _("Taurus IV"),            # TOS: "The Galileo Seven" (class G) 
5498 #    _("Antos IV (Doraphane)"), # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?" 
5499 #    _("Izar"),                 # TOS: "Whom Gods Destroy" 
5500 #    _("Tiburon"),              # TOS: "The Way to Eden" 
5501 #    _("Merak II"),             # TOS: "The Cloud Minders" 
5502 #    _("Coridan (Desotriana)"), # TOS: "Journey to Babel" 
5503 #    _("Iotia"),                # TOS: "A Piece of the Action" 
5504 )
5505
5506 device = (
5507         _("S. R. Sensors"), \
5508         _("L. R. Sensors"), \
5509         _("Phasers"), \
5510         _("Photon Tubes"), \
5511         _("Life Support"), \
5512         _("Warp Engines"), \
5513         _("Impulse Engines"), \
5514         _("Shields"), \
5515         _("Subspace Radio"), \
5516         _("Shuttle Craft"), \
5517         _("Computer"), \
5518         _("Navigation System"), \
5519         _("Transporter"), \
5520         _("Shield Control"), \
5521         _("Death Ray"), \
5522         _("D. S. Probe"), \
5523 )
5524
5525 def setup():
5526     "Prepare to play, set up cosmos."
5527     w = coord()
5528     #  Decide how many of everything
5529     if choose():
5530         return # frozen game
5531     # Prepare the Enterprise
5532     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5533     game.ship = IHE
5534     game.state.crew = FULLCREW
5535     game.energy = game.inenrg = 5000.0
5536     game.shield = game.inshld = 2500.0
5537     game.inlsr = 4.0
5538     game.lsupres = 4.0
5539     game.quadrant = randplace(GALSIZE)
5540     game.sector = randplace(QUADSIZE)
5541     game.torps = game.intorps = 10
5542     game.nprobes = randrange(2, 5)
5543     game.warpfac = 5.0
5544     for i in range(NDEVICES): 
5545         game.damage[i] = 0.0
5546     # Set up assorted game parameters
5547     game.battle = coord()
5548     game.state.date = game.indate = 100.0 * randreal(20, 51)
5549     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5550     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5551     game.isatb = game.state.nplankl = 0
5552     game.state.starkl = game.state.basekl = 0
5553     game.iscraft = "onship"
5554     game.landed = False
5555     game.alive = True
5556     game.docfac = 0.25
5557     # Starchart is functional but we've never seen it
5558     game.lastchart = FOREVER
5559     # Put stars in the galaxy
5560     game.instar = 0
5561     for i in range(GALSIZE):
5562         for j in range(GALSIZE):
5563             k = randrange(1, QUADSIZE**2/10+1)
5564             game.instar += k
5565             game.state.galaxy[i][j].stars = k
5566     # Locate star bases in galaxy
5567     for i in range(game.inbase):
5568         while True:
5569             while True:
5570                 w = randplace(GALSIZE)
5571                 if not game.state.galaxy[w.i][w.j].starbase:
5572                     break
5573             contflag = False
5574             # C version: for (j = i-1; j > 0; j--)
5575             # so it did them in the opposite order.
5576             for j in range(1, i):
5577                 # Improved placement algorithm to spread out bases
5578                 distq = (w - game.state.baseq[j]).distance()
5579                 if distq < 6.0*(BASEMAX+1-game.inbase) and withprob(0.75):
5580                     contflag = True
5581                     if idebug:
5582                         prout("=== Abandoning base #%d at %s" % (i, w))
5583                     break
5584                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5585                     if idebug:
5586                         prout("=== Saving base #%d, close to #%d" % (i, j))
5587             if not contflag:
5588                 break
5589         game.state.baseq.append(w)
5590         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5591     # Position ordinary Klingon Battle Cruisers
5592     krem = game.inkling
5593     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5594     if klumper > MAXKLQUAD: 
5595         klumper = MAXKLQUAD
5596     while True:
5597         r = randreal()
5598         klump = (1.0 - r*r)*klumper
5599         if klump > krem:
5600             klump = krem
5601         krem -= klump
5602         while True:
5603             w = randplace(GALSIZE)
5604             if not game.state.galaxy[w.i][w.j].supernova and \
5605                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5606                 break
5607         game.state.galaxy[w.i][w.j].klingons += int(klump)
5608         if krem <= 0:
5609             break
5610     # Position Klingon Commander Ships
5611     for i in range(game.incom):
5612         while True:
5613             w = randplace(GALSIZE)
5614             if not welcoming(w) or w in game.state.kcmdr:
5615                 continue
5616             if (game.state.galaxy[w.i][w.j].klingons or withprob(0.25)):
5617                 break
5618         game.state.galaxy[w.i][w.j].klingons += 1
5619         game.state.kcmdr.append(w)
5620     # Locate planets in galaxy
5621     for i in range(game.inplan):
5622         while True:
5623             w = randplace(GALSIZE) 
5624             if game.state.galaxy[w.i][w.j].planet == None:
5625                 break
5626         new = planet()
5627         new.quadrant = w
5628         new.crystals = "absent"
5629         if (game.options & OPTION_WORLDS) and i < NINHAB:
5630             new.pclass = "M"    # All inhabited planets are class M
5631             new.crystals = "absent"
5632             new.known = "known"
5633             new.name = systnames[i]
5634             new.inhabited = True
5635         else:
5636             new.pclass = ("M", "N", "O")[randrange(0, 3)]
5637             if withprob(0.33):
5638                 new.crystals = "present"
5639             new.known = "unknown"
5640             new.inhabited = False
5641         game.state.galaxy[w.i][w.j].planet = new
5642         game.state.planets.append(new)
5643     # Locate Romulans
5644     for i in range(game.state.nromrem):
5645         w = randplace(GALSIZE)
5646         game.state.galaxy[w.i][w.j].romulans += 1
5647     # Place the Super-Commander if needed
5648     if game.state.nscrem > 0:
5649         while True:
5650             w = randplace(GALSIZE)
5651             if welcoming(w):
5652                 break
5653         game.state.kscmdr = w
5654         game.state.galaxy[w.i][w.j].klingons += 1
5655     # Initialize times for extraneous events
5656     schedule(FSNOVA, expran(0.5 * game.intime))
5657     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5658     schedule(FSNAP, randreal(1.0, 2.0)) # Force an early snapshot
5659     schedule(FBATTAK, expran(0.3*game.intime))
5660     unschedule(FCDBAS)
5661     if game.state.nscrem:
5662         schedule(FSCMOVE, 0.2777)
5663     else:
5664         unschedule(FSCMOVE)
5665     unschedule(FSCDBAS)
5666     unschedule(FDSPROB)
5667     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5668         schedule(FDISTR, expran(1.0 + game.intime))
5669     else:
5670         unschedule(FDISTR)
5671     unschedule(FENSLV)
5672     unschedule(FREPRO)
5673     # Place thing (in tournament game, we don't want one!)
5674     # New in SST2K: never place the Thing near a starbase.
5675     # This makes sense and avoids a special case in the old code.
5676     global thing
5677     if game.tourn is None:
5678         while True:
5679             thing = randplace(GALSIZE)
5680             if thing not in game.state.baseq:
5681                 break
5682     skip(2)
5683     game.state.snap = False
5684     if game.skill == SKILL_NOVICE:
5685         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5686         prout(_("a deadly Klingon invasion force. As captain of the United"))
5687         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5688         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5689         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5690         prout(_("your mission.  As you proceed you may be given more time."))
5691         skip(1)
5692         prout(_("You will have %d supporting starbases.") % (game.inbase))
5693         proutn(_("Starbase locations-  "))
5694     else:
5695         prout(_("Stardate %d.") % int(game.state.date))
5696         skip(1)
5697         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5698         prout(_("An unknown number of Romulans."))
5699         if game.state.nscrem:
5700             prout(_("And one (GULP) Super-Commander."))
5701         prout(_("%d stardates.") % int(game.intime))
5702         proutn(_("%d starbases in ") % game.inbase)
5703     for i in range(game.inbase):
5704         proutn(`game.state.baseq[i]`)
5705         proutn("  ")
5706     skip(2)
5707     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5708     proutn(_(" Sector %s") % game.sector)
5709     skip(2)
5710     prout(_("Good Luck!"))
5711     if game.state.nscrem:
5712         prout(_("  YOU'LL NEED IT."))
5713     waitfor()
5714     newqad()
5715     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
5716         game.shldup = True
5717     if game.neutz:      # bad luck to start in a Romulan Neutral Zone
5718         attack(torps_ok=False)
5719
5720 def choose():
5721     "Choose your game type."
5722     global thing
5723     while True:
5724         game.tourn = 0
5725         game.thawed = False
5726         game.skill = SKILL_NONE
5727         game.length = 0
5728         if not scanner.inqueue: # Can start with command line options 
5729             proutn(_("Would you like a regular, tournament, or saved game? "))
5730         scanner.next()
5731         if len(scanner.token)==0: # Try again
5732             continue
5733         if scanner.sees("tournament"):
5734             while scanner.next() == "IHEOL":
5735                 proutn(_("Type in tournament number-"))
5736             if scanner.real == 0:
5737                 scanner.chew()
5738                 continue # We don't want a blank entry
5739             game.tourn = int(round(scanner.real))
5740             random.seed(scanner.real)
5741             if logfp:
5742                 logfp.write("# random.seed(%d)\n" % scanner.real)
5743             break
5744         if scanner.sees("saved") or scanner.sees("frozen"):
5745             if thaw():
5746                 continue
5747             scanner.chew()
5748             if game.passwd == None:
5749                 continue
5750             if not game.alldone:
5751                 game.thawed = True # No plaque if not finished
5752             report()
5753             waitfor()
5754             return True
5755         if scanner.sees("regular"):
5756             break
5757         proutn(_("What is \"%s\"?") % scanner.token)
5758         scanner.chew()
5759     while game.length==0 or game.skill==SKILL_NONE:
5760         if scanner.next() == "IHALPHA":
5761             if scanner.sees("short"):
5762                 game.length = 1
5763             elif scanner.sees("medium"):
5764                 game.length = 2
5765             elif scanner.sees("long"):
5766                 game.length = 4
5767             elif scanner.sees("novice"):
5768                 game.skill = SKILL_NOVICE
5769             elif scanner.sees("fair"):
5770                 game.skill = SKILL_FAIR
5771             elif scanner.sees("good"):
5772                 game.skill = SKILL_GOOD
5773             elif scanner.sees("expert"):
5774                 game.skill = SKILL_EXPERT
5775             elif scanner.sees("emeritus"):
5776                 game.skill = SKILL_EMERITUS
5777             else:
5778                 proutn(_("What is \""))
5779                 proutn(scanner.token)
5780                 prout("\"?")
5781         else:
5782             scanner.chew()
5783             if game.length==0:
5784                 proutn(_("Would you like a Short, Medium, or Long game? "))
5785             elif game.skill == SKILL_NONE:
5786                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5787     # Choose game options -- added by ESR for SST2K
5788     if scanner.next() != "IHALPHA":
5789         scanner.chew()
5790         proutn(_("Choose your game style (or just press enter): "))
5791         scanner.next()
5792     if scanner.sees("plain"):
5793         # Approximates the UT FORTRAN version.
5794         game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5795         game.options |= OPTION_PLAIN
5796     elif scanner.sees("almy"):
5797         # Approximates Tom Almy's version.
5798         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5799         game.options |= OPTION_ALMY
5800     elif scanner.sees("fancy"):
5801         pass
5802     elif len(scanner.token):
5803         proutn(_("What is \"%s\"?") % scanner.token)
5804     setpassword()
5805     if game.passwd == "debug":
5806         idebug = True
5807         prout("=== Debug mode enabled.")
5808     # Use parameters to generate initial values of things
5809     game.damfac = 0.5 * game.skill
5810     game.inbase = randrange(BASEMIN, BASEMAX+1)
5811     game.inplan = 0
5812     if game.options & OPTION_PLANETS:
5813         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
5814     if game.options & OPTION_WORLDS:
5815         game.inplan += int(NINHAB)
5816     game.state.nromrem = game.inrom = randrange(2 *game.skill)
5817     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
5818     game.state.remtime = 7.0 * game.length
5819     game.intime = game.state.remtime
5820     game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
5821     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
5822     game.state.remres = (game.inkling+4*game.incom)*game.intime
5823     game.inresor = game.state.remres
5824     if game.inkling > 50:
5825         game.state.inbase += 1
5826     return False
5827
5828 def dropin(iquad=None):
5829     "Drop a feature on a random dot in the current quadrant."
5830     while True:
5831         w = randplace(QUADSIZE)
5832         if game.quad[w.i][w.j] == IHDOT:
5833             break
5834     if iquad is not None:
5835         game.quad[w.i][w.j] = iquad
5836     return w
5837
5838 def newcnd():
5839     "Update our alert status."
5840     game.condition = "green"
5841     if game.energy < 1000.0:
5842         game.condition = "yellow"
5843     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
5844         game.condition = "red"
5845     if not game.alive:
5846         game.condition="dead"
5847
5848 def newkling():
5849     "Drop new Klingon into current quadrant."
5850     return enemy(IHK, loc=dropin(), power=randreal(300,450)+25.0*game.skill)
5851
5852 def newqad():
5853     "Set up a new state of quadrant, for when we enter or re-enter it."
5854     game.justin = True
5855     game.iplnet = None
5856     game.neutz = game.inorbit = game.landed = False
5857     game.ientesc = game.iseenit = False
5858     # Create a blank quadrant
5859     game.quad = fill2d(QUADSIZE, lambda i, j: IHDOT)
5860     if game.iscate:
5861         # Attempt to escape Super-commander, so tbeam back!
5862         game.iscate = False
5863         game.ientesc = True
5864     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
5865     # cope with supernova
5866     if q.supernova:
5867         return
5868     game.klhere = q.klingons
5869     game.irhere = q.romulans
5870     # Position Starship
5871     game.quad[game.sector.i][game.sector.j] = game.ship
5872     game.enemies = []
5873     if q.klingons:
5874         # Position ordinary Klingons
5875         for i in range(game.klhere):
5876             newkling()
5877         # If we need a commander, promote a Klingon
5878         for cmdr in game.state.kcmdr:
5879             if cmdr == game.quadrant:
5880                 e = game.enemies[game.klhere-1]
5881                 game.quad[e.kloc.i][e.kloc.j] = IHC
5882                 e.kpower = randreal(950,1350) + 50.0*game.skill
5883                 break   
5884         # If we need a super-commander, promote a Klingon
5885         if game.quadrant == game.state.kscmdr:
5886             e = game.enemies[0]
5887             game.quad[e.kloc.i][e.kloc.j] = IHS
5888             e.kpower = randreal(1175.0,  1575.0) + 125.0*game.skill
5889             game.iscate = (game.state.remkl > 1)
5890     # Put in Romulans if needed
5891     for i in range(q.romulans):
5892         enemy(IHR, loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
5893     # If quadrant needs a starbase, put it in
5894     if q.starbase:
5895         game.base = dropin(IHB)
5896     # If quadrant needs a planet, put it in
5897     if q.planet:
5898         game.iplnet = q.planet
5899         if not q.planet.inhabited:
5900             game.plnet = dropin(IHP)
5901         else:
5902             game.plnet = dropin(IHW)
5903     # Check for condition
5904     newcnd()
5905     # Check for RNZ
5906     if game.irhere > 0 and game.klhere == 0:
5907         game.neutz = True
5908         if not damaged(DRADIO):
5909             skip(1)
5910             prout(_("LT. Uhura- \"Captain, an urgent message."))
5911             prout(_("  I'll put it on audio.\"  CLICK"))
5912             skip(1)
5913             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
5914             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
5915     # Put in THING if needed
5916     if thing == game.quadrant:
5917         enemy(type=IHQUEST, loc=dropin(),
5918                   power=randreal(6000,6500.0)+250.0*game.skill)
5919         if not damaged(DSRSENS):
5920             skip(1)
5921             prout(_("Mr. Spock- \"Captain, this is most unusual."))
5922             prout(_("    Please examine your short-range scan.\""))
5923     # Decide if quadrant needs a Tholian; lighten up if skill is low 
5924     if game.options & OPTION_THOLIAN:
5925         if (game.skill < SKILL_GOOD and withprob(0.02)) or \
5926             (game.skill == SKILL_GOOD and withprob(0.05)) or \
5927             (game.skill > SKILL_GOOD and withprob(0.08)):
5928             w = coord()
5929             while True:
5930                 w.i = withprob(0.5) * (QUADSIZE-1)
5931                 w.j = withprob(0.5) * (QUADSIZE-1)
5932                 if game.quad[w.i][w.j] == IHDOT:
5933                     break
5934             game.tholian = enemy(type=IHT, loc=w,
5935                                  power=randrange(100, 500) + 25.0*game.skill)
5936             # Reserve unoccupied corners 
5937             if game.quad[0][0]==IHDOT:
5938                 game.quad[0][0] = 'X'
5939             if game.quad[0][QUADSIZE-1]==IHDOT:
5940                 game.quad[0][QUADSIZE-1] = 'X'
5941             if game.quad[QUADSIZE-1][0]==IHDOT:
5942                 game.quad[QUADSIZE-1][0] = 'X'
5943             if game.quad[QUADSIZE-1][QUADSIZE-1]==IHDOT:
5944                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
5945     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
5946     # And finally the stars
5947     for i in range(q.stars):
5948         dropin(IHSTAR)
5949     # Put in a few black holes
5950     for i in range(1, 3+1):
5951         if withprob(0.5): 
5952             dropin(IHBLANK)
5953     # Take out X's in corners if Tholian present
5954     if game.tholian:
5955         if game.quad[0][0]=='X':
5956             game.quad[0][0] = IHDOT
5957         if game.quad[0][QUADSIZE-1]=='X':
5958             game.quad[0][QUADSIZE-1] = IHDOT
5959         if game.quad[QUADSIZE-1][0]=='X':
5960             game.quad[QUADSIZE-1][0] = IHDOT
5961         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
5962             game.quad[QUADSIZE-1][QUADSIZE-1] = IHDOT
5963
5964 def setpassword():
5965     "Set the self-destruct password."
5966     if game.options & OPTION_PLAIN:
5967         while True:
5968             scanner.chew()
5969             proutn(_("Please type in a secret password- "))
5970             scanner.next()
5971             game.passwd = scanner.token
5972             if game.passwd != None:
5973                 break
5974     else:
5975         game.passwd = ""
5976         for i in range(8):
5977             game.passwd += chr(ord('a')+randrange(26))
5978
5979 # Code from sst.c begins here
5980
5981 commands = {
5982     "SRSCAN":           OPTION_TTY,
5983     "STATUS":           OPTION_TTY,
5984     "REQUEST":          OPTION_TTY,
5985     "LRSCAN":           OPTION_TTY,
5986     "PHASERS":          0,
5987     "TORPEDO":          0,
5988     "PHOTONS":          0,
5989     "MOVE":             0,
5990     "SHIELDS":          0,
5991     "DOCK":             0,
5992     "DAMAGES":          0,
5993     "CHART":            0,
5994     "IMPULSE":          0,
5995     "REST":             0,
5996     "WARP":             0,
5997     "SCORE":            0,
5998     "SENSORS":          OPTION_PLANETS,
5999     "ORBIT":            OPTION_PLANETS,
6000     "TRANSPORT":        OPTION_PLANETS,
6001     "MINE":             OPTION_PLANETS,
6002     "CRYSTALS":         OPTION_PLANETS,
6003     "SHUTTLE":          OPTION_PLANETS,
6004     "PLANETS":          OPTION_PLANETS,
6005     "REPORT":           0,
6006     "COMPUTER":         0,
6007     "COMMANDS":         0,
6008     "EMEXIT":           0,
6009     "PROBE":            OPTION_PROBE,
6010     "SAVE":             0,
6011     "FREEZE":           0,      # Synonym for SAVE
6012     "ABANDON":          0,
6013     "DESTRUCT":         0,
6014     "DEATHRAY":         0,
6015     "DEBUG":            0,
6016     "MAYDAY":           0,
6017     "SOS":              0,      # Synonym for MAYDAY
6018     "CALL":             0,      # Synonym for MAYDAY
6019     "QUIT":             0,
6020     "HELP":             0,
6021 }
6022
6023 def listCommands():
6024     "Generate a list of legal commands."
6025     prout(_("LEGAL COMMANDS ARE:"))
6026     emitted = 0
6027     for key in commands:
6028         if not commands[key] or (commands[key] & game.options):
6029             proutn("%-12s " % key)
6030             emitted += 1
6031             if emitted % 5 == 4:
6032                 skip(1)
6033     skip(1)
6034
6035 def helpme():
6036     "Browse on-line help."
6037     key = scanner.next()
6038     while True:
6039         if key == "IHEOL":
6040             setwnd(prompt_window)
6041             proutn(_("Help on what command? "))
6042             key = scanner.next()
6043         setwnd(message_window)
6044         if key == "IHEOL":
6045             return
6046         if scanner.token in commands or scanner.token == "ABBREV":
6047             break
6048         skip(1)
6049         listCommands()
6050         key = "IHEOL"
6051         scanner.chew()
6052         skip(1)
6053     cmd = scanner.token.upper()
6054     try:
6055         fp = open(SSTDOC, "r")
6056     except IOError:
6057         try:
6058             fp = open(DOC_NAME, "r")
6059         except IOError:
6060             prout(_("Spock-  \"Captain, that information is missing from the"))
6061             proutn(_("   computer. You need to find "))
6062             proutn(DOC_NAME)
6063             prout(_(" and put it in the"))
6064             proutn(_("   current directory or to "))
6065             proutn(SSTDOC)
6066             prout(".\"")
6067             #
6068             # This used to continue: "You need to find SST.DOC and put 
6069             # it in the current directory."
6070             # 
6071             return
6072     while True:
6073         linebuf = fp.readline()
6074         if linebuf == '':
6075             prout(_("Spock- \"Captain, there is no information on that command.\""))
6076             fp.close()
6077             return
6078         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6079             linebuf = linebuf[3:].strip()
6080             if cmd == linebuf:
6081                 break
6082     skip(1)
6083     prout(_("Spock- \"Captain, I've found the following information:\""))
6084     skip(1)
6085     while linebuf in fp:
6086         if "******" in linebuf:
6087             break
6088         proutn(linebuf)
6089     fp.close()
6090
6091 def makemoves():
6092     "Command-interpretation loop."
6093     clrscr()
6094     setwnd(message_window)
6095     while True:         # command loop 
6096         drawmaps(1)
6097         while True:     # get a command 
6098             hitme = False
6099             game.justin = False
6100             game.optime = 0.0
6101             scanner.chew()
6102             setwnd(prompt_window)
6103             clrscr()
6104             proutn("COMMAND> ")
6105             if scanner.next() == "IHEOL":
6106                 if game.options & OPTION_CURSES:
6107                     makechart()
6108                 continue
6109             elif scanner.token == "":
6110                 continue
6111             game.ididit = False
6112             clrscr()
6113             setwnd(message_window)
6114             clrscr()
6115             candidates = filter(lambda x: x.startswith(scanner.token.upper()),
6116                                 commands)
6117             if len(candidates) == 1:
6118                 cmd = candidates[0]
6119                 break
6120             elif candidates and not (game.options & OPTION_PLAIN):
6121                 prout("Commands with prefix '%s': %s" % (scanner.token, " ".join(candidates)))
6122             else:
6123                 listCommands()
6124                 continue
6125         if cmd == "SRSCAN":             # srscan
6126             srscan()
6127         elif cmd == "STATUS":           # status
6128             status()
6129         elif cmd == "REQUEST":          # status request 
6130             request()
6131         elif cmd == "LRSCAN":           # long range scan
6132             lrscan(silent=False)
6133         elif cmd == "PHASERS":          # phasers
6134             phasers()
6135             if game.ididit:
6136                 hitme = True
6137         elif cmd == "TORPEDO":          # photon torpedos
6138             photon()
6139             if game.ididit:
6140                 hitme = True
6141         elif cmd == "MOVE":             # move under warp
6142             warp(False)
6143         elif cmd == "SHIELDS":          # shields
6144             doshield(shraise=False)
6145             if game.ididit:
6146                 hitme = True
6147                 game.shldchg = False
6148         elif cmd == "DOCK":             # dock at starbase
6149             dock(True)
6150             if game.ididit:
6151                 attack(torps_ok=False)          
6152         elif cmd == "DAMAGES":          # damage reports
6153             damagereport()
6154         elif cmd == "CHART":            # chart
6155             makechart()
6156         elif cmd == "IMPULSE":          # impulse
6157             impulse()
6158         elif cmd == "REST":             # rest
6159             wait()
6160             if game.ididit:
6161                 hitme = True
6162         elif cmd == "WARP":             # warp
6163             setwarp()
6164         elif cmd == "SCORE":            # score
6165             score()
6166         elif cmd == "SENSORS":          # sensors
6167             sensor()
6168         elif cmd == "ORBIT":            # orbit
6169             orbit()
6170             if game.ididit:
6171                 hitme = True
6172         elif cmd == "TRANSPORT":                # transport "beam"
6173             beam()
6174         elif cmd == "MINE":             # mine
6175             mine()
6176             if game.ididit:
6177                 hitme = True
6178         elif cmd == "CRYSTALS":         # crystals
6179             usecrystals()
6180             if game.ididit:
6181                 hitme = True
6182         elif cmd == "SHUTTLE":          # shuttle
6183             shuttle()
6184             if game.ididit:
6185                 hitme = True
6186         elif cmd == "PLANETS":          # Planet list
6187             survey()
6188         elif cmd == "REPORT":           # Game Report 
6189             report()
6190         elif cmd == "COMPUTER":         # use COMPUTER!
6191             eta()
6192         elif cmd == "COMMANDS":
6193             listCommands()
6194         elif cmd == "EMEXIT":           # Emergency exit
6195             clrscr()                    # Hide screen
6196             freeze(True)                # forced save
6197             raise SysExit,1                     # And quick exit
6198         elif cmd == "PROBE":
6199             probe()                     # Launch probe
6200             if game.ididit:
6201                 hitme = True
6202         elif cmd == "ABANDON":          # Abandon Ship
6203             abandon()
6204         elif cmd == "DESTRUCT":         # Self Destruct
6205             selfdestruct()
6206         elif cmd == "SAVE":             # Save Game
6207             freeze(False)
6208             clrscr()
6209             if game.skill > SKILL_GOOD:
6210                 prout(_("WARNING--Saved games produce no plaques!"))
6211         elif cmd == "DEATHRAY":         # Try a desparation measure
6212             deathray()
6213             if game.ididit:
6214                 hitme = True
6215         elif cmd == "DEBUGCMD":         # What do we want for debug???
6216             debugme()
6217         elif cmd == "MAYDAY":           # Call for help
6218             mayday()
6219             if game.ididit:
6220                 hitme = True
6221         elif cmd == "QUIT":
6222             game.alldone = True         # quit the game
6223         elif cmd == "HELP":
6224             helpme()                    # get help
6225         while True:
6226             if game.alldone:
6227                 break           # Game has ended
6228             if game.optime != 0.0:
6229                 events()
6230                 if game.alldone:
6231                     break       # Events did us in
6232             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6233                 atover(False)
6234                 continue
6235             if hitme and not game.justin:
6236                 attack(torps_ok=True)
6237                 if game.alldone:
6238                     break
6239                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6240                     atover(False)
6241                     hitme = True
6242                     continue
6243             break
6244         if game.alldone:
6245             break
6246     if idebug:
6247         prout("=== Ending")
6248
6249 def cramen(type):
6250     "Emit the name of an enemy or feature." 
6251     if   type == IHR: s = _("Romulan")
6252     elif type == IHK: s = _("Klingon")
6253     elif type == IHC: s = _("Commander")
6254     elif type == IHS: s = _("Super-commander")
6255     elif type == IHSTAR: s = _("Star")
6256     elif type == IHP: s = _("Planet")
6257     elif type == IHB: s = _("Starbase")
6258     elif type == IHBLANK: s = _("Black hole")
6259     elif type == IHT: s = _("Tholian")
6260     elif type == IHWEB: s = _("Tholian web")
6261     elif type == IHQUEST: s = _("Stranger")
6262     elif type == IHW: s = _("Inhabited World")
6263     else: s = "Unknown??"
6264     return s
6265
6266 def crmena(stars, enemy, loctype, w):
6267     "Emit the name of an enemy and his location."
6268     buf = ""
6269     if stars:
6270         buf += "***"
6271     buf += cramen(enemy) + _(" at ")
6272     if loctype == "quadrant":
6273         buf += _("Quadrant ")
6274     elif loctype == "sector":
6275         buf += _("Sector ")
6276     return buf + `w`
6277
6278 def crmshp():
6279     "Emit our ship name." 
6280     return{IHE:_("Enterprise"),IHF:_("Faerie Queene")}.get(game.ship,"Ship???")
6281
6282 def stars():
6283     "Emit a line of stars" 
6284     prouts("******************************************************")
6285     skip(1)
6286
6287 def expran(avrage):
6288     return -avrage*math.log(1e-7 + randreal())
6289
6290 def randplace(size):
6291     "Choose a random location."
6292     w = coord()
6293     w.i = randrange(size) 
6294     w.j = randrange(size)
6295     return w
6296
6297 class sstscanner:
6298     def __init__(self):
6299         self.type = None
6300         self.token = None
6301         self.real = 0.0
6302         self.inqueue = []
6303     def next(self):
6304         # Get a token from the user
6305         self.real = 0.0
6306         self.token = ''
6307         # Fill the token quue if nothing here
6308         while not self.inqueue:
6309             line = cgetline()
6310             if curwnd==prompt_window:
6311                 clrscr()
6312                 setwnd(message_window)
6313                 clrscr()
6314             if line == '':
6315                 return None
6316             if not line:
6317                 continue
6318             else:
6319                 self.inqueue = line.lstrip().split() + ["IHEOL"] 
6320         # From here on in it's all looking at the queue
6321         self.token = self.inqueue.pop(0)
6322         if self.token == "IHEOL":
6323             self.type = "IHEOL"
6324             return "IHEOL"
6325         try:
6326             self.real = float(self.token)
6327             self.type = "IHREAL"
6328             return "IHREAL"
6329         except ValueError:
6330             pass
6331         # Treat as alpha
6332         self.token = self.token.lower()
6333         self.type = "IHALPHA"
6334         self.real = None
6335         return "IHALPHA"
6336     def append(self, tok):
6337         self.inqueue.append(tok)
6338     def push(self, tok):
6339         self.inqueue.insert(0, tok)
6340     def waiting(self):
6341         return self.inqueue
6342     def chew(self):
6343         # Demand input for next scan
6344         self.inqueue = []
6345         self.real = self.token = None
6346     def chew2(self):
6347         # return "IHEOL" next time 
6348         self.inqueue = ["IHEOL"]
6349         self.real = self.token = None
6350     def sees(self, s):
6351         # compares s to item and returns true if it matches to the length of s
6352         return s.startswith(self.token)
6353     def int(self):
6354         # Round token value to nearest integer
6355         return int(round(scanner.real))
6356     def getcoord(self):
6357         s = coord()
6358         scanner.next()
6359         if scanner.type != "IHREAL":
6360             huh()
6361             return None
6362         s.i = scanner.int()-1
6363         scanner.next()
6364         if scanner.type != "IHREAL":
6365             huh()
6366             return None
6367         s.j = scanner.int()-1
6368         return s
6369     def __repr__(str):
6370         return "<sstcanner: token=%s, type=%s, queue=%s>" % (scanner.token, scanner.type, scanner.inqueue)
6371
6372 def ja():
6373     "Yes-or-no confirmation."
6374     scanner.chew()
6375     while True:
6376         scanner.next()
6377         if scanner.token == 'y':
6378             return True
6379         if scanner.token == 'n':
6380             return False
6381         scanner.chew()
6382         proutn(_("Please answer with \"y\" or \"n\": "))
6383
6384 def huh():
6385     "Complain about unparseable input."
6386     scanner.chew()
6387     skip(1)
6388     prout(_("Beg your pardon, Captain?"))
6389
6390 def debugme():
6391     "Access to the internals for debugging."
6392     proutn("Reset levels? ")
6393     if ja() == True:
6394         if game.energy < game.inenrg:
6395             game.energy = game.inenrg
6396         game.shield = game.inshld
6397         game.torps = game.intorps
6398         game.lsupres = game.inlsr
6399     proutn("Reset damage? ")
6400     if ja() == True:
6401         for i in range(NDEVICES): 
6402             if game.damage[i] > 0.0: 
6403                 game.damage[i] = 0.0
6404     proutn("Toggle debug flag? ")
6405     if ja() == True:
6406         idebug = not idebug
6407         if idebug:
6408             prout("Debug output ON")        
6409         else:
6410             prout("Debug output OFF")
6411     proutn("Cause selective damage? ")
6412     if ja() == True:
6413         for i in range(NDEVICES):
6414             proutn("Kill %s?" % device[i])
6415             scanner.chew()
6416             key = scanner.next()
6417             if key == "IHALPHA" and scanner.sees("y"):
6418                 game.damage[i] = 10.0
6419     proutn("Examine/change events? ")
6420     if ja() == True:
6421         ev = event()
6422         w = coord()
6423         legends = {
6424             FSNOVA:  "Supernova       ",
6425             FTBEAM:  "T Beam          ",
6426             FSNAP:   "Snapshot        ",
6427             FBATTAK: "Base Attack     ",
6428             FCDBAS:  "Base Destroy    ",
6429             FSCMOVE: "SC Move         ",
6430             FSCDBAS: "SC Base Destroy ",
6431             FDSPROB: "Probe Move      ",
6432             FDISTR:  "Distress Call   ",
6433             FENSLV:  "Enslavement     ",
6434             FREPRO:  "Klingon Build   ",
6435         }
6436         for i in range(1, NEVENTS):
6437             proutn(legends[i])
6438             if is_scheduled(i):
6439                 proutn("%.2f" % (scheduled(i)-game.state.date))
6440                 if i == FENSLV or i == FREPRO:
6441                     ev = findevent(i)
6442                     proutn(" in %s" % ev.quadrant)
6443             else:
6444                 proutn("never")
6445             proutn("? ")
6446             scanner.chew()
6447             key = scanner.next()
6448             if key == 'n':
6449                 unschedule(i)
6450                 scanner.chew()
6451             elif key == "IHREAL":
6452                 ev = schedule(i, scanner.real)
6453                 if i == FENSLV or i == FREPRO:
6454                     scanner.chew()
6455                     proutn("In quadrant- ")
6456                     key = scanner.next()
6457                     # "IHEOL" says to leave coordinates as they are 
6458                     if key != "IHEOL":
6459                         if key != "IHREAL":
6460                             prout("Event %d canceled, no x coordinate." % (i))
6461                             unschedule(i)
6462                             continue
6463                         w.i = int(round(scanner.real))
6464                         key = scanner.next()
6465                         if key != "IHREAL":
6466                             prout("Event %d canceled, no y coordinate." % (i))
6467                             unschedule(i)
6468                             continue
6469                         w.j = int(round(scanner.real))
6470                         ev.quadrant = w
6471         scanner.chew()
6472     proutn("Induce supernova here? ")
6473     if ja() == True:
6474         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6475         atover(True)
6476
6477 if __name__ == '__main__':
6478     import getopt, socket
6479     try:
6480         global line, thing, game, idebug
6481         game = None
6482         thing = coord()
6483         thing.angry = False
6484         game = gamestate()
6485         idebug = 0
6486         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6487         if os.getenv("TERM"):
6488             game.options |= OPTION_CURSES
6489         else:
6490             game.options |= OPTION_TTY
6491         seed = int(time.time())
6492         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:tx")
6493         for (switch, val) in options:
6494             if switch == '-r':
6495                 try:
6496                     replayfp = open(val, "r")
6497                 except IOError:
6498                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6499                     raise SystemExit, 1
6500                 try:
6501                     line = replayfp.readline().strip()
6502                     (leader, key, seed) = line.split()
6503                     seed = eval(seed)
6504                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
6505                     line = replayfp.readline().strip()
6506                     arguments += line.split()[2:]
6507                 except ValueError:
6508                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6509                     raise SystemExit(1)
6510                 game.options |= OPTION_TTY
6511                 game.options &=~ OPTION_CURSES
6512             elif switch == '-s':
6513                 seed = int(val)
6514             elif switch == '-t':
6515                 game.options |= OPTION_TTY
6516                 game.options &=~ OPTION_CURSES
6517             elif switch == '-x':
6518                 idebug = True
6519             else:
6520                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6521                 raise SystemExit, 1
6522         # where to save the input in case of bugs
6523         try:
6524             logfp = open("/usr/tmp/sst-input.log", "w")
6525         except IOError:
6526             sys.stderr.write("sst: warning, can't open logfile\n")
6527         if logfp:
6528             logfp.write("# seed %s\n" % seed)
6529             logfp.write("# options %s\n" % " ".join(arguments))
6530             logfp.write("# recorded by %s@%s on %s\n" % \
6531                     (os.getenv("LOGNAME"),socket.gethostname(),time.ctime()))
6532         random.seed(seed)
6533         scanner = sstscanner()
6534         map(scanner.append, arguments)
6535         try:
6536             iostart()
6537             while True: # Play a game 
6538                 setwnd(fullscreen_window)
6539                 clrscr()
6540                 prelim()
6541                 setup()
6542                 if game.alldone:
6543                     score()
6544                     game.alldone = False
6545                 else:
6546                     makemoves()
6547                 skip(1)
6548                 stars()
6549                 skip(1)
6550                 if game.tourn and game.alldone:
6551                     proutn(_("Do you want your score recorded?"))
6552                     if ja() == True:
6553                         scanner.chew2()
6554                         freeze(False)
6555                 scanner.chew()
6556                 proutn(_("Do you want to play again? "))
6557                 if not ja():
6558                     break
6559             skip(1)
6560             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6561         finally:
6562             ioend()
6563         raise SystemExit, 0
6564     except KeyboardInterrupt:
6565         if logfp:
6566             logfp.close()
6567         print ""
6568         pass