eigenbom

WoodchopperRL

Oct 8th, 2016
627
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.43 KB | None | 0 0
  1. # WoodchoppererRL
  2. # A tiny little wood-chopping game
  3. # @eigenbom 2016
  4.  
  5. from collections import deque
  6. import math
  7. import os
  8. import sys
  9. import threading
  10. import time
  11.  
  12. class Blocks:
  13.     """Block types."""
  14.     air = 1
  15.     dirt = 2
  16.     wood = 3
  17.  
  18. def char_to_block(ch):
  19.     return {
  20.         ".": Blocks.air,
  21.         "d": Blocks.dirt,
  22.         "w": Blocks.wood,
  23.     }.get(ch, None)
  24.  
  25. def block_to_char(bl):
  26.     return {
  27.         Blocks.air: ".",
  28.         Blocks.dirt: "#",
  29.         Blocks.wood: "+"
  30.     }.get(bl, "?")
  31.  
  32. def string_to_world(string, converter):
  33.     world = []
  34.     landmarks = {}
  35.     row = 0
  36.     for line in string.split("\n")[1:-1]:
  37.         world_row = []
  38.         for col, char in enumerate(line):
  39.             bl = converter(char)
  40.             if bl is None:
  41.                 l = landmarks.get(char, list())
  42.                 l.append((col, row))
  43.                 landmarks[char] = l
  44.                 world_row.append(Blocks.air)
  45.             else:
  46.                 world_row.append(bl)
  47.         world.append(world_row)
  48.         row += 1
  49.     return world, landmarks
  50.  
  51. world_string = """
  52. ddddddddddddddddddddd
  53. ddd,,dd.,ddwwdddddddd
  54. ddd..dd..wwwwwwwddddd
  55. ddd..dd.,.........ddd
  56. ddd..dd..dddwwd..dddd
  57. ddd..dd..d.,..w...ddd
  58. ddd..dd.,...v,w...ddd
  59. ddd........,,,d...ddd
  60. ddd..dddddddddd..dddd
  61. ddd..dd.....,..,.dddd
  62. ddddddddddddddddddddd
  63. """
  64.  
  65. world, landmarks = string_to_world(world_string, char_to_block)
  66. print(landmarks)
  67. world_dim = len(world[0]), len(world)
  68. events = deque()
  69. entities = list()
  70. message_list = list()
  71.  
  72. class UIState:
  73.     normal = 0
  74.     target = 1
  75. ui_state = UIState.normal
  76. cursor = (0, 0)
  77.  
  78. class Entity(object):
  79.     """An entity is a player, a mob, an item, a fireball, etc."""
  80.     def __init__(self):
  81.         # Location
  82.         self.position = None
  83.  
  84.         # Contents, carrying, etc
  85.         self.inventory = None
  86.         self.in_inventory = None
  87.  
  88.         # Bigness, Material, etc
  89.         self.is_solid = False # Only one solid entity is allowed per tile
  90.  
  91.         # Appearance
  92.         self.appearance = "?"
  93.         self.description = ""
  94.  
  95.  
  96.  
  97. player = Entity()
  98. player.position = landmarks.get("@")[0]
  99. player.appearance = "@"
  100. player.is_solid = True
  101. player.inventory = list()
  102. player.description = "player"
  103. entities.append(player)
  104.  
  105. axe = Entity()
  106. axe.position = None
  107. axe.appearance = "/"
  108. axe.is_solid = False
  109. axe.description = "axe"
  110. entities.append(axe)
  111.  
  112. player.inventory.append(axe)
  113. axe.in_inventory = player
  114.  
  115. for p in landmarks.get(",", list()):
  116.     plant = Entity()
  117.     plant.position = p
  118.     plant.appearance = ","
  119.     plant.is_solid = False
  120.     plant.description = "plant"
  121.     entities.append(plant)
  122.  
  123. for p in landmarks.get("v", list()):
  124.     plant = Entity()
  125.     plant.position = p
  126.     plant.appearance = "v"
  127.     plant.is_solid = False
  128.     plant.description = "weed"
  129.     entities.append(plant)
  130.  
  131. def world_to_string(tick):
  132.     rows = []
  133.     for y in range(world_dim[1]):
  134.         row = []
  135.         for x in range(world_dim[0]):
  136.             if ui_state==UIState.target and tick%2 and cursor==(x,y):
  137.                 row.append('X')
  138.             else:
  139.                 for e in entities:
  140.                     if e.position == (x, y):
  141.                         row.append(e.appearance)
  142.                         break
  143.                 else:
  144.                     row.append(block_to_char(world[y][x]))
  145.         rows.append("".join(row))
  146.     return "\n".join(rows)
  147.  
  148. # Math helpers
  149. def clamp(x, a, b):
  150.     if x < a:
  151.         return a
  152.     elif x > b:
  153.         return b
  154.     else:
  155.         return x
  156.  
  157. update_lock = threading.Lock()
  158.  
  159. def read_input():
  160.     """Returns a single key press (windows only)"""
  161.     with update_lock:
  162.         import msvcrt
  163.         if msvcrt.kbhit():
  164.             return msvcrt.getch()
  165.         else:
  166.             return None
  167.  
  168. def message(msg):
  169.     message_list.append(msg)
  170.  
  171. shutdown_renderer = False
  172.  
  173. _print = print
  174. def print(str = ""):
  175.     for str in str.split("\n"):
  176.         _print("    " + str)
  177.  
  178. def render():
  179.     global world, player, entities, events, message_list, update_lock, shutdown_renderer
  180.     # Render map and messages
  181.  
  182.     ticker = 0
  183.     while True:
  184.         with update_lock:
  185.             if shutdown_renderer:
  186.                 return
  187.             os.system('cls')
  188.             title = "----WoodchopperRL----"
  189.             cut = ticker % len(title)
  190.             print()
  191.             print(title[cut:len(title)] + title[0:cut])
  192.             print()
  193.             print(world_to_string(ticker))
  194.             print()
  195.             print("Carrying:")
  196.             for i, item in enumerate(player.inventory):
  197.                 print("+ %s"%item.description)
  198.             print()
  199.             if len(message_list)>0:
  200.                 print("Messages:")
  201.                 for m in message_list:
  202.                     print(m)
  203.                 print()
  204.  
  205.             if ui_state==UIState.normal:
  206.                 print("Command? (h for help)")
  207.             elif ui_state==UIState.target:
  208.                 print("Targetting...")
  209.  
  210.             # Make sure everything is printed before pausing at end of frame
  211.             sys.stdout.flush()
  212.  
  213.         # sleep
  214.         ticker += 1
  215.         time.sleep(0.1)
  216.  
  217. def main():
  218.     global world, player, entities, events, message_list, shutdown_renderer, ui_state, cursor
  219.  
  220.     t = threading.Thread(target=render)
  221.     t.daemon = True
  222.     t.start()
  223.  
  224.     while True:
  225.  
  226.         command = None
  227.         command_ext = None
  228.         while command is None:
  229.             command = read_input()
  230.             if command == b'\xe0': # Must be an arrow
  231.                 command_ext = read_input()
  232.  
  233.         # Input: System Command
  234.         if command == b'q':
  235.             if ui_state == UIState.target:
  236.                 with update_lock:
  237.                     ui_state = UIState.normal
  238.             else:
  239.                 with update_lock:
  240.                     shutdown_renderer = True
  241.                 t.join()
  242.                 sys.exit(0)
  243.         elif command in [b'h', b'?']:
  244.             message("Help:")
  245.             message("move  arrows")
  246.             message("chop  space")
  247.             message("get   g")
  248.             message("quit  q")
  249.             continue
  250.  
  251.         # Input: Direction Command
  252.         # NB: These use the command_ext part
  253.         direction = {
  254.             b'K': (-1, 0),  # Left arrow
  255.             b'M': (1, 0),   # Right arrow
  256.             b'H': (0, -1),  # Up arrow
  257.             b'P': (0, 1)    # Down arrow
  258.         }.get(command_ext, None)
  259.  
  260.         if direction is not None:
  261.             if ui_state==UIState.normal:
  262.                 # Attempt player move
  263.                 current_position = player.position
  264.                 target_position = player.position[0] + direction[0], player.position[1] + direction[1]
  265.                 events.append(("move", player, current_position, target_position))
  266.             else:
  267.                 # Move cursor
  268.                 cursor = clamp(cursor[0] + direction[0], 0, world_dim[0]), clamp(cursor[1] + direction[1], 0, world_dim[1])
  269.  
  270.         if command==b' ':
  271.             if ui_state == UIState.normal:
  272.                 ui_state = UIState.target
  273.                 cursor = player.position
  274.             else:
  275.                 # was in target mode, try to chop target
  276.                 ui_state = UIState.normal
  277.                 events.append(("chop", player, cursor))
  278.  
  279.         if command==b'g':
  280.             events.append(("get", player))
  281.  
  282.         # TODO: Input: Handle other commands
  283.  
  284.         with update_lock:
  285.             message_list.clear()
  286.  
  287.             # TODO: Update World: Simulation
  288.  
  289.             # TODO: Update World: Entities
  290.             for e in entities:
  291.                 pass
  292.  
  293.             # Update World: Events
  294.             while events:
  295.                 ev = events.popleft()
  296.                 if ev[0] == "move":
  297.                     _, entity, position, target_position = ev
  298.                     # Check target position is in bounds
  299.                     out_of_bounds = not (0 <= target_position[0] < world_dim[0] and 0 <= target_position[1] < world_dim[1])
  300.                     if out_of_bounds:
  301.                         message("Can't walk out of bounds!")
  302.                     else:
  303.                         tile = world[target_position[1]][target_position[0]]
  304.                         if tile == Blocks.air:
  305.                             entity.position = target_position
  306.                         else:
  307.                             message("Oof! You walked into a solid tile!")
  308.                 elif ev[0] == "chop":
  309.                     _, entity, target = ev
  310.                     out_of_bounds = not (0 <= target[0] < world_dim[0] and 0 <= target[1] < world_dim[1])
  311.                     if out_of_bounds:
  312.                         message("Can't chop out of bounds!")
  313.                     else:
  314.                         dp = target[0] - entity.position[0], target[1] - entity.position[1]
  315.                         correct_distance = abs(dp[0]) + abs(dp[1]) == 1
  316.                         if correct_distance:
  317.                             tile = world[target[1]][target[0]]
  318.                             if tile == Blocks.wood:
  319.                                 world[target[1]][target[0]] = Blocks.air
  320.                                 lumber = Entity()
  321.                                 lumber.position = target
  322.                                 lumber.appearance = "="
  323.                                 lumber.is_solid = False
  324.                                 lumber.description = "lumber"
  325.                                 entities.append(lumber)
  326.                             elif tile == Blocks.air:
  327.                                 message("Nothing to chop there!")
  328.                             elif tile == Blocks.dirt:
  329.                                 message("Can't chop dirt!")
  330.                         else:
  331.                             message("Can't chop there!")
  332.                 elif ev[0] == "get":
  333.                     _, entity = ev
  334.                     for e in entities:
  335.                         if e is not entity and e.position==entity.position:
  336.                             # Pickup items and put in inventory
  337.                             e.position = None
  338.                             e.in_inventory = entity
  339.                             entity.inventory.append(e)
  340.  
  341.  
  342.             # Update World: Description
  343.             for e in entities:
  344.                 if e is not player and e.position==player.position:
  345.                     message("A %s is here."%e.description)
  346.  
  347.  
  348. if __name__ == "__main__":
  349.     main()
Advertisement
Add Comment
Please, Sign In to add comment