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