| 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 /** Base class for all views. */ | |
| 6 class View { | |
| 7 Document doc; | |
| 8 View(this.doc) {} | |
| 9 } | |
| 10 | |
| 11 /** The view displayed to the player for its own grid. */ | |
| 12 class PlayerGridView extends View { | |
| 13 /** Model associated with the player's state. */ | |
| 14 PlayerState state; | |
| 15 | |
| 16 /** A div element containing the grid. */ | |
| 17 Element _rootNode; | |
| 18 | |
| 19 PlayerGridView( | |
| 20 PlayerState this.state, Element rootNode) | |
| 21 : super(rootNode.document), _rootNode = rootNode { | |
| 22 render(); | |
| 23 } | |
| 24 | |
| 25 /** Create an initial visual representation of this view. */ | |
| 26 void render() { | |
| 27 String cell = "<div class='icons water'></div>"; | |
| 28 StringBuffer _cells = new StringBuffer(); | |
| 29 for (int i = 0 ; i < state.localGrid.cells.length; i++) { | |
| 30 _cells.add(cell); | |
| 31 } | |
| 32 String cells = _cells.toString(); | |
| 33 String row = "<div class='hbox'>${cells}</div>"; | |
| 34 StringBuffer _rows = new StringBuffer(); | |
| 35 for (int i = 0 ; i < state.localGrid.cells.length; i++) { | |
| 36 _rows.add(row); | |
| 37 } | |
| 38 String rows = _rows.toString(); | |
| 39 String table = "<div class='vbox'>${rows}</div>"; | |
| 40 _rootNode.innerHTML = table; | |
| 41 | |
| 42 // Attaches listeners onto this view. | |
| 43 new PlaceBoatView(state, _rootNode).attach(); | |
| 44 } | |
| 45 | |
| 46 /** Adds to this view the respresentation of a missed shot. */ | |
| 47 void addMiss(int x, int y) { | |
| 48 Element node = ViewUtil.createDiv("icons miss"); | |
| 49 ViewUtil.placeNodeAt(node, x, y); | |
| 50 _rootNode.nodes.add(node); | |
| 51 } | |
| 52 | |
| 53 /** Adds to this view the respresentation of a shot that hits our boat. */ | |
| 54 void addHit(int x, int y) { | |
| 55 Element node = ViewUtil.createDiv("icons hit-onboat"); | |
| 56 ViewUtil.placeNodeAt(node, x, y); | |
| 57 _rootNode.nodes.add(node); | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 /** View used to interactively set a new boat on the board. */ | |
| 62 class PlaceBoatView extends View { | |
| 63 PlayerState state; | |
| 64 | |
| 65 /** root of the grid. */ | |
| 66 Element _rootNode; | |
| 67 | |
| 68 /** start location where the user first clicked. */ | |
| 69 int _boatStartX; | |
| 70 int _boatStartY; | |
| 71 | |
| 72 /** last known mouse location. */ | |
| 73 int _boatLastX; | |
| 74 int _boatLastY; | |
| 75 | |
| 76 /** HTML element rendering the actual boat. */ | |
| 77 Element _possibleBoat; | |
| 78 | |
| 79 /** Mouse move-listener to be detached when the boat is placed. */ | |
| 80 Function _moveListener; | |
| 81 | |
| 82 PlaceBoatView( | |
| 83 PlayerState this.state, Element rootNode) | |
| 84 : super(rootNode.document), _rootNode = rootNode {} | |
| 85 | |
| 86 void attach() { | |
| 87 _rootNode.on.mouseDown.add(handleMouseDown); | |
| 88 _rootNode.on.mouseUp.add(handleMouseUp); | |
| 89 } | |
| 90 | |
| 91 void handleMouseDown(e) { | |
| 92 e.preventDefault(); | |
| 93 ViewUtil.positionFromEvent(_rootNode, e).then((List<int> pos) { | |
| 94 _boatStartX = pos[0]; | |
| 95 _boatStartY = pos[1]; | |
| 96 // error case when the mouse was released out of the boat-placing area | |
| 97 if (_moveListener != null) { | |
| 98 _rootNode.on.mouseMove.remove(_moveListener, false); | |
| 99 _possibleBoat.remove(); | |
| 100 _moveListener = null; | |
| 101 } | |
| 102 _possibleBoat = ViewUtil.createDiv("icons boat2"); | |
| 103 ViewUtil.placeNodeAt(_possibleBoat, _boatStartX, _boatStartY); | |
| 104 _rootNode.nodes.add(_possibleBoat); | |
| 105 _moveListener = handleMouseMove; | |
| 106 _rootNode.on.mouseMove.add(_moveListener); | |
| 107 }); | |
| 108 } | |
| 109 | |
| 110 void handleMouseMove(e) { | |
| 111 e.preventDefault(); | |
| 112 ViewUtil.positionFromEvent(_rootNode, e).then((List<int> pos) { | |
| 113 if (_boatLastX == pos[0] && _boatLastY == pos[1]) { | |
| 114 return; | |
| 115 } | |
| 116 _boatLastX = pos[0]; | |
| 117 _boatLastY = pos[1]; | |
| 118 int deltaX = _boatLastX - _boatStartX; | |
| 119 int deltaY = _boatLastY - _boatStartY; | |
| 120 | |
| 121 String dir; | |
| 122 bool flip = false; | |
| 123 int boatSize = 2; | |
| 124 if (deltaX.abs() >= deltaY.abs()) { | |
| 125 dir = deltaX < 0 ? "right" : "left"; | |
| 126 boatSize = Math.max(2, Math.min(5, deltaX.abs() + 1)); | |
| 127 } else { | |
| 128 dir = deltaY < 0 ? "up" : "down"; | |
| 129 boatSize = Math.max(2, Math.min(5, deltaY.abs() + 1)); | |
| 130 } | |
| 131 | |
| 132 _possibleBoat.attributes["class"] = "icons boat${boatSize} boatdir-${dir}"
; | |
| 133 }); | |
| 134 } | |
| 135 | |
| 136 /** Handle end of positioning of a boat. */ | |
| 137 void handleMouseUp(e) { | |
| 138 _rootNode.on.mouseMove.remove(_moveListener, false); | |
| 139 _moveListener = null; | |
| 140 ViewUtil.positionFromEvent(_rootNode, e).then((List<int> pos) { | |
| 141 int _boatEndX = pos[0]; | |
| 142 int _boatEndY = pos[1]; | |
| 143 | |
| 144 int deltaX = _boatEndX - _boatStartX; | |
| 145 int deltaY = _boatEndY - _boatStartY; | |
| 146 Boat boat; | |
| 147 | |
| 148 if (deltaX.abs() >= deltaY.abs()) { | |
| 149 int boatSize = Math.max(2, Math.min(5, deltaX.abs() + 1)); | |
| 150 boat = new Boat(deltaX < 0 ? (_boatStartX - boatSize + 1) : _boatStartX, | |
| 151 _boatStartY, true, boatSize); | |
| 152 } else { | |
| 153 int boatSize = Math.max(2, Math.min(5, deltaY.abs() + 1)); | |
| 154 boat = new Boat(_boatStartX, | |
| 155 deltaY < 0 ? (_boatStartY - boatSize + 1) : _boatStartY, | |
| 156 false, boatSize); | |
| 157 } | |
| 158 | |
| 159 state.addBoat(boat); | |
| 160 }); | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 /** The view displayed to the player for its enemy's grid. */ | |
| 165 class EnemyGridView extends View { | |
| 166 PlayerState state; | |
| 167 bool _enemyReady; | |
| 168 Element _rootNode; | |
| 169 ShootingStatusView statusBar; | |
| 170 | |
| 171 EnemyGridView( | |
| 172 PlayerState this.state, Element rootNode) | |
| 173 : super(rootNode.document), | |
| 174 _enemyReady = false, | |
| 175 _rootNode = rootNode { | |
| 176 | |
| 177 String cell = "<div class='icons water'></div>"; | |
| 178 StringBuffer _cells = new StringBuffer(); | |
| 179 for (int i = 0 ; i < state.enemyGrid.cells.length; i++) { | |
| 180 _cells.add(cell); | |
| 181 } | |
| 182 String cells = _cells.toString(); | |
| 183 String row = "<div class='hbox'>${cells}</div>"; | |
| 184 StringBuffer _rows = new StringBuffer(); | |
| 185 for (int i = 0 ; i < state.enemyGrid.cells.length; i++) { | |
| 186 _rows.add(row); | |
| 187 } | |
| 188 String rows = _rows.toString(); | |
| 189 String table = "<div class='vbox'>${rows}</div>"; | |
| 190 _rootNode.innerHTML = | |
| 191 "${table}<div class='notready'>ENEMY IS NOT READY</div>"; | |
| 192 statusBar = new ShootingStatusView(state, doc); | |
| 193 _rootNode.nodes.add(statusBar._rootNode); | |
| 194 _rootNode.on.click.add((Event e) { | |
| 195 MouseEvent mouseEvent = e; | |
| 196 handleClick(mouseEvent); | |
| 197 }, false); | |
| 198 } | |
| 199 | |
| 200 /** Interpret clicks as a shooting action. */ | |
| 201 void handleClick(MouseEvent e) { | |
| 202 ViewUtil.positionFromEvent(_rootNode, e).then((List<int> pos) { | |
| 203 state.shoot(pos[0], pos[1]); | |
| 204 }); | |
| 205 } | |
| 206 | |
| 207 /** Update the view to indicate the enemy is ready. */ | |
| 208 void setEnemyReady() { | |
| 209 if (!_enemyReady) { | |
| 210 _enemyReady = true; | |
| 211 _rootNode.query(".notready").remove(); | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 | |
| 216 /** Update the view to indicate a shot that hit an enemy's boat. */ | |
| 217 void addHit(int x, int y) { | |
| 218 Element node = ViewUtil.createDiv("icons hit"); | |
| 219 ViewUtil.placeNodeAt(node, x, y); | |
| 220 _rootNode.nodes.add(node); | |
| 221 } | |
| 222 | |
| 223 /** Update the view to indicate a shot that missed an enemy's boat. */ | |
| 224 void addMiss(int x, int y) { | |
| 225 Element node = ViewUtil.createDiv("icons miss"); | |
| 226 ViewUtil.placeNodeAt(node, x, y); | |
| 227 _rootNode.nodes.add(node); | |
| 228 } | |
| 229 | |
| 230 /** Update the view to indicate a shot is in progress. */ | |
| 231 void addMaybeHit(int x, int y) { | |
| 232 Element node = ViewUtil.createDiv("icons maybe-hit"); | |
| 233 ViewUtil.placeNodeAt(node, x, y); | |
| 234 _rootNode.nodes.add(node); | |
| 235 } | |
| 236 | |
| 237 /** | |
| 238 * Remove the icon indicating that a shot is in progress (only called when | |
| 239 * shots failed due to network errors). | |
| 240 */ | |
| 241 void removeMaybeHit(int x, int y) { | |
| 242 for (Element node in _rootNode.queryAll(".maybe-hit")) { | |
| 243 int xoffset = x * 50; | |
| 244 int yoffset = y * 50; | |
| 245 if (node.style.getPropertyValue("top") == "${yoffset}px" | |
| 246 && node.style.getPropertyValue("left") == "${xoffset}px") { | |
| 247 node.remove(); | |
| 248 return; | |
| 249 } | |
| 250 } | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 class ShootingStatusView extends View { | |
| 255 PlayerState state; | |
| 256 Element _rootNode; | |
| 257 | |
| 258 ShootingStatusView(this.state, Document doc) | |
| 259 : super(doc) { | |
| 260 _rootNode = ViewUtil.createDiv("shooting-status"); | |
| 261 updateStatus(); | |
| 262 } | |
| 263 | |
| 264 /** Update the view to indicate we sunk another enemy's boat. */ | |
| 265 void updateStatus() { | |
| 266 final total = state.totalShots; | |
| 267 final hit = state.totalHits; | |
| 268 final miss = state.totalMisses; | |
| 269 final accounted = hit + miss; | |
| 270 final sunk = state.boatsSunk; | |
| 271 _rootNode.innerHTML = | |
| 272 "${total} <= ${accounted} (${hit} + ${miss}); ${sunk}"; | |
| 273 } | |
| 274 } | |
| 275 | |
| 276 /** Utility methods used by the views above. */ | |
| 277 class ViewUtil { | |
| 278 | |
| 279 /** Extract the position of a mouse event in a containing 500x500 grid. */ | |
| 280 static Future<List<int>> positionFromEvent(Element gridNode, MouseEvent e) { | |
| 281 final completer = new Completer<List<int>>(); | |
| 282 gridNode.rect.then((ElementRect rect) { | |
| 283 int x = (e.pageX - rect.offset.left) ~/ 50; | |
| 284 int y = (e.pageY - rect.offset.top) ~/ 50; | |
| 285 completer.complete([x, y]); | |
| 286 }); | |
| 287 return completer.future; | |
| 288 } | |
| 289 | |
| 290 /** Given a grid node (square or boat) place it at a grid coordinate. */ | |
| 291 static void placeNodeAt(Element node, int x, int y) { | |
| 292 int xoffset = x * 50; | |
| 293 int yoffset = y * 50; | |
| 294 node.style.setProperty("top", yoffset.toString() + "px"); | |
| 295 node.style.setProperty("left", xoffset.toString() + "px"); | |
| 296 } | |
| 297 | |
| 298 /** Create a div node with a given class name. */ | |
| 299 static Element createDiv(String className) { | |
| 300 Element node = new Element.tag("div"); | |
| 301 node.attributes["class"] = className; | |
| 302 return node; | |
| 303 } | |
| 304 } | |
| OLD | NEW |