💾 Archived View for gemini.ctrl-c.club › ~nttp › games › orion.py captured on 2024-03-21 at 15:43:24.
⬅️ Previous capture (2020-09-24)
-=-=-=-=-=-=-
#!/usr/bin/env python3 # coding=utf-8 # # Space Cruiser Orion: a text-based strategy game... in spaaace! # 2019-05-02 Felix Pleșoianu <https://felix.plesoianu.ro/> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function from __future__ import division import math import random try: import readline except ImportError as e: print("(Command line editing is unavailable.)\n") import cmd SPACE = 0 STAR = 1 PLANET = 2 ASTEROID = 3 OUTPOST = 4 MAXSHIPS = 20 SHIPOFFS = 10 game_banner = """ Urgent message from Space Patrol Dispatch: Lost contact with our 5 outposts near Sirius Sector. Hostile activity has been detected. We can only spare one ship: the SPV Orion. Your mission: - re-establish communications in the area; - hunt down any Mechanoid raiders. Good luck, Captain. (Type "help" or "?" to see a list of commands.) """ class Ship: def __init__(self): self.energy = random.randint(20, 30) * 100 self.shields = self.energy / 10 self.missiles = 0 self.max_e = 3000 self.max_s = 300 self.damage = 0 self.sector = None self.x = -1 self.y = -1 self.dock_count = 0 self.kills = 0 def jump_dest(self, course, distance): assert(1 <= course <= 9) course = (course - 1) / 8 * 2 * math.pi dx = int(math.cos(course) * distance) dy = int(-math.sin(course) * distance) return self.sector.x + dx, self.sector.y + dy def rocket_dest(self, course, distance): assert(1 <= course <= 9) course = (course - 1) / 8 * 2 * math.pi dx = int(math.cos(course) * distance) dy = int(-math.sin(course) * distance) return self.x + dx, self.y + dy def launch_path(self, course): assert(1 <= course <= 9) course = (course - 1) / 8 * 2 * math.pi dx = math.cos(course) dy = -math.sin(course) return dx, dy def distance_to(self, x, y): return math.hypot(abs(self.x - x), abs(self.y - y)) def shoot_at(self, target, energy): if self.sector != target.sector: return -1 else: energy /= self.distance_to(target.x, target.y) if target.shields >= energy: target.shields -= energy return 0 else: energy -= target.shields target.shields = 0 return energy class Sector: def __init__(self, galaxy, x, y): self.squares = [bytearray(8) for i in range(8)] self.galaxy = galaxy self.x = x self.y = y self.major = False self.outpost = False self.scanned = False self.outpost_x = -1 self.outpost_y = -1 self.energy = 0 def populate(self): for y in range(8): for x in range(8): self.squares[y][x] = SPACE if random.randint(1, 2) == 1: x = random.randint(1, 8) - 1 if self.major: self.squares[y][x] = PLANET else: self.squares[y][x] = ASTEROID if self.major: x = random.randint(3, 6) - 1 y = random.randint(3, 6) - 1 self.squares[y][x] = STAR else: self.energy = 1000 def random_free_square(self): x = random.randint(1, 8) - 1 y = random.randint(1, 8) - 1 while self.squares[y][x] != SPACE: x = random.randint(1, 8) - 1 y = random.randint(1, 8) - 1 return x, y def add_ship(self, slot): ship = self.galaxy.ships[slot] x, y = self.random_free_square() self.squares[y][x] = slot + SHIPOFFS ship.sector = self ship.x = x ship.y = y def remove_ship(self, slot): ship = self.galaxy.ships[slot] self.squares[ship.y][ship.x] = SPACE ship.sector = None ship.x = -1 ship.y = -1 def spawn_enemies(self, slot): if self.outpost: count = random.randint(1, 2) else: count = random.randint(2, 3) for i in range(count): self.galaxy.ships[slot] = Ship() self.add_ship(slot) slot += 1 return slot def enemy_count(self): count = 0 for y in range(8): for x in range(8): if self.squares[y][x] > SHIPOFFS: count += 1 return count def __format__(self, fmt): star = "*" if self.major else "." if self.scanned: outpost = "!" if self.outpost else " " else: outpost = "?" if not self.scanned: return "-" + star + outpost elif self.galaxy.ships[0].sector == self: return "@" + star + outpost count = self.enemy_count() if count > 0: return str(count) + star + outpost else: return " " + star + outpost class Galaxy: def __init__(self): self.ships = [None] * MAXSHIPS self.sectors = [[None] * 8 for i in range(8)] for y in range(8): for x in range(8): self.sectors[y][x] = Sector(self, x, y) def populate(self): major_sectors = [None] * 8 for y in range(8): x = random.randint(1, 8) - 1 self.sectors[y][x].major = True major_sectors[y] = self.sectors[y][x] for x in range(8): self.sectors[y][x].populate() random.shuffle(major_sectors) for i in range(5): major_sectors[i].outpost = True x, y = major_sectors[i].random_free_square() major_sectors[i].squares[y][x] = OUTPOST major_sectors[i].outpost_x = x major_sectors[i].outpost_y = y for i in range(1, MAXSHIPS): self.ships[i] = None slot = 1 for i in range(8): slot = major_sectors[i].spawn_enemies(slot) def reset(self): for y in range(8): for x in range(8): self.sectors[y][x].major = False self.sectors[y][x].outpost = False self.sectors[y][x].scanned = False self.populate() self.ships[0] = Ship() self.ships[0].energy = 5000 self.ships[0].shields = 500 self.ships[0].missiles = 5 self.ships[0].max_e = 10000 self.ships[0].max_s = 1000 x = random.randint(1, 8) - 1 y = random.randint(1, 8) - 1 sector = self.sectors[y][x] sector.add_ship(0) sector.scanned = True def enemies_active(self): count = 0 for i in self.ships[1:]: if i != None and i.sector.outpost: count += 1 return count def outposts_found(self): count = 0 for y in range(8): for x in range(8): sector = self.sectors[y][x] if sector.outpost and sector.scanned: count += 1 return count def print_sector(sector): odds = sector.galaxy.ships[0].damage // 5 print(" 000", end="") for i in range(8): print(" {:03d}".format(i + 1), end="") print("\n") for y in range(8): print(" {:03d}".format(y + 1), end="") for x in range(8): square = sector.squares[y][x] if random.randint(1, 10) <= odds: print(" ???", end="") elif square == STAR: print(" .*.", end="") elif square == PLANET: print(" .O.", end="") elif square == ASTEROID: print(" .o.", end="") elif square == OUTPOST: print(" -!-", end="") elif square == SPACE: print(" ...", end="") elif square == SHIPOFFS: print(" -@-", end="") else: print(" -M-", end="") print("\n") def print_galaxy(galaxy): odds = galaxy.ships[0].damage // 5 print(" 000", end="") for i in range(8): print(" {:03d}".format(i + 1), end="") print("\n") for y in range(8): print(" {:03d}".format(y + 1), end="") for x in range(8): if random.randint(1, 10) <= odds: print(" ???", end="") else: sector = galaxy.sectors[y][x] print(" {0}".format(sector), end="") print("\n") def scan_area(sector): all_sec = sector.galaxy.sectors print("Scanning sectors:", end="") for y in range(sector.y - 1, sector.y + 2): for x in range(sector.x - 1, sector.x + 2): if x == sector.x and y == sector.y: pass elif 0 <= x <= 7 and 0 <= y <= 7: all_sec[y][x].scanned = True print(" ({}, {})".format(x + 1, y + 1), end="") print(" ...Done!") def print_report(galaxy): player = galaxy.ships[0] x, y = player.sector.x, player.sector.y print("\nCurrent sector: {0}, {1}".format(x + 1, y + 1)) print("Raiders active: {0}".format(galaxy.enemies_active())) print("Outposts found: {0}\n".format(galaxy.outposts_found())) print("Energy: {0:4.1f}".format(player.energy)) print("Shields: {0:4.1f}".format(player.shields)) print("Missiles: {0}".format(player.missiles)) print("\nDamage: {0}\n".format(player.damage)) def jump_player(galaxy, course, distance): player = galaxy.ships[0] cost = distance * distance * 100 x, y = player.jump_dest(course, distance) if x == player.sector.x and y == player.sector.y: print("That wouldn't take us very far at all, Captain!") return False elif distance > 8: print("We can't jump that far in one go, Captain!") return False elif cost > player.energy: print("We don't have enough energy, Captain!") return False elif 0 <= x <= 7 and 0 <= y <= 7: player.energy -= cost player.sector.remove_ship(0) galaxy.sectors[y][x].add_ship(0) galaxy.sectors[y][x].scanned = True print("Engaging Polaris drive...") print("Jumped to ({0}, {1})!".format(x + 1, y + 1)) report_energy(player) return True else: print("That would strand us in deep space, Captain!") return False def rocket_player(player, course, distance): cost = (distance + player.damage // 5) * 10 squares = player.sector.squares x, y = player.rocket_dest(course, distance) if x == player.x and y == player.y: print("That wouldn't take us very far at all, Captain!") return False elif cost > player.energy: print("We don't have enough energy, Captain!") return False elif 0 <= x <= 7 and 0 <= y <= 7: if squares[y][x] != SPACE: report_collision(squares[y][x]) return squares[player.y][player.x] = SPACE squares[y][x] = SHIPOFFS player.energy -= cost player.x = x player.y = y print("Engaging Polaris drive...") print("Rocketed to ({0}, {1})!".format(x + 1, y + 1)) report_energy(player) return True else: print("That would strand us in deep space, Captain!") return False def report_collision(square): print("But, Captain, that would send us hurtling into ", end="") if square == ASTEROID: print("an asteroid", end="") elif square == PLANET: print("a planet", end="") elif square == STAR: print("the local star", end="") elif square == OUTPOST: print("the outpost", end="") elif square > SHIPOFFS: print("that enemy ship", end="") print("!") def fire_pulse(energy, ships): assert(ships[0].energy >= energy) ships[0].energy -= energy for i in range(1, MAXSHIPS): if ships[i] == None: continue elif ships[0].sector != ships[i].sector: continue damage = ships[0].shoot_at(ships[i], energy) if damage > 0: ships[0].kills += 1 ships[0].sector.remove_ship(i) ships[i] = None print("Mechanoid ship destroyed, Captain!") else: print("Enemy shields down to {0:4.1f}%!".format( ships[i].shields / 300 * 100)) report_energy(ships[0]) def fire_meteor(course, ships): assert(ships[0].missiles > 0) odds = ships[0].damage // 5 if random.randint(1, 10) <= odds: print("Missile has misfired due to damage, Captain!") return ships[0].missiles -= 1 x, y = ships[0].x, ships[0].y dx, dy = ships[0].launch_path(course) if __debug__: print("(debug) Missile trajectory:", dx, dy) if abs(dx) < 0.01: dx = 0 if abs(dy) < 0.01: dy = 0 print("Firing Meteor missile through:", end="") while 0 <= x <= 7 and 0 <= y <= 7: x += dx y += dy print(" ({0:1.0f}, {1:1.0f})".format(x + 1, y + 1), end="") if 0 <= x <= 7 and 0 <= y <= 7: if handle_impact(ships[0].sector, int(x), int(y)): break def handle_impact(sector, x, y): assert(0 <= x <= 7 and 0 <= y <= 7) square = sector.squares[y][x] if square == SPACE or square == SHIPOFFS: return False elif square == ASTEROID: sector.squares[y][x] = SPACE print("\nOur missile has shattered an asteroid, Captain!") return True elif square == PLANET: print("\nOur missile has struck a planet, Captain!") return True elif square == STAR: print("\nOur missile has caused a solar flare, Captain!") strike_player(sector.galaxy.ships[0]) for i in range(1, MAXSHIPS): if sector.galaxy.ships[i] == None: pass elif sector.galaxy.ships[i].sector == sector: strike_enemy(sector, i) return True elif square == OUTPOST: print("\nOur missile impacted the outpost, Captain!") print("Their shields can't take many hits like that!") return True elif square > SHIPOFFS: i = square - SHIPOFFS strike_enemy(sector, i) if sector.galaxy.ships[i] != None: sector.remove_ship(i) sector.add_ship(i) print("Mechanoid ship has moved, Captain!") return True def strike_player(player): if player.shields > 250: player.shields -= 250 print("Shields holding at {0:4.1f}%.".format( player.shields / player.max_s * 100)) else: damage = 250 - player.shields player.shields = 0 player.damage += int(damage / 10) print("Shields down! We are damaged!") def strike_enemy(sector, index): galaxy = sector.galaxy ship = galaxy.ships[index] if ship.shields > 250: ship.shields -= 250 print("\nEnemy shields down to {0:4.1f}%.".format( ship.shields / 300 * 100)) else: sector.remove_ship(index) galaxy.ships[index] = None galaxy.ships[0].kills += 1 print("\nMechanoid ship destroyed, Captain.") def handle_enemy_fire(ships): for i in range(1, len(ships)): if ships[i] == None: continue elif ships[i].sector != ships[0].sector: continue elif ships[i].energy <= 1500: handle_enemy_jump(ships, i) continue energy = ships[i].distance_to(ships[0].x, ships[0].y) energy *= random.randint(8, 13) * 10 ships[i].energy -= energy damage = ships[i].shoot_at(ships[0], energy) print("Mechanoid ship at ({}, {}) is firing on us!".format( ships[i].x + 1, ships[i].y + 1)) if damage > 0: ships[0].damage += int(damage / 10) print("Shields down! We are damaged!") # TO DO: make the ship take localized damage. else: print("Shields holding at {0:4.1f}%.".format( ships[0].shields / ships[0].max_s * 100)) def handle_enemy_jump(ships, i): print("Mechanoid ship at ({}, {}) has jumped away, Captain!".format( ships[i].x + 1, ships[i].y + 1)) x = random.randint(1, 8) - 1 y = random.randint(1, 8) - 1 galaxy = ships[i].sector.galaxy ships[i].sector.remove_ship(i) galaxy.sectors[y][x].add_ship(i) ships[i].energy += random.randint(5, 10) * 100 ships[i].shields += random.randint(1, 2) * 100 def refuel_player(player): if player.sector.major: print("Can't collect hydrogen near a star, Captain!") elif player.sector.enemy_count() > 0: print("Can't refuel with enemy ships around, Captain!") else: if player.max_e - player.energy > player.sector.energy / 2: player.sector.energy /= 2 player.energy += player.sector.energy else: player.sector.energy -= player.max_e - player.energy player.energy = player.max_e print("Deploying hydrogen collectors...") report_energy(player) def dock_player(player): sec = player.sector if not sec.outpost: print("There's no outpost in this sector, Captain!") elif sec.enemy_count() > 0: print("We can't dock while under attack, Captain!") elif player.distance_to(sec.outpost_x, sec.outpost_y) > 1.5: print("We're too far from the outpost to dock!") elif player.dock_count >= 5: print("The outpost is out of supplies, Captain!") else: print("\nDocking with outpost DSO 8-{0}...\n".format( sec.y * 8 + sec.x)) player.energy += 2500 - player.dock_count * 500 report_energy(player) player.shields += 250 - player.dock_count * 50 print("Shields at {0:4.1f}%.".format( player.shields / player.max_s * 100)) player.missiles += 5 - player.dock_count print("Missile tubes loaded.") if player.damage == 0: print("No repairs needed.") elif player.damage <= 10: player.damage = 0 print("Repairs complete.") else: player.damage -= 10 print("Partial repairs performed.") player.dock_count += 1 def report_energy(ship): print("Energy reserves are at {0:4.1f}%.".format( ship.energy / ship.max_e * 100)) def reroute_energy(ship, energy): if energy > 0: if energy > ship.energy: print("We don't have that much energy, Captain!") elif ship.shields + energy > ship.max_s: print("That would overload the shields, Captain!") else: ship.energy -= energy ship.shields += energy print("Energy transferred to shields, Captain.") elif energy < 0: if energy > ship.shields: print("Our shields aren't that strong, Captain!") else: ship.shields -= -energy ship.energy += -energy print("Energy transferred from shields, Captain.") else: print("Zero units of energy rerouted, Captain!") def repair_ship(ship): if ship.sector.enemy_count() > 0: print("Can't perform field repairs under fire, Captain!") elif ship.energy < 500: print("We don't have enough energy for field repairs!") elif ship.damage == 0: print("No repairs needed, Captain!") elif ship.damage <= 5: ship.energy -= ship.damage * 100 ship.damage = 0 print("Repairs complete, Captain.") else: ship.energy -= 500 ship.damage -= 5 print("Partial repairs performed, Captain.") def score(galaxy): score = galaxy.ships[0].kills * 500 for y in range(8): for x in range(8): if galaxy.sectors[y][x].scanned: score += 100 return score class Game(cmd.Cmd): intro = game_banner prompt = "> " def __init__(self): cmd.Cmd.__init__(self) self.galaxy = Galaxy() self.galaxy.reset() self.game_over = False def do_play(self, args): """Start a new game at the given difficulty level.""" self.galaxy.reset() self.game_over = False player = self.galaxy.ships[0] if args == "easy": player.energy = 8000 player.shields = 800 print("\nNew game started at easy difficulty.") elif args == "medium" or args == "": print("\nNew game started at medium difficulty.") elif args == "hard": player.energy = 3000 player.shields = 300 print("\nNew game started at hard difficulty.") else: print("Unknown difficulty level: " + args) print("Should be one of easy, medium or hard.") print("\nNew game started at medium difficulty.") def do_chart(self, args): """Show a galactic chart of the entire mission area.""" print_galaxy(self.galaxy) def do_visual(self, args): """Show a close-range view of the sector you are in.""" print_sector(self.galaxy.ships[0].sector) def do_scan(self, args): """Perform a long-range scan of surrounding sectors.""" scan_area(self.galaxy.ships[0].sector) print_galaxy(self.galaxy) def do_report(self, args): """Show a report on game progress and ship status.""" print_report(self.galaxy) def do_jump(self, args): """Jump to another galactic sector in the mission area.""" try: course, distance = [float(i) for i in args.split()] except ValueError as e: print("Please give course and distance as numbers.") return False if 1 <= course <= 9: g = self.galaxy if jump_player(g, course, distance): odds = g.ships[0].sector.enemy_count() if odds > 0 and random.randint(1, odds) == 1: handle_enemy_fire(g.ships) else: print("Course can only go from 1 to 9.") def do_rocket(self, args): """Rocket to another square of the sector you are in.""" try: course, distance = [float(i) for i in args.split()] except ValueError as e: print("Please give course and distance as numbers.") return False if 1 <= course <= 9: player = self.galaxy.ships[0] if rocket_player(player, course, distance): handle_enemy_fire(self.galaxy.ships) else: print("Course can only go from 1 to 9.") def do_pulse(self, args): """Fire Pulsar guns at all enemy ships in the sector.""" try: energy = abs(float(args)) except ValueError as e: print("Please give amount of energy as a number.") return False ships = self.galaxy.ships if ships[0].sector.enemy_count() < 1: print("No enemy ships in weapon range, Captain.") elif energy > ships[0].energy: print("We don't have enough energy, Captain.") else: fire_pulse(energy, ships) handle_enemy_fire(ships) def do_meteor(self, args): """Fire a Meteor missile along the given course.""" try: course = float(args) except ValueError as e: print("Please give missile course as a number.") return False ships = self.galaxy.ships if ships[0].sector.enemy_count() < 1: print("No enemy ships in weapon range, Captain.") elif ships[0].missiles < 1: print("We don't have any missiles left, Captain.") else: fire_meteor(course, ships) handle_enemy_fire(ships) def do_refuel(self, args): """Deploy hydrogen collectors to replenish energy.""" refuel_player(self.galaxy.ships[0]) def do_dock(self, args): """Dock with an outpost for repairs and supplies.""" dock_player(self.galaxy.ships[0]) def do_reroute(self, args): """Reroute energy to shields (with +) and back (with -).""" try: energy = float(args) except ValueError as e: print("Please give amount of energy as a number.") return False reroute_energy(self.galaxy.ships[0], energy) def do_repair(self, args): """Perform field repairs, at the expense of energy.""" repair_ship(self.galaxy.ships[0]) def do_score(self, args): """Show the current score given the game state.""" print("\nYour score at this time:", score(self.galaxy)) def do_quit(self, args): """Quit the game and return to the operating system.""" print("\nThanks for playing! See you next time.") return True def default(self, line): if line == "EOF": print("\nThanks for playing! See you next time.") return True else: print("\nUnknown command. Type HELP for a list.") def postcmd(self, stop, line): outposts = self.galaxy.outposts_found() enemies = self.galaxy.enemies_active() if outposts == 5 and enemies == 0: if not self.game_over: print("\nMission complete, Captain!\n") print("Current score:", score(self.galaxy)) self.game_over = True return stop elif self.galaxy.ships[0].damage >= 50: print_report(self.galaxy) print("\nWe've taken too much damage, Captain!") print("Aborting the mission...\n") print("Final score:", score(self.galaxy)) return True elif line == "dock" or line == "refuel": player = self.galaxy.ships[0] if player.energy + player.shields < 100: print_report(self.galaxy) print("\nWe're out of energy, Captain!") print("Aborting the mission...\n") print("Final score:", score(self.galaxy)) return True else: return stop def help_course(self): print("\nCourse is entered as a number from 1 to 9,") print("corresponding to the following directions:") print(" 4 3 2 ") print(" \ | / ") print("5 -- -- 1") print(" / | \ ") print(" 6 7 8 ") print("You can add decimals for fine tuning.") def help_energy(self): print("\nMany actions in game require energy:") print("- long range jumps use energy equal to") print(" the distance squared times 100;") print("- rocketing within a sector uses energy") print(" equal to the distance times 10;") print("- Pulsar guns deal damage to the target") print(" equal to energy divided by distance.") def help_map(self): print("\nMap symbols used in this game:\n") print(" ? not scanned * has star ! has outpost") print("-@- your ship -M- enemy ship -!- outpost ") print(".*. star .O. planet .o. asteroid ") def help_hints(self): print("\nSome hints to help you play well:") print("- outposts are always found orbiting a star;") print("- you can only dock with outposts five times,") print(" once for each, but it can be the same outpost;") print("- your ship can rocket past any obstacles;") print("- missiles sometimes miss, or hit the wrong thing") print(" even when aimed in the right direction;") print("- the more enemies in a sector, the easier") print(" you can surprise them and shoot first;") print("- you can ignore any enemies not blockading") print(" an outpost, but they'll add to your score;") print("- you score 100 points for each scanned sector") print(" and 500 points for each enemy ship destroyed.") if __name__ == "__main__": import sys game = Game() if len(sys.argv) < 2: pass elif sys.argv[1] == "-h" or sys.argv[1] == "--help": print("Usage:\n\torion.py [-h|-v] [easy|medium|hard]") sys.exit() elif sys.argv[1] == "-v" or sys.argv[1] == "--version": print("Space Cruiser Orion v2019-05-02 by No Time To Play") sys.exit() elif sys.argv[1] == "easy": player = game.galaxy.ships[0] player.energy = 8000 player.shields = 800 elif sys.argv[1] == "hard": player = game.galaxy.ships[0] player.energy = 3000 player.shields = 300 elif sys.argv[1] != "medium": print("Unknown command-line argument:", sys.argv[1]) print("Usage:\n\torion.py [-h|-v] [easy|medium|hard]") sys.exit() game.cmdloop()