11 """Calculate n-th Fibonacci number. Very inefficiently."""
15 return fib(n-1) + fib(n-2)
18 class CommandHandler(server_.game.Commander):
20 def __init__(self, game_file_name):
22 self.world = server_.game.World()
23 self.parser = parser.Parser(self)
24 self.game_file_name = game_file_name
25 # self.pool and self.pool_result are currently only needed by the FIB
26 # command and the demo of a parallelized game loop in cmd_inc_p.
27 from multiprocessing import Pool
29 self.pool_result = None
31 def quote(self, string):
32 """Quote & escape string so client interprets it as single token."""
40 return ''.join(quoted)
42 def handle_input(self, input_, connection_id=None, store=True):
43 """Process input_ to command grammar, call command handler if found."""
44 from inspect import signature
46 def answer(connection_id, msg):
48 self.send(msg, connection_id)
53 command = self.parser.parse(input_)
55 answer(connection_id, 'UNHANDLED_INPUT')
57 if 'connection_id' in list(signature(command).parameters):
58 command(connection_id=connection_id)
62 with open(self.game_file_name, 'a') as f:
63 f.write(input_ + '\n')
64 except parser.ArgError as e:
65 answer(connection_id, 'ARGUMENT_ERROR ' + self.quote(str(e)))
66 except server_.game.GameError as e:
67 answer(connection_id, 'GAME_ERROR ' + self.quote(str(e)))
69 def send(self, msg, connection_id=None):
71 self.queues_out[connection_id].put(msg)
73 for connection_id in self.queues_out:
74 self.queues_out[connection_id].put(msg)
76 def send_gamestate(self, connection_id=None):
77 """Send out game state data relevant to clients."""
79 def stringify_yx(tuple_):
80 """Transform tuple (y,x) into string 'Y:'+str(y)+',X:'+str(x)."""
81 return 'Y:' + str(tuple_[0]) + ',X:' + str(tuple_[1])
83 self.send('NEW_TURN ' + str(self.world.turn))
84 self.send('MAP_SIZE ' + stringify_yx(self.world.map_.size))
85 visible_map = self.world.get_player().get_visible_map()
86 for y in range(self.world.map_.size[0]):
87 self.send('VISIBLE_MAP_LINE %5s %s' %
88 (y, self.quote(visible_map.get_line(y))))
89 visible_things = self.world.get_player().get_visible_things()
90 for thing in visible_things:
91 self.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
92 self.send('THING_POS %s %s' % (thing.id_,
93 stringify_yx(thing.position)))
96 """Send turn finish signal, run game world, send new world data.
98 First sends 'TURN_FINISHED' message, then runs game world
99 until new player input is needed, then sends game state.
101 self.send('TURN_FINISHED ' + str(self.world.turn))
102 self.world.proceed_to_next_player_turn()
103 msg = str(self.world.get_player().last_task_result)
104 self.send('LAST_PLAYER_TASK_RESULT ' + self.quote(msg))
105 self.send_gamestate()
107 def cmd_FIB(self, numbers, connection_id):
108 """Reply with n-th Fibonacci numbers, n taken from tokens[1:].
110 Numbers are calculated in parallel as far as possible, using fib().
111 A 'CALCULATING …' message is sent to caller before the result.
113 self.send('CALCULATING …', connection_id)
114 results = self.pool.map(fib, numbers)
115 reply = ' '.join([str(r) for r in results])
116 self.send(reply, connection_id)
117 cmd_FIB.argtypes = 'seq:int:nonneg'
119 def cmd_INC_P(self, connection_id):
120 """Increment world.turn, send game turn data to everyone.
122 To simulate game processing waiting times, a one second delay between
123 TURN_FINISHED and NEW_TURN occurs; after NEW_TURN, some expensive
124 calculations are started as pool processes that need to be finished
125 until a further INC finishes the turn.
127 This is just a demo structure for how the game loop could work when
128 parallelized. One might imagine a two-step game turn, with a non-action
129 step determining actor tasks (the AI determinations would take the
130 place of the fib calculations here), and an action step wherein these
131 tasks are performed (where now sleep(1) is).
133 from time import sleep
134 if self.pool_result is not None:
135 self.pool_result.wait()
136 self.send('TURN_FINISHED ' + str(self.world.turn))
139 self.send_gamestate()
140 self.pool_result = self.pool.map_async(fib, (35, 35))
143 if len(sys.argv) != 2:
144 print('wrong number of arguments, expected one (game file)')
146 game_file_name = sys.argv[1]
147 command_handler = CommandHandler(game_file_name)
148 if os.path.exists(game_file_name):
149 if not os.path.isfile(game_file_name):
150 print('game file name does not refer to a valid game file')
152 with open(game_file_name, 'r') as f:
153 lines = f.readlines()
154 for i in range(len(lines)):
156 print("FILE INPUT LINE %s: %s" % (i, line), end='')
157 command_handler.handle_input(line, store=False)
159 command_handler.handle_input('MAP_SIZE Y:5,X:5')
160 command_handler.handle_input('TERRAIN_LINE 0 "xxxxx"')
161 command_handler.handle_input('TERRAIN_LINE 1 "x...x"')
162 command_handler.handle_input('TERRAIN_LINE 2 "x.X.x"')
163 command_handler.handle_input('TERRAIN_LINE 3 "x...x"')
164 command_handler.handle_input('TERRAIN_LINE 4 "xxxxx"')
165 command_handler.handle_input('THING_TYPE 0 human')
166 command_handler.handle_input('THING_POS 0 Y:3,X:3')
167 command_handler.handle_input('THING_TYPE 1 monster')
168 command_handler.handle_input('THING_POS 1 Y:1,X:1')
171 server_.io.run_server_with_io_loop(command_handler)