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