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