home · contact · privacy
Refactor.
[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             for t in self.thing.world.things:
83                 if t.position == test_pos:
84                     raise GameError('would move into other thing')
85
86
87 class Thing(game_common.Thing):
88
89     def __init__(self, *args, **kwargs):
90         super().__init__(*args, **kwargs)
91         self.task = Task(self, 'wait')
92         self.last_task_result = None
93
94     def task_wait(self):
95         return 'success'
96
97     def task_move(self, direction):
98         move_pos(direction, self.position)
99         return 'success'
100
101     def decide_task(self):
102         if self.position[1] > 1:
103             self.set_task('move', 'LEFT')
104         elif self.position[1] < 3:
105             self.set_task('move', 'RIGHT')
106         else:
107             self.set_task('wait')
108
109     def set_task(self, task_name, *args, **kwargs):
110         self.task = Task(self, task_name, args, kwargs)
111         self.task.check()
112
113     def proceed(self, is_AI=True):
114         """Further the thing in its tasks.
115
116         Decrements .task.todo; if it thus falls to <= 0, enacts method whose
117         name is 'task_' + self.task.name and sets .task = None. If is_AI, calls
118         .decide_task to decide a self.task.
119
120         Before doing anything, checks that task is still possible, and aborts
121         it otherwise (for AI things, decides a new task).
122         """
123         try:
124             self.task.check()
125         except GameError as e:
126             self.task = None
127             self.last_task_result = e
128             if is_AI:
129                 self.decide_task()
130             return
131         self.task.todo -= 1
132         if self.task.todo <= 0:
133             task = getattr(self, 'task_' + self.task.name)
134             self.last_task_result = task(*self.task.args, **self.task.kwargs)
135             self.task = None
136         if is_AI and self.task is None:
137             self.decide_task()
138
139
140 class Commander():
141
142     def cmd_MOVE(self, direction):
143         """Set player task to 'move' with direction arg, finish player turn."""
144         if direction not in {'UP', 'DOWN', 'RIGHT', 'LEFT'}:
145             raise parser.ArgError('Move argument must be one of: '
146                                   'UP, DOWN, RIGHT, LEFT')
147         self.world.get_player().set_task('move', direction=direction)
148         self.proceed()
149     cmd_MOVE.argtypes = 'string'
150
151     def cmd_WAIT(self):
152         """Set player task to 'wait', finish player turn."""
153         self.world.get_player().set_task('wait')
154         self.proceed()
155
156     def cmd_GET_GAMESTATE(self, connection_id):
157         """Send game state jto caller."""
158         self.send_gamestate(connection_id)
159
160     def cmd_ECHO(self, msg, connection_id):
161         """Send msg to caller."""
162         self.send(msg, connection_id)
163     cmd_ECHO.argtypes = 'string'
164
165     def cmd_ALL(self, msg, connection_id):
166         """Send msg to all clients."""
167         self.send(msg)
168     cmd_ALL.argtypes = 'string'