Skip to content

Instantly share code, notes, and snippets.

@carlfindahl
Created September 17, 2017 07:24
Show Gist options
  • Save carlfindahl/7c3e34d453169c06096def63b64de568 to your computer and use it in GitHub Desktop.
Save carlfindahl/7c3e34d453169c06096def63b64de568 to your computer and use it in GitHub Desktop.
DailyProgrammer Weekly #25 Challenge - Escape The Trolls. A solution using Python 3 and Curses
# TROLLBYRINTH - FOR /R/DAILYPROGRAMMER WEEKLY #25 - BY CARL F. (2016)
import curses
import random
from collections import deque
from functools import partial
LABYRINTH = [\
"#########################################################################",
"# # # # # # #",
"# # ######### # ##### ######### ##### ##### ##### # #",
"# # # # # # # # # #",
"######### # ######### ######### ##### # # # ######### #",
"# # # # # # # # # # #",
"# # ############# # # ######### ##### # ######### # #",
"# # # # # # # # # #",
"# ############# ##### ##### # ##### ######### # ##### #",
"# # # # # # # # # #",
"# ##### ##### # ##### # ######### # # # #############",
"# # # # # # # # # # # #",
"############# # # # ######### # ##### # ##### ##### #",
"# # # # # # # # # #",
"# ##### # ######### ##### # ##### ##### ############# #",
"# # # # # # # # # #",
"# # ######### # ##### ######### # # ############# # #",
"# # # # # # # # # # #",
"# ######### # # # ##### ######### ######### # #########",
"# # # # # # # # # #",
"# # ##### ##### ##### ######### ##### # ######### # #",
"# # # # # # #",
"# X #####################################################################"]
class Vector2D(object):
"""Defines a location vector (x, y) that can be added /\
multiplied etc by another Vector2D"""
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def RightVector(self):
return Vector2D(1, 0)
@classmethod
def UpVector(self):
return Vector2D(0, -1)
@classmethod
def LeftVector(self):
return Vector2D(-1, 0)
@classmethod
def DownVector(self):
return Vector2D(0, 1)
def __str__(self):
return "X = {0.x} Y = {0.y}".format(self)
# COMPARISONS
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __ne__(self, other):
return self.x != other.x or self.y != other.y
def __lt__(self, other):
return self.x + self.y < other.x + other.y
def __gt__(self, other):
return self.x + self.y > other.x + other.y
def __le__(self, other):
return self.x + self.y <= other.x + other.y
def __ge__(self, other):
return self.x + self.y >= other.x + other.y
# MATH
def __radd__(self, other):
return self.__add__(other)
def __rsub__(self, other):
return self.__sub__(other)
def __rmul__(self, other):
return self.__mul__(other)
def __rtruediv__(self, other):
return self.__div__(other)
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector2D(self.x - other.x, self.y - other.y)
def __mul__(self, other):
try:
if other[1]:
return Vector2D(self.x * other.x, self.y * other.y)
except:
return Vector2D(self.x * other, self.y * other)
def __truediv__(self, other):
try:
if other[1]:
return Vector2D(self.x / other.x, self.y / other.y)
except:
return Vector2D(self.x / other, self.y / other)
def __iadd__(self, other):
return self.__add__(other)
def __isub__(self, other):
return self.__sub__(other)
def __imul__(self, other):
return self.__mul__(other)
def __itruediv__(self, other):
return self.__truediv__(other)
class Queue(object):
def __init__(self):
self.elements = deque()
def IsEmptyQueue(self):
return len(self.elements) == 0
def Put(self, elem):
self.elements.append(elem)
def Get(self):
return self.elements.popleft()
class Event(object):
def __init__(self, key, func):
self.key = key
self.func = func
def __call__(self):
self.func()
class Creature(object):
def __init__(self, world, location, graphic, color, canPushBlocks=True):
self.graphic = graphic
self.color = color
self.world = world
self.location = location
self.canPushBlocks = canPushBlocks
def Die(self):
del self
def Move(self, direction):
ApproachedTile = self.world.GetTile(self.location + direction)
if ApproachedTile and ApproachedTile.passable:
self.location += direction
elif ApproachedTile and ApproachedTile.pushable:
if ApproachedTile.Push(direction):
self.location += direction
class Player(Creature):
def __init__(self, world, location):
super().__init__(world, location, "@", 1, True)
self.BindPlayerInput()
def Move(self, direction):
super().Move(direction)
if self.world.GetCreature(self.location):
self.Die()
self.world.application.Running = False
self.world.application.bDidWin = False
elif self.world.GetTile(self.location).isExit:
self.world.application.Running = False
self.world.application.bDidWin = True
def BindPlayerInput(self):
Bind = self.world.application.BindEvent
Bind(ord('w'), partial(self.Move, Vector2D(0, -1)))
Bind(ord('a'), partial(self.Move, Vector2D(-1, 0)))
Bind(ord('s'), partial(self.Move, Vector2D(0, 1)))
Bind(ord('d'), partial(self.Move, Vector2D(1, 0)))
Bind(curses.KEY_UP, partial(self.Move, Vector2D(0, -1)))
Bind(curses.KEY_LEFT, partial(self.Move, Vector2D(-1, 0)))
Bind(curses.KEY_DOWN, partial(self.Move, Vector2D(0, 1)))
Bind(curses.KEY_RIGHT, partial(self.Move, Vector2D(1, 0)))
def Update(self):
pass
class Troll(Creature):
def __init__(self, world, location):
super().__init__(world, location, "T", 3, True)
def Die(self):
self.world.trolls.remove(self)
super().Die()
def PathWithBreadthSearch(self, targetTile):
pathingQueue = Queue()
pathingQueue.Put(self.world.GetTile(self.location))
cameFrom = {}
cameFrom[self.world.GetTile(self.location)] = None
while not pathingQueue.IsEmptyQueue():
currentTile = pathingQueue.Get()
if currentTile == targetTile:
break
for tile in currentTile.adjacentTiles:
if tile and tile.passable and tile not in cameFrom:
pathingQueue.Put(tile)
cameFrom[tile] = currentTile
return cameFrom
def EvaluatePath(self, pathDict, targetTile):
try:
cameFrom = pathDict[targetTile]
while True:
if pathDict[cameFrom] and not\
pathDict[cameFrom].location == self.location:
cameFrom = pathDict[cameFrom]
else:
break
return cameFrom
except:
return self.world.GetTile(self.location)
def Update(self):
paths = self.PathWithBreadthSearch(self.world.GetPlayerLocation())
target = self.EvaluatePath(paths, self.world.GetPlayerLocation())
if not target.IsOccupied():
self.location = target.location
class World(object):
def __init__(self, application, mapToInitialize):
self.tiles = []
self.spawnPoints = []
self.application = application
self.InitMap(mapToInitialize)
self.window = curses.newwin(len(mapToInitialize),\
len(mapToInitialize[0]))
self.window.keypad(True)
self.player = None
self.trolls = []
self.SpawnCreature(True)
self.SpawnCreature()
self.SpawnCreature()
self.SpawnCreature()
self.SpawnCreature()
self.SpawnCreature()
self.SpawnCreature()
self.SpawnCreature()
def InitMap(self, mapToInitialize):
for y, row in enumerate(mapToInitialize):
for x, character in enumerate(row):
if character == "#":
self.tiles.append(Tile(self, Vector2D(x, y),\
curses.ACS_CKBOARD, 0,\
curses.A_NORMAL, False, True))
elif character == " ":
self.spawnPoints.append(Vector2D(x, y))
self.tiles.append(Tile(self, Vector2D(x, y),\
ord(" "), 0, curses.A_NORMAL,\
True, False))
elif character == "X":
self.tiles.append(Tile(self, Vector2D(x, y),\
ord('X'), 2, curses.A_BOLD,\
True, False, True))
self.FixIfIsEdgeTile(self.tiles[-1], mapToInitialize)
for tile in self.tiles:
tile.adjacentTiles = tile.GetAdjacent()
def FixIfIsEdgeTile(self, tile, mapToInitialize):
if tile.location.x == 0 or\
tile.location.x == len(mapToInitialize) - 1 or\
tile.location.y == 0 or\
tile.location.y == len(mapToInitialize[0]) - 1:
tile.pushable = False
def GetTile(self, atLocation):
for tile in self.tiles:
if tile.location == atLocation:
return tile
return None
def GetPlayerLocation(self):
return self.GetTile(self.player.location)
def GetCreature(self, location):
for troll in self.trolls:
if troll.location == location:
return troll
return None
def SpawnCreature(self, spawnPlayer=False):
spawnLocation = random.choice(self.spawnPoints)
if spawnPlayer:
self.player = Player(self, spawnLocation)
else:
self.trolls.append(Troll(self, spawnLocation))
def Update(self):
for troll in self.trolls:
troll.Update()
def Draw(self):
self.window.erase()
self.DrawMap()
self.DrawCreatures()
self.window.refresh()
def DrawMap(self):
for tile in self.tiles:
try:
self.window.addch(tile.location.y, tile.location.x,\
tile.graphic, curses.color_pair(tile.color)\
+ tile.attr)
except:
pass
def DrawCreatures(self):
self.window.addch(self.player.location.y, self.player.location.x,\
self.player.graphic,\
curses.color_pair(self.player.color))
for troll in self.trolls:
self.window.addch(troll.location.y, troll.location.x,\
troll.graphic, curses.color_pair(troll.color))
class Tile(object):
def __init__(self, world, location, graphic,\
color, attr, passable, pushable, isExit=False):
self.world = world
self.location = location
self.graphic = graphic
self.color = color
self.attr = attr
self.passable = passable
self.pushable = pushable
self.isExit = isExit
self.adjacentTiles = []
def Push(self, direction):
approachedTile = self.world.GetTile(self.location + direction)
if approachedTile and approachedTile.passable:
approachedTile.location = self.location
self.location += direction
self.UpdateAdjacentAfterPush(approachedTile)
if self.world.GetCreature(self.location):
self.world.GetCreature(self.location).Die()
return True
return False
def IsOccupied(self):
if self.world.player.location == self.location:
return False
for troll in self.world.trolls:
if troll.location == self.location:
return True
return False
def UpdateAdjacentAfterPush(self, approachedTile):
self.adjacentTiles = self.GetAdjacent()
approachedTile.adjacentTiles = approachedTile.GetAdjacent()
for tile in approachedTile.adjacentTiles:
tile.adjacentTiles = tile.GetAdjacent()
for tile in self.adjacentTiles:
tile.adjacentTiles = tile.GetAdjacent()
def GetAdjacent(self):
adjacent = []
GetTile = self.world.GetTile
adjacent.append(GetTile(self.location + Vector2D(0, -1)))
adjacent.append(GetTile(self.location + Vector2D(1, 0)))
adjacent.append(GetTile(self.location + Vector2D(-1, 0)))
adjacent.append(GetTile(self.location + Vector2D(0, 1)))
return adjacent
class Trollbyrinth(object):
def __init__(self, stdscr):
self.Screen = stdscr
self.Running = True
curses.curs_set(0)
curses.raw()
# SET UP COLOR PAIRS
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(4, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(5, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(6, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_GREEN)
curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_YELLOW)
curses.init_pair(9, curses.COLOR_WHITE, curses.COLOR_RED)
curses.init_pair(10, curses.COLOR_WHITE, curses.COLOR_CYAN)
curses.init_pair(11, curses.COLOR_WHITE, curses.COLOR_BLUE)
curses.init_pair(12, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
self.Loading()
# EVENTS
self.events = []
self.BindEvent(ord('q'), self.Quit)
# GAME OBJECTS
self.world = World(self, LABYRINTH)
# GAME DATA
self.Turn = 1
self.bDidWin = False
# MAIN LOOP
self.MainLoop()
def Loading(self):
self.Screen.addstr("LOADING & INITIALIZING TILES...",
curses.color_pair(2))
self.Screen.refresh()
def GameOver(self):
endGameWindow = curses.newwin(curses.LINES, curses.COLS, 0, 0)
if self.bDidWin:
endGameWindow.addstr(curses.LINES // 2, 0, "{0:^{1}}".format\
("YOU WON! CONGRATULATIONS!", curses.COLS),\
curses.color_pair(1) + curses.A_BOLD)
endGameWindow.addstr(curses.LINES // 2 + 1, 0, "{0:^{1}}".format\
("SCORE: {0}".format(self.GetScore()), curses.COLS),\
curses.color_pair(2) + curses.A_BOLD)
else:
endGameWindow.addstr(curses.LINES // 2, 0, "{0:^{1}}".format\
("GAME OVER! THE TROLLS GOT THE BETTER OF YOU!",\
curses.COLS), curses.color_pair(3) + curses.A_BOLD)
endGameWindow.getch()
def GetScore(self):
return (200 - self.Turn) + (7 - len(self.world.trolls)) * 5
def Quit(self):
self.Running = False
def BindEvent(self, key, func):
self.events.append(Event(key, func))
def HandleInput(self):
KeyboardEvent = self.world.window.getch()
for event in self.events:
if event.key == KeyboardEvent:
event()
def Update(self):
self.Screen.addstr(24, 0, "TURN: ", curses.A_BOLD)
self.Screen.addstr(str(self.Turn))
self.Screen.addstr(24, 12, "TROLLS ALIVE: ", curses.A_BOLD)
self.Screen.addstr(str(len(self.world.trolls)))
self.Screen.addstr(25, 0, "SCORE: ", curses.A_BOLD)
self.Screen.addstr(str(self.GetScore()))
self.Screen.refresh()
self.Turn += 1
def MainLoop(self):
self.world.Draw()
self.Update()
while self.Running:
self.HandleInput()
self.world.Update()
self.world.Draw()
self.Update()
self.GameOver()
if __name__ == "__main__":
curses.wrapper(Trollbyrinth)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment