#!/usr/bin/env python import sys import os import threading import random import kon from kon import dbg, DIRS, U, D, L, R, flush, has, set, get NO_DIE = '-m' in sys.argv (pac_buf, mon_ret_buf, warp_buf) = map( kon.rbuf, ['pac.txt', 'mon_ret.txt', 'warp.txt'] ) MONS = 'MRCY' MOVES = 'P' + MONS def pdot_update(): map( lambda (x, y): show_update(x, y), pac_buf.pos('O') ) flush() def stat_dic(c): d = {} if c == 'P': d = { 'dir': L, 'pos': pac_buf.pos('P'), 'dx': 0, 'hz': 3.0, 'th': None, 'late_tmr': kon.timer_new(), 'dot': len( pac_buf.pos('.') + pac_buf.pos('O') ), } elif c in MONS: # mon mode 'cage' 'run' 'weak' 'blink' 'ret' d = { 'dir': U, 'pos': pac_buf.pos(c), 'dx': 0, 'hz': 4.0, 'th': None, 'late_tmr': kon.timer_new(), 'mode': 'cage' if c in 'CMY' else 'run', 'timer': kon.timer_new(), 'flick': kon.flicker(), 'rl': kon.rlock() } elif c == 'O': d = { 'flick': kon.flicker( 2.0, pdot_update, sta=True ) } return d stat_cs = MOVES + 'O' stat_inf = dict( map( lambda c: ( c, kon.dic_to_cls( stat_dic(c) ) ), stat_cs ) ) stat_get = lambda c: stat_inf.get(c) stat_gets = lambda cs: map( stat_get, cs ) c_stat_gets = lambda cs: zip( cs, stat_gets(cs) ) def pac_show(c): (s, col, r) = ('()', 'yellow', True) (x, y) = stat_get(c).pos if (x + y) % 2: s = { L: '>)', R: '(<', U: '(/', D: '/)' }.get( stat_get(c).dir ) return (s, col, r) def mon_show(c): col = { 'M': 'magenta', 'R': 'red', 'C': 'cyan', 'Y': 'white' }.get(c) d = { 'run': ( 'oo', col, True ), 'weak': ( '><', 'blue', True ), 'blink_True': ( '><', 'blue', True ), 'blink_False': ( '--', 'white', True ), 'ret': ( 'oo', 'white', False ), } mode = mon_mode(c) if mode == 'blink': mode += '_' + str( stat_get(c).flick.get() ) return d.get(mode if mode in d else 'run') bit_cs = MONS + 'PO.=#' show_inf = { 'M': mon_show, 'R': mon_show, 'C': mon_show, 'Y': mon_show, 'P': pac_show, 'O': lambda c: ('O ', 'yellow', stat_get(c).flick.get()), '.': lambda c: ('. ', 'white', False), '=': lambda c: ('==', 'yellow', False), '#': lambda c: (' ', 'blue', True), } SPC = (' ', '', False) TRANS = (' ', 'trans', False) get_show_inf = lambda c: show_inf.get(c)(c) if c in show_inf else SPC def mon_price_new(): e = kon.Empty() e.v = 1 e.get = lambda: e.v def check(): if not filter( lambda c: mon_mode(c) in ('weak', 'blink'), MONS ): e.v = 1 e.check = check e.twice = lambda: set( e, 'v', e.v * 2 ) return e mon_price = mon_price_new() def mon_mode(c, v='get'): stat = stat_get(c) def update(): (x, y) = stat.pos show_update(x, y) dx = stat.dx if dx: show_update( x + dx, y ) flush() mode = stat.mode if v == 'get' or v == mode: return mode if mode == 'blink': stat.flick.stop() stat.mode = v update() if v == 'blink': stat.flick.f = update stat.flick.set_hz( stat.hz ) stat.flick.start() mon_price.check() mon_hz_rate = { 'weak': 0.3, 'blink': 0.3, 'ret': 1.5 } warp_rate = { '1': 0.8, '2': 0.6, '3': 0.4, '4': 0.3, 'L': 0.2, 'R': 0.2 } arr_xy_to_show_xy = lambda x, y: (x * 2, y + 1) # for aspect and score def c_to_show_lst(c, x, y): inf = get_show_inf(c) stat = stat_get(c) if c not in MOVES or stat.dx == 0: return [ inf ] if arr.get(x, y, c) else [ TRANS ] (px, py) = stat.pos dx = stat.dx if x not in [ px, px + dx ] or y != py: return [ TRANS ] conn = [ inf, TRANS ] if (x == px) != (dx == 1) else [ TRANS, inf ] return kon.split_show_lst( conn )[1:3] (w, h) = pac_buf.size() arr = kon.arr_xy_bit_c_show_new( w, h, bit_cs, c_to_show_lst, arr_xy_to_show_xy, (1,0) ) is_warp = lambda x, y, d: ( lambda wc: wc if wc in ('L', 'R') and wc.lower() == d else False )( warp_buf.get(x, y) ) def step_to(x, y, d): dic = { 'R': 'L', 'L': 'R' } wc = is_warp(x, y, d) return warp_buf.pos( dic.get(wc) ) if wc else kon.step_to(x, y, d) def is_step(px, py, c, d): def one_way(x, y, d): wc = warp_buf.get(x, y) return wc == d if c in MONS and wc in DIRS else None if is_warp(px, py, d): return True v = one_way(px, py, d) if v != None: return v (nx, ny) = step_to(px, py, d) v = one_way(nx, ny, d) if v != None: return v return pac_buf.get(nx, ny) not in (None, '#') next_ds = lambda x, y, c: filter( lambda d: is_step(x, y, c, d), DIRS ) def show(x, y, s, col, r): if not game_ev.get(): kon.lock_show.f(x, y, s, col, r) def shows(x, y, lst): # [ (s, col, r), ... ] def f( lst, (s, col, r) ): if lst: (s_, col_, r_) = lst[-1] if col_ == col and r_ == r: return lst[:-1] + [ (s_ + s, col_, r_) ] return lst + [ (s, col, r) ] lst = reduce( f, lst, [] ) def f_show( x, (s, col, r) ): show(x, y, s, col, r) return x + len(s) reduce( f_show, lst, x ) def show_update(x, y): if not game_ev.get(): arr.update(x, y) def show_update_line(x, y, xlen): map( lambda dx: show_update(x + dx, y), range(xlen) ) flush() def line_msg_pause(x, y, s, col, r, sec, x_cen=True): xlen = ( len(s) + 1 ) / 2 # for aspect if x_cen: x -= (xlen - 1) / 2 (w, h) = pac_buf.size() y = min( max(y, 0), h - 1 ) x = min( max(x, 0), w - 1 - xlen ) (x_, y_) = arr_xy_to_show_xy(x, y) lock_move.lock() show(x_, y_, s, col, r) flush() kon.sleep(sec) show_update_line(x, y, xlen) lock_move.unlock() def show_anime(x, y, lst, hz, restore=True, n=1): (ss, cols, rs) = zip( *lst ) xlen = max( map( lambda s: ( len(s) + 1 ) / 2, ss ) ) # for aspect (x_, y_) = arr_xy_to_show_xy(x, y) lock_move.lock() for i in range(n): for (s, col, r) in lst: show(x_, y_, s, col, r) flush() kon.sleep_hz(hz) if restore: show_update_line(x, y, xlen) lock_move.unlock() def game_ev_new(): e = kon.Empty() e.ev = ev = threading.Event() e.v = '' e.wait = ev.wait e.clear = ev.clear def set(v): e.v = v ev.set() e.set = set e.get = lambda: e.v if ev.is_set() else '' return e game_ev = game_ev_new() def meet_chk(x, y): if not arr.get(x, y, 'P'): return for c in MONS: if not arr.get(x, y, c): continue mode = mon_mode(c) if mode in ('weak', 'blink'): ss = [ '. ', 'o ', 'O ', '<>', '()', '^^' ] lst = map( lambda s: (s, 'yellow', True), ss ) show_anime(x, y, lst, 8.0, restore=False) add = mon_price.get() * 200 mon_price.twice() score(add) line_msg_pause(x, y-1, str(add), '', False, 1.0) mon_mode(c, 'ret') elif mode != 'ret' and not NO_DIE: ss = [ '()', '||', ')(', '--', '__', ' ' ] lst = map( lambda s: (s, 'yellow', True), ss ) + [ SPC ] show_anime(x, y, lst, 3.0) v = 'over' if spare(-1) == 'over' else 'die' game_ev.set(v) def get_hz(c, nx, ny): hz = stat_get(c).hz if c in MONS: hz *= mon_hz_rate.get( mon_mode(c), 1.0 ) hz *= warp_rate.get( warp_buf.get(nx, ny), 1.0 ) return hz def move(px, py, nx, ny, c): arr.set(px, py, c, False) arr.set(nx, ny, c, True) dx = nx - px if dx in (1, -1): # for warp dx = -dx stat = stat_get(c) stat.dx = dx show_update(px, py) show_update(nx, ny) def late_update(): stat.dx = 0 show_update(px, py) show_update(nx, ny) flush() sec = kon.hz_to_sec( get_hz(c, nx, ny) * 2 ) stat.late_tmr.start( late_update, [], sec ) else: show_update(px, py) show_update(nx, ny) flush() meet_chk(nx, ny) lock_move = kon.lock_wrap( move ) def mon_weak(): def tmr_f(c): mode = mon_mode(c) if mode == 'weak': mon_mode(c, 'blink') stat_get(c).timer.start( tmr_f, [c], 5.0 ) elif mode == 'blink': mon_mode(c, 'run') for c in MONS: if mon_mode(c) not in ('cage', 'ret'): mon_mode(c, 'weak') stat = stat_get(c) stat.dir = kon.bak_dir( stat.dir ) stat.timer.start( tmr_f, [c], 15.0 ) def try_eat(x, y): c = '.' if arr.get(x, y, '.') else 'O' if arr.get(x, y, 'O') else '' if not c: return arr.set(x, y, c, False) stat_get('P').dot -= 1 score( 20 if c == 'O' else 5 ) if c == 'O': mon_weak() if stat_get('P').dot <= 0: c = '#' pos = pac_buf.pos(c) pos = map( lambda (x, y): arr_xy_to_show_xy(x, y), pos ) for i in range(6): (s, col, r) = get_show_inf(c) if i % 2 else ('==', 'white', False) for (x, y) in pos: show(x, y, s, col, r) flush() kon.sleep_hz(6) game_ev.set('clear') rand_sel = lambda lst: lst[ random.randrange( len(lst) ) ] kdir = kon.gval('') def th_key(): d = kon.keydir(1.0) if d in DIRS and d != kdir(): kdir(d) def th_pac(c): stat = stat_get(c) (px, py) = stat.pos pd = stat.dir ds = next_ds(px, py, c) kd = kdir() d = kd if kd and kd in ds else pd if pd in ds else '' (nx, ny) = step_to(px, py, d) if d: stat.dir = d stat.pos = (nx, ny) lock_move.f(px, py, nx, ny, c) try_eat(nx, ny) def th_mon(c): stat = stat_get(c) (px, py) = stat.pos pd = stat.dir mode = mon_mode(c) ds = next_ds(px, py, c) d = ds[0] if mode == 'ret': d = mon_ret_buf.get(px, py) else: ds_ = filter( lambda d: d != kon.bak_dir(pd), ds ) ds = ds_ if ds_ else ds d = rand_sel(ds) (nx, ny) = step_to(px, py, d) stat.dir = d stat.pos = (nx, ny) lock_move.f(px, py, nx, ny, c) if mode == 'cage' and pac_buf.get(px, py) == '=': mon_mode(c, 'run') elif mode == 'ret' and mon_ret_buf.get(nx, ny) == ' ': mon_mode(c, 'cage') stat.th.hz = get_hz(c, nx, ny) def score_new(): (x, y, lb, col, r) = (0, 0, 'score ', 'white', False) e = kon.Empty() e.v = 0 def f(add=0): e.v += add (dx, s) = (0, lb) if add == 0 else ( len(lb), str(e.v) ) show(x + dx, y, s, col, r) return f score = score_new() def spare_new(v): (w, h) = pac_buf.size() (x, y) = arr_xy_to_show_xy(0, h) c = 'P' (s, col, r) = get_show_inf(c) e = kon.Empty() e.v = v def draw(on, i): (s_, col_, r_) = (s, col, r) if on else SPC x_ = x + i * 3 if x_ >= 0: show( x_, y, s_, col_, r_ ) def f(add=0): if add == 0: map( lambda i: draw(True, i), range(e.v) ) return '' while add > 0: draw(True, e.v) e.v += 1 add -= 1 while add < 0: e.v -= 1 draw(False, e.v) add += 1 return 'over' if e.v < 0 else '' return f spare = spare_new(2) def work(): def init_map(): (w, h) = pac_buf.size() arr.clear() def f(x, y): c = pac_buf.get(x, y) arr.set(x, y, c, True) show_update(x, y) kon.loop_xy(w, h, f) flush() def init_stat(clear=False): for c in MOVES: stat = stat_get(c) stat.dir = L if c == 'P' else U stat.pos = pac_buf.pos(c) stat.dx = 0 if c in MONS: stat.mode = 'cage' if c in 'CMY' else 'run' stat.flick.stop() if clear: stat.hz *= 1.2 stat.th.hz = stat.hz if c == 'P': stat.dot = len( pac_buf.pos('.') + pac_buf.pos('O') ) kon.th_loop(th_key) score() spare() init_map() for (c, stat) in c_stat_gets(MOVES): f = th_pac if c == 'P' else th_mon stat.th = kon.th_loop(f, [c], stat.hz, False) run = True while run: (x, y) = pac_buf.pos('B') line_msg_pause(x, y, 'READY!', 'yellow', False, 1.0) map( lambda stat: stat.th.start(), stat_gets(MOVES) ) while not game_ev.wait(1.0): kon.sleep_hz(1.0) # for ^C for stat in stat_gets(MOVES): stat.th.stop() stat.late_tmr.cancel() if has(stat, 'timer'): stat.timer.cancel() kon.sleep(1.0) v = game_ev.get() game_ev.clear() for (c, stat) in c_stat_gets(MOVES): (x, y) = stat.pos d = stat.dir arr.set(x, y, c, False) show_update(x, y) map( lambda d: show_update( *step_to( x, y, d ) ), DIRS ) flush() if v == 'die': init_stat() elif v == 'clear': init_stat(clear=True) init_map() elif v == 'over': run = False if __name__ == "__main__": kon.main( work, '[-bw] [-m]' ) # EOF