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