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