# Escape - A Python Adventure # by Sean McManus / www.sean.co.uk # Art by Rafael Pimenta # THIS VERSION IS MODIFIED TO INCLUDE AN EASTER EGG HUNT # HIDING OBJECTS SECTION AT THE END IS ALSO NEW IN THIS VERSION # MODIFICATIONS ARE COMMENTED (SEARCH FOR "EASTER") # SEE https://www.sean.co.uk/books/mission-python/adding_hidden_objects.shtm import time, random, math ############### ## VARIABLES ## ############### WIDTH = 800 #window size HEIGHT = 800 #PLAYER variables PLAYER_NAME = "Sean" # change this to your name! FRIEND1_NAME = "Karen" # change this to a friend's name! FRIEND2_NAME = "Leo" # change this to another friend's name! current_room = 31 # start room = 31 top_left_x = 100 top_left_y = 150 DEMO_OBJECTS = [images.floor, images.pillar, images.soil] LANDER_SECTOR = random.randint(1, 24) LANDER_X = random.randint(2, 11) LANDER_Y = random.randint(2, 11) TILE_SIZE = 30 player_y, player_x = 2, 5 game_over = False PLAYER = { "left": [images.spacesuit_left, images.spacesuit_left_1, images.spacesuit_left_2, images.spacesuit_left_3, images.spacesuit_left_4 ], "right": [images.spacesuit_right, images.spacesuit_right_1, images.spacesuit_right_2, images.spacesuit_right_3, images.spacesuit_right_4 ], "up": [images.spacesuit_back, images.spacesuit_back_1, images.spacesuit_back_2, images.spacesuit_back_3, images.spacesuit_back_4 ], "down": [images.spacesuit_front, images.spacesuit_front_1, images.spacesuit_front_2, images.spacesuit_front_3, images.spacesuit_front_4 ] } player_direction = "down" player_frame = 0 player_image = PLAYER[player_direction][player_frame] player_offset_x, player_offset_y = 0, 0 PLAYER_SHADOW = { "left": [images.spacesuit_left_shadow, images.spacesuit_left_1_shadow, images.spacesuit_left_2_shadow, images.spacesuit_left_3_shadow, images.spacesuit_left_3_shadow ], "right": [images.spacesuit_right_shadow, images.spacesuit_right_1_shadow, images.spacesuit_right_2_shadow, images.spacesuit_right_3_shadow, images.spacesuit_right_3_shadow ], "up": [images.spacesuit_back_shadow, images.spacesuit_back_1_shadow, images.spacesuit_back_2_shadow, images.spacesuit_back_3_shadow, images.spacesuit_back_3_shadow ], "down": [images.spacesuit_front_shadow, images.spacesuit_front_1_shadow, images.spacesuit_front_2_shadow, images.spacesuit_front_3_shadow, images.spacesuit_front_3_shadow ] } player_image_shadow = PLAYER_SHADOW["down"][0] PILLARS = [ images.pillar, images.pillar_95, images.pillar_80, images.pillar_60, images.pillar_50 ] wall_transparency_frame = 0 BLACK = (0, 0, 0) BLUE = (0, 155, 255) YELLOW = (255, 255, 0) WHITE = (255, 255, 255) GREEN = (0, 255, 0) RED = (128, 0, 0) air, energy = 100, 100 suit_stitched, air_fixed = False, False launch_frame = 0 ############### ## MAP ## ############### MAP_WIDTH = 5 MAP_HEIGHT = 10 MAP_SIZE = MAP_WIDTH * MAP_HEIGHT GAME_MAP = [ ["Room 0 - where unused objects are kept", 0, 0, False, False] ] outdoor_rooms = range(1, 26) for planetsectors in range(1, 26): #rooms 1 to 25 are generated here GAME_MAP.append( ["The dusty planet surface", 13, 13, True, True] ) GAME_MAP += [ #["Room name", height, width, Top exit?, Right exit?] ["The airlock", 13, 5, True, False], # room 26 ["The engineering lab", 13, 13, False, False], # room 27 ["Poodle Mission Control", 9, 13, False, True], # room 28 ["The viewing gallery", 9, 15, False, False], # room 29 ["The crew's bathroom", 5, 5, False, False], # room 30 ["The airlock entry bay", 7, 11, True, True], # room 31 ["Left elbow room", 9, 7, True, False], # room 32 ["Right elbow room", 7, 13, True, True], # room 33 ["The science lab", 13, 13, False, True], # room 34 ["The greenhouse", 13, 13, True, False], # room 35 [PLAYER_NAME + "'s sleeping quarters", 9, 11, False, False], # room 36 ["West corridor", 15, 5, True, True], # room 37 ["The briefing room", 7, 13, False, True], # room 38 ["The crew's community room", 11, 13, True, False], # room 39 ["Main Mission Control", 14, 14, False, False], # room 40 ["The sick bay", 12, 7, True, False], # room 41 ["West corridor", 9, 7, True, False], # room 42 ["Utilities control room", 9, 9, False, True], # room 43 ["Systems engineering bay", 9, 11, False, False], # room 44 ["Security portal to Mission Control", 7, 7, True, False], # room 45 [FRIEND1_NAME + "'s sleeping quarters", 9, 11, True, True], # room 46 [FRIEND2_NAME + "'s sleeping quarters", 9, 11, True, True], # room 47 ["The pipeworks", 13, 11, True, False], # room 48 ["The chief scientist's office", 9, 7, True, True], # room 49 ["The robot workshop", 9, 11, True, False] # room 50 ] #simple sanity check on map above to check data entry assert len(GAME_MAP)-1 == MAP_SIZE, "Map size and GAME_MAP don't match" ############### ## OBJECTS ## ############### objects = { 0: [images.floor, None, "The floor is shiny and clean"], 1: [images.pillar, images.full_shadow, "The wall is smooth and cold"], 2: [images.soil, None, "It's like a desert. Or should that be dessert?"], 3: [images.pillar_low, images.half_shadow, "The wall is smooth and cold"], 4: [images.bed, images.half_shadow, "A tidy and comfortable bed"], 5: [images.table, images.half_shadow, "It's made from strong plastic."], 6: [images.chair_left, None, "A chair with a soft cushion"], 7: [images.chair_right, None, "A chair with a soft cushion"], 8: [images.bookcase_tall, images.full_shadow, "Bookshelves, stacked with reference books"], 9: [images.bookcase_small, images.half_shadow, "Bookshelves, stacked with reference books"], 10: [images.cabinet, images.half_shadow, "A small locker, for storing personal items"], 11: [images.desk_computer, images.half_shadow, "A computer. Use it to run life support diagnostics"], 12: [images.plant, images.plant_shadow, "A spaceberry plant, grown here"], 13: [images.electrical1, images.half_shadow, "Electrical systems used for powering the space station"], 14: [images.electrical2, images.half_shadow, "Electrical systems used for powering the space station"], 15: [images.cactus, images.cactus_shadow, "Ouch! Careful on the cactus!"], 16: [images.shrub, images.shrub_shadow, "A space lettuce. A bit limp, but amazing it's growing here!"], 17: [images.pipes1, images.pipes1_shadow, "Water purification pipes"], 18: [images.pipes2, images.pipes2_shadow, "Pipes for the life support systems"], 19: [images.pipes3, images.pipes3_shadow, "Pipes for the life support systems"], 20: [images.door, images.door_shadow, "Safety door. Opens automatically \ for astronauts in functioning spacesuits."], 21: [images.door, images.door_shadow, "The airlock door. \ For safety reasons, it requires two person operation."], 22: [images.door, images.door_shadow, "A locked door. It needs " \ + PLAYER_NAME + "'s access card"], 23: [images.door, images.door_shadow, "A locked door. It needs " \ + FRIEND1_NAME + "'s access card"], 24: [images.door, images.door_shadow, "A locked door. It needs " \ + FRIEND2_NAME + "'s access card"], 25: [images.door, images.door_shadow, "A locked door. It is opened from Main Mission Control"], 26: [images.door, images.door_shadow, "A locked door in the engineering bay."], 27: [images.map, images.full_shadow, "The screen says the crash site was Sector: " \ + str(LANDER_SECTOR) + " // X: " + str(LANDER_X) + \ " // Y: " + str(LANDER_Y)], 28: [images.rock_large, images.rock_large_shadow, "A rock. Its coarse surface feels like a whetstone", "the rock"], 29: [images.rock_small, images.rock_small_shadow, "A small but heavy piece of Martian rock"], 30: [images.crater, None, "A crater in the planet surface"], 31: [images.fence, None, "A fine gauze fence. It helps protect the station from dust storms"], 32: [images.contraption, images.contraption_shadow, "One of the scientific experiments. It gently vibrates"], 33: [images.robot_arm, images.robot_arm_shadow, "A robot arm, used for heavy lifting"], 34: [images.toilet, images.half_shadow, "A sparkling clean toilet"], 35: [images.sink, None, "A sink with running water", "the taps"], 36: [images.globe, images.globe_shadow, "A giant globe of the planet. It gently glows from inside"], 37: [images.science_lab_table, None, "A table of experiments, analyzing the planet soil and dust"], 38: [images.vending_machine, images.full_shadow, "A vending machine. It requires a credit.", "the vending machine"], 39: [images.floor_pad, None, "A pressure sensor to make sure nobody goes out alone."], 40: [images.rescue_ship, images.rescue_ship_shadow, "A rescue ship!"], 41: [images.mission_control_desk, images.mission_control_desk_shadow, \ "Mission Control stations."], 42: [images.button, images.button_shadow, "The button for opening the time-locked door in engineering."], 43: [images.whiteboard, images.full_shadow, "The whiteboard is used in brainstorms and planning meetings."], 44: [images.window, images.full_shadow, "The window provides a view out onto the planet surface."], 45: [images.robot, images.robot_shadow, "A cleaning robot, turned off."], 46: [images.robot2, images.robot2_shadow, "A planet surface exploration robot, awaiting set-up."], 47: [images.rocket, images.rocket_shadow, "A one-person craft in repair"], 48: [images.toxic_floor, None, "Toxic floor - do not walk on!"], 49: [images.drone, None, "A delivery drone"], 50: [images.energy_ball, None, "An energy ball - dangerous!"], 51: [images.energy_ball2, None, "An energy ball - dangerous!"], 52: [images.computer, images.computer_shadow, "A computer workstation, for managing space station systems."], 53: [images.clipboard, None, "A clipboard. Someone has doodled on it.", "the clipboard"], 54: [images.bubble_gum, None, "A piece of sticky bubble gum. Spaceberry flavour.", "bubble gum"], 55: [images.yoyo, None, "A toy made of fine, strong string and plastic. \ Used for antigrav experiments.", PLAYER_NAME + "'s yoyo"], 56: [images.thread, None, "A piece of fine, strong string", "a piece of string"], 57: [images.needle, None, "A sharp needle from a cactus plant", "a cactus needle"], 58: [images.threaded_needle, None, "A cactus needle, spearing a length of string", "needle and string"], 59: [images.canister, None, "The air canister has a leak.", "a leaky air canister"], 60: [images.canister, None, "It looks like the seal will hold!", "a sealed air canister"], 61: [images.mirror, None, "The mirror throws a circle of light on the walls.", "a mirror"], 62: [images.bin_empty, None, "A rarely used bin, made of light plastic", "a bin"], 63: [images.bin_full, None, "A heavy bin full of water", "a bin full of water"], 64: [images.rags, None, "An oily rag. Pick it up by one corner if you must!", "an oily rag"], 65: [images.hammer, None, "A hammer. Maybe good for cracking things open...", "a hammer"], 66: [images.spoon, None, "A large serving spoon", "a spoon"], 67: [images.food_pouch, None, "A dehydrated food pouch. It needs water.", "a dry food pack"], 68: [images.food, None, "A food pouch. Use it to get 100% energy.", "ready-to-eat food"], 69: [images.book, None, "The book has the words 'Don't Panic' on the \ cover in large, friendly letters", "a book"], 70: [images.mp3_player, None, "An MP3 player, with all the latest tunes", "an MP3 player"], 71: [images.lander, None, "The Poodle, a small space exploration craft. \ Its black box has a radio sealed inside.", "the Poodle lander"], 72: [images.radio, None, "A radio communications system, from the \ Poodle", "a communications radio"], 73: [images.gps_module, None, "A GPS Module", "a GPS module"], 74: [images.positioning_system, None, "Part of a positioning system. \ Needs a GPS module.", "a positioning interface"], 75: [images.positioning_system, None, "A working positioning system", "a positioning computer"], 76: [images.scissors, None, "Scissors. They're too blunt to cut \ anything. Can you sharpen them?", "blunt scissors"], 77: [images.scissors, None, "Razor-sharp scissors. Careful!", "sharpened scissors"], 78: [images.credit, None, "A small coin for the station's vending systems", "a station credit"], 79: [images.access_card, None, "This access card belongs to " + PLAYER_NAME, "an access card"], 80: [images.access_card, None, "This access card belongs to " + FRIEND1_NAME, "an access card"], ### EASTER EGG ADDITION (comma) ### 82: [images.egg, None, "This chocolate egg looks delicious!", "an Easter egg"] ### EASTER EGG ADDITION ### } items_player_may_carry = list(range(53, 83)) ### EASTER EGG ADDITION - changed 82 to 83 so that the egg (82) can be carried ### # Numbers below are for floor, pressure pad, soil, toxic floor. items_player_may_stand_on = items_player_may_carry + [0, 39, 2, 48] ############### ## SCENERY ## ############### # Scenery describes objects that cannot move between rooms. # room number: [[object number, y position, x position]...] scenery = { 26: [[39,8,2]], 27: [[33,5,5], [33,1,1], [33,1,8], [47,5,2], [47,3,10], [47,9,8], [42,1,6]], 28: [[27,0,3], [41,4,3], [41,4,7]], 29: [[7,2,6], [6,2,8], [12,1,13], [44,0,1], [36,4,10], [10,1,1], [19,4,2], [17,4,4]], 30: [[34,1,1], [35,1,3]], 31: [[11,1,1], [19,1,8], [46,1,3]], 32: [[48,2,2], [48,2,3], [48,2,4], [48,3,2], [48,3,3], [48,3,4], [48,4,2], [48,4,3], [48,4,4]], 33: [[13,1,1], [13,1,3], [13,1,8], [13,1,10], [48,2,1], [48,2,7], [48,3,6], [48,3,3]], 34: [[37,2,2], [32,6,7], [37,10,4], [28,5,3]], 35: [[16,2,9], [16,2,2], [16,3,3], [16,3,8], [16,8,9], [16,8,2], [16,1,8], [16,1,3], [12,8,6], [12,9,4], [12,9,8], [15,4,6], [12,7,1], [12,7,11]], 36: [[4,3,1], [9,1,7], [8,1,8], [8,1,9], [5,5,4], [6,5,7], [10,1,1], [12,1,2]], 37: [[48,3,1], [48,3,2], [48,7,1], [48,5,2], [48,5,3], [48,7,2], [48,9,2], [48,9,3], [48,11,1], [48,11,2]], 38: [[43,0,2], [6,2,2], [6,3,5], [6,4,7], [6,2,9], [45,1,10]], 39: [[38,1,1], [7,3,4], [7,6,4], [5,3,6], [5,6,6], [6,3,9], [6,6,9], [45,1,11], [12,1,8], [12,1,4]], 40: [[41,5,3], [41,5,7], [41,9,3], [41,9,7], [13,1,1], [13,1,3], [42,1,12]], 41: [[4,3,1], [10,3,5], [4,5,1], [10,5,5], [4,7,1], [10,7,5], [12,1,1], [12,1,5]], 44: [[46,4,3], [46,4,5], [18,1,1], [19,1,3], [19,1,5], [52,4,7], [14,1,8]], 45: [[48,2,1], [48,2,2], [48,3,3], [48,3,4], [48,1,4], [48,1,1]], 46: [[10,1,1], [4,1,2], [8,1,7], [9,1,8], [8,1,9], [5,4,3], [7,3,2]], 47: [[9,1,1], [9,1,2], [10,1,3], [12,1,7], [5,4,4], [6,4,7], [4,1,8]], 48: [[17,4,1], [17,4,2], [17,4,3], [17,4,4], [17,4,5], [17,4,6], [17,4,7], [17,8,1], [17,8,2], [17,8,3], [17,8,4], [17,8,5], [17,8,6], [17,8,7], [14,1,1]], 49: [[14,2,2], [14,2,4], [7,5,1], [5,5,3], [48,3,3], [48,3,4]], 50: [[45,4,8], [11,1,1], [13,1,8], [33,2,1], [46,4,6]] } checksum = 0 check_counter = 0 for key, room_scenery_list in scenery.items(): for scenery_item_list in room_scenery_list: checksum += (scenery_item_list[0] * key + scenery_item_list[1] * (key + 1) + scenery_item_list[2] * (key + 2)) check_counter += 1 print(check_counter, "scenery items") assert check_counter == 161, "Expected 161 scenery items" assert checksum == 200095, "Error in scenery data" print("Scenery checksum: " + str(checksum)) for room in range(1, 26):# Add random scenery in planet locations. if room != 13: # Skip room 13. scenery_item = random.choice([16, 28, 29, 30]) scenery[room] = [[scenery_item, random.randint(2, 10), random.randint(2, 10)]] # Use loops to add fences to the planet surface rooms. for room_coordinate in range(0, 13): for room_number in [1, 2, 3, 4, 5]: # Add top fence scenery[room_number] += [[31, 0, room_coordinate]] for room_number in [1, 6, 11, 16, 21]: # Add left fence scenery[room_number] += [[31, room_coordinate, 0]] for room_number in [5, 10, 15, 20, 25]: # Add right fence scenery[room_number] += [[31, room_coordinate, 12]] del scenery[21][-1] # Delete last fence panel in Room 21 del scenery[25][-1] # Delete last fence panel in Room 25 ############### ## MAKE MAP ## ############### def get_floor_type(): if current_room in outdoor_rooms: return 2 # soil else: return 0 # tiled floor def generate_map(): # This function makes the map for the current room, # using room data, scenery data and prop data. global room_map, room_width, room_height, room_name, hazard_map global top_left_x, top_left_y, wall_transparency_frame room_data = GAME_MAP[current_room] room_name = room_data[0] room_height = room_data[1] room_width = room_data[2] floor_type = get_floor_type() if current_room in range(1, 21): bottom_edge = 2 #soil side_edge = 2 #soil if current_room in range(21, 26): bottom_edge = 1 #wall side_edge = 2 #soil if current_room > 25: bottom_edge = 1 #wall side_edge = 1 #wall # Create top line of room map. room_map=[[side_edge] * room_width] # Add middle lines of room map (wall, floor to fill width, wall). for y in range(room_height - 2): room_map.append([side_edge] + [floor_type]*(room_width - 2) + [side_edge]) # Add bottom line of room map. room_map.append([bottom_edge] * room_width) # Add doorways. middle_row = int(room_height / 2) middle_column = int(room_width / 2) if room_data[4]: # If exit at right of this room room_map[middle_row][room_width - 1] = floor_type room_map[middle_row+1][room_width - 1] = floor_type room_map[middle_row-1][room_width - 1] = floor_type if current_room % MAP_WIDTH != 1: # If room is not on left of map room_to_left = GAME_MAP[current_room - 1] # If room on the left has a right exit, add left exit in this room if room_to_left[4]: room_map[middle_row][0] = floor_type room_map[middle_row + 1][0] = floor_type room_map[middle_row - 1][0] = floor_type if room_data[3]: # If exit at top of this room room_map[0][middle_column] = floor_type room_map[0][middle_column + 1] = floor_type room_map[0][middle_column - 1] = floor_type if current_room <= MAP_SIZE - MAP_WIDTH: # If room is not on bottom row room_below = GAME_MAP[current_room+MAP_WIDTH] # If room below has a top exit, add exit at bottom of this one if room_below[3]: room_map[room_height-1][middle_column] = floor_type room_map[room_height-1][middle_column + 1] = floor_type room_map[room_height-1][middle_column - 1] = floor_type if current_room in scenery: for this_scenery in scenery[current_room]: scenery_number = this_scenery[0] scenery_y = this_scenery[1] scenery_x = this_scenery[2] room_map[scenery_y][scenery_x] = scenery_number image_here = objects[scenery_number][0] image_width = image_here.get_width() image_width_in_tiles = int(image_width / TILE_SIZE) for tile_number in range(1, image_width_in_tiles): room_map[scenery_y][scenery_x + tile_number] = 255 center_y = int(HEIGHT / 2) # Center of game window center_x = int(WIDTH / 2) room_pixel_width = room_width * TILE_SIZE # Size of room in pixels room_pixel_height = room_height * TILE_SIZE top_left_x = center_x - 0.5 * room_pixel_width top_left_y = (center_y - 0.5 * room_pixel_height) + 110 for prop_number, prop_info in props.items(): prop_room = prop_info[0] prop_y = prop_info[1] prop_x = prop_info[2] if (prop_room == current_room and room_map[prop_y][prop_x] in [0, 39, 2]): room_map[prop_y][prop_x] = prop_number image_here = objects[prop_number][0] image_width = image_here.get_width() image_width_in_tiles = int(image_width / TILE_SIZE) for tile_number in range(1, image_width_in_tiles): room_map[prop_y][prop_x + tile_number] = 255 hazard_map = [] # empty list for y in range(room_height): hazard_map.append( [0] * room_width ) ############### ## GAME LOOP ## ############### def start_room(): global airlock_door_frame show_text("You are here: " + room_name, 0) if current_room == 26: # Room with self-shutting airlock door airlock_door_frame = 0 clock.schedule_interval(door_in_room_26, 0.05) hazard_start() def game_loop(): global player_x, player_y, current_room global from_player_x, from_player_y global player_image, player_image_shadow global selected_item, item_carrying, energy global player_offset_x, player_offset_y global player_frame, player_direction if game_over: return if player_frame > 0: player_frame += 1 time.sleep(0.05) if player_frame == 5: player_frame = 0 player_offset_x = 0 player_offset_y = 0 # save player's current position old_player_x = player_x old_player_y = player_y # move if key is pressed if player_frame == 0: if keyboard.right: from_player_x = player_x from_player_y = player_y player_x += 1 player_direction = "right" player_frame = 1 elif keyboard.left: #elif stops player making diagonal movements from_player_x = player_x from_player_y = player_y player_x -= 1 player_direction = "left" player_frame = 1 elif keyboard.up: from_player_x = player_x from_player_y = player_y player_y -= 1 player_direction = "up" player_frame = 1 elif keyboard.down: from_player_x = player_x from_player_y = player_y player_y += 1 player_direction = "down" player_frame = 1 # check for exiting the room if player_x == room_width: # through door on RIGHT clock.unschedule(hazard_move) current_room += 1 generate_map() player_x = 0 # enter at left player_y = int(room_height / 2) # enter at door player_frame = 0 start_room() return if player_x == -1: # through door on LEFT clock.unschedule(hazard_move) current_room -= 1 generate_map() player_x = room_width - 1 # enter at right player_y = int(room_height / 2) # enter at door player_frame = 0 start_room() return if player_y == room_height: # through door at BOTTOM clock.unschedule(hazard_move) current_room += MAP_WIDTH generate_map() player_y = 0 # enter at top player_x = int(room_width / 2) # enter at door player_frame = 0 start_room() return if player_y == -1: # through door at TOP clock.unschedule(hazard_move) current_room -= MAP_WIDTH generate_map() player_y = room_height - 1 # enter at bottom player_x = int(room_width / 2) # enter at door player_frame = 0 start_room() return if keyboard.g: pick_up_object() if keyboard.tab and len(in_my_pockets) > 0: selected_item += 1 if selected_item > len(in_my_pockets) - 1: selected_item = 0 item_carrying = in_my_pockets[selected_item] display_inventory() if keyboard.d and item_carrying: drop_object(old_player_y, old_player_x) if keyboard.space: examine_object() if keyboard.u: use_object() #### Teleporter for testing #### Remove this section for the real game ## if keyboard.x: ## current_room = int(input("Enter room number:")) ## player_x = 2 ## player_y = 2 ## generate_map() ## start_room() ## sounds.teleport.play() #### Teleport section ends # If the player is standing somewhere they shouldn't, move them back. if room_map[player_y][player_x] not in items_player_may_stand_on \ or hazard_map[player_y][player_x] != 0: player_x = old_player_x player_y = old_player_y player_frame = 0 if room_map[player_y][player_x] == 48: # toxic floor deplete_energy(1) if player_direction == "right" and player_frame > 0: player_offset_x = -1 + (0.25 * player_frame) if player_direction == "left" and player_frame > 0: player_offset_x = 1 - (0.25 * player_frame) if player_direction == "up" and player_frame > 0: player_offset_y = 1 - (0.25 * player_frame) if player_direction == "down" and player_frame > 0: player_offset_y = -1 + (0.25 * player_frame) ############### ## DISPLAY ## ############### def draw_image(image, y, x): screen.blit( image, (top_left_x + (x * TILE_SIZE), top_left_y + (y * TILE_SIZE) - image.get_height()) ) def draw_shadow(image, y, x): screen.blit( image, (top_left_x + (x * TILE_SIZE), top_left_y + (y * TILE_SIZE)) ) def draw_player(): player_image = PLAYER[player_direction][player_frame] draw_image(player_image, player_y + player_offset_y, player_x + player_offset_x) player_image_shadow = PLAYER_SHADOW[player_direction][player_frame] draw_shadow(player_image_shadow, player_y + player_offset_y, player_x + player_offset_x) def draw(): if game_over: return # Clear the game arena area. box = Rect((0, 150), (800, 600)) screen.draw.filled_rect(box, RED) box = Rect ((0, 0), (800, top_left_y + (room_height - 1)*30)) screen.surface.set_clip(box) floor_type = get_floor_type() for y in range(room_height): # Lay down floor tiles, then items on floor. for x in range(room_width): draw_image(objects[floor_type][0], y, x) # Next line enables shadows to fall on top of objects on floor if room_map[y][x] in items_player_may_stand_on: draw_image(objects[room_map[y][x]][0], y, x) # Pressure pad in room 26 is added here, so props can go on top of it. if current_room == 26: draw_image(objects[39][0], 8, 2) image_on_pad = room_map[8][2] if image_on_pad > 0: draw_image(objects[image_on_pad][0], 8, 2) for y in range(room_height): for x in range(room_width): item_here = room_map[y][x] # Player cannot walk on 255: it marks spaces used by wide objects. if item_here not in items_player_may_stand_on + [255]: image = objects[item_here][0] if (current_room in outdoor_rooms and y == room_height - 1 and room_map[y][x] == 1) or \ (current_room not in outdoor_rooms and y == room_height - 1 and room_map[y][x] == 1 and x > 0 and x < room_width - 1): # Add transparent wall image in the front row. image = PILLARS[wall_transparency_frame] draw_image(image, y, x) if objects[item_here][1] is not None: # If object has a shadow shadow_image = objects[item_here][1] # if shadow might need horizontal tiling if shadow_image in [images.half_shadow, images.full_shadow]: shadow_width = int(image.get_width() / TILE_SIZE) # Use shadow across width of object. for z in range(0, shadow_width): draw_shadow(shadow_image, y, x+z) else: draw_shadow(shadow_image, y, x) hazard_here = hazard_map[y][x] if hazard_here != 0: # If there's a hazard at this position draw_image(objects[hazard_here][0], y, x) if (player_y == y): draw_player() screen.surface.set_clip(None) def adjust_wall_transparency(): global wall_transparency_frame if (player_y == room_height - 2 and room_map[room_height - 1][player_x] == 1 and wall_transparency_frame < 4): wall_transparency_frame += 1 # Fade wall out. if ((player_y < room_height - 2 or room_map[room_height - 1][player_x] != 1) and wall_transparency_frame > 0): wall_transparency_frame -= 1 # Fade wall in. def show_text(text_to_show, line_number): if game_over: return text_lines = [15, 50] box = Rect((0, text_lines[line_number]), (800, 35)) screen.draw.filled_rect(box, BLACK) screen.draw.text(text_to_show, (20, text_lines[line_number]), color=GREEN) ############### ## PROPS ## ############### # Props are objects that may move between rooms, appear or disappear. # All props must be set up here. Props not yet in the game go into room 0. # object number : [room, y, x] props = { 20: [31, 0, 4], 21: [26, 0, 1], 22: [41, 0, 2], 23: [39, 0, 5], 24: [45, 0, 2], 25: [32, 0, 2], 26: [27, 12, 5], # two sides of same door 40: [0, 8, 6], 53: [45, 1, 5], 54: [0, 0, 0], 55: [0, 0, 0], 56: [0, 0, 0], 57: [35, 4, 6], 58: [0, 0, 0], 59: [31, 1, 7], 60: [0, 0, 0], 61: [36, 1, 1], 62: [36, 1, 6], 63: [0, 0, 0], 64: [27, 8, 3], 65: [50, 1, 7], 66: [39, 5, 6], 67: [46, 1, 1], 68: [0, 0, 0], 69: [30, 3, 3], 70: [47, 1, 3], 71: [0, LANDER_Y, LANDER_X], 72: [0, 0, 0], 73: [27, 4, 6], 74: [28, 1, 11], 75: [0, 0, 0], 76: [41, 3, 5], 77: [0, 0, 0], 78: [35, 9, 11], 79: [26, 3, 2], 80: [41, 7, 5], 81: [29, 1, 1], ### EASTER EGG ADDITION (comma) ### 82: [31, 3, 3] ### EASTER EGG ADDITION ### } ### commented out following code to add EASTER EGG ### ##checksum = 0 ##for key, prop in props.items(): ## if key != 71: # 71 is skipped because it's different each game. ## checksum += (prop[0] * key ## + prop[1] * (key + 1) ## + prop[2] * (key + 2)) ##print(len(props), "props") ##assert len(props) == 37, "Expected 37 prop items" ##print("Prop checksum:", checksum) ##assert checksum == 61414, "Error in props data" in_my_pockets = [55] selected_item = 0 # the first item item_carrying = in_my_pockets[selected_item] RECIPES = [ [62, 35, 63], [76, 28, 77], [78, 38, 54], [73, 74, 75], [59, 54, 60], [77, 55, 56], [56, 57, 58], [71, 65, 72], [88, 58, 89], [89, 60, 90], [67, 35, 68] ] checksum = 0 check_counter = 1 for recipe in RECIPES: checksum += (recipe[0] * check_counter + recipe[1] * (check_counter + 1) + recipe[2] * (check_counter + 2)) check_counter += 3 print(len(RECIPES), "recipes") assert len(RECIPES) == 11, "Expected 11 recipes" assert checksum == 37296, "Error in recipes data" print("Recipe checksum:", checksum) ####################### ## PROP INTERACTIONS ## ####################### def find_object_start_x(): checker_x = player_x while room_map[player_y][checker_x] == 255: checker_x -= 1 return checker_x def get_item_under_player(): item_x = find_object_start_x() item_player_is_on = room_map[player_y][item_x] return item_player_is_on def pick_up_object(): global room_map # Get object number at player's location. item_player_is_on = get_item_under_player() if item_player_is_on in items_player_may_carry: # Clear the floor space. room_map[player_y][player_x] = get_floor_type() add_object(item_player_is_on) show_text("Now carrying " + objects[item_player_is_on][3], 0) sounds.pickup.play() time.sleep(0.5) else: show_text("You can't carry that!", 0) def add_object(item): # Adds item to inventory. global selected_item, item_carrying in_my_pockets.append(item) item_carrying = item # Minus one because indexes start at 0. selected_item = len(in_my_pockets) - 1 display_inventory() props[item][0] = 0 # Carried objects go into room 0 (off the map). def display_inventory(): box = Rect((0, 45), (800, 105)) screen.draw.filled_rect(box, BLACK) if len(in_my_pockets) == 0: return start_display = (selected_item // 16) * 16 list_to_show = in_my_pockets[start_display : start_display + 16] selected_marker = selected_item % 16 for item_counter in range(len(list_to_show)): item_number = list_to_show[item_counter] image = objects[item_number][0] screen.blit(image, (25 + (46 * item_counter), 90)) box_left = (selected_marker * 46) - 3 box = Rect((22 + box_left, 85), (40, 40)) screen.draw.rect(box, WHITE) item_highlighted = in_my_pockets[selected_item] description = objects[item_highlighted][2] screen.draw.text(description, (20, 130), color="white") def drop_object(old_y, old_x): global room_map, props if room_map[old_y][old_x] in [0, 2, 39]: # places you can drop things props[item_carrying][0] = current_room props[item_carrying][1] = old_y props[item_carrying][2] = old_x room_map[old_y][old_x] = item_carrying show_text("You have dropped " + objects[item_carrying][3], 0) sounds.drop.play() remove_object(item_carrying) time.sleep(0.5) else: # This only happens if there is already a prop here show_text("You can't drop that there.", 0) time.sleep(0.5) def remove_object(item): # Takes item out of inventory global selected_item, in_my_pockets, item_carrying in_my_pockets.remove(item) selected_item = selected_item - 1 if selected_item < 0: selected_item = 0 if len(in_my_pockets) == 0: # If they're not carrying anything item_carrying = False # Set item_carrying to False else: # Otherwise set it to the new selected item item_carrying = in_my_pockets[selected_item] display_inventory() def examine_object(): item_player_is_on = get_item_under_player() left_tile_of_item = find_object_start_x() if item_player_is_on in [0, 2]: # don't describe the floor return description = "You see: " + objects[item_player_is_on][2] for prop_number, details in props.items(): # props = object number: [room number, y, x] if details[0] == current_room: # if prop is in the room # If prop is hidden (= at player's location but not on map) if (details[1] == player_y and details[2] == left_tile_of_item and room_map[details[1]][details[2]] != prop_number): add_object(prop_number) description = "You found " + objects[prop_number][3] sounds.combine.play() show_text(description, 0) time.sleep(0.5) ################# ## USE OBJECTS ## ################# def use_object(): global room_map, props, item_carrying, air, selected_item, energy global in_my_pockets, suit_stitched, air_fixed, game_over use_message = "You fiddle around with it but don't get anywhere." standard_responses = { 4: "Air is running out! You can't take this lying down!", 6: "This is no time to sit around!", 7: "This is no time to sit around!", 32: "It shakes and rumbles, but nothing else happens.", 34: "Ah! That's better. Now wash your hands.", 35: "You wash your hands and shake the water off.", 37: "The test tubes smoke slightly as you shake them.", 54: "You chew the gum. It's sticky like glue.", 55: "The yoyo bounces up and down, slightly slower than on Earth", 56: "It's a bit too fiddly. Can you thread it on something?", 59: "You need to fix the leak before you can use the canister", 61: "You try signalling with the mirror, but nobody can see you.", 62: "Don't throw resources away. Things might come in handy...", 67: "To enjoy yummy space food, just add water!", 75: "You are at Sector: " + str(current_room) + " // X: " \ + str(player_x) + " // Y: " + str(player_y), ### EASTER EGG ADDITION (comma) ### 82: "You nibble at the chocolate. It tastes divine!" ### EASTER EGG ADDITION ### } # Get object number at player's location. item_player_is_on = get_item_under_player() for this_item in [item_player_is_on, item_carrying]: if this_item in standard_responses: use_message = standard_responses[this_item] if item_carrying == 70 or item_player_is_on == 70: use_message = "Banging tunes!" sounds.steelmusic.play(2) elif item_player_is_on == 11: use_message = "AIR: " + str(air) + \ "% / ENERGY " + str(energy) + "% / " if not suit_stitched: use_message += "*ALERT* SUIT FABRIC TORN / " if not air_fixed: use_message += "*ALERT* SUIT AIR BOTTLE MISSING" if suit_stitched and air_fixed: use_message += " SUIT OK" show_text(use_message, 0) sounds.say_status_report.play() time.sleep(0.5) # If "on" the computer, player intention is clearly status update. # Return to stop another object use accidentally overriding this. return elif item_carrying == 60 or item_player_is_on == 60: use_message = "You fix " + objects[60][3] + " to the suit" air_fixed = True air = 90 air_countdown() remove_object(60) elif (item_carrying == 58 or item_player_is_on == 58) \ and not suit_stitched: use_message = "You use " + objects[56][3] + \ " to repair the suit fabric" suit_stitched = True remove_object(58) elif item_carrying == 72 or item_player_is_on == 72: use_message = "You radio for help. A rescue ship is coming. \ Rendezvous Sector 13, outside." props[40][0] = 13 elif (item_carrying == 66 or item_player_is_on == 66) \ and current_room in outdoor_rooms: use_message = "You dig..." if (current_room == LANDER_SECTOR and player_x == LANDER_X and player_y == LANDER_Y): add_object(71) use_message = "You found the Poodle lander!" elif item_player_is_on == 40: clock.unschedule(air_countdown) show_text("Congratulations, "+ PLAYER_NAME +"!", 0) show_text("Mission success! You have made it to safety.", 1) game_over = True sounds.take_off.play() game_completion_sequence() elif item_player_is_on == 16: energy += 1 if energy > 100: energy = 100 use_message = "You munch the lettuce and get a little energy back" draw_energy_air() elif item_player_is_on == 42: if current_room == 27: open_door(26) props[25][0] = 0 # Door from RM32 to engineering bay props[26][0] = 0 # Door inside engineering bay clock.schedule_unique(shut_engineering_door, 60) use_message = "You press the button" show_text("Door to engineering bay is open for 60 seconds", 1) sounds.say_doors_open.play() sounds.doors.play() elif item_carrying == 68 or item_player_is_on == 68: energy = 100 use_message = "You use the food to restore your energy" remove_object(68) draw_energy_air() if suit_stitched and air_fixed: # open airlock access if current_room == 31 and props[20][0] == 31: open_door(20) # which includes removing the door sounds.say_airlock_open.play() show_text("The computer tells you the airlock is now open.", 1) elif props[20][0] == 31: props[20][0] = 0 # remove door from map sounds.say_airlock_open.play() show_text("The computer tells you the airlock is now open.", 1) for recipe in RECIPES: ingredient1 = recipe[0] ingredient2 = recipe[1] combination = recipe[2] if (item_carrying == ingredient1 and item_player_is_on == ingredient2) \ or (item_carrying == ingredient2 and item_player_is_on == ingredient1): use_message = "You combine " + objects[ingredient1][3] \ + " and " + objects[ingredient2][3] \ + " to make " + objects[combination][3] if item_player_is_on in props.keys(): props[item_player_is_on][0] = 0 room_map[player_y][player_x] = get_floor_type() in_my_pockets.remove(item_carrying) add_object(combination) sounds.combine.play() # {key object number: door object number} ACCESS_DICTIONARY = { 79:22, 80:23, 81:24 } if item_carrying in ACCESS_DICTIONARY: door_number = ACCESS_DICTIONARY[item_carrying] if props[door_number][0] == current_room: use_message = "You unlock the door!" sounds.say_doors_open.play() sounds.doors.play() open_door(door_number) show_text(use_message, 0) time.sleep(0.5) def game_completion_sequence(): global launch_frame #(initial value is 0, set up in VARIABLES section) box = Rect((0, 150), (800, 600)) screen.draw.filled_rect(box, (128, 0, 0)) box = Rect ((0, top_left_y - 30), (800, 390)) screen.surface.set_clip(box) for y in range(0, 13): for x in range(0, 13): draw_image(images.soil, y, x) launch_frame += 1 if launch_frame < 9: draw_image(images.rescue_ship, 8 - launch_frame, 6) draw_shadow(images.rescue_ship_shadow, 8 + launch_frame, 6) clock.schedule(game_completion_sequence, 0.25) else: screen.surface.set_clip(None) screen.draw.text("MISSION", (200, 380), color = "white", fontsize = 128, shadow = (1, 1), scolor = "black") screen.draw.text("COMPLETE", (145, 480), color = "white", fontsize = 128, shadow = (1, 1), scolor = "black") sounds.completion.play() sounds.say_mission_complete.play() ############### ## DOORS ## ############### def open_door(opening_door_number): global door_frames, door_shadow_frames global door_frame_number, door_object_number door_frames = [images.door1, images.door2, images.door3, images.door4, images.floor] # (Final frame restores shadow ready for when door reappears). door_shadow_frames = [images.door1_shadow, images.door2_shadow, images.door3_shadow, images.door4_shadow, images.door_shadow] door_frame_number = 0 door_object_number = opening_door_number do_door_animation() def close_door(closing_door_number): global door_frames, door_shadow_frames global door_frame_number, door_object_number, player_y door_frames = [images.door4, images.door3, images.door2, images.door1, images.door] door_shadow_frames = [images.door4_shadow, images.door3_shadow, images.door2_shadow, images.door1_shadow, images.door_shadow] door_frame_number = 0 door_object_number = closing_door_number # If player is in same row as a door, they must be in open doorway if player_y == props[door_object_number][1]: if player_y == 0: # if in the top doorway player_y = 1 # move them down else: player_y = room_height - 2 # move them up do_door_animation() def do_door_animation(): global door_frames, door_frame_number, door_object_number, objects objects[door_object_number][0] = door_frames[door_frame_number] objects[door_object_number][1] = door_shadow_frames[door_frame_number] door_frame_number += 1 if door_frame_number == 5: if door_frames[-1] == images.floor: props[door_object_number][0] = 0 # remove door from props list # Regenerate room map from the props # to put the door in the room if required. generate_map() else: clock.schedule(do_door_animation, 0.15) def shut_engineering_door(): global current_room, door_room_number, props props[25][0] = 32 # Door from room 32 to the engineering bay. props[26][0] = 27 # Door inside engineering bay. generate_map() # Add door to room_map for if in affected room. if current_room == 27: close_door(26) if current_room == 32: close_door(25) show_text("The computer tells you the doors are closed.", 1) sounds.say_doors_closed.play() def door_in_room_26(): global airlock_door_frame, room_map frames = [images.door, images.door1, images.door2, images.door3,images.door4, images.floor ] shadow_frames = [images.door_shadow, images.door1_shadow, images.door2_shadow, images.door3_shadow, images.door4_shadow, None] if current_room != 26: clock.unschedule(door_in_room_26) return # prop 21 is the door in Room 26. if ((player_y == 8 and player_x == 2) or props[63] == [26, 8, 2]) \ and props[21][0] == 26: airlock_door_frame += 1 if airlock_door_frame == 5: props[21][0] = 0 # Remove door from map when fully open. room_map[0][1] = 0 room_map[0][2] = 0 room_map[0][3] = 0 if ((player_y != 8 or player_x != 2) and props[63] != [26, 8, 2]) \ and airlock_door_frame > 0: if airlock_door_frame == 5: # Add door to props and map so animation is shown. props[21][0] = 26 room_map[0][1] = 21 room_map[0][2] = 255 room_map[0][3] = 255 airlock_door_frame -= 1 objects[21][0] = frames[airlock_door_frame] objects[21][1] = shadow_frames[airlock_door_frame] ############### ## AIR ## ############### def draw_energy_air(): box = Rect((20, 765), (350, 20)) screen.draw.filled_rect(box, BLACK) screen.draw.text("AIR", (20, 766), color=BLUE) screen.draw.text("ENERGY", (180, 766), color=YELLOW) if air > 0: box = Rect((50, 765), (air, 20)) screen.draw.filled_rect(box, BLUE) # Draw new air bar. if energy > 0: box = Rect((250, 765), (energy, 20)) screen.draw.filled_rect(box, YELLOW) # Draw new energy bar. def end_the_game(reason): global game_over show_text(reason, 1) game_over = True sounds.say_mission_fail.play() sounds.gameover.play() screen.draw.text("GAME OVER", (120, 400), color = "white", fontsize = 128, shadow = (1, 1), scolor = "black") def air_countdown(): global air, game_over if game_over: return # Don't sap air when they're already dead. air -= 1 if air == 20: sounds.say_air_low.play() if air == 10: sounds.say_act_now.play() draw_energy_air() if air < 1: end_the_game("You're out of air!") def alarm(): show_text("Air is running out, " + PLAYER_NAME + "! Get to safety, then radio for help!", 1) sounds.alarm.play(3) sounds.say_breach.play() ############### ## HAZARDS ## ############### hazard_data = { # room number: [[y, x, direction, bounce addition to direction]] 28: [[1, 8, 2, 1], [7, 3, 4, 1]], 32: [[1, 5, 4, -1]], 34: [[5, 1, 1, 1], [5, 5, 1, 2]], 35: [[4, 4, 1, 2], [2, 5, 2, 2]], 36: [[2, 1, 2, 2]], 38: [[1, 4, 3, 2], [5, 8, 1, 2]], 40: [[3, 1, 3, -1], [6, 5, 2, 2], [7, 5, 4, 2]], 41: [[4, 5, 2, 2], [6, 3, 4, 2], [8, 1, 2, 2]], 42: [[2, 1, 2, 2], [4, 3, 2, 2], [6, 5, 2, 2]], 46: [[2, 1, 2, 2]], 48: [[1, 8, 3, 2], [8, 8, 1, 2], [3, 9, 3, 2]] } def deplete_energy(penalty): global energy, game_over if game_over: return # Don't sap energy when they're already dead. energy = energy - penalty draw_energy_air() if energy < 1: end_the_game("You're out of energy!") def hazard_start(): global current_room_hazards_list, hazard_map if current_room in hazard_data.keys(): current_room_hazards_list = hazard_data[current_room] for hazard in current_room_hazards_list: hazard_y = hazard[0] hazard_x = hazard[1] hazard_map[hazard_y][hazard_x] = 49 + (current_room % 3) clock.schedule_interval(hazard_move, 0.15) def hazard_move(): global current_room_hazards_list, hazard_data, hazard_map global old_player_x, old_player_y if game_over: return for hazard in current_room_hazards_list: hazard_y = hazard[0] hazard_x = hazard[1] hazard_direction = hazard[2] old_hazard_x = hazard_x old_hazard_y = hazard_y hazard_map[old_hazard_y][old_hazard_x] = 0 if hazard_direction == 1: # up hazard_y -= 1 if hazard_direction == 2: # right hazard_x += 1 if hazard_direction == 3: # down hazard_y += 1 if hazard_direction == 4: # left hazard_x -= 1 hazard_should_bounce = False if (hazard_y == player_y and hazard_x == player_x) or \ (hazard_y == from_player_y and hazard_x == from_player_x and player_frame > 0): sounds.ouch.play() deplete_energy(10) hazard_should_bounce = True # Stop hazard going out of the doors if hazard_x == room_width: hazard_should_bounce = True hazard_x = room_width - 1 if hazard_x == -1: hazard_should_bounce = True hazard_x = 0 if hazard_y == room_height: hazard_should_bounce = True hazard_y = room_height - 1 if hazard_y == -1: hazard_should_bounce = True hazard_y = 0 # Stop when hazard hits scenery or another hazard. if room_map[hazard_y][hazard_x] not in items_player_may_stand_on \ or hazard_map[hazard_y][hazard_x] != 0: hazard_should_bounce = True if hazard_should_bounce: hazard_y = old_hazard_y # Move back to last valid position. hazard_x = old_hazard_x hazard_direction += hazard[3] if hazard_direction > 4: hazard_direction -= 4 if hazard_direction < 1: hazard_direction += 4 hazard[2] = hazard_direction hazard_map[hazard_y][hazard_x] = 49 + (current_room % 3) hazard[0] = hazard_y hazard[1] = hazard_x ######################## ## HIDING OBJECTS ## ######################## def hide_object(room_number, object_to_hide): # this function puts an object into a place where there is already a piece of scenery # it can be used for hiding things in a random location in a particular room # for example an Easter egg in the crew room global props number_of_items_in_room = len(scenery[room_number]) item_to_hide_in = random.randint(0, number_of_items_in_room - 1) print(item_to_hide_in) props[object_to_hide][0] = room_number props[object_to_hide][1] = scenery[room_number][item_to_hide_in][1] props[object_to_hide][2] = scenery[room_number][item_to_hide_in][2] hide_object(39, 82) # hide object 82 (egg) in room 39 (crew community room) ############### ## START ## ############### clock.schedule_interval(game_loop, 0.03) generate_map() clock.schedule_interval(adjust_wall_transparency, 0.05) clock.schedule_unique(display_inventory, 1) clock.schedule_unique(draw_energy_air, 0.5) clock.schedule_unique(alarm, 10) # A higher number below gives a longer time limit. clock.schedule_interval(air_countdown, 5) sounds.mission.play() # Intro music