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