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 |