Advertisement
eigenbom

WoodchopperRL

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