2 sst.py =-- Super Star Trek in Python
8 NINHAB = GALSIZE * GALSIZE / 2
10 PLNETMAB = NINHAB + MAXUNINHAB
13 FULLCREW = 428 # BSD Trek was 387, that's wrong
18 # These types have not been dealt with yet
26 def __init(self, x=None, y=None):
30 self.x = self.y = None
32 return self.x != None and self.y != None
33 def __eq__(self, other):
34 return self.x == other.y and self.x == other.y
35 def __add__(self, other):
36 return coord(self.x+self.x, self.y+self.y)
37 def __sub__(self, other):
38 return coord(self.x-self.x, self.y-self.y)
39 def distance(self, other):
40 return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
42 return coord(self.x / abs(x), self.y / abs(y));
46 return "%d - %d" % (self.x, self.y)
49 "A feature in the current quadrant (ship, star, black hole, etc)."
51 self.type = None # name of feature type
52 self.location = None # location
54 return self.location.distance(game.sector)
58 empty = None # Value of empty space in game.quad
61 "An enemy ship in the current quadrant."
63 feature.__init__(self)
64 self.type = None # klingon, romulan, commander,
65 # supercommander, tholian
66 self.power = None # power
67 if self.type in ("Klingon", "Commander", "Super-Commander"):
69 elif self.type == "Romulan":
72 if self.type in ("Klingon", "Commander", "Super-Commander"):
74 elif self.type == "Romulan":
76 def sectormove(self, dest):
77 "Move this ship within the current quadrant."
79 game.quad[self.location] = None
80 game.quad[dest] = self
83 class planet(feature):
84 "A planet. May be inhabited or not, may hold dilithium crystals or not."
86 feature.__init__(self)
88 self.crystals = None # "absent", "present", or "mined"
89 self.inhabited = False
90 self.known = "unknown" # Other values: "known" and "shuttle down"
98 "A star. Has no state, just knows how to identify iself."
100 feature.__init__(self)
105 "A bit of Tholian web. Has no state, just knows how to identify iself."
107 feature.__init__(self)
111 class blackhole(feature):
112 "A black hole. Has no hair, just knows how to identify iself."
114 feature.__init__(self)
118 class starbase(feature):
119 "Starbases also have no features."
121 feature.__init__(self)
123 game.state.bases.remove(self.location)
134 self.supernova = None
136 self.status = "secure" # Other valuues: "distressed", "enslaved"
138 "List enemies in this quadrant."
140 for feature in self.quad.values:
141 if not isinstance(feature, ship):
143 if feature.name not in ("Enterprise", "Faerie Queene"):
148 "A chart page. The starchart is a 2D array of these."
150 self.stars = None # Will hold a number
151 self.starbase = None # Will hold a bool
152 self.klingons = None # Will hold a number
155 "State of the universe. The galaxy is a 2D array of these."
157 self.crew = None # crew complement
158 self.remkl = None # remaining klingons
159 self.remcom = None # remaining commanders
160 self.nscrem = None # remaining super commanders
161 self.rembase = None # remaining bases
162 self.starkl = None # destroyed stars
163 self.basekl = None # destroyed bases
164 self.nromrem = None # Romulans remaining
165 self.nplankl = None # destroyed uninhabited planets
166 self.nworldkl = None # destroyed inhabited planets
167 self.plnets = []; # List of planets known
168 self.date = None # stardate
169 self.remres = None # remaining resources
170 self. remtime = None # remaining time
171 self.bases = [] # Base quadrant coordinates
172 self.kcmdr = [] # Commander quadrant coordinates
173 self.kscmdr = None # Supercommander quadrant coordinates
174 self.galaxy = {} # Dictionary of quadrant objects
175 self.chart = {} # Dictionary of page objects
178 return game.damage[dev] != 0.0
182 self.date = None # The only mandatory attribute
186 self.options = [] # List of option strings
187 self.state = snapshot() # State of the universe
188 self.snapsht = snapshot() # For backwards timetravel
189 self.quad = {} # contents of our quadrant
190 self.kpower = {} # enemy energy levels
191 self.kdist = {} # enemy distances
192 self.kavgd = {} # average distances
193 self.damage = {} # damage encountered
194 self.future = [] # future events
195 self.passwd = None # Self Destruct password
196 # Coordinate members start here
197 self.enemies = {} # enemy sector locations
198 self.quadrant = None # where we are
200 self.tholian = None # coordinates of Tholian
201 self.base = None # position of base in current quadrant
202 self.battle = None # base coordinates being attacked
203 self.plnet = None # location of planet in quadrant
204 self.probec = None # current probe quadrant
205 # Flag members start here
206 self.gamewon = None # Finished!
207 self.ididit = None # action taken -- allows enemy to attack
208 self.alive = None # we are alive (not killed)
209 self.justin = None # just entered quadrant
210 self.shldup = None # shields are up
211 self.shldchg = None # shield changing (affects efficiency)
212 self.comhere = None # commander here
213 self.ishere = None # super-commander in quadrant
214 self.iscate = None # super commander is here
215 self.ientesc = None # attempted escape from supercommander
216 self.ithere = None # Tholian is here
217 self.resting = None # rest time
218 self.icraft = None # Kirk in Galileo
219 self.landed = None # party on planet or on ship
220 self.alldone = None # game is now finished
221 self.neutz = None # Romulan Neutral Zone
222 self.isarmed = None # probe is armed
223 self.inorbit = None # orbiting a planet
224 self.imine = None # mining
225 self.icrystl = None # dilithium crystals aboard
226 self.iseenit = None # seen base attack report
227 self.thawed = None # thawed game
228 # String members start here
229 self.condition = None # green, yellow, red, docked, dead,
230 self.iscraft = None # onship, offship, removed
231 self.skill = None # levels: none, novice, fair, good,
233 # Integer nembers sart here
234 self.inkling = None # initial number of klingons
235 self.inbase = None # initial number of bases
236 self.incom = None # initial number of commanders
237 self.inscom = None # initial number of commanders
238 self.inrom = None # initial number of commanders
239 self.instar = None # initial stars
240 self.intorps = None # initial/max torpedoes
241 self.torps = None # number of torpedoes
242 self.ship = None # ship type -- 'E' is Enterprise
243 self.abandoned = None # count of crew abandoned in space
244 self.length = None # length of game
245 self.klhere = None # klingons here
246 self.casual = None # causalties
247 self.nhelp = None # calls for help
248 self.nkinks = None # count of energy-barrier crossings
249 self.iplnet = None # planet # in quadrant
250 self.inplan = None # initial planets
251 self.irhere = None # Romulans in quadrant
252 self.isatb = None # =1 if super commander is attacking base
253 self.tourn = None # tournament number
254 self.proben = None # number of moves for probe
255 self.nprobes = None # number of probes available
256 # Float members start here
257 self.inresor = None # initial resources
258 self.intime = None # initial time
259 self.inenrg = None # initial/max energy
260 self.inshld = None # initial/max shield
261 self.inlsr = None # initial life support resources
262 self.indate = None # initial date
263 self.energy = None # energy level
264 self.shield = None # shield level
265 self.warpfac = None # warp speed
266 self.wfacsq = None # squared warp factor
267 self.lsupres = None # life support reserves
268 self.dist = None # movement distance
269 self.direc = None # movement direction
270 self.optime = None # time taken by current operation
271 self.docfac = None # repair factor when docking (constant?)
272 self.damfac = None # damage factor
273 self.lastchart = None # time star chart was last updated
274 self.cryprob = None # probability that crystal will work
275 self.probex = None # location of probe
277 self.probeinx = None # probe x,y increment
278 self.probeiny = None #
279 self.height = None # height of orbit around planet
282 "Are we in communication with Starfleet Command?"
283 return (not damaged("DRADIO")) or game.condition == docked
285 # Code corresponding to ai.c begins here
287 def tryexit(look, ship, irun):
288 # a bad guy attempts to bug out of the quadrant
290 iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1
291 iq.y = game.quadrant.y+(look.y+(QUADSIZE-1))/QUADSIZE - 1
292 if not valid_quadrant(iq) or \
293 game.state.galaxy[iq].supernova or \
294 game.state.galaxy[iq].klingons > 8:
295 return False; # no can do -- neg energy, supernovae, or >8 Klingons
296 if ship.type == "Romulan":
297 return False # Romulans cannot escape
299 # avoid intruding on another commander's territory
300 if ship.type == "Commander":
301 if iq in gamestate.kcmdr:
303 # refuse to leave if currently attacking starbase:
304 if game.battle == game.quadrant:
306 # don't leave if over 1000 units of energy
307 if ship.power > 1000.0:
309 # Print escape message and move out of quadrant.
310 # We know this if either short or long range sensors are working
311 if not damaged("DSRSENS") or not damaged("DLRSENS") or game.condition=="docked":
312 crmena(True, "sector", ship)
313 prout(" escapes to quadrant %s (and regains strength)." % iq)
314 # handle local matters related to escape
315 game.quad[ship.location] = None;
316 if game.condition != "docked":
318 # Handle global matters related to escape
319 game.state.galaxy[game.quadrant].klingons -= 1
320 game.state.galaxy[iq].klingons += 1
321 if ship.type == "Super-Commander":
326 schedule("FSCMOVE", 0.2777)
327 unschedule("FSCDBAS")
328 game.state.kscmdr = iq
330 for (n, cmdr) in enumerate(game.state.kcmdr):
331 if cmdr == game.quadrant:
332 game.state.kcmdr[n] = iq
335 return True # successful exit
337 def sgn(n): n / abs(n)
340 Algorithm for moving bad guys:
342 * Enterprise has "force" based on condition of phaser and photon torpedoes.
343 If both are operating full strength, force is 1000. If both are damaged,
344 force is -1000. Having shields down subtracts an additional 1000.
346 * Enemy has forces equal to the energy of the attacker plus
347 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
348 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
350 Attacker Initial energy levels (nominal):
351 Klingon Romulan Commander Super-Commander
354 Good 450 800 1300 1750
355 Expert 475 850 1350 1875
356 Emeritus 500 900 1400 2000
357 VARIANCE 75 200 200 200
359 Enemy vessels only move prior to their attack. In Novice - Good games
360 only commanders move. In Expert games, all enemy vessels move if there
361 is a commander present. In Emeritus games all enemy vessels move.
363 * If Enterprise is not docked, an agressive action is taken if enemy
364 forces are 1000 greater than Enterprise.
366 Agressive action on average cuts the distance between the ship and
367 the enemy to 1/4 the original.
369 * At lower energy advantage, movement units are proportional to the
370 advantage with a 650 advantage being to hold ground, 800 to move forward
371 1, 950 for two, 150 for back 4, etc. Variance of 100.
373 If docked, is reduced by roughly 1.75*game.skill, generally forcing a
374 retreat, especially at high skill levels.
376 * Motion is limited to skill level, except for SC hi-tailing it out.
380 # tactical movement for the bad guys
382 # This should probably be just game.comhere + game.ishere
383 if game.skill >= SKILL_EXPERT:
384 nbaddys = int((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0)
386 nbaddys = game.comhere + game.ishere
387 dist1 = ship.distance()
388 mdist = round(dist1 + 0.5) # Nearest integer distance
389 # If SC, check with spy to see if should high-tail it
390 if ship.type == "Super-Commander" and \
391 (ship.power <= 500.0 or (game.condition==docked and not damaged("DPHOTON"))):
395 # decide whether to advance, retreat, or hold position
396 forces = ship.power + 100.0*len(game.quad.enemies()) + 400*(nbaddys-1)
398 forces += 1000.0 # Good for enemy if shield is down!
399 if not damaged("DPHASER") or not damaged("DPHOTON"):
403 forces -= 0.2*(game.energy - 2500.0);
404 if damaged("DPHOTON"):
407 forces -= 50.0*game.torps
409 # phasers and photon tubes both out!
412 if forces <= 1000.0 and game.condition != "docked": # Typical case
413 motion = ((forces+200.0*Rand())/150.0) - 5.0
415 if forces > 1000.0: # Very strong -- move in for kill
416 motion = (1.0-square(Rand()))*dist1 + 1.0
417 if game.condition == "docked" and "base" in game.options:
418 # protected by base -- back off !
419 motion -= game.skill * (2.0-Rand()**2)
421 proutn("=== MOTION = %1.2f, FORCES = %1.2f, ", motion, forces)
422 # don't move if no motion
425 # Limit motion according to skill
426 if abs(motion) > game.skill:
431 # calculate preferred number of steps
433 if motion > 0 and nsteps > mdist: # don't overshoot
435 if nsteps > QUADSIZE: # This shouldn't be necessary
437 if nsteps < 1: # This shouldn't be necessary
440 proutn("NSTEPS = %d:", nsteps)
441 # Compute preferred values of delta X and Y
442 me = game.sector - com;
443 if 2.0 * abs(me.x) < abs(me.y):
445 if 2.0 * abs(me.y) < abs(game.sector.x-com.x):
447 if me.x != 0: me.x = sgn(me.x*motion)
448 if me.y != 0: me.y = sgn(me.y*motion)
451 for ll in range(nsteps):
454 # Check if preferred position available
458 attempts = 0 # Settle meysterious hang problem
459 while attempts < 20 and not success:
461 if look.x < 1 or look.x > QUADSIZE:
462 if motion < 0 and tryexit(look, ship, bugout):
464 if krawl.x == me.x or me.y == 0:
466 look.x = next.x + krawl.x
468 elif look.y < 1 or look.y > QUADSIZE:
469 if motion < 0 and tryexit(look, ship, bugout):
471 if krawl.y == me.y or me.x == 0:
473 look.y = next.y + krawl.y
475 elif "ramming" in game.options and game.quad[look] != IHDOT:
476 # See if we should ram ship
477 if game.quad[look] == game.ship and ienm in (IHC, IHS):
480 if krawl.x != me.x and me.y != 0:
481 look.x = next.x + krawlx
483 elif krawly != me.y and me.x != 0:
484 look.y = next.y + krawly
487 break # we have failed
498 # Put ship in place within same quadrant
499 if next != ship.location:
501 if not damaged("DSRSENS") or game.condition == "docked":
502 proutn("*** %s from sector %s" % (ship, ship.location))
503 if ship.distance() < dist1:
504 prout(" advances to sector %s" % ship.location)
506 prout(" retreats to sector %s" % ship.location)
507 ship.sectormove(next)
510 "Allow enemies to move."
511 for enemy in self.quad.enemies():
512 if enemy.type == "Commander":
515 for enemy in self.quad.enemies():
516 if enemy.type == "Super-Commander":
519 # Ff skill level is high, move other Klingons and Romulans too!
520 # Move these last so they can base their actions on what the
522 if game.skill >= SKILL_EXPERT and "movebaddy" in game.options:
523 for enemy in self.quad.enemies():
524 if enemy.type in ("Klingon", "Romulan"):
528 def movescom(ship, avoid):
529 # commander movement helper
531 if game.state.kscmdr == game.quadrant or \
532 game.state.galaxy[iq].supernova or \
533 game.state.galaxy[iq].klingons > 8:
536 # Avoid quadrants with bases if we want to avoid Enterprise
537 for base in game.state.starbases:
538 if base.location == ship.location:
540 if game.justin and not game.iscate:
542 # Super-Commander has scooted, Remove him from current quadrant.
543 if game.state.kscmdr == game.quadrant:
547 unschedule("FSCDBAS")
548 if game.condition != "docked":
550 ship.sectormove(None)
552 game.state.galaxy[game.state.kscmdr].klingons -= 1
553 game.state.kscmdr = iq
554 game.state.galaxy[game.state.kscmdr].klingons += 1
555 # check for a helpful planet in the destination quadrant
556 for planet in game.state.plnets:
557 if planet.location == game.state.kscmdr and planet.crystals=="present":
559 game.state.plnets.remove(planet)
564 prout("Lt. Uhura- \"Captain, Starfleet Intelligence reports")
565 proutn(_(" a planet in "))
566 proutn(cramlc(quadrant, game.state.kscmdr))
567 prout(" has been destroyed")
568 prout(" by the Super-commander.\"")
570 return False # looks good!
573 # move the Super Commander
577 # Decide on being active or passive
578 passive = ((NKILLC+NKILLK)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) \
579 or (game.state.date-game.indate) < 3.0)
580 if not game.iscate and passive:
581 # compute move away from Enterprise
582 idelta = game.state.kscmdr - game.quadrant
583 if distance(game.state.kscmdr) > 2.0:
585 idelta,x = game.state.kscmdr.y-game.quadrant.y
586 idelta,y = game.quadrant.x-game.state.kscmdr.x
588 if len(game.state.bases):
589 unschedule("FSCMOVE")
591 sc = game.state.kscmdr
592 # compute distances to starbases
593 game.starbases.sort(lambda x, y: cmp(distance(x, game.quadrant), distance(y, game.quadrant)))
594 # look for nearest base without a commander, no Enterprise, and
595 # without too many Klingons, and not already under attack.
596 nearest = filter(game.starbases,
597 lambda x: game.state.galaxy[x].supernova \
598 and game.state.galaxy[x].klingons <= 8)
599 if game.quadrant in nearest:
600 nearest.remove(game.quadrant)
601 if game.battle in nearest:
602 nearest.remove(game.battle)
603 # if there is a commander, and no other base is appropriate,
604 # we will take the one with the commander
605 nocmd = filter(lambda x: x.location not in game.state.kcmdr, nearest)
609 if len(nearest) == 0:
610 return # Nothing suitable -- wait until next time
611 # decide how to move toward base
612 idelta = ibq - game.state.kscmdr
613 # maximum movement is 1 quadrant in either or both axis
615 # try moving in both x and y directions
616 iq = game.state.kscmdr + idelta
617 if movescom(iq, passive):
618 # failed -- try some other maneuvers
619 if ideltax==0 or ideltay==0:
622 iq.y = game.state.kscmdr.y + 1
623 if movescom(iq, passive):
624 iq.y = game.state.kscmdr.y - 1
625 movescom(iq, passive)
627 iq.x = game.state.kscmdr.x + 1
628 if movescom(iq, passive):
629 iq.x = game.state.kscmdr.x - 1
630 movescom(iq, passive)
632 # try moving just in x or y
633 iq.y = game.state.kscmdr.y
634 if movescom(iq, passive):
635 iq.y = game.state.kscmdr.y + ideltay
636 iq.x = game.state.kscmdr.x
637 movescom(iq, passive)
639 if game.state.rembase == 0:
640 unschedule("FSCMOVE")
642 for ibq in game.bases:
643 if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
646 return # no, don't attack base!
649 schedule("FSCDBAS", 1.0 +2.0*Rand())
650 if is_scheduled("FCDBAS"):
651 postpone("FSCDBAS", scheduled("FCDBAS")-game.state.date)
652 if not communicating():
658 proutn(_("Lt. Uhura- \"Captain, the starbase in "))
659 proutn(cramlc(quadrant, game.state.kscmdr))
661 prout(" reports that it is under attack from the Klingon Super-commander.")
662 proutn(" It can survive until stardate %d.\"",
663 int(scheduled(FSCDBAS)))
666 prout("Mr. Spock- \"Captain, shall we cancel the rest period?\"")
670 game.optime = 0.0 # actually finished
672 # Check for intelligence report
673 if (Rand() > 0.2 or not communicating() or
674 not game.state.galaxy[game.state.kscmdr].charted):
679 prout(_("Lt. Uhura- \"Captain, Starfleet Intelligence reports"))
680 proutn(_(" the Super-commander is in "))
681 proutn(cramlc(quadrant, game.state.kscmdr))
686 "Move the Tholian (an instance of ship type pointed at by game.tholian)."
687 if not game.tholian or game.justin:
690 if game.tholian.location.x == 1 and game.tholian.location.y == 1:
693 elif game.tholian.location.x == 1 and game.tholian.location.y == QUADSIZE:
694 next.x = next.y = QUADSIZE
695 elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == QUADSIZE:
698 elif game.tholian.location.x == QUADSIZE and game.tholian.location.y == 1:
701 # something is wrong!
704 # Do nothing if we are blocked
705 if game.quad[next] != empty and not isinstance(game.quad[next]. web):
708 im = (next - game.tholian.location).sgn()
709 if game.tholian.x != next.x:
711 while game.tholian.location.x != next.x:
712 game.tholian.location.x += im.x
713 if game.quad[game.tholian.location] == empty:
714 game.quad[game.tholian.location] = web()
715 elif game.tholian.y != next.y:
717 while game.tholian.y != next.y:
718 game.tholian.y += im.y
719 if game.quad[game.tholian.location] == empty:
720 game.quad[game.tholian.location] = web()
721 # web is done, move ship
722 game.tholian.movesector(next)
723 # check to see if all holes plugged
724 for i in range(1, QUADSIZE+1):
725 if (not isinstance(game.quad[(1,i)],web)) and game.quad[(1,i)]!=game.tholian:
727 if (not isinstance(game.quad[(QUADSIZE,i)],web)) and game.quad[(QUADSIZE,i)]!=game.tholian:
729 if (not isinstance(game.quad[(i,1)],web)) and game.quad[(i,1)]!=game.tholian:
731 if (not isinstance(game.quad[(i.QUADSIZE)],web)) and game.quad[(i,QUADSIZE)]!=game.tholian:
733 # All plugged up -- Tholian splits
734 game.quad[game.tholian] = web()
735 ship.movesector(None)
736 crmena(True, IHT, sector, game.tholian)
737 prout(" completes web.")