87a057dce4a13dc39506acfcad29cc903ac7dc69
[super-star-trek.git] / src / sst.py
1 #!/usr/bin/env python
2 """
3 sst.py =-- Super Star Trek in Python
4
5 This code is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not.  But it
7 works.
8
9 Dave Matuszek says:
10
11 SRSCAN, MOVE, PHASERS, CALL, STATUS, IMPULSE, PHOTONS, ABANDON,
12 LRSCAN, WARP, SHIELDS, DESTRUCT, CHART, REST, DOCK, QUIT, and DAMAGE
13 were in the original non-"super" version of UT FORTRAN Star Trek.
14
15 Tholians were not in the original. Dave is dubious about their merits.
16 (They are now controlled by OPTION_THOLIAN and turned off if the game
17 type is "plain".)
18
19 Planets and dilithium crystals were not in the original.  Dave is OK
20 with this idea. (It's now controlled by OPTION_PLANETS and turned 
21 off if the game type is "plain".)
22
23 Dave says the bit about the Galileo getting turned into a
24 McDonald's is "consistant with our original vision".  (This has been
25 left permanently enabled, as it can only happen if OPTION_PLANETS
26 is on.)
27
28 Dave also says the Space Thingy should not be preserved across saved
29 games, so you can't prove to others that you've seen it.  He says it
30 shouldn't fire back, either.  It should do nothing except scream and
31 disappear when hit by photon torpedos.  It's OK that it may move
32 when attacked, but it didn't in the original.  (Whether the Thingy
33 can fire back is now controlled by OPTION_THINGY and turned off if the
34 game type is "plain" or "almy".  The no-save behavior has been restored.)
35
36 The Faerie Queen, black holes, and time warping were in the original.
37
38 Here are Tom Almy's changes:
39
40 In early 1997, I got the bright idea to look for references to
41 "Super Star Trek" on the World Wide Web. There weren't many hits,
42 but there was one that came up with 1979 Fortran sources! This
43 version had a few additional features that mine didn't have,
44 however mine had some feature it didn't have. So I merged its
45 features that I liked. I also took a peek at the DECUS version (a
46 port, less sources, to the PDP-10), and some other variations.
47
48 1, Compared to the original UT version, I've changed the "help"
49 command to "call" and the "terminate" command to "quit" to better
50 match user expectations. The DECUS version apparently made those
51 changes as well as changing "freeze" to "save". However I like
52 "freeze".  (Both "freeze" and "save" work in SST2K.)
53
54 2. The experimental deathray originally had only a 5% chance of
55 success, but could be used repeatedly. I guess after a couple
56 years of use, it was less "experimental" because the 1979
57 version had a 70% success rate. However it was prone to breaking
58 after use. I upgraded the deathray, but kept the original set of
59 failure modes (great humor!).  (Now controlled by OPTION_DEATHRAY
60 and turned off if game type is "plain".)
61
62 3. The 1979 version also mentions srscan and lrscan working when
63 docked (using the starbase's scanners), so I made some changes here
64 to do this (and indicating that fact to the player), and then realized
65 the base would have a subspace radio as well -- doing a Chart when docked
66 updates the star chart, and all radio reports will be heard. The Dock
67 command will also give a report if a base is under attack.
68
69 4. Tholian Web from the 1979 version.  (Now controlled by
70 OPTION_THOLIAN and turned off if game type is "plain".)
71
72 5. Enemies can ram the Enterprise. (Now controlled by OPTION_RAMMING
73 and turned off if game type is "plain".)
74
75 6. Regular Klingons and Romulans can move in Expert and Emeritus games. 
76 This code could use improvement. (Now controlled by OPTION_MVBADDY
77 and turned off if game type is "plain".)
78
79 7. The deep-space probe feature from the DECUS version.  (Now controlled
80 by OPTION_PROBE and turned off if game type is "plain").
81
82 8. 'emexit' command from the 1979 version.
83
84 9. Bugfix: Klingon commander movements are no longer reported if long-range 
85 sensors are damaged.
86
87 10. Bugfix: Better base positioning at startup (more spread out).
88 That made sense to add because most people abort games with 
89 bad base placement.
90
91 In June 2002, I fixed two known bugs and a documentation typo.
92 In June 2004 I fixed a number of bugs involving: 1) parsing invalid
93 numbers, 2) manual phasers when SR scan is damaged and commander is
94 present, 3) time warping into the future, 4) hang when moving
95 klingons in crowded quadrants.  (These fixes are in SST2K.)
96
97 Here are Stas Sergeev's changes:
98
99 1. The Space Thingy can be shoved, if you ram it, and can fire back if 
100 fired upon. (Now controlled by OPTION_THINGY and turned off if game 
101 type is "plain" or "almy".)
102
103 2. When you are docked, base covers you with an almost invincible shield. 
104 (A commander can still ram you, or a Romulan can destroy the base,
105 or a SCom can even succeed with direct attack IIRC, but this rarely 
106 happens.)  (Now controlled by OPTION_BASE and turned off if game 
107 type is "plain" or "almy".)
108
109 3. Ramming a black hole is no longer instant death.  There is a
110 chance you might get timewarped instead. (Now controlled by 
111 OPTION_BLKHOLE and turned off if game type is "plain" or "almy".)
112
113 4. The Tholian can be hit with phasers.
114
115 5. SCom can't escape from you if no more enemies remain 
116 (without this, chasing SCom can take an eternity).
117
118 6. Probe target you enter is now the destination quadrant. Before I don't 
119 remember what it was, but it was something I had difficulty using.
120
121 7. Secret password is now autogenerated.
122
123 8. "Plaque" is adjusted for A4 paper :-)
124
125 9. Phasers now tells you how much energy needed, but only if the computer 
126 is alive.
127
128 10. Planets are auto-scanned when you enter the quadrant.
129
130 11. Mining or using crystals in presense of enemy now yields an attack.
131 There are other minor adjustments to what yields an attack
132 and what does not.
133
134 12. "freeze" command reverts to "save", most people will understand this
135 better anyway. (SST2K recognizes both.)
136
137 13. Screen-oriented interface, with sensor scans always up.  (SST2K
138 supports both screen-oriented and TTY modes.)
139
140 Eric Raymond's changes:
141
142 Mainly, I translated this C code out of FORTRAN into C -- created #defines
143 for a lot of magic numbers and refactored the heck out of it.
144
145 1. "sos" and "call" becomes "mayday", "freeze" and "save" are both good.
146
147 2. Status report now indicates when dilithium crystals are on board.
148
149 3. Per Dave Matuszek's remarks, Thingy state is never saved across games.
150
151 4. Added game option selection so you can play a close (but not bug-for-
152 bug identical) approximation of older versions.
153
154 5. Half the quadrants now have inhabited planets, from which one 
155 cannot mine dilithium (there will still be the same additional number
156 of dilithium-bearing planets).  Torpedoing an inhabited world is *bad*.
157 There is BSD-Trek-like logic for Klingons to attack and enslave 
158 inhabited worlds, producing more ships (only is skill is 'good' or 
159 better). (Controlled by OPTION_WORLDS and turned off if game 
160 type is "plain" or "almy".)
161
162 6. User input is now logged so we can do regression testing.
163
164 7. More BSD-Trek features: You can now lose if your entire crew
165 dies in battle.  When abandoning ship in a game with inhabited
166 worlds enabled, they must have one in the quadrant to beam down
167 to; otherwise they die in space and this counts heavily against
168 your score.  Docking at a starbase replenishes your crew.
169
170 8. Still more BSD-Trek: we now have a weighted damage table.
171 Also, the nav subsystem (enabling automatic course
172 setting) can be damaged separately from the main computer (which
173 handles weapons targeting, ETA calculation, and self-destruct).
174
175 After these features were added, I translated this into Python and added
176 more:
177
178 9. A long-range scan is done silently whenever you call CHART; thus
179 the LRSCAN command is no longer needed.  (Controlled by OPTION_AUTOSCAN
180 and turned off if game type is "plain" or "almy".)
181 """
182 import os, sys, math, curses, time, readline, cPickle, random, copy, gettext
183
184 SSTDOC          = "/usr/share/doc/sst/sst.doc"
185 DOC_NAME        = "sst.doc"
186
187 def _(str): return gettext.gettext(str)
188
189 PHASEFAC        = 2.0
190 GALSIZE         = 8
191 NINHAB          = (GALSIZE * GALSIZE / 2)
192 MAXUNINHAB      = 10
193 PLNETMAX        = (NINHAB + MAXUNINHAB)
194 QUADSIZE        = 10
195 BASEMIN         = 2
196 BASEMAX         = (GALSIZE * GALSIZE / 12)
197 MAXKLGAME       = 127
198 MAXKLQUAD       = 9
199 FULLCREW        = 428   # BSD Trek was 387, that's wrong 
200 FOREVER         = 1e30
201 MAXBURST        = 3
202 MINCMDR         = 10
203
204 # How to represent features
205 IHR = 'R',
206 IHK = 'K',
207 IHC = 'C',
208 IHS = 'S',
209 IHSTAR = '*',
210 IHP = 'P',
211 IHW = '@',
212 IHB = 'B',
213 IHBLANK = ' ',
214 IHDOT = '.',
215 IHQUEST = '?',
216 IHE = 'E',
217 IHF = 'F',
218 IHT = 'T',
219 IHWEB = '#',
220 IHMATER0 = '-',
221 IHMATER1 = 'o',
222 IHMATER2 = '0'
223
224 class TrekError:
225     pass
226
227 class coord:
228     def __init__(self, x=None, y=None):
229         self.i = x
230         self.j = y
231     def valid_quadrant(self):
232         return self.i>=0 and self.i<GALSIZE and self.j>=0 and self.j<GALSIZE
233     def valid_sector(self):
234         return self.i>=0 and self.i<QUADSIZE and self.j>=0 and self.j<QUADSIZE
235     def invalidate(self):
236         self.i = self.j = None
237     def is_valid(self):
238         return self.i != None and self.j != None
239     def __eq__(self, other):
240         return other != None and self.i == other.i and self.j == other.j
241     def __ne__(self, other):
242         return other == None or self.i != other.i or self.j != other.j
243     def __add__(self, other):
244         return coord(self.i+other.i, self.j+other.j)
245     def __sub__(self, other):
246         return coord(self.i-other.i, self.j-other.j)
247     def __mul__(self, other):
248         return coord(self.i*other, self.j*other)
249     def __rmul__(self, other):
250         return coord(self.i*other, self.j*other)
251     def __div__(self, other):
252         return coord(self.i/other, self.j/other)
253     def __mod__(self, other):
254         return coord(self.i % other, self.j % other)
255     def __rdiv__(self, other):
256         return coord(self.i/other, self.j/other)
257     def roundtogrid(self):
258         return coord(int(round(self.i)), int(round(self.j)))
259     def distance(self, other=None):
260         if not other: other = coord(0, 0)
261         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
262     def bearing(self):
263         return 1.90985*math.atan2(self.j, self.i)
264     def sgn(self):
265         s = coord()
266         if self.i == 0:
267             s.i = 0
268         else:
269             s.i = self.i / abs(self.i)
270         if self.j == 0:
271             s.j = 0
272         else:
273             s.j = self.j / abs(self.j)
274         return s
275     def quadrant(self):
276         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
277         return self.roundtogrid() / QUADSIZE
278     def sector(self):
279         return self.roundtogrid() % QUADSIZE
280     def scatter(self):
281         s = coord()
282         s.i = self.i + randrange(-1, 2)
283         s.j = self.j + randrange(-1, 2)
284         return s
285     def __str__(self):
286         if self.i == None or self.j == None:
287             return "Nowhere"
288         return "%s - %s" % (self.i+1, self.j+1)
289     __repr__ = __str__
290
291 class planet:
292     def __init__(self):
293         self.name = None        # string-valued if inhabited
294         self.quadrant = coord() # quadrant located
295         self.pclass = None      # could be ""M", "N", "O", or "destroyed"
296         self.crystals = "absent"# could be "mined", "present", "absent"
297         self.known = "unknown"  # could be "unknown", "known", "shuttle_down"
298         self.inhabited = False  # is it inhabites?
299     def __str__(self):
300         return self.name
301
302 class quadrant:
303     def __init__(self):
304         self.stars = 0
305         self.planet = None
306         self.starbase = False
307         self.klingons = 0
308         self.romulans = 0
309         self.supernova = False
310         self.charted = False
311         self.status = "secure"  # Could be "secure", "distressed", "enslaved"
312
313 class page:
314     def __init__(self):
315         self.stars = None
316         self.starbase = None
317         self.klingons = None
318
319 def fill2d(size, fillfun):
320     "Fill an empty list in 2D."
321     lst = []
322     for i in range(size):
323         lst.append([]) 
324         for j in range(size):
325             lst[i].append(fillfun(i, j))
326     return lst
327
328 class snapshot:
329     def __init__(self):
330         self.snap = False       # snapshot taken
331         self.crew = 0           # crew complement
332         self.remkl = 0          # remaining klingons
333         self.nscrem = 0         # remaining super commanders
334         self.starkl = 0         # destroyed stars
335         self.basekl = 0         # destroyed bases
336         self.nromrem = 0        # Romulans remaining
337         self.nplankl = 0        # destroyed uninhabited planets
338         self.nworldkl = 0       # destroyed inhabited planets
339         self.planets = []       # Planet information
340         self.date = 0.0         # stardate
341         self.remres = 0         # remaining resources
342         self.remtime = 0        # remaining time
343         self.baseq = []         # Base quadrant coordinates
344         self.kcmdr = []         # Commander quadrant coordinates
345         self.kscmdr = coord()   # Supercommander quadrant coordinates
346         # the galaxy
347         self.galaxy = fill2d(GALSIZE, lambda i, j: quadrant())
348         # the starchart
349         self.chart = fill2d(GALSIZE, lambda i, j: page())
350
351 class event:
352     def __init__(self):
353         self.date = None        # A real number
354         self.quadrant = None    # A coord structure
355
356 # game options 
357 OPTION_ALL      = 0xffffffff
358 OPTION_TTY      = 0x00000001    # old interface 
359 OPTION_CURSES   = 0x00000002    # new interface 
360 OPTION_IOMODES  = 0x00000003    # cover both interfaces 
361 OPTION_PLANETS  = 0x00000004    # planets and mining 
362 OPTION_THOLIAN  = 0x00000008    # Tholians and their webs (UT 1979 version)
363 OPTION_THINGY   = 0x00000010    # Space Thingy can shoot back (Stas, 2005)
364 OPTION_PROBE    = 0x00000020    # deep-space probes (DECUS version, 1980)
365 OPTION_SHOWME   = 0x00000040    # bracket Enterprise in chart 
366 OPTION_RAMMING  = 0x00000080    # enemies may ram Enterprise (Almy)
367 OPTION_MVBADDY  = 0x00000100    # more enemies can move (Almy)
368 OPTION_BLKHOLE  = 0x00000200    # black hole may timewarp you (Stas, 2005) 
369 OPTION_BASE     = 0x00000400    # bases have good shields (Stas, 2005)
370 OPTION_WORLDS   = 0x00000800    # logic for inhabited worlds (ESR, 2006)
371 OPTION_AUTOSCAN = 0x00001000    # automatic LRSCAN before CHART (ESR, 2006)
372 OPTION_PLAIN    = 0x01000000    # user chose plain game 
373 OPTION_ALMY     = 0x02000000    # user chose Almy variant 
374
375 # Define devices 
376 DSRSENS = 0
377 DLRSENS = 1
378 DPHASER = 2
379 DPHOTON = 3
380 DLIFSUP = 4
381 DWARPEN = 5
382 DIMPULS = 6
383 DSHIELD = 7
384 DRADIO  = 0
385 DSHUTTL = 9
386 DCOMPTR = 10
387 DNAVSYS = 11
388 DTRANSP = 12
389 DSHCTRL = 13
390 DDRAY   = 14
391 DDSP    = 15
392 NDEVICES= 16    # Number of devices
393
394 SKILL_NONE      = 0
395 SKILL_NOVICE    = 1
396 SKILL_FAIR      = 2
397 SKILL_GOOD      = 3
398 SKILL_EXPERT    = 4
399 SKILL_EMERITUS  = 5
400
401 def damaged(dev):       return (game.damage[dev] != 0.0)
402 def communicating():    return not damaged(DRADIO) or game.condition=="docked"
403
404 # Define future events 
405 FSPY    = 0     # Spy event happens always (no future[] entry)
406                 # can cause SC to tractor beam Enterprise
407 FSNOVA  = 1     # Supernova
408 FTBEAM  = 2     # Commander tractor beams Enterprise
409 FSNAP   = 3     # Snapshot for time warp
410 FBATTAK = 4     # Commander attacks base
411 FCDBAS  = 5     # Commander destroys base
412 FSCMOVE = 6     # Supercommander moves (might attack base)
413 FSCDBAS = 7     # Supercommander destroys base
414 FDSPROB = 8     # Move deep space probe
415 FDISTR  = 9     # Emit distress call from an inhabited world 
416 FENSLV  = 10    # Inhabited word is enslaved */
417 FREPRO  = 11    # Klingons build a ship in an enslaved system
418 NEVENTS = 12
419
420 #
421 # abstract out the event handling -- underlying data structures will change
422 # when we implement stateful events
423
424 def findevent(evtype):  return game.future[evtype]
425
426 class enemy:
427     def __init__(self, type=None, loc=None, power=None):
428         self.type = type
429         self.kloc = coord()
430         if loc:
431             self.move(loc)
432         self.kpower = power     # enemy energy level
433         game.enemies.append(self)
434     def move(self, loc):
435         motion = (loc != self.kloc)
436         if self.kloc.i is not None and self.kloc.j is not None:
437             if motion:
438                 if self.type == IHT:
439                     game.quad[self.kloc.i][self.kloc.j] = IHWEB
440                 else:
441                     game.quad[self.kloc.i][self.kloc.j] = IHDOT
442         if loc:
443             self.kloc = copy.copy(loc)
444             game.quad[self.kloc.i][self.kloc.j] = self.type
445             self.kdist = self.kavgd = (game.sector - loc).distance()
446         else:
447             self.kloc = coord()
448             self.kdist = self.kavgd = None
449             game.enemies.remove(self)
450         return motion
451     def __repr__(self):
452         return "<%s,%s.%f>" % (self.type, self.kloc, self.kpower)       # For debugging
453
454 class gamestate:
455     def __init__(self):
456         self.options = None     # Game options
457         self.state = snapshot() # A snapshot structure
458         self.snapsht = snapshot()       # Last snapshot taken for time-travel purposes
459         self.quad = None        # contents of our quadrant
460         self.damage = [0.0] * NDEVICES  # damage encountered
461         self.future = []                # future events
462         for i in range(NEVENTS):
463             self.future.append(event())
464         self.passwd  = None;            # Self Destruct password
465         self.enemies = []
466         self.quadrant = None    # where we are in the large
467         self.sector = None      # where we are in the small
468         self.tholian = None     # Tholian enemy object
469         self.base = None        # position of base in current quadrant
470         self.battle = None      # base coordinates being attacked
471         self.plnet = None       # location of planet in quadrant
472         self.gamewon = False    # Finished!
473         self.ididit = False     # action taken -- allows enemy to attack
474         self.alive = False      # we are alive (not killed)
475         self.justin = False     # just entered quadrant
476         self.shldup = False     # shields are up
477         self.shldchg = False    # shield is changing (affects efficiency)
478         self.iscate = False     # super commander is here
479         self.ientesc = False    # attempted escape from supercommander
480         self.resting = False    # rest time
481         self.icraft = False     # Kirk in Galileo
482         self.landed = False     # party on planet (true), on ship (false)
483         self.alldone = False    # game is now finished
484         self.neutz = False      # Romulan Neutral Zone
485         self.isarmed = False    # probe is armed
486         self.inorbit = False    # orbiting a planet
487         self.imine = False      # mining
488         self.icrystl = False    # dilithium crystals aboard
489         self.iseenit = False    # seen base attack report
490         self.thawed = False     # thawed game
491         self.condition = None   # "green", "yellow", "red", "docked", "dead"
492         self.iscraft = None     # "onship", "offship", "removed"
493         self.skill = None       # Player skill level
494         self.inkling = 0        # initial number of klingons
495         self.inbase = 0         # initial number of bases
496         self.incom = 0          # initial number of commanders
497         self.inscom = 0         # initial number of commanders
498         self.inrom = 0          # initial number of commanders
499         self.instar = 0         # initial stars
500         self.intorps = 0        # initial/max torpedoes
501         self.torps = 0          # number of torpedoes
502         self.ship = 0           # ship type -- 'E' is Enterprise
503         self.abandoned = 0      # count of crew abandoned in space
504         self.length = 0         # length of game
505         self.klhere = 0         # klingons here
506         self.casual = 0         # causalties
507         self.nhelp = 0          # calls for help
508         self.nkinks = 0         # count of energy-barrier crossings
509         self.iplnet = None      # planet # in quadrant
510         self.inplan = 0         # initial planets
511         self.irhere = 0         # Romulans in quadrant
512         self.isatb = 0          # =1 if super commander is attacking base
513         self.tourn = None       # tournament number
514         self.nprobes = 0        # number of probes available
515         self.inresor = 0.0      # initial resources
516         self.intime = 0.0       # initial time
517         self.inenrg = 0.0       # initial/max energy
518         self.inshld = 0.0       # initial/max shield
519         self.inlsr = 0.0        # initial life support resources
520         self.indate = 0.0       # initial date
521         self.energy = 0.0       # energy level
522         self.shield = 0.0       # shield level
523         self.warpfac = 0.0      # warp speed
524         self.wfacsq = 0.0       # squared warp factor
525         self.lsupres = 0.0      # life support reserves
526         self.optime = 0.0       # time taken by current operation
527         self.docfac = 0.0       # repair factor when docking (constant?)
528         self.damfac = 0.0       # damage factor
529         self.lastchart = 0.0    # time star chart was last updated
530         self.cryprob = 0.0      # probability that crystal will work
531         self.probe = None       # object holding probe course info
532         self.height = 0.0       # height of orbit around planet
533     def recompute(self):
534         # Stas thinks this should be (C expression): 
535         # game.state.remkl + len(game.state.kcmdr) > 0 ?
536         #       game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
537         # He says the existing expression is prone to divide-by-zero errors
538         # after killing the last klingon when score is shown -- perhaps also
539         # if the only remaining klingon is SCOM.
540         game.state.remtime = game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr))
541
542 IHR = 'R'
543 IHK = 'K'
544 IHC = 'C'
545 IHS = 'S'
546 IHSTAR = '*'
547 IHP = 'P'
548 IHW = '@'
549 IHB = 'B'
550 IHBLANK = ' '
551 IHDOT = '.'
552 IHQUEST = '?'
553 IHE = 'E'
554 IHF = 'F'
555 IHT = 'T'
556 IHWEB = '#'
557 IHMATER0 = '-'
558 IHMATER1 = 'o'
559 IHMATER2 = '0'
560
561 FWON = 0
562 FDEPLETE = 1
563 FLIFESUP = 2
564 FNRG = 3
565 FBATTLE = 4
566 FNEG3 = 5
567 FNOVA = 6
568 FSNOVAED = 7
569 FABANDN = 8
570 FDILITHIUM = 9
571 FMATERIALIZE = 10
572 FPHASER = 11
573 FLOST = 12
574 FMINING = 13
575 FDPLANET = 14
576 FPNOVA = 15
577 FSSC = 16
578 FSTRACTOR = 17
579 FDRAY = 18
580 FTRIBBLE = 19
581 FHOLE = 20
582 FCREW = 21
583
584 def withprob(p):
585     v = random.random()
586     #logfp.write("# withprob(%s) -> %f (%s) at %s\n" % (p, v, v<p, traceback.extract_stack()[-2][1:]))
587     return v < p
588
589 def randrange(*args):
590     v = random.randrange(*args)
591     #logfp.write("# randrange%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
592     return v
593
594 def randreal(*args):
595     v = random.random()
596     if len(args) == 1:
597         v *= args[0]            # returns from [0, args[0])
598     elif len(args) == 2:
599         v = args[0] + v*(args[1]-args[0])       # returns from [args[0], args[1])
600     #logfp.write("# randreal%s -> %s at %s\n" % (args, v, traceback.extract_stack()[-2][1:]))
601     return v
602
603 # Code from ai.c begins here
604
605 def welcoming(iq):
606     "Would this quadrant welcome another Klingon?"
607     return iq.valid_quadrant() and \
608         not game.state.galaxy[iq.i][iq.j].supernova and \
609         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
610
611 def tryexit(enemy, look, irun):
612     "A bad guy attempts to bug out."
613     iq = coord()
614     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
615     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
616     if not welcoming(iq):
617         return False;
618     if enemy.type == IHR:
619         return False; # Romulans cannot escape! 
620     if not irun:
621         # avoid intruding on another commander's territory 
622         if enemy.type == IHC:
623             if iq in game.state.kcmdr:
624                 return False
625             # refuse to leave if currently attacking starbase 
626             if game.battle == game.quadrant:
627                 return False
628         # don't leave if over 1000 units of energy 
629         if enemy.kpower > 1000.0:
630             return False
631     # emit escape message and move out of quadrant.
632     # we know this if either short or long range sensors are working
633     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
634         game.condition == "docked":
635         prout(crmena(True, enemy.type, "sector", enemy.kloc) + \
636               (_(" escapes to Quadrant %s (and regains strength).") % q))
637     # handle local matters related to escape
638     enemy.move(None)
639     game.klhere -= 1
640     if game.condition != "docked":
641         newcnd()
642     # Handle global matters related to escape 
643     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
644     game.state.galaxy[iq.i][iq.j].klingons += 1
645     if enemy.type==IHS:
646         game.iscate = False
647         game.ientesc = False
648         game.isatb = 0
649         schedule(FSCMOVE, 0.2777)
650         unschedule(FSCDBAS)
651         game.state.kscmdr=iq
652     else:
653         for cmdr in game.state.kcmdr:
654             if cmdr == game.quadrant:
655                 game.state.kcmdr[n] = iq
656                 break
657     return True; # success 
658
659 # The bad-guy movement algorithm:
660
661 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
662 # If both are operating full strength, force is 1000. If both are damaged,
663 # force is -1000. Having shields down subtracts an additional 1000.
664
665 # 2. Enemy has forces equal to the energy of the attacker plus
666 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
667 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
668
669 # Attacker Initial energy levels (nominal):
670 # Klingon   Romulan   Commander   Super-Commander
671 # Novice    400        700        1200        
672 # Fair      425        750        1250
673 # Good      450        800        1300        1750
674 # Expert    475        850        1350        1875
675 # Emeritus  500        900        1400        2000
676 # VARIANCE   75        200         200         200
677
678 # Enemy vessels only move prior to their attack. In Novice - Good games
679 # only commanders move. In Expert games, all enemy vessels move if there
680 # is a commander present. In Emeritus games all enemy vessels move.
681
682 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
683 # forces are 1000 greater than Enterprise.
684
685 # Agressive action on average cuts the distance between the ship and
686 # the enemy to 1/4 the original.
687
688 # 4.  At lower energy advantage, movement units are proportional to the
689 # advantage with a 650 advantage being to hold ground, 800 to move forward
690 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
691
692 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
693 # retreat, especially at high skill levels.
694
695 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
696
697 def movebaddy(enemy):
698     "Tactical movement for the bad guys."
699     next = coord(); look = coord()
700     irun = False
701     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant) 
702     if game.skill >= SKILL_EXPERT:
703         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
704     else:
705         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
706     dist1 = enemy.kdist
707     mdist = int(dist1 + 0.5); # Nearest integer distance 
708     # If SC, check with spy to see if should hi-tail it 
709     if enemy.type==IHS and \
710         (enemy.kpower <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
711         irun = True
712         motion = -QUADSIZE
713     else:
714         # decide whether to advance, retreat, or hold position 
715         forces = enemy.kpower+100.0*len(game.enemies)+400*(nbaddys-1)
716         if not game.shldup:
717             forces += 1000; # Good for enemy if shield is down! 
718         if not damaged(DPHASER) or not damaged(DPHOTON):
719             if damaged(DPHASER): # phasers damaged 
720                 forces += 300.0
721             else:
722                 forces -= 0.2*(game.energy - 2500.0)
723             if damaged(DPHOTON): # photon torpedoes damaged 
724                 forces += 300.0
725             else:
726                 forces -= 50.0*game.torps
727         else:
728             # phasers and photon tubes both out! 
729             forces += 1000.0
730         motion = 0
731         if forces <= 1000.0 and game.condition != "docked": # Typical situation 
732             motion = ((forces + randreal(200))/150.0) - 5.0
733         else:
734             if forces > 1000.0: # Very strong -- move in for kill 
735                 motion = (1.0 - randreal())**2 * dist1 + 1.0
736             if game.condition=="docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
737                 motion -= game.skill*(2.0-randreal()**2)
738         if idebug:
739             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
740         # don't move if no motion 
741         if motion==0:
742             return
743         # Limit motion according to skill 
744         if abs(motion) > game.skill:
745             if motion < 0:
746                 motion = -game.skill
747             else:
748                 motion = game.skill
749     # calculate preferred number of steps 
750     nsteps = abs(int(motion))
751     if motion > 0 and nsteps > mdist:
752         nsteps = mdist; # don't overshoot 
753     if nsteps > QUADSIZE:
754         nsteps = QUADSIZE; # This shouldn't be necessary 
755     if nsteps < 1:
756         nsteps = 1; # This shouldn't be necessary 
757     if idebug:
758         proutn("NSTEPS = %d:" % nsteps)
759     # Compute preferred values of delta X and Y 
760     m = game.sector - enemy.kloc
761     if 2.0 * abs(m.i) < abs(m.j):
762         m.i = 0
763     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.kloc.i):
764         m.j = 0
765     m = (motion * m).sgn()
766     next = enemy.kloc
767     # main move loop 
768     for ll in range(nsteps):
769         if idebug:
770             proutn(" %d" % (ll+1))
771         # Check if preferred position available 
772         look = next + m
773         if m.i < 0:
774             krawli = 1
775         else:
776             krawli = -1
777         if m.j < 0:
778             krawlj = 1
779         else:
780             krawlj = -1
781         success = False
782         attempts = 0; # Settle mysterious hang problem 
783         while attempts < 20 and not success:
784             attempts += 1
785             if look.i < 0 or look.i >= QUADSIZE:
786                 if motion < 0 and tryexit(enemy, look, irun):
787                     return
788                 if krawli == m.i or m.j == 0:
789                     break
790                 look.i = next.i + krawli
791                 krawli = -krawli
792             elif look.j < 0 or look.j >= QUADSIZE:
793                 if motion < 0 and tryexit(enemy, look, irun):
794                     return
795                 if krawlj == m.j or m.i == 0:
796                     break
797                 look.j = next.j + krawlj
798                 krawlj = -krawlj
799             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != IHDOT:
800                 # See if enemy should ram ship 
801                 if game.quad[look.i][look.j] == game.ship and \
802                     (enemy.type == IHC or enemy.type == IHS):
803                     collision(rammed=True, enemy=enemy)
804                     return
805                 if krawli != m.i and m.j != 0:
806                     look.i = next.i + krawli
807                     krawli = -krawli
808                 elif krawlj != m.j and m.i != 0:
809                     look.j = next.j + krawlj
810                     krawlj = -krawlj
811                 else:
812                     break; # we have failed 
813             else:
814                 success = True
815         if success:
816             next = look
817             if idebug:
818                 proutn(`next`)
819         else:
820             break; # done early 
821     if idebug:
822         skip(1)
823     if enemy.move(next):
824         if not damaged(DSRSENS) or game.condition == "docked":
825             proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.kloc))
826             if enemy.kdist < dist1:
827                 proutn(_(" advances to "))
828             else:
829                 proutn(_(" retreats to "))
830             prout("Sector %s." % next)
831
832 def moveklings():
833     "Sequence Klingon tactical movement."
834     if idebug:
835         prout("== MOVCOM")
836     # Figure out which Klingon is the commander (or Supercommander)
837     # and do move
838     if game.quadrant in game.state.kcmdr:
839         for enemy in game.enemies:
840             if enemy.type == IHC:
841                 movebaddy(enemy)
842     if game.state.kscmdr==game.quadrant:
843         for enemy in game.enemies:
844             if enemy.type == IHS:
845                 movebaddy(enemy)
846                 break
847     # If skill level is high, move other Klingons and Romulans too!
848     # Move these last so they can base their actions on what the
849     # commander(s) do.
850     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
851         for enemy in game.enemies:
852             if enemy.type in (IHK, IHR):
853                 movebaddy(enemy)
854     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
855
856 def movescom(iq, avoid):
857     "Commander movement helper." 
858     # Avoid quadrants with bases if we want to avoid Enterprise 
859     if not welcoming(iq) or (avoid and iq in game.state.baseq):
860         return False
861     if game.justin and not game.iscate:
862         return False
863     # do the move 
864     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
865     game.state.kscmdr = iq
866     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
867     if game.state.kscmdr==game.quadrant:
868         # SC has scooted, Remove him from current quadrant 
869         game.iscate=False
870         game.isatb=0
871         game.ientesc = False
872         unschedule(FSCDBAS)
873         for enemy in game.enemies:
874             if enemy.type == IHS:
875                 break
876         enemy.move(None)
877         game.klhere -= 1
878         if game.condition != "docked":
879             newcnd()
880         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
881     # check for a helpful planet 
882     for i in range(game.inplan):
883         if game.state.planets[i].quadrant == game.state.kscmdr and \
884             game.state.planets[i].crystals == "present":
885             # destroy the planet 
886             game.state.planets[i].pclass = "destroyed"
887             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
888             if communicating():
889                 announce()
890                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
891                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
892                 prout(_("   by the Super-commander.\""))
893             break
894     return True; # looks good! 
895                         
896 def supercommander():
897     "Move the Super Commander." 
898     iq = coord(); sc = coord(); ibq = coord(); idelta = coord()
899     basetbl = []
900     if idebug:
901         prout("== SUPERCOMMANDER")
902     # Decide on being active or passive 
903     avoid = ((game.incom - len(game.state.kcmdr) + game.inkling - game.state.remkl)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
904             (game.state.date-game.indate) < 3.0)
905     if not game.iscate and avoid:
906         # compute move away from Enterprise 
907         idelta = game.state.kscmdr-game.quadrant
908         if idelta.distance() > 2.0:
909             # circulate in space 
910             idelta.i = game.state.kscmdr.j-game.quadrant.j
911             idelta.j = game.quadrant.i-game.state.kscmdr.i
912     else:
913         # compute distances to starbases 
914         if not game.state.baseq:
915             # nothing left to do 
916             unschedule(FSCMOVE)
917             return
918         sc = game.state.kscmdr
919         for base in game.state.baseq:
920             basetbl.append((i, (base - sc).distance()))
921         if game.state.baseq > 1:
922             basetbl.sort(lambda x, y: cmp(x[1]. y[1]))
923         # look for nearest base without a commander, no Enterprise, and
924         # without too many Klingons, and not already under attack. 
925         ifindit = iwhichb = 0
926         for (i2, base) in enumerate(game.state.baseq):
927             i = basetbl[i2][0]; # bug in original had it not finding nearest
928             if base==game.quadrant or base==game.battle or not welcoming(base):
929                 continue
930             # if there is a commander, and no other base is appropriate,
931             # we will take the one with the commander
932             for cmdr in game.state.kcmdr:
933                 if base == cmdr and ifindit != 2:
934                     ifindit = 2
935                     iwhichb = i
936                     break
937             else:       # no commander -- use this one 
938                 ifindit = 1
939                 iwhichb = i
940                 break
941         if ifindit==0:
942             return # Nothing suitable -- wait until next time
943         ibq = game.state.baseq[iwhichb]
944         # decide how to move toward base 
945         idelta = ibq - game.state.kscmdr
946     # Maximum movement is 1 quadrant in either or both axes 
947     idelta = idelta.sgn()
948     # try moving in both x and y directions
949     # there was what looked like a bug in the Almy C code here,
950     # but it might be this translation is just wrong.
951     iq = game.state.kscmdr + idelta
952     if not movescom(iq, avoid):
953         # failed -- try some other maneuvers 
954         if idelta.i==0 or idelta.j==0:
955             # attempt angle move 
956             if idelta.i != 0:
957                 iq.j = game.state.kscmdr.j + 1
958                 if not movescom(iq, avoid):
959                     iq.j = game.state.kscmdr.j - 1
960                     movescom(iq, avoid)
961             elif idelta.j != 0:
962                 iq.i = game.state.kscmdr.i + 1
963                 if not movescom(iq, avoid):
964                     iq.i = game.state.kscmdr.i - 1
965                     movescom(iq, avoid)
966         else:
967             # try moving just in x or y 
968             iq.j = game.state.kscmdr.j
969             if not movescom(iq, avoid):
970                 iq.j = game.state.kscmdr.j + idelta.j
971                 iq.i = game.state.kscmdr.i
972                 movescom(iq, avoid)
973     # check for a base 
974     if len(game.state.baseq) == 0:
975         unschedule(FSCMOVE)
976     else:
977         for ibq in game.state.baseq:
978             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
979                 # attack the base 
980                 if avoid:
981                     return # no, don't attack base! 
982                 game.iseenit = False
983                 game.isatb = 1
984                 schedule(FSCDBAS, randreal(1.0, 3.0))
985                 if is_scheduled(FCDBAS):
986                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
987                 if not communicating():
988                     return # no warning 
989                 game.iseenit = True
990                 announce()
991                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
992                       % game.state.kscmdr)
993                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
994                 proutn(_("   It can survive until stardate %d.\"") \
995                        % int(scheduled(FSCDBAS)))
996                 if not game.resting:
997                     return
998                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
999                 if ja() == False:
1000                     return
1001                 game.resting = False
1002                 game.optime = 0.0; # actually finished 
1003                 return
1004     # Check for intelligence report 
1005     if not idebug and \
1006         (withprob(0.8) or \
1007          (not communicating()) or \
1008          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
1009         return
1010     announce()
1011     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
1012     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
1013     return
1014
1015 def movetholian():
1016     "Move the Tholian."
1017     if not game.tholian or game.justin:
1018         return
1019     id = coord()
1020     if game.tholian.kloc.i == 0 and game.tholian.kloc.j == 0:
1021         id.i = 0; id.j = QUADSIZE-1
1022     elif game.tholian.kloc.i == 0 and game.tholian.kloc.j == QUADSIZE-1:
1023         id.i = QUADSIZE-1; id.j = QUADSIZE-1
1024     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == QUADSIZE-1:
1025         id.i = QUADSIZE-1; id.j = 0
1026     elif game.tholian.kloc.i == QUADSIZE-1 and game.tholian.kloc.j == 0:
1027         id.i = 0; id.j = 0
1028     else:
1029         # something is wrong! 
1030         game.tholian.move(None)
1031         prout("***Internal error: Tholian in a bad spot.")
1032         return
1033     # do nothing if we are blocked 
1034     if game.quad[id.i][id.j] not in (IHDOT, IHWEB):
1035         return
1036     here = copy.copy(game.tholian.kloc)
1037     delta = (id - game.tholian.kloc).sgn()
1038     # move in x axis 
1039     while here.i != id.i:
1040         here.i += delta.i
1041         if game.quad[here.i][here.j]==IHDOT:
1042             game.tholian.move(here)
1043     # move in y axis 
1044     while here.j != id.j:
1045         here.j += delta.j
1046         if game.quad[here.i][here.j]==IHDOT:
1047             game.tholian.move(here)
1048     # check to see if all holes plugged 
1049     for i in range(QUADSIZE):
1050         if game.quad[0][i]!=IHWEB and game.quad[0][i]!=IHT:
1051             return
1052         if game.quad[QUADSIZE-1][i]!=IHWEB and game.quad[QUADSIZE-1][i]!=IHT:
1053             return
1054         if game.quad[i][0]!=IHWEB and game.quad[i][0]!=IHT:
1055             return
1056         if game.quad[i][QUADSIZE-1]!=IHWEB and game.quad[i][QUADSIZE-1]!=IHT:
1057             return
1058     # All plugged up -- Tholian splits 
1059     game.quad[game.tholian.kloc.i][game.tholian.kloc.j]=IHWEB
1060     dropin(IHBLANK)
1061     prout(crmena(True, IHT, "sector", game.tholian) + _(" completes web."))
1062     game.tholian.move(None)
1063     return
1064
1065 # Code from battle.c begins here
1066
1067 def doshield(shraise):
1068     "Change shield status."
1069     action = "NONE"
1070     game.ididit = False
1071     if shraise:
1072         action = "SHUP"
1073     else:
1074         key = scanner.next()
1075         if key == "IHALPHA":
1076             if scanner.sees("transfer"):
1077                 action = "NRG"
1078             else:
1079                 if damaged(DSHIELD):
1080                     prout(_("Shields damaged and down."))
1081                     return
1082                 if scanner.sees("up"):
1083                     action = "SHUP"
1084                 elif scanner.sees("down"):
1085                     action = "SHDN"
1086         if action=="NONE":
1087             proutn(_("Do you wish to change shield energy? "))
1088             if ja() == True:
1089                 proutn(_("Energy to transfer to shields- "))
1090                 action = "NRG"
1091             elif damaged(DSHIELD):
1092                 prout(_("Shields damaged and down."))
1093                 return
1094             elif game.shldup:
1095                 proutn(_("Shields are up. Do you want them down? "))
1096                 if ja() == True:
1097                     action = "SHDN"
1098                 else:
1099                     scanner.chew()
1100                     return
1101             else:
1102                 proutn(_("Shields are down. Do you want them up? "))
1103                 if ja() == True:
1104                     action = "SHUP"
1105                 else:
1106                     scanner.chew()
1107                     return    
1108     if action == "SHUP": # raise shields 
1109         if game.shldup:
1110             prout(_("Shields already up."))
1111             return
1112         game.shldup = True
1113         game.shldchg = True
1114         if game.condition != "docked":
1115             game.energy -= 50.0
1116         prout(_("Shields raised."))
1117         if game.energy <= 0:
1118             skip(1)
1119             prout(_("Shields raising uses up last of energy."))
1120             finish(FNRG)
1121             return
1122         game.ididit=True
1123         return
1124     elif action == "SHDN":
1125         if not game.shldup:
1126             prout(_("Shields already down."))
1127             return
1128         game.shldup=False
1129         game.shldchg=True
1130         prout(_("Shields lowered."))
1131         game.ididit = True
1132         return
1133     elif action == "NRG":
1134         while scanner.next() != "IHREAL":
1135             scanner.chew()
1136             proutn(_("Energy to transfer to shields- "))
1137         scanner.chew()
1138         if scanner.real == 0:
1139             return
1140         if scanner.real > game.energy:
1141             prout(_("Insufficient ship energy."))
1142             return
1143         game.ididit = True
1144         if game.shield+scanner.real >= game.inshld:
1145             prout(_("Shield energy maximized."))
1146             if game.shield+scanner.real > game.inshld:
1147                 prout(_("Excess energy requested returned to ship energy"))
1148             game.energy -= game.inshld-game.shield
1149             game.shield = game.inshld
1150             return
1151         if scanner.real < 0.0 and game.energy-scanner.real > game.inenrg:
1152             # Prevent shield drain loophole 
1153             skip(1)
1154             prout(_("Engineering to bridge--"))
1155             prout(_("  Scott here. Power circuit problem, Captain."))
1156             prout(_("  I can't drain the shields."))
1157             game.ididit = False
1158             return
1159         if game.shield+scanner.real < 0:
1160             prout(_("All shield energy transferred to ship."))
1161             game.energy += game.shield
1162             game.shield = 0.0
1163             return
1164         proutn(_("Scotty- \""))
1165         if scanner.real > 0:
1166             prout(_("Transferring energy to shields.\""))
1167         else:
1168             prout(_("Draining energy from shields.\""))
1169         game.shield += scanner.real
1170         game.energy -= scanner.real
1171         return
1172
1173 def randdevice():
1174     "Choose a device to damage, at random."
1175     # Quoth Eric Allman in the code of BSD-Trek:
1176     # "Under certain conditions you can get a critical hit.  This
1177     # sort of hit damages devices.  The probability that a given
1178     # device is damaged depends on the device.  Well protected
1179     # devices (such as the computer, which is in the core of the
1180     # ship and has considerable redundancy) almost never get
1181     # damaged, whereas devices which are exposed (such as the
1182     # warp engines) or which are particularly delicate (such as
1183     # the transporter) have a much higher probability of being
1184     # damaged."
1185     # 
1186     # This is one place where OPTION_PLAIN does not restore the
1187     # original behavior, which was equiprobable damage across
1188     # all devices.  If we wanted that, we'd return randrange(NDEVICES)
1189     # and have done with it.  Also, in the original game, DNAVYS
1190     # and DCOMPTR were the same device. 
1191     # 
1192     # Instead, we use a table of weights similar to the one from BSD Trek.
1193     # BSD doesn't have the shuttle, shield controller, death ray, or probes. 
1194     # We don't have a cloaking device.  The shuttle got the allocation
1195     # for the cloaking device, then we shaved a half-percent off
1196     # everything to have some weight to give DSHCTRL/DDRAY/DDSP.
1197     weights = (
1198         105,    # DSRSENS: short range scanners 10.5% 
1199         105,    # DLRSENS: long range scanners          10.5% 
1200         120,    # DPHASER: phasers                      12.0% 
1201         120,    # DPHOTON: photon torpedoes             12.0% 
1202         25,     # DLIFSUP: life support          2.5% 
1203         65,     # DWARPEN: warp drive                    6.5% 
1204         70,     # DIMPULS: impulse engines               6.5% 
1205         145,    # DSHIELD: deflector shields            14.5% 
1206         30,     # DRADIO:  subspace radio                3.0% 
1207         45,     # DSHUTTL: shuttle                       4.5% 
1208         15,     # DCOMPTR: computer                      1.5% 
1209         20,     # NAVCOMP: navigation system             2.0% 
1210         75,     # DTRANSP: transporter                   7.5% 
1211         20,     # DSHCTRL: high-speed shield controller 2.0% 
1212         10,     # DDRAY: death ray                       1.0% 
1213         30,     # DDSP: deep-space probes                3.0% 
1214     )
1215     idx = randrange(1000)       # weights must sum to 1000 
1216     sum = 0
1217     for (i, w) in enumerate(weights):
1218         sum += w
1219         if idx < sum:
1220             return i
1221     return None;        # we should never get here
1222
1223 def collision(rammed, enemy):
1224     "Collision handling fot rammong events."
1225     prouts(_("***RED ALERT!  RED ALERT!"))
1226     skip(1)
1227     prout(_("***COLLISION IMMINENT."))
1228     skip(2)
1229     proutn("***")
1230     proutn(crmshp())
1231     hardness = {IHR:1.5, IHC:2.0, IHS:2.5, IHT:0.5, IHQUEST:4.0}.get(enemy.type, 1.0)
1232     if rammed:
1233         proutn(_(" rammed by "))
1234     else:
1235         proutn(_(" rams "))
1236     proutn(crmena(False, enemy.type, "sector", enemy.kloc))
1237     if rammed:
1238         proutn(_(" (original position)"))
1239     skip(1)
1240     deadkl(enemy.kloc, enemy.type, game.sector)
1241     proutn("***" + crmship() + " heavily damaged.")
1242     icas = randrange(10, 30)
1243     prout(_("***Sickbay reports %d casualties"), icas)
1244     game.casual += icas
1245     game.state.crew -= icas
1246     # In the pre-SST2K version, all devices got equiprobably damaged,
1247     # which was silly.  Instead, pick up to half the devices at
1248     # random according to our weighting table,
1249     ncrits = randrange(NDEVICES/2)
1250     for m in range(ncrits):
1251         dev = randdevice()
1252         if game.damage[dev] < 0:
1253             continue
1254         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1255         # Damage for at least time of travel! 
1256         game.damage[dev] += game.optime + extradm
1257     game.shldup = False
1258     prout(_("***Shields are down."))
1259     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1260         announce()
1261         damagereport()
1262     else:
1263         finish(FWON)
1264     return
1265
1266 def torpedo(origin, bearing, dispersion, number, nburst):
1267     "Let a photon torpedo fly" 
1268     if not damaged(DSRSENS) or game.condition=="docked":
1269         setwnd(srscan_window)
1270     else: 
1271         setwnd(message_window)
1272     ac = bearing + 0.25*dispersion      # dispersion is a random variable
1273     bullseye = (15.0 - bearing)*0.5235988
1274     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin)) 
1275     bumpto = coord(0, 0)
1276     # Loop to move a single torpedo 
1277     setwnd(message_window)
1278     for step in range(1, QUADSIZE*2):
1279         if not track.next(): break
1280         w = track.sector()
1281         if not w.valid_sector():
1282             break
1283         iquad=game.quad[w.i][w.j]
1284         tracktorpedo(origin, w, step, number, nburst, iquad)
1285         if iquad==IHDOT:
1286             continue
1287         # hit something 
1288         if not damaged(DSRSENS) or game.condition == "docked":
1289             skip(1);    # start new line after text track 
1290         if iquad in (IHE, IHF): # Hit our ship 
1291             skip(1)
1292             prout(_("Torpedo hits %s.") % crmshp())
1293             hit = 700.0 + randreal(100) - \
1294                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1295             newcnd(); # we're blown out of dock 
1296             if game.landed or game.condition=="docked":
1297                 return hit # Cheat if on a planet 
1298             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 not game.probe.next():
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):
3931         "Next step on course."
3932         self.step += 1
3933         self.nextlocation = self.location + self.increment
3934         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
3935         self.location = self.nextlocation
3936         return samequad
3937     def quadrant(self):
3938         return self.location.quadrant()
3939     def sector(self):
3940         return self.location.sector()
3941     def power(self, warp):
3942         return self.distance*(warp**3)*(game.shldup+1)
3943     def time(self, warp):
3944         return 10.0*self.distance/warp**2
3945
3946 def impulse():
3947     "Move under impulse power."
3948     game.ididit = False
3949     if damaged(DIMPULS):
3950         scanner.chew()
3951         skip(1)
3952         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
3953         return
3954     if game.energy > 30.0:
3955         try:
3956             course = getcourse(isprobe=False)
3957         except TrekError:
3958             return
3959         power = 20.0 + 100.0*course.distance
3960     else:
3961         power = 30.0
3962     if power >= game.energy:
3963         # Insufficient power for trip 
3964         skip(1)
3965         prout(_("First Officer Spock- \"Captain, the impulse engines"))
3966         prout(_("require 20.0 units to engage, plus 100.0 units per"))
3967         if game.energy > 30:
3968             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
3969                      int(0.01 * (game.energy-20.0)-0.05))
3970             prout(_(" quadrants.\""))
3971         else:
3972             prout(_("quadrant.  They are, therefore, useless.\""))
3973         scanner.chew()
3974         return
3975     # Make sure enough time is left for the trip 
3976     game.optime = course.dist/0.095
3977     if game.optime >= game.state.remtime:
3978         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
3979         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
3980         proutn(_("we dare spend the time?\" "))
3981         if ja() == False:
3982             return
3983     # Activate impulse engines and pay the cost 
3984     imove(course, noattack=False)
3985     game.ididit = True
3986     if game.alldone:
3987         return
3988     power = 20.0 + 100.0*course.dist
3989     game.energy -= power
3990     game.optime = course.dist/0.095
3991     if game.energy <= 0:
3992         finish(FNRG)
3993     return
3994
3995 def warp(course, involuntary):
3996     "ove under warp drive."
3997     blooey = False; twarp = False
3998     if not involuntary: # Not WARPX entry 
3999         game.ididit = False
4000         if game.damage[DWARPEN] > 10.0:
4001             scanner.chew()
4002             skip(1)
4003             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4004             return
4005         if damaged(DWARPEN) and game.warpfac > 4.0:
4006             scanner.chew()
4007             skip(1)
4008             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4009             prout(_("  is repaired, I can only give you warp 4.\""))
4010             return
4011         # Read in course and distance
4012         if course==None:
4013             try:
4014                 course = getcourse(isprobe=False)
4015             except TrekError:
4016                 return
4017         # Make sure starship has enough energy for the trip
4018         # Note: this formula is slightly different from the C version,
4019         # and lets you skate a bit closer to the edge.
4020         if course.power(game.warpfac) >= game.energy:
4021             # Insufficient power for trip 
4022             game.ididit = False
4023             skip(1)
4024             prout(_("Engineering to bridge--"))
4025             if not game.shldup or 0.5*power > game.energy:
4026                 iwarp = (game.energy/(course.dist+0.05)) ** 0.333333333
4027                 if iwarp <= 0:
4028                     prout(_("We can't do it, Captain. We don't have enough energy."))
4029                 else:
4030                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4031                     if game.shldup:
4032                         prout(",")
4033                         prout(_("if you'll lower the shields."))
4034                     else:
4035                         prout(".")
4036             else:
4037                 prout(_("We haven't the energy to go that far with the shields up."))
4038             return                              
4039         # Make sure enough time is left for the trip 
4040         game.optime = course.time(game.warpfac)
4041         if game.optime >= 0.8*game.state.remtime:
4042             skip(1)
4043             prout(_("First Officer Spock- \"Captain, I compute that such"))
4044             proutn(_("  a trip would require approximately %2.0f") %
4045                    (100.0*game.optime/game.state.remtime))
4046             prout(_(" percent of our"))
4047             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4048             if ja() == False:
4049                 game.ididit = False
4050                 game.optime=0 
4051                 return
4052     # Entry WARPX 
4053     if game.warpfac > 6.0:
4054         # Decide if engine damage will occur
4055         # ESR: Seems wrong. Probability of damage goes *down* with distance? 
4056         prob = course.distance*(6.0-game.warpfac)**2/66.666666666
4057         if prob > randreal():
4058             blooey = True
4059             course.distance = randreal(course.distance)
4060         # Decide if time warp will occur 
4061         if 0.5*course.distance*math.pow(7.0,game.warpfac-10.0) > randreal():
4062             twarp = True
4063         if idebug and game.warpfac==10 and not twarp:
4064             blooey = False
4065             proutn("=== Force time warp? ")
4066             if ja() == True:
4067                 twarp = True
4068         if blooey or twarp:
4069             # If time warp or engine damage, check path 
4070             # If it is obstructed, don't do warp or damage 
4071             for m in range(course.moves):
4072                 course.next()
4073                 w = course.sector()
4074                 if not w.valid_sector():
4075                     break
4076                 if game.quad[w.i][w.j] != IHDOT:
4077                     blooey = False
4078                     twarp = False
4079             course.reset()
4080     # Activate Warp Engines and pay the cost 
4081     imove(course, noattack=False)
4082     if game.alldone:
4083         return
4084     game.energy -= course.power(game.warpfac)
4085     if game.energy <= 0:
4086         finish(FNRG)
4087     game.optime = course.time(game.warpfac)
4088     if twarp:
4089         timwrp()
4090     if blooey:
4091         game.damage[DWARPEN] = game.damfac * randreal(1.0, 4.0)
4092         skip(1)
4093         prout(_("Engineering to bridge--"))
4094         prout(_("  Scott here.  The warp engines are damaged."))
4095         prout(_("  We'll have to reduce speed to warp 4."))
4096     game.ididit = True
4097     return
4098
4099 def setwarp():
4100     "Change the warp factor."
4101     while True:
4102         key=scanner.next()
4103         if key != "IHEOL":
4104             break
4105         scanner.chew()
4106         proutn(_("Warp factor- "))
4107     scanner.chew()
4108     if key != "IHREAL":
4109         huh()
4110         return
4111     if game.damage[DWARPEN] > 10.0:
4112         prout(_("Warp engines inoperative."))
4113         return
4114     if damaged(DWARPEN) and scanner.real > 4.0:
4115         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4116         prout(_("  but right now we can only go warp 4.\""))
4117         return
4118     if scanner.real > 10.0:
4119         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4120         return
4121     if scanner.real < 1.0:
4122         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4123         return
4124     oldfac = game.warpfac
4125     game.warpfac = scanner.real
4126     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4127         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4128                int(game.warpfac))
4129         return
4130     if game.warpfac < 8.00:
4131         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4132         return
4133     if game.warpfac == 10.0:
4134         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4135         return
4136     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4137     return
4138
4139 def atover(igrab):
4140     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4141     scanner.chew()
4142     # is captain on planet? 
4143     if game.landed:
4144         if damaged(DTRANSP):
4145             finish(FPNOVA)
4146             return
4147         prout(_("Scotty rushes to the transporter controls."))
4148         if game.shldup:
4149             prout(_("But with the shields up it's hopeless."))
4150             finish(FPNOVA)
4151         prouts(_("His desperate attempt to rescue you . . ."))
4152         if withprob(0.5):
4153             prout(_("fails."))
4154             finish(FPNOVA)
4155             return
4156         prout(_("SUCCEEDS!"))
4157         if game.imine:
4158             game.imine = False
4159             proutn(_("The crystals mined were "))
4160             if withprob(0.25):
4161                 prout(_("lost."))
4162             else:
4163                 prout(_("saved."))
4164                 game.icrystl = True
4165     if igrab:
4166         return
4167     # Check to see if captain in shuttle craft 
4168     if game.icraft:
4169         finish(FSTRACTOR)
4170     if game.alldone:
4171         return
4172     # Inform captain of attempt to reach safety 
4173     skip(1)
4174     while True:
4175         if game.justin:
4176             prouts(_("***RED ALERT!  RED ALERT!"))
4177             skip(1)
4178             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4179             prouts(_("   a supernova."))
4180             skip(2)
4181         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4182         prout(_("safely out of quadrant."))
4183         if not damaged(DRADIO):
4184             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4185         # Try to use warp engines 
4186         if damaged(DWARPEN):
4187             skip(1)
4188             prout(_("Warp engines damaged."))
4189             finish(FSNOVAED)
4190             return
4191         game.warpfac = randreal(6.0, 8.0)
4192         prout(_("Warp factor set to %d") % int(game.warpfac))
4193         power = 0.75*game.energy
4194         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4195         dist = max(dist, randreal(math.sqrt(2)))
4196         bugout = course(bearing=randreal(12), distance=dist)    # How dumb!
4197         game.optime = bugout.time(game.warpfac)
4198         game.justin = False
4199         game.inorbit = False
4200         warp(bugout, involuntary=True)
4201         if not game.justin:
4202             # This is bad news, we didn't leave quadrant. 
4203             if game.alldone:
4204                 return
4205             skip(1)
4206             prout(_("Insufficient energy to leave quadrant."))
4207             finish(FSNOVAED)
4208             return
4209         # Repeat if another snova
4210         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4211             break
4212     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0: 
4213         finish(FWON) # Snova killed remaining enemy. 
4214
4215 def timwrp():
4216     "Let's do the time warp again."
4217     prout(_("***TIME WARP ENTERED."))
4218     if game.state.snap and withprob(0.5):
4219         # Go back in time 
4220         prout(_("You are traveling backwards in time %d stardates.") %
4221               int(game.state.date-game.snapsht.date))
4222         game.state = game.snapsht
4223         game.state.snap = False
4224         if len(game.state.kcmdr):
4225             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4226             schedule(FBATTAK, expran(0.3*game.intime))
4227         schedule(FSNOVA, expran(0.5*game.intime))
4228         # next snapshot will be sooner 
4229         schedule(FSNAP, expran(0.25*game.state.remtime))
4230                                 
4231         if game.state.nscrem:
4232             schedule(FSCMOVE, 0.2777)       
4233         game.isatb = 0
4234         unschedule(FCDBAS)
4235         unschedule(FSCDBAS)
4236         game.battle.invalidate()
4237         # Make sure Galileo is consistant -- Snapshot may have been taken
4238         # when on planet, which would give us two Galileos! 
4239         gotit = False
4240         for l in range(game.inplan):
4241             if game.state.planets[l].known == "shuttle_down":
4242                 gotit = True
4243                 if game.iscraft == "onship" and game.ship==IHE:
4244                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4245                     game.iscraft = "offship"
4246         # Likewise, if in the original time the Galileo was abandoned, but
4247         # was on ship earlier, it would have vanished -- let's restore it.
4248         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4249             prout(_("Checkov-  \"Security reports the Galileo has reappeared in the dock!\""))
4250             game.iscraft = "onship"
4251         # There used to be code to do the actual reconstrction here,
4252         # but the starchart is now part of the snapshotted galaxy state.
4253         prout(_("Spock has reconstructed a correct star chart from memory"))
4254     else:
4255         # Go forward in time 
4256         game.optime = -0.5*game.intime*math.log(randreal())
4257         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4258         # cheat to make sure no tractor beams occur during time warp 
4259         postpone(FTBEAM, game.optime)
4260         game.damage[DRADIO] += game.optime
4261     newqad()
4262     events()    # Stas Sergeev added this -- do pending events 
4263
4264 def probe():
4265     "Launch deep-space probe." 
4266     # New code to launch a deep space probe 
4267     if game.nprobes == 0:
4268         scanner.chew()
4269         skip(1)
4270         if game.ship == IHE: 
4271             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4272         else:
4273             prout(_("Ye Faerie Queene has no deep space probes."))
4274         return
4275     if damaged(DDSP):
4276         scanner.chew()
4277         skip(1)
4278         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4279         return
4280     if is_scheduled(FDSPROB):
4281         scanner.chew()
4282         skip(1)
4283         if damaged(DRADIO) and game.condition != "docked":
4284             prout(_("Spock-  \"Records show the previous probe has not yet"))
4285             prout(_("   reached its destination.\""))
4286         else:
4287             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4288         return
4289     key = scanner.next()
4290     if key == "IHEOL":
4291         if game.nprobes == 1:
4292             prout(_("1 probe left."))
4293         else:
4294             prout(_("%d probes left") % game.nprobes)
4295         proutn(_("Are you sure you want to fire a probe? "))
4296         if ja() == False:
4297             return
4298     game.isarmed = False
4299     if key == "IHALPHA" and scanner.token == "armed":
4300         game.isarmed = True
4301         key = scanner.next()
4302     elif key == "IHEOL":
4303         proutn(_("Arm NOVAMAX warhead? "))
4304         game.isarmed = ja()
4305     elif key == "IHREAL":               # first element of course
4306         scanner.push(scanner.token)
4307     try:
4308         game.probe = getcourse(isprobe=True)
4309     except TrekError:
4310         return
4311     game.nprobes -= 1
4312     schedule(FDSPROB, 0.01) # Time to move one sector
4313     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4314     game.ididit = True
4315     return
4316
4317 def mayday():
4318     "Yell for help from nearest starbase."
4319     # There's more than one way to move in this game! 
4320     scanner.chew()
4321     # Test for conditions which prevent calling for help 
4322     if game.condition == "docked":
4323         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4324         return
4325     if damaged(DRADIO):
4326         prout(_("Subspace radio damaged."))
4327         return
4328     if not game.state.baseq:
4329         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4330         return
4331     if game.landed:
4332         prout(_("You must be aboard the %s.") % crmshp())
4333         return
4334     # OK -- call for help from nearest starbase 
4335     game.nhelp += 1
4336     if game.base.i!=0:
4337         # There's one in this quadrant 
4338         ddist = (game.base - game.sector).distance()
4339     else:
4340         ddist = FOREVER
4341         for ibq in game.state.baseq:
4342             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4343             if xdist < ddist:
4344                 ddist = xdist
4345         # Since starbase not in quadrant, set up new quadrant 
4346         game.quadrant = ibq
4347         newqad()
4348     # dematerialize starship 
4349     game.quad[game.sector.i][game.sector.j]=IHDOT
4350     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4351            % (game.quadrant, crmshp()))
4352     game.sector.invalidate()
4353     for m in range(1, 5+1):
4354         w = game.base.scatter() 
4355         if w.valid_sector() and game.quad[w.i][w.j]==IHDOT:
4356             # found one -- finish up 
4357             game.sector = w
4358             break
4359     if not game.sector.is_valid():
4360         prout(_("You have been lost in space..."))
4361         finish(FMATERIALIZE)
4362         return
4363     # Give starbase three chances to rematerialize starship 
4364     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4365     for m in range(1, 3+1):
4366         if m == 1: proutn(_("1st"))
4367         elif m == 2: proutn(_("2nd"))
4368         elif m == 3: proutn(_("3rd"))
4369         proutn(_(" attempt to re-materialize ") + crmshp())
4370         game.quad[ix][iy]=(IHMATER0,IHMATER1,IHMATER2)[m-1]
4371         #textcolor("red")
4372         warble()
4373         if randreal() > probf:
4374             break
4375         prout(_("fails."))
4376         curses.delay_output(500)
4377         #textcolor(None)
4378     if m > 3:
4379         game.quad[ix][iy]=IHQUEST
4380         game.alive = False
4381         drawmaps(1)
4382         setwnd(message_window)
4383         finish(FMATERIALIZE)
4384         return
4385     game.quad[ix][iy]=game.ship
4386     #textcolor("green")
4387     prout(_("succeeds."))
4388     #textcolor(None)
4389     dock(False)
4390     skip(1)
4391     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4392
4393 def abandon():
4394     "Abandon ship."
4395     scanner.chew()
4396     if game.condition=="docked":
4397         if game.ship!=IHE:
4398             prout(_("You cannot abandon Ye Faerie Queene."))
4399             return
4400     else:
4401         # Must take shuttle craft to exit 
4402         if game.damage[DSHUTTL]==-1:
4403             prout(_("Ye Faerie Queene has no shuttle craft."))
4404             return
4405         if game.damage[DSHUTTL]<0:
4406             prout(_("Shuttle craft now serving Big Macs."))
4407             return
4408         if game.damage[DSHUTTL]>0:
4409             prout(_("Shuttle craft damaged."))
4410             return
4411         if game.landed:
4412             prout(_("You must be aboard the ship."))
4413             return
4414         if game.iscraft != "onship":
4415             prout(_("Shuttle craft not currently available."))
4416             return
4417         # Emit abandon ship messages 
4418         skip(1)
4419         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4420         skip(1)
4421         prouts(_("***ALL HANDS ABANDON SHIP!"))
4422         skip(2)
4423         prout(_("Captain and crew escape in shuttle craft."))
4424         if not game.state.baseq:
4425             # Oops! no place to go... 
4426             finish(FABANDN)
4427             return
4428         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4429         # Dispose of crew 
4430         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4431             prout(_("Remainder of ship's complement beam down"))
4432             prout(_("to nearest habitable planet."))
4433         elif q.planet != None and not damaged(DTRANSP):
4434             prout(_("Remainder of ship's complement beam down to %s.") %
4435                     q.planet)
4436         else:
4437             prout(_("Entire crew of %d left to die in outer space.") %
4438                     game.state.crew)
4439             game.casual += game.state.crew
4440             game.abandoned += game.state.crew
4441         # If at least one base left, give 'em the Faerie Queene 
4442         skip(1)
4443         game.icrystl = False # crystals are lost 
4444         game.nprobes = 0 # No probes 
4445         prout(_("You are captured by Klingons and released to"))
4446         prout(_("the Federation in a prisoner-of-war exchange."))
4447         nb = randrange(len(game.state.baseq))
4448         # Set up quadrant and position FQ adjacient to base 
4449         if not game.quadrant == game.state.baseq[nb]:
4450             game.quadrant = game.state.baseq[nb]
4451             game.sector.i = game.sector.j = 5
4452             newqad()
4453         while True:
4454             # position next to base by trial and error 
4455             game.quad[game.sector.i][game.sector.j] = IHDOT
4456             for l in range(QUADSIZE):
4457                 game.sector = game.base.scatter()
4458                 if game.sector.valid_sector() and \
4459                        game.quad[game.sector.i][game.sector.j] == IHDOT:
4460                     break
4461             if l < QUADSIZE+1:
4462                 break # found a spot 
4463             game.sector.i=QUADSIZE/2
4464             game.sector.j=QUADSIZE/2
4465             newqad()
4466     # Get new commission 
4467     game.quad[game.sector.i][game.sector.j] = game.ship = IHF
4468     game.state.crew = FULLCREW
4469     prout(_("Starfleet puts you in command of another ship,"))
4470     prout(_("the Faerie Queene, which is antiquated but,"))
4471     prout(_("still useable."))
4472     if game.icrystl:
4473         prout(_("The dilithium crystals have been moved."))
4474     game.imine = False
4475     game.iscraft = "offship" # Galileo disappears 
4476     # Resupply ship 
4477     game.condition="docked"
4478     for l in range(NDEVICES): 
4479         game.damage[l] = 0.0
4480     game.damage[DSHUTTL] = -1
4481     game.energy = game.inenrg = 3000.0
4482     game.shield = game.inshld = 1250.0
4483     game.torps = game.intorps = 6
4484     game.lsupres=game.inlsr=3.0
4485     game.shldup=False
4486     game.warpfac=5.0
4487     return
4488
4489 # Code from planets.c begins here.
4490
4491 def consumeTime():
4492     "Abort a lengthy operation if an event interrupts it." 
4493     game.ididit = True
4494     events()
4495     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin: 
4496         return True
4497     return False
4498
4499 def survey():
4500     "Report on (uninhabited) planets in the galaxy."
4501     iknow = False
4502     skip(1)
4503     scanner.chew()
4504     prout(_("Spock-  \"Planet report follows, Captain.\""))
4505     skip(1)
4506     for i in range(game.inplan):
4507         if game.state.planets[i].pclass == "destroyed":
4508             continue
4509         if (game.state.planets[i].known != "unknown" \
4510             and not game.state.planets[i].inhabited) \
4511             or idebug:
4512             iknow = True
4513             if idebug and game.state.planets[i].known=="unknown":
4514                 proutn("(Unknown) ")
4515             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4516             proutn(_("   class "))
4517             proutn(game.state.planets[i].pclass)
4518             proutn("   ")
4519             if game.state.planets[i].crystals != present:
4520                 proutn(_("no "))
4521             prout(_("dilithium crystals present."))
4522             if game.state.planets[i].known=="shuttle_down": 
4523                 prout(_("    Shuttle Craft Galileo on surface."))
4524     if not iknow:
4525         prout(_("No information available."))
4526
4527 def orbit():
4528     "Enter standard orbit." 
4529     skip(1)
4530     scanner.chew()
4531     if game.inorbit:
4532         prout(_("Already in standard orbit."))
4533         return
4534     if damaged(DWARPEN) and damaged(DIMPULS):
4535         prout(_("Both warp and impulse engines damaged."))
4536         return
4537     if not game.plnet.is_valid():
4538         prout("There is no planet in this sector.")
4539         return
4540     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4541         prout(crmshp() + _(" not adjacent to planet."))
4542         skip(1)
4543         return
4544     game.optime = randreal(0.02, 0.05)
4545     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4546     newcnd()
4547     if consumeTime():
4548         return
4549     game.height = randreal(1400, 8600)
4550     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4551     game.inorbit = True
4552     game.ididit = True
4553
4554 def sensor():
4555     "Examine planets in this quadrant."
4556     if damaged(DSRSENS):
4557         if game.options & OPTION_TTY:
4558             prout(_("Short range sensors damaged."))
4559         return
4560     if game.iplnet == None:
4561         if game.options & OPTION_TTY:
4562             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4563         return
4564     if game.iplnet.known == "unknown":
4565         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4566         skip(1)
4567         prout(_("         Planet at Sector %s is of class %s.") %
4568               (game.plnet, game.iplnet.pclass))
4569         if game.iplnet.known=="shuttle_down": 
4570             prout(_("         Sensors show Galileo still on surface."))
4571         proutn(_("         Readings indicate"))
4572         if game.iplnet.crystals != "present":
4573             proutn(_(" no"))
4574         prout(_(" dilithium crystals present.\""))
4575         if game.iplnet.known == "unknown":
4576             game.iplnet.known = "known"
4577     elif game.iplnet.inhabited:
4578         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4579         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4580
4581 def beam():
4582     "Use the transporter."
4583     nrgneed = 0
4584     scanner.chew()
4585     skip(1)
4586     if damaged(DTRANSP):
4587         prout(_("Transporter damaged."))
4588         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4589             skip(1)
4590             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4591             if ja() == True:
4592                 shuttle()
4593         return
4594     if not game.inorbit:
4595         prout(crmshp() + _(" not in standard orbit."))
4596         return
4597     if game.shldup:
4598         prout(_("Impossible to transport through shields."))
4599         return
4600     if game.iplnet.known=="unknown":
4601         prout(_("Spock-  \"Captain, we have no information on this planet"))
4602         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4603         prout(_("  you may not go down.\""))
4604         return
4605     if not game.landed and game.iplnet.crystals=="absent":
4606         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4607         prout(_("  exploring a planet with no dilithium crystals."))
4608         proutn(_("  Are you sure this is wise?\" "))
4609         if ja() == False:
4610             scanner.chew()
4611             return
4612     if not (game.options & OPTION_PLAIN):
4613         nrgneed = 50 * game.skill + game.height / 100.0
4614         if nrgneed > game.energy:
4615             prout(_("Engineering to bridge--"))
4616             prout(_("  Captain, we don't have enough energy for transportation."))
4617             return
4618         if not game.landed and nrgneed * 2 > game.energy:
4619             prout(_("Engineering to bridge--"))
4620             prout(_("  Captain, we have enough energy only to transport you down to"))
4621             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4622             if game.iplnet.known == "shuttle_down":
4623                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4624             proutn(_("  Are you sure this is wise?\" "))
4625             if ja() == False:
4626                 scanner.chew()
4627                 return
4628     if game.landed:
4629         # Coming from planet 
4630         if game.iplnet.known=="shuttle_down":
4631             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4632             if ja() == True:
4633                 scanner.chew()
4634                 return
4635             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4636         prout(_("Landing party assembled, ready to beam up."))
4637         skip(1)
4638         prout(_("Kirk whips out communicator..."))
4639         prouts(_("BEEP  BEEP  BEEP"))
4640         skip(2)
4641         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4642     else:
4643         # Going to planet 
4644         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4645         skip(1)
4646         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4647         skip(1)
4648         prout(_("Kirk-  \"Energize.\""))
4649     game.ididit = True
4650     skip(1)
4651     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4652     skip(2)
4653     if withprob(0.98):
4654         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4655         skip(2)
4656         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4657         finish(FLOST)
4658         return
4659     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4660     game.landed = not game.landed
4661     game.energy -= nrgneed
4662     skip(2)
4663     prout(_("Transport complete."))
4664     if game.landed and game.iplnet.known=="shuttle_down":
4665         prout(_("The shuttle craft Galileo is here!"))
4666     if not game.landed and game.imine:
4667         game.icrystl = True
4668         game.cryprob = 0.05
4669     game.imine = False
4670     return
4671
4672 def mine():
4673     "Strip-mine a world for dilithium."
4674     skip(1)
4675     scanner.chew()
4676     if not game.landed:
4677         prout(_("Mining party not on planet."))
4678         return
4679     if game.iplnet.crystals == "mined":
4680         prout(_("This planet has already been strip-mined for dilithium."))
4681         return
4682     elif game.iplnet.crystals == "absent":
4683         prout(_("No dilithium crystals on this planet."))
4684         return
4685     if game.imine:
4686         prout(_("You've already mined enough crystals for this trip."))
4687         return
4688     if game.icrystl and game.cryprob == 0.05:
4689         prout(_("With all those fresh crystals aboard the ") + crmshp())
4690         prout(_("there's no reason to mine more at this time."))
4691         return
4692     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4693     if consumeTime():
4694         return
4695     prout(_("Mining operation complete."))
4696     game.iplnet.crystals = "mined"
4697     game.imine = game.ididit = True
4698
4699 def usecrystals():
4700     "Use dilithium crystals."
4701     game.ididit = False
4702     skip(1)
4703     scanner.chew()
4704     if not game.icrystl:
4705         prout(_("No dilithium crystals available."))
4706         return
4707     if game.energy >= 1000:
4708         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4709         prout(_("  except when Condition Yellow exists."))
4710         return
4711     prout(_("Spock- \"Captain, I must warn you that loading"))
4712     prout(_("  raw dilithium crystals into the ship's power"))
4713     prout(_("  system may risk a severe explosion."))
4714     proutn(_("  Are you sure this is wise?\" "))
4715     if ja() == False:
4716         scanner.chew()
4717         return
4718     skip(1)
4719     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4720     prout(_("  Mr. Spock and I will try it.\""))
4721     skip(1)
4722     prout(_("Spock-  \"Crystals in place, Sir."))
4723     prout(_("  Ready to activate circuit.\""))
4724     skip(1)
4725     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4726     skip(1)
4727     if with(game.cryprob):
4728         prouts(_("  \"Activating now! - - No good!  It's***"))
4729         skip(2)
4730         prouts(_("***RED ALERT!  RED A*L********************************"))
4731         skip(1)
4732         stars()
4733         prouts(_("******************   KA-BOOM!!!!   *******************"))
4734         skip(1)
4735         kaboom()
4736         return
4737     game.energy += randreal(5000.0, 5500.0)
4738     prouts(_("  \"Activating now! - - "))
4739     prout(_("The instruments"))
4740     prout(_("   are going crazy, but I think it's"))
4741     prout(_("   going to work!!  Congratulations, Sir!\""))
4742     game.cryprob *= 2.0
4743     game.ididit = True
4744
4745 def shuttle():
4746     "Use shuttlecraft for planetary jaunt."
4747     scanner.chew()
4748     skip(1)
4749     if damaged(DSHUTTL):
4750         if game.damage[DSHUTTL] == -1.0:
4751             if game.inorbit and game.iplnet.known == "shuttle_down":
4752                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4753             else:
4754                 prout(_("Ye Faerie Queene had no shuttle craft."))
4755         elif game.damage[DSHUTTL] > 0:
4756             prout(_("The Galileo is damaged."))
4757         else: # game.damage[DSHUTTL] < 0  
4758             prout(_("Shuttle craft is now serving Big Macs."))
4759         return
4760     if not game.inorbit:
4761         prout(crmshp() + _(" not in standard orbit."))
4762         return
4763     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4764         prout(_("Shuttle craft not currently available."))
4765         return
4766     if not game.landed and game.iplnet.known=="shuttle_down":
4767         prout(_("You will have to beam down to retrieve the shuttle craft."))
4768         return
4769     if game.shldup or game.condition == "docked":
4770         prout(_("Shuttle craft cannot pass through shields."))
4771         return
4772     if game.iplnet.known=="unknown":
4773         prout(_("Spock-  \"Captain, we have no information on this planet"))
4774         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4775         prout(_("  you may not fly down.\""))
4776         return
4777     game.optime = 3.0e-5*game.height
4778     if game.optime >= 0.8*game.state.remtime:
4779         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4780         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4781                int(100*game.optime/game.state.remtime))
4782         prout(_("remaining time."))
4783         proutn(_("Are you sure this is wise?\" "))
4784         if ja() == False:
4785             game.optime = 0.0
4786             return
4787     if game.landed:
4788         # Kirk on planet 
4789         if game.iscraft == "onship":
4790             # Galileo on ship! 
4791             if not damaged(DTRANSP):
4792                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4793                 if ja() == True:
4794                     beam()
4795                     return
4796                 proutn(_("Shuttle crew"))
4797             else:
4798                 proutn(_("Rescue party"))
4799             prout(_(" boards Galileo and swoops toward planet surface."))
4800             game.iscraft = "offship"
4801             skip(1)
4802             if consumeTime():
4803                 return
4804             game.iplnet.known="shuttle_down"
4805             prout(_("Trip complete."))
4806             return
4807         else:
4808             # Ready to go back to ship 
4809             prout(_("You and your mining party board the"))
4810             prout(_("shuttle craft for the trip back to the Enterprise."))
4811             skip(1)
4812             prouts(_("The short hop begins . . ."))
4813             skip(1)
4814             game.iplnet.known="known"
4815             game.icraft = True
4816             skip(1)
4817             game.landed = False
4818             if consumeTime():
4819                 return
4820             game.iscraft = "onship"
4821             game.icraft = False
4822             if game.imine:
4823                 game.icrystl = True
4824                 game.cryprob = 0.05
4825             game.imine = False
4826             prout(_("Trip complete."))
4827             return
4828     else:
4829         # Kirk on ship and so is Galileo 
4830         prout(_("Mining party assembles in the hangar deck,"))
4831         prout(_("ready to board the shuttle craft \"Galileo\"."))
4832         skip(1)
4833         prouts(_("The hangar doors open; the trip begins."))
4834         skip(1)
4835         game.icraft = True
4836         game.iscraft = "offship"
4837         if consumeTime():
4838             return
4839         game.iplnet.known = "shuttle_down"
4840         game.landed = True
4841         game.icraft = False
4842         prout(_("Trip complete."))
4843         return
4844
4845 def deathray():
4846     "Use the big zapper."
4847     game.ididit = False
4848     skip(1)
4849     scanner.chew()
4850     if game.ship != IHE:
4851         prout(_("Ye Faerie Queene has no death ray."))
4852         return
4853     if len(game.enemies)==0:
4854         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
4855         return
4856     if damaged(DDRAY):
4857         prout(_("Death Ray is damaged."))
4858         return
4859     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
4860     prout(_("  is highly unpredictible.  Considering the alternatives,"))
4861     proutn(_("  are you sure this is wise?\" "))
4862     if ja() == False:
4863         return
4864     prout(_("Spock-  \"Acknowledged.\""))
4865     skip(1)
4866     game.ididit = True
4867     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
4868     skip(1)
4869     prout(_("Crew scrambles in emergency preparation."))
4870     prout(_("Spock and Scotty ready the death ray and"))
4871     prout(_("prepare to channel all ship's power to the device."))
4872     skip(1)
4873     prout(_("Spock-  \"Preparations complete, sir.\""))
4874     prout(_("Kirk-  \"Engage!\""))
4875     skip(1)
4876     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
4877     skip(1)
4878     dprob = 0.30
4879     if game.options & OPTION_PLAIN:
4880         dprob = 0.5
4881     r = randreal()
4882     if r > dprob:
4883         prouts(_("Sulu- \"Captain!  It's working!\""))
4884         skip(2)
4885         while len(game.enemies) > 0:
4886             deadkl(game.enemies[1].kloc, game.quad[game.enemies[1].kloc.i][game.enemies[1].kloc.j],game.enemies[1].kloc)
4887         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
4888         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
4889             finish(FWON)    
4890         if (game.options & OPTION_PLAIN) == 0:
4891             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
4892             if withprob(0.05):
4893                 prout(_("   is still operational.\""))
4894             else:
4895                 prout(_("   has been rendered nonfunctional.\""))
4896                 game.damage[DDRAY] = 39.95
4897         return
4898     r = randreal()      # Pick failure method 
4899     if r <= 0.30:
4900         prouts(_("Sulu- \"Captain!  It's working!\""))
4901         skip(1)
4902         prouts(_("***RED ALERT!  RED ALERT!"))
4903         skip(1)
4904         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
4905         skip(1)
4906         prouts(_("***RED ALERT!  RED A*L********************************"))
4907         skip(1)
4908         stars()
4909         prouts(_("******************   KA-BOOM!!!!   *******************"))
4910         skip(1)
4911         kaboom()
4912         return
4913     if r <= 0.55:
4914         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
4915         skip(1)
4916         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
4917         skip(1)
4918         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
4919         prout(_("  have apparently been transformed into strange mutations."))
4920         prout(_("  Vulcans do not seem to be affected."))
4921         skip(1)
4922         prout(_("Kirk-  \"Raauch!  Raauch!\""))
4923         finish(FDRAY)
4924         return
4925     if r <= 0.75:
4926         intj
4927         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
4928         skip(2)
4929         proutn(_("Spock-  \"I believe the word is"))
4930         prouts(_(" *ASTONISHING*"))
4931         prout(_(" Mr. Sulu."))
4932         for i in range(QUADSIZE):
4933             for j in range(QUADSIZE):
4934                 if game.quad[i][j] == IHDOT:
4935                     game.quad[i][j] = IHQUEST
4936         prout(_("  Captain, our quadrant is now infested with"))
4937         prouts(_(" - - - - - -  *THINGS*."))
4938         skip(1)
4939         prout(_("  I have no logical explanation.\""))
4940         return
4941     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
4942     skip(1)
4943     prout(_("Scotty-  \"There are so many tribbles down here"))
4944     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
4945     finish(FTRIBBLE)
4946     return
4947
4948 # Code from reports.c begins here
4949
4950 def attackreport(curt):
4951     "eport status of bases under attack."
4952     if not curt:
4953         if is_scheduled(FCDBAS):
4954             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
4955             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
4956         elif game.isatb == 1:
4957             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
4958             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
4959         else:
4960             prout(_("No Starbase is currently under attack."))
4961     else:
4962         if is_scheduled(FCDBAS):
4963             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
4964         if game.isatb:
4965             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
4966         clreol()
4967
4968 def report():
4969     # report on general game status 
4970     scanner.chew()
4971     s1 = "" and game.thawed and _("thawed ")
4972     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
4973     s3 = (None, _("novice"). _("fair"),
4974           _("good"), _("expert"), _("emeritus"))[game.skill]
4975     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
4976     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
4977         prout(_("No plaque is allowed."))
4978     if game.tourn:
4979         prout(_("This is tournament game %d.") % game.tourn)
4980     prout(_("Your secret password is \"%s\"") % game.passwd)
4981     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)), 
4982            (game.inkling + game.incom + game.inscom)))
4983     if game.incom - len(game.state.kcmdr):
4984         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
4985     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
4986         prout(_(", but no Commanders."))
4987     else:
4988         prout(".")
4989     if game.skill > SKILL_FAIR:
4990         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
4991     if len(game.state.baseq) != game.inbase:
4992         proutn(_("There "))
4993         if game.inbase-len(game.state.baseq)==1:
4994             proutn(_("has been 1 base"))
4995         else:
4996             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
4997         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
4998     else:
4999         prout(_("There are %d bases.") % game.inbase)
5000     if communicating() or game.iseenit:
5001         # Don't report this if not seen and
5002         # either the radio is dead or not at base!
5003         attackreport(False)
5004         game.iseenit = True
5005     if game.casual: 
5006         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5007     if game.nhelp:
5008         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5009     if game.ship == IHE:
5010         proutn(_("You have "))
5011         if game.nprobes:
5012             proutn("%d" % (game.nprobes))
5013         else:
5014             proutn(_("no"))
5015         proutn(_(" deep space probe"))
5016         if game.nprobes!=1:
5017             proutn(_("s"))
5018         prout(".")
5019     if communicating() and is_scheduled(FDSPROB):
5020         if game.isarmed: 
5021             proutn(_("An armed deep space probe is in "))
5022         else:
5023             proutn(_("A deep space probe is in "))
5024         prout("Quadrant %s." % game.probec)
5025     if game.icrystl:
5026         if game.cryprob <= .05:
5027             prout(_("Dilithium crystals aboard ship... not yet used."))
5028         else:
5029             i=0
5030             ai = 0.05
5031             while game.cryprob > ai:
5032                 ai *= 2.0
5033                 i += 1
5034             prout(_("Dilithium crystals have been used %d time%s.") % \
5035                   (i, (_("s"), "")[i==1]))
5036     skip(1)
5037         
5038 def lrscan(silent):
5039     "Long-range sensor scan."
5040     if damaged(DLRSENS):
5041         # Now allow base's sensors if docked 
5042         if game.condition != "docked":
5043             if not silent:
5044                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5045             return
5046         if not silent:
5047             prout(_("Starbase's long-range scan"))
5048     elif not silent:
5049         prout(_("Long-range scan"))
5050     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5051         if not silent:
5052             proutn(" ")
5053         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5054             if not coord(x, y).valid_quadrant():
5055                 if not silent:
5056                     proutn("  -1")
5057             else:
5058                 if not damaged(DRADIO):
5059                     game.state.galaxy[x][y].charted = True
5060                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5061                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5062                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5063                 if not silent and game.state.galaxy[x][y].supernova: 
5064                     proutn(" ***")
5065                 elif not silent:
5066                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
5067         prout(" ")
5068
5069 def damagereport():
5070     "Damage report."
5071     jdam = False
5072     scanner.chew()
5073     for i in range(NDEVICES):
5074         if damaged(i):
5075             if not jdam:
5076                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5077                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5078                 jdam = True
5079             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5080                                                game.damage[i]+0.05,
5081                                                game.docfac*game.damage[i]+0.005))
5082     if not jdam:
5083         prout(_("All devices functional."))
5084
5085 def rechart():
5086     "Update the chart in the Enterprise's computer from galaxy data."
5087     game.lastchart = game.state.date
5088     for i in range(GALSIZE):
5089         for j in range(GALSIZE):
5090             if game.state.galaxy[i][j].charted:
5091                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5092                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5093                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5094
5095 def chart():
5096     "Display the star chart."
5097     scanner.chew()
5098     if (game.options & OPTION_AUTOSCAN):
5099         lrscan(silent=True)
5100     if not damaged(DRADIO):
5101         rechart()
5102     if game.lastchart < game.state.date and game.condition == "docked":
5103         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5104         rechart()
5105     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5106     if game.state.date > game.lastchart:
5107         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5108     prout("      1    2    3    4    5    6    7    8")
5109     for i in range(GALSIZE):
5110         proutn("%d |" % (i+1))
5111         for j in range(GALSIZE):
5112             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5113                 proutn("<")
5114             else:
5115                 proutn(" ")
5116             if game.state.galaxy[i][j].supernova:
5117                 show = "***"
5118             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5119                 show = ".1."
5120             elif game.state.galaxy[i][j].charted:
5121                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5122             else:
5123                 show = "..."
5124             proutn(show)
5125             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5126                 proutn(">")
5127             else:
5128                 proutn(" ")
5129         proutn("  |")
5130         if i<GALSIZE:
5131             skip(1)
5132
5133 def sectscan(goodScan, i, j):
5134     "Light up an individual dot in a sector."
5135     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5136         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):
5137             #if game.condition   == "red": textcolor("red")
5138             #elif game.condition == "green": textcolor("green")
5139             #elif game.condition == "yellow": textcolor("yellow")
5140             #elif game.condition == "docked": textcolor("cyan")
5141             #elif game.condition == "dead": textcolor("brown")
5142             if game.quad[i][j] != game.ship: 
5143                 highvideo()
5144         proutn("%c " % game.quad[i][j])
5145         #textcolor(None)
5146     else:
5147         proutn("- ")
5148
5149 def status(req=0):
5150     "Emit status report lines"
5151     if not req or req == 1:
5152         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5153                % (game.state.date, game.state.remtime))
5154     if not req or req == 2:
5155         if game.condition != "docked":
5156             newcnd()
5157         dam = 0
5158         for t in range(NDEVICES):
5159             if game.damage[t]>0: 
5160                 dam += 1
5161         prstat(_("Condition"), _("%s, %i DAMAGES") % (game.condition.upper(), dam))
5162     if not req or req == 3:
5163         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5164     if not req or req == 4:
5165         if damaged(DLIFSUP):
5166             if game.condition == "docked":
5167                 s = _("DAMAGED, Base provides")
5168             else:
5169                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5170         else:
5171             s = _("ACTIVE")
5172         prstat(_("Life Support"), s)
5173     if not req or req == 5:
5174         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5175     if not req or req == 6:
5176         extra = ""
5177         if game.icrystl and (game.options & OPTION_SHOWME):
5178             extra = _(" (have crystals)")
5179         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5180     if not req or req == 7:
5181         prstat(_("Torpedoes"), "%d" % (game.torps))
5182     if not req or req == 8:
5183         if damaged(DSHIELD):
5184             s = _("DAMAGED,")
5185         elif game.shldup:
5186             s = _("UP,")
5187         else:
5188             s = _("DOWN,")
5189         data = _(" %d%% %.1f units") \
5190                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5191         prstat(_("Shields"), s+data)
5192     if not req or req == 9:
5193         prstat(_("Klingons Left"), "%d" \
5194                % (game.state.remkl+len(game.state.kcmdr)+game.state.nscrem))
5195     if not req or req == 10:
5196         if game.options & OPTION_WORLDS:
5197             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5198             if plnet and plnet.inhabited:
5199                 prstat(_("Major system"), plnet.name)
5200             else:
5201                 prout(_("Sector is uninhabited"))
5202     elif not req or req == 11:
5203         attackreport(not req)
5204
5205 def request():
5206     "Request specified status data, a historical relic from slow TTYs."
5207     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5208     while scanner.next() == "IHEOL":
5209         proutn(_("Information desired? "))
5210     scanner.chew()
5211     if scanner.token in requests:
5212         status(requests.index(scanner.token))
5213     else:
5214         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5215         prout(("  date, condition, position, lsupport, warpfactor,"))
5216         prout(("  energy, torpedoes, shields, klingons, system, time."))
5217                 
5218 def srscan():
5219     "Short-range scan." 
5220     goodScan=True
5221     if damaged(DSRSENS):
5222         # Allow base's sensors if docked 
5223         if game.condition != "docked":
5224             prout(_("   S.R. SENSORS DAMAGED!"))
5225             goodScan=False
5226         else:
5227             prout(_("  [Using Base's sensors]"))
5228     else:
5229         prout(_("     Short-range scan"))
5230     if goodScan and not damaged(DRADIO): 
5231         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5232         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5233         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5234         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5235     prout("    1 2 3 4 5 6 7 8 9 10")
5236     if game.condition != "docked":
5237         newcnd()
5238     for i in range(QUADSIZE):
5239         proutn("%2d  " % (i+1))
5240         for j in range(QUADSIZE):
5241             sectscan(goodScan, i, j)
5242         skip(1)
5243                 
5244 def eta():
5245     "Use computer to get estimated time of arrival for a warp jump."
5246     w1 = coord(); w2 = coord()
5247     prompt = False
5248     if damaged(DCOMPTR):
5249         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5250         skip(1)
5251         return
5252     if scanner.next() != "IHREAL":
5253         prompt = True
5254         scanner.chew()
5255         proutn(_("Destination quadrant and/or sector? "))
5256         if scanner.next()!="IHREAL":
5257             huh()
5258             return
5259     w1.j = int(scanner.real-0.5)
5260     if scanner.next() != "IHREAL":
5261         huh()
5262         return
5263     w1.i = int(scanner.real-0.5)
5264     if scanner.next() == "IHREAL":
5265         w2.j = int(scanner.real-0.5)
5266         if scanner.next() != "IHREAL":
5267             huh()
5268             return
5269         w2.i = int(scanner.real-0.5)
5270     else:
5271         if game.quadrant.j>w1.i:
5272             w2.i = 0
5273         else:
5274             w2.i=QUADSIZE-1
5275         if game.quadrant.i>w1.j:
5276             w2.j = 0
5277         else:
5278             w2.j=QUADSIZE-1
5279     if not w1.valid_quadrant() or not w2.valid_sector():
5280         huh()
5281         return
5282     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5283                 (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5284     wfl = False
5285     if prompt:
5286         prout(_("Answer \"no\" if you don't know the value:"))
5287     while True:
5288         scanner.chew()
5289         proutn(_("Time or arrival date? "))
5290         if scanner.next()=="IHREAL":
5291             ttime = scanner.real
5292             if ttime > game.state.date:
5293                 ttime -= game.state.date # Actually a star date
5294             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5295             if ttime <= 1e-10 or twarp > 10:
5296                 prout(_("We'll never make it, sir."))
5297                 scanner.chew()
5298                 return
5299             if twarp < 1.0:
5300                 twarp = 1.0
5301             break
5302         scanner.chew()
5303         proutn(_("Warp factor? "))
5304         if scanner.next()== "IHREAL":
5305             wfl = True
5306             twarp = scanner.real
5307             if twarp<1.0 or twarp > 10.0:
5308                 huh()
5309                 return
5310             break
5311         prout(_("Captain, certainly you can give me one of these."))
5312     while True:
5313         scanner.chew()
5314         ttime = (10.0*dist)/twarp**2
5315         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5316         if tpower >= game.energy:
5317             prout(_("Insufficient energy, sir."))
5318             if not game.shldup or tpower > game.energy*2.0:
5319                 if not wfl:
5320                     return
5321                 proutn(_("New warp factor to try? "))
5322                 if scanner.next() == "IHREAL":
5323                     wfl = True
5324                     twarp = scanner.real
5325                     if twarp<1.0 or twarp > 10.0:
5326                         huh()
5327                         return
5328                     continue
5329                 else:
5330                     scanner.chew()
5331                     skip(1)
5332                     return
5333             prout(_("But if you lower your shields,"))
5334             proutn(_("remaining"))
5335             tpower /= 2
5336         else:
5337             proutn(_("Remaining"))
5338         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5339         if wfl:
5340             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5341         elif twarp==1.0:
5342             prout(_("Any warp speed is adequate."))
5343         else:
5344             prout(_("Minimum warp needed is %.2f,") % (twarp))
5345             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5346         if game.state.remtime < ttime:
5347             prout(_("Unfortunately, the Federation will be destroyed by then."))
5348         if twarp > 6.0:
5349             prout(_("You'll be taking risks at that speed, Captain"))
5350         if (game.isatb==1 and game.state.kscmdr == w1 and \
5351              scheduled(FSCDBAS)< ttime+game.state.date) or \
5352             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5353             prout(_("The starbase there will be destroyed by then."))
5354         proutn(_("New warp factor to try? "))
5355         if scanner.next() == "IHREAL":
5356             wfl = True
5357             twarp = scanner.real
5358             if twarp<1.0 or twarp > 10.0:
5359                 huh()
5360                 return
5361         else:
5362             scanner.chew()
5363             skip(1)
5364             return
5365
5366 # Code from setup.c begins here
5367
5368 def prelim():
5369     "Issue a historically correct banner."
5370     skip(2)
5371     prout(_("-SUPER- STAR TREK"))
5372     skip(1)
5373 # From the FORTRAN original
5374 #    prout(_("Latest update-21 Sept 78"))
5375 #    skip(1)
5376
5377 def freeze(boss):
5378     "Save game."
5379     if boss:
5380         scanner.push("emsave.trk")
5381     key = scanner.next()
5382     if key == "IHEOL":
5383         proutn(_("File name: "))
5384         key = scanner.next()
5385     if key != "IHALPHA":
5386         huh()
5387         return
5388     scanner.chew()
5389     if '.' not in scanner.token:
5390         scanner.token += ".trk"
5391     try:
5392         fp = open(scanner.token, "wb")
5393     except IOError:
5394         prout(_("Can't freeze game as file %s") % scanner.token)
5395         return
5396     cPickle.dump(game, fp)
5397     fp.close()
5398
5399 def thaw():
5400     "Retrieve saved game." 
5401     game.passwd[0] = '\0'
5402     key = scanner.next()
5403     if key == "IHEOL":
5404         proutn(_("File name: "))
5405         key = scanner.next()
5406     if key != "IHALPHA":
5407         huh()
5408         return True
5409     scanner.chew()
5410     if '.' not in scanner.token:
5411         scanner.token += ".trk"
5412     try:
5413         fp = open(scanner.token, "rb")
5414     except IOError:
5415         prout(_("Can't thaw game in %s") % scanner.token)
5416         return
5417     game = cPickle.load(fp)
5418     fp.close()
5419     return False
5420
5421 # I used <http://www.memory-alpha.org> to find planets
5422 # with references in ST:TOS.  Eath and the Alpha Centauri
5423 # Colony have been omitted.
5424
5425 # Some planets marked Class G and P here will be displayed as class M
5426 # because of the way planets are generated. This is a known bug.
5427 systnames = (
5428     # Federation Worlds 
5429     _("Andoria (Fesoan)"),      # several episodes 
5430     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel" 
5431     _("Vulcan (T'Khasi)"),      # many episodes 
5432     _("Medusa"),                # TOS: "Is There in Truth No Beauty?" 
5433     _("Argelius II (Nelphia)"), # TOS: "Wolf in the Fold" ("IV" in BSD) 
5434     _("Ardana"),                # TOS: "The Cloud Minders" 
5435     _("Catulla (Cendo-Prae)"),  # TOS: "The Way to Eden" 
5436     _("Gideon"),                # TOS: "The Mark of Gideon" 
5437     _("Aldebaran III"),         # TOS: "The Deadly Years" 
5438     _("Alpha Majoris I"),       # TOS: "Wolf in the Fold" 
5439     _("Altair IV"),             # TOS: "Amok Time 
5440     _("Ariannus"),              # TOS: "Let That Be Your Last Battlefield" 
5441     _("Benecia"),               # TOS: "The Conscience of the King" 
5442     _("Beta Niobe I (Sarpeidon)"),      # TOS: "All Our Yesterdays" 
5443     _("Alpha Carinae II"),      # TOS: "The Ultimate Computer" 
5444     _("Capella IV (Kohath)"),   # TOS: "Friday's Child" (Class G) 
5445     _("Daran V"),               # TOS: "For the World is Hollow and I Have Touched the Sky" 
5446     _("Deneb II"),              # TOS: "Wolf in the Fold" ("IV" in BSD) 
5447     _("Eminiar VII"),           # TOS: "A Taste of Armageddon" 
5448     _("Gamma Canaris IV"),      # TOS: "Metamorphosis" 
5449     _("Gamma Tranguli VI (Vaalel)"),    # TOS: "The Apple" 
5450     _("Ingraham B"),            # TOS: "Operation: Annihilate" 
5451     _("Janus IV"),              # TOS: "The Devil in the Dark" 
5452     _("Makus III"),             # TOS: "The Galileo Seven" 
5453     _("Marcos XII"),            # TOS: "And the Children Shall Lead", 
5454     _("Omega IV"),              # TOS: "The Omega Glory" 
5455     _("Regulus V"),             # TOS: "Amok Time 
5456     _("Deneva"),                # TOS: "Operation -- Annihilate!" 
5457     # Worlds from BSD Trek 
5458     _("Rigel II"),              # TOS: "Shore Leave" ("III" in BSD) 
5459     _("Beta III"),              # TOS: "The Return of the Archons" 
5460     _("Triacus"),               # TOS: "And the Children Shall Lead", 
5461     _("Exo III"),               # TOS: "What Are Little Girls Made Of?" (Class P) 
5462 #       # Others 
5463 #    _("Hansen's Planet"),      # TOS: "The Galileo Seven" 
5464 #    _("Taurus IV"),            # TOS: "The Galileo Seven" (class G) 
5465 #    _("Antos IV (Doraphane)"), # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?" 
5466 #    _("Izar"),                 # TOS: "Whom Gods Destroy" 
5467 #    _("Tiburon"),              # TOS: "The Way to Eden" 
5468 #    _("Merak II"),             # TOS: "The Cloud Minders" 
5469 #    _("Coridan (Desotriana)"), # TOS: "Journey to Babel" 
5470 #    _("Iotia"),                # TOS: "A Piece of the Action" 
5471 )
5472
5473 device = (
5474         _("S. R. Sensors"), \
5475         _("L. R. Sensors"), \
5476         _("Phasers"), \
5477         _("Photon Tubes"), \
5478         _("Life Support"), \
5479         _("Warp Engines"), \
5480         _("Impulse Engines"), \
5481         _("Shields"), \
5482         _("Subspace Radio"), \
5483         _("Shuttle Craft"), \
5484         _("Computer"), \
5485         _("Navigation System"), \
5486         _("Transporter"), \
5487         _("Shield Control"), \
5488         _("Death Ray"), \
5489         _("D. S. Probe"), \
5490 )
5491
5492 def setup():
5493     "Prepare to play, set up cosmos."
5494     w = coord()
5495     #  Decide how many of everything
5496     if choose():
5497         return # frozen game
5498     # Prepare the Enterprise
5499     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5500     game.ship = IHE
5501     game.state.crew = FULLCREW
5502     game.energy = game.inenrg = 5000.0
5503     game.shield = game.inshld = 2500.0
5504     game.inlsr = 4.0
5505     game.lsupres = 4.0
5506     game.quadrant = randplace(GALSIZE)
5507     game.sector = randplace(QUADSIZE)
5508     game.torps = game.intorps = 10
5509     game.nprobes = randrange(2, 5)
5510     game.warpfac = 5.0
5511     for i in range(NDEVICES): 
5512         game.damage[i] = 0.0
5513     # Set up assorted game parameters
5514     game.battle = coord()
5515     game.state.date = game.indate = 100.0 * randreal(20, 51)
5516     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5517     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5518     game.isatb = game.state.nplankl = 0
5519     game.state.starkl = game.state.basekl = 0
5520     game.iscraft = "onship"
5521     game.landed = False
5522     game.alive = True
5523     game.docfac = 0.25
5524     # Starchart is functional but we've never seen it
5525     game.lastchart = FOREVER
5526     # Put stars in the galaxy
5527     game.instar = 0
5528     for i in range(GALSIZE):
5529         for j in range(GALSIZE):
5530             k = randrange(1, QUADSIZE**2/10+1)
5531             game.instar += k
5532             game.state.galaxy[i][j].stars = k
5533     # Locate star bases in galaxy
5534     for i in range(game.inbase):
5535         while True:
5536             while True:
5537                 w = randplace(GALSIZE)
5538                 if not game.state.galaxy[w.i][w.j].starbase:
5539                     break
5540             contflag = False
5541             # C version: for (j = i-1; j > 0; j--)
5542             # so it did them in the opposite order.
5543             for j in range(1, i):
5544                 # Improved placement algorithm to spread out bases
5545                 distq = (w - game.state.baseq[j]).distance()
5546                 if distq < 6.0*(BASEMAX+1-game.inbase) and withprob(0.75):
5547                     contflag = True
5548                     if idebug:
5549                         prout("=== Abandoning base #%d at %s" % (i, w))
5550                     break
5551                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5552                     if idebug:
5553                         prout("=== Saving base #%d, close to #%d" % (i, j))
5554             if not contflag:
5555                 break
5556         game.state.baseq.append(w)
5557         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5558     # Position ordinary Klingon Battle Cruisers
5559     krem = game.inkling
5560     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5561     if klumper > MAXKLQUAD: 
5562         klumper = MAXKLQUAD
5563     while True:
5564         r = randreal()
5565         klump = (1.0 - r*r)*klumper
5566         if klump > krem:
5567             klump = krem
5568         krem -= klump
5569         while True:
5570             w = randplace(GALSIZE)
5571             if not game.state.galaxy[w.i][w.j].supernova and \
5572                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5573                 break
5574         game.state.galaxy[w.i][w.j].klingons += int(klump)
5575         if krem <= 0:
5576             break
5577     # Position Klingon Commander Ships
5578     for i in range(game.incom):
5579         while True:
5580             w = randplace(GALSIZE)
5581             if not welcoming(w) or w in game.state.kcmdr:
5582                 continue
5583             if (game.state.galaxy[w.i][w.j].klingons or withprob(0.25)):
5584                 break
5585         game.state.galaxy[w.i][w.j].klingons += 1
5586         game.state.kcmdr.append(w)
5587     # Locate planets in galaxy
5588     for i in range(game.inplan):
5589         while True:
5590             w = randplace(GALSIZE) 
5591             if game.state.galaxy[w.i][w.j].planet == None:
5592                 break
5593         new = planet()
5594         new.quadrant = w
5595         new.crystals = "absent"
5596         if (game.options & OPTION_WORLDS) and i < NINHAB:
5597             new.pclass = "M"    # All inhabited planets are class M
5598             new.crystals = "absent"
5599             new.known = "known"
5600             new.name = systnames[i]
5601             new.inhabited = True
5602         else:
5603             new.pclass = ("M", "N", "O")[randrange(0, 3)]
5604             if withprob(0.33):
5605                 new.crystals = "present"
5606             new.known = "unknown"
5607             new.inhabited = False
5608         game.state.galaxy[w.i][w.j].planet = new
5609         game.state.planets.append(new)
5610     # Locate Romulans
5611     for i in range(game.state.nromrem):
5612         w = randplace(GALSIZE)
5613         game.state.galaxy[w.i][w.j].romulans += 1
5614     # Place the Super-Commander if needed
5615     if game.state.nscrem > 0:
5616         while True:
5617             w = randplace(GALSIZE)
5618             if welcoming(w):
5619                 break
5620         game.state.kscmdr = w
5621         game.state.galaxy[w.i][w.j].klingons += 1
5622     # Initialize times for extraneous events
5623     schedule(FSNOVA, expran(0.5 * game.intime))
5624     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5625     schedule(FSNAP, randreal(1.0, 2.0)) # Force an early snapshot
5626     schedule(FBATTAK, expran(0.3*game.intime))
5627     unschedule(FCDBAS)
5628     if game.state.nscrem:
5629         schedule(FSCMOVE, 0.2777)
5630     else:
5631         unschedule(FSCMOVE)
5632     unschedule(FSCDBAS)
5633     unschedule(FDSPROB)
5634     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5635         schedule(FDISTR, expran(1.0 + game.intime))
5636     else:
5637         unschedule(FDISTR)
5638     unschedule(FENSLV)
5639     unschedule(FREPRO)
5640     # Place thing (in tournament game, we don't want one!)
5641     # New in SST2K: never place the Thing near a starbase.
5642     # This makes sense and avoids a special case in the old code.
5643     global thing
5644     if game.tourn is None:
5645         while True:
5646             thing = randplace(GALSIZE)
5647             if thing not in game.state.baseq:
5648                 break
5649     skip(2)
5650     game.state.snap = False
5651     if game.skill == SKILL_NOVICE:
5652         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5653         prout(_("a deadly Klingon invasion force. As captain of the United"))
5654         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5655         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5656         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5657         prout(_("your mission.  As you proceed you may be given more time."))
5658         skip(1)
5659         prout(_("You will have %d supporting starbases.") % (game.inbase))
5660         proutn(_("Starbase locations-  "))
5661     else:
5662         prout(_("Stardate %d.") % int(game.state.date))
5663         skip(1)
5664         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5665         prout(_("An unknown number of Romulans."))
5666         if game.state.nscrem:
5667             prout(_("And one (GULP) Super-Commander."))
5668         prout(_("%d stardates.") % int(game.intime))
5669         proutn(_("%d starbases in ") % game.inbase)
5670     for i in range(game.inbase):
5671         proutn(`game.state.baseq[i]`)
5672         proutn("  ")
5673     skip(2)
5674     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5675     proutn(_(" Sector %s") % game.sector)
5676     skip(2)
5677     prout(_("Good Luck!"))
5678     if game.state.nscrem:
5679         prout(_("  YOU'LL NEED IT."))
5680     waitfor()
5681     newqad()
5682     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
5683         game.shldup = True
5684     if game.neutz:      # bad luck to start in a Romulan Neutral Zone
5685         attack(torps_ok=False)
5686
5687 def choose():
5688     "Choose your game type."
5689     global thing
5690     while True:
5691         game.tourn = 0
5692         game.thawed = False
5693         game.skill = SKILL_NONE
5694         game.length = 0
5695         if not scanner.inqueue: # Can start with command line options 
5696             proutn(_("Would you like a regular, tournament, or saved game? "))
5697         scanner.next()
5698         if scanner.sees("tournament"):
5699             while scanner.next() == "IHEOL":
5700                 proutn(_("Type in tournament number-"))
5701             if scanner.real == 0:
5702                 scanner.chew()
5703                 continue # We don't want a blank entry
5704             game.tourn = int(round(scanner.real))
5705             random.seed(scanner.real)
5706             if logfp:
5707                 logfp.write("# random.seed(%d)\n" % scanner.real)
5708             break
5709         if scanner.sees("saved") or scanner.sees("frozen"):
5710             if thaw():
5711                 continue
5712             scanner.chew()
5713             if game.passwd == None:
5714                 continue
5715             if not game.alldone:
5716                 game.thawed = True # No plaque if not finished
5717             report()
5718             waitfor()
5719             return True
5720         if scanner.sees("regular"):
5721             break
5722         proutn(_("What is \"%s\"?") % scanner.token)
5723         scanner.chew()
5724     while game.length==0 or game.skill==SKILL_NONE:
5725         if scanner.next() == "IHALPHA":
5726             if scanner.sees("short"):
5727                 game.length = 1
5728             elif scanner.sees("medium"):
5729                 game.length = 2
5730             elif scanner.sees("long"):
5731                 game.length = 4
5732             elif scanner.sees("novice"):
5733                 game.skill = SKILL_NOVICE
5734             elif scanner.sees("fair"):
5735                 game.skill = SKILL_FAIR
5736             elif scanner.sees("good"):
5737                 game.skill = SKILL_GOOD
5738             elif scanner.sees("expert"):
5739                 game.skill = SKILL_EXPERT
5740             elif scanner.sees("emeritus"):
5741                 game.skill = SKILL_EMERITUS
5742             else:
5743                 proutn(_("What is \""))
5744                 proutn(scanner.token)
5745                 prout("\"?")
5746         else:
5747             scanner.chew()
5748             if game.length==0:
5749                 proutn(_("Would you like a Short, Medium, or Long game? "))
5750             elif game.skill == SKILL_NONE:
5751                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5752     # Choose game options -- added by ESR for SST2K
5753     if scanner.next() != "IHALPHA":
5754         scanner.chew()
5755         proutn(_("Choose your game style (or just press enter): "))
5756         scanner.next()
5757     if scanner.sees("plain"):
5758         # Approximates the UT FORTRAN version.
5759         game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5760         game.options |= OPTION_PLAIN
5761     elif scanner.sees("almy"):
5762         # Approximates Tom Almy's version.
5763         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5764         game.options |= OPTION_ALMY
5765     elif scanner.sees("fancy") or scanner.sees("\n"):
5766         pass
5767     elif len(scanner.token):
5768         proutn(_("What is \"%s\"?") % scanner.token)
5769     setpassword()
5770     if game.passwd == "debug":
5771         idebug = True
5772         prout("=== Debug mode enabled.")
5773     # Use parameters to generate initial values of things
5774     game.damfac = 0.5 * game.skill
5775     game.inbase = randrange(BASEMIN, BASEMAX+1)
5776     game.inplan = 0
5777     if game.options & OPTION_PLANETS:
5778         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
5779     if game.options & OPTION_WORLDS:
5780         game.inplan += int(NINHAB)
5781     game.state.nromrem = game.inrom = randrange(2 *game.skill)
5782     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
5783     game.state.remtime = 7.0 * game.length
5784     game.intime = game.state.remtime
5785     game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
5786     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
5787     game.state.remres = (game.inkling+4*game.incom)*game.intime
5788     game.inresor = game.state.remres
5789     if game.inkling > 50:
5790         game.state.inbase += 1
5791     return False
5792
5793 def dropin(iquad=None):
5794     "Drop a feature on a random dot in the current quadrant."
5795     while True:
5796         w = randplace(QUADSIZE)
5797         if game.quad[w.i][w.j] == IHDOT:
5798             break
5799     if iquad is not None:
5800         game.quad[w.i][w.j] = iquad
5801     return w
5802
5803 def newcnd():
5804     "Update our alert status."
5805     game.condition = "green"
5806     if game.energy < 1000.0:
5807         game.condition = "yellow"
5808     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
5809         game.condition = "red"
5810     if not game.alive:
5811         game.condition="dead"
5812
5813 def newkling():
5814     "Drop new Klingon into current quadrant."
5815     return enemy(IHK, loc=dropin(), power=randreal(300,450)+25.0*game.skill)
5816
5817 def newqad():
5818     "Set up a new state of quadrant, for when we enter or re-enter it."
5819     game.justin = True
5820     game.iplnet = None
5821     game.neutz = game.inorbit = game.landed = False
5822     game.ientesc = game.iseenit = False
5823     # Create a blank quadrant
5824     game.quad = fill2d(QUADSIZE, lambda i, j: IHDOT)
5825     if game.iscate:
5826         # Attempt to escape Super-commander, so tbeam back!
5827         game.iscate = False
5828         game.ientesc = True
5829     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
5830     # cope with supernova
5831     if q.supernova:
5832         return
5833     game.klhere = q.klingons
5834     game.irhere = q.romulans
5835     # Position Starship
5836     game.quad[game.sector.i][game.sector.j] = game.ship
5837     game.enemies = []
5838     if q.klingons:
5839         # Position ordinary Klingons
5840         for i in range(game.klhere):
5841             newkling()
5842         # If we need a commander, promote a Klingon
5843         for cmdr in game.state.kcmdr:
5844             if cmdr == game.quadrant:
5845                 e = game.enemies[game.klhere-1]
5846                 game.quad[e.kloc.i][e.kloc.j] = IHC
5847                 e.kpower = randreal(950,1350) + 50.0*game.skill
5848                 break   
5849         # If we need a super-commander, promote a Klingon
5850         if game.quadrant == game.state.kscmdr:
5851             e = game.enemies[0]
5852             game.quad[e.kloc.i][e.kloc.j] = IHS
5853             e.kpower = randreal(1175.0,  1575.0) + 125.0*game.skill
5854             game.iscate = (game.state.remkl > 1)
5855     # Put in Romulans if needed
5856     for i in range(q.romulans):
5857         enemy(IHR, loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
5858     # If quadrant needs a starbase, put it in
5859     if q.starbase:
5860         game.base = dropin(IHB)
5861     # If quadrant needs a planet, put it in
5862     if q.planet:
5863         game.iplnet = q.planet
5864         if not q.planet.inhabited:
5865             game.plnet = dropin(IHP)
5866         else:
5867             game.plnet = dropin(IHW)
5868     # Check for condition
5869     newcnd()
5870     # Check for RNZ
5871     if game.irhere > 0 and game.klhere == 0:
5872         game.neutz = True
5873         if not damaged(DRADIO):
5874             skip(1)
5875             prout(_("LT. Uhura- \"Captain, an urgent message."))
5876             prout(_("  I'll put it on audio.\"  CLICK"))
5877             skip(1)
5878             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
5879             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
5880     # Put in THING if needed
5881     if thing == game.quadrant:
5882         enemy(type=IHQUEST, loc=dropin(),
5883                   power=randreal(6000,6500.0)+250.0*game.skill)
5884         if not damaged(DSRSENS):
5885             skip(1)
5886             prout(_("Mr. Spock- \"Captain, this is most unusual."))
5887             prout(_("    Please examine your short-range scan.\""))
5888     # Decide if quadrant needs a Tholian; lighten up if skill is low 
5889     if game.options & OPTION_THOLIAN:
5890         if (game.skill < SKILL_GOOD and withprob(0.02)) or \
5891             (game.skill == SKILL_GOOD and withprob(0.05)) or \
5892             (game.skill > SKILL_GOOD and withprob(0.08)):
5893             w = coord()
5894             while True:
5895                 w.i = withprob(0.5) * (QUADSIZE-1)
5896                 w.j = withprob(0.5) * (QUADSIZE-1)
5897                 if game.quad[w.i][w.j] == IHDOT:
5898                     break
5899             game.tholian = enemy(type=IHT, loc=w,
5900                                  power=randrange(100, 500) + 25.0*game.skill)
5901             # Reserve unoccupied corners 
5902             if game.quad[0][0]==IHDOT:
5903                 game.quad[0][0] = 'X'
5904             if game.quad[0][QUADSIZE-1]==IHDOT:
5905                 game.quad[0][QUADSIZE-1] = 'X'
5906             if game.quad[QUADSIZE-1][0]==IHDOT:
5907                 game.quad[QUADSIZE-1][0] = 'X'
5908             if game.quad[QUADSIZE-1][QUADSIZE-1]==IHDOT:
5909                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
5910     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
5911     # And finally the stars
5912     for i in range(q.stars):
5913         dropin(IHSTAR)
5914     # Put in a few black holes
5915     for i in range(1, 3+1):
5916         if withprob(0.5): 
5917             dropin(IHBLANK)
5918     # Take out X's in corners if Tholian present
5919     if game.tholian:
5920         if game.quad[0][0]=='X':
5921             game.quad[0][0] = IHDOT
5922         if game.quad[0][QUADSIZE-1]=='X':
5923             game.quad[0][QUADSIZE-1] = IHDOT
5924         if game.quad[QUADSIZE-1][0]=='X':
5925             game.quad[QUADSIZE-1][0] = IHDOT
5926         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
5927             game.quad[QUADSIZE-1][QUADSIZE-1] = IHDOT
5928
5929 def setpassword():
5930     "Set the self-destruct password."
5931     if game.options & OPTION_PLAIN:
5932         while True:
5933             scanner.chew()
5934             proutn(_("Please type in a secret password- "))
5935             scanner.next()
5936             game.passwd = scanner.token
5937             if game.passwd != None:
5938                 break
5939     else:
5940         game.passwd = ""
5941         for i in range(8):
5942             game.passwd += chr(ord('a')+randrange(26))
5943
5944 # Code from sst.c begins here
5945
5946 commands = {
5947     "SRSCAN":           OPTION_TTY,
5948     "STATUS":           OPTION_TTY,
5949     "REQUEST":          OPTION_TTY,
5950     "LRSCAN":           OPTION_TTY,
5951     "PHASERS":          0,
5952     "TORPEDO":          0,
5953     "PHOTONS":          0,
5954     "MOVE":             0,
5955     "SHIELDS":          0,
5956     "DOCK":             0,
5957     "DAMAGES":          0,
5958     "CHART":            0,
5959     "IMPULSE":          0,
5960     "REST":             0,
5961     "WARP":             0,
5962     "SCORE":            0,
5963     "SENSORS":          OPTION_PLANETS,
5964     "ORBIT":            OPTION_PLANETS,
5965     "TRANSPORT":        OPTION_PLANETS,
5966     "MINE":             OPTION_PLANETS,
5967     "CRYSTALS":         OPTION_PLANETS,
5968     "SHUTTLE":          OPTION_PLANETS,
5969     "PLANETS":          OPTION_PLANETS,
5970     "REPORT":           0,
5971     "COMPUTER":         0,
5972     "COMMANDS":         0,
5973     "EMEXIT":           0,
5974     "PROBE":            OPTION_PROBE,
5975     "SAVE":             0,
5976     "FREEZE":           0,      # Synonym for SAVE
5977     "ABANDON":          0,
5978     "DESTRUCT":         0,
5979     "DEATHRAY":         0,
5980     "DEBUG":            0,
5981     "MAYDAY":           0,
5982     "SOS":              0,      # Synonym for MAYDAY
5983     "CALL":             0,      # Synonym for MAYDAY
5984     "QUIT":             0,
5985     "HELP":             0,
5986 }
5987
5988 def listCommands():
5989     "Generate a list of legal commands."
5990     prout(_("LEGAL COMMANDS ARE:"))
5991     emitted = 0
5992     for key in commands:
5993         if not commands[key] or (commands[key] & game.options):
5994             proutn("%-12s " % key)
5995             emitted += 1
5996             if emitted % 5 == 4:
5997                 skip(1)
5998     skip(1)
5999
6000 def helpme():
6001     "Browse on-line help."
6002     key = scanner.next()
6003     while True:
6004         if key == "IHEOL":
6005             setwnd(prompt_window)
6006             proutn(_("Help on what command? "))
6007             key = scanner.next()
6008         setwnd(message_window)
6009         if key == "IHEOL":
6010             return
6011         if scanner.token in commands or scanner.token == "ABBREV":
6012             break
6013         skip(1)
6014         listCommands()
6015         key = "IHEOL"
6016         scanner.chew()
6017         skip(1)
6018     cmd = scanner.token.upper()
6019     try:
6020         fp = open(SSTDOC, "r")
6021     except IOError:
6022         try:
6023             fp = open(DOC_NAME, "r")
6024         except IOError:
6025             prout(_("Spock-  \"Captain, that information is missing from the"))
6026             proutn(_("   computer. You need to find "))
6027             proutn(DOC_NAME)
6028             prout(_(" and put it in the"))
6029             proutn(_("   current directory or to "))
6030             proutn(SSTDOC)
6031             prout(".\"")
6032             # This used to continue: "You need to find SST.DOC and put 
6033             # it in the current directory."
6034             return
6035     while True:
6036         linebuf = fp.readline()
6037         if linebuf == '':
6038             prout(_("Spock- \"Captain, there is no information on that command.\""))
6039             fp.close()
6040             return
6041         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6042             linebuf = linebuf[3:].strip()
6043             if cmd == linebuf:
6044                 break
6045     skip(1)
6046     prout(_("Spock- \"Captain, I've found the following information:\""))
6047     skip(1)
6048     while linebuf in fp:
6049         if "******" in linebuf:
6050             break
6051         proutn(linebuf)
6052     fp.close()
6053
6054 def makemoves():
6055     "Command-interpretation loop."
6056     clrscr()
6057     setwnd(message_window)
6058     while True:         # command loop 
6059         drawmaps(1)
6060         while True:     # get a command 
6061             hitme = False
6062             game.justin = False
6063             game.optime = 0.0
6064             scanner.chew()
6065             setwnd(prompt_window)
6066             clrscr()
6067             proutn("COMMAND> ")
6068             if scanner.next() == "IHEOL":
6069                 if game.options & OPTION_CURSES:
6070                     makechart()
6071                 continue
6072             elif scanner.token == "":
6073                 continue
6074             game.ididit = False
6075             clrscr()
6076             setwnd(message_window)
6077             clrscr()
6078             candidates = filter(lambda x: x.startswith(scanner.token.upper()),
6079                                 commands)
6080             if len(candidates) == 1:
6081                 cmd = candidates[0]
6082                 break
6083             elif candidates and not (game.options & OPTION_PLAIN):
6084                 prout("Commands with prefix '%s': %s" % (scanner.token, " ".join(candidates)))
6085             else:
6086                 listCommands()
6087                 continue
6088         if cmd == "SRSCAN":             # srscan
6089             srscan()
6090         elif cmd == "STATUS":           # status
6091             status()
6092         elif cmd == "REQUEST":          # status request 
6093             request()
6094         elif cmd == "LRSCAN":           # long range scan
6095             lrscan(silent=False)
6096         elif cmd == "PHASERS":          # phasers
6097             phasers()
6098             if game.ididit:
6099                 hitme = True
6100         elif cmd == "TORPEDO":          # photon torpedos
6101             photon()
6102             if game.ididit:
6103                 hitme = True
6104         elif cmd == "MOVE":             # move under warp
6105             warp(course=None, involuntary=False)
6106         elif cmd == "SHIELDS":          # shields
6107             doshield(shraise=False)
6108             if game.ididit:
6109                 hitme = True
6110                 game.shldchg = False
6111         elif cmd == "DOCK":             # dock at starbase
6112             dock(True)
6113             if game.ididit:
6114                 attack(torps_ok=False)          
6115         elif cmd == "DAMAGES":          # damage reports
6116             damagereport()
6117         elif cmd == "CHART":            # chart
6118             makechart()
6119         elif cmd == "IMPULSE":          # impulse
6120             impulse()
6121         elif cmd == "REST":             # rest
6122             wait()
6123             if game.ididit:
6124                 hitme = True
6125         elif cmd == "WARP":             # warp
6126             setwarp()
6127         elif cmd == "SCORE":            # score
6128             score()
6129         elif cmd == "SENSORS":          # sensors
6130             sensor()
6131         elif cmd == "ORBIT":            # orbit
6132             orbit()
6133             if game.ididit:
6134                 hitme = True
6135         elif cmd == "TRANSPORT":                # transport "beam"
6136             beam()
6137         elif cmd == "MINE":             # mine
6138             mine()
6139             if game.ididit:
6140                 hitme = True
6141         elif cmd == "CRYSTALS":         # crystals
6142             usecrystals()
6143             if game.ididit:
6144                 hitme = True
6145         elif cmd == "SHUTTLE":          # shuttle
6146             shuttle()
6147             if game.ididit:
6148                 hitme = True
6149         elif cmd == "PLANETS":          # Planet list
6150             survey()
6151         elif cmd == "REPORT":           # Game Report 
6152             report()
6153         elif cmd == "COMPUTER":         # use COMPUTER!
6154             eta()
6155         elif cmd == "COMMANDS":
6156             listCommands()
6157         elif cmd == "EMEXIT":           # Emergency exit
6158             clrscr()                    # Hide screen
6159             freeze(True)                # forced save
6160             raise SysExit,1                     # And quick exit
6161         elif cmd == "PROBE":
6162             probe()                     # Launch probe
6163             if game.ididit:
6164                 hitme = True
6165         elif cmd == "ABANDON":          # Abandon Ship
6166             abandon()
6167         elif cmd == "DESTRUCT":         # Self Destruct
6168             selfdestruct()
6169         elif cmd == "SAVE":             # Save Game
6170             freeze(False)
6171             clrscr()
6172             if game.skill > SKILL_GOOD:
6173                 prout(_("WARNING--Saved games produce no plaques!"))
6174         elif cmd == "DEATHRAY":         # Try a desparation measure
6175             deathray()
6176             if game.ididit:
6177                 hitme = True
6178         elif cmd == "DEBUGCMD":         # What do we want for debug???
6179             debugme()
6180         elif cmd == "MAYDAY":           # Call for help
6181             mayday()
6182             if game.ididit:
6183                 hitme = True
6184         elif cmd == "QUIT":
6185             game.alldone = True         # quit the game
6186         elif cmd == "HELP":
6187             helpme()                    # get help
6188         while True:
6189             if game.alldone:
6190                 break           # Game has ended
6191             if game.optime != 0.0:
6192                 events()
6193                 if game.alldone:
6194                     break       # Events did us in
6195             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6196                 atover(False)
6197                 continue
6198             if hitme and not game.justin:
6199                 attack(torps_ok=True)
6200                 if game.alldone:
6201                     break
6202                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6203                     atover(False)
6204                     hitme = True
6205                     continue
6206             break
6207         if game.alldone:
6208             break
6209     if idebug:
6210         prout("=== Ending")
6211
6212 def cramen(type):
6213     "Emit the name of an enemy or feature." 
6214     if   type == IHR: s = _("Romulan")
6215     elif type == IHK: s = _("Klingon")
6216     elif type == IHC: s = _("Commander")
6217     elif type == IHS: s = _("Super-commander")
6218     elif type == IHSTAR: s = _("Star")
6219     elif type == IHP: s = _("Planet")
6220     elif type == IHB: s = _("Starbase")
6221     elif type == IHBLANK: s = _("Black hole")
6222     elif type == IHT: s = _("Tholian")
6223     elif type == IHWEB: s = _("Tholian web")
6224     elif type == IHQUEST: s = _("Stranger")
6225     elif type == IHW: s = _("Inhabited World")
6226     else: s = "Unknown??"
6227     return s
6228
6229 def crmena(stars, enemy, loctype, w):
6230     "Emit the name of an enemy and his location."
6231     buf = ""
6232     if stars:
6233         buf += "***"
6234     buf += cramen(enemy) + _(" at ")
6235     if loctype == "quadrant":
6236         buf += _("Quadrant ")
6237     elif loctype == "sector":
6238         buf += _("Sector ")
6239     return buf + `w`
6240
6241 def crmshp():
6242     "Emit our ship name." 
6243     return{IHE:_("Enterprise"),IHF:_("Faerie Queene")}.get(game.ship,"Ship???")
6244
6245 def stars():
6246     "Emit a line of stars" 
6247     prouts("******************************************************")
6248     skip(1)
6249
6250 def expran(avrage):
6251     return -avrage*math.log(1e-7 + randreal())
6252
6253 def randplace(size):
6254     "Choose a random location."
6255     w = coord()
6256     w.i = randrange(size) 
6257     w.j = randrange(size)
6258     return w
6259
6260 class sstscanner:
6261     def __init__(self):
6262         self.type = None
6263         self.token = None
6264         self.real = 0.0
6265         self.inqueue = []
6266     def next(self):
6267         # Get a token from the user
6268         self.real = 0.0
6269         self.token = ''
6270         # Fill the token quue if nothing here
6271         while not self.inqueue:
6272             line = cgetline()
6273             if curwnd==prompt_window:
6274                 clrscr()
6275                 setwnd(message_window)
6276                 clrscr()
6277             if line == '':
6278                 return None
6279             if not line:
6280                 continue
6281             else:
6282                 self.inqueue = line.lstrip().split() + ["\n"]
6283         # From here on in it's all looking at the queue
6284         self.token = self.inqueue.pop(0)
6285         if self.token == "\n":
6286             self.type = "IHEOL"
6287             return "IHEOL"
6288         try:
6289             self.real = float(self.token)
6290             self.type = "IHREAL"
6291             return "IHREAL"
6292         except ValueError:
6293             pass
6294         # Treat as alpha
6295         self.token = self.token.lower()
6296         self.type = "IHALPHA"
6297         self.real = None
6298         return "IHALPHA"
6299     def append(self, tok):
6300         self.inqueue.append(tok)
6301     def push(self, tok):
6302         self.inqueue.insert(0, tok)
6303     def waiting(self):
6304         return self.inqueue
6305     def chew(self):
6306         # Demand input for next scan
6307         self.inqueue = []
6308         self.real = self.token = None
6309     def sees(self, s):
6310         # compares s to item and returns true if it matches to the length of s
6311         return s.startswith(self.token)
6312     def int(self):
6313         # Round token value to nearest integer
6314         return int(round(scanner.real))
6315     def getcoord(self):
6316         s = coord()
6317         scanner.next()
6318         if scanner.type != "IHREAL":
6319             huh()
6320             return None
6321         s.i = scanner.int()-1
6322         scanner.next()
6323         if scanner.type != "IHREAL":
6324             huh()
6325             return None
6326         s.j = scanner.int()-1
6327         return s
6328     def __repr__(str):
6329         return "<sstcanner: token=%s, type=%s, queue=%s>" % (scanner.token, scanner.type, scanner.inqueue)
6330
6331 def ja():
6332     "Yes-or-no confirmation."
6333     scanner.chew()
6334     while True:
6335         scanner.next()
6336         if scanner.token == 'y':
6337             return True
6338         if scanner.token == 'n':
6339             return False
6340         scanner.chew()
6341         proutn(_("Please answer with \"y\" or \"n\": "))
6342
6343 def huh():
6344     "Complain about unparseable input."
6345     scanner.chew()
6346     skip(1)
6347     prout(_("Beg your pardon, Captain?"))
6348
6349 def debugme():
6350     "Access to the internals for debugging."
6351     proutn("Reset levels? ")
6352     if ja() == True:
6353         if game.energy < game.inenrg:
6354             game.energy = game.inenrg
6355         game.shield = game.inshld
6356         game.torps = game.intorps
6357         game.lsupres = game.inlsr
6358     proutn("Reset damage? ")
6359     if ja() == True:
6360         for i in range(NDEVICES): 
6361             if game.damage[i] > 0.0: 
6362                 game.damage[i] = 0.0
6363     proutn("Toggle debug flag? ")
6364     if ja() == True:
6365         idebug = not idebug
6366         if idebug:
6367             prout("Debug output ON")        
6368         else:
6369             prout("Debug output OFF")
6370     proutn("Cause selective damage? ")
6371     if ja() == True:
6372         for i in range(NDEVICES):
6373             proutn("Kill %s?" % device[i])
6374             scanner.chew()
6375             key = scanner.next()
6376             if key == "IHALPHA" and scanner.sees("y"):
6377                 game.damage[i] = 10.0
6378     proutn("Examine/change events? ")
6379     if ja() == True:
6380         ev = event()
6381         w = coord()
6382         legends = {
6383             FSNOVA:  "Supernova       ",
6384             FTBEAM:  "T Beam          ",
6385             FSNAP:   "Snapshot        ",
6386             FBATTAK: "Base Attack     ",
6387             FCDBAS:  "Base Destroy    ",
6388             FSCMOVE: "SC Move         ",
6389             FSCDBAS: "SC Base Destroy ",
6390             FDSPROB: "Probe Move      ",
6391             FDISTR:  "Distress Call   ",
6392             FENSLV:  "Enslavement     ",
6393             FREPRO:  "Klingon Build   ",
6394         }
6395         for i in range(1, NEVENTS):
6396             proutn(legends[i])
6397             if is_scheduled(i):
6398                 proutn("%.2f" % (scheduled(i)-game.state.date))
6399                 if i == FENSLV or i == FREPRO:
6400                     ev = findevent(i)
6401                     proutn(" in %s" % ev.quadrant)
6402             else:
6403                 proutn("never")
6404             proutn("? ")
6405             scanner.chew()
6406             key = scanner.next()
6407             if key == 'n':
6408                 unschedule(i)
6409                 scanner.chew()
6410             elif key == "IHREAL":
6411                 ev = schedule(i, scanner.real)
6412                 if i == FENSLV or i == FREPRO:
6413                     scanner.chew()
6414                     proutn("In quadrant- ")
6415                     key = scanner.next()
6416                     # "IHEOL" says to leave coordinates as they are 
6417                     if key != "IHEOL":
6418                         if key != "IHREAL":
6419                             prout("Event %d canceled, no x coordinate." % (i))
6420                             unschedule(i)
6421                             continue
6422                         w.i = int(round(scanner.real))
6423                         key = scanner.next()
6424                         if key != "IHREAL":
6425                             prout("Event %d canceled, no y coordinate." % (i))
6426                             unschedule(i)
6427                             continue
6428                         w.j = int(round(scanner.real))
6429                         ev.quadrant = w
6430         scanner.chew()
6431     proutn("Induce supernova here? ")
6432     if ja() == True:
6433         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6434         atover(True)
6435
6436 if __name__ == '__main__':
6437     import getopt, socket
6438     try:
6439         global line, thing, game, idebug
6440         game = None
6441         thing = coord()
6442         thing.angry = False
6443         game = gamestate()
6444         idebug = 0
6445         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6446         if os.getenv("TERM"):
6447             game.options |= OPTION_CURSES
6448         else:
6449             game.options |= OPTION_TTY
6450         seed = int(time.time())
6451         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:tx")
6452         for (switch, val) in options:
6453             if switch == '-r':
6454                 try:
6455                     replayfp = open(val, "r")
6456                 except IOError:
6457                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6458                     raise SystemExit, 1
6459                 try:
6460                     line = replayfp.readline().strip()
6461                     (leader, key, seed) = line.split()
6462                     seed = eval(seed)
6463                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
6464                     line = replayfp.readline().strip()
6465                     arguments += line.split()[2:]
6466                 except ValueError:
6467                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6468                     raise SystemExit(1)
6469                 game.options |= OPTION_TTY
6470                 game.options &=~ OPTION_CURSES
6471             elif switch == '-s':
6472                 seed = int(val)
6473             elif switch == '-t':
6474                 game.options |= OPTION_TTY
6475                 game.options &=~ OPTION_CURSES
6476             elif switch == '-x':
6477                 idebug = True
6478             else:
6479                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6480                 raise SystemExit, 1
6481         # where to save the input in case of bugs
6482         try:
6483             logfp = open("/usr/tmp/sst-input.log", "w")
6484         except IOError:
6485             sys.stderr.write("sst: warning, can't open logfile\n")
6486         if logfp:
6487             logfp.write("# seed %s\n" % seed)
6488             logfp.write("# options %s\n" % " ".join(arguments))
6489             logfp.write("# recorded by %s@%s on %s\n" % \
6490                     (os.getenv("LOGNAME"),socket.gethostname(),time.ctime()))
6491         random.seed(seed)
6492         scanner = sstscanner()
6493         map(scanner.append, arguments)
6494         try:
6495             iostart()
6496             while True: # Play a game 
6497                 setwnd(fullscreen_window)
6498                 clrscr()
6499                 prelim()
6500                 setup()
6501                 if game.alldone:
6502                     score()
6503                     game.alldone = False
6504                 else:
6505                     makemoves()
6506                 skip(1)
6507                 stars()
6508                 skip(1)
6509                 if game.tourn and game.alldone:
6510                     proutn(_("Do you want your score recorded?"))
6511                     if ja() == True:
6512                         scanner.chew()
6513                         scanner.push("\n")
6514                         freeze(False)
6515                 scanner.chew()
6516                 proutn(_("Do you want to play again? "))
6517                 if not ja():
6518                     break
6519             skip(1)
6520             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6521         finally:
6522             ioend()
6523         raise SystemExit, 0
6524     except KeyboardInterrupt:
6525         if logfp:
6526             logfp.close()
6527         print ""