Last active
July 18, 2018 22:50
-
-
Save bootandy/f9438a6253e5e58b5e5b7a89b85dfcd9 to your computer and use it in GitHub Desktop.
A tiny text based game based on FTL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import math | |
from os import system, name | |
ship_types = [ | |
"Cobra", "Krait", "Viper", "Anaconda", "Sidewinder", "Vulture", "Asp", | |
] | |
ship_grading = [ | |
"Rookie", | |
"Veteran", | |
"Competant", | |
"Dangerous", | |
"Deadly", | |
"Elite" | |
] | |
free_scrap_str = [ | |
'Wow free scrap', | |
'There seems to have been a battle here recently', | |
'A disused mining robot can be junked for scrap', | |
'An empty ship shell can be converted into scrap' | |
] | |
free_weapon_str = [ | |
'Boarding an abandoned ship allows you to salvage a weapon', | |
'A weapon just floating there in space!', | |
'Scanning an astroid reveals a small arms cache', | |
'An abandoned settlement has an abandoned weapon system', | |
] | |
shop_names = [ | |
"Aperture Science Outlet", "Lacuna, Inc.", "Cyberdyne Systems", "Planet Express, Inc.", "Stark Industries", "Soylent Industry", "Weyland Corp" | |
] | |
movie_names = [ | |
"2001 Space Odyssey", "Close Encounters of the Third Kind", "Star Wars", "Forbidden Planet", "Dune", "The Last Star Fighter", "Solaris", "Alien", "Serenity" | |
] | |
to_do_for_movie = [ | |
"hum the theme tune from", "think of your favourite scene from", "contemplate who is the most attractive from", "wonder what you would do if this was a scene from" | |
] | |
weapons_start = { | |
0:'Tiny', | |
1:'Standard', | |
2:'Dangerous', | |
3:'Military', | |
4:'Epic', | |
} | |
weapons_mid = { | |
0:('Photon', 2, 7, 0.9), | |
1:('Tachyon', 1, 8, 0.95), | |
2:('Anti-Matter', 4, 5, 0.95), | |
3:('Death', 3, 10, 0.8), | |
} | |
weapons_end = { | |
0:('Canon', 1, 1), | |
1:('Blaster', 2, 1.3), | |
2:('Ray', 3, 1.6), | |
3:('Missile', 5, 2.0), | |
} | |
color_format = { | |
'RED': '\033[91m', | |
'GREEN': '\033[92m', | |
'END': '\033[0m', | |
} | |
class Weapon(): | |
def __init__(self, name, low_dam, high_dam, freq, reliability, cost=None): | |
self.name = name | |
self.low_dam = (int)(low_dam) | |
self.high_dam = (int)(high_dam) | |
self.freq = (int)(freq) | |
self.reliability = reliability | |
self.cool_down = 0 | |
if cost is None: | |
self.mycost = int((self.low_dam + self.high_dam)*3 / (math.sqrt(self.freq))) | |
else: | |
self.mycost = cost | |
def fire_weapon(self): | |
if self.cool_down == 0: | |
self.cool_down = max(self.cool_down, self.freq) | |
if random.random() > math.sqrt(self.reliability): | |
print(as_red('The {} missfires'.format(self.name))) | |
return 0 | |
if random.random() > self.reliability: | |
print(as_red('The {} critically over heats!'.format(self.name))) | |
self.cool_down *= 3 | |
return random.randint(self.low_dam, self.high_dam) | |
else: | |
return 0 | |
def cool(self): | |
self.cool_down = max(self.cool_down - 1, 0) | |
def as_str(self): | |
return '{:30s}{:2d} {:2d} {:2d} {:.2f}'.format(self.name, self.low_dam, self.high_dam, self.freq, self.reliability) | |
def buy_cost(self): | |
return self.mycost | |
def sell_cost(self): | |
return int(self.buy_cost() * 0.75) | |
class Hostile(): | |
def __init__(self, name, level): | |
self.name = name | |
self.level = level | |
self.weapons = {} | |
for i in range(1, level + 1): | |
self.weapons[str(i)] = weapon_builder(level - random.randint(0, 1)) | |
self.health = random.randint(10, 20) + level * 8 | |
def choose_weapon(self): | |
# Allow hostile captain 10 attempts to choose a weapon that is ready to fire | |
for _ in range(0, 10): | |
wp_index = random.choice(list(self.weapons.keys())) | |
if self.weapons[wp_index].cool_down <= 0: | |
return self.weapons[wp_index] | |
return self.weapons[wp_index] | |
def desc(self): | |
return "{} {}".format(ship_grading[min(self.level, len(ship_grading)) - 1], self.name) | |
class Me(): | |
def __init__(self): | |
self.health = 100 | |
self.weapons = {} | |
for i in range(0, 3): | |
self.weapons[str(i + 1)] = weapon_builder(1) | |
self.kills = 0 | |
def choose_weapon(self): | |
print('Choose weapon to file:') | |
print(' Turn b4 Rdy, {:30s}Min Dam, Max Dam, Cooldown, Reliability'.format('Name,')) | |
for k in ['1', '2', '3', '4']: | |
if k in self.weapons: | |
v = self.weapons[k] | |
if v.cool_down > 0: | |
weapon_str = as_red(v.as_str()) | |
else: | |
weapon_str = as_green(v.as_str()) | |
print('{} : {:3d} {}'.format(k, v.cool_down, weapon_str)) | |
else: | |
print('{} : {}'.format(k, as_red('None'))) | |
wp = input() | |
return self.weapons.get(wp) | |
def get_rnd_scrap(self): | |
self.get_scrap(random.randint(1, 10)) | |
def get_scrap(self, bonus): | |
print('You collect {} health from scrap'.format(as_green(str(bonus)))) | |
self.health += bonus | |
def update_weapon(self, weapon, is_at_shop=False): | |
if not weapon: | |
return | |
print('Select a weapon to replace 1 -> 4 (or press q to abandon fitting)') | |
for k in ['1', '2', '3', '4']: | |
name = 'EMPTY' | |
if k in self.weapons: | |
name = self.weapons[k].name | |
print('{} : {}'.format(k, name)) | |
wp = '' | |
while wp not in ['1', '2', '3', '4', 'q']: | |
wp = input() | |
if wp in ['1', '2', '3', '4']: | |
if is_at_shop: | |
self.sell(wp) | |
self.health -= weapon.buy_cost() | |
self.weapons[wp] = weapon | |
print('The new {} is fitted'.format(weapon.name)) | |
def sell(self, choice): | |
if choice in self.weapons.keys(): | |
self.health += self.weapons[choice].sell_cost() | |
print('You sell your {}. Your health is now: {}'.format(self.weapons[choice].name, self.health)) | |
del self.weapons[choice] | |
class GameState(): | |
sub_zone_limit = 7 | |
def __init__(self): | |
self.zone = 10 | |
self.me = Me() | |
# These flags added to stop you having the same chance encouner twice per zone. | |
self.has_seen_cargo = False | |
self.has_done_nothing = False | |
self.has_free_scrap = False | |
self.has_done_bargin = False | |
self.has_free_weapon = False | |
def move_zone(self): | |
self.zone += 1 | |
if self.zone % 10 == GameState.sub_zone_limit: | |
self.zone = self.zone + (10 - GameState.sub_zone_limit) | |
self.has_seen_cargo = False | |
self.has_done_nothing = False | |
self.has_free_scrap = False | |
self.has_done_bargin = False | |
self.has_free_weapon = False | |
def level(self): | |
return int(self.zone / 10) | |
def generate_cargo(self): | |
# We can encounter cargo only once per zone. | |
if self.has_seen_cargo: | |
return False | |
if self.zone % 10 == [0, 1, 2]: | |
will_see = random.random() < 0.5 | |
else: | |
will_see = True | |
if will_see: | |
self.has_seen_cargo = True | |
return True | |
return False | |
def as_green(s): | |
return "{}{}{}".format(color_format['GREEN'], str(s), color_format['END']) | |
def as_red(s): | |
return "{}{}{}".format(color_format['RED'], str(s), color_format['END']) | |
def weapon_builder(level): | |
level = max(0, min(level - 1, 4)) | |
start = weapons_start[level] | |
mid = random.choice(weapons_mid) | |
end = random.choice(weapons_end) | |
name = start + " " + mid[0] + " " + end[0] | |
low_dam = (mid[1] + level * 2) * end[2] | |
high_dam = (mid[2] + level * 2) * end[2] | |
freq = end[1] | |
reliability = mid[3] | |
return Weapon(name, low_dam, high_dam, freq, reliability) | |
def valuable_cargo_builder(level): | |
return Weapon('Valuable Cargo', 0, 0, 0, 1.0, cost=50 + 20 * min(4, level)) | |
def print_fight_stats(me, hostile): | |
print('\n') | |
sa = 'Your health: {:3d}'.format(me.health) | |
sb = ' vs ' | |
sc = '{} health: {:3d}'.format(hostile.desc(), hostile.health) | |
len_all = len(sa) + len(sb) + len(sc) | |
print('-'*len_all) | |
print(as_green(sa) + sb + as_red(sc)) | |
print('-'*len_all + '\n') | |
def fight(gs): | |
me = gs.me | |
hostile = Hostile(random.choice(ship_types), gs.level()) | |
verbs = ['attacked', 'jumped', 'ambushed', 'challenged'] | |
print('You are {} by an enemy {}'.format(random.choice(verbs), as_red(hostile.desc()))) | |
print_fight_stats(me, hostile) | |
while me.health > 0 and hostile.health > 0: | |
wp = me.choose_weapon() | |
clear() | |
if wp: | |
if wp.cool_down == 0: | |
dmg = wp.fire_weapon() | |
hostile.health -= dmg | |
red_health = as_red('{}'.format(hostile.health)) | |
damage = as_green('{}'.format(dmg)) | |
print('Fire {}. You deal {} damage. Enemy health: {}'.format(wp.name, damage, red_health)) | |
else: | |
print('That weapon fails to fire - wait for cooldown') | |
else: | |
print('You did not select a valid weapon. Nothing happens') | |
print('\n') | |
if hostile.health > 0: | |
wp = hostile.choose_weapon() | |
if wp and wp.cool_down == 0: | |
dmg = wp.fire_weapon() | |
me.health -= dmg | |
my_health = as_green('{}'.format(me.health)) | |
damage = as_red('{}'.format(dmg)) | |
print('Enemy {} fires {}. Does {} damage, your health: {}'.format(hostile.desc(), wp.name, damage, my_health)) | |
else: | |
print('Enemy {} does not fire a weapon.'.format(hostile.desc())) | |
print_fight_stats(me, hostile) | |
for wp in me.weapons.values(): | |
wp.cool() | |
for wp in hostile.weapons.values(): | |
wp.cool() | |
print() | |
if hostile.health <= 0: | |
print('Enemy {} is destroyed'.format(hostile.name)) | |
me.kills += 1 | |
print('Choose your spoils') | |
print(' {:30s}Min Dam, Max Dam, Cooldown, Reliability'.format('Name,')) | |
wp = random.choice(list(hostile.weapons.values())) | |
print('w: {}'.format(wp.as_str())) | |
scrap = random.randint(3, 15) + gs.level() * 2 | |
print('s: Choose {} scrap '.format(scrap)) | |
gen_cargo = gs.generate_cargo() | |
if gen_cargo: | |
print('v: Choose valuable cargo') | |
choice = input() | |
while choice not in ['q', 'w', 's', 'v']: | |
print('Choose w, v, s or q to quit') | |
choice = input() | |
if choice == 's': | |
me.get_scrap(scrap) | |
if choice == 'v' and gen_cargo: | |
me.update_weapon(valuable_cargo_builder(hostile.level)) | |
if choice == 'w': | |
me.update_weapon(wp) | |
for wp in me.weapons.values(): | |
wp.cool_down = 0 | |
def shop(gs): | |
me = gs.me | |
shop_name = random.choice(shop_names) | |
print('Welcome to the {} shop '.format(shop_name)) | |
weapons = {} | |
for i in range(6, 10): | |
wp = weapon_builder(int(gs.zone / 10) - 1 + random.randint(0, 1)) | |
weapons[str(i)] = wp # horrible str int casting in this code | |
choice = '' | |
while choice != 'q': | |
print('Your health (& money): {}'.format(as_green(me.health))) | |
print('To Sell: ') | |
print(' Cost, {:30s}Min Dam, Max Dam, Cooldown, Reliability'.format('Name,')) | |
for k in ['1', '2', '3', '4']: | |
if k in me.weapons: | |
wp = me.weapons[k] | |
print('{}: {:4d} {}'.format(k, wp.sell_cost(), wp.as_str())) | |
print('To Buy: ') | |
print(' Cost, {:30s}Min Dam, Max Dam, Cooldown, Reliability'.format('Name,')) | |
for k, wp in weapons.items(): | |
print('{}: {:4d} {}'.format(k, wp.buy_cost(), wp.as_str())) | |
print('To buy/sell select the number on the left. To leave the shop press \'q\' (quit)') | |
choice = input() | |
clear() | |
if choice in weapons.keys(): | |
me.update_weapon(weapons[choice], is_at_shop=True) | |
del weapons[choice] | |
if choice in me.weapons.keys(): | |
me.sell(choice) | |
print('Thank you for shopping at {}'.format(shop_name)) | |
def _enter_y_or_n(): | |
choice = input() | |
while choice not in ['y', 'n']: | |
print('Enter y or n') | |
choice = input() | |
return choice | |
def bargin(me): | |
cost = random.randint(0, 12) + 4 | |
dice = random.randint(0, 2) | |
if dice == 0: | |
print('Increase minimum & maximum damage of your weapons for cost of {} health? y / n'.format(cost)) | |
if _enter_y_or_n() == 'y': | |
me.health -= cost | |
for w in me.weapons.values(): | |
w.low_dam += 1 | |
w.high_dam += 1 | |
elif dice == 1: | |
print('Make all your weapons totally reliable for cost of {} health? y / n'.format(cost)) | |
if _enter_y_or_n() == 'y': | |
me.health -= cost | |
for w in me.weapons.values(): | |
w.reliability = 1.0 | |
else: | |
print('Decrease all your weapon cooldowns by 1 for cost of {} health? y / n'.format(cost)) | |
if _enter_y_or_n() == 'y': | |
me.health -= cost | |
for w in me.weapons.values(): | |
w.freq = 0 | |
def clear(): | |
if name == 'nt': | |
_ = system('cls') | |
else: | |
_ = system('clear') | |
def encounter(gs): | |
clear() | |
print('You are in zone: {}. You have {} health'.format(as_green(str(gs.zone/10.0)), as_green(str(gs.me.health)))) | |
if gs.zone % 10 == 0: | |
shop(gs) | |
else: | |
dice_roll = random.randint(0, 9) | |
if dice_roll == 0 and not gs.has_free_scrap: | |
gs.has_free_scrap = True | |
print(random.choice(free_scrap_str)) | |
gs.me.get_rnd_scrap() | |
elif dice_roll == 1 and not gs.has_done_nothing: | |
gs.has_done_nothing = True | |
print('It is very quiet here.') | |
print('You {} the movie {}'.format(random.choice(to_do_for_movie), random.choice(movie_names))) | |
print('Nothing happens') | |
elif dice_roll == 2 and not gs.has_done_bargin: | |
gs.has_done_bargin = True | |
print('A Faustian bargin is proposed!') | |
bargin(gs.me) | |
elif dice_roll == 3 and not gs.has_free_weapon: | |
gs.has_free_weapon = True | |
weapon = weapon_builder(gs.level()) | |
print(random.choice(free_weapon_str)) | |
print() | |
print('{:30s}Min Dam, Max Dam, Cooldown, Reliability'.format('Name,')) | |
print('{}'.format(weapon.as_str())) | |
print() | |
gs.me.update_weapon(weapon) | |
else: | |
fight(gs) | |
def play(): | |
gs = GameState() | |
clear() | |
print('Your aim is to travel across as many zones as you can. Each zone has {} phases'.format(gs.sub_zone_limit)) | |
print('There is a shop at the end of each zone.') | |
print('-----------------------------------------------\n') | |
while gs.me.health > 0: | |
gs.move_zone() | |
print('\nPress any key to continue') | |
_ = input() | |
encounter(gs) | |
print('You are dead, you destroyed {} ships and got to zone {}'.format(gs.me.kills, gs.zone/10.0)) | |
play() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment