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