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