diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9936d17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/.vs/ +.vs/ +/__pycache__/ +__pycache__/ diff --git a/AbsDisplay.py b/AbsDisplay.py new file mode 100644 index 0000000..61ac562 --- /dev/null +++ b/AbsDisplay.py @@ -0,0 +1,59 @@ +import abc + +class abs_display(abc.ABC): + ''' + The plan is to have several places to + display information. The idea is to + use only one display to show a certain + type of message. + + Networked screens (let alone game + play) would also be a great idea. + + Here is how we could to that: + https://www.oreilly.com/library/view/tcpip-and-udpip/9781484294543/ + + Event logging might be in there somewhere + as well? + ''' + + ST_DEFAULT = 'd' + ST_CONSOLE = 'c' + ST_NETWORK = 'n' + + def __init__(self, type_ = ST_DEFAULT, width = 80, height = 24): + super().__init__() + self.screen_type = type_ + self.width = width + self.height = height + self.xpos = self.ypos = -1 + + @abc.abstractmethod + def display(self, message): + pass + + def show_strings(self, string_list): + ''' + Enumerate an array of strings into + self.display. + ''' + for string in string_list: + self.display(string) + self.display() + + def show_banner(self, string_list, star = '*'): + ''' + Enumerate an array of strings into a + single-character self.display box. + ''' + if not star: + star = '*' + star = star[0] + sz = len(max(string_list, key=len)) + max_ = sz + 4 + self.display(star * max_) + for str_ in string_list: + self.display(star + ' ' + str_.center(sz) + ' ' + star) + self.display(star * max_) + return sz + diff --git a/AbsShip.py b/AbsShip.py new file mode 100644 index 0000000..a686abc --- /dev/null +++ b/AbsShip.py @@ -0,0 +1,17 @@ +import abc +import random + +from PyTrek import Glyphs as Glyphs +from PyTrek.Quips import Quips as Quips +from PyTrek.Sector import Sector as Sector + +class AbsShip(abc.ABC): + ''' The first step, into a much larger universe ... ''' + + def __init__(self): + self.shield_level = 0 + + @abc.abstractmethod + def get_glyph(self): + pass + diff --git a/Calculators.py b/Calculators.py new file mode 100644 index 0000000..23530b2 --- /dev/null +++ b/Calculators.py @@ -0,0 +1,156 @@ +from math import sqrt +import random + +from PyTrek.MapGame import * +from PyTrek.AbsShip import * +from PyTrek.Points import * +from PyTrek.Difficulity import Probabilities + +class Calc(): + + @staticmethod + def surrounding(pos): + ''' + Return the complete set of + points surrounding a piece. + Sanity checking is not performed. + ''' + results = [] + if pos: + above = pos.ypos - 1 + below = pos.ypos + 1 + left = pos.xpos - 1 + right = pos.xpos + 1 + results.append([left, above]) + results.append([right, below]) + results.append([left, below]) + results.append([right, above]) + results.append([pos.xpos, above]) + results.append([pos.xpos, below]) + results.append([left, pos.ypos]) + results.append([right, pos.ypos]) + return results + + @staticmethod + def distance(x1, y1, x2, y2): + x = x2 - x1 + y = y2 - y1 + return sqrt(x * x + y * y) + + @staticmethod + def sublight_navigation(game): + dest_sys = game.read_xypos() + if not dest_sys: + game.display("Invalid course.") + game.display() + return + + + dist = Calc.distance(game.game_map.xpos, game.game_map.ypos, + dest_sys.xpos, dest_sys.ypos) + energy_required = int(dist) + if energy_required >= game.enterprise.energy: + game.display("Insufficient energy move to that location.") + game.display() + return + + game.display() + game.display("Sub-light engines engaged.") + game.enterprise.energy -= energy_required + game.display() + game.move_to(dest_sys) + + game.time_remaining -= 1 + game.star_date += 1 + + game.enterprise.short_range_scan(game) + + if game.enterprise.docked: + game.display("Lowering shields as part of docking sequence...") + game.display("Enterprise successfully docked with starbase.") + game.display() + else: + if game.game_map.count_area_klingons() > 0: + ShipKlingon.attack_if_you_can(game) + game.display() + elif not game.enterprise.repair(game): + game.enterprise.damage(game, Probabilities.LRS) + + @staticmethod + def warp_navigation(game): + if game.enterprise.navigation_damage > 0: + max_warp_factor = 0.2 + random.randint(0, 8) / 10.0 + game.display(f"Warp engines damaged. Maximum warp factor: {max_warp_factor}") + game.display() + + dest_sys = game.read_sector() + if not dest_sys: + game.display("Invalid course.") + game.display() + return + + game.display() + + if dest_sys.warp < 1: + dest_sys.warp = 1 + + dist = dest_sys.warp * 8 + energy_required = int(dist) + if energy_required >= game.enterprise.energy: + game.display("Insufficient energy to travel at that speed.") + game.display() + return + else: + game.display("Warp engines engaged.") + game.display() + game.enterprise.energy -= energy_required + + n = lambda dist, speed: round(0.5 + speed/dist) #Quick calculator for time passage + + game.time_remaining -= n(abs(game.game_map.sector - dest_sys.sector),dest_sys.warp) # before moving there, adjust time by lambda calc + game.star_date += n(abs(game.game_map.sector - dest_sys.sector),dest_sys.warp) + + game.move_to(dest_sys) + + game.enterprise.short_range_scan(game) + + if game.enterprise.docked: + game.display("Lowering shields as part of docking sequence...") + game.display("Enterprise successfully docked with starbase.") + game.display() + else: + if game.game_map.count_area_klingons() > 0: + ShipKlingon.attack_if_you_can(game) + game.display() + elif not game.enterprise.repair(game): + game.enterprise.damage(game, Probabilities.RANDOM) + + + @staticmethod + def show_starbase(game): + game.display() + bases = game.game_map.get_all(Glyphs.STARBASE) + game.display() + if bases: + game.display("Starbases:") + for info in bases: + area = info[0]; base = info[1] + game.display(f"\tSector #{area.number} at [{base.xpos},{base.ypos}].") + else: + game.display("There are no Starbases.") + game.display() + + + @staticmethod + def show_torp_targets(game): + game.display() + kships = game.game_map.get_area_klingons() + if len(kships) == 0: + game.display("There are no enemies in this sector.") + return + + game.display("Enemies:") + for ship in kships: + game.display(f"\tKlingon [{ship.xpos},{ship.ypos}].") + game.display() + diff --git a/Console.py b/Console.py new file mode 100644 index 0000000..3f2e596 --- /dev/null +++ b/Console.py @@ -0,0 +1,40 @@ +from PyTrek.AbsDisplay import abs_display +from PyTrek.Points import * + +class Con(abs_display): + ''' + The best place to start is by encapsulating the default + display. Will add screen metadata for it all, later. + ''' + def __init__(self): + super().__init__(abs_display.ST_CONSOLE) + + def display(self, message = ''): + print(message) + + def read(self, prompt=''): + return input(prompt) + + def read_double(self, prompt): + text = input(prompt) + try: + value = float(text) + return value + except: + pass + return False + + def read_sector(self, prompt= "Helm: sector 1-64, speed 1.0-9.0?"): + text = input(prompt + ': ') + return WarpDest.parse(text) + + def read_xypos(self, prompt= "Helm: a-h, 1-8?"): + text = input(prompt + ': ') + return SubDest.parse(text) + + +if __name__ == '__main__': + con = Con() + con.display("Testing!") + con.show_banner(["Testing, too!"]) + con.show_banner(["Testing", " .......... too!"]) diff --git a/Controls.py b/Controls.py new file mode 100644 index 0000000..3654605 --- /dev/null +++ b/Controls.py @@ -0,0 +1,172 @@ +import random + +from PyTrek import TrekStrings +from PyTrek import Glyphs +from PyTrek.ShipKlingon import ShipKlingon +from PyTrek.ShipEnterprise import ShipEnterprise +from PyTrek.Calculators import Calc +from PyTrek.Reports import Stats +from PyTrek.Quips import Quips +from PyTrek.Difficulity import Probabilities + +class Control(): + + @staticmethod + def computer(game): + if game.enterprise.computer_damage > 0: + game.display(Quips.jibe_damage('computer')) + game.display() + return + game.show_strings(TrekStrings.CPU_CMDS) + command = game.read("Enter computer command: ").strip().lower() + if command == "rec": + Stats.show_galactic_status(game) + elif command == "sta": + Stats.show_ship_status(game) + elif command == "tor": + Calc.show_torp_targets(game) + elif command == "bas": + Calc.show_starbase(game) + else: + game.display() + game.display("Invalid computer command.") + game.display() + game.enterprise.damage(game, Probabilities.COMPUTER) + + + @staticmethod + def phasers(game): + if game.enterprise.phaser_damage > 0: + game.display(Quips.jibe_damage("phaser")) + game.display() + return + kships = game.game_map.get_area_klingons() + if len(kships) == 0: + game.display("There are no Klingon ships in this sector.") + game.display() + return + game.display("Phasers locked on target.") + phaser_energy = game.read_double("Enter phaser energy (1--{0}): ".format(game.enterprise.energy)) + if not phaser_energy or phaser_energy < 1 or phaser_energy > game.enterprise.energy: + game.display("Invalid energy level.") + game.display() + return + game.display() + game.display("Firing phasers...") + destroyed_ships = [] + for ss, ship in enumerate(kships): + game.enterprise.energy -= int(phaser_energy) + if game.enterprise.energy < 0: + game.enterprise.energy = 0 + break + dist = Calc.distance(game.game_map.xpos, + game.game_map.ypos, + ship.xpos, ship.ypos) + delivered_energy = phaser_energy * (1.0 - dist / 11.3) + ship.shield_level -= int(delivered_energy) + if ship.shield_level <= 0: + game.display(f"Enemy ship destroyed at [{ship.xpos + 1},{ship.ypos + 1}].") + game.display(Quips.jibe_defeat('enemy')) + destroyed_ships.append(ship) + else: + game.display(f"Hit ship at [{ship.xpos + 1},{ship.ypos + 1}].") + game.display(f"Enemy shield down to {ship.shield_level}.") + game.game_map.remove_area_items(destroyed_ships) + if game.game_map.count_area_klingons() > 0: + game.display() + ShipKlingon.attack_if_you_can(game) + game.display() + game.enterprise.damage(game, Probabilities.PHASERS) + + + def shields(game): + game.display("--- Shield Controls ----------------") + game.display("add = Add energy to shields.") + game.display("sub = Subtract energy from shields.") + game.display() + command = game.read("Enter shield control command: ").strip().lower() + game.display() + if command == "add": + adding = True + max_transfer = game.enterprise.energy + elif command == "sub": + adding = False + max_transfer = game.enterprise.shield_level + else: + game.display("Invalid command.") + game.display() + return + transfer = game.read_double( + "Enter amount of energy (1--{0}): ".format(max_transfer)) + if not transfer or transfer < 1 or transfer > max_transfer: + game.display("Invalid amount of energy.") + game.display() + return + game.display() + if adding: + game.enterprise.energy -= int(transfer) + game.enterprise.shield_level += int(transfer) + else: + game.enterprise.energy += int(transfer) + game.enterprise.shield_level -= int(transfer) + game.display("Shield strength is now {0}. Energy level is now {1}.".format(game.enterprise.shield_level, game.enterprise.energy)) + game.display() + game.enterprise.damage(game, Probabilities.SHIELDS) + + + def torpedos(game): + if game.enterprise.photon_damage > 0: + game.display(Quips.jibe_damage('photon launcher')) + game.display() + return + if game.enterprise.photon_torpedoes == 0: + game.display("Photon torpedoes exhausted.") + game.display() + return + if game.game_map.count_area_klingons() == 0: + game.display("There are no Klingon ships in this sector.") + game.display() + return + shot = game.read_xypos() + if not shot: + game.display("Invalid shot.") + game.display() + return + game.display() + game.display("Photon torpedo fired...") + game.enterprise.photon_torpedoes -= 1 + hit = False + for ship in game.game_map.get_area_objects(): + if game.is_testing: + print(f'{ship.glyph}({ship.xpos},{ship.ypos}), shot({shot.xpos},{shot.ypos})') + if ship.xpos == shot.xpos and ship.ypos == shot.ypos: + if ship.glyph == Glyphs.KLINGON: + num = game.game_map.get_game_id(ship) + game.display(f"Klingon ship #{num} destroyed.") + game.display(Quips.jibe_defeat('enemy')) + game.game_map.remove_area_items([ship]) + hit = True + break + elif ship.glyph == Glyphs.STARBASE: + game.game_map.game_starbases -= 1 + num = game.game_map.get_game_id(ship) + game.display("Federation Starbase #{num} destroyed!") + game.display(Quips.jibe_defeat('commander')) + game.game_map.remove_area_items([ship]) + hit = True + break + elif ship.glyph == Glyphs.STAR: + num = game.game_map.get_game_id(ship) + game.display(f"Torpedo vaporizes star #{num}!") + game.display(Quips.jibe_defeat('academic')) + game.game_map.remove_area_items([ship]) + hit = True + break + if not hit: + game.display("Torpedo missed.") + if game.game_map.count_area_klingons() > 0: + game.display() + ShipKlingon.attack_if_you_can(game) + game.display() + game.enterprise.damage(game, Probabilities.PHOTON) + diff --git a/Difficulity.py b/Difficulity.py new file mode 100644 index 0000000..53a0410 --- /dev/null +++ b/Difficulity.py @@ -0,0 +1,49 @@ +import random + +class Probabilities: + ''' + Extracting allows for easier customization. + ''' + _LEVEL = 4 + RANDOM = -1 + NAV = 0 + SRS = 1 + LRS = 2 + SHIELDS = 3 + COMPUTER = 4 + PHOTON = 5 + PHASERS = 6 + + @staticmethod + def set_difficulity(game, level): + ''' + The HIGHER the level, the MORE difficult + the game will be. 0 = EASY, 6 = HIGHEST + ''' + if level < 0: level = 0 + if level > 6: level = 6 + Probabilities._LEVEL = level + + @staticmethod + def calc_damage(game, item): + ''' + How mch damage? + ''' + return random.randint(1, 3) + + @staticmethod + def should_damage_enterprise(game, item): + ''' + Should we damage? + Lowest level (above) eliminates damage to ITEM + ''' + if game.is_testing: + return False + if item == Probabilities.SHIELDS: + return False + if random.randint(0, 6) < Probabilities._LEVEL: + return True + return False + + + diff --git a/ErrorCollision.py b/ErrorCollision.py new file mode 100644 index 0000000..bab439b --- /dev/null +++ b/ErrorCollision.py @@ -0,0 +1,9 @@ +class ErrorEnterpriseCollision(Exception): + ''' + ... because some problems simply have to wait ... =) + ''' + def __init__(self, glyph): + super().__init__() + self.glyph = glyph + + diff --git a/Glyphs.py b/Glyphs.py new file mode 100644 index 0000000..ef41a81 --- /dev/null +++ b/Glyphs.py @@ -0,0 +1,7 @@ +SPACE = " " +STAR = " * " +STARBASE = ">S<" +ENTERPRISE = "" +KLINGON = "+K+" + +GLYPHS = SPACE, STARBASE, KLINGON, ENTERPRISE diff --git a/MapGame.py b/MapGame.py new file mode 100644 index 0000000..2de4058 --- /dev/null +++ b/MapGame.py @@ -0,0 +1,306 @@ +import random +import PyTrek.TrekStrings as TrekStrings + +from PyTrek import Glyphs as Glyphs +from PyTrek.ShipKlingon import ShipKlingon as ShipKlingon +from PyTrek.Points import * +from PyTrek.Sector import Sector as Sector +from PyTrek.ErrorCollision import ErrorEnterpriseCollision as ErrorEnterpriseCollision + +import PyTrek.MapSparse as MapSparse + +class GameMap(MapSparse.SparseMap): + + def __init__(self): + ''' + Prepare a 'Trekian GameMap for future + initialization. + ''' + super().__init__() + self.sector = -1 + self.xpos = self.ypos = -1 + self.game_stars = -1 + self.game_klingons = -1 + self.game_starbases = -1 + self.last_nav = Dest() + + def place(self, takers): + ''' + Randomly place game-objects, into the map. + ''' + if not sum(takers): + return None + takers =list(takers) + for which, nelem in enumerate(takers): + if not nelem: + continue + to_take = random.randint(0, nelem) + if nelem == 1: + to_take = 1 + if not to_take: + continue + taken = 0 + while taken != to_take: + ss = random.randrange(1, 64) # Ignore "Outer Limits" + area = self.get_area(ss) + should_take = random.randint(1, 8) + if which == 0: + if area.count_glyphs(Glyphs.STARBASE) != 0: + continue + area.place_glyph(Glyphs.STARBASE) + elif which == 1: + area.place_glyph(Glyphs.STAR) + elif which == 2: + if area.count_glyphs(Glyphs.KLINGON) > 3: + continue + area.place_glyph(Glyphs.KLINGON) + taken += 1 + if taken == to_take: + break; + takers[which] -= taken + return tuple(takers) + + def enterprise_in(self, dest=None): + ''' + Place the ENTERPRISE at the destination, else a + random one. + + Will raise an ErrorEnterpriseCollision, upon same. + + Returns the final x, y location upon success + ''' + area = self.pw_area() + if not dest: + return area.place_glyph(Glyphs.ENTERPRISE) + berror = False + if area: + for p in area._pieces: + if p.xpos == dest.xpos and p.ypos == dest.ypos: + pos = area.place_glyph(Glyphs.ENTERPRISE) + berror = p.glyph + pos = area.place_glyph(Glyphs.ENTERPRISE, dest) + if berror: + raise ErrorEnterpriseCollision(berror) + if pos: + return pos + return False + + def enterprise_location(self): + ''' + Get Enterprise location. False if not found. + ''' + area = self.pw_area() + if area: + for obj in area._pieces: + if obj.glyph == Glyphs.ENTERPRISE: + return obj.xpos, obj.ypos + return False + + def enterprise_out(self)->None: + ''' + Remove the ENTERPRISE from the present AREA + ''' + pos = self.enterprise_location() + if pos: + self.clear_area(*pos) + + def place_glyph(self, glyph, dest=None)->bool: + ''' + Place the glyph at the destination, else a random one + ''' + area = self.pw_area() + if area: + pos = area.place_glyph(self, glyph, dest) + if pos: + return True + return False + + def clear_area(self, xpos, ypos)->None: + ''' + Remove ANYTHING from the present AREA + ''' + area = self.pw_area() + if area: + area.remove(xpos, ypos) + + def pw_area(self)->MapSparse.SparseMap.Area: + ''' + Return the internal / sparsely populated AREA object. + Return an empty / default AREA upon coordinate error. + ''' + if self.sector > 0: + for area in self.areas(): + if area.number == self.sector: + return area + return MapSparse.SparseMap.Area() + + def scan_sector(self, sector)->Sector: + ''' + Return Sector() information (e.g. LRS) for a specific AREA. + Return empty Sector() upon error. + ''' + area = self.get_area(sector) + if area: + return Sector.from_area(area) + return Sector() + + def count_area_klingons(self)->int: + ''' + How many surround, U.S.S? + ''' + return self._count_area(Glyphs.KLINGON) + + def count_area_starbases(self)->int: + ''' + How many surround, U.S.S? + ''' + return self._count_area(Glyphs.STARBASE) + + def count_area_stars(self)->int: + ''' + How many surround, U.S.S? + ''' + return self._count_area(Glyphs.STAR) + + def _count_area(self, glyph)->int: + ''' + Tally the number of glyphs in the DEFAULT AREA + ''' + count = 0 + area = self.pw_area() + if area: + for obj in area._pieces: + if obj.glyph == glyph: + count += 1 + return count + + def update_counts(self)->None: + ''' + Update this map's official game-pieces-in-play tally + ''' + self.game_klingons = self.game_starbases = self.game_stars = 0 + for area in self.areas(): + self.game_klingons += area.count_glyphs(Glyphs.KLINGON) + self.game_starbases += area.count_glyphs(Glyphs.STARBASE) + self.game_stars += area.count_glyphs(Glyphs.STAR) + + def remove_area_items(self, piece_array)->None: + ''' + Remove a collection of pieces (e.g. ships), + from the PRESENT / default AREA. + ''' + area = self.pw_area() + for obj in piece_array: + area.remove(obj.xpos, obj.ypos) + self.update_counts() + + def get_area_klingons(self)->list: + ''' + Return this Area's data for Kingons, in an array. + ''' + results = [] + area = self.pw_area() + for data in area.query(Glyphs.KLINGON): + ship = ShipKlingon() + ship.from_map(data.xpos, data.ypos) + results.append(ship) + return results + + def get_area_objects(self)->list: + ''' + Return the actual pieces, as maintained in the Area. + WARNING: Changes to this collection WILL update Area + content. + ''' + area = self.pw_area() + return area._pieces + + def get_game_id(self, piece)->str: + ''' + Uniquely identify a game piece / object. + ''' + area = self.pw_area() + num = (area.number * 100) + (piece.ypos * 8) + piece.xpos + return f"{piece.glyph[1]}x{num}" + + def get_all(self, glyph)->list: + ''' + Return [ [AREA, PIECE], ... ] for every glyph found. + ''' + results = [] + for area in self.areas(): + for piece in area.query(glyph): + results.append([area, piece]) + return results + + def get_pw_sector(self)->Sector: + ''' + Create a Sector() report for the DEFAULT AREA + ''' + area = self.pw_area() + return Sector.from_area(area) + + def get_map(self)->list: + ''' + Generate AREA map of the present sector. + ''' + area = self.pw_area() + return area.get_map() + + def _go_to(self, dest): + ''' + Either a WARP ~or~ a SUBSPACE destination is ok. + Place the main player (Enterprise, for now) into the Area. + Returns the final, effective, arrival Dest(). + ''' + if not dest: + return + if not self.last_nav.is_null(): + self.enterprise_out() + pos = None + if isinstance(dest, WarpDest): + if dest.sector > 0: + self.sector = dest.sector + dest.sector = self.sector + pos = self.enterprise_in() # SAFE WARP-IN! + self.last_nav.sector = dest.sector + self.last_nav.warp = dest.warp + else: + if dest.xpos != -1: + self.xpos = dest.xpos + self.ypos = dest.ypos + dest.xpos = self.xpos + dest.ypos = self.ypos + pos = self.enterprise_in(dest) # CAPIN' KNOWS BEST? + dest.xpos = pos[0] + dest.ypos = pos[1] + self.xpos = pos[0] + self.ypos = pos[1] + self.last_nav.xpos = pos[0] + self.last_nav.ypos = pos[1] + return self.last_nav.clone() + + def randomize(self, bases=None, stars=None, aliens=None)->None: + ''' + Randomly place the inventoried items into the map. + If no bases ot aliens are specified, a random number + will be selected. Stars are not required. Not having + any stars is ok. + ''' + if not aliens: + aliens = random.randint(5, 10) + if not bases: + bases = random.randint(2, 4) + self.game_starbases = bases + self.game_klingons = aliens + self.game_stars = stars + self.init() + takers = bases, stars, aliens + while takers: + for lrs in self._map: + takers = self.place(takers) + if not takers: + break + + + diff --git a/MapSparse.py b/MapSparse.py new file mode 100644 index 0000000..1e1f98a --- /dev/null +++ b/MapSparse.py @@ -0,0 +1,237 @@ +import random +import PyTrek.TrekStrings as TrekStrings +import PyTrek.Glyphs as Glyphs + +class SparseMap: + ''' + Minimalist mapping. + On-demand 'sparse-array' expansion to full-Area views. + ''' + + class Region(): + ''' + Regional meta for a collection of Area maps. + ''' + def __init__(self): + self.name = "" + + class Area(): + ''' + A minimalist collection of Area-plotted Glyphs. + Area numbers are 1's based. + Area plotting is 0's based. + Area names are Trekian. + ''' + class Piece: + ''' + Sparse data management for an increasingly minimalist world. + ''' + def __init__(self, xpos, ypos, glyph=Glyphs.SPACE): + self.xpos = xpos + self.ypos = ypos + self.glyph = glyph + + def __init__(self): + ''' + Create an empty AREA. + ''' + self.name = "" + self.number = -1 + self._pieces = [] + + def is_null(self): + ''' + See if this AREA has anything important. + ''' + dum = SparseMap.Area() + return dum.name == self.name and \ + dum.number == self.number and \ + len(dum.objs) == len(self._pieces) + + def is_empty(self): + ''' Checks to see if the Area has anything ...''' + return len(self._pieces) == True + + def item_count(self)->int: + ''' The number of pieces / items in the randint ...''' + return len(self._pieces) + + def remove(self, xpos, ypos): + ''' Remove an item from the Area. ''' + for ss, obj in enumerate(self._pieces): + if obj.xpos == xpos and obj.ypos == ypos: + self._pieces.remove(obj) + return + + def get_map(self)->list: + ''' + Generate a map of this randint. Map is full + of Glyphs.SPACE on error. + ''' + results = [[Glyphs.SPACE for _ in range(8)] for _ in range(8)] + for obj in self._pieces: + results[obj.ypos][obj.xpos] = obj.glyph # ASSURED + return results + + def __str__(self): + result = '' + for line in self.get_map(): + result += ''.join(line) + result += '\n' + return result + + def range_ok(self, xpos, ypos): + ''' Verify that coordinates are plottable. ''' + if xpos < 0 or ypos < 0 or \ + xpos > 7 or ypos > 7: + return False + return True + + def query(self, glyph): + ''' + Clone each Piece for a glyph into a new + collection. Changes to the results WILL NOT + affect the glyph in the AREA. + ''' + results = [] + for p in self._pieces: + if p.glyph == glyph: + results.append(SparseMap.Area.clone(p)) + return results + + def count_glyphs(self, glyph): + ''' + Tally the number of glyphs that we have in the Area. + ''' + count = 0 + for p in self._pieces: + if p.glyph == glyph: + count += 1 + return count + + def plot_glyph(self, xpos, ypos, glyph): + ''' + Sync (update or add) a glyph to the sparse array. + Return coordinate occupied, else None on error. + ''' + if self.range_ok(xpos, ypos) is False: + return None + for p in self._pieces: + if p.xpos is xpos and p.ypos is ypos: + p.glyph = glyph + return xpos, ypos + self._pieces.append(SparseMap.Area.Piece(xpos, ypos, glyph)) + return xpos, ypos + + def place_glyph(self, glyph, dest=None): + ''' Place / randomly place a glyph into the Map. ''' + area = self.get_map() + if not dest: + while True: + xpos = random.randint(0,7) + ypos = random.randint(0,7) + if area[xpos][ypos] == Glyphs.SPACE: + self.plot_glyph(xpos, ypos, glyph) + return xpos, ypos + else: + self.plot_glyph(dest.xpos, dest.ypos, glyph) + return dest.xpos, dest.ypos + + @staticmethod + def clone(piece): + ''' Copy a piece. ''' + return SparseMap.Area.Piece(piece.xpos, piece.ypos, piece.glyph) + + def __init__(self): + ''' + Create the uninitialized REGION. Use .init() to + populate same with AREAs. + ''' + self.initalized = False + self._map = [[[y,x] for y in range(8)] for x in range(8)] + + def init(self, reset=False): + ''' + Fill a newly-created map with a set of randomly-named AREAs. + ''' + if not reset and self.initalized: + return + for xx, row in enumerate(self._map): + lrs = SparseMap.Region() + for yy, col in enumerate(row): + self._map[xx][yy] = [lrs, SparseMap.Area()] + self.name_areas() + self.initalized = True + + def get_area(self, which): + ''' + Get an AREA from the map. + Area identifiers are 1's based. + Returns False on under / over flows. + ''' + if which < 1: + return False + if which >= 65: + return False + which -= 1 # ASSURED + fid = which / 8 + ypos = 0 + xpos = int(fid) + if not fid.is_integer(): + ypos = which %8 + return self._map[xpos][ypos][1] + + def data(self): + ''' Enumerate thru every [Region, Area] in the Map ''' + for row in self._map: + for col in row: + yield col + + def areas(self): + ''' Enumerare thru every Area on the Map ''' + for row in self._map: + for col in row: + yield col[1] + + def name_areas(self): + ''' + Assign / re-assign 'Trekian names. + ''' + names = list(TrekStrings.AREA_NAMES) + for num, area in enumerate(self.areas(),1): + index = random.randrange(0, len(names)) + area.name = names[index] + area.number = num + del names[index] + + def get_sector_names(self): + ''' + Return a list of [secor numbers, sector_name] pairs. + ''' + results = [] + temp = [] + for ss, col in enumerate(self.areas(),1): + temp.append([col.number,col.name]) + if ss % 8 == 0: + results.append(temp) + temp = [] + results.append(temp) + return results + + def plot(self, ones_based, xpos, ypos, glyph): + ''' + Add an item to a MAP using the 1's based AREA identifier. + Coordinates here are ZERO based. + ''' + for area in self.areas(): + if area.number == ones_based: + area.plot_glyph(xpos, ypos, glyph) + return True + return False + + def plot_ones_based(self, ones_based, xpos, ypos, glyph): + ''' + Add an item to a MAP using the 1's based AREA identifier. + Coordinates here are ONES based. + ''' + return self.plot(ones_based, xpos -1, ypos -1, glyph) diff --git a/Points.py b/Points.py new file mode 100644 index 0000000..6b1217c --- /dev/null +++ b/Points.py @@ -0,0 +1,108 @@ +class WarpDest(): + ''' Warp Speed: + One's based GALAXY navigation. + Guaranteed SAFE placement. + ''' + def __init__(self, sector=-1, warp=0): + if sector > 64: sector = 64 # zOuter Limits =) + if warp > 10: warp = 10 + if warp < 0: warp = 0 + self.warp = warp + self.sector = sector + + @staticmethod + def parse(dest, sep=','): + ''' + Parse: sector-num, speed-float - None on error + Example: 5,1.1 + ''' + dest = str(dest) + cols = dest.split(sep) + if len(cols) == 2: + try: + sector = int(cols[0].strip()) + if sector < 1: + sector = 1 + speed = float(cols[1].strip()) + if speed < 0: speed = 0.1 + if speed > 9: speed = 9.0 + return WarpDest(sector, speed) + except: + pass + return None + + +class SubDest(): + ''' Sublight Navigation: + Zero based, AREA placement. + Caveat, User! ;-) + ''' + def __init__(self, xpos=-1, ypos=-1): + if xpos > 7: xpos = 7 + if ypos > 7: ypos = 7 + if xpos < 0: xpos = 0 + if ypos < 0: ypos = 0 + self.xpos = xpos + self.ypos = ypos + + @staticmethod + def parse(dest, sep=','): + ''' + WARNING: USER 1's -> 0-BASED TRANSLATION HERE + + Parse: [a-h], ypos + or + #,# + Return None on error + Example: b,5 + ''' + dest = str(dest) + cols = dest.split(sep) + if len(cols) == 2: + try: + alph = cols[0].strip().lower()[0] + num = 0 + if alph.isalpha(): + num = ord(alph) - 96 # 'a' == 1 + else: + num = int(alph) + xpos = num + ypos = int(cols[1].strip()[0]) + return SubDest(xpos-1, ypos-1) + except: + pass + return None + + +class Dest(WarpDest, SubDest): + + def __init__(self): + WarpDest.__init__(self) + SubDest.__init__(self) + + def is_null(self): + return self.xpos == 0 and \ + self.sector == -1 + + def clone(self): + result = Dest() + result.xpos = self.xpos + result.ypos = self.ypos + result.sector = self.sector + result.warp = self.warp + return result + + +if __name__ == '__main__': + test = Dest() + assert(test.is_null() == True) + test.xpos = test.ypos = 123 + test.sector = 22 + test.warp = 22 + assert(test.is_null() == False) + clone = test.clone() + assert(clone.sector == test.sector) + assert(clone.warp == test.warp) + assert(clone.xpos == test.xpos) + assert(clone.ypos == test.ypos) + diff --git a/PyTrek.png b/PyTrek.png new file mode 100644 index 0000000..0ca29f0 Binary files /dev/null and b/PyTrek.png differ diff --git a/PyTrek1.py b/PyTrek1.py new file mode 100644 index 0000000..6f49bb0 --- /dev/null +++ b/PyTrek1.py @@ -0,0 +1,149 @@ +import random + +import PyTrek.TrekStrings as TrekStrings +from PyTrek.Console import Con +from PyTrek.ShipKlingon import ShipKlingon as ShipKlingon +from PyTrek.ShipEnterprise import ShipEnterprise +from PyTrek.ShipStarbase import ShipStarbase +from PyTrek.Calculators import Calc +from PyTrek.Controls import Control +from PyTrek.Reports import Stats +from PyTrek.Points import * +from PyTrek.Quips import Quips +from PyTrek.MapGame import * + + +class Game(Con): + + def __init__(self): + self.is_testing = False + self.is_cloked = False # unable to be fired-upon + self.game_map = GameMap() + self.enterprise = ShipEnterprise() + self.star_date = 0 + self.time_remaining = 0 + self.destroyed = False + + def move_to(self, dest): + ''' + Move the player to a nav, or a sub, + destination. Handles docking, random + warp-in placement, as well as deliberate + collisions / rammming. + + Returns final resting coordinate on success. + Raises ErrorEnterpriseCollision on yikes. + ''' + pos = self.game_map._go_to(dest) + area = self.game_map.pw_area() + was_docked = self.enterprise.docked + self.enterprise.docked = False + for p in area._pieces: + if p.glyph == Glyphs.STARBASE: + for point in Calc.surrounding(pos): + if p.xpos == point[0] and \ + p.ypos == point[1]: + self.enterprise.docked = True + ShipStarbase.dock_enterprise(self.enterprise) + if was_docked and self.enterprise.docked == False: + ShipStarbase.launch_enterprise(self.enterprise) + return pos + + def game_on(self): + ''' + See if the game is still running. + ''' + running = self.enterprise.energy > 0 and not \ + self.destroyed and self.game_map.game_klingons > 0 and \ + self.time_remaining > 0 + return running + + def run(self): + ''' + The game loop - runs until the game is over. + ''' + self.show_strings(TrekStrings.LOGO_TREKER) + self.star_date = random.randint(2250, 2300) + self.time_remaining = random.randint(40, 45) + self.destroyed = False + stars = random.randint(500, 700) # 4096 = ALL + aliens = random.randint(14, 24) + starbases = random.randint(6, 8) + self.game_map.randomize(starbases, stars, aliens) + dest = WarpDest(64, 0) + self.move_to(dest) + self.game_map.get_area(64).name = 'Outer Limits' + self.print_mission() + + self.show_strings(TrekStrings.HELM_CMDS) + running = True + try: + while self.game_on(): + if not self.command_prompt(): + break + if self.is_testing: + self.destoryed = False + ShipStarbase.dock_enterprise(self.enterprise) + ShipStarbase.launch_enterprise(self.enterprise) + self.enterprise.shield_level = 1000 + + except ErrorEnterpriseCollision as ex: + if ex.glyph == Glyphs.KLINGON: + self.display("You flew into a KLINGON!") + if ex.glyph == Glyphs.STARBASE: + self.display("You flew into a STARBASE?") + if ex.glyph == Glyphs.STAR: + self.display("You flew into a STAR?") + self.destroyed = True + self.display() + Stats.show_exit_status(self) + self.display() + if self.destroyed == True: + self.display(Quips.jibe_fatal_mistake()) + self.display() + return False + + def command_prompt(self): + command = self.read("Enter command: ").strip().lower() + self.display() + if command == "nav": + Calc.warp_navigation(self) + if command == "sub": + Calc.sublight_navigation(self) + elif command == "srs": + self.enterprise.short_range_scan(self) + elif command == "lrs": + self.enterprise.long_range_scan(self) + elif command == "pha": + Control.phasers(self) + elif command == "tor": + Control.torpedos(self) + elif command == "she": + Control.shields(self) + elif command == "com": + Control.computer(self) + elif command.startswith('qui') or command.startswith('exi'): + return False + else: + self.show_strings(TrekStrings.HELM_CMDS) + return True + + def print_mission(self): + self.display("Mission: Destroy {0} Klingon ships in {1} stardates with {2} starbases.".format( + self.game_map.game_klingons, self.time_remaining, self.game_map.game_starbases)) + self.display() + + @staticmethod + def mainloop(): + import traceback + game = Game() + try: + game.run() + except Exception as ex: + print(ex) + # Stack trace: + traceback.print_exc() + + +Game.mainloop() + diff --git a/Quips.py b/Quips.py new file mode 100644 index 0000000..234253d --- /dev/null +++ b/Quips.py @@ -0,0 +1,123 @@ +import random + +DAMAGE_PREFIX = [ + "The main ", + "That @#$#@& ", + "Our cheap ", + "Darn-it captain, that ", + "Yikes, the ", + ] +DAMAGE_SUFFIX = [ + " has died. We're on it!", + " is out. We're working on it!", + " is almost repaired!", + " is dead.", + " is fried. Working as fast as we can!", + " is toast. Working as fast as I can!", + " is being replaced.", + " is dead. Please leave a message.", + ] +DEFEAT_PREFIX = [ + "A defeated ", + "The vengeful ", + "An angry ", + "The ejecting ", + "A confused ", + "Another ", + "Yet another ", + ] +DEFEAT_SUFFIX = [ + " says: `I'll be back!`", + " cries: ... 'a lucky shot!'", + " sighs bitterly.", + " dies.", + " is rescued.", + " is history.", + " is no more.", + " is recycled.", + " is eliminated.", + " was aborted. Few lives, matter?", + " ejects.", + " crew is rescued.", + " crew is spaced.", + " crew is recycled.", + " crew is recovered.", + " yells: 'thy mother mates poorly!'", + " snarls: 'lucky shot.'", + " laughs: 'you'll not do THAT again!'", + " says nothing.", + " screams: 'thy father is a Targ!'", + " yells: 'thine family eats bats!'", + " snarls: 'thine people eat vermin!'", + " curses: 'thy fathers spreadeth pox!'", + " yells: 'thy mother is progressive!'", + ] +MISTAKES = [ + "... the crew was not impressed ...", + "... that's going to leave a mark ...", + "... next time carry the 1?", + "... math lives matter ...", + "... its coming out of your pay ...", + "... this is not a bumper car ...", + "... life can be tough that way ...", + "... who ordered THAT take-out?", + "... random is, what random does ...", + "... you've got their attention ...", + "... next time, just text them?", + "... how rude!", + "... yes, karma CAN hurt ...", + "... life is but a dream!", + "... game over.", + "... starfleet will talk about this for years.", + "... who is going to pay for that?", + "... galactic insurance premiums skyrocket ...", + "... captain goes down with the starship ...", + "... we'll notify your next-of-kin.", + "... that was not in the script ...", + "... you never did THAT in the simulator ...", + ] + +QUITS = [ + "-Let's call it a draw?", + "-You call yourself a 'Trekkie?", + "Kobayashi Maru. Python for you?", + "(Spock shakes his head)", + "(Duras, stop laughing!)", + "(... and the Klingons rejoice)", + "(... and our enemies, rejoice)", + "Kobayashi Maru... Got Python?", + "(Kirk shakes his head)", + ] + +class Quips(): + + @staticmethod + def jibe(noun, prefix, suffix): + prand = random.randrange(0, len(prefix)) + srand = random.randrange(0, len(suffix)) + return prefix[prand] + noun + suffix[srand] + + @staticmethod + def jibe_quit(): + return QUITS[random.randrange(0, len(QUITS))] + + @staticmethod + def jibe_damage(noun): + if random.randrange(0, 100) > 25: + return f"{noun.capitalize()} damaged. Repairs are underway." + return Quips.jibe(noun, DAMAGE_PREFIX, DAMAGE_SUFFIX) + + @staticmethod + def jibe_defeat(noun): + if random.randrange(0, 100) > 25: + return f"Another {noun.lower()} defeated." + return Quips.jibe(noun, DEFEAT_PREFIX, DEFEAT_SUFFIX) + + @staticmethod + def jibe_fatal_mistake(): + return MISTAKES[random.randrange(0, len(MISTAKES))] + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e16cbae --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +*** +# PyTrek 9000 +*** + +The original free-and-open Star Trek console game was THE most played game of the day .... back in the 1970's! + +Today, however, we're adding a multi user interface on the [@TotalPythoneering](https://www.youtube.com/@TotalPythoneering) playlist... [Check it out](https://youtu.be/dRoMF2orxew?si=fxtwwoZQo6u3ViMv)? + +![PyTrek 9000](https://github.com/Python3-Training/PyTrek-9000/blob/master/PyTrek.png) + +Originally written in B.A.S.I.C, from C/C++, FORTRAN, C#, Python 2 ... and now Python 3, many have been inspired to re-create, improve, and / or simply experiment with the concept. + +So far: + +* Converted from Python 2, to Python 3. + +* Changed in-system coordinates to simple 'chess like' (b,4 (etc)) references. + +* Added random event Quips – should make the game a tad more ‘NPC’? + +* Added that classic sublight / in system propulsion system. Warp speeds engines are now a separate navigational system. + +* Warp speed selection changes game time. (Thanks Loondas!) + +* Heavily re-factored for growth, testing, re-use, and maintenance using modern Python. + +Video: [Game Overview](https://youtu.be/VLEXHtqKKaA?si=5FAw48gFkjAgbnmi) + +Original authors did an excellent job - made the modernization a WHOLE LOT easier! + +Feel free to do a 'Kirk here - learn how to code a [Kobayashi Maru](https://www.udemy.com/course/python-1000-the-python-primer/?referralCode=A22C48BD99DBF167A3DE)? + +Thinking about adding mult-user / networked features? -Then [here is a good video](https://www.oreilly.com/library/view/tcpip-and-udpip/9781484294543/) to assist with those networking 'Py-spirations. + + + +Enjoy the journey, + + +-- Randall Nagy + +### P.S +Changed the official PyTrek number from 2020 to 9000 to better support age-nostic updates & enhancements. Same for the `Game` Class in PyTrek1.py + +To play PyTrek 9000: + +``` +>>> from PyTrek import PyTrek1 +``` +-will automaticall run this TUI universal. + + +## zSupport? +If you want to support the effort, I seek no donations. Instead, simply feel free to purchase one of [my educational](https://www.udemy.com/user/randallnagy2/) or [printed](https://www.amazon.com/Randall-Nagy/e/B08ZJLH1VN?ref=sr_ntt_srch_lnk_1&qid=1660050704&sr=8-1) productions? diff --git a/README.rst b/README.rst deleted file mode 100644 index d3958ed..0000000 --- a/README.rst +++ /dev/null @@ -1,39 +0,0 @@ -================ - Star Trek 1971 -================ ------------- - for Python ------------- - -About -===== - -I recently discovered the classic old BASIC game `Star Trek`_ from 1971, through a post seen on Reddit_. - -The post contained a version of the game rewritten in C-Sharp which I thought was quite good. -I wondered if anyone had ported it to Python. - -After a little bit of research, I didn't find a definitive version for Python. - -This is by no means a definitive version itself; I just took the C# version and converted it to Python. - -.. _Star Trek: http://en.wikipedia.org/wiki/Star_Trek_%28text_game%29 -.. _Reddit: http://www.codeproject.com/Articles/28228/Star-Trek-Text-Game - -Improvements -============ - -There's heaps that can be done with this program. A lot of implementations used global variables. -I tried to fix this by encapsulating them in a global object, but this can definitely be improved further. - -Here is a list of possible improvements: - -- Encapsulate everything in classes -- Include help/instructions -- Add extra features; - + new ships, celestial objects, etc - + new weapon types - + crew functions -- Easier navigation (using cartesian system maybe) -- Make some parts more 'Pythonic' -- ...Plenty more! \ No newline at end of file diff --git a/Reports.py b/Reports.py new file mode 100644 index 0000000..e965517 --- /dev/null +++ b/Reports.py @@ -0,0 +1,51 @@ +from PyTrek import Glyphs as Glyphs +from PyTrek.Quips import Quips as Quips + +class Stats(): + ''' + Reports do not generate damage. + ''' + @staticmethod + def show_ship_status(game): + game.display() + game.display(f" Time Remaining: {game.time_remaining}") + game.display(f" Klingon Ships Remaining: {game.game_map.game_klingons}") + game.display(f" Starbases: {game.game_map.game_starbases}") + game.display(f" Warp Engine Damage: {game.enterprise.navigation_damage}") + game.display(f" Short Range Scanner Damage: {game.enterprise.short_range_scan_damage}") + game.display(f" Long Range Scanner Damage: {game.enterprise.long_range_scan_damage}") + game.display(f" Shield Controls Damage: {game.enterprise.shield_control_damage}") + game.display(f" Main Computer Damage: {game.enterprise.computer_damage}") + game.display(f"Photon Torpedo Control Damage: {game.enterprise.photon_damage}") + game.display(f" Phaser Damage: {game.enterprise.phaser_damage}") + game.display() + + @staticmethod + def show_galactic_status(game): + game.display() + str_ = f"| KLINGONS: {game.game_map.game_klingons:>04} | " + \ + f"STARBASES: {game.game_map.game_starbases:>04} | " + \ + f"STARS: {game.game_map.game_stars:>04} |" + dots = len(str_) * '-' + game.display(dots) + game.display(str_) + game.display(dots) + + @staticmethod + def show_exit_status(game): + if game.destroyed: + msg = "MISSION FAILED: SHIP DESTROYED" + game.show_banner([msg], '!') + elif game.enterprise.energy == 0: + msg = "MISSION FAILED: OUT OF ENERGY." + game.show_banner([msg], '!') + elif game.game_map.game_klingons == 0: + msg = "MISSION ACCOMPLISHED","ENEMIES DESTROYED","WELL DONE!" + game.show_banner(msg) + elif game.time_remaining == 0: + msg = "MISSION FAILED: OUT OF TIME." + game.show_banner([msg], '!') + else: + ary = ["::::::::: MISSION ABORTED :::::::::", Quips.jibe_quit()] + game.show_banner(ary, ':') + diff --git a/Sector.py b/Sector.py new file mode 100644 index 0000000..9979bfc --- /dev/null +++ b/Sector.py @@ -0,0 +1,69 @@ +import PyTrek.Glyphs as Glyphs + +class Sector(): + def __init__(self, num=-1, name='', + aliens=-1, stars=-1, + starbases=-1, lines=[]): + self.name = name + self.number = num + self.lines = lines + self.area_klingons = aliens + self.area_stars = stars + self.area_starbases = starbases + + def is_null(self): + return self.num == -1 + + @staticmethod + def from_area(area): + if not area: + return Sector() + name = area.name + num = area.number + map = area.get_map() + return Sector(num, name, + area.count_glyphs(Glyphs.KLINGON), + area.count_glyphs(Glyphs.STAR), + area.count_glyphs(Glyphs.STARBASE), + map) + + + @staticmethod + def display_area(game, sector): + game.enterprise.condition = "GREEN" + if sector.area_klingons > 0: + game.enterprise.condition = "RED" + elif game.enterprise.energy < 300: + game.enterprise.condition = "YELLOW" + + sb = " a b c d e f g h \n" + sb += f" -=--=--=--=--=--=--=--=- Sector: {sector.name}\n" + info = list() + info.append(f" Number: [{sector.number}]\n") + info.append(f" Hazzards: [{sector.area_stars + sector.area_klingons}]\n") + info.append(f" Stardate: {game.star_date}\n") + info.append(f" Condition: {game.enterprise.condition}\n") + info.append(f" Energy: {game.enterprise.energy}\n") + info.append(f" Shields: {game.enterprise.shield_level}\n") + info.append(f" Photon Torpedoes: {game.enterprise.photon_torpedoes}\n") + info.append(f" Time remaining: {game.time_remaining}\n") + for row, line in enumerate(sector.lines): + sb += f" {row+1} |" + for col in line: + sb += col + sb += info[row] + sb += f" -=--=--=--=--=--=--=--=- Docked: {game.enterprise.docked}\n" + sb += " a b c d e f g h \n" + print(sb, end='') + + if sector.area_klingons > 0: + game.display() + game.display("Condition RED: Klingon ship{0} detected.".format("" if sector.area_klingons == 1 else "s")) + if game.enterprise.shield_level == 0 and not game.enterprise.docked: + game.display("Warning: Shields are down.") + elif game.enterprise.energy < 300: + game.display() + game.display("Condition YELLOW: Low energy level.") + game.enterprise.condition = "YELLOW" + + diff --git a/ShipEnterprise.py b/ShipEnterprise.py new file mode 100644 index 0000000..aba24ec --- /dev/null +++ b/ShipEnterprise.py @@ -0,0 +1,151 @@ +import random +from PyTrek.AbsShip import AbsShip +from PyTrek.ShipStarbase import ShipStarbase +from PyTrek.Sector import Sector +from PyTrek.Difficulity import Probabilities +from PyTrek import Glyphs as Glyphs +from PyTrek.Quips import Quips as Quips + +class ShipEnterprise(AbsShip): + + def __init__(self): + super().__init__() + self.energy = 0 + self.docked = False + self.condition = "GREEN" + self.navigation_damage = 0 + self.short_range_scan_damage = 0 + self.long_range_scan_damage = 0 + self.shield_control_damage = 0 + self.computer_damage = 0 + self.photon_damage = 0 + self.phaser_damage = 0 + self.photon_torpedoes = 0 + ShipStarbase.dock_enterprise(self) + ShipStarbase.launch_enterprise(self) + + def get_glyph(self): + return Glyphs.ENTERPRISE + + def damage(self, game, item): + ''' + Damage the Enterprise. + ''' + if not Probabilities.should_damage_enterprise(game, item): + return + damage = Probabilities.calc_damage(game, item) + if item < 0: + item = random.randint(0, 6) + if item == 0: + self.navigation_damage = damage + game.display(Quips.jibe_damage('warp engine')) + elif item == 1: + self.short_range_scan_damage = damage + game.display(Quips.jibe_damage('short range scanner')) + elif item == 2: + self.long_range_scan_damage = damage + game.display(Quips.jibe_damage('long range scanner')) + elif item == 3: + self.shield_control_damage = damage + game.display(Quips.jibe_damage('shield control')) + elif item == 4: + self.computer_damage = damage + game.display(Quips.jibe_damage('main computer')) + elif item == 5: + self.photon_damage = damage + game.display(Quips.jibe_damage('torpedo controller')) + elif item == 6: + self.phaser_damage = damage + game.display(Quips.jibe_damage('phaser')) + game.display() + + def repair(self, game): + ''' + Repair damage to the Enterprise. + ''' + if self.navigation_damage > 0: + self.navigation_damage -= 1 + if self.navigation_damage == 0: + game.display("Warp engines have been repaired.") + game.display() + return True + if self.short_range_scan_damage > 0: + self.short_range_scan_damage -= 1 + if self.short_range_scan_damage == 0: + game.display("Short range scanner has been repaired.") + game.display() + return True + if self.long_range_scan_damage > 0: + self.long_range_scan_damage -= 1 + if self.long_range_scan_damage == 0: + game.display("Long range scanner has been repaired.") + game.display() + return True + if self.shield_control_damage > 0: + self.shield_control_damage -= 1 + if self.shield_control_damage == 0: + game.display("Shield controls have been repaired.") + game.display() + return True + if self.computer_damage > 0: + self.computer_damage -= 1 + if self.computer_damage == 0: + game.display("The main computer has been repaired.") + game.display() + return True + if self.photon_damage > 0: + self.photon_damage -= 1 + if self.photon_damage == 0: + game.display("Photon torpedo controls have been repaired.") + game.display() + return True + if self.phaser_damage > 0: + self.phaser_damage -= 1 + if self.phaser_damage == 0: + game.display("Phasers have been repaired.") + game.display() + return True + return False + + def short_range_scan(self, game): + if self.short_range_scan_damage > 0: + game.display(Quips.jibe_damage('short ranged scanner')) + game.display() + else: + quad = game.game_map.get_pw_sector() + Sector.display_area(game, quad) + game.display() + if not game.enterprise.repair(game): + game.enterprise.damage(game, Probabilities.SRS) + + def long_range_scan(self, game): + if self.long_range_scan_damage > 0: + game.display(Quips.jibe_damage('long ranged scanner')) + game.display() + return + + pw_sector = game.game_map.sector + if pw_sector < 4: + pw_sector = 5 + elif pw_sector > 60: + pw_sector = 60 + lines = [] + for peek in range(pw_sector-4, pw_sector + 5): + quad = game.game_map.scan_sector(peek) + lines.append(f"SEC: {quad.number:>03}") + lines.append(f"{Glyphs.KLINGON}: {quad.area_klingons:>03}") + lines.append(f"{Glyphs.STARBASE}: {quad.area_starbases:>03}") + lines.append(f"{Glyphs.STAR}: {quad.area_stars:>03}") + dots = ' +' + ('-' * 35) + '+' + game.display(dots) + game.display(' | LONG RANGE SCAN |') + game.display(dots) + for ss in range(0,(len(lines)-1),12): + for offs in range(4): + line = f' | {lines[ss+offs]:<9} | {lines[ss+4+offs]:<9} | {lines[ss+8+offs]:<9} |' + game.display(line) + game.display(dots) + game.display() + if not game.enterprise.repair(game): + game.enterprise.damage(game, Probabilities.SRS) + diff --git a/ShipKlingon.py b/ShipKlingon.py new file mode 100644 index 0000000..a135afb --- /dev/null +++ b/ShipKlingon.py @@ -0,0 +1,51 @@ +import random +import PyTrek.Glyphs as Glyphs +from PyTrek.AbsShip import AbsShip as AbsShip + +class ShipKlingon(AbsShip): + + def __init__(self): + super().__init__() + self.xpos = 0 + self.ypos = 0 + self.shield_level = 0 + + def get_glyph(self): + return Glyphs.KLINGON + + def from_map(self, xpos, ypos): + self.xpos = xpos + self.ypos = ypos + self.shield_level = random.randint(300, 599) + + @staticmethod + def attack_if_you_can(game): + ''' + IF you ever find yourself in the AREA, then have at USS? + ''' + if game.is_cloked: + return False + from PyTrek.Calculators import Calc as Calc + kships = game.game_map.get_area_klingons() + if len(kships) > 0: + for ship in kships: + if game.enterprise.docked: + game.display("Enterprise hit by ship at sector [{0},{1}]. No damage due to starbase shields.".format( + ship.xpos + 1, ship.ypos + 1 + )) + else: + dist = Calc.distance( + game.game_map.xpos, game.game_map.ypos, ship.xpos, ship.ypos) + delivered_energy = 300 * \ + random.uniform(0.0, 1.0) * (1.0 - dist / 11.3) + game.enterprise.shield_level -= int(delivered_energy) + if game.enterprise.shield_level < 0: + game.enterprise.shield_level = 0 + game.destroyed = True + game.display("Enterprise hit by ship at sector [{0},{1}]. Shields dropped to {2}.".format( + ship.xpos + 1, ship.ypos + 1, game.enterprise.shield_level + )) + if game.enterprise.shield_level == 0: + return True + return True + return False diff --git a/ShipStarbase.py b/ShipStarbase.py new file mode 100644 index 0000000..e2bb9cb --- /dev/null +++ b/ShipStarbase.py @@ -0,0 +1,28 @@ +import PyTrek.Glyphs as Glyphs +from PyTrek.AbsShip import AbsShip as AbsShip + +class ShipStarbase(AbsShip): + + def __init__(self): + super().__init__() + + def get_glyph(self): + return Glyphs.STARBASE + + @staticmethod + def dock_enterprise(ship): + ship.energy = 3000 + ship.photon_torpedoes = 10 + ship.navigation_damage = 0 + ship.short_range_scan_damage = 0 + ship.long_range_scan_damage = 0 + ship.shield_control_damage = 0 + ship.computer_damage = 0 + ship.photon_damage = 0 + ship.phaser_damage = 0 + ship.shield_level = 0 + ship.docked = True + + @staticmethod + def launch_enterprise(ship): + ship.docked = False diff --git a/strings.py b/TrekStrings.py similarity index 84% rename from strings.py rename to TrekStrings.py index 5781948..009f66d 100644 --- a/strings.py +++ b/TrekStrings.py @@ -1,11 +1,12 @@ -titleStrings = r""" +LOGO_TREKER = r""" ______ _______ ______ ______ _______ ______ ______ __ __ / __ //__ __// __ // __ / /__ __// __ / / ____// / / / / / /_/ / / / /_/ // /_/ / / / / /_/ / / /__ / // / _\ \ / / / __ // __/ / / / __/ / __ / / / / /_/ / / / / / / // /\ \ / / / /\ \ / /___ / /\ \ /_____/ /_/ /_/ /_//_/ \_\ /_/ /_/ \_\/_____//_/ \_\ - + + 1971 - 2020 ________________ _ \__(=======/_=_/____.--'-`--.___ \ \ `,--,-.___.----' @@ -13,7 +14,7 @@ '---._____.|]""".split('\n') -quadrantNames = [ +AREA_NAMES = [ "Aaamazzara", "Altair IV", "Aurelia", @@ -96,23 +97,17 @@ "Zytchin III", ] -commandStrings = [ +HELM_CMDS = [ "--- Commands -----------------", - "nav = Navigation", - "srs = Short Range Scan", - "lrs = Long Range Scan", - "pha = Phaser Control", - "tor = Photon Torpedo Control", - "she = Shield Control", - "com = Access Computer", - "qui = Quit the game", + "nav = Warp Speed srs = Short Range Scan tor = Photon Torpedos", + "sub = Sublight Speed lrs = Long Range Scan pha = Phasers", + "qui = Quit com = Access Computer she = Shields", ] -computerStrings = [ +CPU_CMDS = [ "--- Main Computer --------------", "rec = Cumulative Galatic Record", "sta = Status Report", - "tor = Photon Torpedo Calculator", - "bas = Starbase Calculator", - "nav = Navigation Calculator", + "tor = Photon Torpedo Targets", + "bas = Starbase Locations", ] \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/startrek.py b/startrek.py deleted file mode 100644 index ca745be..0000000 --- a/startrek.py +++ /dev/null @@ -1,879 +0,0 @@ -from math import atan2, pi, sqrt, cos, sin -import random - -import strings - - -class Quadrant(): - - def __init__(self): - self.name = "" - self.klingons = 0 - self.stars = 0 - self.starbase = False - self.scanned = False - - -class SectorType(): - - def __init__(self): - self.empty, self.star, self.klingon, self.enterprise, self.starbase = 1, 2, 3, 4, 5 - -sector_type = SectorType() - - -class KlingonShip(): - - def __init__(self): - self.sector_x = 0 - self.sector_y = 0 - self.shield_level = 0 - - -class Game(): - - def __init__(self): - self.star_date = 0 - self.time_remaining = 0 - self.energy = 0 - self.klingons = 0 - self.starbases = 0 - self.quadrant_x, self.quadrant_y = 0, 0 - self.sector_x, self.sector_y = 0, 0 - self.shield_level = 0 - self.navigation_damage = 0 - self.short_range_scan_damage = 0 - self.long_range_scan_damage = 0 - self.shield_control_damage = 0 - self.computer_damage = 0 - self.photon_damage = 0 - self.phaser_damage = 0 - self.photon_torpedoes = 0 - self.docked = False - self.destroyed = False - self.starbase_x, self.starbase_y = 0, 0 - self.quadrants = [[Quadrant() for _ in range(8)] for _ in range(8)] - self.sector = [[SectorType() for _ in range(8)] for _ in range(8)] - self.klingon_ships = [] - -game = Game() - - -def run(): - global game - print_strings(strings.titleStrings) - while True: - initialize_game() - print_mission() - generate_sector() - print_strings(strings.commandStrings) - while game.energy > 0 and not game.destroyed and game.klingons > 0 and game.time_remaining > 0: - command_prompt() - print_game_status() - - -def print_game_status(): - global game - if game.destroyed: - print "MISSION FAILED: ENTERPRISE DESTROYED!!!" - print - print - print - elif game.energy == 0: - print "MISSION FAILED: ENTERPRISE RAN OUT OF ENERGY." - print - print - print - elif game.klingons == 0: - print "MISSION ACCOMPLISHED: ALL KLINGON SHIPS DESTROYED. WELL DONE!!!" - print - print - print - elif game.time_remaining == 0: - print "MISSION FAILED: ENTERPRISE RAN OUT OF TIME." - print - print - print - - -def command_prompt(): - command = raw_input("Enter command: ").strip().lower() - print - if command == "nav": - navigation() - elif command == "srs": - short_range_scan() - elif command == "lrs": - long_range_scan() - elif command == "pha": - phaser_controls() - elif command == "tor": - torpedo_control() - elif command == "she": - shield_controls() - elif command == "com": - computer_controls() - elif command.startswith('qui') or command.startswith('exi'): - exit() - else: - print_strings(strings.commandStrings) - - -def computer_controls(): - global game - if game.computer_damage > 0: - print "The main computer is damaged. Repairs are underway." - print - return - print_strings(strings.computerStrings) - command = raw_input("Enter computer command: ").strip().lower() - if command == "rec": - display_galactic_record() - elif command == "sta": - display_status() - elif command == "tor": - photon_torpedo_calculator() - elif command == "bas": - starbase_calculator() - elif command == "nav": - navigation_calculator() - else: - print - print "Invalid computer command." - print - induce_damage(4) - - -def compute_direction(x1, y1, x2, y2): - if x1 == x2: - if y1 < y2: - direction = 7 - else: - direction = 3 - elif y1 == y2: - if x1 < x2: - direction = 1 - else: - direction = 5 - else: - dy = abs(y2 - y1) - dx = abs(x2 - x1) - angle = atan2(dy, dx) - if x1 < x2: - if y1 < y2: - direction = 9.0 - 4.0 * angle / pi - else: - direction = 1.0 + 4.0 * angle / pi - else: - if y1 < y2: - direction = 5.0 + 4.0 * angle / pi - else: - direction = 5.0 - 4.0 * angle / pi - return direction - - -def navigation_calculator(): - global game - print - print "Enterprise located in quadrant [%s,%s]." % (game.quadrant_x + 1, game.quadrant_y + 1) - print - quad_x = input_double("Enter destination quadrant X (1--8): ") - if quad_x is False or quad_x < 1 or quad_x > 8: - print "Invalid X coordinate." - print - return - quad_y = input_double("Enter destination quadrant Y (1--8): ") - if quad_y is False or quad_y < 1 or quad_y > 8: - print "Invalid Y coordinate." - print - return - print - qx = int(quad_x) - 1 - qy = int(quad_y) - 1 - if qx == game.quadrant_x and qy == game.quadrant_y: - print "That is the current location of the Enterprise." - print - return - print "Direction: {0:1.2f}".format(compute_direction(game.quadrant_x, game.quadrant_y, qx, qy)) - print "Distance: {0:2.2f}".format(distance(game.quadrant_x, game.quadrant_y, qx, qy)) - print - - -def starbase_calculator(): - global game - print - if game.quadrants[game.quadrant_y][game.quadrant_x].starbase: - print "Starbase in sector [%s,%s]." % (game.starbase_x + 1, game.starbase_y + 1) - print "Direction: {0:1.2f}".format( - compute_direction(game.sector_x, game.sector_y, game.starbase_x, game.starbase_y) - ) - print "Distance: {0:2.2f}".format(distance(game.sector_x, game.sector_y, game.starbase_x, game.starbase_y) / 8) - else: - print "There are no starbases in this quadrant." - print - - -def photon_torpedo_calculator(): - global game - print - if len(game.klingon_ships) == 0: - print "There are no Klingon ships in this quadrant." - print - return - - for ship in game.klingon_ships: - text = "Direction {2:1.2f}: Klingon ship in sector [{0},{1}]." - print text.format( - ship.sector_x + 1, ship.sector_y + 1, - compute_direction(game.sector_x, game.sector_y, ship.sector_x, ship.sector_y)) - print - - -def display_status(): - global game - print - print " Time Remaining: {0}".format(game.time_remaining) - print " Klingon Ships Remaining: {0}".format(game.klingons) - print " Starbases: {0}".format(game.starbases) - print " Warp Engine Damage: {0}".format(game.navigation_damage) - print " Short Range Scanner Damage: {0}".format(game.short_range_scan_damage) - print " Long Range Scanner Damage: {0}".format(game.long_range_scan_damage) - print " Shield Controls Damage: {0}".format(game.shield_control_damage) - print " Main Computer Damage: {0}".format(game.computer_damage) - print "Photon Torpedo Control Damage: {0}".format(game.photon_damage) - print " Phaser Damage: {0}".format(game.phaser_damage) - print - - -def display_galactic_record(): - global game - print - sb = "" - print "-------------------------------------------------" - for i in range(8): - for j in range(8): - sb += "| " - klingon_count = 0 - starbase_count = 0 - star_count = 0 - quadrant = game.quadrants[i][j] - if quadrant.scanned: - klingon_count = quadrant.klingons - starbase_count = 1 if quadrant.starbase else 0 - star_count = quadrant.stars - sb = sb + \ - "{0}{1}{2} ".format(klingon_count, starbase_count, star_count) - sb += "|" - print sb - sb = "" - print "-------------------------------------------------" - print - - -def phaser_controls(): - global game - if game.phaser_damage > 0: - print "Phasers are damaged. Repairs are underway." - print - return - if len(game.klingon_ships) == 0: - print "There are no Klingon ships in this quadrant." - print - return - print "Phasers locked on target." - phaser_energy = input_double("Enter phaser energy (1--{0}): ".format(game.energy)) - if not phaser_energy or phaser_energy < 1 or phaser_energy > game.energy: - print "Invalid energy level." - print - return - print - print "Firing phasers..." - destroyed_ships = [] - for ship in game.klingon_ships: - game.energy -= int(phaser_energy) - if game.energy < 0: - game.energy = 0 - break - dist = distance(game.sector_x, game.sector_y, ship.sector_x, ship.sector_y) - delivered_energy = phaser_energy * (1.0 - dist / 11.3) - ship.shield_level -= int(delivered_energy) - if ship.shield_level <= 0: - print "Klingon ship destroyed at sector [{0},{1}].".format(ship.sector_x + 1, ship.sector_y + 1) - destroyed_ships.append(ship) - else: - print "Hit ship at sector [{0},{1}]. Klingon shield strength dropped to {2}.".format( - ship.sector_x + 1, ship.sector_y + 1, ship.shield_level - ) - for ship in destroyed_ships: - game.quadrants[game.quadrant_y][game.quadrant_x].klingons -= 1 - game.klingons -= 1 - game.sector[ship.sector_y][ship.sector_x] = sector_type.empty - game.klingon_ships.remove(ship) - if len(game.klingon_ships) > 0: - print - klingons_attack() - print - - -def shield_controls(): - global game - print "--- Shield Controls ----------------" - print "add = Add energy to shields." - print "sub = Subtract energy from shields." - print - print "Enter shield control command: " - command = raw_input("Enter shield control command: ").strip().lower() - print - if command == "add": - adding = True - max_transfer = game.energy - elif command == "sub": - adding = False - max_transfer = game.shield_level - else: - print "Invalid command." - print - return - transfer = input_double( - "Enter amount of energy (1--{0}): ".format(max_transfer)) - if not transfer or transfer < 1 or transfer > max_transfer: - print "Invalid amount of energy." - print - return - print - if adding: - game.energy -= int(transfer) - game.shield_level += int(transfer) - else: - game.energy += int(transfer) - game.shield_level -= int(transfer) - print "Shield strength is now {0}. Energy level is now {1}.".format(game.shield_level, game.energy) - print - - -def klingons_attack(): - global game - if len(game.klingon_ships) > 0: - for ship in game.klingon_ships: - if game.docked: - print "Enterprise hit by ship at sector [{0},{1}]. No damage due to starbase shields.".format( - ship.sector_x + 1, ship.sector_y + 1 - ) - else: - dist = distance( - game.sector_x, game.sector_y, ship.sector_x, ship.sector_y) - delivered_energy = 300 * \ - random.uniform(0.0, 1.0) * (1.0 - dist / 11.3) - game.shield_level -= int(delivered_energy) - if game.shield_level < 0: - game.shield_level = 0 - game.destroyed = True - print "Enterprise hit by ship at sector [{0},{1}]. Shields dropped to {2}.".format( - ship.sector_x + 1, ship.sector_y + 1, game.shield_level - ) - if game.shield_level == 0: - return True - return True - return False - - -def distance(x1, y1, x2, y2): - x = x2 - x1 - y = y2 - y1 - return sqrt(x * x + y * y) - - -def induce_damage(item): - global game - if random.randint(0, 6) > 0: - return - damage = 1 + random.randint(0, 4) - if item < 0: - item = random.randint(0, 6) - if item == 0: - game.navigation_damage = damage - print "Warp engines are malfunctioning." - elif item == 1: - game.short_range_scan_damage = damage - print "Short range scanner is malfunctioning." - elif item == 2: - game.long_range_scan_damage = damage - print "Long range scanner is malfunctioning." - elif item == 3: - game.shield_control_damage = damage - print "Shield controls are malfunctioning." - elif item == 4: - game.computer_damage = damage - print "The main computer is malfunctioning." - elif item == 5: - game.photon_damage = damage - print "Photon torpedo controls are malfunctioning." - elif item == 6: - game.phaser_damage = damage - print "Phasers are malfunctioning." - print - - -def repair_damage(): - global game - if game.navigation_damage > 0: - game.navigation_damage -= 1 - if game.navigation_damage == 0: - print "Warp engines have been repaired." - print - return True - if game.short_range_scan_damage > 0: - game.short_range_scan_damage -= 1 - if game.short_range_scan_damage == 0: - print "Short range scanner has been repaired." - print - return True - if game.long_range_scan_damage > 0: - game.long_range_scan_damage -= 1 - if game.long_range_scan_damage == 0: - print "Long range scanner has been repaired." - print - return True - if game.shield_control_damage > 0: - game.shield_control_damage -= 1 - if game.shield_control_damage == 0: - print "Shield controls have been repaired." - print - return True - if game.computer_damage > 0: - game.computer_damage -= 1 - if game.computer_damage == 0: - print "The main computer has been repaired." - print - return True - if game.photon_damage > 0: - game.photon_damage -= 1 - if game.photon_damage == 0: - print "Photon torpedo controls have been repaired." - print - return True - if game.phaser_damage > 0: - game.phaser_damage -= 1 - if game.phaser_damage == 0: - print "Phasers have been repaired." - print - return True - return False - - -def long_range_scan(): - global game - if game.long_range_scan_damage > 0: - print "Long range scanner is damaged. Repairs are underway." - print - return - sb = "" - print "-------------------" - for i in range(game.quadrant_y - 1, game.quadrant_y+2): # quadrantY + 1 ? - for j in range(game.quadrant_x - 1, game.quadrant_x+2): # quadrantX + 1? - sb += "| " - klingon_count = 0 - starbase_count = 0 - star_count = 0 - if 0 <= i < 8 and 0 <= j < 8: - quadrant = game.quadrants[i][j] - quadrant.scanned = True - klingon_count = quadrant.klingons - starbase_count = 1 if quadrant.starbase else 0 - star_count = quadrant.stars - sb = sb + \ - "{0}{1}{2} ".format(klingon_count, starbase_count, star_count) - sb += "|" - print sb - sb = "" - print "-------------------" - print - - -def torpedo_control(): - global game - if game.photon_damage > 0: - print "Photon torpedo control is damaged. Repairs are underway." - print - return - if game.photon_torpedoes == 0: - print "Photon torpedoes exhausted." - print - return - if len(game.klingon_ships) == 0: - print "There are no Klingon ships in this quadrant." - print - return - direction = input_double("Enter firing direction (1.0--9.0): ") - if not direction or direction < 1.0 or direction > 9.0: - print "Invalid direction." - print - return - print - print "Photon torpedo fired..." - game.photon_torpedoes -= 1 - angle = -(pi * (direction - 1.0) / 4.0) - if random.randint(0, 2) == 0: - angle += (1.0 - 2.0 * random.uniform(0.0, 1.0) * pi * 2.0) * 0.03 - x = game.sector_x - y = game.sector_y - vx = cos(angle) / 20 - vy = sin(angle) / 20 - last_x = last_y = -1 - # new_x = game.sector_x - # new_y = game.sector_y - hit = False - while x >= 0 and y >= 0 and round(x) < 8 and round(y) < 8: - new_x = int(round(x)) - new_y = int(round(y)) - if last_x != new_x or last_y != new_y: - print " [{0},{1}]".format(new_x + 1, new_y + 1) - last_x = new_x - last_y = new_y - for ship in game.klingon_ships: - if ship.sector_x == new_x and ship.sector_y == new_y: - print "Klingon ship destroyed at sector [{0},{1}].".format(ship.sector_x + 1, ship.sector_y + 1) - game.sector[ship.sector_y][ship.sector_x] = sector_type.empty - game.klingons -= 1 - game.klingon_ships.remove(ship) - game.quadrants[game.quadrant_y][game.quadrant_x].klingons -= 1 - hit = True - break # break out of the for loop - if hit: - break # break out of the while loop - if game.sector[new_y][new_x] == sector_type.starbase: - game.starbases -= 1 - game.quadrants[game.quadrant_y][game.quadrant_x].starbase = False - game.sector[new_y][new_x] = sector_type.empty - print "The Enterprise destroyed a Federation starbase at sector [{0},{1}]!".format(new_x + 1, new_y + 1) - hit = True - break - elif game.sector[new_y][new_x] == sector_type.star: - print "The torpedo was captured by a star's gravitational field at sector [{0},{1}].".format( - new_x + 1, new_y + 1 - ) - hit = True - break - x += vx - y += vy - if not hit: - print "Photon torpedo failed to hit anything." - if len(game.klingon_ships) > 0: - print - klingons_attack() - print - - -def navigation(): - global game - max_warp_factor = 8.0 - if game.navigation_damage > 0: - max_warp_factor = 0.2 + random.randint(0, 8) / 10.0 - print "Warp engines damaged. Maximum warp factor: {0}".format(max_warp_factor) - print - - direction = input_double("Enter course (1.0--8.9): ") - if not direction or direction < 1.0 or direction > 9.0: - print "Invalid course." - print - return - - dist = input_double( - "Enter warp factor (0.1--{0}): ".format(max_warp_factor)) - if not dist or dist < 0.1 or dist > max_warp_factor: - print "Invalid warp factor." - print - return - - print - - dist *= 8 - energy_required = int(dist) - if energy_required >= game.energy: - print "Unable to comply. Insufficient energy to travel that speed." - print - return - else: - print "Warp engines engaged." - print - game.energy -= energy_required - - last_quad_x = game.quadrant_x - last_quad_y = game.quadrant_y - angle = -(pi * (direction - 1.0) / 4.0) - x = game.quadrant_x * 8 + game.sector_x - y = game.quadrant_y * 8 + game.sector_y - dx = dist * cos(angle) - dy = dist * sin(angle) - vx = dx / 1000 - vy = dy / 1000 - # quad_x = quad_y = sect_x = sect_y = 0 - last_sect_x = game.sector_x - last_sect_y = game.sector_y - game.sector[game.sector_y][game.sector_x] = sector_type.empty - obstacle = False - for i in range(999): - x += vx - y += vy - quad_x = int(round(x)) / 8 - quad_y = int(round(y)) / 8 - if quad_x == game.quadrant_x and quad_y == game.quadrant_y: - sect_x = int(round(x)) % 8 - sect_y = int(round(y)) % 8 - if game.sector[sect_y][sect_x] != sector_type.empty: - game.sector_x = last_sect_x - game.sector_y = last_sect_y - game.sector[game.sector_y][game.sector_x] = sector_type.enterprise - print "Encountered obstacle within quadrant." - print - obstacle = True - break - last_sect_x = sect_x - last_sect_y = sect_y - - if not obstacle: - if x < 0: - x = 0 - elif x > 63: - x = 63 - if y < 0: - y = 0 - elif y > 63: - y = 63 - quad_x = int(round(x)) / 8 - quad_y = int(round(y)) / 8 - game.sector_x = int(round(x)) % 8 - game.sector_y = int(round(y)) % 8 - if quad_x != game.quadrant_x or quad_y != game.quadrant_y: - game.quadrant_x = quad_x - game.quadrant_y = quad_y - generate_sector() - else: - game.quadrant_x = quad_x - game.quadrant_y = quad_y - game.sector[game.sector_y][game.sector_x] = sector_type.enterprise - if is_docking_location(game.sector_y, game.sector_x): - game.energy = 3000 - game.photon_torpedoes = 10 - game.navigation_damage = 0 - game.short_range_scan_damage = 0 - game.long_range_scan_damage = 0 - game.shield_control_damage = 0 - game.computer_damage = 0 - game.photon_damage = 0 - game.phaser_damage = 0 - game.shield_level = 0 - game.docked = True - else: - game.docked = False - - if last_quad_x != game.quadrant_x or last_quad_y != game.quadrant_y: - game.time_remaining -= 1 - game.star_date += 1 - - short_range_scan() - - if game.docked: - print "Lowering shields as part of docking sequence..." - print "Enterprise successfully docked with starbase." - print - else: - if game.quadrants[game.quadrant_y][game.quadrant_x].klingons > 0 \ - and last_quad_x == game.quadrant_x and last_quad_y == game.quadrant_y: - klingons_attack() - print - elif not repair_damage(): - induce_damage(-1) - - -def input_double(prompt): - text = raw_input(prompt) - value = float(text) - if type(value) == float: - return value - else: - return False - - -def generate_sector(): - global game - quadrant = game.quadrants[game.quadrant_y][game.quadrant_x] - starbase = quadrant.starbase - stars = quadrant.stars - klingons = quadrant.klingons - game.klingon_ships = [] - for i in range(8): - for j in range(8): - game.sector[i][j] = sector_type.empty - game.sector[game.sector_y][game.sector_x] = sector_type.enterprise - while starbase or stars > 0 or klingons > 0: - i = random.randint(0, 7) - j = random.randint(0, 7) - if is_sector_region_empty(i, j): - if starbase: - starbase = False - game.sector[i][j] = sector_type.starbase - game.starbase_y = i - game.starbase_x = j - elif stars > 0: - game.sector[i][j] = sector_type.star - stars -= 1 - elif klingons > 0: - game.sector[i][j] = sector_type.klingon - klingon_ship = KlingonShip() - klingon_ship.shield_level = 300 + random.randint(0, 199) - klingon_ship.sector_y = i - klingon_ship.sector_x = j - game.klingon_ships.append(klingon_ship) - klingons -= 1 - - -def is_docking_location(i, j): - for y in range(i - 1, i+1): # i + 1? - for x in range(j - 1, j+1): # j + 1? - if read_sector(y, x) == sector_type.starbase: - return True - return False - - -def is_sector_region_empty(i, j): - for y in range(i - 1, i+1): # i + 1? - if read_sector(y, j - 1) != sector_type.empty and read_sector(y, j + 1) != sector_type.empty: - return False - return read_sector(i, j) == sector_type.empty - - -def read_sector(i, j): - global game - if i < 0 or j < 0 or i > 7 or j > 7: - return sector_type.empty - return game.sector[i][j] - - -def short_range_scan(): - global game - if game.short_range_scan_damage > 0: - print "Short range scanner is damaged. Repairs are underway." - print - else: - quadrant = game.quadrants[game.quadrant_y][game.quadrant_x] - quadrant.scanned = True - print_sector(quadrant) - print - - -def print_sector(quadrant): - global game - game.condition = "GREEN" - if quadrant.klingons > 0: - game.condition = "RED" - elif game.energy < 300: - game.condition = "YELLOW" - - sb = "" - print "-=--=--=--=--=--=--=--=- Region: {0}".format(quadrant.name) - print_sector_row(sb, 0, " Quadrant: [{0},{1}]".format(game.quadrant_x + 1, game.quadrant_y + 1)) - print_sector_row(sb, 1, " Sector: [{0},{1}]".format(game.sector_x + 1, game.sector_y + 1)) - print_sector_row(sb, 2, " Stardate: {0}".format(game.star_date)) - print_sector_row(sb, 3, " Time remaining: {0}".format(game.time_remaining)) - print_sector_row(sb, 4, " Condition: {0}".format(game.condition)) - print_sector_row(sb, 5, " Energy: {0}".format(game.energy)) - print_sector_row(sb, 6, " Shields: {0}".format(game.shield_level)) - print_sector_row(sb, 7, " Photon Torpedoes: {0}".format(game.photon_torpedoes)) - print "-=--=--=--=--=--=--=--=- Docked: {0}".format(game.docked) - - if quadrant.klingons > 0: - print - print "Condition RED: Klingon ship{0} detected.".format("" if quadrant.klingons == 1 else "s") - if game.shield_level == 0 and not game.docked: - print "Warning: Shields are down." - elif game.energy < 300: - print - print "Condition YELLOW: Low energy level." - game.condition = "YELLOW" - - -def print_sector_row(sb, row, suffix): - global game - for column in range(8): - if game.sector[row][column] == sector_type.empty: - sb += " " - elif game.sector[row][column] == sector_type.enterprise: - sb += "" - elif game.sector[row][column] == sector_type.klingon: - sb += "+K+" - elif game.sector[row][column] == sector_type.star: - sb += " * " - elif game.sector[row][column] == sector_type.starbase: - sb += ">S<" - if suffix is not None: - sb = sb + suffix - print sb - - -def print_mission(): - global game - print "Mission: Destroy {0} Klingon ships in {1} stardates with {2} starbases.".format( - game.klingons, game.time_remaining, game.starbases) - print - - -def initialize_game(): - # gah, globals - global game - game.quadrant_x = random.randint(0, 7) - game.quadrant_y = random.randint(0, 7) - game.sector_x = random.randint(0, 7) - game.sector_y = random.randint(0, 7) - game.star_date = random.randint(0, 50) + 2250 - game.energy = 3000 - game.photon_torpedoes = 10 - game.time_remaining = 40 + random.randint(0, 9) - game.klingons = 15 + random.randint(0, 5) - game.starbases = 2 + random.randint(0, 2) - game.destroyed = False - game.navigation_damage = 0 - game.short_range_scan_damage = 0 - game.long_range_scan_damage = 0 - game.shield_control_damage = 0 - game.computer_damage = 0 - game.photon_damage = 0 - game.phaser_damage = 0 - game.shield_level = 0 - game.docked = False - - names = [] - for name in strings.quadrantNames: - names.append(name) - - for i in range(8): - for j in range(8): - index = random.randint(0, len(names) - 1) - quadrant = Quadrant() - quadrant.name = names[index] - quadrant.stars = 1 + random.randint(0, 7) - game.quadrants[i][j] = quadrant - del names[index] - - klingon_count = game.klingons - starbase_count = game.starbases - while klingon_count > 0 or starbase_count > 0: - i = random.randint(0, 7) - j = random.randint(0, 7) - quadrant = game.quadrants[i][j] - if not quadrant.starbase: - quadrant.starbase = True - starbase_count -= 1 - if quadrant.klingons < 3: - quadrant.klingons += 1 - klingon_count -= 1 - - -def print_strings(string_list): - for string in string_list: - print string - print - - -if __name__ == '__main__': - run() diff --git a/test_MapSparse.py b/test_MapSparse.py new file mode 100644 index 0000000..4770945 --- /dev/null +++ b/test_MapSparse.py @@ -0,0 +1,50 @@ +import random +import PyTrek.MapGame as MapGame +import PyTrek.Glyphs as Glyphs +from PyTrek.MapSparse import SparseMap as SparseMap + +def fill_map(): + map = SparseMap() + map.init() + for sector in range(1, 65): + for xpos in range(8): + for ypos in range(8): + assert(map.plot(sector,xpos,ypos,Glyphs.ENTERPRISE)) + return map + +def define_map(): + map = SparseMap() + map.init() + for ypos, area in enumerate(range(8)): + for xpos, area in enumerate(range(8)): + which = random.randint(0, 14) + glyph = Glyphs.SPACE + if which < 0: + pass + elif which < 8: + glyph = Glyphs.STAR + elif which < 13: + glyph = Glyphs.STARBASE + else: + glyph = Glyphs.KLINGON + + map.plot(area, + random.randint(0, 7), + random.randint(0, 7), + glyph) + return map + +if __name__ == '__main__': + map = fill_map() + for sect, area in enumerate(map.areas(),1): + assert(area.number == sect) + + for sector in map.areas(): + for line in sector.get_map(): + print(line, end='') + print() + + + + +