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.

293 lines
8.9KB

  1. #!/usr/bin/python3
  2. import collections as cl
  3. import networkx as nx
  4. import curses as c
  5. import random
  6. from time import sleep
  7. from grid import Grid, bresenhams
  8. from fov import fieldOfView
  9. class Tile():
  10. def __init__(self, glyph, name, description, solid=False, transparent=True, color=c.COLOR_WHITE):
  11. self.appearance = glyph
  12. self.name = name
  13. self.description = description
  14. self.solid = solid
  15. self.transparent = transparent
  16. self.color = color
  17. def draw(self):
  18. return (self.glyph, self.color)
  19. def describe(self):
  20. return self.description
  21. class World(Grid):
  22. def __init__(self, window, width, height, initial_tile):
  23. super().__init__(width, height, initial_tile)
  24. self.run = True
  25. self.window = window
  26. self.width = width
  27. self.height = height
  28. self.bodies = []
  29. self.behavior = []
  30. class Behaviour():
  31. def __init__(self, name):
  32. self.name = name
  33. self.body = None
  34. self.world = None
  35. def behave(self):
  36. pass
  37. def incarnate(self, body):
  38. if not self.body is None:
  39. self.body.behavior = None
  40. if not body.behavior is None:
  41. body.behavior.body = None
  42. self.body = body
  43. body.behavior = self
  44. def message(self, msg):
  45. pass
  46. class Player(Behaviour):
  47. def __init__(self, name):
  48. super().__init__(name)
  49. self.msgbuffer = cl.deque([], 200)
  50. self.messaged = False
  51. self.messaged_last = False
  52. self.realtime = False
  53. def message(self, msg):
  54. if len(self.msgbuffer) > 0 and msg == self.msgbuffer[0][1] and self.messaged_last:
  55. self.msgbuffer.appendleft((self.msgbuffer.popleft()[0] + 1, msg))
  56. else:
  57. self.msgbuffer.appendleft((1, msg))
  58. self.messaged = True
  59. def behave(self):
  60. # calculate visibility
  61. visibility = Grid(self.world.width, self.world.height, False)
  62. 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)
  63. self.world.window.erase()
  64. # draw interface you
  65. for x in range(0, self.world.width):
  66. for y in range(0, self.world.height):
  67. if visibility.get(x, y):
  68. self.world.window.addch(y, x, self.world.get(x, y).appearance)
  69. for body in self.world.bodies:
  70. if visibility.get(body.x, body.y):
  71. body.draw()
  72. if self.messaged:
  73. if self.msgbuffer[0][0] == 1:
  74. self.world.window.addstr(self.world.height + 1, 0, self.msgbuffer[0][1])
  75. else:
  76. self.world.window.addstr(self.world.height + 1, 0, self.msgbuffer[0][1] + " (x" + str(self.msgbuffer[0][0]) + ")")
  77. self.messaged = False
  78. self.messaged_last = True
  79. else:
  80. self.messaged_last = False
  81. self.world.window.refresh()
  82. # this is the one gameplay input point.
  83. if self.realtime:
  84. sleep(0.2)
  85. try:
  86. key = self.world.window.getkey()
  87. except c.error:
  88. key = ""
  89. c.flushinp()
  90. else:
  91. key = self.world.window.getkey()
  92. match key:
  93. case 'w':
  94. if not self.body.move((0, -1)):
  95. self.message("You cannot go there.")
  96. case 's':
  97. if not self.body.move((0, +1)):
  98. self.message("You cannot go there.")
  99. case 'a':
  100. if not self.body.move((-1, 0)):
  101. self.message("You cannot go there.")
  102. case 'd':
  103. if not self.body.move((+1, 0)):
  104. self.message("You cannot go there.")
  105. case 'c':
  106. coords = self.world.neighbors(self.body.x, self.body.y)
  107. for coord in coords:
  108. x, y = coord
  109. t = self.world.get(x, y)
  110. if t.name == "open door":
  111. self.body.close_door(x, y)
  112. break
  113. else:
  114. self.message("You see nothing here to close.")
  115. case 'q':
  116. self.world.run = False
  117. case 'r':
  118. if self.realtime:
  119. self.realtime = False
  120. self.world.window.nodelay(False)
  121. else:
  122. self.realtime = True
  123. self.world.window.nodelay(True)
  124. # case _: self.world.message(key)
  125. class Drunkard(Behaviour):
  126. def behave(self):
  127. while True:
  128. dx, dy = random.randint(-1,1), random.randint(-1,1)
  129. tx, ty = dx + self.body.x, dy + self.body.y
  130. if self.world.in_bounds(tx, ty) and not self.world.get(tx, ty).solid:
  131. break
  132. bodies = [body for body in self.world.bodies if body.x == tx and body.y == ty]
  133. if bodies:
  134. for tb in bodies:
  135. tb.behavior.message("A " + self.body.name + " bumps into you.")
  136. else:
  137. self.body.move((dx, dy))
  138. class Body():
  139. def __init__(self, name, appearence, description, color):
  140. self.name = name
  141. self.appearence = appearence
  142. self.description = description
  143. self.color = color
  144. self.behavior = None
  145. self.world = None
  146. self.x = None
  147. self.y = None
  148. def move(self, direction):
  149. x, y = direction
  150. tx, ty = self.x + x, self.y + y
  151. if not self.world.in_bounds(tx, ty):
  152. return False
  153. elif self.world.get(tx, ty).name == "closed door":
  154. self.open_door(tx, ty)
  155. return True
  156. elif self.world.get(tx, ty).solid:
  157. return False
  158. else:
  159. self.x = tx
  160. self.y = ty
  161. return True
  162. def open_door(self, x, y):
  163. if not self.world.in_bounds(x, y):
  164. self.behavior.message("You cannot open anything there.")
  165. return False
  166. elif self.world.get(x, y).name == "closed door":
  167. self.world.set(x, y, door_open)
  168. self.behavior.message("You open the door.")
  169. return True
  170. else:
  171. self.behavior.message("You cannot open anything there.")
  172. return False
  173. def close_door(self, x, y):
  174. if not self.world.in_bounds(x, y):
  175. self.behavior.message("You cannot close anything there.")
  176. return False
  177. elif self.world.get(x, y).name == "open door":
  178. self.world.set(x, y, door_closed)
  179. self.behavior.message("You close the door.")
  180. return True
  181. else:
  182. self.behavior.message("You cannot close anything there.")
  183. return False
  184. def describe(self):
  185. return self.description
  186. def draw(self):
  187. self.world.window.addch(self.y, self.x, self.appearence)
  188. class Conversation(list):
  189. def __init__(self, title, description):
  190. self.title = title
  191. self.description = description
  192. def run(self):
  193. # rendering code goes here
  194. pass
  195. class Response():
  196. def __init__(self, name, text, target=None, prerequisite=None, function=None):
  197. self.name = name
  198. self.text = text
  199. self.target = target
  200. self.function = function
  201. def respond(self, title, description):
  202. # rendering code goes here
  203. if function:
  204. function()
  205. if target:
  206. target.run()
  207. bum_conversation = Conversation("Conversing With a Drunkard", "A foul-smelling drunkard waves to and fro, trying to form words. His tattered clothes are brown with filth.")
  208. bum_conversation.append(Response("Hello? Is everything all right?", "Hargl... hurgl...", target=bum_conversation))
  209. bum_conversation.append(Response("I will take my leave, good sir.", "..."))
  210. p = Player("you")
  211. pb = Body("yourself", '@', "an unremarkable person", c.COLOR_WHITE)
  212. p.incarnate(pb)
  213. floor = Tile('.', "floor", "an unremarkable piece of floor")
  214. wall = Tile('#', "wall", "an unremarkable wall", solid=True, transparent=False)
  215. door_closed = Tile('+', "closed door", "an unremarkable door", solid=True, transparent=False)
  216. door_open = Tile(',', "open door", "an unremarkable door", solid=False, transparent=True)
  217. w = c.initscr()
  218. c.curs_set(0)
  219. c.raw()
  220. c.echo(False)
  221. wld = World(w, 80, 23, floor)
  222. drunk = Drunkard("drunkard")
  223. db = Body("drunkard", 'H', "A smelly drunkard, stumbling about.", c.COLOR_WHITE)
  224. drunk.incarnate(db)
  225. db.world = wld
  226. drunk.world = wld
  227. db.x = 20
  228. db.y = 16
  229. wld.behaviors = [p, drunk]
  230. wld.bodies = [pb, db]
  231. pb.world = wld
  232. p.world = wld
  233. pb.x = 3
  234. pb.y = 6
  235. wld.set(12, 12, wall)
  236. wld.set(13, 12, wall)
  237. wld.set(14, 12, wall)
  238. wld.set(15, 12, wall)
  239. wld.set(16, 12, wall)
  240. wld.set(16, 13, wall)
  241. wld.set(16, 14, wall)
  242. wld.set(18, 12, wall)
  243. wld.set(18, 13, wall)
  244. wld.set(18, 14, wall)
  245. wld.set(12, 13, door_closed)
  246. while wld.run:
  247. for behavior in wld.behaviors:
  248. behavior.behave()
  249. c.endwin()