import fcntl import os import random import select import struct import sys import termios import tty DIRS = { '\x1b[A': (0, -1, [(j, i) for i in range(4) for j in range(4)]), '\x1b[B': (0, 1, [(j, i) for i in reversed(range(4)) for j in range(4)]), '\x1b[C': (1, 0, [(i, j) for i in reversed(range(4)) for j in range(4)]), '\x1b[D': (-1, 0, [(i, j) for i in range(4) for j in range(4)]), } CELL = 6 SIZE = 4 BOARD = {} class Number: def __init__(self, i=None, new=1): if i is None: i = 4 if random.random() > 0.9 else 2 self.i = i self.new = new def __unicode__(self): attrs = [] if self.new: attrs.append(self.new) if self.i > 2000: attrs.append(5) if self.i > 1000: attrs.append(31) elif self.i > 100: attrs.append(35) elif self.i > 10: attrs.append(33) text = '\033[%sm%d\033[m' % (';'.join(map(str, attrs)), self.i) pad = CELL - len(str(self.i)) return u'%s%s%s' % (' ' * (pad - pad / 2), text, ' ' * (pad / 2)) def reset(): BOARD.clear() add() add() def add(): free = set([(i, j) for i in range(4) for j in range(4)]) - set(BOARD) BOARD[random.choice(list(free))] = Number(new=4) def move(key): dx, dy, order = DIRS[key] dirty = False for num in BOARD.values(): num.new = 0 for cell in order: if cell not in BOARD: continue next = cell while True: x, y = next x += dx y += dy if x not in range(4) or y not in range(4): break if (x, y) in BOARD: if BOARD[x,y].i == BOARD[cell].i and not BOARD[x,y].new: next = (x, y) BOARD[cell] = Number(BOARD[cell].i * 2) break next = (x, y) if next != cell: BOARD[next] = BOARD.pop(cell) dirty = True if dirty: add() def show(f): winsize = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0\0\0\0') height, width = struct.unpack('HH', winsize[:4]) total = (CELL + 1) * SIZE + 1 template = '\033[1;36m\033[%%d;%dH%%s\033[m' % (width - total + 1) lines = SIZE * 4 + 1 f.write('\033[s') for i in range(lines): if i % 4 == 0: part = [u'\u2501' * CELL] * SIZE if i / 4 == 0: line = u'\u250f%s\u2513' % u'\u2533'.join(part) elif i / 4 == SIZE: line = u'\u2517%s\u251b' % u'\u253b'.join(part) else: line = u'\u2523%s\u252b' % u'\u254b'.join(part) else: part = map(unicode, ((None if i % 2 else BOARD.get((j, i / 4))) or u' ' * CELL for j in range(-1, SIZE + 1))) line = u'\033[1;36m\u2503\033[m'.join(part).strip() f.write(template % (i + 1, line)) line = (' \033[1;36mR\033[m \033[1mto reset' + (' ' * (total - 21)) + '\033[1;36mQ\033[m \033[1mto quit ') f.write(template % (lines + 1, line)) f.write('\033[u') f.flush() fd = sys.stdin.fileno() attr = termios.tcgetattr(fd) tty.setcbreak(fd) fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) reset() try: while True: show(sys.stdout) select.select([sys.stdin], [], []) key = sys.stdin.read() if key in 'qQ\x1b': break if key in 'rR': reset() if key in DIRS: move(key) finally: termios.tcsetattr(fd, termios.TCSAFLUSH, attr)