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