home · contact · privacy
4f31dc1fb5ff397665fa3b71143f4961d92085cb
[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         return Map(self.world.map_.size, self.world.map_.terrain)
149
150
151 class Commander():
152
153     def cmd_MOVE(self, direction):
154         """Set player task to 'move' with direction arg, finish player turn."""
155         if direction not in {'UP', 'DOWN', 'RIGHT', 'LEFT'}:
156             raise parser.ArgError('Move argument must be one of: '
157                                   'UP, DOWN, RIGHT, LEFT')
158         self.world.get_player().set_task('move', direction=direction)
159         self.proceed()
160     cmd_MOVE.argtypes = 'string'
161
162     def cmd_WAIT(self):
163         """Set player task to 'wait', finish player turn."""
164         self.world.get_player().set_task('wait')
165         self.proceed()
166
167     def cmd_GET_GAMESTATE(self, connection_id):
168         """Send game state jto caller."""
169         self.send_gamestate(connection_id)
170
171     def cmd_ECHO(self, msg, connection_id):
172         """Send msg to caller."""
173         self.send(msg, connection_id)
174     cmd_ECHO.argtypes = 'string'
175
176     def cmd_ALL(self, msg, connection_id):
177         """Send msg to all clients."""
178         self.send(msg)
179     cmd_ALL.argtypes = 'string'
180
181     def cmd_TERRAIN_LINE(self, y, terrain_line):
182         self.world.map_.set_line(y, terrain_line)
183     cmd_TERRAIN_LINE.argtypes = 'int:nonneg string'