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