home · contact · privacy
51bb37271f31bbb6db66787ddc9cbc38025fcd7b
[plomrogue2-experiments] / new / plomrogue / game.py
1 from plomrogue.tasks import Task_WAIT, Task_MOVE, Task_PICKUP, Task_DROP
2 from plomrogue.errors import ArgError
3 from plomrogue.commands import (cmd_GEN_WORLD, cmd_GET_GAMESTATE, cmd_MAP,
4                                 cmd_MAP, cmd_THING_TYPE, cmd_THING_POS,
5                                 cmd_TERRAIN_LINE, cmd_PLAYER_ID, cmd_TURN,
6                                 cmd_SWITCH_PLAYER, cmd_SAVE)
7 from plomrogue.mapping import MapHex
8 from plomrogue.parser import Parser
9 from plomrogue.io import GameIO
10 from plomrogue.misc import quote, stringify_yx
11 from plomrogue.things import Thing, ThingMonster, ThingHuman, ThingItem
12
13
14
15 class WorldBase:
16
17     def __init__(self, game):
18         self.turn = 0
19         self.things = []
20         self.game = game
21
22     def get_thing(self, id_, create_unfound=True):
23         for thing in self.things:
24             if id_ == thing.id_:
25                 return thing
26         if create_unfound:
27             t = self.game.thing_type(self, id_)
28             self.things += [t]
29             return t
30         return None
31
32
33
34 class World(WorldBase):
35
36     def __init__(self, *args, **kwargs):
37         super().__init__(*args, **kwargs)
38         self.player_id = 0
39
40     def new_thing_id(self):
41         if len(self.things) == 0:
42             return 0
43         return self.things[-1].id_ + 1
44
45     def new_map(self, yx):
46         self.map_ = self.game.map_type(yx)
47
48     def proceed_to_next_player_turn(self):
49         """Run game world turns until player can decide their next step.
50
51         Iterates through all non-player things, on each step
52         furthering them in their tasks (and letting them decide new
53         ones if they finish). The iteration order is: first all things
54         that come after the player in the world things list, then
55         (after incrementing the world turn) all that come before the
56         player; then the player's .proceed() is run, and if it does
57         not finish his task, the loop starts at the beginning. Once
58         the player's task is finished, the loop breaks.
59         """
60         while True:
61             player = self.get_player()
62             player_i = self.things.index(player)
63             for thing in self.things[player_i+1:]:
64                 thing.proceed()
65             self.turn += 1
66             for thing in self.things[:player_i]:
67                 thing.proceed()
68             player.proceed(is_AI=False)
69             if player.task is None:
70                 break
71
72     def get_player(self):
73         return self.get_thing(self.player_id)
74
75     def make_new(self, yx, seed):
76         import random
77
78         def add_thing(type_):
79             t = self.game.thing_types[type_](self)
80             t.position = [random.randint(0, yx[0] -1),
81                           random.randint(0, yx[1] - 1)]
82             self.things += [t]
83             return t
84
85         self.things = []
86         random.seed(seed)
87         self.turn = 0
88         self.new_map(yx)
89         for pos in self.map_:
90             if 0 in pos or (yx[0] - 1) == pos[0] or (yx[1] - 1) == pos[1]:
91                 self.map_[pos] = '#'
92                 continue
93             self.map_[pos] = random.choice(('.', '.', '.', '.', 'x'))
94
95         player = add_thing('human')
96         self.player_id = player.id_
97         add_thing('monster')
98         add_thing('monster')
99         add_thing('item')
100         add_thing('item')
101         return 'success'
102
103
104
105 class Game:
106
107     def __init__(self, game_file_name):
108         self.io = GameIO(game_file_name, self)
109         self.map_type = MapHex
110         self.tasks = {'WAIT': Task_WAIT,
111                       'MOVE': Task_MOVE,
112                       'PICKUP': Task_PICKUP,
113                       'DROP': Task_DROP}
114         self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
115                          'GET_GAMESTATE': cmd_GET_GAMESTATE,
116                          'MAP': cmd_MAP,
117                          'THING_TYPE': cmd_THING_TYPE,
118                          'THING_POS': cmd_THING_POS,
119                          'TERRAIN_LINE': cmd_TERRAIN_LINE,
120                          'PLAYER_ID': cmd_PLAYER_ID,
121                          'TURN': cmd_TURN,
122                          'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
123                          'SAVE': cmd_SAVE}
124         self.world_type = World
125         self.world = self.world_type(self)
126         self.thing_type = Thing
127         self.thing_types = {'human': ThingHuman,
128                             'monster': ThingMonster,
129                             'item': ThingItem}
130
131     def get_string_options(self, string_option_type):
132         if string_option_type == 'direction':
133             return self.world.map_.get_directions()
134         elif string_option_type == 'thingtype':
135             return list(self.thing_types.keys())
136         return None
137
138     def send_gamestate(self, connection_id=None):
139         """Send out game state data relevant to clients."""
140
141         self.io.send('TURN ' + str(self.world.turn))
142         self.io.send('MAP ' + stringify_yx(self.world.map_.size))
143         visible_map = self.world.get_player().get_visible_map()
144         for y, line in visible_map.lines():
145             self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
146         visible_things = self.world.get_player().get_visible_things()
147         for thing in visible_things:
148             self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
149             self.io.send('THING_POS %s %s' % (thing.id_,
150                                               stringify_yx(thing.position)))
151         player = self.world.get_player()
152         self.io.send('PLAYER_POS %s' % (stringify_yx(player.position)))
153         self.io.send('PLAYER_INVENTORY %s' % ','.join([str(i) for i in
154                                                        player.inventory]))
155         for id_ in player.inventory:
156             thing = self.world.get_thing(id_)
157             self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
158             self.io.send('THING_POS %s %s' % (thing.id_,
159                                               stringify_yx(thing.position)))
160         self.io.send('GAME_STATE_COMPLETE')
161
162     def proceed(self):
163         """Send turn finish signal, run game world, send new world data.
164
165         First sends 'TURN_FINISHED' message, then runs game world
166         until new player input is needed, then sends game state.
167         """
168         self.io.send('TURN_FINISHED ' + str(self.world.turn))
169         self.world.proceed_to_next_player_turn()
170         msg = str(self.world.get_player()._last_task_result)
171         self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
172         self.send_gamestate()
173
174     def get_command(self, command_name):
175
176         def partial_with_attrs(f, *args, **kwargs):
177             from functools import partial
178             p = partial(f, *args, **kwargs)
179             p.__dict__.update(f.__dict__)
180             return p
181
182         def cmd_TASK_colon(task_name, game, *args):
183             game.world.get_player().set_task(task_name, args)
184             game.proceed()
185
186         def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
187             t = game.world.get_thing(thing_id, False)
188             if t is None:
189                 raise ArgError('No such Thing.')
190             task_class = game.tasks[task_name]
191             t.task = task_class(t, args)
192             t.task.todo = todo
193
194         def task_prefixed(command_name, task_prefix, task_command,
195                           argtypes_prefix=None):
196             if command_name[:len(task_prefix)] == task_prefix:
197                 task_name = command_name[len(task_prefix):]
198                 if task_name in self.tasks:
199                     f = partial_with_attrs(task_command, task_name, self)
200                     task = self.tasks[task_name]
201                     if argtypes_prefix:
202                         f.argtypes = argtypes_prefix + ' ' + task.argtypes
203                     else:
204                         f.argtypes = task.argtypes
205                     return f
206             return None
207
208         command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
209         if command:
210             return command
211         command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
212                                 'int:nonneg int:nonneg ')
213         if command:
214             return command
215         if command_name in self.commands:
216             f = partial_with_attrs(self.commands[command_name], self)
217             return f
218         return None