rouge
This commit is contained in:
parent
252ae8d03c
commit
d5d80345fc
@ -1,4 +1,4 @@
|
||||
#!/bin/zsh
|
||||
xrdb -merge ~/.Xresources.green
|
||||
#xrdb -merge ~/.Xresources.green
|
||||
urxvt -fn xft:inconsolata-26:antialias=true:hinting=true -letsp 1 -b 26 &
|
||||
xrdb -merge ~/.Xresources
|
||||
#xrdb -merge ~/.Xresources
|
||||
|
324
fov.py
Normal file
324
fov.py
Normal file
@ -0,0 +1,324 @@
|
||||
"""
|
||||
Author: Aaron MacDonald
|
||||
Date: June 14, 2007
|
||||
|
||||
Description: An implementation of the precise permissive field
|
||||
of view algorithm for use in tile-based games.
|
||||
Based on the algorithm presented at
|
||||
http://roguebasin.roguelikedevelopment.org/
|
||||
index.php?title=
|
||||
Precise_Permissive_Field_of_View.
|
||||
|
||||
You are free to use or modify this code as long as this notice is
|
||||
included.
|
||||
This code is released without warranty.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
def fieldOfView(startX, startY, mapWidth, mapHeight, radius, \
|
||||
funcVisitTile, funcTileBlocked):
|
||||
"""
|
||||
Determines which coordinates on a 2D grid are visible from a
|
||||
particular coordinate.
|
||||
|
||||
startX, startY: The (x, y) coordinate on the grid that
|
||||
is the centre of view.
|
||||
|
||||
mapWidth, mapHeight: The maximum extents of the grid. The
|
||||
minimum extents are assumed to be both
|
||||
zero.
|
||||
|
||||
radius: How far the field of view may extend
|
||||
in either direction along the x and y
|
||||
axis.
|
||||
|
||||
funcVisitTile: User function that takes two integers
|
||||
representing an (x, y) coordinate. Is
|
||||
used to "visit" visible coordinates.
|
||||
|
||||
funcTileBlocked: User function that takes two integers
|
||||
representing an (x, y) coordinate.
|
||||
Returns True if the coordinate blocks
|
||||
sight to coordinates "behind" it.
|
||||
"""
|
||||
|
||||
visited = set() # Keep track of what tiles have been visited so
|
||||
# that no tile will be visited twice.
|
||||
|
||||
# Will always see the centre.
|
||||
funcVisitTile(startX, startY)
|
||||
visited.add((startX, startY))
|
||||
|
||||
# Ge the dimensions of the actual field of view, making
|
||||
# sure not to go off the map or beyond the radius.
|
||||
|
||||
if startX < radius:
|
||||
minExtentX = startX
|
||||
else:
|
||||
minExtentX = radius
|
||||
|
||||
if mapWidth - startX - 1 < radius:
|
||||
maxExtentX = mapWidth - startX - 1
|
||||
else:
|
||||
maxExtentX = radius
|
||||
|
||||
if startY < radius:
|
||||
minExtentY = startY
|
||||
else:
|
||||
minExtentY = radius
|
||||
|
||||
if mapHeight - startY - 1 < radius:
|
||||
maxExtentY = mapHeight - startY - 1
|
||||
else:
|
||||
maxExtentY = radius
|
||||
|
||||
# Northeast quadrant
|
||||
__checkQuadrant(visited, startX, startY, 1, 1, \
|
||||
maxExtentX, maxExtentY, \
|
||||
funcVisitTile, funcTileBlocked)
|
||||
|
||||
# Southeast quadrant
|
||||
__checkQuadrant(visited, startX, startY, 1, -1, \
|
||||
maxExtentX, minExtentY, \
|
||||
funcVisitTile, funcTileBlocked)
|
||||
|
||||
# Southwest quadrant
|
||||
__checkQuadrant(visited, startX, startY, -1, -1, \
|
||||
minExtentX, minExtentY, \
|
||||
funcVisitTile, funcTileBlocked)
|
||||
|
||||
# Northwest quadrant
|
||||
__checkQuadrant(visited, startX, startY, -1, 1, \
|
||||
minExtentX, maxExtentY, \
|
||||
funcVisitTile, funcTileBlocked)
|
||||
|
||||
#-------------------------------------------------------------
|
||||
|
||||
class __Line(object):
|
||||
def __init__(self, xi, yi, xf, yf):
|
||||
self.xi = xi
|
||||
self.yi = yi
|
||||
self.xf = xf
|
||||
self.yf = yf
|
||||
|
||||
dx = property(fget = lambda self: self.xf - self.xi)
|
||||
dy = property(fget = lambda self: self.yf - self.yi)
|
||||
|
||||
def pBelow(self, x, y):
|
||||
return self.relativeSlope(x, y) > 0
|
||||
|
||||
def pBelowOrCollinear(self, x, y):
|
||||
return self.relativeSlope(x, y) >= 0
|
||||
|
||||
def pAbove(self, x, y):
|
||||
return self.relativeSlope(x, y) < 0
|
||||
|
||||
def pAboveOrCollinear(self, x, y):
|
||||
return self.relativeSlope(x, y) <= 0
|
||||
|
||||
def pCollinear(self, x, y):
|
||||
return self.relativeSlope(x, y) == 0
|
||||
|
||||
def lineCollinear(self, line):
|
||||
return self.pCollinear(line.xi, line.yi) \
|
||||
and self.pCollinear(line.xf, line.yf)
|
||||
|
||||
def relativeSlope(self, x, y):
|
||||
return (self.dy * (self.xf - x)) \
|
||||
- (self.dx * (self.yf - y))
|
||||
|
||||
class __ViewBump:
|
||||
def __init__(self, x, y, parent):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.parent = parent
|
||||
|
||||
class __View:
|
||||
def __init__(self, shallowLine, steepLine):
|
||||
self.shallowLine = shallowLine
|
||||
self.steepLine = steepLine
|
||||
|
||||
self.shallowBump = None
|
||||
self.steepBump = None
|
||||
|
||||
def __checkQuadrant(visited, startX, startY, dx, dy, \
|
||||
extentX, extentY, funcVisitTile, funcTileBlocked):
|
||||
activeViews = []
|
||||
|
||||
shallowLine = __Line(0, 1, extentX, 0)
|
||||
steepLine = __Line(1, 0, 0, extentY)
|
||||
|
||||
activeViews.append( __View(shallowLine, steepLine) )
|
||||
viewIndex = 0
|
||||
|
||||
# Visit the tiles diagonally and going outwards
|
||||
#
|
||||
# .
|
||||
# .
|
||||
# . .
|
||||
# 9 .
|
||||
# 5 8 .
|
||||
# 2 4 7
|
||||
# @ 1 3 6 . . .
|
||||
maxI = extentX + extentY
|
||||
i = 1
|
||||
while i != maxI + 1 and len(activeViews) > 0:
|
||||
if 0 > i - extentX:
|
||||
startJ = 0
|
||||
else:
|
||||
startJ = i - extentX
|
||||
|
||||
if i < extentY:
|
||||
maxJ = i
|
||||
else:
|
||||
maxJ = extentY
|
||||
|
||||
j = startJ
|
||||
while j != maxJ + 1 and viewIndex < len(activeViews):
|
||||
x = i - j
|
||||
y = j
|
||||
__visitCoord(visited, startX, startY, x, y, dx, dy, \
|
||||
viewIndex, activeViews, \
|
||||
funcVisitTile, funcTileBlocked)
|
||||
|
||||
j += 1
|
||||
|
||||
i += 1
|
||||
|
||||
def __visitCoord(visited, startX, startY, x, y, dx, dy, viewIndex, \
|
||||
activeViews, funcVisitTile, funcTileBlocked):
|
||||
# The top left and bottom right corners of the current coordinate.
|
||||
topLeft = (x, y + 1)
|
||||
bottomRight = (x + 1, y)
|
||||
|
||||
while viewIndex < len(activeViews) \
|
||||
and activeViews[viewIndex].steepLine.pBelowOrCollinear( \
|
||||
bottomRight[0], bottomRight[1]):
|
||||
# The current coordinate is above the current view and is
|
||||
# ignored. The steeper fields may need it though.
|
||||
viewIndex += 1
|
||||
|
||||
if viewIndex == len(activeViews) \
|
||||
or activeViews[viewIndex].shallowLine.pAboveOrCollinear( \
|
||||
topLeft[0], topLeft[1]):
|
||||
# Either the current coordinate is above all of the fields
|
||||
# or it is below all of the fields.
|
||||
return
|
||||
|
||||
# It is now known that the current coordinate is between the steep
|
||||
# and shallow lines of the current view.
|
||||
|
||||
isBlocked = False
|
||||
|
||||
# The real quadrant coordinates
|
||||
realX = x * dx
|
||||
realY = y * dy
|
||||
|
||||
if (startX + realX, startY + realY) not in visited:
|
||||
visited.add((startX + realX, startY + realY))
|
||||
funcVisitTile(startX + realX, startY + realY)
|
||||
"""else:
|
||||
# Debugging
|
||||
print (startX + realX, startY + realY)"""
|
||||
|
||||
isBlocked = funcTileBlocked(startX + realX, startY + realY)
|
||||
|
||||
if not isBlocked:
|
||||
# The current coordinate does not block sight and therefore
|
||||
# has no effect on the view.
|
||||
return
|
||||
|
||||
if activeViews[viewIndex].shallowLine.pAbove( \
|
||||
bottomRight[0], bottomRight[1]) \
|
||||
and activeViews[viewIndex].steepLine.pBelow( \
|
||||
topLeft[0], topLeft[1]):
|
||||
# The current coordinate is intersected by both lines in the
|
||||
# current view. The view is completely blocked.
|
||||
del activeViews[viewIndex]
|
||||
elif activeViews[viewIndex].shallowLine.pAbove( \
|
||||
bottomRight[0], bottomRight[1]):
|
||||
# The current coordinate is intersected by the shallow line of
|
||||
# the current view. The shallow line needs to be raised.
|
||||
__addShallowBump(topLeft[0], topLeft[1], \
|
||||
activeViews, viewIndex)
|
||||
__checkView(activeViews, viewIndex)
|
||||
elif activeViews[viewIndex].steepLine.pBelow( \
|
||||
topLeft[0], topLeft[1]):
|
||||
# The current coordinate is intersected by the steep line of
|
||||
# the current view. The steep line needs to be lowered.
|
||||
__addSteepBump(bottomRight[0], bottomRight[1], activeViews, \
|
||||
viewIndex)
|
||||
__checkView(activeViews, viewIndex)
|
||||
else:
|
||||
# The current coordinate is completely between the two lines
|
||||
# of the current view. Split the current view into two views
|
||||
# above and below the current coordinate.
|
||||
|
||||
shallowViewIndex = viewIndex
|
||||
viewIndex += 1
|
||||
steepViewIndex = viewIndex
|
||||
|
||||
activeViews.insert(shallowViewIndex, \
|
||||
copy.deepcopy(activeViews[shallowViewIndex]))
|
||||
|
||||
__addSteepBump(bottomRight[0], bottomRight[1], \
|
||||
activeViews, shallowViewIndex)
|
||||
if not __checkView(activeViews, shallowViewIndex):
|
||||
viewIndex -= 1
|
||||
steepViewIndex -= 1
|
||||
|
||||
__addShallowBump(topLeft[0], topLeft[1], activeViews, \
|
||||
steepViewIndex)
|
||||
__checkView(activeViews, steepViewIndex)
|
||||
|
||||
def __addShallowBump(x, y, activeViews, viewIndex):
|
||||
activeViews[viewIndex].shallowLine.xf = x
|
||||
activeViews[viewIndex].shallowLine.yf = y
|
||||
|
||||
activeViews[viewIndex].shallowBump = __ViewBump(x, y, \
|
||||
activeViews[viewIndex].shallowBump)
|
||||
|
||||
curBump = activeViews[viewIndex].steepBump
|
||||
while curBump is not None:
|
||||
if activeViews[viewIndex].shallowLine.pAbove( \
|
||||
curBump.x, curBump.y):
|
||||
activeViews[viewIndex].shallowLine.xi = curBump.x
|
||||
activeViews[viewIndex].shallowLine.yi = curBump.y
|
||||
|
||||
curBump = curBump.parent
|
||||
|
||||
def __addSteepBump(x, y, activeViews, viewIndex):
|
||||
activeViews[viewIndex].steepLine.xf = x
|
||||
activeViews[viewIndex].steepLine.yf = y
|
||||
|
||||
activeViews[viewIndex].steepBump = __ViewBump(x, y, \
|
||||
activeViews[viewIndex].steepBump)
|
||||
|
||||
curBump = activeViews[viewIndex].shallowBump
|
||||
while curBump is not None:
|
||||
if activeViews[viewIndex].steepLine.pBelow( \
|
||||
curBump.x, curBump.y):
|
||||
activeViews[viewIndex].steepLine.xi = curBump.x
|
||||
activeViews[viewIndex].steepLine.yi = curBump.y
|
||||
|
||||
curBump = curBump.parent
|
||||
|
||||
def __checkView(activeViews, viewIndex):
|
||||
"""
|
||||
Removes the view in activeViews at index viewIndex if
|
||||
- The two lines are coolinear
|
||||
- The lines pass through either extremity
|
||||
"""
|
||||
|
||||
shallowLine = activeViews[viewIndex].shallowLine
|
||||
steepLine = activeViews[viewIndex].steepLine
|
||||
|
||||
if shallowLine.lineCollinear(steepLine) \
|
||||
and ( shallowLine.pCollinear(0, 1) \
|
||||
or shallowLine.pCollinear(1, 0) ):
|
||||
del activeViews[viewIndex]
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
60
grid.py
Normal file
60
grid.py
Normal file
@ -0,0 +1,60 @@
|
||||
class Grid():
|
||||
def __init__(self, x, y, initial):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.initial = initial
|
||||
self.data = [initial] * x * y
|
||||
|
||||
def get(self, x, y):
|
||||
return self.data[self.x * y + x]
|
||||
|
||||
def set(self, x, y, v):
|
||||
self.data[self.x * y + x] = v
|
||||
|
||||
def in_bounds(self, x, y):
|
||||
return x < self.x and x >= 0 and y < self.y and y >= 0
|
||||
|
||||
def __getitem__(self, coord):
|
||||
(x, y) = coord
|
||||
return self.get(x, y)
|
||||
|
||||
def __setitem__(self, coord, value):
|
||||
(x, y) = coord
|
||||
self.set(x, y, value)
|
||||
|
||||
def neighbors(self, x, y):
|
||||
r = []
|
||||
for direction in [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)]:
|
||||
xc, yc = direction
|
||||
tx, ty = x + xc, y + yc
|
||||
if self.in_bounds(tx, ty):
|
||||
r.append((tx, ty))
|
||||
return r
|
||||
|
||||
def bresenhams(x1, y1, x2, y2):
|
||||
r = []
|
||||
dx = abs(x2 - x1)
|
||||
dy = abs(y2 - y1)
|
||||
x, y = x1, y1
|
||||
sx = -1 if x1 > x2 else 1
|
||||
sy = -1 if y1 > y2 else 1
|
||||
if dx > dy:
|
||||
err = dx / 2.0
|
||||
while x != x2:
|
||||
r.append((x, y))
|
||||
err -= dy
|
||||
if err < 0:
|
||||
y += sy
|
||||
err += dx
|
||||
x += sx
|
||||
else:
|
||||
err = dy / 2.0
|
||||
while y != y2:
|
||||
r.append((x, y))
|
||||
err -= dx
|
||||
if err < 0:
|
||||
x += sx
|
||||
err += dy
|
||||
y += sy
|
||||
r.append((x, y))
|
||||
return r
|
243
rougelike.py
Executable file
243
rougelike.py
Executable file
@ -0,0 +1,243 @@
|
||||
#!/usr/bin/python3
|
||||
import collections as cl
|
||||
import networkx as nx
|
||||
import curses as c
|
||||
import random
|
||||
from grid import Grid, bresenhams
|
||||
from fov import fieldOfView
|
||||
|
||||
class Tile():
|
||||
def __init__(self, glyph, name, description, solid=False, transparent=True, color=c.COLOR_WHITE):
|
||||
self.appearance = glyph
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.solid = solid
|
||||
self.transparent = transparent
|
||||
self.color = color
|
||||
|
||||
def draw(self):
|
||||
return (self.glyph, self.color)
|
||||
|
||||
def describe(self):
|
||||
return self.description
|
||||
|
||||
class World(Grid):
|
||||
def __init__(self, window, width, height, initial_tile):
|
||||
super().__init__(width, height, initial_tile)
|
||||
self.run = True
|
||||
self.window = window
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self.bodies = []
|
||||
self.behavior = []
|
||||
|
||||
class Spirit():
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.body = None
|
||||
self.world = None
|
||||
|
||||
def behave(self):
|
||||
pass
|
||||
|
||||
def incarnate(self, body):
|
||||
if not self.body is None:
|
||||
self.body.behavior = None
|
||||
if not body.behavior is None:
|
||||
body.behavior.body = None
|
||||
self.body = body
|
||||
body.behavior = self
|
||||
|
||||
def message(self, msg):
|
||||
pass
|
||||
|
||||
class Player(Spirit):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
self.msgbuffer = cl.deque([], 200)
|
||||
self.messaged = False
|
||||
|
||||
def message(self, msg):
|
||||
if len(self.msgbuffer) > 0 and msg == self.msgbuffer[0][1]:
|
||||
self.msgbuffer.appendleft((self.msgbuffer.pop()[0] + 1, msg))
|
||||
else:
|
||||
self.msgbuffer.appendleft((1, msg))
|
||||
self.messaged = True
|
||||
|
||||
def behave(self):
|
||||
|
||||
# calculate visibility
|
||||
visibility = Grid(self.world.width, self.world.height, False)
|
||||
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)
|
||||
|
||||
self.world.window.erase()
|
||||
|
||||
# draw interface
|
||||
for x in range(0, self.world.width):
|
||||
for y in range(0, self.world.height):
|
||||
if visibility.get(x, y):
|
||||
self.world.window.addch(y, x, self.world.get(x, y).appearance)
|
||||
for body in self.world.bodies:
|
||||
if visibility.get(body.x, body.y):
|
||||
body.draw()
|
||||
if not len(self.msgbuffer) == 0 and self.messaged:
|
||||
if self.msgbuffer[0][0] == 1:
|
||||
self.world.window.addstr(self.world.height + 1, 0, self.msgbuffer[0][1])
|
||||
else:
|
||||
self.world.window.addstr(self.world.height + 1, 0, self.msgbuffer[0][1] + " (x" + str(self.msgbuffer[0][0]) + ")")
|
||||
self.messaged = False
|
||||
|
||||
self.world.window.refresh()
|
||||
|
||||
# this is the one gameplay input point.
|
||||
key = self.world.window.getkey()
|
||||
match key:
|
||||
case 'w':
|
||||
if not self.body.move((0, -1)):
|
||||
self.message("You cannot go there.")
|
||||
case 's':
|
||||
if not self.body.move((0, +1)):
|
||||
self.message("You cannot go there.")
|
||||
case 'a':
|
||||
if not self.body.move((-1, 0)):
|
||||
self.message("You cannot go there.")
|
||||
case 'd':
|
||||
if not self.body.move((+1, 0)):
|
||||
self.message("You cannot go there.")
|
||||
case 'c':
|
||||
coords = self.world.neighbors(self.body.x, self.body.y)
|
||||
for coord in coords:
|
||||
x, y = coord
|
||||
t = self.world.get(x, y)
|
||||
if t.name == "open door":
|
||||
self.body.close_door(x, y)
|
||||
break
|
||||
else:
|
||||
self.message("You see nothing here to close.")
|
||||
case 'q':
|
||||
self.world.run = False
|
||||
# case _: self.world.message(key)
|
||||
|
||||
class Drunkard(Spirit):
|
||||
|
||||
def behave(self):
|
||||
while True:
|
||||
dx, dy = random.randint(-1,1), random.randint(-1,1)
|
||||
tx, ty = dx + self.body.x, dy + self.body.y
|
||||
if self.world.in_bounds(tx, ty) and not self.world.get(tx, ty).solid:
|
||||
break
|
||||
bodies = [body for body in self.world.bodies if body.x == tx and body.y == ty]
|
||||
if bodies:
|
||||
for tb in bodies:
|
||||
tb.behavior.message("A " + self.body.name + " bumps into you.")
|
||||
else:
|
||||
self.body.move((dx, dy))
|
||||
|
||||
class Body():
|
||||
def __init__(self, name, appearence, description, color):
|
||||
self.name = name
|
||||
self.appearence = appearence
|
||||
self.description = description
|
||||
self.color = color
|
||||
self.behavior = None
|
||||
self.world = None
|
||||
self.x = None
|
||||
self.y = None
|
||||
|
||||
def move(self, direction):
|
||||
x, y = direction
|
||||
tx, ty = self.x + x, self.y + y
|
||||
if not self.world.in_bounds(tx, ty):
|
||||
return False
|
||||
elif self.world.get(tx, ty).name == "closed door":
|
||||
self.open_door(tx, ty)
|
||||
return True
|
||||
elif self.world.get(tx, ty).solid:
|
||||
return False
|
||||
else:
|
||||
self.x = tx
|
||||
self.y = ty
|
||||
return True
|
||||
|
||||
def open_door(self, x, y):
|
||||
if not self.world.in_bounds(x, y):
|
||||
self.behavior.message("You cannot open anything there.")
|
||||
return False
|
||||
elif self.world.get(x, y).name == "closed door":
|
||||
self.world.set(x, y, door_open)
|
||||
self.behavior.message("You open the door.")
|
||||
return True
|
||||
else:
|
||||
self.behavior.message("You cannot open anything there.")
|
||||
return False
|
||||
|
||||
def close_door(self, x, y):
|
||||
if not self.world.in_bounds(x, y):
|
||||
self.behavior.message("You cannot close anything there.")
|
||||
return False
|
||||
elif self.world.get(x, y).name == "open door":
|
||||
self.world.set(x, y, door_closed)
|
||||
self.behavior.message("You close the door.")
|
||||
return True
|
||||
else:
|
||||
self.behavior.message("You cannot close anything there.")
|
||||
return False
|
||||
|
||||
def describe(self):
|
||||
return self.description
|
||||
|
||||
def draw(self):
|
||||
self.world.window.addch(self.y, self.x, self.appearence)
|
||||
|
||||
p = Player("you")
|
||||
|
||||
pb = Body("yourself", '@', "an unremarkable person", c.COLOR_WHITE)
|
||||
|
||||
p.incarnate(pb)
|
||||
|
||||
floor = Tile('.', "floor", "an unremarkable piece of floor")
|
||||
wall = Tile('#', "wall", "an unremarkable wall", solid=True, transparent=False)
|
||||
|
||||
door_closed = Tile('+', "closed door", "an unremarkable door", solid=True, transparent=False)
|
||||
door_open = Tile(',', "open door", "an unremarkable door", solid=False, transparent=True)
|
||||
|
||||
w = c.initscr()
|
||||
c.curs_set(0)
|
||||
c.raw()
|
||||
|
||||
wld = World(w, 80, 23, floor)
|
||||
|
||||
drunk = Drunkard("drunkard")
|
||||
db = Body("drunkard", 'H', "A smelly drunkard, stumbling about.", c.COLOR_WHITE)
|
||||
drunk.incarnate(db)
|
||||
|
||||
db.world = wld
|
||||
drunk.world = wld
|
||||
|
||||
db.x = 20
|
||||
db.y = 16
|
||||
|
||||
wld.behaviors = [p, drunk]
|
||||
wld.bodies = [pb, db]
|
||||
pb.world = wld
|
||||
p.world = wld
|
||||
pb.x = 3
|
||||
pb.y = 6
|
||||
|
||||
wld.set(12, 12, wall)
|
||||
wld.set(13, 12, wall)
|
||||
wld.set(14, 12, wall)
|
||||
wld.set(15, 12, wall)
|
||||
wld.set(16, 12, wall)
|
||||
wld.set(16, 13, wall)
|
||||
wld.set(16, 14, wall)
|
||||
wld.set(18, 12, wall)
|
||||
wld.set(18, 13, wall)
|
||||
wld.set(18, 14, wall)
|
||||
wld.set(12, 13, door_closed)
|
||||
|
||||
while wld.run:
|
||||
for behavior in wld.behaviors:
|
||||
behavior.behave()
|
||||
c.endwin()
|
Loading…
Reference in New Issue
Block a user