home · contact · privacy
7e1524716585c44ad9c88fd9712566aeadfe4ac5
[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 Map(game_common.Map):
23
24     def get_line(self, y):
25         width = self.size[1]
26         return self.terrain[y * width:(y + 1) * width]
27
28
29 class World(game_common.World):
30
31     def __init__(self):
32         super().__init__()
33         self.Thing = Thing  # use local Thing class instead of game_common's
34         self.map_ = Map()  # use extended child class
35         self.player_id = 0
36
37     def proceed_to_next_player_turn(self):
38         """Run game world turns until player can decide their next step.
39
40         Iterates through all non-player things, on each step
41         furthering them in their tasks (and letting them decide new
42         ones if they finish). The iteration order is: first all things
43         that come after the player in the world things list, then
44         (after incrementing the world turn) all that come before the
45         player; then the player's .proceed() is run, and if it does
46         not finish his task, the loop starts at the beginning. Once
47         the player's task is finished, the loop breaks.
48         """
49         while True:
50             player = self.get_player()
51             player_i = self.things.index(player)
52             for thing in self.things[player_i+1:]:
53                 thing.proceed()
54             self.turn += 1
55             for thing in self.things[:player_i]:
56                 thing.proceed()
57             player.proceed(is_AI=False)
58             if player.task is None:
59                 break
60
61     def get_player(self):
62         return self.get_thing(self.player_id)
63
64
65 class Task:
66
67     def __init__(self, thing, name, args=(), kwargs={}):
68         self.name = name
69         self.thing = thing
70         self.args = args
71         self.kwargs = kwargs
72         self.todo = 3
73
74     def check(self):
75         if self.name == 'move':
76             if len(self.args) > 0:
77                 direction = self.args[0]
78             else:
79                 direction = self.kwargs['direction']
80             test_pos = self.thing.position[:]
81             move_pos(direction, test_pos)
82             if test_pos[0] < 0 or test_pos[1] < 0 or \
83                test_pos[0] >= self.thing.world.map_.size[0] or \
84                test_pos[1] >= self.thing.world.map_.size[1]:
85                 raise GameError('would move outside map bounds')
86             pos_i = test_pos[0] * self.thing.world.map_.size[1] + test_pos[1]
87             map_tile = self.thing.world.map_.terrain[pos_i]
88             if map_tile != '.':
89                 raise GameError('would move into illegal terrain')
90             for t in self.thing.world.things:
91                 if t.position == test_pos:
92                     raise GameError('would move into other thing')
93
94
95 class Thing(game_common.Thing):
96
97     def __init__(self, *args, **kwargs):
98         super().__init__(*args, **kwargs)
99         self.task = Task(self, 'wait')
100         self.last_task_result = None
101
102     def task_wait(self):
103         return 'success'
104
105     def task_move(self, direction):
106         move_pos(direction, self.position)
107         return 'success'
108
109     def decide_task(self):
110         if self.position[1] > 1:
111             self.set_task('move', 'LEFT')
112         elif self.position[1] < 3:
113             self.set_task('move', 'RIGHT')
114         else:
115             self.set_task('wait')
116
117     def set_task(self, task_name, *args, **kwargs):
118         self.task = Task(self, task_name, args, kwargs)
119         self.task.check()
120
121     def proceed(self, is_AI=True):
122         """Further the thing in its tasks.
123
124         Decrements .task.todo; if it thus falls to <= 0, enacts method whose
125         name is 'task_' + self.task.name and sets .task = None. If is_AI, calls
126         .decide_task to decide a self.task.
127
128         Before doing anything, checks that task is still possible, and aborts
129         it otherwise (for AI things, decides a new task).
130         """
131         try:
132             self.task.check()
133         except GameError as e:
134             self.task = None
135             self.last_task_result = e
136             if is_AI:
137                 self.decide_task()
138             return
139         self.task.todo -= 1
140         if self.task.todo <= 0:
141             task = getattr(self, 'task_' + self.task.name)
142             self.last_task_result = task(*self.task.args, **self.task.kwargs)
143             self.task = None
144         if is_AI and self.task is None:
145             self.decide_task()
146
147     def get_visible_map(self):
148         size = self.world.map_.size
149         m = Map(size, '?'*size[0]*size[1])
150         y_me = self.position[0]
151         x_me = self.position[1]
152         for y in range(m.size[0]):
153             if y in (y_me - 1, y_me, y_me + 1):
154                 for x in range(m.size[1]):
155                     if x in (x_me - 1, x_me, x_me + 1):
156                         pos = y * size[1] + x
157                         c = self.world.map_.terrain[pos]
158                         m.terrain = m.terrain[:pos] + c + m.terrain[pos+1:]
159         return m
160
161
162 class Commander():
163
164     def cmd_MOVE(self, direction):
165         """Set player task to 'move' with direction arg, finish player turn."""
166         if direction not in {'UP', 'DOWN', 'RIGHT', 'LEFT'}:
167             raise parser.ArgError('Move argument must be one of: '
168                                   'UP, DOWN, RIGHT, LEFT')
169         self.world.get_player().set_task('move', direction=direction)
170         self.proceed()
171     cmd_MOVE.argtypes = 'string'
172
173     def cmd_WAIT(self):
174         """Set player task to 'wait', finish player turn."""
175         self.world.get_player().set_task('wait')
176         self.proceed()
177
178     def cmd_GET_GAMESTATE(self, connection_id):
179         """Send game state jto caller."""
180         self.send_gamestate(connection_id)
181
182     def cmd_ECHO(self, msg, connection_id):
183         """Send msg to caller."""
184         self.send(msg, connection_id)
185     cmd_ECHO.argtypes = 'string'
186
187     def cmd_ALL(self, msg, connection_id):
188         """Send msg to all clients."""
189         self.send(msg)
190     cmd_ALL.argtypes = 'string'
191
192     def cmd_TERRAIN_LINE(self, y, terrain_line):
193         self.world.map_.set_line(y, terrain_line)
194     cmd_TERRAIN_LINE.argtypes = 'int:nonneg string'