home · contact · privacy
Keep checking tasks are possible during proceeding.
[plomrogue2-experiments] / server_ / game.py
1 import sys
2 sys.path.append('../')
3 import game_common
4
5
6 class GameError(Exception):
7     pass
8
9
10 def move_pos(direction, pos_yx):
11     if direction == 'UP':
12         pos_yx[0] -= 1
13     elif direction == 'DOWN':
14         pos_yx[0] += 1
15     elif direction == 'RIGHT':
16         pos_yx[1] += 1
17     elif direction == 'LEFT':
18         pos_yx[1] -= 1
19
20
21 class World(game_common.World):
22
23     def __init__(self):
24         super().__init__()
25         self.Thing = Thing  # use local Thing class instead of game_common's
26         self.player_id = 0
27
28     def proceed_to_next_player_turn(self):
29         """Run game world turns until player can decide their next step.
30
31         Iterates through all non-player things, on each step
32         furthering them in their tasks (and letting them decide new
33         ones if they finish). The iteration order is: first all things
34         that come after the player in the world things list, then
35         (after incrementing the world turn) all that come before the
36         player; then the player's .proceed() is run, and if it does
37         not finish his task, the loop starts at the beginning. Once
38         the player's task is finished, the loop breaks.
39         """
40         while True:
41             player = self.get_player()
42             player_i = self.things.index(player)
43             for thing in self.things[player_i+1:]:
44                 thing.proceed()
45             self.turn += 1
46             for thing in self.things[:player_i]:
47                 thing.proceed()
48             player.proceed(is_AI=False)
49             if player.task is None:
50                 break
51
52     def get_player(self):
53         return self.get_thing(self.player_id)
54
55
56 class Task:
57
58     def __init__(self, thing, name, args=(), kwargs={}):
59         self.name = name
60         self.thing = thing
61         self.args = args
62         self.kwargs = kwargs
63         self.todo = 1
64
65     def check(self):
66         if self.name == 'move':
67             if len(self.args) > 0:
68                 direction = self.args[0]
69             else:
70                 direction = self.kwargs['direction']
71             test_pos = self.thing.position[:]
72             move_pos(direction, test_pos)
73             if test_pos[0] < 0 or test_pos[1] < 0 or \
74                test_pos[0] >= self.thing.world.map_size[0] or \
75                test_pos[1] >= self.thing.world.map_size[1]:
76                 raise GameError('would move outside map bounds')
77             pos_i = test_pos[0] * self.thing.world.map_size[1] + test_pos[1]
78             map_tile = self.thing.world.terrain_map[pos_i]
79             if map_tile != '.':
80                 raise GameError('would move into illegal terrain')
81
82
83 class Thing(game_common.Thing):
84
85     def __init__(self, *args, **kwargs):
86         super().__init__(*args, **kwargs)
87         self.task = Task(self, 'wait')
88
89     def task_wait(self):
90         pass
91
92     def task_move(self, direction):
93         move_pos(direction, self.position)
94
95     def decide_task(self):
96         if self.position[1] > 1:
97             self.set_task('move', 'LEFT')
98         elif self.position[1] < 3:
99             self.set_task('move', 'RIGHT')
100         else:
101             self.set_task('wait')
102
103     def set_task(self, task_name, *args, **kwargs):
104         self.task = Task(self, task_name, args, kwargs)
105         self.task.check()
106
107     def proceed(self, is_AI=True):
108         """Further the thing in its tasks.
109
110         Decrements .task.todo; if it thus falls to <= 0, enacts method whose
111         name is 'task_' + self.task.name and sets .task = None. If is_AI, calls
112         .decide_task to decide a self.task.
113
114         Before doing anything, checks that task is still possible, and aborts
115         it otherwise (for AI things, decides a new task).
116         """
117         try:
118             self.task.check()
119         except GameError:
120             self.task = None
121             if is_AI:
122                 self.decide_task()
123             return
124         self.task.todo -= 1
125         if self.task.todo <= 0:
126             task = getattr(self, 'task_' + self.task.name)
127             task(*self.task.args, **self.task.kwargs)
128             self.task = None
129         if is_AI and self.task is None:
130             self.decide_task()
131
132
133 class Commander():
134
135     def cmd_MOVE(self, direction):
136         """Set player task to 'move' with direction arg, finish player turn."""
137         if direction not in {'UP', 'DOWN', 'RIGHT', 'LEFT'}:
138             raise parser.ArgError('Move argument must be one of: '
139                                   'UP, DOWN, RIGHT, LEFT')
140         self.world.get_player().set_task('move', direction=direction)
141         self.proceed()
142     cmd_MOVE.argtypes = 'string'
143
144     def cmd_WAIT(self):
145         """Set player task to 'wait', finish player turn."""
146         self.world.get_player().set_task('wait')
147         self.proceed()
148
149     def cmd_GET_TURN(self, connection_id):
150         """Send world.turn to caller."""
151         self.send_to(connection_id, str(self.world.turn))
152
153     def cmd_ECHO(self, msg, connection_id):
154         """Send msg to caller."""
155         self.send_to(connection_id, msg)
156     cmd_ECHO.argtypes = 'string'
157
158     def cmd_ALL(self, msg, connection_id):
159         """Send msg to all clients."""
160         self.send_all(msg)
161     cmd_ALL.argtypes = 'string'