| 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 void singleShot(int x, int y) { | |
| 144 if (_canShoot(x, y)) { | |
| 145 _recordPendingShot(x, y); | |
| 146 Future<int> res = enemy.shoot(x, y); // async shot! | |
| 147 res.then((int result) { | |
| 148 _recordShotResult(result, x, y); | |
| 149 }); | |
| 150 res.handleException((String error) { | |
| 151 _recordFailedShot(x, y); | |
| 152 return true; | |
| 153 }); | |
| 154 } | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Takes 1 shot, if it's a hit, it then shoots to each of the 4 cardinal | |
| 159 * directions until a boat is sunk. When [parallel] all directions are | |
| 160 * explored in parallel. | |
| 161 */ | |
| 162 void superShot(int x, int y, bool parallel) { | |
| 163 if (_canShoot(x, y)) { | |
| 164 _recordPendingShot(x, y); | |
| 165 Future<int> firstShot = enemy.shoot(x, y); | |
| 166 firstShot.then((int res) { | |
| 167 _recordShotResult(res, x, y); | |
| 168 if (res == Constants.HIT) { | |
| 169 // no miss, but no sunk, search around | |
| 170 _exploreAllDirections(x, y, parallel); | |
| 171 } | |
| 172 }); | |
| 173 firstShot.handleException((String error) { | |
| 174 _recordFailedShot(x, y); | |
| 175 return true; | |
| 176 }); | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 static final LEFT_DIR = const [-1, 0]; | |
| 181 static final RIGHT_DIR = const [1, 0]; | |
| 182 static final UP_DIR = const [0, -1]; | |
| 183 static final DOWN_DIR = const [0, 1]; | |
| 184 | |
| 185 Future<bool> _exploreAllDirections(int x, int y, bool parallel) { | |
| 186 Completer<bool> superShot_ = new Completer<bool>(); | |
| 187 if (parallel) { | |
| 188 final arr = new List<Future<bool>>(); | |
| 189 arr.add(_exploreDirectionHelper(LEFT_DIR, x, y)); | |
| 190 arr.add(_exploreDirectionHelper(RIGHT_DIR, x, y)); | |
| 191 arr.add(_exploreDirectionHelper(UP_DIR, x, y)); | |
| 192 arr.add(_exploreDirectionHelper(DOWN_DIR, x, y)); | |
| 193 Futures.wait(arr).then((arrValues) { | |
| 194 superShot_.complete(true); | |
| 195 }); | |
| 196 } else { | |
| 197 _seqExploreDirectionHelper(LEFT_DIR, x, y, superShot_, | |
| 198 _seqExploreDirectionHelper(RIGHT_DIR, x, y, superShot_, | |
| 199 _seqExploreDirectionHelper(UP_DIR, x, y, superShot_, | |
| 200 _seqExploreDirectionHelper(DOWN_DIR, x, y, superShot_, null))))(fa
lse); | |
| 201 } | |
| 202 return superShot_.future; | |
| 203 } | |
| 204 Function _seqExploreDirectionHelper(List<int> dir, int x, int y, | |
| 205 Completer<bool> seq, void _next(bool res)) { | |
| 206 return (bool res) { | |
| 207 if (res) { | |
| 208 seq.complete(true); | |
| 209 } else { | |
| 210 _exploreDirectionHelper(dir, x, y).then( | |
| 211 (_next != null) ? _next : (void _(v) {seq.complete(false);})); | |
| 212 } | |
| 213 }; | |
| 214 } | |
| 215 | |
| 216 Future<bool> _exploreDirectionHelper(List<int> dir, int x, int y) { | |
| 217 Completer<bool> sunk = new Completer<bool>(); | |
| 218 _followDir(x + dir[0], y + dir[1], dir[0], dir[1], sunk); | |
| 219 return sunk.future; | |
| 220 } | |
| 221 | |
| 222 void _followDir(int x, int y, int incX, int incY, Completer<bool> sunk) { | |
| 223 if (_canShoot(x, y)) { | |
| 224 _recordPendingShot(x, y); | |
| 225 Future<int> shot = enemy.shoot(x, y); | |
| 226 shot.then((int res) { | |
| 227 _recordShotResult(res, x, y); | |
| 228 switch (res) { | |
| 229 case Constants.HIT: | |
| 230 if (!sunk.future.isComplete) { | |
| 231 _followDir(x + incX, y + incY, incX, incY, sunk); | |
| 232 } | |
| 233 break; | |
| 234 case Constants.SUNK: | |
| 235 sunk.complete(true); | |
| 236 break; | |
| 237 case Constants.MISS: | |
| 238 sunk.complete(false); | |
| 239 break; | |
| 240 } | |
| 241 }); | |
| 242 shot.handleException((String error) { | |
| 243 _recordFailedShot(x, y); | |
| 244 sunk.completeException(error); | |
| 245 return true; | |
| 246 }); | |
| 247 // We don't actually chain sunk.cancel with shot.cancel because individual | |
| 248 // shots can't be cancelled. | |
| 249 } else { | |
| 250 sunk.complete(false); | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 /** checks that a shot is in range and has not been done before. */ | |
| 255 bool _canShoot(int x, int y) { | |
| 256 return _inRange(x, y) && enemyGrid.valueAt(x, y) == null; | |
| 257 } | |
| 258 | |
| 259 /** checks that a shot is in range. */ | |
| 260 bool _inRange(int x, int y) { | |
| 261 return x >= 0 && y >=0 && x < Constants.SIZE && y < Constants.SIZE; | |
| 262 } | |
| 263 | |
| 264 /** register a pending shot in the local enemyGrid state and update the UI. */ | |
| 265 void _recordPendingShot(int x, int y) { | |
| 266 totalShots++; | |
| 267 _enemyView.statusBar.updateStatus(); | |
| 268 _enemyView.addMaybeHit(x, y); | |
| 269 enemyGrid.pending(x, y); | |
| 270 } | |
| 271 | |
| 272 /** record a cancelled shot in the local enemyGrid state and update the UI. */ | |
| 273 void _recordCancelledShot(int x, int y) { | |
| 274 totalShots--; | |
| 275 _enemyView.removeMaybeHit(x, y); | |
| 276 _enemyView.statusBar.updateStatus(); | |
| 277 enemyGrid.clear(x, y); | |
| 278 } | |
| 279 | |
| 280 /** record a failing shot in the local enemyGrid state and update the UI. */ | |
| 281 void _recordFailedShot(int x, int y) { | |
| 282 _recordCancelledShot(x, y); | |
| 283 } | |
| 284 | |
| 285 /** register the result of a shot and update the UI. */ | |
| 286 void _recordShotResult(int shotResult, int x, int y) { | |
| 287 switch(shotResult) { | |
| 288 case Constants.MISS: | |
| 289 totalMisses++; | |
| 290 _enemyView.addMiss(x, y); | |
| 291 enemyGrid.miss(x, y); | |
| 292 break; | |
| 293 case Constants.HIT: | |
| 294 totalHits++; | |
| 295 _enemyView.addHit(x, y); | |
| 296 enemyGrid.hit(x, y); | |
| 297 break; | |
| 298 case Constants.SUNK: | |
| 299 totalHits++; | |
| 300 boatsSunk++; | |
| 301 _enemyView.addHit(x, y); | |
| 302 enemyGrid.hit(x, y); | |
| 303 break; | |
| 304 } | |
| 305 _enemyView.statusBar.updateStatus(); | |
| 306 } | |
| 307 } | |
| OLD | NEW |