home · contact · privacy
More refactoring.
[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
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_type(self, 0)
81         player.type_ = 'human'
82         player.position = [random.randint(0, yx[0] -1),
83                            random.randint(0, yx[1] - 1)]
84         npc = self.game.thing_type(self, 1)
85         npc.type_ = 'monster'
86         npc.position = [random.randint(0, yx[0] -1),
87                         random.randint(0, yx[1] -1)]
88         self.things = [player, npc]
89         return 'success'
90
91
92
93 class Game:
94
95     def __init__(self, game_file_name):
96         self.io = GameIO(game_file_name, self)
97         self.map_type = MapHex
98         self.tasks = {'WAIT': Task_WAIT, 'MOVE': Task_MOVE}
99         self.commands = {'GEN_WORLD': cmd_GEN_WORLD,
100                          'GET_GAMESTATE': cmd_GET_GAMESTATE,
101                          'MAP': cmd_MAP,
102                          'THING_TYPE': cmd_THING_TYPE,
103                          'THING_POS': cmd_THING_POS,
104                          'TERRAIN_LINE': cmd_TERRAIN_LINE,
105                          'PLAYER_ID': cmd_PLAYER_ID,
106                          'TURN': cmd_TURN,
107                          'SWITCH_PLAYER': cmd_SWITCH_PLAYER,
108                          'SAVE': cmd_SAVE}
109         self.world_type = World
110         self.world = self.world_type(self)
111         self.thing_type = Thing
112
113     def get_string_options(self, string_option_type):
114         if string_option_type == 'direction':
115             return self.world.map_.get_directions()
116         return None
117
118     def send_gamestate(self, connection_id=None):
119         """Send out game state data relevant to clients."""
120
121         self.io.send('TURN ' + str(self.world.turn))
122         self.io.send('MAP ' + stringify_yx(self.world.map_.size))
123         visible_map = self.world.get_player().get_visible_map()
124         for y, line in visible_map.lines():
125             self.io.send('VISIBLE_MAP_LINE %5s %s' % (y, quote(line)))
126         visible_things = self.world.get_player().get_visible_things()
127         for thing in visible_things:
128             self.io.send('THING_TYPE %s %s' % (thing.id_, thing.type_))
129             self.io.send('THING_POS %s %s' % (thing.id_,
130                                               stringify_yx(thing.position)))
131         player = self.world.get_player()
132         self.io.send('PLAYER_POS %s' % (stringify_yx(player.position)))
133         self.io.send('GAME_STATE_COMPLETE')
134
135     def proceed(self):
136         """Send turn finish signal, run game world, send new world data.
137
138         First sends 'TURN_FINISHED' message, then runs game world
139         until new player input is needed, then sends game state.
140         """
141         self.io.send('TURN_FINISHED ' + str(self.world.turn))
142         self.world.proceed_to_next_player_turn()
143         msg = str(self.world.get_player()._last_task_result)
144         self.io.send('LAST_PLAYER_TASK_RESULT ' + quote(msg))
145         self.send_gamestate()
146
147     def get_command(self, command_name):
148
149         def partial_with_attrs(f, *args, **kwargs):
150             from functools import partial
151             p = partial(f, *args, **kwargs)
152             p.__dict__.update(f.__dict__)
153             return p
154
155         def cmd_TASK_colon(task_name, game, *args):
156             game.world.get_player().set_task(task_name, args)
157             game.proceed()
158
159         def cmd_SET_TASK_colon(task_name, game, thing_id, todo, *args):
160             t = game.world.get_thing(thing_id, False)
161             if t is None:
162                 raise ArgError('No such Thing.')
163             task_class = game.tasks[task_name]
164             t.task = task_class(t, args)
165             t.task.todo = todo
166
167         def task_prefixed(command_name, task_prefix, task_command,
168                           argtypes_prefix=None):
169             if command_name[:len(task_prefix)] == task_prefix:
170                 task_name = command_name[len(task_prefix):]
171                 if task_name in self.tasks:
172                     f = partial_with_attrs(task_command, task_name, self)
173                     task = self.tasks[task_name]
174                     if argtypes_prefix:
175                         f.argtypes = argtypes_prefix + ' ' + task.argtypes
176                     else:
177                         f.argtypes = task.argtypes
178                     return f
179             return None
180
181         command = task_prefixed(command_name, 'TASK:', cmd_TASK_colon)
182         if command:
183             return command
184         command = task_prefixed(command_name, 'SET_TASK:', cmd_SET_TASK_colon,
185                                 'int:nonneg int:nonneg ')
186         if command:
187             return command
188         if command_name in self.commands:
189             f = partial_with_attrs(self.commands[command_name], self)
190             return f
191         return None