continuity

curses game inspired by continuitygame.com
git clone https://a3nm.net/git/continuity/
Log | Files | Refs | README

continuity.py (12828B)


      1 #!/usr/bin/python3
      2 # continuity -- a curses game inspired by continuitygame.com
      3 # by Antoine Amarilli
      4 # licence: public domain
      5 
      6 import sys
      7 import curses
      8 
      9 class Square:
     10   wall, ladder, empty, exit, player, blank, key = range(7)
     11   asciiMap = {wall: '#', ladder:'%', empty:' ', exit:'^', player:'@',
     12       blank:'!', key:'*'}
     13 
     14   def __init__(self, parent, type, pos):
     15     self.parent = parent
     16     self.type = type
     17     self.pos = pos
     18 
     19   def getNeighbour(self, dir):
     20     return self.parent.getSquare((self.pos[0] + dir[0], self.pos[1] + dir[1]))
     21 
     22   def getType(self):
     23     return self.type
     24 
     25   def setType(self, type):
     26     self.type = type
     27 
     28   def render(self, pad, x, y):
     29     if (self.parent.parent.getPlayer().getPos() == self):
     30       self.parent.parent.getPlayer().setGraphPos((x + self.pos[0],y +
     31         self.pos[1]))
     32       c = '@'
     33       mode = curses.A_BOLD + curses.A_BLINK + curses.color_pair(1)
     34     else:
     35       if (self.parent.parent.getPlayer().getPos().getParent() == self.parent):
     36         mode = curses.A_BOLD
     37       else:
     38         mode = 0
     39       c = Square.asciiMap[self.type]
     40       if (c == '^'):
     41         if (self.parent.parent.keysLeft() == True):
     42           mode = curses.A_BOLD + curses.color_pair(8)
     43         else:
     44           mode = curses.A_BOLD + curses.color_pair(2)
     45       if (c == '%'): mode = curses.color_pair(3)
     46       if (c == '#'): mode = curses.color_pair(4)
     47       if (c == '!'):
     48         mode = curses.color_pair(5)
     49         pad.addch( x + self.pos[0],y + self.pos[1], c, mode)
     50         c = ' '
     51       if (c == '*'): mode = curses.A_BOLD + curses.color_pair(2)
     52     try:
     53       pad.addch( x + self.pos[0],y + self.pos[1], c, mode)
     54     except curses.error:
     55       pass
     56 
     57   def getParent(self):
     58     return self.parent
     59 
     60 
     61 class Block:
     62   left, right, top, bottom = range(4)
     63   def __init__(self, parent, h, w, pos):
     64     self.parent = parent
     65     self.h = h
     66     self.w = w
     67     self.pos = pos
     68     self.map = []
     69     for i in range(0, h):
     70       self.map.append([])
     71       for j in range(0, w):
     72         self.map[i].append(Square(self, 0, (0, 0)))
     73 
     74   def getBorder(self, t):
     75     a = []
     76     if (t == Block.left):
     77       for i in range(0, self.h):
     78         a.append(self.getSquare((i,0)).getType())
     79     if (t == Block.right):
     80       for i in range(0, self.h):
     81         a.append(self.getSquare((i, self.w-1)).getType())
     82     if (t == Block.top):
     83       for i in range(0, self.w):
     84         a.append(self.getSquare((0, i)).getType())
     85     if (t == Block.bottom):
     86       for i in range(0, self.w):
     87         a.append(self.getSquare((self.h-1, i)).getType())
     88     return a
     89 
     90   def getNeighbour(self, dir):
     91     return self.parent.getBlock((self.pos[0] + dir[0], self.pos[1] + dir[1]))
     92 
     93   def setSquare(self, x, y, s):
     94     self.map[x][y] = s
     95 
     96   def getSquare(self, p):
     97     x = p[0]
     98     y = p[1]
     99     if (x < 0):
    100       a = self.getNeighbour((-1, 0))
    101       if (self.getBorder(Block.top) != a.getBorder(Block.bottom)):
    102         return BlockSentry(self.parent, 0, 0, (0, 0)).getSquare((0,0))
    103       return a.getSquare((a.getH() + p[0], p[1]))
    104     elif (y < 0):
    105       a = self.getNeighbour((0, -1))
    106       if (self.getBorder(Block.left) != a.getBorder(Block.right)):
    107         return BlockSentry(self.parent, 0, 0, (0, 0)).getSquare((0,0))
    108       return a.getSquare((p[0], a.getW() + p[1]))
    109     elif (x >= self.getH()):
    110       a = self.getNeighbour((1, 0))
    111       if (self.getBorder(Block.bottom) != a.getBorder(Block.top)):
    112         return BlockSentry(self.parent, 0, 0, (0, 0)).getSquare((0,0))
    113       return a.getSquare((p[0] - self.getH(), p[1]))
    114     elif (y >= self.getW()):
    115       a = self.getNeighbour((0, 1))
    116       if (self.getBorder(Block.right) != a.getBorder(Block.left)):
    117         return BlockSentry(self.parent, 0, 0, (0, 0)).getSquare((0,0))
    118       return a.getSquare((p[0], p[1] - self.getW()))
    119     else:
    120       return self.map[x][y]
    121 
    122   def getW(self):
    123     return self.w
    124 
    125   def getH(self):
    126     return self.h
    127 
    128   def render(self, pad, x, y):
    129     for i in self.map:
    130       for j in i:
    131         j.render(pad, x, y)
    132 
    133   def loadFromFile(self, f):
    134     for i in range(0, self.getH()):
    135       l = f.readline()
    136       for j in range(0, self.getW()):
    137         x = l[j]
    138         c = {v:k for k, v in Square.asciiMap.items()}[x]
    139         if (x == '*'):
    140           self.parent.addKey()
    141         if (x == '@'):
    142           c = Square.empty
    143         self.map[i][j] = Square(self, c, (i, j))
    144         if (x == '@'):
    145           self.parent.getPlayer().setPos(self.map[i][j])
    146 
    147 
    148   def move(self, p):
    149     self.pos = (self.pos[0] + p[0], self.pos[1] + p[1])
    150 
    151 
    152 
    153 class BlockSentry(Block):
    154   def getSquare(self, p):
    155     return Square(self, Square.blank, (0,0))
    156 
    157   def render(self, pad, x, y):
    158     for i in range(0, self.h):
    159       for j in range(0, self.w):
    160         (Square(self, Square.blank, (i,j))).render(pad, x, y)
    161 
    162 class Player:
    163   keyMap = {(0, -1): 'h', (1, 0):'j', (-1, 0):'k', (0, 1): 'l'}
    164 
    165   def __init__(self, parent, square):
    166     self.parent = parent
    167     self.pos = square
    168     self.graphPos = (0,0)
    169 
    170   def render(self, pad):
    171     pad.move(self.graphPos[0], self.graphPos[1])
    172 
    173   def setGraphPos(self, p):
    174     self.graphPos = p
    175 
    176   def move(self, direction, volont):
    177     a = self.pos.getNeighbour(direction)
    178     if (a.getType() != Square.wall and a.getType() != Square.blank):
    179       self.pos = a
    180       self.parent.addDist()
    181       if (volont == True): self.parent.addMoves()
    182     if (self.pos.getType() == Square.key):
    183       self.parent.removeKey()
    184       self.pos.setType(Square.empty)
    185     if (self.pos.getType() == Square.exit and not self.parent.keysLeft()):
    186       return True
    187     else:
    188       return self.fall()
    189 
    190   def getPos(self):
    191     return self.pos
    192 
    193   def setPos(self, pos):
    194     self.pos = pos
    195 
    196   def fall(self):
    197     a = self.pos.getType()
    198     b = self.pos.getNeighbour((1,0)).getType() 
    199     while (self.pos.getType() != Square.ladder and (
    200       self.pos.getNeighbour((1,0)).getType() == Square.empty or
    201       self.pos.getNeighbour((1,0)).getType() == Square.key or
    202       self.pos.getNeighbour((1,0)).getType() == Square.exit)):
    203       if self.move((1, 0), 0):
    204         return True
    205     return False
    206 
    207 
    208 class Level:
    209   codeMap = {104: 'h', 106:'j', 107:'k', 108: 'l', 113: 'q', 72: 'H',
    210       74:'J', 75: 'K', 76:'L', curses.KEY_LEFT:'h', curses.KEY_DOWN:'j',
    211       curses.KEY_UP:'k', curses.KEY_RIGHT:'l', 97: 'H', 115: 'J',
    212       119: 'K', 100: 'L'}
    213   def __init__(self, h, w, x, y):
    214     self.h = h
    215     self.w = w
    216     self.holeX = x
    217     self.holeY = y
    218     self.map = []
    219     self.keys = 0
    220     self.cswap = 0
    221     self.cmove = 0
    222     self.cdist = 0
    223     for i in range(0, h):
    224       self.map.append([])
    225       for j in range(0, w):
    226         self.map[i].append(Block(0, 0, 0, 0))
    227     self.player = Player(self, Square(0, 0, (0,0)))
    228 
    229   def loadFromFile(self, f):
    230     bh = int(f.readline())
    231     bw = int(f.readline())
    232     for i in range(0, self.h):
    233       for j in range(0, self.w):
    234         if ((i, j) != (self.holeX, self.holeY)):
    235           self.map[i][j] = Block(self, bh, bw, (i, j))
    236         else:
    237           self.map[i][j] = BlockSentry(self, bh, bw, (i, j))
    238         self.map[i][j].loadFromFile(f)
    239 
    240   def addKey(self):
    241     self.keys+=1
    242 
    243   def addDist(self):
    244     self.cdist += 1
    245 
    246   def addMoves(self):
    247     self.cmove += 1
    248 
    249   def addSwap(self):
    250     self.cswap += 1
    251 
    252   def removeKey(self):
    253     self.keys-=1
    254 
    255   def keysLeft(self):
    256     return self.keys != 0
    257 
    258   def getPlayer(self):
    259     return self.player
    260 
    261   def render(self, pad):
    262     m = 0
    263     for i in range(0, self.w):
    264       m = m + self.map[0][i].getW() + 1
    265     for i in range(0, m+1):
    266       pad.addch( 0 ,i, '\'', curses.color_pair(5))
    267       pad.addch( 0 ,i, ' ', curses.color_pair(5))
    268     x = 0
    269     for j in range(0, self.h):
    270       y = 0
    271       for i in range(0, self.map[0][j].getH()+1):
    272         pad.addch( j*(self.map[0][j].getH()+1)+1 +i ,0, ' ', curses.color_pair(5))
    273       for i in range(0, self.w):
    274         a = self.map[j][i]
    275         a.render(pad, j*a.getH()+1+j, i*a.getW()+1+i)
    276         l = a.getBorder(Block.bottom)
    277         l2 = a.getNeighbour((1, 0)).getBorder(Block.top)
    278         if (l != l2):
    279           for k in range(0, a.getW()+1):
    280             if (l2 != [] and l != [] and l[k-1] != l2[k-1]):
    281               pad.addch(x+self.map[0][j].getH() +1,y+k,
    282                   '?', curses.color_pair(5))
    283             else:
    284               pad.addch( x+self.map[0][j].getH() +1,y+k,
    285                   '\'', curses.color_pair(5))
    286               pad.addch( x+self.map[0][j].getH() +1,y+k,
    287                   ' ', curses.color_pair(5))
    288         else:
    289           for k in range(0, a.getW()):
    290             if (l[k] == Square.wall):
    291               pad.addch( x+self.map[0][j].getH()+1,y+k+1,
    292                   '|', curses.color_pair(6))
    293             else:
    294               pad.addch( x+self.map[0][j].getH()+1,y+k+1,
    295                   '\'', curses.color_pair(7))
    296               pad.addch( x+self.map[0][j].getH()+1,y+k+1,
    297                   ' ', curses.color_pair(7))
    298         y = y + a.getW() + 1
    299         l = a.getBorder(Block.right)
    300         l2 = a.getNeighbour((0, 1)).getBorder(Block.left)
    301         if (l != l2):
    302           for k in range(0, a.getH()+1):
    303             if (l2 != [] and l != [] and l[k-1] != l2[k-1]):
    304               pad.addch( x+k ,y, '?', curses.color_pair(5))
    305             else:
    306               pad.addch( x+k ,y, '\'', curses.color_pair(5))
    307               pad.addch( x+k ,y, ' ', curses.color_pair(5))
    308         else:
    309           for k in range(0, a.getH()):
    310             if (l[k] == Square.wall):
    311               pad.addch( x+k+1 ,y, '-', curses.color_pair(6))
    312             else:
    313               pad.addch( x+k+1 ,y, '\'', curses.color_pair(7))
    314               pad.addch( x+k+1 ,y, ' ', curses.color_pair(7))
    315       for k in range(0, a.getH()+1):
    316         pad.addch( x+k ,y, '\'', curses.color_pair(5))
    317         pad.addch( x+k ,y, ' ', curses.color_pair(5))
    318       x = x + self.map[0][j].getH() + 1
    319     m = 0
    320     for i in range(0, self.h):
    321       m = m + self.map[i][0].getH() + 1
    322     for i in range(0, y+1):
    323       pad.addch( m ,i, '\'', curses.color_pair(5))
    324       pad.addch( m ,i, ' ', curses.color_pair(5))
    325     x = 0
    326     for j in range(0, self.h+1):
    327       y = 0
    328       for i in range(0, self.w+1):
    329         pad.addch(x, y, '+', curses.color_pair(9))
    330         y += self.getBlock((min(j, self.h-1), i)).getW() + 1;
    331       x += self.getBlock((j, self.w-1)).getH() + 1;
    332 
    333     pad.addstr(self.scoreRep())
    334     self.player.render(pad)
    335     pad.refresh()
    336 
    337   def scoreRep(self):
    338     return "Moves: %3d\nDist: %4d\nSwaps: %3d" % (
    339         self.cmove, self.cdist, self.cswap )
    340 
    341   def getH(self):
    342     return self.h
    343 
    344   def getW(self):
    345     return self.w
    346 
    347   def getBlock(self, p):
    348     x = p[0]
    349     y = p[1]
    350     if (x < 0 or y < 0 or x >= self.getH() or y >= self.getW()):
    351       return BlockSentry(self, 0, 0, (0,0))
    352     else:
    353       return self.map[x][y]
    354 
    355   def move(self, p):
    356     p2 = (-p[0], -p[1])
    357     tx = p2[0]+self.holeX
    358     ty = p2[1]+self.holeY
    359     t = self.getBlock((tx, ty))
    360     if (type(t) != BlockSentry):
    361       self.map[tx][ty] = self.map[self.holeX][self.holeY]
    362       self.map[self.holeX][self.holeY] = t
    363       t.move(p)
    364       self.map[tx][ty].move(p2)
    365       self.holeX = tx
    366       self.holeY = ty
    367       self.addSwap()
    368     return self.player.fall()
    369 
    370 if (len(sys.argv) != 2):
    371   print("Usage: %s LEVEL" % sys.argv[0])
    372   sys.exit(1)
    373 
    374 try:
    375   f = open(sys.argv[1])
    376 except IOError as e:
    377   print("Cannot access %s: %s" % (sys.argv[1], e.strerror))
    378   sys.exit(1)
    379 
    380 
    381 
    382 stdscr = curses.initscr()
    383 curses.noecho()
    384 
    385 h = int(f.readline())
    386 w = int(f.readline())
    387 x = int(f.readline())
    388 y = int(f.readline())
    389 l = Level(h, w, x, y)
    390 l.loadFromFile(f)
    391 
    392 pad = curses.newwin(h*(l.getBlock((0,0)).getH()+1)+4,
    393     w*(l.getBlock((0,0)).getW()+1)+1)
    394 
    395 curses.start_color()
    396 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
    397 curses.init_pair(2, curses.COLOR_RED, curses.COLOR_YELLOW)
    398 curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK)
    399 curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLUE)
    400 curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_RED)
    401 curses.init_pair(6, curses.COLOR_RED, curses.COLOR_BLUE)
    402 curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK)
    403 curses.init_pair(8, curses.COLOR_YELLOW, curses.COLOR_BLACK)
    404 curses.init_pair(9, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA)
    405 
    406 stdscr.keypad(1)
    407 
    408 exit = False
    409 won = False
    410 
    411 stdscr.refresh()
    412 
    413 while (not exit):
    414   l.render(pad)
    415   try: input = Level.codeMap[stdscr.getch()]
    416   except KeyError:
    417     input = ''
    418     pass
    419   if (input == 'q'):
    420     exit = True
    421   elif (input == input.upper()):
    422     try: won = l.move({v:k for k, v in Player.keyMap.items()}[input.lower()])
    423     except KeyError: pass
    424   else:
    425     try: won = l.getPlayer().move({v:k for k, v in 
    426       Player.keyMap.items()}[input], 1)
    427     except KeyError: pass
    428   exit = exit or won
    429 
    430 curses.nocbreak(); stdscr.keypad(0); curses.echo()
    431 
    432 curses.endwin()
    433 
    434 if (won):
    435   print ("Congratulations, you won!\n")
    436   print ("Your score was:")
    437   print (l.scoreRep())
    438 else: print ("Bye.")
    439