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