| 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 | 
|---|