#!/usr/bin/env python """\ This is ski %s, designed by Mark Stevans, ported by Eric S. Raymond. You are hurtling down a ski slope in reverse, trying to evade the Yeti. Available commands are: l = turn left r = turn right j = jump h = hop t = teleport Enter = keep skiing i = launch ICBM d = summon Fire Demon ! = interpret line as shell command and execute. ? = print this help message. """ version='6.4' import time, random, curses, copy, sys, os REP_SNOW = '.' REP_TREE = 'Y' REP_GROUND = ':' REP_ICE = '#' REP_PLAYER = 'I' REP_YETI = 'A' REP_ICBM = '*' REP_DEMON = 'D' terrain_key = [ "Terrain types: ", REP_SNOW, " = snow ", REP_TREE, " = tree ", REP_GROUND, " = bare ground ", REP_ICE, " = ice\n\ Creatures: ", REP_PLAYER, " = player ", REP_YETI, " = yeti ", REP_ICBM, " = ICBM ", REP_DEMON, " = fire demon"] # Length of line LINE_LEN = 70 # Minimum distance from the player at which a new Yeti may appear. MIN_YETI_APPEARANCE_DISTANCE = 3 # Constant multiplied into the first element of the cellular growth # automaton probability array with each passing level. LEVEL_MULTIPLIER = 1.01 # Absolute value of maximum horizontal player speed. MAX_HORIZONTAL_PLAYER_SPEED = 5 ICBM_SPEED = 3 # Horizontal speed of an ICBM. ICBM_RANGE = 2 # Horizontal Yeti lethality range of the ICBM. DEMON_RANGE = 1 # Horizontal Yeti lethality range of the demon. DEMON_SPEED = 1 # Horizontal maximum speed of an ICBM. # Per-turn probabilities PROB_YETI_MELT = 1.0 # Of Yeti spontaneously melting. PROB_SKIS_MELT_YETI = 20.0 # Of melting Yeti by jumping over it. PROB_BAD_SPELL = 10.0 # Of "Summon Fire Demon" spell going awry. PROB_BAD_TELEPORT = 10.0 # Of a teleport going wrong. PROB_BAD_ICBM = 30.0 # Of your ICBM exploding on launch. PROB_SLIP_ON_ICE = 2.0 # Of slipping when on ice. PROB_FALL_ON_GROUND = 10.0 # Of falling down on bare ground. PROB_HIT_TREE = 25.0 # Of hitting tree, each turn in trees PROB_BAD_LANDING = 3.0 # Of land badly from jump or hop. # Number of points awarded to the player for the successful completion # of one jump. For scoring purposes, a hop is considered to consist # of exactly one half-jump. POINTS_PER_JUMP = 20 # Number of points awarded to the player for each meter of horizontal # or vertical motion during each turn. POINTS_PER_METER = 1 # Number of points awarded to the player for each Yeti that melts # during the course of the game, regardless of whether the player # passively caused the Yeti to melt by luring him from a snowbank, or # actively melted the Yeti using his skis, an ICBM, or with the # assistance of the Fire Demon. POINTS_PER_MELTED_YETI = 100 # Number of pints docked from your score for each degree of injury. POINTS_PER_INJURY_DEGREE = -40 # The injury categories. SLIGHT_INJURY = 0 MODERATE_INJURY = 3 SEVERE_INJURY = 6 # The randomness of injury degrees. INJURY_RANDOMNESS = 6 colordict = { REP_SNOW : curses.COLOR_WHITE, REP_TREE : curses.COLOR_GREEN, REP_PLAYER : curses.COLOR_MAGENTA, REP_GROUND : curses.COLOR_YELLOW, # Brown on PC-compatibles REP_ICE : curses.COLOR_CYAN, REP_YETI : curses.COLOR_BLUE, REP_ICBM : curses.COLOR_MAGENTA, REP_DEMON : curses.COLOR_RED } def percent(X): return (random.randint(0, 9999) / 100.0) <= (X) def exists(X): return X != None class SkiWorld: def __init__(self): # Constants controlling the multiple cellular growth # automatons that are executed in parallel to generate # hazards. # # Each cellular growth automaton probability element tends to control a # different facet of hazard generation: # # [0] appearance of new hazards in clear snow # [1] hazards edge growth # [2] hazards edge stability # [3] appearance of holes in solid hazards (this allows the Yeti # to work his way through forests) self.prob_tree = [0.0, 30.0, 70.0, 90.0] self.prob_ice = [0.0, 30.0, 70.0, 90.0] self.prob_ground = [0.0, 30.0, 70.0, 90.0] self.level_num = 0 self.slope = [REP_SNOW] * LINE_LEN self.player_pos = self.teleport() self.yeti = None self.icbm_pos = None self.demon_pos = None # Randomize the appearance probabilities. self.prob_tree[0] = (random.randint(0, 99)) / 500.0 + 0.05 self.prob_ice[0] = (random.randint(0, 99)) / 500.0 + 0.05 self.prob_ground[0] = (random.randint(0, 99)) / 500.0 + 0.05 self.prob_yeti_appearance = (random.randint(0, 99)) / 25.0 + 1.0 def terrain(self): "What kind of terrain are we on?" return self.slope[self.player_pos] def nearby(self, pos, min=1): "Is the specified position near enough to the player?" return exists(pos) and abs(pos - self.player_pos) <= min def teleport(self): "Return a random location" return random.choice(range(len(self.slope))) def gen_next_slope(self, next_level): # Generates the slope of the next level, dependent upon # the characteristics of the current level, the probabilities, and the # position of the player. # Stash away old state so we don't step on it while generating new current_slope = copy.copy(self.slope) # Generate each character of the next level. for i in range(len(self.slope)): # Count the number of nearby trees, ice patches, and # ground patches on the current level. num_nearby_trees = 0 num_nearby_ice = 0 num_nearby_ground = 0 if current_slope[i] == REP_TREE: num_nearby_trees += 1 if current_slope[i] == REP_ICE: num_nearby_ice += 1 if current_slope[i] == REP_GROUND: num_nearby_ground += 1 if i > 0: if current_slope[i - 1] == REP_TREE: num_nearby_trees += 1 elif current_slope[i - 1] == REP_ICE: num_nearby_ice += 1 elif current_slope[i - 1] == REP_GROUND: num_nearby_ground += 1 if i < len(self.slope) - 1: if current_slope[i + 1] == REP_TREE: num_nearby_trees += 1 elif current_slope[i + 1] == REP_ICE: num_nearby_ice += 1 elif current_slope[i + 1] == REP_GROUND: num_nearby_ground += 1 # Generate this character of the next level based upon # the characteristics of the nearby characters on the # current level. if percent(self.prob_tree[num_nearby_trees]) and \ ((i != self.player_pos) or (num_nearby_trees > 0)): self.slope[i] = REP_TREE elif percent(self.prob_ice[num_nearby_ice]) and \ ((i != self.player_pos) or (num_nearby_ice > 0)): self.slope[i] = REP_ICE elif percent(self.prob_ground[num_nearby_ground]) and \ ((i != self.player_pos) or (num_nearby_ground > 0)): self.slope[i] = REP_GROUND else: self.slope[i] = REP_SNOW def update_level(self, player): # Go to the next level, and move the player. self.level_num += 1 # Figure out the new player position based on a modulo # addition. Note that we must add the line length into the # expression before taking the modulus to make sure that we # are not taking the modulus of a negative integer. self.player_pos = (self.player_pos + player.player_speed + len(self.slope)) % len(self.slope) # Generate the updated slope. self.gen_next_slope(self) # If there is no Yeti, one might be created. if not exists(self.yeti) and percent(self.prob_yeti_appearance): # Make sure that the Yeti does not appear too # close to the player. while True: self.yeti = self.teleport() if not self.nearby(self.yeti,MIN_YETI_APPEARANCE_DISTANCE): break # Increase the initial appearance probabilities of all obstacles and # the Yeti. self.prob_tree[0] *= LEVEL_MULTIPLIER self.prob_ice[0] *= LEVEL_MULTIPLIER self.prob_ground[0] *= LEVEL_MULTIPLIER self.prob_yeti_appearance *= LEVEL_MULTIPLIER def manipulate_objects(self, player): # If there is a Yeti, the player's jet-powered skis may melt him, # or he may spontaneously melt. Otherwise move him towards the player. # If there is a tree in the way, the Yeti is blocked. if exists(self.yeti): if (self.nearby(self.yeti) and percent(PROB_SKIS_MELT_YETI)) \ or percent(PROB_YETI_MELT): self.yeti = None player.num_snomen_melted += 1 if exists(self.yeti): if self.yeti < self.player_pos: if self.slope[self.yeti + 1] != REP_TREE: self.yeti += 1 else: if self.slope[self.yeti - 1] != REP_TREE: self.yeti -= 1 # If there is an ICBM, handle it. if exists(self.icbm_pos): # If there is a Yeti, move the ICBM towards him. Else, # self-destruct the ICBM. if exists(self.yeti): if self.icbm_pos < self.yeti: self.icbm_pos += ICBM_SPEED else: self.icbm_pos -= ICBM_SPEED else: self.icbm_pos = None # If there is a fire demon on the level, handle it. if exists(self.demon_pos): # If there is a Yeti on the current level, move the demon # towards him. Else, the demon might decide to leave. if exists(self.yeti): if self.demon_pos < self.yeti: self.demon_pos += DEMON_SPEED else: self.demon_pos -= DEMON_SPEED else: if percent(25.0): self.demon_pos = None # If there is a Yeti and an ICBM on the slope, the Yeti # might get melted. if exists(self.yeti) and exists(self.icbm_pos): if abs(self.yeti - self.icbm_pos) <= ICBM_RANGE: self.icbm_pos = None self.yeti = None player.num_snomen_melted += 1 # If there is a Yeti and a fire demon, he might get melted. if exists(self.yeti) and exists(self.demon_pos): if abs(self.yeti - self.demon_pos) <= 1: self.yeti = None player.num_snomen_melted += 1 def __repr__(self): "Create a picture of the current level." picture = copy.copy(self.slope) picture[self.player_pos] = REP_PLAYER if exists(self.yeti): picture[self.yeti] = REP_YETI if exists(self.demon_pos): picture[self.demon_pos] = REP_DEMON if exists(self.icbm_pos): picture[self.icbm_pos] = REP_ICBM picture = colorize(picture) return "%4d %s" % (self.level_num, picture) class SkiPlayer: injuries = ( "However, you escaped injury!", "But you weren't hurt at all!", "But you only got a few scratches.", "You received some cuts and bruises.", "You wind up with a concussion and some contusions.", "You now have a broken rib.", "Your left arm has been fractured.", "You suffered a broken ankle.", "You have a broken arm and a broken leg.", "You have four broken limbs and a cut!", "You broke every bone in your body!", "I'm sorry to tell you that you have been killed....") def __init__(self): self.jump_count = -1 self.num_snomen_melted = 0 self.num_jumps_attempted = 0.0 self.player_speed = 0 self.meters_travelled = 0 def __accident(self, msg, severity): # "__accident" is called when the player gets into an __accident, # which ends the game. "msg" is the description of the # __accident type, and "severity" is the severity. This # function should never return. # Compute the degree of the player's injuries. degree = severity + random.randint(0, INJURY_RANDOMNESS - 1) # Print a message indicating the termination of the game. print "!\n\n%s %s\n" % (msg, SkiPlayer.injuries[degree]) # Print the statistics of the game. print "You skiied %d meters with %d jumps and melted %d %s."%(\ self.meters_travelled, self.num_jumps_attempted, self.num_snomen_melted, ("Yeti", "Yetis")[self.num_snomen_melted != 1]) # Initially calculate the player's score based upon the number of # meters travelled. score = self.meters_travelled * POINTS_PER_METER # Add bonus points for the number of jumps completed. score += self.num_jumps_attempted * POINTS_PER_JUMP # Add bonus points for each Yeti that melted during the course of # the game. score += self.num_snomen_melted * POINTS_PER_MELTED_YETI # Subtract a penalty for the degree of injury experienced by the # player. score += degree * POINTS_PER_INJURY_DEGREE # Negative scores are just too silly. if score < 0: score = 0 # Print the player's score. print "Your score for this run is %ld." % score # Exit the game with a code indicating successful completion. sys.exit(0) def check_obstacles(self, world): # If we are just landing after a jump, we might fall down. if (self.jump_count == 0) and percent(PROB_BAD_LANDING): self.__accident("Whoops! A bad landing!", LIGHT_INJURY) # If there is a tree in our position, we might hit it. if (world.terrain() == REP_TREE) and percent(PROB_HIT_TREE): self.__accident("Oh no! You hit a tree!", SEVERE_INJURY) # If there is bare ground under us, we might fall down. if (world.terrain() == REP_GROUND) and percent(PROB_FALL_ON_GROUND): self.__accident("You fell on the ground!", MODERATE_INJURY) # If we are on ice, we might slip. if (world.terrain() == REP_ICE) and percent(PROB_SLIP_ON_ICE): self.__accident("Oops! You slipped on the ice!",SLIGHT_INJURY) # If there is a Yeti next to us, he may grab us. if world.nearby(world.yeti): self.__accident("Yikes! The Yeti's got you!",MODERATE_INJURY) def update_player(self): "Update state of player for current move." self.meters_travelled += abs(self.player_speed) + 1 # If the player was jumping, decrement the jump count. if player.jump_count >= 0: player.jump_count -= 1 def do_command(self, world, cmdline): # Print a prompt, and read a command. Return True to advance game. cmd = cmdline[0].upper() if cmd == '?': print __doc__ % version, terrain_key return False elif cmd == '!': os.system(cmdline[1:]) return False elif cmd == 'R': # Move right if ((world.terrain() != REP_ICE) and \ (self.player_speed < MAX_HORIZONTAL_PLAYER_SPEED)): self.player_speed += 1 return True elif cmd == 'L': # Move left if ((world.terrain() != REP_ICE) and \ (self.player_speed > -MAX_HORIZONTAL_PLAYER_SPEED)): self.player_speed -= 1 return True elif cmd == 'J': # Jump self.jump_count = random.randint(0, 5) + 4 self.num_jumps_attempted += 1.0 return True elif cmd == 'H': # Do a hop self.jump_count = random.randint(0, 2) + 2 self.num_jumps_attempted += 0.5 return True elif cmd == 'T': # Attempt teleportation if percent(PROB_BAD_TELEPORT): self.__accident("You materialized 25 feet in the air!", SLIGHT_INJURY) world.player_pos = world.teleport() return True elif cmd == 'I': # Launch backpack ICBM if percent(PROB_BAD_ICBM): self.__accident("Nuclear blast in your backpack!", SEVERE_INJURY) world.icbm_pos = world.player_pos return True elif cmd == 'D': # Incant spell for fire demon if percent(PROB_BAD_SPELL): self.__accident("A bad spell -- the demon grabs you!", MODERATE_INJURY) world.demon_pos = world.teleport() return True else: # Any other command just advances return True def colorize(picture): "Colorize special characters in a display list." for i in range(len(picture)): if (i == 0 or (picture[i] != picture[i-1][-1])): if picture[i] in colordict: picture[i] = colordict[picture[i]] + picture[i] else: picture[i] = reset + picture[i] picture += reset return "".join(picture) # Main sequence if __name__ == "__main__": # Initialize the random number generator. random.seed(time.time()) # Arrange for color to be available curses.setupterm() color = curses.tigetstr("setaf") for (ch, idx) in colordict.items(): if color: colordict[ch] = curses.tparm(color, idx) else: colordict[ch] = "" reset = curses.tigetstr("sgr0") or "" terrain_key = colorize(terrain_key) print "SKI! Version %s. Type ? for help." % version world = SkiWorld() player = SkiPlayer() # Perform the game loop until the game is over. try: repeat = 1 while True: sys.stdout.write(`world`) # If we are jumping, just finish the line. Otherwise, check for # obstacles, and do a command. if player.jump_count >= 0: sys.stdout.write('\n') else: player.check_obstacles(world) if repeat: repeat -= 1 if repeat: sys.stdout.write('\n') else: cmd = raw_input("? ") if cmd == '': cmd = '\n' elif cmd[0] in "0123456789": if cmd[-1] in "0123456789": repeat = int(cmd) cmd = '\n' else: repeat = int(cmd[:-1]) cmd = cmd[-1] if not player.do_command(world, cmd): continue world.manipulate_objects(player) world.update_level(player) player.update_player() except (KeyboardInterrupt, EOFError): sys.stdout.write(reset) print "\nBye!" # end