OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * Stores the actual data on a player's boat grid, the UI representation for its | |
7 * grid and the status of each shot. Acts as a controller handling isolate | |
8 * messages (from the main isolate message and shots from the enemy), and UI | |
9 * events. | |
10 */ | |
11 // TODO(sigmund): move UI setup out of here, e.g. into a controller class. | |
12 class PlayerState extends Isolate { | |
13 | |
14 /** internal id for this player. */ | |
15 int _id; | |
16 | |
17 /** Set up of boats on the board. */ | |
18 BoatGrid boats; | |
19 | |
20 /** State of shots taken by the enemy on this player's board. */ | |
21 GridState localGrid; | |
22 | |
23 /** State of shots this player has taken on the enemy. */ | |
24 GridState enemyGrid; | |
25 | |
26 /** Total shots made. */ | |
27 int totalShots; | |
28 | |
29 /** Total hits. */ | |
30 int totalHits; | |
31 | |
32 /** Total misses. */ | |
33 int totalMisses; | |
34 | |
35 /** Total boats that we have sunk. */ | |
36 int boatsSunk; | |
37 | |
38 /** Interface to interact with the enemy. */ | |
39 Enemy enemy; | |
40 | |
41 /** UI representation of this player's grid. */ | |
42 PlayerGridView _ownView; | |
43 | |
44 /** UI representation of the enemy's grid. */ | |
45 EnemyGridView _enemyView; | |
46 | |
47 /** Port used for testing purposes. */ | |
48 SendPort _portForTest; | |
49 | |
50 // This can take no arguments for now (wait for isolate redesign). | |
51 PlayerState() : super.light() {} | |
52 | |
53 void main() { | |
54 this.port.receive((message, SendPort replyTo) { | |
55 dispatch(message, replyTo); | |
56 }); | |
57 } | |
58 | |
59 /** dispatches all messages that are expected in this isolate. */ | |
60 void dispatch(var message, SendPort replyTo) { | |
61 int action = message['action']; | |
62 List args = message['args']; | |
63 switch (action) { | |
64 // message from the main program, setting this as player 1 or 2 | |
65 case MessageIds.SETUP: | |
66 handleSetup(args[0]); | |
67 break; | |
68 // message from the main program, giving port to talk with other player | |
69 case MessageIds.SET_ENEMY: | |
70 enemy = new Enemy(args[0]); | |
71 break; | |
72 // message from the other player indicating it's ready to play. | |
73 case MessageIds.ENEMY_IS_READY: | |
74 _enemyView.setEnemyReady(); | |
75 replyTo.send([true], null); | |
76 break; | |
77 // message from the other player indicating a shot. | |
78 case MessageIds.SHOOT: | |
79 List res = handleShot(args[0], args[1]); | |
80 replyTo.send([true, res], null); | |
81 break; | |
82 // message from the unit test (used to make tests deterministic) | |
83 case MessageIds.SET_PORT_FOR_TEST: | |
84 _portForTest = args[0]; | |
85 replyTo.send([true], null); | |
86 break; | |
87 default: | |
88 break; | |
89 } | |
90 } | |
91 | |
92 /** Handles a message from the main isolate to setup this player. */ | |
93 void handleSetup(int id) { | |
94 _id = id; | |
95 boats = new BoatGrid(); | |
96 localGrid = new GridState(); | |
97 enemyGrid = new GridState(); | |
98 totalShots = 0; | |
99 totalHits = 0; | |
100 totalMisses = 0; | |
101 boatsSunk = 0; | |
102 | |
103 _ownView = new PlayerGridView(this, document.query("#p${id}own")); | |
104 _enemyView = new EnemyGridView(this, document.query("#p${id}enemy")); | |
105 if (_portForTest != null) { | |
106 _portForTest.call(["_TEST:handleSetup", id]); | |
107 } | |
108 } | |
109 | |
110 /** Handles a shot message from the enemy player. */ | |
111 List handleShot(int x, int y) { | |
112 List res = boats.shoot(localGrid, x, y); | |
113 switch (res[0]) { | |
114 case Constants.MISS: | |
115 _ownView.addMiss(x, y); | |
116 break; | |
117 case Constants.HIT: | |
118 _ownView.addHit(x, y); | |
119 break; | |
120 case Constants.SUNK: | |
121 _ownView.addHit(x, y); | |
122 break; | |
123 } | |
124 if (_portForTest != null) { | |
125 _portForTest.call(["_TEST:handleShot", _id, res[0], x, y]); | |
126 } | |
127 return res; | |
128 } | |
129 | |
130 /** local action to add a boat in the grid. */ | |
131 void addBoat(Boat boat) { | |
132 boats.placeBoats([boat]); | |
133 assert(enemy != null); | |
134 enemy.ready(); | |
135 } | |
136 | |
137 /** local action to generate an asynchronous shot at the enemy. */ | |
138 void shoot(int x, int y) { | |
139 superShot(x, y, _id % 2 == 0); | |
140 } | |
141 | |
142 /** A single shot on (x, y). */ | |
143 singleShot(int x, int y) { | |
144 if (_canShoot(x, y)) { | |
145 _recordPendingShot(x, y); | |
146 try { // async shot! | |
147 _recordShotResult(await enemy.shoot(x, y), x, y); | |
148 } catch (var e) { | |
149 _recordFailedShot(x, y); | |
150 } | |
151 } | |
152 } | |
153 | |
154 /** | |
155 * Takes 1 shot, if it's a hit, it then shoots to each of the 4 cardinal | |
156 * directions until a boat is sunk. When [parallel] all directions are | |
157 * explored in parallel. | |
158 */ | |
159 superShot(int x, int y, bool parallel) { | |
160 if (_canShoot(x, y)) { | |
161 _recordPendingShot(x, y); | |
162 try { | |
163 int firstShot = await enemy.shoot(x, y); | |
164 _recordShotResult(firstShot, x, y); | |
165 if (firstShot == Constants.HIT) { | |
166 // no miss, but no sunk, search around | |
167 _exploreAllDirections(x, y, parallel); | |
168 } | |
169 } catch (var e) { | |
170 _recordFailedShot(x, y); | |
171 } | |
172 } | |
173 } | |
174 | |
175 static final LEFT_DIR = const [-1, 0]; | |
176 static final RIGHT_DIR = const [1, 0]; | |
177 static final UP_DIR = const [0, -1]; | |
178 static final DOWN_DIR = const [0, 1]; | |
179 | |
180 Future<bool> _exploreAllDirections(int x, int y, bool parallel) { | |
181 if (parallel) { | |
182 final arr = [ | |
183 _exploreDirectionHelper(LEFT_DIR, x, y), | |
184 _exploreDirectionHelper(RIGHT_DIR, x, y), | |
185 _exploreDirectionHelper(UP_DIR, x, y), | |
186 _exploreDirectionHelper(DOWN_DIR, x, y)]; | |
187 return (await Futures.wait(arr)).some((v) => v); | |
188 } else { | |
189 return await _exploreDirectionHelper(LEFT_DIR, x, y) | |
190 || await _exploreDirectionHelper(RIGHT_DIR, x, y) | |
191 || await _exploreDirectionHelper(UP_DIR, x, y) | |
192 || await _exploreDirectionHelper(DOWN_DIR, x, y); | |
193 } | |
194 } | |
195 | |
196 Future<bool> _exploreDirectionHelper(List<int> dir, int x, int y) { | |
197 bool res = await _followDir(x + dir[0], y + dir[1], dir[0], dir[1]); | |
198 return res; | |
199 } | |
200 | |
201 Future<bool> _followDir(int x, int y, int incX, int incY) { | |
202 while (_canShoot(x, y)) { | |
203 _recordPendingShot(x, y); | |
204 try { | |
205 int shot = await enemy.shoot(x, y); | |
206 _recordShotResult(shot, x, y); | |
207 // TODO(sigmund): make this into a switch when they are supported | |
208 if (shot == Constants.HIT) { | |
209 x += incX; | |
210 y += incY; | |
211 } else { | |
212 // TODO(sigmund): fix awaitc - this return statement is not | |
213 // transformed correctly because we currently prune the AST too | |
214 // agressively. We need an extra analysis phase that determines which | |
215 // statements have to be rewritten (returns anywhere, break/continue | |
216 // within loops that contain await, etc). | |
217 return shot == Constants.SUNK; | |
218 } | |
219 } catch (var e) { | |
220 _recordFailedShot(x, y); | |
221 throw e; | |
222 } | |
223 } | |
224 return false; | |
225 } | |
226 | |
227 /** checks that a shot is in range and has not been done before. */ | |
228 bool _canShoot(int x, int y) { | |
229 return _inRange(x, y) && enemyGrid.valueAt(x, y) == null; | |
230 } | |
231 | |
232 /** checks that a shot is in range. */ | |
233 bool _inRange(int x, int y) { | |
234 return x >= 0 && y >=0 && x < Constants.SIZE && y < Constants.SIZE; | |
235 } | |
236 | |
237 /** register a pending shot in the local enemyGrid state and update the UI. */ | |
238 void _recordPendingShot(int x, int y) { | |
239 totalShots++; | |
240 _enemyView.statusBar.updateStatus(); | |
241 _enemyView.addMaybeHit(x, y); | |
242 enemyGrid.pending(x, y); | |
243 } | |
244 | |
245 /** record a cancelled shot in the local enemyGrid state and update the UI. */ | |
246 void _recordCancelledShot(int x, int y) { | |
247 totalShots--; | |
248 _enemyView.removeMaybeHit(x, y); | |
249 _enemyView.statusBar.updateStatus(); | |
250 enemyGrid.clear(x, y); | |
251 } | |
252 | |
253 /** record a failing shot in the local enemyGrid state and update the UI. */ | |
254 void _recordFailedShot(int x, int y) { | |
255 _recordCancelledShot(x, y); | |
256 } | |
257 | |
258 /** register the result of a shot and update the UI. */ | |
259 void _recordShotResult(int shotResult, int x, int y) { | |
260 switch(shotResult) { | |
261 case Constants.MISS: | |
262 totalMisses++; | |
263 _enemyView.addMiss(x, y); | |
264 enemyGrid.miss(x, y); | |
265 break; | |
266 case Constants.HIT: | |
267 totalHits++; | |
268 _enemyView.addHit(x, y); | |
269 enemyGrid.hit(x, y); | |
270 break; | |
271 case Constants.SUNK: | |
272 totalHits++; | |
273 boatsSunk++; | |
274 _enemyView.addHit(x, y); | |
275 enemyGrid.hit(x, y); | |
276 break; | |
277 } | |
278 _enemyView.statusBar.updateStatus(); | |
279 } | |
280 } | |
OLD | NEW |