git.fiddlerwoaroof.com
Browse code

Merge branch 'feature/gui' into develop

edwlan authored on 01/08/2013 06:00:25
Showing 9 changed files
2 2
new file mode 100644
... ...
@@ -0,0 +1,56 @@
1
+import src.console
2
+import collections
3
+
4
+class MessageBox(object):
5
+	def __init__(self, console, num, pos, event=None):
6
+		self.console = console
7
+		self.pos = x,y = pos
8
+		self.messages = collections.deque([], num)
9
+		self.maxlen = -float('inf')
10
+		if event is not None:
11
+			import src.events
12
+			src.events.EventHandler().handle_event(event, self.msg)
13
+
14
+	def msg(self, msg, *a):
15
+		msg %= a
16
+		self.messages.append(msg)
17
+		self.maxlen = max(self.maxlen, len(msg))
18
+		x,y = self.pos
19
+		for idx, msg in enumerate(self.messages):
20
+			self.console.print_( (x,y+idx), msg.ljust(self.maxlen) )
21
+		return False
22
+
23
+class TextBox(object):
24
+	def __init__(self, console, num, pos, event=None):
25
+		self.console = console
26
+		self.pos = x,y = pos
27
+		self.lines = [''] * num
28
+		self.maxlen = -float('inf')
29
+		if event is not None:
30
+			import src.events
31
+			src.events.EventHandler().handle_event(event, self.set_line)
32
+
33
+	def set_line(self, line, msg, *a):
34
+		msg = msg % a
35
+		self.maxlen = max(self.maxlen, len(msg))
36
+		self.lines[line] = msg
37
+		x,y = self.pos
38
+		self.console.print_( (x,y+line), msg.ljust(self.maxlen) )
39
+		return False
40
+
41
+class Label(object):
42
+	def __init__(self, console, pos, event=None):
43
+		self.console = console
44
+		self.pos = x,y = pos
45
+		self.line = ''
46
+		self.maxlen = -float('inf')
47
+		if event is not None:
48
+			import src.events
49
+			src.events.EventHandler().handle_event(event, self.set_text)
50
+	def set_text(self, msg, *a):
51
+		msg = msg % a
52
+		self.maxlen = max(self.maxlen, len(msg))
53
+		self.line = msg
54
+		x,y = self.pos
55
+		self.console.print_( (x,y), msg.ljust(self.maxlen) )
56
+		return False
... ...
@@ -5,6 +5,9 @@ from .combat_parts import equipment
5 5
 from .combat_parts import skills
6 6
 from .combat_parts import constants as const
7 7
 
8
+from src import events
9
+eh = events.EventHandler()
10
+
8 11
 import collections
9 12
 import random
10 13
 
... ...
@@ -48,6 +51,10 @@ class Adventurer(object):
48 51
 		if val > const.KNOCKOUT: val = const.KNOCKOUT
49 52
 		self.attributes.state = val
50 53
 
54
+	@property
55
+	def readable_state(self):
56
+		return ['healthy', 'wounded', 'knocked out'][self.state if self.state < 3 else 2]
57
+
51 58
 	@classmethod
52 59
 	def randomize(cls, stat_sum=28):
53 60
 		name = '%s %s%s' % (
... ...
@@ -89,7 +96,9 @@ class Adventurer(object):
89 96
 		if pass_:
90 97
 			if self.weapon is not None:
91 98
 				damage += self.weapon.damage_mod
92
-			print '%s inflicts %d damage upon %s' % (self.name, damage, opponent.name)
99
+			eh.trigger_event('msg',
100
+				'%s inflicts %d damage upon %s' % (self.name, damage, opponent.name)
101
+			)
93 102
 			opponent.take_damage(damage)
94 103
 
95 104
 	def take_damage(self, damage):
... ...
@@ -104,7 +113,9 @@ class Adventurer(object):
104 113
 		if not result:
105 114
 			if damage > 0:
106 115
 				self.state += 1
107
-				print '%s takes %d damage and is now %s' % (self.name, damage, ['healthy', 'wounded', 'ko'][self.state if self.state < 3 else 2])
116
+				eh.trigger_event('msg',
117
+					'%s takes %d damage and is now %s' % (self.name, damage, self.readable_state)
118
+				)
108 119
 				if self.state >= const.KNOCKOUT:
109 120
 					self.die()
110 121
 
... ...
@@ -1,3 +1,4 @@
1
+from src import events
1 2
 import abc
2 3
 import collections
3 4
 from . import combat
... ...
@@ -13,6 +14,7 @@ class Overlay(object):
13 14
 	def pos(self):
14 15
 		return self.x, self.y
15 16
 	def __init__(self, x,y, map):
17
+		print self.handled_events
16 18
 		self.events = collections.OrderedDict()
17 19
 		self.x = x
18 20
 		self.y = y
... ...
@@ -38,21 +40,30 @@ class Overlay(object):
38 40
 			result = result is None or result # if the event returns a false value besides None, break
39 41
 			if result == False: break
40 42
 
41
-	handled_events = collections.OrderedDict()
42
-	@classmethod
43
-	def handle_event(cls, event):
44
-		def _inner(cb):
45
-			cls.handled_events.setdefault(event, []).append(cb)
46
-			return cb
43
+	@staticmethod
44
+	def add_event(event,  cb_name):
45
+		def _inner(cls):
46
+			if not hasattr(cls,  'handled_events'):
47
+				cls.handled_events = {}
48
+				for base in cls.bases:
49
+					if hasattr(base,  'handled_events'):
50
+						cls.handled_events.update(base.handled_events)
51
+			cls.handled_events.setdefault(event,  []).append(getattr(cls,  cb_name))
52
+			return cls
47 53
 		return _inner
48 54
 
55
+	def tick(self):
56
+		pass
49 57
 
50 58
 
59
+@Overlay.add_event('attacked',  'attacked_by')
60
+@Overlay.add_event('bumped',  'bumped_by')
51 61
 class Actor(Overlay):
52 62
 	char = ord('@')
53 63
 	color = (255,0,0)
54 64
 	blocks = True
55 65
 
66
+	handled_events = collections.OrderedDict()
56 67
 	@property
57 68
 	def pos(self):
58 69
 		return self.x, self.y
... ...
@@ -63,18 +74,35 @@ class Actor(Overlay):
63 74
 		if adventurer is None:
64 75
 			adventurer = combat.Adventurer.randomize()
65 76
 		self.adventurer = adventurer
77
+		self.repeated_actions = {}
66 78
 
67
-	def move(self, dx, dy):
68
-		dx, dy = self.map.move(self, dx,dy)
79
+	def add_repeated_action(self, time, action, *args, **kw):
80
+		print 'adding action', action, args, kw
81
+		self.repeated_actions.setdefault(time, []).append( (action, args, kw) )
82
+
83
+	def update_pos(self, dx,dy):
69 84
 		self.x += dx
70 85
 		self.y += dy
71 86
 
87
+	def move(self, dx, dy):
88
+		print 'moving'
89
+		self.map.move(self, dx,dy, self.update_pos)
90
+
72 91
 	def tick(self):
73 92
 		result = True
74 93
 		if self.adventurer.state >= 2:
75 94
 			result = False
76 95
 			self.char = ord('%')
77 96
 			self.blocks = False
97
+		else:
98
+			ractions = {}
99
+			for nleft, actions in self.repeated_actions.items():
100
+				for (action,args,kwargs) in actions:
101
+					print action,args,kwargs
102
+					action(*args, **kwargs)
103
+					if nleft > 1:
104
+						ractions.setdefault(nleft-1, []).append( (action,args,kwargs) )
105
+			self.repeated_actions = ractions
78 106
 		return result
79 107
 
80 108
 	def ishostile(self, other):
... ...
@@ -85,32 +113,43 @@ class Actor(Overlay):
85 113
 		if isinstance(other, Actor) and other.ishostile(self):
86 114
 			self.adventurer.attack(other.adventurer)
87 115
 			other.trigger_event('attacked', self)
88
-		elif isinstance(other, Object):
116
+
117
+	def interact(self, other):
118
+		result = False
119
+		if isinstance(other, Object):
120
+			result = True
89 121
 			self.pickup(other)
90 122
 			other.trigger_event('picked_up', self)
91 123
 
92 124
 		other.trigger_event('bumped', self)
125
+		return result
126
+
127
+	def pickup(self, other):
128
+		self.inventory.append(other)
93 129
 
94
-	@Overlay.handle_event('attacked')
95 130
 	def attacked_by(self, other):
96 131
 		if self.adventurer.skills.check('agility'):
97 132
 			self.adventurer.attack(other.adventurer)
98 133
 
99
-	@Overlay.handle_event('bumped')
100 134
 	def bumped_by(self, other):
101 135
 		print '%s was bumped by %s' % (type(self).__name__, type(other).__name__)
102 136
 
137
+@Overlay.add_event('picked_up',  'picked_up_by')
138
+@Overlay.add_event('bumped',  'bumped_by')
103 139
 class Object(Overlay):
104 140
 	item = None
105
-	@Overlay.handle_event('picked_up')
141
+	handled_events = collections.OrderedDict()
106 142
 	def picked_up_by(self, other):
143
+		events.EventHandler().trigger_event('msg', 'picked up a %s' % self)
107 144
 		self.map.remove(self)
145
+	def bumped_by(self, other): pass
108 146
 
109 147
 class Weapon(Object):
110 148
 	item = None
111 149
 
112 150
 class Potion(Object):
113 151
 	char = ord('!')
152
+	def __str__(self): return 'potion'
114 153
 
115 154
 class Scroll(Object):
116 155
 	char = ord('!')
... ...
@@ -1,3 +1,6 @@
1
+if __name__ == '__main__':
2
+    execfile('/home/edwlan/python_envs/roguelike/bin/activate_this.py',  dict(__file__='/home/edwlan/python_envs/roguelike/bin/activate_this.py'))
3
+
1 4
 import libs.patch_random
2 5
 import random
3 6
 #random.seed(2)
... ...
@@ -5,6 +8,7 @@ import random
5 8
 import libtcodpy as tc
6 9
 import numpy as np
7 10
 
11
+from gui import text_display
8 12
 from libs import overlay
9 13
 from libs import combat
10 14
 from src import events
... ...
@@ -15,20 +19,32 @@ from src import map
15 19
 import random
16 20
 import bisect
17 21
 
22
+from twisted.internet import reactor
23
+import twisted.internet
18 24
 
19
-WIDTH = 79
20
-HEIGHT = 46
25
+
26
+WIDTH = 118
27
+HEIGHT = 62
21 28
 class Application(object):
22 29
 	def __init__(self):
23
-		self.screen = console.Screen(WIDTH, HEIGHT)
30
+		self.screen = console.Screen(WIDTH, HEIGHT+5)
31
+		self.console = console.Console(WIDTH, HEIGHT, self.screen)
32
+		self.message_console = console.Console(WIDTH, 5, self.screen, (0,HEIGHT))
33
+		self.messages = text_display.MessageBox(self.message_console, 5, (10,0), 'msg')
34
+		self.fps_display = text_display.Label(self.message_console, (0,0), None)
35
+
24 36
 		self.terrain_registry = map.TerrainRegistry()
25 37
 		self.terrain_registry.load_from_file('data/terrain.yml')
26
-		self.map = map.Map(WIDTH,HEIGHT, self.terrain_registry)
27
-		self.player = player.Player(4,5, self.map, combat.Adventurer.randomize())
38
+		self.map = map.Map(200,200, self.terrain_registry)
39
+		self.tc_events = events.TCODEventHandler()
28 40
 		self.events = events.EventHandler()
29
-		player.ArrowHandler(self.player, self.events)
30 41
 
31
-		self.actors = []
42
+		self.player = player.Player(4,5, self.map, combat.Adventurer.randomize())
43
+		self.player_stat = text_display.TextBox(self.message_console, 4, (0,1))
44
+		self.player.claim_display(self.player_stat)
45
+		player.ArrowHandler(self.player, self.tc_events)
46
+
47
+		self.actors = [self.player]
32 48
 		for x in range(40):
33 49
 			self.actors.append(overlay.Actor(random.randrange(WIDTH), random.randrange(HEIGHT), self.map))
34 50
 
... ...
@@ -36,7 +52,7 @@ class Application(object):
36 52
 		for x in range(50):
37 53
 			self.objects.append(overlay.Potion(random.randrange(WIDTH), random.randrange(HEIGHT), self.map))
38 54
 
39
-		tc.sys_set_fps(60)
55
+		tc.sys_set_fps(30)
40 56
 
41 57
 	def update_actors(self):
42 58
 		to_pop = []
... ...
@@ -44,25 +60,33 @@ class Application(object):
44 60
 			if not actor.tick():
45 61
 				bisect.insort(to_pop, idx)
46 62
 		for pop in reversed(to_pop):
47
-			self.actors.pop(pop)
63
+			actor = self.actors.pop(pop)
64
+			self.objects.append(actor)
48 65
 
49 66
 	def init(self):
50 67
 		self.screen.init("test")
51
-		self.player.draw()
68
+		self.message_console.fill(0,0,128)
69
+		#self.player.draw()
52 70
 		self.update_actors()
53 71
 		for overlay in self.actors + self.objects:
54 72
 			overlay.draw()
55 73
 
56
-	def run(self):
57
-		while not tc.console_is_window_closed():
58
-			self.events.tick()
59
-			self.update_actors()
60
-			self.map.draw(self.screen)
61
-			tc.console_print(0, 0,1, '%d' % tc.sys_get_fps())
62
-			tc.console_flush()
74
+	def tick(self):
75
+		self.tc_events.tick()
76
+		self.fps_display.set_text('%d', tc.sys_get_fps())
77
+		self.update_actors()
78
+		self.map.draw(self.console)
79
+		self.console.blit()
80
+		self.message_console.blit()
81
+		tc.console_flush()
82
+		if tc.console_is_window_closed():
83
+			reactor.stop()
84
+		else:
85
+			reactor.callLater(0, self.tick)
63 86
 
64 87
 
65 88
 if __name__ == '__main__':
66 89
 	app = Application()
67 90
 	app.init()
68
-	app.run()
91
+	reactor.callWhenRunning(app.tick)
92
+	reactor.run()
... ...
@@ -1,14 +1,55 @@
1 1
 import libtcodpy as tc
2
+import numpy as np
2 3
 
3 4
 class Console(object):
4 5
 	# TBI: must have a self.con member with the console to be drawn on
5
-	pass
6
+	@property
7
+	def dim(self):
8
+		return self.width, self.height
6 9
 
7
-class Screen(object):
8
-	def __init__(self, w, h):
10
+	def __init__(self, w,h, parent=None, offset=(0,0)):
9 11
 		self.width = w
10 12
 		self.height = h
11
-		self.con = 0
13
+		self.offset = offset
14
+		self.parent = parent
15
+
16
+		if parent is None:
17
+			self.con = 0
18
+		else:
19
+			self.con = tc.console_new(w,h)
20
+
21
+	def blit(self):
22
+		if self.parent is not None:
23
+			xdst,ydst = self.offset
24
+			tc.console_blit(self.con, 0,0, self.width, self.height, self.parent.con, xdst,ydst)
25
+
26
+	def set_bg(self, r,g,b):
27
+		tc.console_set_default_background(self.con, r,g,b)
28
+
29
+	def set_fg(self, r,g,b):
30
+		tc.console_set_default_foreground(self.con, r,g,b)
31
+
32
+	def clear(self):
33
+		tc.console_clear(self.con)
34
+
35
+	def fill(self, r,g,b):
36
+		r = np.ones(self.dim) * r
37
+		g = np.ones(self.dim) * g
38
+		b = np.ones(self.dim) * b
39
+		tc.console_fill_background(self.con, r,g,b)
40
+
41
+	def print_(self, pos, msg, *fmt):
42
+		x,y = pos
43
+		tc.console_print(self.con, x,y, msg % fmt)
44
+
45
+
46
+
47
+
48
+class Screen(Console):
49
+	# Root console
12 50
 	def init(self, title, fullscreen=False):
13 51
 		tc.console_init_root(self.width, self.height, title, fullscreen, 2)
14 52
 		return self
53
+
54
+	def flush(self):
55
+		tc.console_flush()
... ...
@@ -1,6 +1,22 @@
1 1
 import libtcodpy as tc
2
+import collections
2 3
 
3 4
 class EventHandler(object):
5
+	events = {}
6
+	def register_event(self, event):
7
+		return self.events.setdefault(event, [])
8
+	def handle_event(self, event, cb, *a, **kw):
9
+		cbs = self.register_event(event)
10
+		cbs.append( (cb, a, kw) )
11
+	def trigger_event(self, event, *a, **kw):
12
+		for cb, args, kwargs in self.events.get(event,[]):
13
+			kw.update(kwargs)
14
+			a += args
15
+			result = cb(*a, **kw)
16
+			if result is not None and not result: break
17
+
18
+
19
+class TCODEventHandler(object):
4 20
 	def __init__(self):
5 21
 		self.key = tc.Key()
6 22
 		self.mouse = tc.Mouse()
... ...
@@ -51,35 +51,47 @@ class Map(object):
51 51
 			for other in self.overlays[x,y]:
52 52
 				object.bump(other)
53 53
 
54
-	def move(self, object, dx,dy):
55
-		self.overlays[object.pos].remove(object)
54
+	def interact(self, a, bs):
55
+		print 'interacting with:', bs
56
+		for b in bs:
57
+			a.interact(b)
56 58
 
57
-		collide_x, collide_y = None, None
59
+	def remove(self, object):
60
+		pos = object.pos
61
+		if pos in self.overlays and object in self.overlays[pos]:
62
+			self.overlays[pos].remove(object)
63
+
64
+	def move(self, object, dx,dy, update_cb):
65
+		ox,oy = object.pos
58 66
 
59 67
 		if abs(dx) < 2 and abs(dy) < 2:
60
-			ox,oy = object.pos
68
+			self.overlays[object.pos].remove(object)
69
+
70
+			collide_x, collide_y = None, None
61 71
 			x = squeeze(ox+dx, 0, self.width-1)
62 72
 			y = squeeze(oy+dy, 0, self.height-1)
63 73
 			if not self.is_passable((x,y)):
64 74
 				collide_x, collide_y = x,y
65 75
 				x,y = ox,oy
76
+			update_cb(x-ox, y-oy)
77
+
78
+			if collide_x is not None:
79
+				self.check_and_execute_bump(object, collide_x, collide_y)
80
+			if object.pos in self.overlays:
81
+				self.interact(object, self.overlays[object.pos])
66 82
 
83
+			self.overlays.setdefault((x,y), []).append(object)
84
+			self.update_overlay(ox,oy)
67 85
 		else:
68
-			ox,oy = object.pos
69
-			tx,ty = ox+dx, oy+dy
70
-			gx,gy = ox,oy
71
-			for x,y in libs.bresenham.line(ox,oy, tx,ty, 1):
72
-				if not self.is_passable((x,y)):
73
-					collide_x, collide_y = x,y
86
+			# calculate the deltas for each step
87
+			line = list(libs.bresenham.line(0,0, dx,dy, 0))
88
+			line = [(bx-ax, by-ay) for (ax,ay), (bx,by) in zip(line,line[1:])]
89
+			print line
90
+			for x,y in line:
91
+				if self.move(object, x,y, update_cb) != (x,y):
74 92
 					break
75
-				else: gx,gy = x,y
76
-			x,y = gx,gy
93
+			x,y = object.pos
77 94
 
78
-		if collide_x is not None:
79
-			self.check_and_execute_bump(object, collide_x, collide_y)
80
-
81
-		self.overlays.setdefault((x,y), []).append(object)
82
-		self.update_overlay(ox,oy)
83 95
 		return x-ox, y-oy
84 96
 
85 97
 	def update_overlay(self, x=None, y=None):
... ...
@@ -102,38 +114,55 @@ class Map(object):
102 114
 
103 115
 
104 116
 	def get_rgb(self, colors, fg=True,slices=(slice(0),slice(0))):
105
-		result = np.rollaxis(colors[slices], 2)
117
+		result = np.rollaxis(colors, 2)
106 118
 		return [x.transpose() for x in result]
107 119
 
108 120
 	def draw(self, con, tl=(0,0)):
121
+		def mv(x,y, (lx,ty)):
122
+			return x-lx, y-ty
109 123
 		br = tl[0]+con.width, tl[1]+con.height
124
+		slices = slice(tl[0], br[0]), slice(tl[1], br[1])
110 125
 
111 126
 		fgcolors = self.fgcolors.astype('int')
112 127
 		bgcolors = self.bgcolors.astype('int')
113 128
 		color_mask = np.ones( (con.width, con.height, 3) )
114 129
 		char_mask = np.ones( (con.width, con.height) ).astype('bool')
130
+
115 131
 		if self.pov is not None:
116 132
 			origin, radius = self.pov
133
+			ox, oy = origin
134
+			if ox+radius >= br[0] or oy+radius >= br[1]:
135
+				xmod, ymod = 0,0
136
+				if ox + radius >= br[0]:
137
+					xmod = min(ox + radius - br[0], self.width - br[0])
138
+				if oy + radius >= br[1]:
139
+					ymod =  min(oy + radius - br[1], self.height - br[1])
140
+				br = br[0] + xmod, br[1] + ymod
141
+				tl = tl[0] + xmod, tl[1] + ymod
142
+				slices = slice(tl[0], br[0]), slice(tl[1], br[1])
117 143
 			def calc_mask():
118 144
 				for x in range(tl[0], br[0]):
119 145
 					for y in range(tl[1], br[1]):
120 146
 						if not self.fov.is_visible(origin, radius, (x,y)):
121
-							color_mask[x,y] = (0.25, 0.5, 0.5)
122
-							char_mask[x,y] = False
147
+							color_mask[x-tl[0], y-tl[1]] = (0.25, 0.5, 0.5)
148
+							char_mask[x-tl[0], y-tl[1]] = False
123 149
 				return color_mask, char_mask
124
-			color_mask, char_mask = self.povcache.get( (origin,radius), calc_mask )
125
-			fgcolors = (fgcolors * color_mask).astype('int')
126
-			bgcolors = (bgcolors * color_mask).astype('int')
150
+			color_mask, char_mask = self.povcache.get( (origin,radius,tl), calc_mask )
151
+			fgcolors = (fgcolors[slices] * color_mask).astype('int')
152
+			bgcolors = (bgcolors[slices] * color_mask).astype('int')
153
+		else:
154
+			fgcolors = fgcolors[slices]
155
+			bgcolors = bgcolors[slices]
127 156
 
128
-		slices = slice(tl[0], tl[0]+con.width), slice(tl[1], tl[1]+con.height)
129
-		tc.console_fill_foreground(con.con, *self.get_rgb(fgcolors, slices=slices))
130
-		tc.console_fill_background(con.con, *self.get_rgb(bgcolors, False,slices=slices))
157
+		tc.console_fill_foreground(con.con, *self.get_rgb(fgcolors))
158
+		tc.console_fill_background(con.con, *self.get_rgb(bgcolors, False))
131 159
 
132 160
 		chars = np.copy(self.map[slices])
133 161
 		for x,y in self.overlays:
134 162
 			screen_x = x-tl[0]
135 163
 			screen_y = y-tl[1]
136
-			if not char_mask[screen_x,screen_y]: continue
164
+			if (not (tl[0] <= x < br[0])) or (not (tl[1] <= y < br[1])): continue
165
+			elif not char_mask[screen_x,screen_y]: continue
137 166
 			if 0 <= screen_x < con.width and 0 <= screen_y < con.height:
138 167
 				obj = self.overlays[x,y][-1]
139 168
 				chars[screen_x,screen_y] = obj.char
... ...
@@ -183,7 +212,7 @@ class FovCache(object):
183 212
 			self.fovmaps[key] = fovmap
184 213
 
185 214
 			x,y = origin
186
-			tc.map_compute_fov(fovmap, x,y, radius)
215
+			tc.map_compute_fov(fovmap, x,y, radius, algo=tc.FOV_DIAMOND)
187 216
 
188 217
 		return fovmap
189 218
 
... ...
@@ -10,10 +10,17 @@ class Player(libs.overlay.Actor):
10 10
 		libs.overlay.Actor.__init__(self, x,y, map, adventurer)
11 11
 		print 'Player\'s name is %s' % self.adventurer.name
12 12
 		self.map.set_pov((self.pos, self.light_radius))
13
+		self.display = None
13 14
 	def move(self, dx, dy):
14 15
 		libs.overlay.Actor.move(self, dx,dy)
15 16
 		self.map.set_pov((self.pos, self.light_radius))
16 17
 
18
+	def claim_display(self, display):
19
+		self.display = display
20
+	def tick(self):
21
+		print 'player_tick'
22
+		return libs.overlay.Actor.tick(self)
23
+
17 24
 class ArrowHandler(object):
18 25
 	def __init__(self, player, eh):
19 26
 		self.player = player
... ...
@@ -21,28 +28,23 @@ class ArrowHandler(object):
21 28
 		eh.register(tc.KEY_RIGHT,self.right)
22 29
 		eh.register(tc.KEY_UP,self.up)
23 30
 		eh.register(tc.KEY_DOWN,self.down)
31
+
32
+	def do_move(self, shift, dx, dy):
33
+		self.player.add_repeated_action(10 if shift else 1, self.player.move, dx,dy)
34
+
24 35
 	def left(self, alt, shift, ctrl):
25
-		val = 10 if shift else 1
26
-		if alt:
27
-			self.player.move(-val, -val)
28
-		else:
29
-			self.player.move(-val, 0)
36
+		dx,dy = (-1,-1) if alt else (-1,0)
37
+		self.do_move(shift, dx,dy)
38
+
30 39
 	def right(self, alt, shift, ctrl):
31
-		val = 10 if shift else 1
32
-		if alt:
33
-			self.player.move(val, val)
34
-		else:
35
-			self.player.move(val, 0)
40
+		dx,dy = (1,1) if alt else (1,0)
41
+		self.do_move(shift, dx,dy)
42
+
36 43
 	def up(self, alt, shift, ctrl):
37
-		val = 10 if shift else 1
38
-		if alt:
39
-			self.player.move(val, -val)
40
-		else:
41
-			self.player.move(0, -val)
44
+		dx,dy = (1,-1) if alt else (0,-1)
45
+		self.do_move(shift, dx,dy)
46
+
42 47
 	def down(self, alt, shift, ctrl):
43
-		val = 10 if shift else 1
44
-		if alt:
45
-			self.player.move(-val, val)
46
-		else:
47
-			self.player.move(0, val)
48
+		dx,dy = (-1,1) if alt else (0,1)
49
+		self.do_move(shift, dx,dy)
48 50