You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
7.3KB

  1. #!/usr/bin/python3
  2. import collections as cl
  3. import networkx as nx
  4. import curses as c
  5. import random
  6. from grid import Grid, bresenhams
  7. from fov import fieldOfView
  8. class Tile():
  9. def __init__(self, glyph, name, description, solid=False, transparent=True, color=c.COLOR_WHITE):
  10. self.appearance = glyph
  11. self.name = name
  12. self.description = description
  13. self.solid = solid
  14. self.transparent = transparent
  15. self.color = color
  16. def draw(self):
  17. return (self.glyph, self.color)
  18. def describe(self):
  19. return self.description
  20. class World(Grid):
  21. def __init__(self, window, width, height, initial_tile):
  22. super().__init__(width, height, initial_tile)
  23. self.run = True
  24. self.window = window
  25. self.width = width
  26. self.height = height
  27. self.bodies = []
  28. self.behavior = []
  29. class Spirit():
  30. def __init__(self, name):
  31. self.name = name
  32. self.body = None
  33. self.world = None
  34. def behave(self):
  35. pass
  36. def incarnate(self, body):
  37. if not self.body is None:
  38. self.body.behavior = None
  39. if not body.behavior is None:
  40. body.behavior.body = None
  41. self.body = body
  42. body.behavior = self
  43. def message(self, msg):
  44. pass
  45. class Player(Spirit):
  46. def __init__(self, name):
  47. super().__init__(name)
  48. self.msgbuffer = cl.deque([], 200)
  49. self.messaged = False
  50. def message(self, msg):
  51. if len(self.msgbuffer) > 0 and msg == self.msgbuffer[0][1]:
  52. self.msgbuffer.appendleft((self.msgbuffer.pop()[0] + 1, msg))
  53. else:
  54. self.msgbuffer.appendleft((1, msg))
  55. self.messaged = True
  56. def behave(self):
  57. # calculate visibility
  58. visibility = Grid(self.world.width, self.world.height, False)
  59. fieldOfView(self.body.x, self.body.y, self.world.width, self.world.height, 100, lambda x, y: visibility.set(x, y, True), lambda x, y: self.world.get(x, y).solid)
  60. self.world.window.erase()
  61. # draw interface
  62. for x in range(0, self.world.width):
  63. for y in range(0, self.world.height):
  64. if visibility.get(x, y):
  65. self.world.window.addch(y, x, self.world.get(x, y).appearance)
  66. for body in self.world.bodies:
  67. if visibility.get(body.x, body.y):
  68. body.draw()
  69. if not len(self.msgbuffer) == 0 and self.messaged:
  70. if self.msgbuffer[0][0] == 1:
  71. self.world.window.addstr(self.world.height + 1, 0, self.msgbuffer[0][1])
  72. else:
  73. self.world.window.addstr(self.world.height + 1, 0, self.msgbuffer[0][1] + " (x" + str(self.msgbuffer[0][0]) + ")")
  74. self.messaged = False
  75. self.world.window.refresh()
  76. # this is the one gameplay input point.
  77. key = self.world.window.getkey()
  78. match key:
  79. case 'w':
  80. if not self.body.move((0, -1)):
  81. self.message("You cannot go there.")
  82. case 's':
  83. if not self.body.move((0, +1)):
  84. self.message("You cannot go there.")
  85. case 'a':
  86. if not self.body.move((-1, 0)):
  87. self.message("You cannot go there.")
  88. case 'd':
  89. if not self.body.move((+1, 0)):
  90. self.message("You cannot go there.")
  91. case 'c':
  92. coords = self.world.neighbors(self.body.x, self.body.y)
  93. for coord in coords:
  94. x, y = coord
  95. t = self.world.get(x, y)
  96. if t.name == "open door":
  97. self.body.close_door(x, y)
  98. break
  99. else:
  100. self.message("You see nothing here to close.")
  101. case 'q':
  102. self.world.run = False
  103. # case _: self.world.message(key)
  104. class Drunkard(Spirit):
  105. def behave(self):
  106. while True:
  107. dx, dy = random.randint(-1,1), random.randint(-1,1)
  108. tx, ty = dx + self.body.x, dy + self.body.y
  109. if self.world.in_bounds(tx, ty) and not self.world.get(tx, ty).solid:
  110. break
  111. bodies = [body for body in self.world.bodies if body.x == tx and body.y == ty]
  112. if bodies:
  113. for tb in bodies:
  114. tb.behavior.message("A " + self.body.name + " bumps into you.")
  115. else:
  116. self.body.move((dx, dy))
  117. class Body():
  118. def __init__(self, name, appearence, description, color):
  119. self.name = name
  120. self.appearence = appearence
  121. self.description = description
  122. self.color = color
  123. self.behavior = None
  124. self.world = None
  125. self.x = None
  126. self.y = None
  127. def move(self, direction):
  128. x, y = direction
  129. tx, ty = self.x + x, self.y + y
  130. if not self.world.in_bounds(tx, ty):
  131. return False
  132. elif self.world.get(tx, ty).name == "closed door":
  133. self.open_door(tx, ty)
  134. return True
  135. elif self.world.get(tx, ty).solid:
  136. return False
  137. else:
  138. self.x = tx
  139. self.y = ty
  140. return True
  141. def open_door(self, x, y):
  142. if not self.world.in_bounds(x, y):
  143. self.behavior.message("You cannot open anything there.")
  144. return False
  145. elif self.world.get(x, y).name == "closed door":
  146. self.world.set(x, y, door_open)
  147. self.behavior.message("You open the door.")
  148. return True
  149. else:
  150. self.behavior.message("You cannot open anything there.")
  151. return False
  152. def close_door(self, x, y):
  153. if not self.world.in_bounds(x, y):
  154. self.behavior.message("You cannot close anything there.")
  155. return False
  156. elif self.world.get(x, y).name == "open door":
  157. self.world.set(x, y, door_closed)
  158. self.behavior.message("You close the door.")
  159. return True
  160. else:
  161. self.behavior.message("You cannot close anything there.")
  162. return False
  163. def describe(self):
  164. return self.description
  165. def draw(self):
  166. self.world.window.addch(self.y, self.x, self.appearence)
  167. p = Player("you")
  168. pb = Body("yourself", '@', "an unremarkable person", c.COLOR_WHITE)
  169. p.incarnate(pb)
  170. floor = Tile('.', "floor", "an unremarkable piece of floor")
  171. wall = Tile('#', "wall", "an unremarkable wall", solid=True, transparent=False)
  172. door_closed = Tile('+', "closed door", "an unremarkable door", solid=True, transparent=False)
  173. door_open = Tile(',', "open door", "an unremarkable door", solid=False, transparent=True)
  174. w = c.initscr()
  175. c.curs_set(0)
  176. c.raw()
  177. wld = World(w, 80, 23, floor)
  178. drunk = Drunkard("drunkard")
  179. db = Body("drunkard", 'H', "A smelly drunkard, stumbling about.", c.COLOR_WHITE)
  180. drunk.incarnate(db)
  181. db.world = wld
  182. drunk.world = wld
  183. db.x = 20
  184. db.y = 16
  185. wld.behaviors = [p, drunk]
  186. wld.bodies = [pb, db]
  187. pb.world = wld
  188. p.world = wld
  189. pb.x = 3
  190. pb.y = 6
  191. wld.set(12, 12, wall)
  192. wld.set(13, 12, wall)
  193. wld.set(14, 12, wall)
  194. wld.set(15, 12, wall)
  195. wld.set(16, 12, wall)
  196. wld.set(16, 13, wall)
  197. wld.set(16, 14, wall)
  198. wld.set(18, 12, wall)
  199. wld.set(18, 13, wall)
  200. wld.set(18, 14, wall)
  201. wld.set(12, 13, door_closed)
  202. while wld.run:
  203. for behavior in wld.behaviors:
  204. behavior.behave()
  205. c.endwin()