mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-29 10:12:41 +01:00
316122c4e0
Summary: Phabricator, as we all know, is marketed as a fun adventure game. However, while it is occasionally fun and often an adventure, it's so far been sorely deficient in the game aspect. This patch aims to rectify that oversight. (Presence of the first two qualities is not guaranteed.) Note: In case there's any doubt, this is not a serious suggestion. I was bored. Test Plan: Seriously? Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Differential Revision: https://secure.phabricator.com/D3026
202 lines
6 KiB
Python
Executable file
202 lines
6 KiB
Python
Executable file
#!/usr/bin/python
|
|
import sys
|
|
import time
|
|
import select
|
|
import curses
|
|
import curses.wrapper
|
|
|
|
entities = []
|
|
grid = []
|
|
|
|
class Wall:
|
|
def collide(self, ball):
|
|
return False
|
|
|
|
class Block:
|
|
killed = 0
|
|
total = 0
|
|
|
|
def __init__(self, x, y, w, h, c):
|
|
self.x = x
|
|
self.y = y
|
|
self.w = w
|
|
self.h = h
|
|
self.fmt = curses.A_BOLD | curses.color_pair(c)
|
|
self.alive = True
|
|
for i in range(self.x, self.x + self.w):
|
|
for j in range(self.y, self.y + self.h):
|
|
grid[j + 1][i + 1] = self
|
|
Block.total += 1
|
|
|
|
def collide(self, ball):
|
|
self.alive = False
|
|
for i in range(self.x, self.x + self.w):
|
|
for j in range(self.y, self.y + self.h):
|
|
grid[j + 1][i + 1] = None
|
|
Block.killed += 1
|
|
return False
|
|
|
|
def tick(self, win):
|
|
if self.alive:
|
|
for i in range(self.x, self.x + self.w):
|
|
for j in range(self.y, self.y + self.h):
|
|
win.addch(j, i, curses.ACS_BLOCK, self.fmt)
|
|
return self.alive
|
|
|
|
class Ball:
|
|
alive = False
|
|
killed = 0
|
|
|
|
def __init__(self, x, y, vx, vy):
|
|
self.x = x
|
|
self.y = y
|
|
self.vx = vx
|
|
self.vy = vy
|
|
Ball.alive = True
|
|
|
|
def collide(self, ball):
|
|
return True
|
|
|
|
def encounter(self, dx, dy):
|
|
ent = grid[self.y + dy + 1][self.x + dx + 1]
|
|
if ent and not ent.collide(self):
|
|
self.vx -= 2 * dx
|
|
self.vy -= 2 * dy
|
|
return ent
|
|
|
|
def tick(self, win):
|
|
while self.y < ship.y:
|
|
if self.encounter((self.vx + self.vy) / 2, (self.vy - self.vx) / 2):
|
|
continue
|
|
if self.encounter((self.vx - self.vy) / 2, (self.vy + self.vx) / 2):
|
|
continue
|
|
if self.encounter(self.vx, self.vy):
|
|
continue
|
|
break
|
|
self.x += self.vx
|
|
self.y += self.vy
|
|
try:
|
|
win.addch(self.y, self.x, 'O')
|
|
except curses.error:
|
|
Ball.alive = False
|
|
Ball.killed += 1
|
|
return Ball.alive
|
|
|
|
class Ship:
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
self.hw = 10
|
|
self.v = 4
|
|
self.last = 1
|
|
self.update()
|
|
|
|
def update(self):
|
|
grid[self.y + 1] = (
|
|
[ None ] * (self.x - self.hw + 1) +
|
|
[ self ] * (self.hw * 2 + 1) +
|
|
[ None ] * (width - self.x - self.hw)
|
|
)
|
|
|
|
def collide(self, ball):
|
|
ball.vy = -1
|
|
if ball.x > self.x + self.hw / 2:
|
|
ball.vx = 1
|
|
elif ball.x < self.x - self.hw / 2:
|
|
ball.vx = -1
|
|
return True
|
|
|
|
def shift(self, i):
|
|
self.last = i
|
|
self.x += self.v * i
|
|
if self.x - self.hw < 0:
|
|
self.x = self.hw
|
|
elif self.x + self.hw >= width:
|
|
self.x = width - self.hw - 1
|
|
self.update()
|
|
|
|
def spawn(self):
|
|
if not Ball.alive:
|
|
entities.append(Ball(self.x, self.y - 1, self.last, -1))
|
|
|
|
def tick(self, win):
|
|
if not Ball.alive:
|
|
win.addch(self.y - 1, self.x, 'O')
|
|
win.addch(self.y, self.x - self.hw, curses.ACS_LTEE)
|
|
for i in range(-self.hw + 1, self.hw):
|
|
win.addch(curses.ACS_HLINE)
|
|
win.addch(curses.ACS_RTEE)
|
|
return True
|
|
|
|
def main(stdscr):
|
|
global height, width, ship
|
|
|
|
for i in range(1, 8):
|
|
curses.init_pair(i, i, 0)
|
|
curses.curs_set(0)
|
|
curses.raw()
|
|
height, width = stdscr.getmaxyx()
|
|
status = curses.newwin(1, width, 0, 0)
|
|
height -= 1
|
|
game = curses.newwin(height, width, 1, 0)
|
|
game.nodelay(1)
|
|
game.keypad(1)
|
|
|
|
grid[:] = [ [ None for x in range(width + 2) ] for y in range(height + 2) ]
|
|
wall = Wall()
|
|
for x in range(width + 2):
|
|
grid[0][x] = wall
|
|
for y in range(height + 2):
|
|
grid[y][0] = grid[y][-1] = wall
|
|
ship = Ship(width / 2, height - 5)
|
|
entities.append(ship)
|
|
|
|
colors = [ 1, 3, 2, 6, 4, 5 ]
|
|
h = height / 10
|
|
for x in range(1, width / 7 - 1):
|
|
for y in range(1, 7):
|
|
entities.append(Block(x * 7, y * h + x / 2 % 2, 7, h, colors[y - 1]))
|
|
|
|
while True:
|
|
while select.select([ sys.stdin ], [], [], 0)[0]:
|
|
key = game.getch()
|
|
if key == curses.KEY_LEFT or key == ord('a') or key == ord('A'):
|
|
ship.shift(-1)
|
|
elif key == curses.KEY_RIGHT or key == ord('d') or key == ord('D'):
|
|
ship.shift(1)
|
|
elif key == ord(' '):
|
|
ship.spawn()
|
|
elif key == 0x1b or key == 3 or key == ord('q') or key == ord('Q'):
|
|
return
|
|
|
|
game.resize(height, width)
|
|
game.erase()
|
|
entities[:] = [ ent for ent in entities if ent.tick(game) ]
|
|
|
|
status.hline(0, 0, curses.ACS_HLINE, width)
|
|
status.addch(0, 2, curses.ACS_RTEE)
|
|
status.addstr(' SCORE: ', curses.A_BOLD | curses.color_pair(4))
|
|
status.addstr('%s/%s ' % (Block.killed, Block.total), curses.A_BOLD)
|
|
status.addch(curses.ACS_VLINE)
|
|
status.addstr(' DEATHS: ', curses.A_BOLD | curses.color_pair(4))
|
|
status.addstr('%s ' % Ball.killed, curses.A_BOLD)
|
|
status.addch(curses.ACS_LTEE)
|
|
|
|
if Block.killed == Block.total:
|
|
message = ' A WINNER IS YOU!! '
|
|
i = int(time.time() / 0.8)
|
|
for x in range(width):
|
|
for y in range(6):
|
|
game.addch(height / 2 + y - 3 + (x / 8 + i) % 2, x,
|
|
curses.ACS_BLOCK,
|
|
curses.A_BOLD | curses.color_pair(colors[y]))
|
|
game.addstr(height / 2, (width - len(message)) / 2, message,
|
|
curses.A_BOLD | curses.color_pair(7))
|
|
|
|
game.refresh()
|
|
status.refresh()
|
|
time.sleep(0.05)
|
|
|
|
curses.wrapper(main)
|
|
print ('You destroyed %s blocks out of %s with %s deaths.' %
|
|
(Block.killed, Block.total, Ball.killed))
|