パックマンもどき2017秋

2020/NOV/30

更新履歴
日付 変更内容
2017秋 新規作成
2018年代 色々追加
2020/OCT/25 お写真
2020/NOV/30 第8弾

目次

風が秋めいてきた今日この頃に、遠い昔の記憶。 コンピュータクラブの学園祭展示の準備に追われたあの頃。

テテリス に続き、 8ビットCPUのパソコンで、 キャラクタ端末で動作する「パックマンもどき」をC言語で書いてました。

頑張って思い出して、Pythonで書いてみました。

例によってクロージャ多用なソースになってます。

まだまだ動きが怪しいので、ぼちぼち改良してみます。


お写真

スマホでページを開いて、動画の画面コピーをとり、 画質を調整してみました。

キャラクタ端末で、エスケープシーケンスを使って、 「もどき」な画面なのに、とってもそれらしい感じです。

ページを 簡易なおれおれマークダウン 2019秋の使用例 の形式で書き直してみたついでに、追加してみました。

お写真


第1弾

$ chmod +x pac.py
$ tar xzf pac_data.tgz
$ ./pac.py


第2弾

v2.patch
diff -ur v1/kon.py v2/kon.py
--- v1/kon.py	Fri Oct  6 01:40:24 2017
+++ v2/kon.py	Fri Oct  6 22:00:00 2017
@@ -68,6 +68,8 @@
 	e.v = init_v
 	return lambda v='get': e.v if v == 'get' else setattr(e, 'v', init_v if v == 'reset' else v)

+cond_wrap = lambda cond_f, targ_f: ( lambda *arg: targ_f(*arg) if cond_f() else None )
+
 def rlock(rl=None):
 	if not rl:
 		rl = threading.RLock()
@@ -77,18 +79,21 @@
 	e.unlock = rl.release
 	return e

-def lock_wrap(f, rl=None):
+def lock_wrap(f, rl=None, cond_f=None):
 	if not rl:
 		rl = rlock()
 	e = extends(rl)
+	e.cond_f = cond_f if cond_f else lambda: True
 	def wrap_f(*args):
 		rl.lock()
-		r = f(*args)
+		r = f(*args) if e.cond_f() else None
 		rl.unlock()
 		return r
 	e.f = wrap_f
 	return e

+updiv = lambda a, b: (a + b - 1) / b
+
 hz_to_sec = lambda hz: 1.0 / hz if hz > 0 else 0
 sec_to_hz = lambda sec: 1.0 / sec if sec > 0 else 0

@@ -131,11 +136,19 @@
 	e.cancel = cancel
 	return e

-def th_loop(f, args=[], hz=0, sta=True):
+def evt():
+	ev = threading.Event()
+	e = extends(ev)
+	e.v = ''
+	e.set = lambda v: ( set(e, 'v', v), ev.set() )[-1]
+	e.get = lambda: e.v if ev.is_set() else ''
+	return e
+
+def th_loop(f, args=[], hz=0, run=True):
 	e = Empty()
 	e.hz = hz
-	e.run = sta
-	ev = threading.Event()
+	e.run = run
+	ev = evt()
 	def loop():
 		while not ev.wait( hz_to_sec(e.hz) ):
 			# sec == 0 when hz == 0
@@ -143,21 +156,24 @@
 				f( *args )
 	th = threading.Thread( target=loop )
 	th.daemon = True
+
 	e.stop = lambda: set(e, 'run', False)
 	e.start = lambda: set(e, 'run', True)
-	e.kill = ev.set
+	e.kill = lambda: ev.set('kill')
 	e.join = th.join
 	e.kill_join = lambda: ( e.kill(), e.join() )[-1]
+
 	th.start()
 	return e

-def flicker(hz=1.0, f=None, args=[], sta=False):
+def flicker(hz=1.0, f=None, args=[], run=False):
 	def th_f():
 		set(e, 'v', not e.v)
 		if has(e, 'f'):
 			e.f( *e.args )

-	e = extends( th_loop(th_f, [], hz*2, sta) )
+	#e = extends( th_loop(th_f, [], hz*2, run) )
+	e = extends( th_loop(th_f, [], hz, run) )
 	e.v = False
 	e.get = lambda: e.v
 	e.f = f
@@ -189,7 +205,8 @@

 DIRS = ( U, D, L, R ) = 'udlr'
 bak_dir = lambda d: { U: D, D: U, L: R, R: L }.get(d)
-step_to = lambda x, y, d: { U: (x, y-1), D: (x, y+1), L: (x-1, y), R: (x+1, y) }.get( d, (x, y) )
+dir_to_xy = lambda d: { U: (0, -1), D: (0, 1), L: (-1, 0), R: (1, 0) }.get(d, (0, 0) )
+step_to = lambda x, y, d: ( lambda (dx, dy): (x+dx, y+dy) )( dir_to_xy(d) )

 def keydir(tmout=None):
 	ctl = lambda s: chr( 1 + ord(s) - ord('a') )
@@ -227,7 +244,7 @@
 	if v in lst:
 		out( esc( '{}m'.format( 30 + lst.index(v) ) ) )

-def show(x, y, s, col, r):
+def show_inner(x, y, s, col, r, flush_f):
 	loc(x, y)
 	rev(r)
 	nx = x + len(s)
@@ -236,9 +253,13 @@
 	else:
 		color(col)
 		out(s)
+	if flush_f:
+		flush()
 	return nx

-lock_show = lock_wrap( show )
+lock_show = lock_wrap( show_inner )
+
+show = lambda x, y, s, col, r, flush_f=False: lock_show.f(x, y, s, col, r, flush_f)

 def sum_show_lst(lst):
 	# lst = [ (s, col, r), (s, col, r) ... ]
@@ -253,7 +274,18 @@

 split_show_lst = lambda lst: sum( map( lambda (s, col, r): map( lambda c: (c, col, r), s ), lst ), [] )

-lock_shows = lambda x, y, lst: reduce( lambda x, (s, col, r): lock_show.f(x, y, s, col, r), sum_show_lst(lst), x )
+show_lst_len = lambda lst: sum( map( lambda (s, col, r): len(s), lst ) )
+
+def fit_show_lsts(show_lsts):
+	ns = map( show_lst_len, show_lsts )
+	max_n = max(ns)
+	return map( lambda (lst, n): sum_show_lst( lst + [ (' ', '', False) ] * (max_n - n) ), zip( show_lsts, ns ) )
+
+def shows(x, y, lst, flush_f=False):
+	x = reduce( lambda x, (s, col, r): show(x, y, s, col, r), sum_show_lst(lst), x )
+	if flush_f:
+		flush()
+	return x

 def over_show_lst(l, h):
 	get_col = lambda (s, col, r): col
@@ -277,14 +309,26 @@
 		w += 1
 	return w

+def bin_search(s, e, f, v):
+	sv = f(s)
+	ev = f(e)
+	while e - s > 0:
+		m = (s + e) / 2
+		mv = f(m)
+		(s, e, sv, ev) = (s, m, sv, mv) if mv < v else (m, e, mv, ev)
+	return s
+
 loop_xy = lambda w, h, f: sum( map( lambda y: map( lambda x: f(x, y), range(w) ), range(h) ), [] )
+loop_xywh = lambda x, y, w, h, f: loop_xy( w, h, lambda dx, dy: f(x+dx, y+dy) )

-lst_xy = lambda w ,h: loop_xy( w, h, lambda x, y: (x, y) )
+lst_xy = lambda w, h: loop_xy( w, h, lambda x, y: (x, y) )
+lst_xywh = lambda x, y, w, h: loop_xywh( x, y, w, h, lambda x, y: (x, y) )

 chk_xy = lambda x, y, w, h: x in range(w) and y in range(h)
+chk_xywh = lambda x, y, sx, sy, w, h: chk_xy(x-sx, y-sy)

-into_x = lambda x, iw, ow: max( min( ow - iw, x ), 0 )
-into_xy = lambda x, y, iw, ih, ow, oh: ( into_x(x, iw, ow), into_x(y, ih, oh) )
+into_x = lambda x, w, ow: max( min( ow - w, x ), 0 )
+into_xy = lambda x, y, w, h, ow, oh: ( into_x(x, w, ow), into_x(y, h, oh) )

 def arr_xy_new(w, h, v):
 	e = Empty()
@@ -306,7 +350,8 @@
 	arr = arr_xy_new(w, h, 0)
 	e = extends(arr)
 	e.get = lambda x, y, bit: arr.get(x, y, 0) & pat(bit)
-	e.set = lambda x, y, bit, bv: arr.set( x, y, chg( arr.get(x, y), bit, bv ) )
+	e.set_ = lambda x, y, bit, bv: arr.set( x, y, chg( arr.get(x, y), bit, bv ) )
+	e.set = lock_wrap( e.set_ ).f
 	e.clear = lambda: arr.clear(0)
 	e.gets = lambda x, y: filter( lambda bit: e.get(x, y, bit), range( bit_w( arr.get(x, y, 0) ) ) )
 	return e
@@ -325,8 +370,19 @@

 	arr_bit_c = arr_xy_bit_c_new(w, h, bit_cs)
 	e = extends(arr_bit_c)
+	e.xy_to_view = xy_to_view

-	def update(x, y):
+	def view_to_wh(vw, vh):
+		(w, h) = e.size()
+		(sx, sy) = xy_to_view(0, 0)
+		(ex, ey) = xy_to_view(w, h)
+		(vw_, vh_) = (ex-sx, ey-sy)
+		return ( updiv(vw * w, vw_), updiv(vh * h, vh_) )
+	e.view_to_wh = view_to_wh
+
+	def update_arg(x, y):
+		if not e.chk_xy(x, y):
+			return
 		cs = []
 		if update_border != (0,0):
 			(bw, bh) = update_border
@@ -342,9 +398,35 @@
 		show_lst = trans_to_spc_show_lst(show_lst) if show_lst else [ ('  ', '', False) ]

 		(x, y) = xy_to_view(x, y)
-		lock_shows(x, y, show_lst)
+		return (x, y, show_lst)

+	def update(x, y, f=shows, flush_f=False):
+		f( *update_arg(x, y) )
+		if flush_f:
+			flush()
 	e.update = update
+	e.update_xys = lambda xys, f=shows, flush_f=False: map( lambda (x, y): e.update(x, y, f, flush_f), xys )
+	e.update_area = lambda x, y, w, h, f=shows, flush_f=False: e.update_xys( lst_xywh(x, y, w, h), f, flush_f )
+	e.update_line = lambda x, y, w, f=shows, flush_f=False: e.update_area(x, y, w, 1, f, flush_f)
+
+	e.get_w = lambda vw: view_to_wh( vw, 0 )[0]
+	e.show_lst_wh = lambda show_lst: ( e.get_w( show_lst_len(show_lst) ), 1 )
+
+	def shows_(x, y, show_lst, xcen=False, restore=False, flush_f=False):
+		(w, h) = e.show_lst_wh(show_lst)
+		if xcen:
+			x -= (w - 1) / 2
+		(ow, oh) = e.size()
+		(x, y) = into_xy(x, y, w, h, ow, oh)
+
+		if restore:
+			e.update_area(x, y, w, h)
+		else:
+			(vx, vy) = xy_to_view(x, y)
+			shows( vx, vy, show_lst )
+		if flush_f:
+			flush()
+	e.shows = shows_
 	return e

 def rbuf(fname):
diff -ur v1/mon_ret.txt v2/mon_ret.txt
--- v1/mon_ret.txt	Sat Sep 30 00:25:44 2017
+++ v2/mon_ret.txt	Fri Oct  6 22:00:00 2017
@@ -2,6 +2,7 @@
 #dllrdllrd#dlrrdlrrd#
 #d###d###d#d###d###d#
 #d###d###d#d###d###d#
+#d###d###d#d###d###d#
 #rrrrrrdlllrrdllllll#
 #d###d#d#####d#d###d#
 #rrrrd#rrd#dll#dllll#
diff -ur v1/pac.py v2/pac.py
--- v1/pac.py	Fri Oct  6 01:40:20 2017
+++ v2/pac.py	Fri Oct  6 22:00:00 2017
@@ -3,7 +3,6 @@
 import sys
 import os
 import threading
-import random
 import kon
 from kon import dbg, DIRS, U, D, L, R, flush, has, set, get

@@ -14,10 +13,6 @@
 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':
@@ -45,7 +40,7 @@
 			'rl': kon.rlock()
 		}
 	elif c == 'O':
-		d = { 'flick': kon.flicker( 2.0, pdot_update, sta=True ) }
+		d = { 'flick': kon.flicker( 2.0, lambda: arr.update_xys( pac_buf.pos('O'), flush_f=True ), run=True ) }
 	return d

 stat_cs = MOVES + 'O'
@@ -54,14 +49,14 @@
 stat_gets = lambda cs: map( stat_get, cs )
 c_stat_gets = lambda cs: zip( cs, stat_gets(cs) )

-def pac_show(c):
+def pac_show(c, x, y):
 	(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):
+def mon_show(c, x, y):
 	col = { 'M': 'magenta', 'R': 'red', 'C': 'cyan', 'Y': 'white' }.get(c)
 	d = {
 		'run': ( 'oo', col, True ),
@@ -75,7 +70,17 @@
 		mode += '_' + str( stat_get(c).flick.get() )
 	return d.get(mode if mode in d else 'run')

-bit_cs = MONS + 'PO.=#'
+def blk_show(c, x, y):
+	k = filter( lambda d: pac_buf.get( *kon.step_to(x, y, d) ) == c, DIRS ) # 'udlr'
+	s = '=='
+	d = {
+		( 'ud', 'u', 'd', 'udl', 'udr' ): '||',
+		( 'ul', 'dl' ): '= ',
+		( 'ur', 'dr' ): ' =',
+		( 'udlr', ): '  ',
+	}
+	s = dict( sum( map( lambda (ks, v): map( lambda k: (k, v), ks ), d.items() ), [] ) ).get(k, s)
+	return (s, 'blue', False)

 show_inf = {
 	'M': mon_show,
@@ -83,15 +88,15 @@
 	'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),
+	'O': lambda c, x, y: ('O ', 'yellow', stat_get(c).flick.get()),
+	'.': lambda c, x, y: ('. ', 'white', False),
+        '=': lambda c, x, y: ('==', 'yellow', False),
+	'#': blk_show,
 }

 SPC = ('  ', '', False)
 TRANS = ('  ', 'trans', False)
-get_show_inf = lambda c: show_inf.get(c)(c) if c in show_inf else SPC
+get_show_inf = lambda c, x, y: show_inf.get(c)(c, x, y) if c in show_inf else SPC

 def mon_price_new():
 	e = kon.Empty()
@@ -110,10 +115,10 @@
 	stat = stat_get(c)
 	def update():
 		(x, y) = stat.pos
-		show_update(x, y)
+		arr.update(x, y)
 		dx = stat.dx
 		if dx:
-			show_update( x + dx, y )
+			arr.update( x + dx, y )
 		flush()

 	mode = stat.mode
@@ -140,7 +145,7 @@
 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)
+	inf = get_show_inf(c, x, y)
 	stat = stat_get(c)

 	if c not in MOVES or stat.dx == 0:
@@ -155,6 +160,7 @@
 	return kon.split_show_lst( conn )[1:3]

 (w, h) = pac_buf.size()
+bit_cs = MONS + 'PO.=#'
 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) )
@@ -186,79 +192,26 @@

 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)
-
+def line_msg_pause(x, y, s, col, r, sec, xcen=True):
 	lock_move.lock()
-	show(x_, y_, s, col, r)
-	flush()
+	show_lst = [ (s, col, r) ]
+	arr.shows(x, y, show_lst, xcen, flush_f=True)
 	kon.sleep(sec)
-	show_update_line(x, y, xlen)
+	arr.shows(x, y, show_lst, xcen, restore=True, flush_f=True)
 	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)
-
+def show_anime(x, y, lsts, hz, restore=True, n=1):
 	lock_move.lock()
+	lsts = kon.fit_show_lsts(lsts)
 	for i in range(n):
-		for (s, col, r) in lst:
-			show(x_, y_, s, col, r)
-			flush()
+		for lst in lsts:
+			arr.shows(x, y, lst, flush_f=True)
 			kon.sleep_hz(hz)
-	if restore:
-		show_update_line(x, y, xlen)
+	if restore and lsts:
+		arr.shows(x, y, lsts[0], flush_f=True)
 	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()
+game_ev = kon.evt()

 def meet_chk(x, y):
 	if not arr.get(x, y, 'P'):
@@ -270,17 +223,18 @@
 		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)
+			lsts = map( lambda s: [ (s, 'yellow', True) ], ss )
+			show_anime(x, y, lsts, 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)
+			ss = [ '()', '||', ')(', '><', '--', '><', '--', '><', '--', '__', '  ' ]
+			lsts = map( lambda s: [ (s, 'yellow', True) ], ss )
+			lsts.append( [ SPC ] )
+			show_anime(x, y, lsts, 5.0, restore=False)
 			v = 'over' if spare(-1) == 'over' else 'die'
 			game_ev.set(v)

@@ -300,20 +254,19 @@
 		dx = -dx
 		stat = stat_get(c)
 		stat.dx = dx
-		show_update(px, py)
-		show_update(nx, ny)
+		arr.update(px, py)
+		arr.update(nx, ny)

 		def late_update():
 			stat.dx = 0
-			show_update(px, py)
-			show_update(nx, ny)
-			flush()
+			arr.update(px, py)
+			arr.update(nx, ny, flush_f=True)

 		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)
+		arr.update(px, py)
+		arr.update(nx, ny)
 	flush()

 	meet_chk(nx, ny)
@@ -349,16 +302,15 @@
 	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) ) ]
+		def shows(x, y, lst):
+			lst = map( lambda (s, col, r): (s, col, r) if flick.get() else ('==', 'white', False), lst )
+			kon.shows(x, y, lst)
+
+		flick = kon.flicker( 3.0, arr.update_xys, [ pos, shows ], True )
+		kon.sleep(1.0)
+		flick.kill_join()
+		game_ev.set('clear')

 kdir = kon.gval('')

@@ -383,6 +335,24 @@
 		lock_move.f(px, py, nx, ny, c)
 		try_eat(nx, ny)

+def sel_mon_way(c, x, y, ds):
+	def rand(n):
+		v = sum( sum( map( lambda c: list( stat_get(c).pos ), MOVES ), [] ) )
+		return ( v / 4 ) % n
+	rand_sel = lambda lst: lst[ rand( len(lst) ) ]
+
+	(tx, ty) = stat_get('P').pos
+	xd = R if tx - x > 0 else L
+	yd = D if ty - y > 0 else U
+	rd = rand_sel(ds)
+
+	return {
+		'M': lambda: yd if yd in ds else rd,
+		'R': lambda: xd if xd in ds else yd if yd in ds else rd,
+		'C': lambda: xd if xd in ds else rd,
+		'Y': lambda: rd,
+	}.get(c)()
+
 def th_mon(c):
 	stat = stat_get(c)
 	(px, py) = stat.pos
@@ -396,7 +366,7 @@
 	else:
 		ds_ = filter( lambda d: d != kon.bak_dir(pd), ds )
 		ds = ds_ if ds_ else ds
-		d = rand_sel(ds)
+		d = ds[0] if len(ds) == 1 else sel_mon_way(c, px, py, ds)

 	(nx, ny) = step_to(px, py, d)
 	stat.dir = d
@@ -417,7 +387,7 @@
 	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)
+		kon.show(x + dx, y, s, col, r, flush_f=True)
 	return f

 score = score_new()
@@ -426,14 +396,14 @@
 	(w, h) = pac_buf.size()
 	(x, y) = arr_xy_to_show_xy(0, h)
 	c = 'P'
-	(s, col, r) = get_show_inf(c)
+	(s, col, r) = ('()', 'yellow', True)
 	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_ )
+			kon.show( x_, y, s_, col_, r_, flush_f=True )
 	def f(add=0):
 		if add == 0:
 			map( lambda i: draw(True, i), range(e.v) )
@@ -453,13 +423,15 @@
 spare = spare_new(2)

 def work():
+	kon.lock_show.cond_f = lambda: not game_ev.get()
+
 	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)
+			arr.update(x, y)
 		kon.loop_xy(w, h, f)
 		flush()

@@ -509,11 +481,9 @@

 		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()
+			xys = [(x, y)] + map( lambda d: step_to(x, y, d), DIRS )
+			arr.update_xys( xys, flush_f=True )

 		if v == 'die':
 			init_stat()
diff -ur v1/pac.txt v2/pac.txt
--- v1/pac.txt	Thu Oct  5 17:10:14 2017
+++ v2/pac.txt	Fri Oct  6 22:00:00 2017
@@ -1,5 +1,6 @@
 #####################
 #.........#.........#
+#.###.###.#.###.###.#
 #O###.###.#.###.###O#
 #.###.###.#.###.###.#
 #...................#
Only in v1: pac_data.tgz
diff -ur v1/warp.txt v2/warp.txt
--- v1/warp.txt	Sat Sep 30 00:24:20 2017
+++ v2/warp.txt	Fri Oct  6 22:00:00 2017
@@ -2,6 +2,7 @@
 #         #         #
 # ### ### # ### ### #
 # ### ### # ### ### #
+# ### ### # ### ### #
 #                   #
 # ### #u#####u# ### #
 #     #   #   #     #

$ cat v2.patch | patch -p1
$ chmod +x pac.py
$ ./pac.py


第3弾

v3.patch
diff -urN v2/kon.py v3/kon.py
--- v2/kon.py	2017-10-06 22:00:00.000000000 +0900
+++ v3/kon.py	2017-10-17 03:15:30.000000000 +0900
@@ -84,9 +84,11 @@
 		rl = rlock()
 	e = extends(rl)
 	e.cond_f = cond_f if cond_f else lambda: True
+	e.en = True
+	chk = lambda: e.cond_f() and e.en
 	def wrap_f(*args):
 		rl.lock()
-		r = f(*args) if e.cond_f() else None
+		r = f(*args) if chk() else None
 		rl.unlock()
 		return r
 	e.f = wrap_f
@@ -148,37 +150,52 @@
 	e = Empty()
 	e.hz = hz
 	e.run = run
-	ev = evt()
+	kill_ev = threading.Event()
 	def loop():
-		while not ev.wait( hz_to_sec(e.hz) ):
-			# sec == 0 when hz == 0
+		while not kill_ev.is_set():
 			if e.run:
 				f( *args )
+			kill_ev.wait( hz_to_sec(e.hz) ) # sec == 0 when hz == 0
+
 	th = threading.Thread( target=loop )
 	th.daemon = True
-
 	e.stop = lambda: set(e, 'run', False)
 	e.start = lambda: set(e, 'run', True)
-	e.kill = lambda: ev.set('kill')
+	e.kill = lambda: kill_ev.set()
 	e.join = th.join
 	e.kill_join = lambda: ( e.kill(), e.join() )[-1]
+	e.set_hz = lambda hz: set(e, 'hz', hz)
+	e.get_hz = lambda: e.hz

 	th.start()
 	return e

-def flicker(hz=1.0, f=None, args=[], run=False):
+def counter(n, hz=1.0, f=None, run=False, repeat=True):
+	ev = threading.Event()
 	def th_f():
-		set(e, 'v', not e.v)
-		if has(e, 'f'):
-			e.f( *e.args )
-
-	#e = extends( th_loop(th_f, [], hz*2, run) )
-	e = extends( th_loop(th_f, [], hz, run) )
-	e.v = False
-	e.get = lambda: e.v
+		e.i = (e.i + 1) % n if e.i != None else 0
+		if e.f:
+			e.f()
+		if not repeat and e.i == n - 1:
+			e.stop()
+			ev.set()
+	th = th_loop(th_f, [], hz, False)
+	e = extends(th)
 	e.f = f
-	e.args = args
-	e.set_hz = lambda hz: set(e, 'hz', hz*2)
+	e.i = None
+	e.get = lambda: e.i if e.i != None else 0
+	e.wait = ev.wait # for no repeat
+	e.set_f = lambda f: set(e, 'f', f)
+	e.start = lambda: ( set(e, 'i', None), th.start() )[-1]
+	e.restart = lambda: ( e.stop(), e.start() )[-1]
+	if run:
+		e.start()
+	return e
+
+def slider(lst, hz=1.0, f=None, run=False, repeat=True):
+	ctr = counter( len(lst), hz, f, run, repeat )
+	e = extends(ctr)
+	e.get = lambda: lst[ ctr.get() ]
 	return e

 def term_raw(sigint=True):
@@ -226,7 +243,7 @@
 		buf += getkey(tmout)
 	return ''

-cls = lambda : out( esc('2J') )
+clr = lambda : out( esc('2J') )
 loc = lambda x, y: out( esc( '{};{}H'.format(y+1, x+1) ) )
 rev = lambda v: out( esc( '7m' if v else '0m' ) )
 cursor = lambda v: out( esc_ex(25, v) )
@@ -261,6 +278,8 @@

 show = lambda x, y, s, col, r, flush_f=False: lock_show.f(x, y, s, col, r, flush_f)

+mk_slst = lambda s, col='', r=False: [ (s, col, r) ]
+
 def sum_show_lst(lst):
 	# lst = [ (s, col, r), (s, col, r) ... ]

@@ -270,7 +289,7 @@
 	a = (s, col, r) = lst[0]
 	lst = sum_show_lst( lst[1:] )
 	(s_, col_, r_) = lst[0]
-	return [ (s + s_, col, r) ] + lst[1:] if col == col_ and r == r_ else [a] + lst
+	return mk_slst(s + s_, col, r) + lst[1:] if col == col_ and r == r_ else [a] + lst

 split_show_lst = lambda lst: sum( map( lambda (s, col, r): map( lambda c: (c, col, r), s ), lst ), [] )

@@ -279,7 +298,7 @@
 def fit_show_lsts(show_lsts):
 	ns = map( show_lst_len, show_lsts )
 	max_n = max(ns)
-	return map( lambda (lst, n): sum_show_lst( lst + [ (' ', '', False) ] * (max_n - n) ), zip( show_lsts, ns ) )
+	return map( lambda (lst, n): sum_show_lst( lst + mk_slst(' ') * (max_n - n) ), zip( show_lsts, ns ) )

 def shows(x, y, lst, flush_f=False):
 	x = reduce( lambda x, (s, col, r): show(x, y, s, col, r), sum_show_lst(lst), x )
@@ -290,11 +309,10 @@
 def over_show_lst(l, h):
 	get_col = lambda (s, col, r): col
 	f = lambda l, h: l if get_col(h) == 'trans' else h
-
 	l = split_show_lst(l)
 	h = split_show_lst(h)
 	n = max( len(l), len(h) )
-	apd = [ (' ', 'trans', False) ] * n
+	apd = mk_slst(' ', 'trans') * n
 	l = ( l + apd )[:n]
 	h = ( h + apd )[:n]
 	o = map( lambda (l, h): f(l, h), zip(l, h) )
@@ -325,11 +343,13 @@
 lst_xywh = lambda x, y, w, h: loop_xywh( x, y, w, h, lambda x, y: (x, y) )

 chk_xy = lambda x, y, w, h: x in range(w) and y in range(h)
-chk_xywh = lambda x, y, sx, sy, w, h: chk_xy(x-sx, y-sy)
+chk_xywh = lambda x, y, sx, sy, w, h: chk_xy(x-sx, y-sy, w, h)

 into_x = lambda x, w, ow: max( min( ow - w, x ), 0 )
 into_xy = lambda x, y, w, h, ow, oh: ( into_x(x, w, ow), into_x(y, h, oh) )

+cen_xywh = lambda x, y, w, h: (x + w/2, y + h/2)
+
 def arr_xy_new(w, h, v):
 	e = Empty()
 	e.data = data = map( lambda _: [ v ] * w, range(h) )
@@ -337,39 +357,53 @@
 	e.chk_xy = lambda x, y: chk_xy(x, y, w, h)
 	e.get = lambda x, y, v=None: data[y][x] if e.chk_xy(x, y) else v
 	e.set = lambda x, y, c: set( data[y], x, c ) if e.chk_xy(x, y) else None
-	e.clear = lambda c: loop_xy( w, h, lambda x, y: e.set(x, y, c) )
-	def pos(c):
-		lst = filter( lambda (x, y): e.get(x, y) == c, lst_xy(w, h) )
-		return lst[0] if len(lst) == 1 else lst
-	e.pos = pos
+	e.clear = lambda v: loop_xy( w, h, lambda x, y: e.set(x, y, v) )
+	e.poss = lambda v: filter( lambda (x, y): e.get(x, y) == v, lst_xy(w, h) )
+	e.pos = lambda v: next( iter( e.poss(v) ), (-1, -1) )
 	return e

 def arr_xy_bit_new(w, h):
 	pat = lambda bit: 1 << bit if bit >= 0 else 0
 	chg = lambda v, bit, bv: v | pat(bit) if bv else v & ~pat(bit)
 	arr = arr_xy_new(w, h, 0)
+	pos_d = {}
+
+	def set_(x, y, bit, bv):
+		if not e.chk_xy(x, y):
+			return
+		arr.set(x, y, chg(arr.get(x, y), bit, bv) )
+
+		if bit not in pos_d:
+			pos_d[bit] = []
+		lst = pos_d.get(bit)
+		p = (x, y)
+		if bv and p not in lst:
+			lst.append(p)
+		if not bv and p in lst:
+			lst.remove(p)
 	e = extends(arr)
 	e.get = lambda x, y, bit: arr.get(x, y, 0) & pat(bit)
-	e.set_ = lambda x, y, bit, bv: arr.set( x, y, chg( arr.get(x, y), bit, bv ) )
-	e.set = lock_wrap( e.set_ ).f
+	e.set = lock_wrap( set_ ).f
 	e.clear = lambda: arr.clear(0)
 	e.gets = lambda x, y: filter( lambda bit: e.get(x, y, bit), range( bit_w( arr.get(x, y, 0) ) ) )
+	e.poss = lambda bit: pos_d.get(bit, [])
+	e.pos = lambda bit: next( iter( e.poss(bit) ), (-1, -1) )
 	return e

 def arr_xy_bit_c_new(w, h, bit_cs):
-	arr_bit = arr_xy_bit_new(w, h)
-	e = extends(arr_bit)
-	e.to_bit = to_bit = dict( zip( bit_cs, range( len(bit_cs) ) ) )
-	e.get = lambda x, y, c: arr_bit.get( x, y, to_bit.get(c, -1) )
-	e.set = lambda x, y, c, v: arr_bit.set( x ,y, to_bit.get(c, -1), v )
-	e.gets = lambda x, y: map( lambda bit: bit_cs[bit], arr_bit.gets(x, y) )
+	arr = arr_xy_bit_new(w, h)
+	e = extends(arr)
+	e.to_bit = to_bit = lambda c: bit_cs.index(c) if c in bit_cs else -1
+	e.get = lambda x, y, c: arr.get( x, y, to_bit(c) )
+	e.set = lambda x, y, c, v: arr.set( x ,y, to_bit(c), v )
+	e.gets = lambda x, y: map( lambda bit: bit_cs[bit], arr.gets(x, y) )
+	e.poss = lambda c: arr.poss( to_bit(c) )
+	e.pos = lambda c: next( iter( e.poss(c) ), (-1, -1) )
 	return e

-def arr_xy_bit_c_show_new(w, h, bit_cs, c_to_show_lst, xy_to_view, update_border=(0,0)):
-	# c_to_show_lst(c, x, y) return [ (s, col, r), (s, col, r) ... ]
-
-	arr_bit_c = arr_xy_bit_c_new(w, h, bit_cs)
-	e = extends(arr_bit_c)
+def arr_xy_bit_c_show_new(w, h, bit_cs, xy_to_view):
+	arr = arr_xy_bit_c_new(w, h, bit_cs)
+	e = extends(arr)
 	e.xy_to_view = xy_to_view

 	def view_to_wh(vw, vh):
@@ -380,53 +414,146 @@
 		return ( updiv(vw * w, vw_), updiv(vh * h, vh_) )
 	e.view_to_wh = view_to_wh

-	def update_arg(x, y):
-		if not e.chk_xy(x, y):
-			return
-		cs = []
-		if update_border != (0,0):
-			(bw, bh) = update_border
-			xys = map( lambda (dx, dy): (x+dx-bw, y+dy-bh), lst_xy(bw*2+1, bh*2+1) )
-			cs = sum( map( lambda (x, y): e.gets(x, y), xys ), [] )
-			cs = reduce( lambda cs, c: cs if c in cs else cs + [c], cs, [] )
-			cs.sort( key=lambda c: bit_cs.index(c) )
-		else:
-			cs = e.gets(x, y)
-
-		show_lsts = map( lambda c: c_to_show_lst(c, x, y), cs )
-		show_lst = reduce( over_show_lst, reversed( show_lsts ), [] )
-		show_lst = trans_to_spc_show_lst(show_lst) if show_lst else [ ('  ', '', False) ]
-
-		(x, y) = xy_to_view(x, y)
-		return (x, y, show_lst)
-
-	def update(x, y, f=shows, flush_f=False):
-		f( *update_arg(x, y) )
-		if flush_f:
-			flush()
-	e.update = update
-	e.update_xys = lambda xys, f=shows, flush_f=False: map( lambda (x, y): e.update(x, y, f, flush_f), xys )
-	e.update_area = lambda x, y, w, h, f=shows, flush_f=False: e.update_xys( lst_xywh(x, y, w, h), f, flush_f )
-	e.update_line = lambda x, y, w, f=shows, flush_f=False: e.update_area(x, y, w, 1, f, flush_f)
+	sdic = {}
+	e.get_sdic = lambda c: sdic.get( c, (None, None) )
+	e.set_sdic = lambda c, v: set(sdic, c, v)
+
+	def get_show_lst(c, x, y):
+		(t, o) = e.get_sdic(c)
+		if not o:
+			return mk_slst('  ')
+		if t == 'func':
+			return o(c, x, y)
+		if t == 'slider':
+			return o.get()
+		return o
+	e.get_show_lst = get_show_lst

 	e.get_w = lambda vw: view_to_wh( vw, 0 )[0]
 	e.show_lst_wh = lambda show_lst: ( e.get_w( show_lst_len(show_lst) ), 1 )

-	def shows_(x, y, show_lst, xcen=False, restore=False, flush_f=False):
+	def show_lst_area(x, y, show_lst, xcen=False):
 		(w, h) = e.show_lst_wh(show_lst)
 		if xcen:
 			x -= (w - 1) / 2
 		(ow, oh) = e.size()
 		(x, y) = into_xy(x, y, w, h, ow, oh)
+		return (x, y, w, h)
+	e.show_lst_area = show_lst_area

-		if restore:
-			e.update_area(x, y, w, h)
-		else:
-			(vx, vy) = xy_to_view(x, y)
-			shows( vx, vy, show_lst )
-		if flush_f:
-			flush()
+	def shows_(x, y, show_lst, xcen=False, flush_f=False):
+		(x, y, w, h) = e.show_lst_area(x, y, show_lst, xcen)
+		(vx, vy) = xy_to_view(x, y)
+		shows(vx, vy, show_lst, flush_f)
 	e.shows = shows_
+	e.flush = lambda flush_f=True: flush() if flush_f else None
+	return e
+
+def arr_xy_bit_c_update_new(w, h, bit_cs, xy_to_view, get_dx=None):
+	arr = arr_xy_bit_c_show_new(w, h, bit_cs, xy_to_view)
+	e = extends(arr)
+
+	def update(x, y, flush_f=False):
+		if not e.chk_xy(x, y):
+			return
+		lst = []
+		if get_dx:
+			for ix in (-1, 0, 1):
+				x_ = (x + ix + w) % w
+				cs = filter( lambda c: ix == 0 or get_dx(c) == -ix, e.gets(x_, y) )
+				lst += map( lambda c: ( c, x_, ix if ix else get_dx(c) ), cs )
+
+			TRANS = mk_slst('  ', 'trans')
+			shift = lambda lst, dx: split_show_lst( lst + TRANS if dx < 0 else TRANS + lst )[1:3] if dx else lst
+			lst = map( lambda (c, x_, dx): ( c, shift( e.get_show_lst(c, x_, y), dx ) ), lst )
+		else:
+			lst = map( lambda c: ( c, e.get_show_lst(c, x, y) ), e.gets(x, y) )
+
+		lst.sort( key=lambda (c, show_lst): e.to_bit(c) )
+		show_lsts = zip(*lst)[1] if lst else []
+		show_lst = reduce( over_show_lst, reversed( show_lsts ), [] )
+		show_lst = trans_to_spc_show_lst(show_lst) if show_lst else mk_slst('  ')
+		e.shows(x, y, show_lst, flush_f)
+
+	e.update = update
+	e.update_xys = lambda xys, flush_f=False: ( map( lambda (x, y): e.update(x, y), xys ), e.flush(flush_f) )[-1]
+	e.update_area = lambda x, y, w, h, flush_f=False: e.update_xys( lst_xywh(x, y, w, h), flush_f )
+	e.update_line = lambda x, y, w, flush_f=False: e.update_area(x, y, w, 1, flush_f)
+	e.update_all = lambda flush_f=False: e.update_area(0, 0, w, h, flush_f)
+
+	def update_c(c, flush_f=False):
+		dx = get_dx(c) if get_dx else 0
+		xys = e.poss(c)
+		#xys += map( lambda (x, y): (x+dx, y), xys ) if dx else []
+		if dx:
+			xys = xys + map( lambda (x, y): (x+dx, y), xys )
+		e.update_xys(xys, flush_f)
+	e.update_c = update_c
+
+	def set_sdic(c, v):
+		(t, o) = e.get_sdic(c)
+		if t == 'slider' and o:
+			o.kill_join()
+
+		arr.set_sdic(c, v)
+
+		(t, o) = e.get_sdic(c)
+		if t == 'slider' and o:
+			o.set_f( lambda: e.update_c(c, flush_f=True) )
+
+		e.update_c(c, flush_f=True)
+
+	e.set_sdic = set_sdic
+	return e
+
+def arr_xy_bit_c_move_new(w, h, bit_cs, xy_to_view):
+	dx_d = {}
+	get_dx = lambda c: dx_d.get(c, 0)
+	arr = arr_xy_bit_c_update_new(w, h, bit_cs, xy_to_view, get_dx)
+	e = extends(arr)
+	dir_d = {}
+
+	e.get_dir = lambda c: dir_d.get(c, '')
+	e.set_dir = lambda c, d: set(dir_d, c, d)
+
+	def move(c, d, hz=None):
+		(x, y) = e.pos(c)
+		if not e.chk_xy(x, y):
+			return
+
+		(nx, ny) = step_to(x, y, d)
+		(nx, ny) = ( (nx + w) % w, (ny + h) % h )
+		arr.set(x, y, c, False)
+		arr.set(nx, ny, c, True)
+		e.set_dir(c, d)
+
+		if hz:
+			dx_d[c] = { R: -1, L: 1 }.get(d, 0)
+
+		xys = [ (x, y), (nx, ny) ]
+		arr.update_xys(xys, flush_f=True)
+
+		if hz:
+			sec = hz_to_sec( hz * 2 )
+			tmr_f = lambda: ( set(dx_d, c, 0), arr.update_xys(xys, flush_f=True) )[-1]
+			timer_new(tmr_f, [], sec).start()
+	e.move = move
+
+	def set_(x, y, c, v=True, flush_f=False, d=''):
+		arr.set(x, y, c, v)
+		e.set_dir(c, d)
+		arr.update(x, y, flush_f)
+	e.set = set_
+
+	e.rm = lambda c, flush_f=False: map( lambda (x, y): e.set(x, y, c, False, flush_f), e.poss(c) )
+
+	def msg_pause(x, y, show_lst, sec, xcen=False):
+		e.shows(x, y, show_lst, xcen, flush_f=True)
+		sleep(sec)
+		(x, y, w, h) = e.show_lst_area(x, y, show_lst, xcen)
+		e.update_area(x, y, w, h, flush_f=True)
+	e.msg_pause = msg_pause
+
 	return e

 def rbuf(fname):
diff -urN v2/pac.py v3/pac.py
--- v2/pac.py	2017-10-06 22:00:00.000000000 +0900
+++ v3/pac.py	2017-10-17 03:15:30.000000000 +0900
@@ -4,72 +4,25 @@
 import os
 import threading
 import kon
-from kon import dbg, DIRS, U, D, L, R, flush, has, set, get
+from kon import dbg, DIRS, U, D, L, R, flush, has, set, get, mk_slst

 NO_DIE = '-m' in sys.argv

-(pac_buf, mon_ret_buf, warp_buf) = map( kon.rbuf, ['pac.txt', 'mon_ret.txt', 'warp.txt'] )
+txts = map( lambda s: s + '.txt', [ 'pac', 'mon_ret', 'warp', 'pato' ] )
+(pac_buf, mon_ret_buf, warp_buf, pato_buf) = map(kon.rbuf, txts)

-MONS = 'MRCY'
+MONS = 'RCMY'
 MOVES = 'P' + MONS

-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, lambda: arr.update_xys( pac_buf.pos('O'), flush_f=True ), run=True ) }
-	return d
-
-stat_cs = MOVES + 'O'
-stat_inf = dict( map( lambda c: ( c, kon.dic_to_cls( stat_dic(c) ) ), stat_cs ) )
+stat_inf = dict( map( lambda c: ( c, kon.Empty() ), MOVES ) )
 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, x, y):
 	(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)
+		s = { L: '>)', R: '(<', U: '(/', D: '/)' }.get( arr.get_dir(c), s )
+	return mk_slst(s, col, r)

-def mon_show(c, x, y):
-	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')
-
 def blk_show(c, x, y):
 	k = filter( lambda d: pac_buf.get( *kon.step_to(x, y, d) ) == c, DIRS ) # 'udlr'
 	s = '=='
@@ -80,23 +33,9 @@
 		( 'udlr', ): '  ',
 	}
 	s = dict( sum( map( lambda (ks, v): map( lambda k: (k, v), ks ), d.items() ), [] ) ).get(k, s)
-	return (s, 'blue', False)
-
-show_inf = {
-	'M': mon_show,
-	'R': mon_show,
-	'C': mon_show,
-	'Y': mon_show,
-	'P': pac_show,
-	'O': lambda c, x, y: ('O ', 'yellow', stat_get(c).flick.get()),
-	'.': lambda c, x, y: ('. ', 'white', False),
-        '=': lambda c, x, y: ('==', 'yellow', False),
-	'#': blk_show,
-}
+	return mk_slst(s, 'blue', False)

 SPC = ('  ', '', False)
-TRANS = ('  ', 'trans', False)
-get_show_inf = lambda c, x, y: show_inf.get(c)(c, x, y) if c in show_inf else SPC

 def mon_price_new():
 	e = kon.Empty()
@@ -113,55 +52,36 @@

 def mon_mode(c, v='get'):
 	stat = stat_get(c)
-	def update():
-		(x, y) = stat.pos
-		arr.update(x, y)
-		dx = stat.dx
-		if dx:
-			arr.update( x + dx, y )
-		flush()
-
-	mode = stat.mode
+	mode = stat.mode if has(stat, 'mode') else None
 	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()
+	col = { 'M': 'magenta', 'R': 'red', 'C': 'cyan', 'Y': 'white' }.get(c)
+	blinks= [ mk_slst('><', 'blue', True), mk_slst('--', 'white', True) ]
+	d = {
+		'cage': ( '', mk_slst('oo', col, True) ),
+		'run': ( '', mk_slst('oo', col, True) ),
+		'weak': ( '', mk_slst('><', 'blue', True) ),
+		'blink': ( 'slider', kon.slider(blinks, stat.hz, None, True) ),
+		'ret': ( '', mk_slst('oo', 'white', False) ),
+	}
+	arr.set_sdic( c, d.get(v) )

 	mon_price.check()

+	stat.th.set_hz( get_hz( c, *arr.pos(c) ) )
+

 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, x, y)
-	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()
-bit_cs = MONS + 'PO.=#'
-arr = kon.arr_xy_bit_c_show_new( w, h, bit_cs, c_to_show_lst, arr_xy_to_show_xy, (1,0) )
+bit_cs = MOVES + 'O.=#'
+arr = kon.arr_xy_bit_c_move_new( w, h, bit_cs, arr_xy_to_show_xy)

 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) )

@@ -184,32 +104,35 @@

 	(nx, ny) = step_to(px, py, d)

+	# gate chk
+	nc = pac_buf.get(nx, ny)
+	if c in MONS and mon_mode(c) == 'cage' and nc == '=' and MONS.index(c) > gate.get():
+		return False
+
 	v = one_way(nx, ny, d)
 	if v != None:
 		return v

-	return pac_buf.get(nx, ny) not in (None, '#')
+	return nc != '#'

 next_ds = lambda x, y, c: filter( lambda d: is_step(x, y, c, d), DIRS )

-def line_msg_pause(x, y, s, col, r, sec, xcen=True):
-	lock_move.lock()
-	show_lst = [ (s, col, r) ]
-	arr.shows(x, y, show_lst, xcen, flush_f=True)
-	kon.sleep(sec)
-	arr.shows(x, y, show_lst, xcen, restore=True, flush_f=True)
-	lock_move.unlock()
-
-def show_anime(x, y, lsts, hz, restore=True, n=1):
-	lock_move.lock()
-	lsts = kon.fit_show_lsts(lsts)
-	for i in range(n):
-		for lst in lsts:
-			arr.shows(x, y, lst, flush_f=True)
-			kon.sleep_hz(hz)
-	if restore and lsts:
-		arr.shows(x, y, lsts[0], flush_f=True)
-	lock_move.unlock()
+def arr_msg_pause(show_lst, x=None, y=None, sec=1.0, xcen=True):
+	game_ev.set('stop')
+	(bx, by) = pac_buf.pos('B')
+	x = bx if x == None else x
+	y = by if y == None else y
+	arr.msg_pause(x, y, show_lst, sec, xcen)
+	game_ev.set('start')
+
+def pac_slider(slider):
+	game_ev.set('stop')
+	c = 'P'
+	bak = arr.get_sdic(c)
+	arr.set_sdic( c, ('slider', slider) )
+	slider.wait()
+	game_ev.set('start')
+	return bak

 game_ev = kon.evt()

@@ -222,63 +145,63 @@

 		mode = mon_mode(c)
 		if mode in ('weak', 'blink'):
+			mov.en = False
 			ss = [ '. ', 'o ', 'O ', '<>', '()', '^^' ]
-			lsts = map( lambda s: [ (s, 'yellow', True) ], ss )
-			show_anime(x, y, lsts, 8.0, restore=False)
+			lsts = map( lambda s: mk_slst(s, 'yellow', True), ss )
+			bak = pac_slider( kon.slider(lsts, 8.0, None, True, repeat=False) )
 			add = mon_price.get() * 200
 			mon_price.twice()
+			arr_msg_pause( mk_slst( str(add) ), x, y-1, 1.0 )
 			score(add)
-			line_msg_pause(x, y-1, str(add), '', False, 1.0)
 			mon_mode(c, 'ret')
+			arr.set_sdic('P', bak)
+			mov.en = True
+			break
 		elif mode != 'ret' and not NO_DIE:
+			mov.en = False
 			ss = [ '()', '||', ')(', '><', '--', '><', '--', '><', '--', '__', '  ' ]
-			lsts = map( lambda s: [ (s, 'yellow', True) ], ss )
+			lsts = map( lambda s: mk_slst(s, 'yellow', True), ss )
 			lsts.append( [ SPC ] )
-			show_anime(x, y, lsts, 5.0, restore=False)
+			bak = pac_slider( kon.slider(lsts, 5.0, None, True, repeat=False) )
 			v = 'over' if spare(-1) == 'over' else 'die'
 			game_ev.set(v)
+			arr.set_sdic('P', bak)
+			break

 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 )
+		mode = mon_mode(c)
+		hz *= mon_hz_rate.get(mode, 1.0)
+		if mode != 'ret':
+			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
-		arr.update(px, py)
-		arr.update(nx, ny)
-
-		def late_update():
-			stat.dx = 0
-			arr.update(px, py)
-			arr.update(nx, ny, flush_f=True)
-
-		sec = kon.hz_to_sec( get_hz(c, nx, ny) * 2 )
-		stat.late_tmr.start( late_update, [], sec )
-	else:
-		arr.update(px, py)
-		arr.update(nx, ny)
-	flush()
-
-	meet_chk(nx, ny)
+def move_new():
+	e = kon.Empty()
+	e.en = True
+	def f(c, d, hz):
+		if e.en:
+			arr.move(c, d, hz)
+			(x, y) = arr.pos(c)
+			meet_chk(x, y)
+	e.f = f
+	return e
+mov = move_new()
+move = mov.f

-lock_move = kon.lock_wrap( move )
+def short_sec(sec):
+	c = 'P'
+	hz = stat_get(c).hz
+	init_hz = 3.0
+	return sec * init_hz / hz

 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 )
+			stat_get(c).timer.start( tmr_f, [c], short_sec(3.0) )
 		elif mode == 'blink':
 			mon_mode(c, 'run')

@@ -286,8 +209,8 @@
 		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 )
+			arr.set_dir( c, kon.bak_dir( arr.get_dir(c) ) )
+			stat.timer.start( tmr_f, [c], short_sec(15.0) )

 def try_eat(x, y):
 	c = '.' if arr.get(x, y, '.') else 'O' if arr.get(x, y, 'O') else ''
@@ -296,20 +219,18 @@
 	arr.set(x, y, c, False)
 	stat_get('P').dot -= 1

-	score( 20 if c == 'O' else 5 )
+	score( 50 if c == 'O' else 10 )
 	if c == 'O':
 		mon_weak()
 	if stat_get('P').dot <= 0:
+		mov.en = False
 		c = '#'
-		pos = pac_buf.pos(c)
-
-		def shows(x, y, lst):
-			lst = map( lambda (s, col, r): (s, col, r) if flick.get() else ('==', 'white', False), lst )
-			kon.shows(x, y, lst)
-
-		flick = kon.flicker( 3.0, arr.update_xys, [ pos, shows ], True )
-		kon.sleep(1.0)
-		flick.kill_join()
+		bak = arr.get_sdic(c)
+		lst = map( lambda col: mk_slst('==', col, False), ('white', 'blue') )
+		slider = kon.slider(lst, 4.0, None, True)
+		arr.set_sdic( c, ('slider', slider) )
+		kon.sleep(2.0)
+		arr.set_sdic(c, bak)
 		game_ev.set('clear')

 kdir = kon.gval('')
@@ -321,8 +242,8 @@

 def th_pac(c):
 	stat = stat_get(c)
-	(px, py) = stat.pos
-	pd = stat.dir
+	(px, py) = arr.pos(c)
+	pd = arr.get_dir(c)

 	ds = next_ds(px, py, c)
 	kd = kdir()
@@ -330,64 +251,96 @@
 	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)
+		hz = get_hz(c, nx, ny)
+		move(c, d, hz)
 		try_eat(nx, ny)

-def sel_mon_way(c, x, y, ds):
-	def rand(n):
-		v = sum( sum( map( lambda c: list( stat_get(c).pos ), MOVES ), [] ) )
-		return ( v / 4 ) % n
-	rand_sel = lambda lst: lst[ rand( len(lst) ) ]
-
-	(tx, ty) = stat_get('P').pos
-	xd = R if tx - x > 0 else L
-	yd = D if ty - y > 0 else U
+def rand(n):
+	v = sum( sum( map( lambda c: list( arr.pos(c) ), MOVES ), [] ) )
+	return ( v / 4 ) % n
+
+rand_sel = lambda lst: lst[ rand( len(lst) ) ]
+
+def ways(x, y, tx, ty, ds, rev=False):
+	(dx, dy) = (tx - x, ty - y)
+	xd = R if dx > 0 else L
+	yd = D if dy > 0 else U
+	if rev:
+		(xd, yd) = ( kon.bak_dir(xd), kon.bak_dir(yd) )
 	rd = rand_sel(ds)
+	ds_ = [ xd, yd, rd ] if abs(dx) > abs(dy) else [ yd, xd, rd ]
+	return next( ( d for d in ds_ if d in ds ), ds[0] )

-	return {
-		'M': lambda: yd if yd in ds else rd,
-		'R': lambda: xd if xd in ds else yd if yd in ds else rd,
-		'C': lambda: xd if xd in ds else rd,
-		'Y': lambda: rd,
-	}.get(c)()
+def sel_mon_way(c, x, y, ds):
+	mode = mon_mode(c)
+	if mode == 'ret':
+		return mon_ret_buf.get(x, y)
+
+	pd = arr.get_dir(c)
+	ds_ = filter( lambda d: d != kon.bak_dir(pd), ds )
+	ds = ds_ if ds_ else ds
+
+	(tx, ty) = arr.pos('P')
+
+	if mode in ('weak', 'blink'):
+		return ways(x, y, tx, ty, ds, rev=True)
+
+	if mode == 'run' and run_mode.get() == 'pato':
+		# MONS = 'RCMY'
+		(w2, h2) = (w/2, h/2)
+		area_d = {
+			'R': (w2, 0, w2, h2),
+			'C': (w2, h2, w2, h2),
+			'M': (0, 0, w2, h2),
+			'Y': (0, h2, w2, h2),
+		}
+		area = area_d.get(c)
+		d = pato_buf.get(x, y)
+		if kon.chk_xywh(x, y, *area) and d in DIRS:
+			return d
+		(ax, ay) = kon.cen_xywh(*area)
+		return ways(x, y, ax, ay, ds)
+
+	if c == 'R':
+		return ways(x, y, tx, ty, ds)
+	if c == 'C':
+		return ways(x, y, 2*x-tx, 2*y-ty, ds)
+	if c == 'M':
+		(dx, dy) = kon.dir_to_xy( arr.get_dir('P') )
+		return ways(x, y, tx + dx * 3, ty + dy * 3, ds)
+	return rand_sel(ds)

 def th_mon(c):
 	stat = stat_get(c)
-	(px, py) = stat.pos
-	pd = stat.dir
-	mode = mon_mode(c)
-
+	(px, py) = arr.pos(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 = ds[0] if len(ds) == 1 else sel_mon_way(c, px, py, ds)
+
+	d = sel_mon_way(c, px, py, ds)

 	(nx, ny) = step_to(px, py, d)
-	stat.dir = d
-	stat.pos = (nx, ny)
-	lock_move.f(px, py, nx, ny, c)
+	hz = get_hz(c, nx, ny)
+	move(c, d, hz)

+	mode = mon_mode(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 chk_1up(add):
+		t = 10000
+		if e.v >= t and e.v - add < t:
+			spare(1)
+			arr_msg_pause( mk_slst('1UP') )
 	def f(add=0):
 		e.v += add
 		(dx, s) = (0, lb) if add == 0 else ( len(lb), str(e.v) )
 		kon.show(x + dx, y, s, col, r, flush_f=True)
+		chk_1up(add)
 	return f

 score = score_new()
@@ -422,76 +375,113 @@

 spare = spare_new(2)

+gate = kon.counter(4)
+
+mon_turn = lambda: map( lambda c: arr.set_dir( c, kon.bak_dir( arr.get_dir(c) ) ), MONS )
+run_mode = kon.slider( ['pato', 'chase'], f=mon_turn )
+
 def work():
-	kon.lock_show.cond_f = lambda: not game_ev.get()
+	kon.clr()
+
+	dot_n = len( pac_buf.poss('.') + pac_buf.poss('O') )

 	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)
-			arr.update(x, y)
+			arr.set(x, y, c)
 		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
+			(x, y) = pac_buf.pos(c)
+			d = L if c == 'P' else U
+			arr.set(x, y, c, True, True, d)
+
 			if c in MONS:
-				stat.mode = 'cage' if c in 'CMY' else 'run'
-				stat.flick.stop()
+				mon_mode( c, 'cage' if c in 'CMY' else 'run' )
 			if clear:
+				stat = stat_get(c)
 				stat.hz *= 1.2
-				stat.th.hz = stat.hz
+				stat.th.set_hz(stat.hz)
 				if c == 'P':
-					stat.dot = len( pac_buf.pos('.') + pac_buf.pos('O') )
+					stat.dot = dot_n
+		mon_r_hz = stat_get('R').hz
+		gate.set_hz( mon_r_hz / 10.0 )
+		gate.restart()
+
+		run_mode.set_hz( mon_r_hz / 100.0 )
+		run_mode.restart()
+
+	lst = map( lambda r: mk_slst('O ', 'yellow', r), (False, True) )
+	arr.set_sdic( 'O', ( 'slider', kon.slider(lst, 2.0, None, True) ) )
+
+	arr.set_sdic( 'P', ('func', pac_show) )
+	arr.set_sdic( '#', ('func', blk_show) )
+	arr.set_sdic( '.', ( '', mk_slst('. ', 'white', False) ) )
+	arr.set_sdic( '=', ( '', mk_slst('==', 'yellow', False) ) )

 	kon.th_loop(th_key)
 	score()
 	spare()
-	init_map()

-	for (c, stat) in c_stat_gets(MOVES):
+	stat_get('P').dot = dot_n
+	map( lambda c: set( stat_get(c), 'hz', 3.0 if c == 'P' else 3.3 ), MOVES )
+	map( lambda c: set( stat_get(c), 'timer', kon.timer_new() ), MONS )
+
+	for c in MOVES:
+		stat = stat_get(c)
 		f = th_pac if c == 'P' else th_mon
-		stat.th = kon.th_loop(f, [c], stat.hz, False)
+		stat.th = kon.th_loop(f, [c], stat.hz, run=False)

-	run = True
-	while run:
-		(x, y) = pac_buf.pos('B')
-		line_msg_pause(x, y, 'READY!', 'yellow', False, 1.0)
+	hdl_ready = lambda: arr_msg_pause( mk_slst('READY!', 'yellow'), *pac_buf.pos('B') )
+	hdl_start = lambda: map( lambda c: stat_get(c).th.start(), MOVES )
+	hdl_stop = lambda: map( lambda c: stat_get(c).th.stop(), MOVES )
+	hdl_timer_cancel = lambda: map( lambda c: stat_get(c).timer.cancel(), MONS )
+	hdl_rm = lambda: ( map( lambda c: arr.rm(c), MOVES ), arr.update_all(flush_f=True) )[-1]

-		map( lambda stat: stat.th.start(), stat_gets(MOVES) )
+	init_map()
+	init_stat()
+	hdl_ready()
+	hdl_start()

+	while True:
 		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
-			arr.set(x, y, c, False)
-			xys = [(x, y)] + map( lambda d: step_to(x, y, d), DIRS )
-			arr.update_xys( xys, flush_f=True )
-
-		if v == 'die':
+		if v == 'start':
+			hdl_start()
+		elif v == 'stop':
+			hdl_stop()
+
+		elif v == 'die':
+			hdl_stop()
+			hdl_timer_cancel()
+			kon.sleep(2.0)
+			hdl_rm()
 			init_stat()
+			hdl_ready()
+			hdl_start()
+			mov.en = True
+
 		elif v == 'clear':
-			init_stat(clear=True)
+			hdl_stop()
+			hdl_timer_cancel()
+			kon.sleep(2.0)
+			hdl_rm()
 			init_map()
+			init_stat(clear=True)
+			hdl_ready()
+			hdl_start()
+			mov.en = True
+
 		elif v == 'over':
-			run = False
+			break
+
+		game_ev.clear()

 if __name__ == "__main__":
 	kon.main( work, '[-bw] [-m]' )
diff -urN v2/pato.txt v3/pato.txt
--- v2/pato.txt	1970-01-01 09:00:00.000000000 +0900
+++ v3/pato.txt	2017-10-17 03:15:12.000000000 +0900
@@ -0,0 +1,23 @@
+#####################
+#dllll    #    rrrrd#
+#d###u### # ###u###d#
+#d###u### # ###u###d#
+#d###u### # ###u###d#
+#rrrru         ullll#
+# ### # ##### # ### #
+#     #   #   #     #
+##### ### # ### #####
+    # #       # #
+##### # ## ## # #####
+        #   #
+##### # ##### # #####
+    # #       # #
+##### # ##### # #####
+#rrrrd    #    dllll#
+#u###d### # ###d###u#
+#ull#d         d#rru#
+###u#d# ##### #d#u###
+#  ull#   #   #rru  #
+# ####### # ####### #
+#                   #
+#####################
diff -urN v2/warp.txt v3/warp.txt
--- v2/warp.txt	2017-10-06 22:00:00.000000000 +0900
+++ v3/warp.txt	2017-10-17 03:15:12.000000000 +0900
@@ -4,9 +4,9 @@
 # ### ### # ### ### #
 # ### ### # ### ### #
 #                   #
-# ### #u#####u# ### #
+# ### # ##### # ### #
 #     #   #   #     #
-##### ### # ### #####
+##### ###d#d### #####
     # #       # #
 ##### # ##u## # #####
 L4321   #   #   1234R
@@ -14,7 +14,7 @@
     # #       # #
 ##### # ##### # #####
 #         #         #
-# ### ### # ### ### #
+# ### ###d#d### ### #
 #   #           #   #
 ### # # ##### # # ###
 #     #   #   #     #

$ cat v3.patch | patch -p1
$ chmod +x pac.py
$ ./pac.py

昔の記憶で適当に作ってましたが、少し調べてみました。


第4弾

v4.patch
diff -ur v3/pac.py v4/pac.py
--- v3/pac.py	2017-10-17 03:15:30.000000000 +0900
+++ v4/pac.py	2017-10-17 23:00:00.000000000 +0900
@@ -14,6 +14,8 @@
 MONS = 'RCMY'
 MOVES = 'P' + MONS

+dot_n = len( pac_buf.poss('.') + pac_buf.poss('O') )
+
 stat_inf = dict( map( lambda c: ( c, kon.Empty() ), MOVES ) )
 stat_get = lambda c: stat_inf.get(c)

@@ -71,16 +73,13 @@

 	mon_price.check()

-	stat.th.set_hz( get_hz( c, *arr.pos(c) ) )
-
-
 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

 (w, h) = pac_buf.size()
-bit_cs = MOVES + 'O.=#'
+bit_cs = MOVES + 'O.=#B'
 arr = kon.arr_xy_bit_c_move_new( w, h, bit_cs, arr_xy_to_show_xy)

 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) )
@@ -118,20 +117,20 @@
 next_ds = lambda x, y, c: filter( lambda d: is_step(x, y, c, d), DIRS )

 def arr_msg_pause(show_lst, x=None, y=None, sec=1.0, xcen=True):
-	game_ev.set('stop')
+	ths_stop()
 	(bx, by) = pac_buf.pos('B')
 	x = bx if x == None else x
 	y = by if y == None else y
 	arr.msg_pause(x, y, show_lst, sec, xcen)
-	game_ev.set('start')
+	ths_start()

 def pac_slider(slider):
-	game_ev.set('stop')
+	ths_stop()
 	c = 'P'
 	bak = arr.get_sdic(c)
 	arr.set_sdic( c, ('slider', slider) )
 	slider.wait()
-	game_ev.set('start')
+	ths_start()
 	return bak

 game_ev = kon.evt()
@@ -159,13 +158,8 @@
 			break
 		elif mode != 'ret' and not NO_DIE:
 			mov.en = False
-			ss = [ '()', '||', ')(', '><', '--', '><', '--', '><', '--', '__', '  ' ]
-			lsts = map( lambda s: mk_slst(s, 'yellow', True), ss )
-			lsts.append( [ SPC ] )
-			bak = pac_slider( kon.slider(lsts, 5.0, None, True, repeat=False) )
 			v = 'over' if spare(-1) == 'over' else 'die'
 			game_ev.set(v)
-			arr.set_sdic('P', bak)
 			break

 def get_hz(c, nx, ny):
@@ -212,25 +206,79 @@
 			arr.set_dir( c, kon.bak_dir( arr.get_dir(c) ) )
 			stat.timer.start( tmr_f, [c], short_sec(15.0) )

+round = kon.gval(1)
+
+def bonus_new():
+	c = 'B'
+	(x, y) = pac_buf.pos(c)
+
+	cherry = ( [ ('8', 'red', True), ('>', 'green', False) ], 100 )
+	strawberry = ( [ ('<', 'magenta', True), ('k', 'green', False) ], 300 )
+	orange = ( [ ('O', 'yellow', 'True'), ('-', 'green', False) ], 500 )
+	apple = ( [ ('()', 'red', True) ], 700 )
+	melon = ( [ ('<>', 'green', True) ], 1000 )
+	galaxyan = ( [ ('<', 'red', False), ('<', 'blue', False) ], 2000 )
+	bell =( [ ('.>', 'white', True) ], 3000 )
+	key = ( [ ('=', 'white', False), ('m', 'cyan', False) ], 5000 )
+
+	d = {
+		1: cherry, 2: strawberry,
+		3: orange, 4: orange,
+		5: apple, 6: apple,
+		7: melon, 8: melon,
+		9: galaxyan, 10: galaxyan,
+		11: bell, 12: bell,
+	}
+	get_inf = lambda: d.get( round(), key )
+	get_slst = lambda: get_inf()[0]
+
+	tmr = kon.timer_new()
+
+	e = kon.Empty()
+	e.eat_cnt = 0
+	e.get_v = lambda: get_inf()[1]
+
+	def hide():
+		tmr.cancel()
+		arr.rm(c, flush_f=True)
+	e.hide = hide
+	def show():
+		hide()
+		arr.set_sdic( c, ( '', get_slst() ) )
+		arr.set( x, y, c, flush_f=True )
+		sec = short_sec(15.0)
+		tmr.start( hide, [], sec )
+	e.show = show
+	def eat():
+		e.eat_cnt += 1
+		hide()
+		(vx, vy) = arr_xy_to_show_xy( *pac_buf.size() )
+		vx -= 3 * e.eat_cnt
+		kon.shows(vx, vy, get_slst(), flush_f=True)
+	e.eat = eat
+	return e
+
+bonus = bonus_new()
+
 def try_eat(x, y):
-	c = '.' if arr.get(x, y, '.') else 'O' if arr.get(x, y, 'O') else ''
-	if not c:
+	lst = filter( lambda c: c in '.OB', arr.gets(x, y) )
+	if not lst:
 		return
-	arr.set(x, y, c, False)
-	stat_get('P').dot -= 1
+	c = lst[0]
+
+	if c in '.O':
+		arr.set(x, y, c, False)
+		stat_get('P').dot -= 1
+	else:
+		bonus.eat()

-	score( 50 if c == 'O' else 10 )
+	score( 50 if c == 'O' else 10 if c == '.' else bonus.get_v() )
 	if c == 'O':
 		mon_weak()
+	if c in '.O' and dot_n - stat_get('P').dot in (70, 170):
+		bonus.show()
 	if stat_get('P').dot <= 0:
 		mov.en = False
-		c = '#'
-		bak = arr.get_sdic(c)
-		lst = map( lambda col: mk_slst('==', col, False), ('white', 'blue') )
-		slider = kon.slider(lst, 4.0, None, True)
-		arr.set_sdic( c, ('slider', slider) )
-		kon.sleep(2.0)
-		arr.set_sdic(c, bak)
 		game_ev.set('clear')

 kdir = kon.gval('')
@@ -311,7 +359,6 @@
 	return rand_sel(ds)

 def th_mon(c):
-	stat = stat_get(c)
 	(px, py) = arr.pos(c)
 	ds = next_ds(px, py, c)

@@ -327,6 +374,8 @@
 	elif mode == 'ret' and mon_ret_buf.get(nx, ny) == ' ':
 		mon_mode(c, 'cage')

+	stat_get(c).th.set_hz(hz)
+
 def score_new():
 	(x, y, lb, col, r) = (0, 0, 'score ', 'white', False)
 	e = kon.Empty()
@@ -380,18 +429,43 @@
 mon_turn = lambda: map( lambda c: arr.set_dir( c, kon.bak_dir( arr.get_dir(c) ) ), MONS )
 run_mode = kon.slider( ['pato', 'chase'], f=mon_turn )

-def work():
+def init_once():
 	kon.clr()

-	dot_n = len( pac_buf.poss('.') + pac_buf.poss('O') )
+	lst = map( lambda r: mk_slst('O ', 'yellow', r), (False, True) )
+	arr.set_sdic( 'O', ( 'slider', kon.slider(lst, 2.0, None, True) ) )
+
+	arr.set_sdic( 'P', ('func', pac_show) )
+	arr.set_sdic( '#', ('func', blk_show) )
+	arr.set_sdic( '.', ( '', mk_slst('. ', 'white', False) ) )
+	arr.set_sdic( '=', ( '', mk_slst('==', 'yellow', False) ) )
+
+	kon.th_loop(th_key)
+
+	score()
+	spare()
+
+	map( lambda c: set( stat_get(c), 'hz', 3.0 if c == 'P' else 3.3 ), MOVES )
+	map( lambda c: set( stat_get(c), 'timer', kon.timer_new() ), MONS )
+
+	for c in MOVES:
+		stat = stat_get(c)
+		f = th_pac if c == 'P' else th_mon
+		stat.th = kon.th_loop(f, [c], stat.hz, run=False)
+
+ths_start = lambda: map( lambda c: stat_get(c).th.start(), MOVES )
+ths_stop = lambda: map( lambda c: stat_get(c).th.stop(), MOVES )
+ths_timer_cancel = lambda: map( lambda c: stat_get(c).timer.cancel(), MONS )
+
+def work():
+	init_once()

 	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)
+		f = lambda x, y: arr.set( x, y, pac_buf.get(x, y) )
+		(w, h) = pac_buf.size()
 		kon.loop_xy(w, h, f)
+		arr.rm('B')
 		flush()

 	def init_stat(clear=False):
@@ -415,70 +489,59 @@
 		run_mode.set_hz( mon_r_hz / 100.0 )
 		run_mode.restart()

-	lst = map( lambda r: mk_slst('O ', 'yellow', r), (False, True) )
-	arr.set_sdic( 'O', ( 'slider', kon.slider(lst, 2.0, None, True) ) )
-
-	arr.set_sdic( 'P', ('func', pac_show) )
-	arr.set_sdic( '#', ('func', blk_show) )
-	arr.set_sdic( '.', ( '', mk_slst('. ', 'white', False) ) )
-	arr.set_sdic( '=', ( '', mk_slst('==', 'yellow', False) ) )
-
-	kon.th_loop(th_key)
-	score()
-	spare()
-
 	stat_get('P').dot = dot_n
-	map( lambda c: set( stat_get(c), 'hz', 3.0 if c == 'P' else 3.3 ), MOVES )
-	map( lambda c: set( stat_get(c), 'timer', kon.timer_new() ), MONS )
-
-	for c in MOVES:
-		stat = stat_get(c)
-		f = th_pac if c == 'P' else th_mon
-		stat.th = kon.th_loop(f, [c], stat.hz, run=False)

-	hdl_ready = lambda: arr_msg_pause( mk_slst('READY!', 'yellow'), *pac_buf.pos('B') )
-	hdl_start = lambda: map( lambda c: stat_get(c).th.start(), MOVES )
-	hdl_stop = lambda: map( lambda c: stat_get(c).th.stop(), MOVES )
-	hdl_timer_cancel = lambda: map( lambda c: stat_get(c).timer.cancel(), MONS )
-	hdl_rm = lambda: ( map( lambda c: arr.rm(c), MOVES ), arr.update_all(flush_f=True) )[-1]
+	def ready_go():
+		arr_msg_pause( mk_slst('READY!', 'yellow'), *pac_buf.pos('B') )
+		ths_start()
+		mov.en = True
+
+	def goal():
+		ths_stop()
+		ths_timer_cancel()
+		kon.sleep(2.0)
+		map( lambda c: arr.rm(c), MOVES )
+		bonus.hide()
+		arr.update_all(flush_f=True)

 	init_map()
 	init_stat()
-	hdl_ready()
-	hdl_start()
+	ready_go()

 	while True:
 		while not game_ev.wait(1.0):
 			kon.sleep_hz(1.0) # for ^C

 		v = game_ev.get()
-		if v == 'start':
-			hdl_start()
-		elif v == 'stop':
-			hdl_stop()
-
-		elif v == 'die':
-			hdl_stop()
-			hdl_timer_cancel()
-			kon.sleep(2.0)
-			hdl_rm()
+		if v == 'die':
+			ss = [ '()', '||', ')(', '><', '--', '><', '--', '><', '--', '__', '  ' ]
+			lsts = map( lambda s: mk_slst(s, 'yellow', True), ss )
+			lsts.append( [ SPC ] )
+			bak = pac_slider( kon.slider(lsts, 5.0, None, True, repeat=False) )
+
+			goal()
 			init_stat()
-			hdl_ready()
-			hdl_start()
-			mov.en = True
+			arr.set_sdic('P', bak)
+			ready_go()

 		elif v == 'clear':
-			hdl_stop()
-			hdl_timer_cancel()
-			kon.sleep(2.0)
-			hdl_rm()
+			c = '#'
+			bak = arr.get_sdic(c)
+			lst = map( lambda col: mk_slst('==', col, False), ('white', 'blue') )
+			slider = kon.slider(lst, 4.0, None, True)
+			arr.set_sdic( c, ('slider', slider) )
+			kon.sleep(3.0)
+			arr.set_sdic(c, bak)
+
+			goal()
+			round( round() + 1 )
 			init_map()
 			init_stat(clear=True)
-			hdl_ready()
-			hdl_start()
-			mov.en = True
+			ready_go()

 		elif v == 'over':
+			(x, y) = pac_buf.pos('B')
+			arr_msg_pause( mk_slst('GAME OVER', 'red'), x, y, 2.0 )
 			break

 		game_ev.clear()

$ cat v4.patch | patch -p1
$ chmod +x pac.py
$ ./pac.py

クロージャと空クラス


第5弾

v5.patch
diff -ur v4/kon.py v5/kon.py
--- v4/kon.py	2017-10-17 23:00:00.000000000 +0900
+++ v5/kon.py	2017-10-19 23:00:00.000000000 +0900
@@ -6,13 +6,49 @@
 import termios
 import select
 import threading
+import socket

 def dbg(s, *arg):
 	sys.stderr.write(s.format(*arg) + os.linesep)
 	sys.stdout.flush()

-def out(s):
-	sys.stdout.write(s)
+def get_arg(k):
+	# -k -k=xxx --k --k=xxx
+	for ds in ('-', '--'):
+		t = ds + k
+		if t in sys.argv:
+			return ''
+		t += '='
+		r = next( ( a[ len(t): ] for a in sys.argv if a.startwith(t) ), None )
+		if r != None:
+			return r
+	return None
+
+def get_host_port(s, host='localhost', port=12345):
+	if not s:
+		return (host, port)
+	if ':' in s:
+		(h, p) = s.split(':')
+		if h:
+			host = h
+		if p:
+			port = p
+	else:
+		if s.isdigit():
+			port = int(s)
+		else:
+			host = s
+	return (host, port)
+
+def out_new():
+	sock = None
+	s = get_arg('o')
+	if s != None:
+		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		sock.connect( get_host_port(s, port=55765) )
+	return lambda s: ( sock.sendall if sock else sys.stdout.write )(s)
+
+out = out_new()

 def flush():
 	sys.stdout.flush()
diff -ur v4/pac.py v5/pac.py
--- v4/pac.py	2017-10-17 23:00:00.000000000 +0900
+++ v5/pac.py	2017-10-19 23:00:00.000000000 +0900
@@ -158,17 +158,20 @@
 			break
 		elif mode != 'ret' and not NO_DIE:
 			mov.en = False
-			v = 'over' if spare(-1) == 'over' else 'die'
+			v = 'over' if spare(-1) == 'over' or is_remote else 'die'
 			game_ev.set(v)
 			break

-def get_hz(c, nx, ny):
+def get_hz(c, nx, ny, nd):
 	hz = stat_get(c).hz
 	if c in MONS:
 		mode = mon_mode(c)
 		hz *= mon_hz_rate.get(mode, 1.0)
 		if mode != 'ret':
 			hz *= warp_rate.get( warp_buf.get(nx, ny), 1.0 )
+	if c == 'P':
+		if nd != arr.get_dir(c):
+			hz *= 1.5
 	return hz

 def move_new():
@@ -288,20 +291,36 @@
 	if d in DIRS and d != kdir():
 		kdir(d)

+is_remote = '-r' in sys.argv
+
+def pac_remote(pd, ds):
+	ds = filter( lambda d: d != kon.bak_dir(pd), ds )
+	if len(ds) == 1:
+		return ds[0]
+
+	d = sys.stdin.read(1)
+	if d not in ds:
+		sys.stdout.write('?')
+	sys.stdout.write(d)
+	flush()
+
 def th_pac(c):
-	stat = stat_get(c)
 	(px, py) = arr.pos(c)
 	pd = arr.get_dir(c)
-
 	ds = next_ds(px, py, c)
-	kd = kdir()
+	d = ''
+	if is_remote:
+		d = pac_remote(pd, ds)
+	else:
+		kd = kdir()
+		d = kd if kd and kd in ds else pd if pd in ds else ''

-	d = kd if kd and kd in ds else pd if pd in ds else ''
 	(nx, ny) = step_to(px, py, d)
 	if d:
-		hz = get_hz(c, nx, ny)
+		hz = get_hz(c, nx, ny, d)
 		move(c, d, hz)
 		try_eat(nx, ny)
+		stat_get(c).th.set_hz(hz)

 def rand(n):
 	v = sum( sum( map( lambda c: list( arr.pos(c) ), MOVES ), [] ) )
@@ -365,7 +384,7 @@
 	d = sel_mon_way(c, px, py, ds)

 	(nx, ny) = step_to(px, py, d)
-	hz = get_hz(c, nx, ny)
+	hz = get_hz(c, nx, ny, d)
 	move(c, d, hz)

 	mode = mon_mode(c)
@@ -547,5 +566,5 @@
 		game_ev.clear()

 if __name__ == "__main__":
-	kon.main( work, '[-bw] [-m]' )
+	kon.main( work, '[-bw] [-m] [-o[=host:port]] [-r]' )
 # EOF

$ cat v5.patch | patch -p1
$ chmod +x pac.py
$ ./pac.py

「角を曲がるときはモンスターより速度が速くなる」と言う情報がありました。

そう言えば昔ゲーセンで、内側の迷路をクネクネ曲がって逃げてると、 うまく振り切れていた覚えがあります。

安易に「向きを変える瞬間だけ速度アップ」処理を追加してみました。

標準出力を他に使いたくなったので、出力先を別のホストのポートに飛ばせるようにしてみました。

-o=ホスト名:ポート番号
または
--o=ホスト名:ポート番号

ホスト名を省略すると localhost
ポート番号を省略すると 55765

で指定します。

「クリアパターン」を探すために、「リモートモード」なるものを追加してみました。

起動オプション -r でリモートモードになる事にします。

リモートモードでは、

リモートモードの時は、この用途で標準出力を使うので、-o も合わせて指定するようにします。

そして、「クリアパターン」を探すプログラム...


第6弾

v6.patch
diff -urN v5/fuga.py v6/fuga.py
--- v5/fuga.py	1970-01-01 09:00:00.000000000 +0900
+++ v6/fuga.py	2018-02-21 13:09:19.000000000 +0900
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+import sys
+import time
+import random
+
+if __name__ == "__main__":
+
+	err = lambda s: sys.stderr.write(s+'\n')
+
+	while True:
+		s = sys.stdin.readline().strip()
+		err('get "{}"'.format(s))
+
+		i = random.randrange(len(s))
+		err('out "{}"'.format( s[i] ) )
+		print s[i]
+		sys.stdout.flush()
+# EOF
diff -urN v5/kon.py v6/kon.py
--- v5/kon.py	2017-10-19 23:00:00.000000000 +0900
+++ v6/kon.py	2018-02-21 13:09:19.000000000 +0900
@@ -19,7 +19,7 @@
 		if t in sys.argv:
 			return ''
 		t += '='
-		r = next( ( a[ len(t): ] for a in sys.argv if a.startwith(t) ), None )
+		r = next( ( a[ len(t): ] for a in sys.argv if a.startswith(t) ), None )
 		if r != None:
 			return r
 	return None
@@ -32,7 +32,7 @@
 		if h:
 			host = h
 		if p:
-			port = p
+			port = int(p)
 	else:
 		if s.isdigit():
 			port = int(s)
@@ -236,7 +236,11 @@

 def term_raw(sigint=True):
 	fd = sys.stdin.fileno()
-	a = termios.tcgetattr(fd)
+	try:
+		a = termios.tcgetattr(fd)
+	except:
+		return lambda : None
+
 	bak = a[:]
 	f = termios.ECHO | termios.ECHONL | termios.FLUSHO | termios.ICANON | termios.IEXTEN
 	if not sigint:
diff -urN v5/pac.py v6/pac.py
--- v5/pac.py	2017-10-19 23:00:00.000000000 +0900
+++ v6/pac.py	2018-02-21 13:09:19.000000000 +0900
@@ -298,11 +298,13 @@
 	if len(ds) == 1:
 		return ds[0]

-	d = sys.stdin.read(1)
+	sys.stdout.write(ds + '\n')
+	flush()
+	d = sys.stdin.readline().strip()
 	if d not in ds:
 		sys.stdout.write('?')
-	sys.stdout.write(d)
-	flush()
+		d = ds[0]
+	return d

 def th_pac(c):
 	(px, py) = arr.pos(c)
diff -urN v5/stdio.py v6/stdio.py
--- v5/stdio.py	1970-01-01 09:00:00.000000000 +0900
+++ v6/stdio.py	2018-02-21 12:28:53.000000000 +0900
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+import sys
+import subprocess
+
+#
+# ./stdio.py "./hoge.py 123" ./hoge.py
+#
+
+if __name__ == "__main__":
+
+	if len(sys.argv) < 3:
+		print 'Usage: {} "cmd1 prm .." "cmd2 prm .."'.format(sys.argv[0])
+		sys.exit(0)
+
+	cmd1 = sys.argv[1]
+	cmd2 = sys.argv[2]
+	cmd = 'rm -f fifo ; mkfifo fifo ; cat fifo | {} | {} > fifo'.format(cmd1, cmd2)
+	#print cmd
+
+	subprocess.call(cmd, shell=True)
+
+# EOF

随分間が空いて年も変わりましたが、少しだけ更新してみます。

リモートモードを試してみると、案の定バグを発見。 修正しつつ、仕様も少し変えました。

$ cat v6.patch | patch -p1

stdio.py と fuga.py を追加しました。

stdio.py は2つの引数を取り、2つのコマンドとして起動します。 その2つのコマンドの標準入出力を、互い違いに繋ぎます。

fuga.py はリモートモードの pac.py の相手をする、 プログラムの雛形です。 今はとりあえず、乱数で、でたらめなキー操作になってます。

まず表示用に別の端末から

other-term $ nc -l -k 55765

などと netcat で 55765 ポートで待ち受けておいて

$ ./stdio.py "./pac.py -o -r" ./fuga.py

として、pac.py と fuga.py の標準入出力を相互に繋いだ状態で起動します。

pac.py が岐路に差し掛かると、選択肢の候補を fuga.py に出力します。

fuga.py は受け取った候補からランダムに1つ選んで、pac.py に返答します。

pac.py は受け取った方向に進めようとします。

とりあえず動作確認だけなので、すぐGAME OVERです :-p)


第7弾

v7.patch
diff -urN v6/kon.py v7/kon.py
--- v6/kon.py	2018-02-21 13:09:19.000000000 +0900
+++ v7/kon.py	2018-06-22 23:10:50.000000000 +0900
@@ -8,6 +8,14 @@
 import threading
 import socket

+map_lst = lambda f, lst: list( map( f, lst ) )
+map_up = lambda f, lst: map( lambda a: f(*a), lst )
+map_up_lst = lambda f, lst: list( map_up( f, lst ) )
+
+filter_lst = lambda f, lst: list( filter( f, lst ) )
+filter_up = lambda f, lst: filter( lambda a: f(*a), lst )
+filter_up_lst = lambda f, lst: list( filter_up(f, lst) )
+
 def dbg(s, *arg):
 	sys.stderr.write(s.format(*arg) + os.linesep)
 	sys.stdout.flush()
@@ -46,7 +54,7 @@
 	if s != None:
 		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 		sock.connect( get_host_port(s, port=55765) )
-	return lambda s: ( sock.sendall if sock else sys.stdout.write )(s)
+	return lambda s: ( sock.sendall if sock else sys.stdout.write )( s.encode() )

 out = out_new()

@@ -84,13 +92,13 @@
 			dic_to_cls( cls_to_dic(pcls), self )

 def cls_to_dic(c):
-	ks = filter( lambda k: not k.startswith('_'), dir(c) )
-	return dict( map( lambda k: ( k, getattr(c, k) ), ks ) )
+	ks = filter_lst( lambda k: not k.startswith('_'), dir(c) )
+	return dict( map_lst( lambda k: ( k, getattr(c, k) ), ks ) )

 def dic_to_cls(d, c=None):
 	if not c:
 		c = Empty()
-	map( lambda (k, v): setattr(c, k, v), d.items() )
+	map_up_lst( lambda k, v: setattr(c, k, v), d.items() )
 	return c

 def extends(pcls, c=None):
@@ -130,7 +138,7 @@
 	e.f = wrap_f
 	return e

-updiv = lambda a, b: (a + b - 1) / b
+updiv = lambda a, b: int( (a + b - 1) / b )

 hz_to_sec = lambda hz: 1.0 / hz if hz > 0 else 0
 sec_to_hz = lambda sec: 1.0 / sec if sec > 0 else 0
@@ -255,7 +263,8 @@

 def getkey(tmout=None):
 	fd = sys.stdin.fileno()
-	return os.read(fd, 1) if readable(fd, tmout) else ''
+	#return os.read(fd, 1) if readable(fd, tmout) else ''
+	return os.read(fd, 1).decode() if readable(fd, tmout) else ''

 esc = lambda s: chr(0x1b) + '[' + s
 esc_ex = lambda d, v: esc( '?{}{}'.format(d, 'h' if v else 'l' ) )
@@ -263,7 +272,7 @@
 DIRS = ( U, D, L, R ) = 'udlr'
 bak_dir = lambda d: { U: D, D: U, L: R, R: L }.get(d)
 dir_to_xy = lambda d: { U: (0, -1), D: (0, 1), L: (-1, 0), R: (1, 0) }.get(d, (0, 0) )
-step_to = lambda x, y, d: ( lambda (dx, dy): (x+dx, y+dy) )( dir_to_xy(d) )
+step_to = lambda x, y, d: ( lambda dx, dy: (x+dx, y+dy) )( *dir_to_xy(d) )

 def keydir(tmout=None):
 	ctl = lambda s: chr( 1 + ord(s) - ord('a') )
@@ -272,12 +281,12 @@
 		[ ctl('p'), ctl('n'), ctl('b'), ctl('f') ], # emacs
 		[ 'k', 'j', 'h', 'l' ], # vi
 	]
-	d = dict( sum( map( lambda k4: zip(k4, DIRS), lst ), [] ) )
+	d = dict( sum( map_lst( lambda k4: list( zip(k4, DIRS) ), lst ), [] ) )
 	d.update({ ctl('a'): L, ctl('e'): R}) # emacs
 	d.update({ '\n': U, '\t': 'U', ' ': 'U' })
 	keys = d.keys()
 	buf = getkey(tmout)
-	while buf and any( map( lambda k: k.startswith(buf), keys ) ):
+	while buf and any( map_lst( lambda k: k.startswith(buf), keys ) ):
 		if buf in keys:
 			return d.get(buf)
 		buf += getkey(tmout)
@@ -331,23 +340,25 @@
 	(s_, col_, r_) = lst[0]
 	return mk_slst(s + s_, col, r) + lst[1:] if col == col_ and r == r_ else [a] + lst

-split_show_lst = lambda lst: sum( map( lambda (s, col, r): map( lambda c: (c, col, r), s ), lst ), [] )
+split_show_lst = lambda lst: sum( map_up_lst( lambda s, col, r: map_lst( lambda c: (c, col, r), s ), lst ), [] )

-show_lst_len = lambda lst: sum( map( lambda (s, col, r): len(s), lst ) )
+show_lst_len = lambda lst: sum( map_up_lst( lambda s, col, r: len(s), lst ) )

 def fit_show_lsts(show_lsts):
-	ns = map( show_lst_len, show_lsts )
+	ns = map_lst( show_lst_len, show_lsts )
 	max_n = max(ns)
-	return map( lambda (lst, n): sum_show_lst( lst + mk_slst(' ') * (max_n - n) ), zip( show_lsts, ns ) )
+	return map_up_lst( lambda lst, n: sum_show_lst( lst + mk_slst(' ') * (max_n - n) ), zip( show_lsts, ns ) )

 def shows(x, y, lst, flush_f=False):
-	x = reduce( lambda x, (s, col, r): show(x, y, s, col, r), sum_show_lst(lst), x )
+	#x = reduce( lambda x, s_col_r: show(x, y, *s_col_r), sum_show_lst(lst), x )
+	for (s, col, r) in sum_show_lst(lst):
+		x = show(x, y, s, col, r)
 	if flush_f:
 		flush()
 	return x

 def over_show_lst(l, h):
-	get_col = lambda (s, col, r): col
+	get_col = lambda s_col_r: s_col_r[1]
 	f = lambda l, h: l if get_col(h) == 'trans' else h
 	l = split_show_lst(l)
 	h = split_show_lst(h)
@@ -355,10 +366,10 @@
 	apd = mk_slst(' ', 'trans') * n
 	l = ( l + apd )[:n]
 	h = ( h + apd )[:n]
-	o = map( lambda (l, h): f(l, h), zip(l, h) )
+	o = map_up_lst( lambda l, h: f(l, h), zip(l, h) )
 	return sum_show_lst(o)

-trans_to_spc_show_lst = lambda lst: map( lambda (s, col, r): (' ' * len(s), '', False) if col == 'trans' else (s, col, r), lst )
+trans_to_spc_show_lst = lambda lst: map_up_lst( lambda s, col, r: (' ' * len(s), '', False) if col == 'trans' else (s, col, r), lst )

 def bit_w(v): # only positive value
 	w = 0
@@ -371,12 +382,12 @@
 	sv = f(s)
 	ev = f(e)
 	while e - s > 0:
-		m = (s + e) / 2
+		m = int( (s + e) / 2 )
 		mv = f(m)
 		(s, e, sv, ev) = (s, m, sv, mv) if mv < v else (m, e, mv, ev)
 	return s

-loop_xy = lambda w, h, f: sum( map( lambda y: map( lambda x: f(x, y), range(w) ), range(h) ), [] )
+loop_xy = lambda w, h, f: sum( map_lst( lambda y: map_lst( lambda x: f(x, y), range(w) ), range(h) ), [] )
 loop_xywh = lambda x, y, w, h, f: loop_xy( w, h, lambda dx, dy: f(x+dx, y+dy) )

 lst_xy = lambda w, h: loop_xy( w, h, lambda x, y: (x, y) )
@@ -388,17 +399,17 @@
 into_x = lambda x, w, ow: max( min( ow - w, x ), 0 )
 into_xy = lambda x, y, w, h, ow, oh: ( into_x(x, w, ow), into_x(y, h, oh) )

-cen_xywh = lambda x, y, w, h: (x + w/2, y + h/2)
+cen_xywh = lambda x, y, w, h: ( x + int(w/2), y + int(h/2) )

 def arr_xy_new(w, h, v):
 	e = Empty()
-	e.data = data = map( lambda _: [ v ] * w, range(h) )
+	e.data = data = map_lst( lambda _: [ v ] * w, range(h) )
 	e.size = lambda: (w, h)
 	e.chk_xy = lambda x, y: chk_xy(x, y, w, h)
 	e.get = lambda x, y, v=None: data[y][x] if e.chk_xy(x, y) else v
 	e.set = lambda x, y, c: set( data[y], x, c ) if e.chk_xy(x, y) else None
 	e.clear = lambda v: loop_xy( w, h, lambda x, y: e.set(x, y, v) )
-	e.poss = lambda v: filter( lambda (x, y): e.get(x, y) == v, lst_xy(w, h) )
+	e.poss = lambda v: filter_up_lst( lambda x, y: e.get(x, y) == v, lst_xy(w, h) )
 	e.pos = lambda v: next( iter( e.poss(v) ), (-1, -1) )
 	return e

@@ -425,7 +436,7 @@
 	e.get = lambda x, y, bit: arr.get(x, y, 0) & pat(bit)
 	e.set = lock_wrap( set_ ).f
 	e.clear = lambda: arr.clear(0)
-	e.gets = lambda x, y: filter( lambda bit: e.get(x, y, bit), range( bit_w( arr.get(x, y, 0) ) ) )
+	e.gets = lambda x, y: filter_lst( lambda bit: e.get(x, y, bit), range( bit_w( arr.get(x, y, 0) ) ) )
 	e.poss = lambda bit: pos_d.get(bit, [])
 	e.pos = lambda bit: next( iter( e.poss(bit) ), (-1, -1) )
 	return e
@@ -436,7 +447,7 @@
 	e.to_bit = to_bit = lambda c: bit_cs.index(c) if c in bit_cs else -1
 	e.get = lambda x, y, c: arr.get( x, y, to_bit(c) )
 	e.set = lambda x, y, c, v: arr.set( x ,y, to_bit(c), v )
-	e.gets = lambda x, y: map( lambda bit: bit_cs[bit], arr.gets(x, y) )
+	e.gets = lambda x, y: map_lst( lambda bit: bit_cs[bit], arr.gets(x, y) )
 	e.poss = lambda c: arr.poss( to_bit(c) )
 	e.pos = lambda c: next( iter( e.poss(c) ), (-1, -1) )
 	return e
@@ -475,7 +486,7 @@
 	def show_lst_area(x, y, show_lst, xcen=False):
 		(w, h) = e.show_lst_wh(show_lst)
 		if xcen:
-			x -= (w - 1) / 2
+			x -= int( (w - 1) / 2 )
 		(ow, oh) = e.size()
 		(x, y) = into_xy(x, y, w, h, ow, oh)
 		return (x, y, w, h)
@@ -500,23 +511,28 @@
 		if get_dx:
 			for ix in (-1, 0, 1):
 				x_ = (x + ix + w) % w
-				cs = filter( lambda c: ix == 0 or get_dx(c) == -ix, e.gets(x_, y) )
-				lst += map( lambda c: ( c, x_, ix if ix else get_dx(c) ), cs )
+				cs = filter_lst( lambda c: ix == 0 or get_dx(c) == -ix, e.gets(x_, y) )
+				lst += map_lst( lambda c: ( c, x_, ix if ix else get_dx(c) ), cs )

 			TRANS = mk_slst('  ', 'trans')
 			shift = lambda lst, dx: split_show_lst( lst + TRANS if dx < 0 else TRANS + lst )[1:3] if dx else lst
-			lst = map( lambda (c, x_, dx): ( c, shift( e.get_show_lst(c, x_, y), dx ) ), lst )
+			lst = map_up_lst( lambda c, x_, dx: ( c, shift( e.get_show_lst(c, x_, y), dx ) ), lst )
 		else:
-			lst = map( lambda c: ( c, e.get_show_lst(c, x, y) ), e.gets(x, y) )
+			lst = map_lst( lambda c: ( c, e.get_show_lst(c, x, y) ), e.gets(x, y) )
+
+		lst.sort( key=lambda c_show_lst: e.to_bit( c_show_lst[0] ) )
+		show_lsts = list( zip(*lst) )[1] if lst else []
+
+		#show_lst = reduce( over_show_lst, reversed( show_lsts ), [] )
+		show_lst = []
+		for a in reversed( show_lsts ):
+			show_lst = over_show_lst( show_lst, a )

-		lst.sort( key=lambda (c, show_lst): e.to_bit(c) )
-		show_lsts = zip(*lst)[1] if lst else []
-		show_lst = reduce( over_show_lst, reversed( show_lsts ), [] )
 		show_lst = trans_to_spc_show_lst(show_lst) if show_lst else mk_slst('  ')
 		e.shows(x, y, show_lst, flush_f)

 	e.update = update
-	e.update_xys = lambda xys, flush_f=False: ( map( lambda (x, y): e.update(x, y), xys ), e.flush(flush_f) )[-1]
+	e.update_xys = lambda xys, flush_f=False: ( map_up_lst( lambda x, y: e.update(x, y), xys ), e.flush(flush_f) )[-1]
 	e.update_area = lambda x, y, w, h, flush_f=False: e.update_xys( lst_xywh(x, y, w, h), flush_f )
 	e.update_line = lambda x, y, w, flush_f=False: e.update_area(x, y, w, 1, flush_f)
 	e.update_all = lambda flush_f=False: e.update_area(0, 0, w, h, flush_f)
@@ -524,9 +540,9 @@
 	def update_c(c, flush_f=False):
 		dx = get_dx(c) if get_dx else 0
 		xys = e.poss(c)
-		#xys += map( lambda (x, y): (x+dx, y), xys ) if dx else []
+		#xys += map_lst( lambda (x, y): (x+dx, y), xys ) if dx else []
 		if dx:
-			xys = xys + map( lambda (x, y): (x+dx, y), xys )
+			xys = xys + map_up_lst( lambda x, y: (x+dx, y), xys )
 		e.update_xys(xys, flush_f)
 	e.update_c = update_c

@@ -585,7 +601,7 @@
 		arr.update(x, y, flush_f)
 	e.set = set_

-	e.rm = lambda c, flush_f=False: map( lambda (x, y): e.set(x, y, c, False, flush_f), e.poss(c) )
+	e.rm = lambda c, flush_f=False: map_up_lst( lambda x, y: e.set(x, y, c, False, flush_f), e.poss(c) )

 	def msg_pause(x, y, show_lst, sec, xcen=False):
 		e.shows(x, y, show_lst, xcen, flush_f=True)
@@ -601,17 +617,17 @@
 	with open(fname, 'r') as f:
 		buf = f.read().strip().split( os.linesep )
 		h = len(buf)
-		w = max( map(len, buf) )
-		buf = map( lambda s: s + ' ' * ( w - len(s) ), buf ) # fill
+		w = max( map_lst(len, buf) )
+		buf = map_lst( lambda s: s + ' ' * ( w - len(s) ), buf ) # fill

 	arr = arr_xy_new(w, h, ' ')
-	e = extends(arr)
+	e = extends(arr, None)
 	loop_xy( w, h, lambda x, y: arr.set( x, y, buf[y][x] ) )
 	return e

 def main(f, help_msg):
 	if '-h' in sys.argv:
-		print 'Usage: {} {}'.format( sys.argv[0], help_msg )
+		print( 'Usage: {} {}'.format( sys.argv[0], help_msg ) )
 		sys.exit(0)
 	try:
 		restore = term_raw()
diff -urN v6/pac.py v7/pac.py
--- v6/pac.py	2018-02-21 13:09:19.000000000 +0900
+++ v7/pac.py	2018-06-22 23:10:50.000000000 +0900
@@ -8,15 +8,15 @@

 NO_DIE = '-m' in sys.argv

-txts = map( lambda s: s + '.txt', [ 'pac', 'mon_ret', 'warp', 'pato' ] )
-(pac_buf, mon_ret_buf, warp_buf, pato_buf) = map(kon.rbuf, txts)
+txts = kon.map_lst( lambda s: s + '.txt', [ 'pac', 'mon_ret', 'warp', 'pato' ] )
+(pac_buf, mon_ret_buf, warp_buf, pato_buf) = kon.map_lst(kon.rbuf, txts)

 MONS = 'RCMY'
 MOVES = 'P' + MONS

 dot_n = len( pac_buf.poss('.') + pac_buf.poss('O') )

-stat_inf = dict( map( lambda c: ( c, kon.Empty() ), MOVES ) )
+stat_inf = dict( kon.map_lst( lambda c: ( c, kon.Empty() ), MOVES ) )
 stat_get = lambda c: stat_inf.get(c)

 def pac_show(c, x, y):
@@ -26,7 +26,8 @@
 	return mk_slst(s, col, r)

 def blk_show(c, x, y):
-	k = filter( lambda d: pac_buf.get( *kon.step_to(x, y, d) ) == c, DIRS ) # 'udlr'
+	k = kon.filter_lst( lambda d: pac_buf.get( *kon.step_to(x, y, d) ) == c, DIRS ) # 'udlr'
+	k = ''.join(k)
 	s = '=='
 	d = {
 		( 'ud', 'u', 'd', 'udl', 'udr' ): '||',
@@ -34,7 +35,7 @@
 		( 'ur', 'dr' ): ' =',
 		( 'udlr', ): '  ',
 	}
-	s = dict( sum( map( lambda (ks, v): map( lambda k: (k, v), ks ), d.items() ), [] ) ).get(k, s)
+	s = dict( sum( kon.map_up_lst( lambda ks, v: kon.map_lst( lambda k: (k, v), ks ), d.items() ), [] ) ).get(k, s)
 	return mk_slst(s, 'blue', False)

 SPC = ('  ', '', False)
@@ -44,7 +45,7 @@
 	e.v = 1
 	e.get = lambda: e.v
 	def check():
-		if not filter( lambda c: mon_mode(c) in ('weak', 'blink'), MONS ):
+		if not kon.filter_lst( lambda c: mon_mode(c) in ('weak', 'blink'), MONS ):
 			e.v = 1
 	e.check = check
 	e.twice = lambda: set( e, 'v', e.v * 2 )
@@ -114,7 +115,7 @@

 	return nc != '#'

-next_ds = lambda x, y, c: filter( lambda d: is_step(x, y, c, d), DIRS )
+next_ds = lambda x, y, c: kon.filter_lst( lambda d: is_step(x, y, c, d), DIRS )

 def arr_msg_pause(show_lst, x=None, y=None, sec=1.0, xcen=True):
 	ths_stop()
@@ -146,7 +147,7 @@
 		if mode in ('weak', 'blink'):
 			mov.en = False
 			ss = [ '. ', 'o ', 'O ', '<>', '()', '^^' ]
-			lsts = map( lambda s: mk_slst(s, 'yellow', True), ss )
+			lsts = kon.map_lst( lambda s: mk_slst(s, 'yellow', True), ss )
 			bak = pac_slider( kon.slider(lsts, 8.0, None, True, repeat=False) )
 			add = mon_price.get() * 200
 			mon_price.twice()
@@ -264,7 +265,7 @@
 bonus = bonus_new()

 def try_eat(x, y):
-	lst = filter( lambda c: c in '.OB', arr.gets(x, y) )
+	lst = kon.filter_lst( lambda c: c in '.OB', arr.gets(x, y) )
 	if not lst:
 		return
 	c = lst[0]
@@ -294,7 +295,7 @@
 is_remote = '-r' in sys.argv

 def pac_remote(pd, ds):
-	ds = filter( lambda d: d != kon.bak_dir(pd), ds )
+	ds = kon.filter_lst( lambda d: d != kon.bak_dir(pd), ds )
 	if len(ds) == 1:
 		return ds[0]

@@ -325,8 +326,8 @@
 		stat_get(c).th.set_hz(hz)

 def rand(n):
-	v = sum( sum( map( lambda c: list( arr.pos(c) ), MOVES ), [] ) )
-	return ( v / 4 ) % n
+	v = sum( sum( kon.map_lst( lambda c: list( arr.pos(c) ), MOVES ), [] ) )
+	return ( int( v / 4 ) ) % n

 rand_sel = lambda lst: lst[ rand( len(lst) ) ]

@@ -346,7 +347,7 @@
 		return mon_ret_buf.get(x, y)

 	pd = arr.get_dir(c)
-	ds_ = filter( lambda d: d != kon.bak_dir(pd), ds )
+	ds_ = kon.filter_lst( lambda d: d != kon.bak_dir(pd), ds )
 	ds = ds_ if ds_ else ds

 	(tx, ty) = arr.pos('P')
@@ -356,7 +357,7 @@

 	if mode == 'run' and run_mode.get() == 'pato':
 		# MONS = 'RCMY'
-		(w2, h2) = (w/2, h/2)
+		(w2, h2) = ( int(w/2), int(h/2) )
 		area_d = {
 			'R': (w2, 0, w2, h2),
 			'C': (w2, h2, w2, h2),
@@ -429,7 +430,7 @@
 			kon.show( x_, y, s_, col_, r_, flush_f=True )
 	def f(add=0):
 		if add == 0:
-			map( lambda i: draw(True, i), range(e.v) )
+			kon.map_lst( lambda i: draw(True, i), range(e.v) )
 			return ''
 		while add > 0:
 			draw(True, e.v)
@@ -447,13 +448,13 @@

 gate = kon.counter(4)

-mon_turn = lambda: map( lambda c: arr.set_dir( c, kon.bak_dir( arr.get_dir(c) ) ), MONS )
+mon_turn = lambda: kon.map_lst( lambda c: arr.set_dir( c, kon.bak_dir( arr.get_dir(c) ) ), MONS )
 run_mode = kon.slider( ['pato', 'chase'], f=mon_turn )

 def init_once():
 	kon.clr()

-	lst = map( lambda r: mk_slst('O ', 'yellow', r), (False, True) )
+	lst = kon.map_lst( lambda r: mk_slst('O ', 'yellow', r), (False, True) )
 	arr.set_sdic( 'O', ( 'slider', kon.slider(lst, 2.0, None, True) ) )

 	arr.set_sdic( 'P', ('func', pac_show) )
@@ -466,17 +467,17 @@
 	score()
 	spare()

-	map( lambda c: set( stat_get(c), 'hz', 3.0 if c == 'P' else 3.3 ), MOVES )
-	map( lambda c: set( stat_get(c), 'timer', kon.timer_new() ), MONS )
+	kon.map_lst( lambda c: set( stat_get(c), 'hz', 3.0 if c == 'P' else 3.3 ), MOVES )
+	kon.map_lst( lambda c: set( stat_get(c), 'timer', kon.timer_new() ), MONS )

 	for c in MOVES:
 		stat = stat_get(c)
 		f = th_pac if c == 'P' else th_mon
 		stat.th = kon.th_loop(f, [c], stat.hz, run=False)

-ths_start = lambda: map( lambda c: stat_get(c).th.start(), MOVES )
-ths_stop = lambda: map( lambda c: stat_get(c).th.stop(), MOVES )
-ths_timer_cancel = lambda: map( lambda c: stat_get(c).timer.cancel(), MONS )
+ths_start = lambda: kon.map_lst( lambda c: stat_get(c).th.start(), MOVES )
+ths_stop = lambda: kon.map_lst( lambda c: stat_get(c).th.stop(), MOVES )
+ths_timer_cancel = lambda: kon.map_lst( lambda c: stat_get(c).timer.cancel(), MONS )

 def work():
 	init_once()
@@ -521,7 +522,7 @@
 		ths_stop()
 		ths_timer_cancel()
 		kon.sleep(2.0)
-		map( lambda c: arr.rm(c), MOVES )
+		kon.map_lst( lambda c: arr.rm(c), MOVES )
 		bonus.hide()
 		arr.update_all(flush_f=True)

@@ -536,7 +537,7 @@
 		v = game_ev.get()
 		if v == 'die':
 			ss = [ '()', '||', ')(', '><', '--', '><', '--', '><', '--', '__', '  ' ]
-			lsts = map( lambda s: mk_slst(s, 'yellow', True), ss )
+			lsts = kon.map_lst( lambda s: mk_slst(s, 'yellow', True), ss )
 			lsts.append( [ SPC ] )
 			bak = pac_slider( kon.slider(lsts, 5.0, None, True, repeat=False) )

@@ -548,7 +549,7 @@
 		elif v == 'clear':
 			c = '#'
 			bak = arr.get_sdic(c)
-			lst = map( lambda col: mk_slst('==', col, False), ('white', 'blue') )
+			lst = kon.map_lst( lambda col: mk_slst('==', col, False), ('white', 'blue') )
 			slider = kon.slider(lst, 4.0, None, True)
 			arr.set_sdic( c, ('slider', slider) )
 			kon.sleep(3.0)

レイトレーシング 2018春 で、手持ちの環境のpythonのデフォルトのバージョンを 3 に上げてしまいました。

レイトレーシング 2018春パックマンもどき で、使いたいので python3 対応しておきます。

$ cat v7.patch | patch -p1


第8弾

v8.patch
diff -ur v7/kon.py v8/kon.py
--- v7/kon.py	2018-06-22 23:10:50.000000000 +0900
+++ v8/kon.py	2020-11-30 00:09:27.000000000 +0900
@@ -1,6 +1,7 @@
 #!/usr/bin/env python

 import sys
+import six
 import os
 import time
 import termios
@@ -8,6 +9,8 @@
 import threading
 import socket

+get_stdout_buffer = lambda : sys.stdout if six.PY2 else sys.stdout.buffer
+
 map_lst = lambda f, lst: list( map( f, lst ) )
 map_up = lambda f, lst: map( lambda a: f(*a), lst )
 map_up_lst = lambda f, lst: list( map_up( f, lst ) )
@@ -54,7 +57,7 @@
 	if s != None:
 		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 		sock.connect( get_host_port(s, port=55765) )
-	return lambda s: ( sock.sendall if sock else sys.stdout.write )( s.encode() )
+	return lambda s: ( sock.sendall if sock else get_stdout_buffer().write )( s.encode() )

 out = out_new()

久しぶりに ./pac.py として起動してみたら、python3でひっかかってました。

安易なパッチですが修正しておきます。

$ cat v8.patch | patch -p1



クロージャと空クラス

「クロージャ多用なソース」ですが、 あまりに自己流な感じになってきたので少々説明。

そもそもの経緯は、 Pythonでグローバル変数に代入するときに global xxx が面倒なので、

def gval(v=None):
	keep = [ v ]
	def set(v):
		keep[0] = v
	return lambda v='get': keep[0] if v == 'get' else set(v)

を用意した所から始まります。

この関数で返るクロージャの関数を、 引数なしで実行すると、内部の値が返り、 引数を指定して実行すると、その値が内部に設定されます。

このようにクロージャの関数1つだけを返していて多機能を求めると、 引数にコマンド的なものを与えて、switch文のような関数になります。

gvalの例でも、値vの指定が'get'なら参照する機能。'get'以外なら設定する機能。 これはちょっと「かっちょ悪い...」

安易に逃げるならば、

def gval(v=None):
	keep = [ v ]
	def set(v):
		keep[0] = v
	return (lambda: keep[0], set)

(foo_get, foo_set) = gval('bar')

foo_set('hoge')
print foo_get()

辞書で返すなら

def gval(v=None):
	keep = [ v ]
	def set(v):
		keep[0] = v
	return { 'get': lambda: keep[0], 'set': set }

foo = gval('bar')

foo.get('set')('hoge')
print foo.get('get')()

あるいは

foo['set']('hoge')
print foo['get']()

名前'get' や 'set'は、クラスのメソッドのように .get() や .set() にして使いたいところ...

そういえばPythonのクラスのインスタンスは、割りと何でもアリなはず。

例えば、教科書的な使い方だと

foo.py
class Foo:
	def __init__(self, rate=0.3):
		self.rate = rate
		self.a = 0
		self.b = 0
	def get(self):
		return (self.a, self.b)
	def add(self, v):
		if v <= 100:
			self.a += v
		elif v <= 200:
			self.b += v
		else:
			t = int(v * self.rate)
			self.a += t
			self.b += v - t
		return self.get()

class Empty:
	pass

def gval(v=None):
	keep = [ v ]
	def set(v):
		keep[0] = v
	e = Empty()
	e.get = lambda: keep[0]
	e.set = set
	return e

def foo_new(rate=0.3):
	p = Empty()
	p.a = 0
	p.b = 0
	e = Empty()
	e.get = lambda: (p.a, p.b)
	def add(v):
		if v <= 100:
			p.a += v
		elif v <= 200:
			p.b += v
		else:
			t = int(v * rate)
			p.a += t
			p.b += v - t
		return e.get()
	e.add = add
	return e

def cls_to_dic(c):
	ks = filter( lambda k: not k.startswith('_'), dir(c) )
	return dict( map( lambda k: ( k, getattr(c, k) ), ks ) )

def dic_to_cls(d, c=None):
	if not c:
		c = Empty()
	map( lambda (k, v): setattr(c, k, v), d.items() )
	return c

def extends(pcls, c=None):
	if not c:
		c = Empty()
	dic_to_cls( cls_to_dic(pcls), c )
	return c

def bar_new(rate=0.3):
	foo = foo_new(rate)
	e = extends(foo)
	e.vs = [402, 229, 338, 48, 80, 448]
	def show():
		print e.get()
	e.show = show
	return e

def hoge_new(rate=0.3):
	bar = bar_new(rate)
	e = extends(bar)
	def get(name=None):
		(a, b) = r = bar.get()
		return a if name == 'a' else b if name == 'b' else [ a, b ]
	e.get = get
	return e

的なクラス。

add(v)で追加する売上が、
100円以下ならaさんの取分。
100円超えて200円以下ならbさんの取分。
200円超えてるものは、3:7 で配分。
$ python
>>> from foo import Foo
>>> from random import randint

>>> vs = map( lambda i: randint(10,500), range(6) )
>>> vs
[402, 229, 338, 48, 80, 448]

半年の売上がこうならば aさんbさんの持ち分の推移は

>>> foo = Foo()
>>> map( foo.add, vs )
[(120, 282), (188, 443), (289, 680), (337, 680), (417, 680), (551, 994)]

もうちょっとaさんの配分を増やして 4:6 にして試すなら

>>> bar = Foo(0.4)
>>> map( bar.add, vs )
[(160, 242), (251, 380), (386, 583), (434, 583), (514, 583), (693, 852)]

クラスのアトリビュート a, b な訳ですが、 これは悪魔でクラスインスタンスで「共通の」アトリビュート。

>>> foo.hoge = 123
>>> bar.plus3 = lambda v: v+3

各インスタンスに個別に勝手にアトリビュートを追加できます。

>>> foo.hoge
123
>>> bar.plus3(7)
10

.xxx じゃなくて 'xxx' な名前でも使えて

>>> hasattr(foo, 'hoge')
True
>>> setattr(foo, 'hoge', 999)
>>> getattr(foo, 'hoge')
999
>>> foo.hoge
999

もちろん各インスタンス「共通の」アトリビュートに対しても名前で

>>> getattr(bar, 'get')()
(693, 852)

>>> bar.get()
(693, 852)

問題なく何でもアリです。 アトリビュート .xxx は辞書で実装されてるのでしょうね。

ならば、 クロージャで辞書を返すように、 空クラスのインスタンスにアトリビュートを追加して返せば、 辞書のように使える?

class Empty:
	pass

を用意しておいて

def gval(v=None):
	keep = [ v ]
	def set(v):
		keep[0] = v
	e = Empty()
	e.get = lambda: keep[0]
	e.set = set
	return e

>>> foo = gval('hoge')
>>> foo.get()
'hoge'
>>> foo.set('fuga')
>>> foo.get()
'fuga'

gval()はクロージャを返すのでクラスみたいなものですが、 classそのものは、複数のクロージャをまとめて返すためだけに使って、 受け取った側で .xxx() でメソッドとして呼び出すようにしてます。

例えば先の配分クラスを書くならば、

def foo_new(rate=0.3):
	p = Empty()
	p.a = 0
	p.b = 0
	e = Empty()
	e.get = lambda: (p.a, p.b)
	def add(v):
		if v <= 100:
			p.a += v
		elif v <= 200:
			p.b += v
		else:
			t = int(v * rate)
			p.a += t
			p.b += v - t
		return e.get()
	e.add = add
	return e

この関数そのものはクラスじゃないので、self. は出てきません。

引数の rate や、使ってませんがローカル変数の値も、 この関数を実行したときの環境として内部に保持されます。

クロージャ関数 add() や get() の中からは、 引数やローカル変数の値を変更しようとすると、例の global 問題があります。

なので、値を変更するけど関数の内部だけに留めたい a, b は、 空クラスのインスタンス p にぶら下げてます。

外に公開するメソッド get(), add() は、 空クラスのインスタンス e にぶら下げて return e で返してます。

>>> from foo import foo_new

>>> vs
[402, 229, 338, 48, 80, 448]

>>> foo = foo_new()
>>> map( foo.add, vs )
[(120, 282), (188, 443), (289, 680), (337, 680), (417, 680), (551, 994)]

>>> bar = foo_new(0.4)
>>> map( bar.add, vs )
[(160, 242), (251, 380), (386, 583), (434, 583), (514, 583), (693, 852)]

ほれ。同様に動きます。

まぁ元々Pythonのクラスのアトリビュートは丸見えで、 多少隠蔽できるようにしたところで、アレなので、

def foo_new(rate=0.3):
	e = Empty()
	e.a = 0
	e.b = 0
	e.get = lambda: (e.a, e.b)
	def add(v):
		if v <= 100:
			e.a += v
		elif v <= 200:
			e.b += v
		else:
			t = int(v * rate)
			e.a += t
			e.b += v - t
		return e.get()
	e.add = add
	return e

な感じで、全部公開するように書いとります。

で、クラスもどきなので継承的な事も。

def cls_to_dic(c):
	ks = filter( lambda k: not k.startswith('_'), dir(c) )
	return dict( map( lambda k: ( k, getattr(c, k) ), ks ) )

クラスのアトリビューとで '_' で始まるもの以外を、辞書に。

def dic_to_cls(d, c=None):
	if not c:
		c = Empty()
	map( lambda (k, v): setattr(c, k, v), d.items() )
	return c

辞書を受け取って、クラスのアトリビュートに上書き。

def extends(pcls, c=None):
	if not c:
		c = Empty()
	dic_to_cls( cls_to_dic(pcls), c )
	return c

親のクラスインスタンスの、アトリビュート全てを、 辞書を経由して、子供のクラスインスタンスのアトリビュートに上書き。

これで「継承もどき」です。

先の取分クラスもどきを継承して、 サンプルのデータと、表示機能だけ追加してみましょう。

def bar_new(rate=0.3):
	foo = foo_new(rate)
	e = extends(foo)
	e.vs = [402, 229, 338, 48, 80, 448]
	def show():
		print e.get()
	e.show = show
	return e

>>> from foo import bar_new

>>> bar = bar_new()
>>> bar.vs
[402, 229, 338, 48, 80, 448]

>>> map( bar.add, bar.vs )
[(120, 282), (188, 443), (289, 680), (337, 680), (417, 680), (551, 994)]

>>> bar.show()
(551, 994)

インタプリタで対話的に使うと、show()のありがた味ゼロですね。

親のメソッドは丸々 e にコピーされていて、 呼び出せば親の関数の中でクロージャ関数が起動します。

ただし、メソッド呼び出しせずに、値として扱うならば、 値はコピーされてるだけなので、 子供側で値を変更しても、親側には反映されないので要注意です。

例えば、親からコピーした get を上書きして書き換えるなら、

def hoge_new(rate=0.3):
	bar = bar_new(rate)
	e = extends(bar)
	def get(name=None):
		(a, b) = r = bar.get()
		return a if name == 'a' else b if name == 'b' else [ a, b ]
	e.get = get
	return e

>>> from foo import hoge_new

>>> hoge = hoge_new()
>>> hoge.vs
[402, 229, 338, 48, 80, 448]
>>> map( hoge.add, hoge.vs )
[(120, 282), (188, 443), (289, 680), (337, 680), (417, 680), (551, 994)]

>>> hoge.get()
[551, 994]
>>> hoge.get('a')
551
>>> hoge.get('b')
994

ただし、仮想関数テーブルもなにもなくて、単に上書きしてるだけなので

>>> hoge.show()
(551, 994)

まぁ、そりゃそうです。「クラスもどき」という事で。


工事中