578e3f9a3f9975fdf67ea21a45aae4304eb559fd
[super-star-trek.git] / src / sst.py
1 """
2 sst.py =-- Super Star Trek in Python
3
4 """
5 import os, sys, math, curses
6
7 SSTDOC = "/usr/share/doc/sst/sst.doc"
8
9 # Stub to be replaced
10 def _(str): return str
11
12 PHASEFAC        = 2.0
13 GALSIZE         = 8
14 NINHAB          = (GALSIZE * GALSIZE / 2)
15 MAXUNINHAB      = 10
16 PLNETMAX        = (NINHAB + MAXUNINHAB)
17 QUADSIZE        = 10
18 BASEMAX         = (GALSIZE * GALSIZE / 12)
19 MAXKLGAME       = 127
20 MAXKLQUAD       = 9
21 FULLCREW        = 428   # BSD Trek was 387, that's wrong 
22 FOREVER         = 1e30
23
24 # These functions hide the difference between 0-origin and 1-origin addressing.
25 def VALID_QUADRANT(x, y):       return ((x)>=1 and (x)<=GALSIZE and (y)>=1 and (y)<=GALSIZE)
26 def VALID_SECTOR(x, y): return ((x)>=1 and (x)<=QUADSIZE and (y)>=1 and (y)<=QUADSIZE)
27
28 def square(i):          return ((i)*(i))
29 def distance(c1, c2):   return math.sqrt(square(c1.x - c2.x) + square(c1.y - c2.y))
30 def invalidate(w):      w.x = w.y = 0
31 def is_valid(w):        return (w.x != 0 and w.y != 0)
32
33 class coord:
34     def __init(self, x=None, y=None):
35         self.x = x
36         self.y = y
37     def invalidate(self):
38         self.x = self.y = None
39     def is_valid(self):
40         return self.x != None and self.y != None
41     def __eq__(self, other):
42         return self.x == other.y and self.x == other.y
43     def __add__(self, other):
44         return coord(self.x+self.x, self.y+self.y)
45     def __sub__(self, other):
46         return coord(self.x-self.x, self.y-self.y)
47     def distance(self, other):
48         return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
49     def sgn(self):
50         return coord(self.x / abs(x), self.y / abs(y));
51     def __hash__(self):
52         return hash((x, y))
53     def __str__(self):
54         return "%d - %d" % (self.x, self.y)
55
56 class planet:
57     def __init(self):
58         self.name = None        # string-valued if inhabited
59         self.w = coord()        # quadrant located
60         self.pclass = None      # could be ""M", "N", "O", or "destroyed"
61         self.crystals = None    # could be "mined", "present", "absent"
62         self.known = None       # could be "unknown", "known", "shuttle_down"
63
64 # How to represent features
65 IHR = 'R',
66 IHK = 'K',
67 IHC = 'C',
68 IHS = 'S',
69 IHSTAR = '*',
70 IHP = 'P',
71 IHW = '@',
72 IHB = 'B',
73 IHBLANK = ' ',
74 IHDOT = '.',
75 IHQUEST = '?',
76 IHE = 'E',
77 IHF = 'F',
78 IHT = 'T',
79 IHWEB = '#',
80 IHMATER0 = '-',
81 IHMATER1 = 'o',
82 IHMATER2 = '0'
83
84 NOPLANET = None
85 class quadrant:
86     def __init(self):
87         self.stars = None
88         self.planet = None
89         self.starbase = None
90         self.klingons = None
91         self.romulans = None
92         self.supernova = None
93         self.charted = None
94         self.status = None      # Could be "secure", "distressed", "enslaved"
95
96 class page:
97     def __init(self):
98         self.stars = None
99         self.starbase = None
100         self.klingons = None
101
102 class snapshot:
103     def __init(self):
104         self.snap = False       # snapshot taken
105         self.crew = None        # crew complement
106         self.remkl = None       # remaining klingons
107         self.remcom = None      # remaining commanders
108         self.nscrem = None      # remaining super commanders
109         self.rembase = None     # remaining bases
110         self.starkl = None      # destroyed stars
111         self.basekl = None      # destroyed bases
112         self.nromrem = None     # Romulans remaining
113         self.nplankl = None     # destroyed uninhabited planets
114         self.nworldkl = None    # destroyed inhabited planets
115         self.planets = []       # Planet information
116         for i in range(PLNETMAX):
117             self.planets.append(planet())
118         self.date = None        # stardate
119         self.remres = None      # remaining resources
120         self.remtime = None     # remaining time
121         self.baseq = []         # Base quadrant coordinates
122         for i in range(BASEMAX+1):
123             self.baseq.append(coord())
124         self.kcmdr = []         # Commander quadrant coordinates
125         for i in range(QUADSIZE+1):
126             self.kcmdr.append(coord())
127         self.kscmdr = coord()   # Supercommander quadrant coordinates
128         self.galaxy = []        # The Galaxy (subscript 0 not used)
129         for i in range(GALSIZE+1):
130             self.chart.append([])
131             for j in range(GALSIZE+1):
132                 self.galaxy[i].append(quadrant())
133         self.chart = []         # the starchart (subscript 0 not used)
134         for i in range(GALSIZE+1):
135             self.chart.append([])
136             for j in range(GALSIZE+1):
137                 self.chart[i].append(page())
138
139 class event:
140     def __init__(self):
141         self.date = None        # A real number
142         self.quadrant = None    # A coord structure
143
144 # game options 
145 OPTION_ALL      = 0xffffffff
146 OPTION_TTY      = 0x00000001    # old interface 
147 OPTION_CURSES   = 0x00000002    # new interface 
148 OPTION_IOMODES  = 0x00000003    # cover both interfaces 
149 OPTION_PLANETS  = 0x00000004    # planets and mining 
150 OPTION_THOLIAN  = 0x00000008    # Tholians and their webs 
151 OPTION_THINGY   = 0x00000010    # Space Thingy can shoot back 
152 OPTION_PROBE    = 0x00000020    # deep-space probes 
153 OPTION_SHOWME   = 0x00000040    # bracket Enterprise in chart 
154 OPTION_RAMMING  = 0x00000080    # enemies may ram Enterprise 
155 OPTION_MVBADDY  = 0x00000100    # more enemies can move 
156 OPTION_BLKHOLE  = 0x00000200    # black hole may timewarp you 
157 OPTION_BASE     = 0x00000400    # bases have good shields 
158 OPTION_WORLDS   = 0x00000800    # logic for inhabited worlds 
159 OPTION_PLAIN    = 0x01000000    # user chose plain game 
160 OPTION_ALMY     = 0x02000000    # user chose Almy variant 
161
162 # Define devices 
163 DSRSENS = 0
164 DLRSENS = 1
165 DPHASER = 2
166 DPHOTON = 3
167 DLIFSUP = 4
168 DWARPEN = 5
169 DIMPULS = 6
170 DSHIELD = 7
171 DRADIO  = 0
172 DSHUTTL = 9
173 DCOMPTR = 10
174 DNAVSYS = 11
175 DTRANSP = 12
176 DSHCTRL = 13
177 DDRAY   = 14
178 DDSP    = 15
179 NDEVICES= 16    # Number of devices
180
181 def damaged(dev):       return (game.damage[dev] != 0.0)
182
183 # Define future events 
184 FSPY    = 0     # Spy event happens always (no future[] entry)
185                 # can cause SC to tractor beam Enterprise
186 FSNOVA  = 1     # Supernova
187 FTBEAM  = 2     # Commander tractor beams Enterprise
188 FSNAP   = 3     # Snapshot for time warp
189 FBATTAK = 4     # Commander attacks base
190 FCDBAS  = 5     # Commander destroys base
191 FSCMOVE = 6     # Supercommander moves (might attack base)
192 FSCDBAS = 7     # Supercommander destroys base
193 FDSPROB = 8     # Move deep space probe
194 FDISTR  = 9     # Emit distress call from an inhabited world 
195 FENSLV  = 10    # Inhabited word is enslaved */
196 FREPRO  = 11    # Klingons build a ship in an enslaved system
197 NEVENTS = 12
198
199 #
200 # abstract out the event handling -- underlying data structures will change
201 # when we implement stateful events
202
203 def findevent(evtype):  return game.future[evtype]
204
205 class gamestate:
206     def __init__(self):
207         self.options = None     # Game options
208         self.state = None       # A snapshot structure
209         self.snapsht = None     # Last snapshot taken for time-travel purposes
210         self.quad = [[IHDOT * (QUADSIZE+1)] * (QUADSIZE+1)]     # contents of our quadrant
211         self.kpower = [[0 * (QUADSIZE+1)] * (QUADSIZE+1)]       # enemy energy levels
212         self.kdist = [[0 * (QUADSIZE+1)] * (QUADSIZE+1)]        # enemy distances
213         self.kavgd = [[0 * (QUADSIZE+1)] * (QUADSIZE+1)]        # average distances
214         self.damage = [0] * NDEVICES    # damage encountered
215         self.future = [0.0] * NEVENTS   # future events
216         for i in range(NEVENTS):
217             self.future.append(event())
218         self.passwd  = None;            # Self Destruct password
219         self.ks = [[None * (QUADSIZE+1)] * (QUADSIZE+1)]        # enemy sector locations
220         self.quadrant = None    # where we are in the large
221         self.sector = None      # where we are in the small
222         self.tholian = None     # coordinates of Tholian
223         self.base = None        # position of base in current quadrant
224         self.battle = None      # base coordinates being attacked
225         self.plnet = None       # location of planet in quadrant
226         self.probec = None      # current probe quadrant
227         self.gamewon = False    # Finished!
228         self.ididit = False     # action taken -- allows enemy to attack
229         self.alive = False      # we are alive (not killed)
230         self.justin = False     # just entered quadrant
231         self.shldup = False     # shields are up
232         self.shldchg = False    # shield is changing (affects efficiency)
233         self.comhere = False    # commander here
234         self.ishere = False     # super-commander in quadrant
235         self.iscate = False     # super commander is here
236         self.ientesc = False    # attempted escape from supercommander
237         self.ithere = False     # Tholian is here 
238         self.resting = False    # rest time
239         self.icraft = False     # Kirk in Galileo
240         self.landed = False     # party on planet (true), on ship (false)
241         self.alldone = False    # game is now finished
242         self.neutz = False      # Romulan Neutral Zone
243         self.isarmed = False    # probe is armed
244         self.inorbit = False    # orbiting a planet
245         self.imine = False      # mining
246         self.icrystl = False    # dilithium crystals aboard
247         self.iseenit = False    # seen base attack report
248         self.thawed = False     # thawed game
249         self.condition = None   # "green", "yellow", "red", "docked", "dead"
250         self.iscraft = None     # "onship", "offship", "removed"
251         self.skill = None       # Player skill level
252         self.inkling = 0        # initial number of klingons
253         self.inbase = 0         # initial number of bases
254         self.incom = 0          # initial number of commanders
255         self.inscom = 0         # initial number of commanders
256         self.inrom = 0          # initial number of commanders
257         self.instar = 0         # initial stars
258         self.intorps = 0        # initial/max torpedoes
259         self.torps = 0          # number of torpedoes
260         self.ship = 0           # ship type -- 'E' is Enterprise
261         self.abandoned = 0      # count of crew abandoned in space
262         self.length = 0         # length of game
263         self.klhere = 0         # klingons here
264         self.casual = 0         # causalties
265         self.nhelp = 0          # calls for help
266         self.nkinks = 0         # count of energy-barrier crossings
267         self.iplnet = 0         # planet # in quadrant
268         self.inplan = 0         # initial planets
269         self.nenhere = 0        # number of enemies in quadrant
270         self.irhere = 0         # Romulans in quadrant
271         self.isatb = 0          # =1 if super commander is attacking base
272         self.tourn = 0          # tournament number
273         self.proben = 0         # number of moves for probe
274         self.nprobes = 0        # number of probes available
275         self.inresor = 0.0      # initial resources
276         self.intime = 0.0       # initial time
277         self.inenrg = 0.0       # initial/max energy
278         self.inshld = 0.0       # initial/max shield
279         self.inlsr = 0.0        # initial life support resources
280         self.indate = 0.0       # initial date
281         self.energy = 0.0       # energy level
282         self.shield = 0.0       # shield level
283         self.warpfac = 0.0      # warp speed
284         self.wfacsq = 0.0       # squared warp factor
285         self.lsupres = 0.0      # life support reserves
286         self.dist = 0.0         # movement distance
287         self.direc = 0.0        # movement direction
288         self.optime = 0.0       # time taken by current operation
289         self.docfac = 0.0       # repair factor when docking (constant?)
290         self.damfac = 0.0       # damage factor
291         self.lastchart = 0.0    # time star chart was last updated
292         self.cryprob = 0.0      # probability that crystal will work
293         self.probex = 0.0       # location of probe
294         self.probey = 0.0       #
295         self.probeinx = 0.0     # probe x,y increment
296         self.probeiny = 0.0     #
297         self.height = 0.0       # height of orbit around planet
298
299 # From enumerated type 'feature'
300 IHR = 'R'
301 IHK = 'K'
302 IHC = 'C'
303 IHS = 'S'
304 IHSTAR = '*'
305 IHP = 'P'
306 IHW = '@'
307 IHB = 'B'
308 IHBLANK = ' '
309 IHDOT = '.'
310 IHQUEST = '?'
311 IHE = 'E'
312 IHF = 'F'
313 IHT = 'T'
314 IHWEB = '#'
315 IHMATER0 = '-'
316 IHMATER1 = 'o'
317 IHMATER2 = '0'
318
319
320 # From enumerated type 'FINTYPE'
321 FWON = 0
322 FDEPLETE = 1
323 FLIFESUP = 2
324 FNRG = 3
325 FBATTLE = 4
326 FNEG3 = 5
327 FNOVA = 6
328 FSNOVAED = 7
329 FABANDN = 8
330 FDILITHIUM = 9
331 FMATERIALIZE = 10
332 FPHASER = 11
333 FLOST = 12
334 FMINING = 13
335 FDPLANET = 14
336 FPNOVA = 15
337 FSSC = 16
338 FSTRACTOR = 17
339 FDRAY = 18
340 FTRIBBLE = 19
341 FHOLE = 20
342 FCREW = 21
343
344 # From enumerated type 'COLORS'
345 DEFAULT = 0
346 BLACK = 1
347 BLUE = 2
348 GREEN = 3
349 CYAN = 4
350 RED = 5
351 MAGENTA = 6
352 BROWN = 7
353 LIGHTGRAY = 8
354 DARKGRAY = 9
355 LIGHTBLUE = 10
356 LIGHTGREEN = 11
357 LIGHTCYAN = 12
358 LIGHTRED = 13
359 LIGHTMAGENTA = 14
360 YELLOW = 15
361 WHITE = 16
362
363 # Code from ai.c begins here
364
365 def tryexit(look, ienm, loccom, irun):
366     # a bad guy attempts to bug out 
367     iq = coord()
368     iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1
369     iq.y = game.quadrant.y+(look.y+(QUADSIZE-1))/QUADSIZE - 1
370     if not VALID_QUADRANT(iq.x,iq.y) or \
371         game.state.galaxy[iq.x][iq.y].supernova or \
372         game.state.galaxy[iq.x][iq.y].klingons > MAXKLQUAD-1:
373         return False; # no can do -- neg energy, supernovae, or >MAXKLQUAD-1 Klingons 
374     if ienm == IHR:
375         return False; # Romulans cannot escape! 
376     if not irun:
377         # avoid intruding on another commander's territory 
378         if ienm == IHC:
379             for n in range(1, game.state.remcom+1):
380                 if same(game.state.kcmdr[n],iq):
381                     return False
382             # refuse to leave if currently attacking starbase 
383             if same(game.battle, game.quadrant):
384                 return False
385         # don't leave if over 1000 units of energy 
386         if game.kpower[loccom] > 1000.0:
387             return False
388     # print escape message and move out of quadrant.
389     # we know this if either short or long range sensors are working
390     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
391         game.condition == docked:
392         crmena(True, ienm, "sector", game.ks[loccom])
393         prout(_(" escapes to Quadrant %s (and regains strength).") % q)
394     # handle local matters related to escape 
395     game.quad[game.ks[loccom].x][game.ks[loccom].y] = IHDOT
396     game.ks[loccom] = game.ks[game.nenhere]
397     game.kavgd[loccom] = game.kavgd[game.nenhere]
398     game.kpower[loccom] = game.kpower[game.nenhere]
399     game.kdist[loccom] = game.kdist[game.nenhere]
400     game.klhere -= 1
401     game.nenhere -= 1
402     if game.condition != docked:
403         newcnd()
404     # Handle global matters related to escape 
405     game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons -= 1
406     game.state.galaxy[iq.x][iq.y].klingons += 1
407     if ienm==IHS:
408         game.ishere = False
409         game.iscate = False
410         game.ientesc = False
411         game.isatb = 0
412         schedule(FSCMOVE, 0.2777)
413         unschedule(FSCDBAS)
414         game.state.kscmdr=iq
415     else:
416         for n in range(1, game.state.remcom+1):
417             if same(game.state.kcmdr[n], game.quadrant):
418                 game.state.kcmdr[n]=iq
419                 break
420         game.comhere = False
421     return True; # success 
422
423 #
424 # The bad-guy movement algorithm:
425
426 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
427 # If both are operating full strength, force is 1000. If both are damaged,
428 # force is -1000. Having shields down subtracts an additional 1000.
429
430 # 2. Enemy has forces equal to the energy of the attacker plus
431 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
432 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
433
434 # Attacker Initial energy levels (nominal):
435 # Klingon   Romulan   Commander   Super-Commander
436 # Novice    400        700        1200        
437 # Fair      425        750        1250
438 # Good      450        800        1300        1750
439 # Expert    475        850        1350        1875
440 # Emeritus  500        900        1400        2000
441 # VARIANCE   75        200         200         200
442
443 # Enemy vessels only move prior to their attack. In Novice - Good games
444 # only commanders move. In Expert games, all enemy vessels move if there
445 # is a commander present. In Emeritus games all enemy vessels move.
446
447 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
448 # forces are 1000 greater than Enterprise.
449
450 # Agressive action on average cuts the distance between the ship and
451 # the enemy to 1/4 the original.
452
453 # 4.  At lower energy advantage, movement units are proportional to the
454 # advantage with a 650 advantage being to hold ground, 800 to move forward
455 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
456
457 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
458 # retreat, especially at high skill levels.
459
460 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
461
462
463 def movebaddy(com, loccom, ienm):
464     # tactical movement for the bad guys 
465     next = coord(); look = coord()
466     irun = False
467     # This should probably be just game.comhere + game.ishere 
468     if game.skill >= SKILL_EXPERT:
469         nbaddys = ((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0)
470     else:
471         nbaddys = game.comhere + game.ishere
472
473     dist1 = game.kdist[loccom]
474     mdist = int(dist1 + 0.5); # Nearest integer distance 
475
476     # If SC, check with spy to see if should hi-tail it 
477     if ienm==IHS and \
478         (game.kpower[loccom] <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
479         irun = True
480         motion = -QUADSIZE
481     else:
482         # decide whether to advance, retreat, or hold position 
483         forces = game.kpower[loccom]+100.0*game.nenhere+400*(nbaddys-1)
484         if not game.shldup:
485             forces += 1000; # Good for enemy if shield is down! 
486         if not damaged(DPHASER) or not damaged(DPHOTON):
487             if damaged(DPHASER): # phasers damaged 
488                 forces += 300.0
489             else:
490                 forces -= 0.2*(game.energy - 2500.0)
491             if damaged(DPHOTON): # photon torpedoes damaged 
492                 forces += 300.0
493             else:
494                 forces -= 50.0*game.torps
495         else:
496             # phasers and photon tubes both out! 
497             forces += 1000.0
498         motion = 0
499         if forces <= 1000.0 and game.condition != "docked": # Typical situation 
500             motion = ((forces+200.0*Rand())/150.0) - 5.0
501         else:
502             if forces > 1000.0: # Very strong -- move in for kill 
503                 motion = (1.0-square(Rand()))*dist1 + 1.0
504             if game.condition=="docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
505                 motion -= game.skill*(2.0-square(Rand()))
506         if idebug:
507             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
508         # don't move if no motion 
509         if motion==0:
510             return
511         # Limit motion according to skill 
512         if abs(motion) > game.skill:
513             if motion < 0:
514                 motion = -game.skill
515             else:
516                 motion = game.skill
517     # calculate preferred number of steps 
518     if motion < 0:
519         msteps = -motion
520     else:
521         msteps = motion
522     if motion > 0 and nsteps > mdist:
523         nsteps = mdist; # don't overshoot 
524     if nsteps > QUADSIZE:
525         nsteps = QUADSIZE; # This shouldn't be necessary 
526     if nsteps < 1:
527         nsteps = 1; # This shouldn't be necessary 
528     if idebug:
529         proutn("NSTEPS = %d:" % nsteps)
530     # Compute preferred values of delta X and Y 
531     mx = game.sector.x - com.x
532     my = game.sector.y - com.y
533     if 2.0 * abs(mx) < abs(my):
534         mx = 0
535     if 2.0 * abs(my) < abs(game.sector.x-com.x):
536         my = 0
537     if mx != 0:
538         if mx*motion < 0:
539             mx = -1
540         else:
541             mx = 1
542     if my != 0:
543         if my*motion < 0:
544             my = -1
545         else:
546             my = 1
547     next = com
548     # main move loop 
549     for ll in range(nsteps):
550         if idebug:
551             proutn(" %d" % (ll+1))
552         # Check if preferred position available 
553         look.x = next.x + mx
554         look.y = next.y + my
555         if mx < 0:
556             krawlx = 1
557         else:
558             krawlx = -1
559         if my < 0:
560             krawly = 1
561         else:
562             krawly = -1
563         success = False
564         attempts = 0; # Settle mysterious hang problem 
565         while attempts < 20 and not success:
566             attempts += 1
567             if look.x < 1 or look.x > QUADSIZE:
568                 if motion < 0 and tryexit(look, ienm, loccom, irun):
569                     return
570                 if krawlx == mx or my == 0:
571                     break
572                 look.x = next.x + krawlx
573                 krawlx = -krawlx
574             elif look.y < 1 or look.y > QUADSIZE:
575                 if motion < 0 and tryexit(look, ienm, loccom, irun):
576                     return
577                 if krawly == my or mx == 0:
578                     break
579                 look.y = next.y + krawly
580                 krawly = -krawly
581             elif (game.options & OPTION_RAMMING) and game.quad[look.x][look.y] != IHDOT:
582                 # See if we should ram ship 
583                 if game.quad[look.x][look.y] == game.ship and \
584                     (ienm == IHC or ienm == IHS):
585                     ram(True, ienm, com)
586                     return
587                 if krawlx != mx and my != 0:
588                     look.x = next.x + krawlx
589                     krawlx = -krawlx
590                 elif krawly != my and mx != 0:
591                     look.y = next.y + krawly
592                     krawly = -krawly
593                 else:
594                     break; # we have failed 
595             else:
596                 success = True
597         if success:
598             next = look
599             if idebug:
600                 proutn(`next`)
601         else:
602             break; # done early 
603         
604     if idebug:
605         skip(1)
606     # Put commander in place within same quadrant 
607     game.quad[com.x][com.y] = IHDOT
608     game.quad[next.x][next.y] = ienm
609     if not same(next, com):
610         # it moved 
611         game.ks[loccom] = next
612         game.kdist[loccom] = game.kavgd[loccom] = distance(game.sector, next)
613         if not damaged(DSRSENS) or game.condition == docked:
614             proutn("***")
615             cramen(ienm)
616             proutn(_(" from Sector %s") % com)
617             if game.kdist[loccom] < dist1:
618                 proutn(_(" advances to "))
619             else:
620                 proutn(_(" retreats to "))
621             prout("Sector %s." % next)
622
623 def moveklings():
624     # Klingon tactical movement 
625     if idebug:
626         prout("== MOVCOM")
627     # Figure out which Klingon is the commander (or Supercommander)
628     # and do move
629     if game.comhere:
630         for i in range(1, game.nenhere+1):
631             w = game.ks[i]
632             if game.quad[w.x][w.y] == IHC:
633                 movebaddy(w, i, IHC)
634                 break
635     if game.ishere:
636         for i in range(1, game.nenhere+1):
637             w = game.ks[i]
638             if game.quad[w.x][w.y] == IHS:
639                 movebaddy(w, i, IHS)
640                 break
641     # If skill level is high, move other Klingons and Romulans too!
642     # Move these last so they can base their actions on what the
643     # commander(s) do.
644     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
645         for i in range(1, game.nenhere+1):
646             w = game.ks[i]
647             if game.quad[w.x][w.y] == IHK or game.quad[w.x][w.y] == IHR:
648                 movebaddy(w, i, game.quad[w.x][w.y])
649     sortklings();
650
651 def movescom(iq, avoid):
652     # commander movement helper 
653     if same(iq, game.quadrant) or not VALID_QUADRANT(iq.x, iq.y) or \
654         game.state.galaxy[iq.x][iq.y].supernova or \
655         game.state.galaxy[iq.x][iq.y].klingons > MAXKLQUAD-1:
656         return 1
657     if avoid:
658         # Avoid quadrants with bases if we want to avoid Enterprise 
659         for i in range(1, game.state.rembase+1):
660             if same(game.state.baseq[i], iq):
661                 return True
662     if game.justin and not game.iscate:
663         return True
664     # do the move 
665     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons -= 1
666     game.state.kscmdr = iq
667     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons += 1
668     if game.ishere:
669         # SC has scooted, Remove him from current quadrant 
670         game.iscate=False
671         game.isatb=0
672         game.ishere = False
673         game.ientesc = False
674         unschedule(FSCDBAS)
675         for i in range(1, game.nenhere+1):
676             if game.quad[game.ks[i].x][game.ks[i].y] == IHS:
677                 break
678         game.quad[game.ks[i].x][game.ks[i].y] = IHDOT
679         game.ks[i] = game.ks[game.nenhere]
680         game.kdist[i] = game.kdist[game.nenhere]
681         game.kavgd[i] = game.kavgd[game.nenhere]
682         game.kpower[i] = game.kpower[game.nenhere]
683         game.klhere -= 1
684         game.nenhere -= 1
685         if game.condition!=docked:
686             newcnd()
687         sortklings()
688     # check for a helpful planet 
689     for i in range(game.inplan):
690         if same(game.state.planets[i].w, game.state.kscmdr) and \
691             game.state.planets[i].crystals == present:
692             # destroy the planet 
693             game.state.planets[i].pclass = destroyed
694             game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].planet = NOPLANET
695             if not damaged(DRADIO) or game.condition == docked:
696                 announce()
697                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
698                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
699                 prout(_("   by the Super-commander.\""))
700             break
701     return False; # looks good! 
702                         
703 def supercommander():
704     # move the Super Commander 
705     iq = coord(); sc = coord(); ibq = coord(); idelta = coord()
706     basetbl = []
707     if idebug:
708         prout("== SUPERCOMMANDER")
709     # Decide on being active or passive 
710     avoid = ((game.incom - game.state.remcom + game.inkling - game.state.remkl)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
711             (game.state.date-game.indate) < 3.0)
712     if not game.iscate and avoid:
713         # compute move away from Enterprise 
714         idelta = game.state.kscmdr-game.quadrant
715         if math.sqrt(idelta.x*idelta.x+idelta.y*idelta.y) > 2.0:
716             # circulate in space 
717             idelta.x = game.state.kscmdr.y-game.quadrant.y
718             idelta.y = game.quadrant.x-game.state.kscmdr.x
719     else:
720         # compute distances to starbases 
721         if game.state.rembase <= 0:
722             # nothing left to do 
723             unschedule(FSCMOVE)
724             return
725         sc = game.state.kscmdr
726         for i in range(1, game.state.rembase+1):
727             basetbl.append((i, distance(game.state.baseq[i], sc)))
728         if game.state.rembase > 1:
729             basetbl.sort(lambda x, y: cmp(x[1]. y[1]))
730         # look for nearest base without a commander, no Enterprise, and
731         # without too many Klingons, and not already under attack. 
732         ifindit = iwhichb = 0
733         for i2 in range(1, game.state.rembase+1):
734             i = basetbl[i2][0]; # bug in original had it not finding nearest
735             ibq = game.state.baseq[i]
736             if same(ibq, game.quadrant) or same(ibq, game.battle) or \
737                 game.state.galaxy[ibq.x][ibq.y].supernova or \
738                 game.state.galaxy[ibq.x][ibq.y].klingons > MAXKLQUAD-1:
739                 continue
740             # if there is a commander, and no other base is appropriate,
741             #   we will take the one with the commander
742             for j in range(1, game.state.remcom+1):
743                 if same(ibq, game.state.kcmdr[j]) and ifindit!= 2:
744                     ifindit = 2
745                     iwhichb = i
746                     break
747             if j > game.state.remcom: # no commander -- use this one 
748                 ifindit = 1
749                 iwhichb = i
750                 break
751         if ifindit==0:
752             return; # Nothing suitable -- wait until next time
753         ibq = game.state.baseq[iwhichb]
754         # decide how to move toward base 
755         idelta = ibq - game.state.kscmdr
756     # Maximum movement is 1 quadrant in either or both axes 
757     idelta = idelta.sgn()
758     # try moving in both x and y directions
759     # there was what looked like a bug in the Almy C code here,
760     # but it might be this translation is just wrong.
761     iq = game.state.kscmdr + idelta
762     if movescom(iq, avoid):
763         # failed -- try some other maneuvers 
764         if idelta.x==0 or idelta.y==0:
765             # attempt angle move 
766             if idelta.x != 0:
767                 iq.y = game.state.kscmdr.y + 1
768                 if movescom(iq, avoid):
769                     iq.y = game.state.kscmdr.y - 1
770                     movescom(iq, avoid)
771             else:
772                 iq.x = game.state.kscmdr.x + 1
773                 if movescom(iq, avoid):
774                     iq.x = game.state.kscmdr.x - 1
775                     movescom(iq, avoid)
776         else:
777             # try moving just in x or y 
778             iq.y = game.state.kscmdr.y
779             if movescom(iq, avoid):
780                 iq.y = game.state.kscmdr.y + idelta.y
781                 iq.x = game.state.kscmdr.x
782                 movescom(iq, avoid)
783     # check for a base 
784     if game.state.rembase == 0:
785         unschedule(FSCMOVE)
786     else:
787         for i in range(1, game.state.rembase+1):
788             ibq = game.state.baseq[i]
789             if same(ibq, game.state.kscmdr) and same(game.state.kscmdr, game.battle):
790                 # attack the base 
791                 if avoid:
792                     return; # no, don't attack base! 
793                 game.iseenit = False
794                 game.isatb = 1
795                 schedule(FSCDBAS, 1.0 +2.0*Rand())
796                 if is_scheduled(FCDBAS):
797                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
798                 if damaged(DRADIO) and game.condition != docked:
799                     return; # no warning 
800                 game.iseenit = True
801                 announce()
802                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
803                       % game.state.kscmdr)
804                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
805                 proutn(_("   It can survive until stardate %d.\"") \
806                        % int(scheduled(FSCDBAS)))
807                 if not game.resting:
808                     return
809                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
810                 if ja() == False:
811                     return
812                 game.resting = False
813                 game.optime = 0.0; # actually finished 
814                 return
815     # Check for intelligence report 
816     if not idebug and \
817         (Rand() > 0.2 or \
818          (damaged(DRADIO) and game.condition != docked) or \
819          not game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].charted):
820         return
821     announce()
822     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
823     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
824     return;
825
826 def movetholian():
827     # move the Tholian 
828     if not game.ithere or game.justin:
829         return
830
831     if game.tholian.x == 1 and game.tholian.y == 1:
832         idx = 1; idy = QUADSIZE
833     elif game.tholian.x == 1 and game.tholian.y == QUADSIZE:
834         idx = QUADSIZE; idy = QUADSIZE
835     elif game.tholian.x == QUADSIZE and game.tholian.y == QUADSIZE:
836         idx = QUADSIZE; idy = 1
837     elif game.tholian.x == QUADSIZE and game.tholian.y == 1:
838         idx = 1; idy = 1
839     else:
840         # something is wrong! 
841         game.ithere = False
842         return
843
844     # do nothing if we are blocked 
845     if game.quad[idx][idy]!= IHDOT and game.quad[idx][idy]!= IHWEB:
846         return
847     game.quad[game.tholian.x][game.tholian.y] = IHWEB
848
849     if game.tholian.x != idx:
850         # move in x axis 
851         im = math.fabs(idx - game.tholian.x)*1.0/(idx - game.tholian.x)
852         while game.tholian.x != idx:
853             game.tholian.x += im
854             if game.quad[game.tholian.x][game.tholian.y]==IHDOT:
855                 game.quad[game.tholian.x][game.tholian.y] = IHWEB
856     elif game.tholian.y != idy:
857         # move in y axis 
858         im = math.fabs(idy - game.tholian.y)*1.0/(idy - game.tholian.y)
859         while game.tholian.y != idy:
860             game.tholian.y += im
861             if game.quad[game.tholian.x][game.tholian.y]==IHDOT:
862                 game.quad[game.tholian.x][game.tholian.y] = IHWEB
863     game.quad[game.tholian.x][game.tholian.y] = IHT
864     game.ks[game.nenhere] = game.tholian
865
866     # check to see if all holes plugged 
867     for i in range(1, QUADSIZE+1):
868         if game.quad[1][i]!=IHWEB and game.quad[1][i]!=IHT:
869             return
870         if game.quad[QUADSIZE][i]!=IHWEB and game.quad[QUADSIZE][i]!=IHT:
871             return
872         if game.quad[i][1]!=IHWEB and game.quad[i][1]!=IHT:
873             return
874         if game.quad[i][QUADSIZE]!=IHWEB and game.quad[i][QUADSIZE]!=IHT:
875             return
876     # All plugged up -- Tholian splits 
877     game.quad[game.tholian.x][game.tholian.y]=IHWEB
878     dropin(IHBLANK)
879     crmena(True, IHT, "sector", game.tholian)
880     prout(_(" completes web."))
881     game.ithere = False
882     game.nenhere -= 1
883     return
884
885 # Code from battle.c begins here
886
887 def doshield(shraise):
888     # change shield status 
889     action = "NONE"
890     game.ididit = False
891     if shraise:
892         action = "SHUP"
893     else:
894         key = scan()
895         if key == IHALPHA:
896             if isit("transfer"):
897                 action = "NRG"
898             else:
899                 chew()
900                 if damaged(DSHIELD):
901                     prout(_("Shields damaged and down."))
902                     return
903                 if isit("up"):
904                     action = "SHUP"
905                 elif isit("down"):
906                     action = "SHDN"
907         if action=="NONE":
908             proutn(_("Do you wish to change shield energy? "))
909             if ja() == True:
910                 proutn(_("Energy to transfer to shields- "))
911                 action = "NRG"
912             elif damaged(DSHIELD):
913                 prout(_("Shields damaged and down."))
914                 return
915             elif game.shldup:
916                 proutn(_("Shields are up. Do you want them down? "))
917                 if ja() == True:
918                     action = "SHDN"
919                 else:
920                     chew()
921                     return
922             else:
923                 proutn(_("Shields are down. Do you want them up? "))
924                 if ja() == True:
925                     action = "SHUP"
926                 else:
927                     chew()
928                     return    
929     if action == "SHUP": # raise shields 
930         if game.shldup:
931             prout(_("Shields already up."))
932             return
933         game.shldup = True
934         game.shldchg = True
935         if game.condition != "docked":
936             game.energy -= 50.0
937         prout(_("Shields raised."))
938         if game.energy <= 0:
939             skip(1)
940             prout(_("Shields raising uses up last of energy."))
941             finish(FNRG)
942             return
943         game.ididit=True
944         return
945     elif action == "SHDN":
946         if not game.shldup:
947             prout(_("Shields already down."))
948             return
949         game.shldup=False
950         game.shldchg=True
951         prout(_("Shields lowered."))
952         game.ididit = True
953         return
954     elif action == "NRG":
955         while scan() != IHREAL:
956             chew()
957             proutn(_("Energy to transfer to shields- "))
958         chew()
959         if aaitem==0:
960             return
961         if aaitem > game.energy:
962             prout(_("Insufficient ship energy."))
963             return
964         game.ididit = True
965         if game.shield+aaitem >= game.inshld:
966             prout(_("Shield energy maximized."))
967             if game.shield+aaitem > game.inshld:
968                 prout(_("Excess energy requested returned to ship energy"))
969             game.energy -= game.inshld-game.shield
970             game.shield = game.inshld
971             return
972         if aaitem < 0.0 and game.energy-aaitem > game.inenrg:
973             # Prevent shield drain loophole 
974             skip(1)
975             prout(_("Engineering to bridge--"))
976             prout(_("  Scott here. Power circuit problem, Captain."))
977             prout(_("  I can't drain the shields."))
978             game.ididit = False
979             return
980         if game.shield+aaitem < 0:
981             prout(_("All shield energy transferred to ship."))
982             game.energy += game.shield
983             game.shield = 0.0
984             return
985         proutn(_("Scotty- \""))
986         if aaitem > 0:
987             prout(_("Transferring energy to shields.\""))
988         else:
989             prout(_("Draining energy from shields.\""))
990         game.shield += aaitem
991         game.energy -= aaitem
992         return
993
994 def randdevice():
995     # choose a device to damage, at random. 
996     #
997     # Quoth Eric Allman in the code of BSD-Trek:
998     # "Under certain conditions you can get a critical hit.  This
999     # sort of hit damages devices.  The probability that a given
1000     # device is damaged depends on the device.  Well protected
1001     # devices (such as the computer, which is in the core of the
1002     # ship and has considerable redundancy) almost never get
1003     # damaged, whereas devices which are exposed (such as the
1004     # warp engines) or which are particularly delicate (such as
1005     # the transporter) have a much higher probability of being
1006     # damaged."
1007     # 
1008     # This is one place where OPTION_PLAIN does not restore the
1009     # original behavior, which was equiprobable damage across
1010     # all devices.  If we wanted that, we'd return NDEVICES*Rand()
1011     # and have done with it.  Also, in the original game, DNAVYS
1012     # and DCOMPTR were the same device. 
1013     # 
1014     # Instead, we use a table of weights similar to the one from BSD Trek.
1015     # BSD doesn't have the shuttle, shield controller, death ray, or probes. 
1016     # We don't have a cloaking device.  The shuttle got the allocation
1017     # for the cloaking device, then we shaved a half-percent off
1018     # everything to have some weight to give DSHCTRL/DDRAY/DDSP.
1019     # 
1020     weights = (
1021         105,    # DSRSENS: short range scanners 10.5% 
1022         105,    # DLRSENS: long range scanners          10.5% 
1023         120,    # DPHASER: phasers                      12.0% 
1024         120,    # DPHOTON: photon torpedoes             12.0% 
1025         25,     # DLIFSUP: life support          2.5% 
1026         65,     # DWARPEN: warp drive                    6.5% 
1027         70,     # DIMPULS: impulse engines               6.5% 
1028         145,    # DSHIELD: deflector shields            14.5% 
1029         30,     # DRADIO:  subspace radio                3.0% 
1030         45,     # DSHUTTL: shuttle                       4.5% 
1031         15,     # DCOMPTR: computer                      1.5% 
1032         20,     # NAVCOMP: navigation system             2.0% 
1033         75,     # DTRANSP: transporter                   7.5% 
1034         20,     # DSHCTRL: high-speed shield controller 2.0% 
1035         10,     # DDRAY: death ray                       1.0% 
1036         30,     # DDSP: deep-space probes                3.0% 
1037     )
1038     idx = Rand() * 1000.0       # weights must sum to 1000 
1039     sum = 0
1040     for (i, w) in enumerate(weights):
1041         sum += w
1042         if idx < sum:
1043             return i
1044     return None;        # we should never get here
1045
1046 def ram(ibumpd, ienm, w):
1047     # make our ship ram something 
1048     prouts(_("***RED ALERT!  RED ALERT!"))
1049     skip(1)
1050     prout(_("***COLLISION IMMINENT."))
1051     skip(2)
1052     proutn("***")
1053     crmshp()
1054     hardness = {IHR:1.5, IHC:2.0, IHS:2.5, IHT:0.5, IHQUEST:4.0}.get(ienm, 1.0)
1055     if ibumpd:
1056         proutn(_(" rammed by "))
1057     else:
1058         proutn(_(" rams "))
1059     crmena(False, ienm, sector, w)
1060     if ibumpd:
1061         proutn(_(" (original position)"))
1062     skip(1)
1063     deadkl(w, ienm, game.sector)
1064     proutn("***")
1065     crmshp()
1066     prout(_(" heavily damaged."))
1067     icas = 10.0+20.0*Rand()
1068     prout(_("***Sickbay reports %d casualties"), icas)
1069     game.casual += icas
1070     game.state.crew -= icas
1071     #
1072     # In the pre-SST2K version, all devices got equiprobably damaged,
1073     # which was silly.  Instead, pick up to half the devices at
1074     # random according to our weighting table,
1075     # 
1076     ncrits = Rand() * (NDEVICES/2)
1077     for m in range(ncrits):
1078         dev = randdevice()
1079         if game.damage[dev] < 0:
1080             continue
1081         extradm = (10.0*hardness*Rand()+1.0)*game.damfac
1082         # Damage for at least time of travel! 
1083         game.damage[dev] += game.optime + extradm
1084     game.shldup = False
1085     prout(_("***Shields are down."))
1086     if game.state.remkl + game.state.remcom + game.state.nscrem:
1087         announce()
1088         damagereport()
1089     else:
1090         finish(FWON)
1091     return;
1092
1093 def torpedo(course, r, incoming, i, n):
1094     # let a photon torpedo fly 
1095     iquad = 0
1096     shoved = False
1097     ac = course + 0.25*r
1098     angle = (15.0-ac)*0.5235988
1099     bullseye = (15.0 - course)*0.5235988
1100     deltax = -math.sin(angle);
1101     deltay = math.cos(angle);
1102     x = incoming.x; y = incoming.y
1103     w = coord(); jw = coord()
1104     w.x = w.y = jw.x = jw.y = 0
1105     bigger = max(math.fabs(deltax), math.fabs(deltay))
1106     deltax /= bigger
1107     deltay /= bigger
1108     if not damaged(DSRSENS) or game.condition=="docked":
1109         setwnd(srscan_window)
1110     else: 
1111         setwnd(message_window)
1112     # Loop to move a single torpedo 
1113     for l in range(1, 15+1):
1114         x += deltax
1115         w.x = x + 0.5
1116         y += deltay
1117         w.y = y + 0.5
1118         if not VALID_SECTOR(w.x, w.y):
1119             break
1120         iquad=game.quad[w.x][w.y]
1121         tracktorpedo(w, l, i, n, iquad)
1122         if iquad==IHDOT:
1123             continue
1124         # hit something 
1125         setwnd(message_window)
1126         if damaged(DSRSENS) and not game.condition=="docked":
1127             skip(1);    # start new line after text track 
1128         if iquad in (IHE, IHF): # Hit our ship 
1129             skip(1)
1130             proutn(_("Torpedo hits "))
1131             crmshp()
1132             prout(".")
1133             hit = 700.0 + 100.0*Rand() - \
1134                 1000.0 * distance(w, incoming) * math.fabs(math.sin(bullseye-angle))
1135             newcnd(); # we're blown out of dock 
1136             # We may be displaced. 
1137             if game.landed or game.condition=="docked":
1138                 return hit # Cheat if on a planet 
1139             ang = angle + 2.5*(Rand()-0.5)
1140             temp = math.fabs(math.sin(ang))
1141             if math.fabs(math.cos(ang)) > temp:
1142                 temp = math.fabs(math.cos(ang))
1143             xx = -math.sin(ang)/temp
1144             yy = math.cos(ang)/temp
1145             jw.x=w.x+xx+0.5
1146             jw.y=w.y+yy+0.5
1147             if not VALID_SECTOR(jw.x, jw.y):
1148                 return hit
1149             if game.quad[jw.x][jw.y]==IHBLANK:
1150                 finish(FHOLE)
1151                 return hit
1152             if game.quad[jw.x][jw.y]!=IHDOT:
1153                 # can't move into object 
1154                 return hit
1155             game.sector = jw
1156             crmshp()
1157             shoved = True
1158         elif iquad in (IHC, IHS): # Hit a commander 
1159             if Rand() <= 0.05:
1160                 crmena(True, iquad, sector, w)
1161                 prout(_(" uses anti-photon device;"))
1162                 prout(_("   torpedo neutralized."))
1163                 return None
1164         elif iquad in (IHR, IHK): # Hit a regular enemy 
1165             # find the enemy 
1166             for ll in range(1, game.nenhere+1):
1167                 if same(w, game.ks[ll]):
1168                     break
1169             kp = math.fabs(game.kpower[ll])
1170             h1 = 700.0 + 100.0*Rand() - \
1171                 1000.0 * distance(w, incoming) * math.fabs(math.sin(bullseye-angle))
1172             h1 = math.fabs(h1)
1173             if kp < h1:
1174                 h1 = kp
1175             if game.kpower[ll] < 0:
1176                 game.kpower[ll] -= -h1
1177             else:
1178                 game.kpower[ll] -= h1
1179             if game.kpower[ll] == 0:
1180                 deadkl(w, iquad, w)
1181                 return None
1182             crmena(True, iquad, "sector", w)
1183             # If enemy damaged but not destroyed, try to displace 
1184             ang = angle + 2.5*(Rand()-0.5)
1185             temp = math.fabs(math.sin(ang))
1186             if math.fabs(math.cos(ang)) > temp:
1187                 temp = math.fabs(math.cos(ang))
1188             xx = -math.sin(ang)/temp
1189             yy = math.cos(ang)/temp
1190             jw.x=w.x+xx+0.5
1191             jw.y=w.y+yy+0.5
1192             if not VALID_SECTOR(jw.x, jw.y):
1193                 prout(_(" damaged but not destroyed."))
1194                 return
1195             if game.quad[jw.x][jw.y]==IHBLANK:
1196                 prout(_(" buffeted into black hole."))
1197                 deadkl(w, iquad, jw)
1198                 return None
1199             if game.quad[jw.x][jw.y]!=IHDOT:
1200                 # can't move into object 
1201                 prout(_(" damaged but not destroyed."))
1202                 return None
1203             proutn(_(" damaged--"))
1204             game.ks[ll] = jw
1205             shoved = True
1206             break
1207         elif iquad == IHB: # Hit a base 
1208             skip(1)
1209             prout(_("***STARBASE DESTROYED.."))
1210             for ll in range(1, game.state.rembase+1):
1211                 if same(game.state.baseq[ll], game.quadrant):
1212                     game.state.baseq[ll]=game.state.baseq[game.state.rembase]
1213                     break
1214             game.quad[w.x][w.y]=IHDOT
1215             game.state.rembase -= 1
1216             game.base.x=game.base.y=0
1217             game.state.galaxy[game.quadrant.x][game.quadrant.y].starbase -= 1
1218             game.state.chart[game.quadrant.x][game.quadrant.y].starbase -= 1
1219             game.state.basekl += 1
1220             newcnd()
1221             return None
1222         elif iquad == IHP: # Hit a planet 
1223             crmena(True, iquad, sector, w)
1224             prout(_(" destroyed."))
1225             game.state.nplankl += 1
1226             game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = NOPLANET
1227             game.state.planets[game.iplnet].pclass = destroyed
1228             game.iplnet = 0
1229             invalidate(game.plnet)
1230             game.quad[w.x][w.y] = IHDOT
1231             if game.landed:
1232                 # captain perishes on planet 
1233                 finish(FDPLANET)
1234             return None
1235         elif iquad == IHW: # Hit an inhabited world -- very bad! 
1236             crmena(True, iquad, sector, w)
1237             prout(_(" destroyed."))
1238             game.state.nworldkl += 1
1239             game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = NOPLANET
1240             game.state.planets[game.iplnet].pclass = destroyed
1241             game.iplnet = 0
1242             invalidate(game.plnet)
1243             game.quad[w.x][w.y] = IHDOT
1244             if game.landed:
1245                 # captain perishes on planet 
1246                 finish(FDPLANET)
1247             prout(_("You have just destroyed an inhabited planet."))
1248             prout(_("Celebratory rallies are being held on the Klingon homeworld."))
1249             return None
1250         elif iquad == IHSTAR: # Hit a star 
1251             if Rand() > 0.10:
1252                 nova(w)
1253                 return None
1254             crmena(True, IHSTAR, sector, w)
1255             prout(_(" unaffected by photon blast."))
1256             return None
1257         elif iquad == IHQUEST: # Hit a thingy 
1258             if not (game.options & OPTION_THINGY) or Rand()>0.7:
1259                 skip(1)
1260                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1261                 skip(1)
1262                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1263                 skip(1)
1264                 proutn(_("Mr. Spock-"))
1265                 prouts(_("  \"Fascinating!\""))
1266                 skip(1)
1267                 deadkl(w, iquad, w)
1268             else:
1269                 #
1270                 # Stas Sergeev added the possibility that
1271                 # you can shove the Thingy and piss it off.
1272                 # It then becomes an enemy and may fire at you.
1273                 # 
1274                 iqengry = True
1275                 shoved = True
1276             return None
1277         elif iquad == IHBLANK: # Black hole 
1278             skip(1)
1279             crmena(True, IHBLANK, sector, w)
1280             prout(_(" swallows torpedo."))
1281             return None
1282         elif iquad == IHWEB: # hit the web 
1283             skip(1)
1284             prout(_("***Torpedo absorbed by Tholian web."))
1285             return None
1286         elif iquad == IHT:  # Hit a Tholian 
1287             h1 = 700.0 + 100.0*Rand() - \
1288                 1000.0 * distance(w, incoming) * math.fabs(math.sin(bullseye-angle))
1289             h1 = math.fabs(h1)
1290             if h1 >= 600:
1291                 game.quad[w.x][w.y] = IHDOT
1292                 game.ithere = False
1293                 deadkl(w, iquad, w)
1294                 return None
1295             skip(1)
1296             crmena(True, IHT, sector, w)
1297             if Rand() > 0.05:
1298                 prout(_(" survives photon blast."))
1299                 return None
1300             prout(_(" disappears."))
1301             game.quad[w.x][w.y] = IHWEB
1302             game.ithere = False
1303             game.nenhere -= 1
1304             dropin(IHBLANK)
1305             return None
1306         else: # Problem!
1307             skip(1)
1308             proutn("Don't know how to handle torpedo collision with ")
1309             crmena(True, iquad, sector, w)
1310             skip(1)
1311             return None
1312         break
1313     if curwnd!=message_window:
1314         setwnd(message_window)
1315     if shoved:
1316         game.quad[w.x][w.y]=IHDOT
1317         game.quad[jw.x][jw.y]=iquad
1318         prout(_(" displaced by blast to %s "), cramlc(sector, jw))
1319         for ll in range(1, game.nenhere+1):
1320             game.kdist[ll] = game.kavgd[ll] = distance(game.sector,game.ks[ll])
1321         sortklings()
1322         return None
1323     skip(1)
1324     prout(_("Torpedo missed."))
1325     return None;
1326
1327 def fry(hit):
1328     # critical-hit resolution 
1329     ktr=1
1330     # a critical hit occured 
1331     if hit < (275.0-25.0*game.skill)*(1.0+0.5*Rand()):
1332         return
1333
1334     ncrit = 1.0 + hit/(500.0+100.0*Rand())
1335     proutn(_("***CRITICAL HIT--"))
1336     # Select devices and cause damage
1337     cdam = []
1338     for loop1 in range(ncrit):
1339         while True:
1340             j = randdevice()
1341             # Cheat to prevent shuttle damage unless on ship 
1342             if not (game.damage[j]<0.0 or (j==DSHUTTL and game.iscraft != "onship")):
1343                 break
1344         cdam.append(j)
1345         extradm = (hit*game.damfac)/(ncrit*(75.0+25.0*Rand()))
1346         game.damage[j] += extradm
1347         if loop1 > 0:
1348             for loop2 in range(loop1):
1349                 if j == cdam[loop2]:
1350                     break
1351             if loop2 < loop1:
1352                 continue
1353             ktr += 1
1354             if ktr==3:
1355                 skip(1)
1356             proutn(_(" and "))
1357         proutn(device[j])
1358     prout(_(" damaged."))
1359     if damaged(DSHIELD) and game.shldup:
1360         prout(_("***Shields knocked down."))
1361         game.shldup=False
1362
1363 def attack(torps_ok):
1364     # bad guy attacks us 
1365     # torps_ok == false forces use of phasers in an attack 
1366     atackd = False; attempt = False; ihurt = False;
1367     hitmax=0.0; hittot=0.0; chgfac=1.0
1368     jay = coord()
1369     where = "neither"
1370
1371     # game could be over at this point, check 
1372     if game.alldone:
1373         return
1374
1375     if idebug:
1376         prout("=== ATTACK!")
1377
1378     # Tholian gewts to move before attacking 
1379     if game.ithere:
1380         movetholian()
1381
1382     # if you have just entered the RNZ, you'll get a warning 
1383     if game.neutz: # The one chance not to be attacked 
1384         game.neutz = False
1385         return
1386
1387     # commanders get a chance to tac-move towards you 
1388     if (((game.comhere or game.ishere) and not game.justin) or game.skill == SKILL_EMERITUS) and torps_ok:
1389         moveklings()
1390
1391     # if no enemies remain after movement, we're done 
1392     if game.nenhere==0 or (game.nenhere==1 and iqhere and not iqengry):
1393         return
1394
1395     # set up partial hits if attack happens during shield status change 
1396     pfac = 1.0/game.inshld
1397     if game.shldchg:
1398         chgfac = 0.25+0.5*Rand()
1399
1400     skip(1)
1401
1402     # message verbosity control 
1403     if game.skill <= SKILL_FAIR:
1404         where = "sector"
1405
1406     for loop in range(1, game.nenhere+1):
1407         if game.kpower[loop] < 0:
1408             continue;   # too weak to attack 
1409         # compute hit strength and diminish shield power 
1410         r = Rand()
1411         # Increase chance of photon torpedos if docked or enemy energy low 
1412         if game.condition == "docked":
1413             r *= 0.25
1414         if game.kpower[loop] < 500:
1415             r *= 0.25; 
1416         jay = game.ks[loop]
1417         iquad = game.quad[jay.x][jay.y]
1418         if iquad==IHT or (iquad==IHQUEST and not iqengry):
1419             continue
1420         # different enemies have different probabilities of throwing a torp 
1421         usephasers = not torps_ok or \
1422             (iquad == IHK and r > 0.0005) or \
1423             (iquad==IHC and r > 0.015) or \
1424             (iquad==IHR and r > 0.3) or \
1425             (iquad==IHS and r > 0.07) or \
1426             (iquad==IHQUEST and r > 0.05)
1427         if usephasers:      # Enemy uses phasers 
1428             if game.condition == "docked":
1429                 continue; # Don't waste the effort! 
1430             attempt = True; # Attempt to attack 
1431             dustfac = 0.8+0.05*Rand()
1432             hit = game.kpower[loop]*math.pow(dustfac,game.kavgd[loop])
1433             game.kpower[loop] *= 0.75
1434         else: # Enemy uses photon torpedo 
1435             course = 1.90985*math.atan2(game.sector.y-jay.y, jay.x-game.sector.x)
1436             hit = 0
1437             proutn(_("***TORPEDO INCOMING"))
1438             if not damaged(DSRSENS):
1439                 proutn(_(" From "))
1440                 crmena(False, iquad, where, jay)
1441             attempt = True
1442             prout("  ")
1443             r = (Rand()+Rand())*0.5 -0.5
1444             r += 0.002*game.kpower[loop]*r
1445             hit = torpedo(course, r, jay, 1, 1)
1446             if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
1447                 finish(FWON); # Klingons did themselves in! 
1448             if game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova or game.alldone:
1449                 return; # Supernova or finished 
1450             if hit == None:
1451                 continue
1452         # incoming phaser or torpedo, shields may dissipate it 
1453         if game.shldup or game.shldchg or game.condition=="docked":
1454             # shields will take hits 
1455             propor = pfac * game.shield
1456             if game.condition =="docked":
1457                 propr *= 2.1
1458             if propor < 0.1:
1459                 propor = 0.1
1460             hitsh = propor*chgfac*hit+1.0
1461             absorb = 0.8*hitsh
1462             if absorb > game.shield:
1463                 absorb = game.shield
1464             game.shield -= absorb
1465             hit -= hitsh
1466             # taking a hit blasts us out of a starbase dock 
1467             if game.condition == "docked":
1468                 dock(False)
1469             # but the shields may take care of it 
1470             if propor > 0.1 and hit < 0.005*game.energy:
1471                 continue
1472         # hit from this opponent got through shields, so take damage 
1473         ihurt = True
1474         proutn(_("%d unit hit") % int(hit))
1475         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1476             proutn(_(" on the "))
1477             crmshp()
1478         if not damaged(DSRSENS) and usephasers:
1479             proutn(_(" from "))
1480             crmena(False, iquad, where, jay)
1481         skip(1)
1482         # Decide if hit is critical 
1483         if hit > hitmax:
1484             hitmax = hit
1485         hittot += hit
1486         fry(hit)
1487         game.energy -= hit
1488     if game.energy <= 0:
1489         # Returning home upon your shield, not with it... 
1490         finish(FBATTLE)
1491         return
1492     if not attempt and game.condition == "docked":
1493         prout(_("***Enemies decide against attacking your ship."))
1494     if not atackd:
1495         return
1496     percent = 100.0*pfac*game.shield+0.5
1497     if not ihurt:
1498         # Shields fully protect ship 
1499         proutn(_("Enemy attack reduces shield strength to "))
1500     else:
1501         # Print message if starship suffered hit(s) 
1502         skip(1)
1503         proutn(_("Energy left %2d    shields ") % int(game.energy))
1504         if game.shldup:
1505             proutn(_("up "))
1506         elif not damaged(DSHIELD):
1507             proutn(_("down "))
1508         else:
1509             proutn(_("damaged, "))
1510     prout(_("%d%%,   torpedoes left %d"), percent, game.torps)
1511     # Check if anyone was hurt 
1512     if hitmax >= 200 or hittot >= 500:
1513         icas= hittot*Rand()*0.015
1514         if icas >= 2:
1515             skip(1)
1516             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1517             prout(_("   in that last attack.\""))
1518             game.casual += icas
1519             game.state.crew -= icas
1520     # After attack, reset average distance to enemies 
1521     for loop in range(1, game.nenhere+1):
1522         game.kavgd[loop] = game.kdist[loop]
1523     sortklings()
1524     return;
1525                 
1526 def deadkl(w, type, mv):
1527     # kill a Klingon, Tholian, Romulan, or Thingy 
1528     # Added mv to allow enemy to "move" before dying 
1529
1530     crmena(True, type, sector, mv)
1531     # Decide what kind of enemy it is and update appropriately 
1532     if type == IHR:
1533         # chalk up a Romulan 
1534         game.state.galaxy[game.quadrant.x][game.quadrant.y].romulans -= 1
1535         game.irhere -= 1
1536         game.state.nromrem -= 1
1537     elif type == IHT:
1538         # Killed a Tholian 
1539         game.ithere = False
1540     elif type == IHQUEST:
1541         # Killed a Thingy 
1542         iqhere = iqengry = False
1543         invalidate(thing)
1544     else:
1545         # Some type of a Klingon 
1546         game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons -= 1
1547         game.klhere -= 1
1548         if type == IHC:
1549             game.comhere = False
1550             for i in range(1, game.state.remcom+1):
1551                 if same(game.state.kcmdr[i], game.quadrant):
1552                     break
1553             game.state.kcmdr[i] = game.state.kcmdr[game.state.remcom]
1554             game.state.kcmdr[game.state.remcom].x = 0
1555             game.state.kcmdr[game.state.remcom].y = 0
1556             game.state.remcom -= 1
1557             unschedule(FTBEAM)
1558             if game.state.remcom != 0:
1559                 schedule(FTBEAM, expran(1.0*game.incom/game.state.remcom))
1560         elif type ==  IHK:
1561             game.state.remkl -= 1
1562         elif type ==  IHS:
1563             game.state.nscrem -= 1
1564             game.ishere = False
1565             game.state.kscmdr.x = game.state.kscmdr.y = game.isatb = 0
1566             game.iscate = False
1567             unschedule(FSCMOVE)
1568             unschedule(FSCDBAS)
1569         else:
1570             prout("*** Internal error, deadkl() called on %s\n" % type)
1571
1572     # For each kind of enemy, finish message to player 
1573     prout(_(" destroyed."))
1574     game.quad[w.x][w.y] = IHDOT
1575     if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
1576         return
1577
1578     game.state.remtime = game.state.remres/(game.state.remkl + 4*game.state.remcom)
1579
1580     # Remove enemy ship from arrays describing local conditions 
1581     if is_scheduled(FCDBAS) and same(game.battle, game.quadrant) and type==IHC:
1582         unschedule(FCDBAS)
1583     for i in range(1, game.nenhere+1):
1584         if same(game.ks[i], w):
1585             break
1586     game.nenhere -= 1
1587     if i <= game.nenhere:
1588         for j in range(i, game.nenhere+1):
1589             game.ks[j] = game.ks[j+1]
1590             game.kpower[j] = game.kpower[j+1]
1591             game.kavgd[j] = game.kdist[j] = game.kdist[j+1]
1592     game.ks[game.nenhere+1].x = 0
1593     game.ks[game.nenhere+1].x = 0
1594     game.kdist[game.nenhere+1] = 0
1595     game.kavgd[game.nenhere+1] = 0
1596     game.kpower[game.nenhere+1] = 0
1597     return;
1598
1599 def targetcheck(x, y):
1600     # Return None if target is invalid 
1601     if not VALID_SECTOR(x, y):
1602         huh()
1603         return None
1604     deltx = 0.1*(y - game.sector.y)
1605     delty = 0.1*(x - game.sector.x)
1606     if deltx==0 and delty== 0:
1607         skip(1)
1608         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1609         prout(_("  I recommend an immediate review of"))
1610         prout(_("  the Captain's psychological profile.\""))
1611         chew()
1612         return None
1613     return 1.90985932*math.atan2(deltx, delty)
1614
1615 def photon():
1616     # launch photon torpedo 
1617     game.ididit = False
1618     if damaged(DPHOTON):
1619         prout(_("Photon tubes damaged."))
1620         chew()
1621         return
1622     if game.torps == 0:
1623         prout(_("No torpedoes left."))
1624         chew()
1625         return
1626     key = scan()
1627     while True:
1628         if key == IHALPHA:
1629             huh()
1630             return
1631         elif key == IHEOL:
1632             prout(_("%d torpedoes left."), game.torps)
1633             proutn(_("Number of torpedoes to fire- "))
1634             key = scan()
1635         else: # key == IHREAL  {
1636             n = aaitem + 0.5
1637             if n <= 0: # abort command 
1638                 chew()
1639                 return
1640             if n > 3:
1641                 chew()
1642                 prout(_("Maximum of 3 torpedoes per burst."))
1643                 key = IHEOL
1644                 return
1645             if n <= game.torps:
1646                 break
1647             chew()
1648             key = IHEOL
1649     for i in range(1, n+1):
1650         key = scan()
1651         if i==1 and key == IHEOL:
1652             break;      # we will try prompting 
1653         if i==2 and key == IHEOL:
1654             # direct all torpedoes at one target 
1655             while i <= n:
1656                 targ[i][1] = targ[1][1]
1657                 targ[i][2] = targ[1][2]
1658                 course[i] = course[1]
1659                 i += 1
1660             break
1661         if key != IHREAL:
1662             huh()
1663             return
1664         targ[i][1] = aaitem
1665         key = scan()
1666         if key != IHREAL:
1667             huh()
1668             return
1669         targ[i][2] = aaitem
1670         course[i] = targetcheck(targ[i][1], targ[i][2])
1671         if course[i] == None:
1672             return
1673     chew()
1674     if i == 1 and key == IHEOL:
1675         # prompt for each one 
1676         for i in range(1, n+1):
1677             proutn(_("Target sector for torpedo number %d- "), i)
1678             key = scan()
1679             if key != IHREAL:
1680                 huh()
1681                 return
1682             targ[i][1] = aaitem
1683             key = scan()
1684             if key != IHREAL:
1685                 huh()
1686                 return
1687             targ[i][2] = aaitem
1688             chew()
1689             course[i] = targetcheck(targ[i][1], targ[i][2])
1690             if course[i] == None:
1691                 return
1692     game.ididit = True
1693     # Loop for moving <n> torpedoes 
1694     for i in range(1, n+1):
1695         if game.condition != "docked":
1696             game.torps -= 1
1697         r = (Rand()+Rand())*0.5 -0.5
1698         if math.fabs(r) >= 0.47:
1699             # misfire! 
1700             r = (Rand()+1.2) * r
1701             if n>1:
1702                 prouts(_("***TORPEDO NUMBER %d MISFIRES"), i)
1703             else:
1704                 prouts(_("***TORPEDO MISFIRES."))
1705             skip(1)
1706             if i < n:
1707                 prout(_("  Remainder of burst aborted."))
1708             if Rand() <= 0.2:
1709                 prout(_("***Photon tubes damaged by misfire."))
1710                 game.damage[DPHOTON] = game.damfac*(1.0+2.0*Rand())
1711             break
1712         if game.shldup or game.condition == "docked":
1713             r *= 1.0 + 0.0001*game.shield
1714         torpedo(course[i], r, game.sector, i, n)
1715         if game.alldone or game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova:
1716             return
1717     if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
1718         finish(FWON);
1719
1720 def overheat(rpow):
1721     # check for phasers overheating 
1722     if rpow > 1500:
1723         chekbrn = (rpow-1500.)*0.00038
1724         if Rand() <= chekbrn:
1725             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1726             game.damage[DPHASER] = game.damfac*(1.0 + Rand()) * (1.0+chekbrn)
1727
1728 def checkshctrl(rpow):
1729     # check shield control 
1730         
1731     skip(1)
1732     if Rand() < 0.998:
1733         prout(_("Shields lowered."))
1734         return False
1735     # Something bad has happened 
1736     prouts(_("***RED ALERT!  RED ALERT!"))
1737     skip(2)
1738     hit = rpow*game.shield/game.inshld
1739     game.energy -= rpow+hit*0.8
1740     game.shield -= hit*0.2
1741     if game.energy <= 0.0:
1742         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1743         skip(1)
1744         stars()
1745         finish(FPHASER)
1746         return True
1747     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1748     skip(2)
1749     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1750     icas = hit*Rand()*0.012
1751     skip(1)
1752     fry(0.8*hit)
1753     if icas:
1754         skip(1)
1755         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1756         prout(_("  %d casualties so far.\""), icas)
1757         game.casual += icas
1758         game.state.crew -= icas
1759     skip(1)
1760     prout(_("Phaser energy dispersed by shields."))
1761     prout(_("Enemy unaffected."))
1762     overheat(rpow)
1763     return True;
1764
1765 def hittem(doublehits):
1766     # register a phaser hit on Klingons and Romulans 
1767     nenhr2=game.nenhere; kk=1
1768     w = coord()
1769     skip(1)
1770     for k in range(1, nenhr2+1):
1771         wham = hits[k]
1772         if wham==0:
1773             continue
1774         dustfac = 0.9 + 0.01*Rand()
1775         hit = wham*math.pow(dustfac,game.kdist[kk])
1776         kpini = game.kpower[kk]
1777         kp = math.fabs(kpini)
1778         if PHASEFAC*hit < kp:
1779             kp = PHASEFAC*hit
1780         if game.kpower[kk] < 0:
1781             game.kpower[kk] -= -kp
1782         else:
1783             game.kpower[kk] -= kp
1784         kpow = game.kpower[kk]
1785         w = game.ks[kk]
1786         if hit > 0.005:
1787             if not damaged(DSRSENS):
1788                 boom(w)
1789             proutn(_("%d unit hit on ") % int(hit))
1790         else:
1791             proutn(_("Very small hit on "))
1792         ienm = game.quad[w.x][w.y]
1793         if ienm==IHQUEST:
1794             iqengry = True
1795         crmena(False, ienm, "sector", w)
1796         skip(1)
1797         if kpow == 0:
1798             deadkl(w, ienm, w)
1799             if (game.state.remkl + game.state.remcom + game.state.nscrem)==0:
1800                 finish(FWON);           
1801             if game.alldone:
1802                 return
1803             kk -= 1; # don't do the increment 
1804         else: # decide whether or not to emasculate klingon 
1805             if kpow > 0 and Rand() >= 0.9 and \
1806                 kpow <= ((0.4 + 0.4*Rand())*kpini):
1807                 prout(_("***Mr. Spock-  \"Captain, the vessel at %s"),
1808                       cramlc(sector, w))
1809                 prout(_("   has just lost its firepower.\""))
1810                 game.kpower[kk] = -kpow
1811         kk += 1
1812     return;
1813
1814 def phasers():
1815     # fire phasers 
1816     hits = []; rpow=0
1817     kz = 0; k = 1; irec=0 # Cheating inhibitor 
1818     ifast = False; no = False; itarg = True; msgflag = True
1819     automode = "NOTSET"
1820     key=0
1821
1822     skip(1)
1823     # SR sensors and Computer are needed fopr automode 
1824     if damaged(DSRSENS) or damaged(DCOMPTR):
1825         itarg = False
1826     if game.condition == "docked":
1827         prout(_("Phasers can't be fired through base shields."))
1828         chew()
1829         return
1830     if damaged(DPHASER):
1831         prout(_("Phaser control damaged."))
1832         chew()
1833         return
1834     if game.shldup:
1835         if damaged(DSHCTRL):
1836             prout(_("High speed shield control damaged."))
1837             chew()
1838             return
1839         if game.energy <= 200.0:
1840             prout(_("Insufficient energy to activate high-speed shield control."))
1841             chew()
1842             return
1843         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1844         ifast = True
1845                 
1846     # Original code so convoluted, I re-did it all 
1847     while automode=="NOTSET":
1848         key=scan()
1849         if key == IHALPHA:
1850             if isit("manual"):
1851                 if game.nenhere==0:
1852                     prout(_("There is no enemy present to select."))
1853                     chew()
1854                     key = IHEOL
1855                     automode="AUTOMATIC"
1856                 else:
1857                     automode = "MANUAL"
1858                     key = scan()
1859             elif isit("automatic"):
1860                 if (not itarg) and game.nenhere != 0:
1861                     automode = "FORCEMAN"
1862                 else:
1863                     if game.nenhere==0:
1864                         prout(_("Energy will be expended into space."))
1865                     automode = "AUTOMATIC"
1866                     key = scan()
1867             elif isit("no"):
1868                 no = True
1869             else:
1870                 huh()
1871                 return
1872         elif key == IHREAL:
1873             if game.nenhere==0:
1874                 prout(_("Energy will be expended into space."))
1875                 automode = "AUTOMATIC"
1876             elif not itarg:
1877                 automode = "FORCEMAN"
1878             else:
1879                 automode = "AUTOMATIC"
1880         else:
1881             # IHEOL 
1882             if game.nenhere==0:
1883                 prout(_("Energy will be expended into space."))
1884                 automode = "AUTOMATIC"
1885             elif not itarg:
1886                 automode = "FORCEMAN"
1887             else: 
1888                 proutn(_("Manual or automatic? "))                      
1889     avail = game.energy
1890     if ifast:
1891         avail -= 200.0
1892     if automode == "AUTOMATIC":
1893         if key == IHALPHA and isit("no"):
1894             no = True
1895             key = scan()
1896         if key != IHREAL and game.nenhere != 0:
1897             prout(_("Phasers locked on target. Energy available: %.2f"),
1898                   avail)
1899         irec=0
1900         while True:
1901             chew()
1902             if not kz:
1903                 for i in range(1, game.nenhere+1):
1904                     irec += math.fabs(game.kpower[i])/(PHASEFAC*math.pow(0.90,game.kdist[i]))*(1.01+0.05*Rand()) + 1.0
1905             kz=1
1906             proutn(_("%d units required. "), irec)
1907             chew()
1908             proutn(_("Units to fire= "))
1909             key = scan()
1910             if key!=IHREAL:
1911                 return
1912             rpow = aaitem
1913             if rpow > avail:
1914                 proutn(_("Energy available= %.2f") % avail)
1915                 skip(1)
1916                 key = IHEOL
1917             if not rpow > avail:
1918                 break
1919         if rpow<=0:
1920             # chicken out 
1921             chew()
1922             return
1923         key=scan()
1924         if key == IHALPHA and isit("no"):
1925             no = True
1926         if ifast:
1927             game.energy -= 200; # Go and do it! 
1928             if checkshctrl(rpow):
1929                 return
1930         chew()
1931         game.energy -= rpow
1932         extra = rpow
1933         if game.nenhere:
1934             extra = 0.0
1935             powrem = rpow
1936             for i in range(1, game.nenhere+1):
1937                 hits[i] = 0.0
1938                 if powrem <= 0:
1939                     continue
1940                 hits[i] = math.fabs(game.kpower[i])/(PHASEFAC*math.pow(0.90,game.kdist[i]))
1941                 over = (0.01 + 0.05*Rand())*hits[i]
1942                 temp = powrem
1943                 powrem -= hits[i] + over
1944                 if powrem <= 0 and temp < hits[i]:
1945                     hits[i] = temp
1946                 if powrem <= 0:
1947                     over = 0.0
1948                 extra += over
1949             if powrem > 0.0:
1950                 extra += powrem
1951             hittem(hits)
1952             game.ididit = True
1953         if extra > 0 and not game.alldone:
1954             if game.ithere:
1955                 proutn(_("*** Tholian web absorbs "))
1956                 if game.nenhere>0:
1957                     proutn(_("excess "))
1958                 prout(_("phaser energy."))
1959             else:
1960                 prout(_("%d expended on empty space."), int(extra))
1961     elif automode == "FORCEMAN":
1962         chew()
1963         key = IHEOL
1964         if damaged(DCOMPTR):
1965             prout(_("Battle computer damaged, manual fire only."))
1966         else:
1967             skip(1)
1968             prouts(_("---WORKING---"))
1969             skip(1)
1970             prout(_("Short-range-sensors-damaged"))
1971             prout(_("Insufficient-data-for-automatic-phaser-fire"))
1972             prout(_("Manual-fire-must-be-used"))
1973             skip(1)
1974     elif automode == "MANUAL":
1975         rpow = 0.0
1976         for k in range(1, game.nenhere+1):
1977             aim = game.ks[k]
1978             ienm = game.quad[aim.x][aim.y]
1979             if msgflag:
1980                 proutn(_("Energy available= %.2f") % avail-0.006)
1981                 skip(1)
1982                 msgflag = False
1983                 rpow = 0.0
1984             if damaged(DSRSENS) and not (abs(game.sector.x-aim.x) < 2 and abs(game.sector.y-aim.y) < 2) and \
1985                 (ienm == IHC or ienm == IHS):
1986                 cramen(ienm)
1987                 prout(_(" can't be located without short range scan."))
1988                 chew()
1989                 key = IHEOL
1990                 hits[k] = 0; # prevent overflow -- thanks to Alexei Voitenko 
1991                 k += 1
1992                 continue
1993             if key == IHEOL:
1994                 chew()
1995                 if itarg and k > kz:
1996                     irec=(abs(game.kpower[k])/(PHASEFAC*math.pow(0.9,game.kdist[k]))) * (1.01+0.05*Rand()) + 1.0
1997                 kz = k
1998                 proutn("(")
1999                 if not damaged(DCOMPTR):
2000                     proutn("%d", irec)
2001                 else:
2002                     proutn("??")
2003                 proutn(")  ")
2004                 proutn(_("units to fire at "))
2005                 crmena(False, ienm, sector, aim)
2006                 proutn("-  ")
2007                 key = scan()
2008             if key == IHALPHA and isit("no"):
2009                 no = True
2010                 key = scan()
2011                 continue
2012             if key == IHALPHA:
2013                 huh()
2014                 return
2015             if key == IHEOL:
2016                 if k==1: # Let me say I'm baffled by this 
2017                     msgflag = True
2018                 continue
2019             if aaitem < 0:
2020                 # abort out 
2021                 chew()
2022                 return
2023             hits[k] = aaitem
2024             rpow += aaitem
2025             # If total requested is too much, inform and start over 
2026             if rpow > avail:
2027                 prout(_("Available energy exceeded -- try again."))
2028                 chew()
2029                 return
2030             key = scan(); # scan for next value 
2031             k += 1
2032         if rpow == 0.0:
2033             # zero energy -- abort 
2034             chew()
2035             return
2036         if key == IHALPHA and isit("no"):
2037             no = True
2038         game.energy -= rpow
2039         chew()
2040         if ifast:
2041             game.energy -= 200.0
2042             if checkshctrl(rpow):
2043                 return
2044         hittem(hits)
2045         game.ididit = True
2046      # Say shield raised or malfunction, if necessary 
2047     if game.alldone:
2048         return
2049     if ifast:
2050         skip(1)
2051         if no == 0:
2052             if Rand() >= 0.99:
2053                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2054                 prouts(_("         CLICK   CLICK   POP  . . ."))
2055                 prout(_(" No response, sir!"))
2056                 game.shldup = False
2057             else:
2058                 prout(_("Shields raised."))
2059         else:
2060             game.shldup = False
2061     overheat(rpow);
2062