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 handleMouseDown(e) { | |
92 e.preventDefault(); | |
93 List<int> pos = await ViewUtil.positionFromEvent(_rootNode, e); | |
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 handleMouseMove(e) { | |
110 e.preventDefault(); | |
111 List<int> pos = await ViewUtil.positionFromEvent(_rootNode, e); | |
112 if (_boatLastX == pos[0] && _boatLastY == pos[1]) { | |
113 return; | |
114 } | |
115 _boatLastX = pos[0]; | |
116 _boatLastY = pos[1]; | |
117 int deltaX = _boatLastX - _boatStartX; | |
118 int deltaY = _boatLastY - _boatStartY; | |
119 | |
120 String dir; | |
121 bool flip = false; | |
122 int boatSize = 2; | |
123 if (deltaX.abs() >= deltaY.abs()) { | |
124 dir = deltaX < 0 ? "right" : "left"; | |
125 boatSize = Math.max(2, Math.min(5, deltaX.abs() + 1)); | |
126 } else { | |
127 dir = deltaY < 0 ? "up" : "down"; | |
128 boatSize = Math.max(2, Math.min(5, deltaY.abs() + 1)); | |
129 } | |
130 | |
131 _possibleBoat.attributes["class"] = "icons boat${boatSize} boatdir-${dir}"; | |
132 } | |
133 | |
134 /** Handle end of positioning of a boat. */ | |
135 handleMouseUp(e) { | |
136 _rootNode.on.mouseMove.remove(_moveListener, false); | |
137 _moveListener = null; | |
138 List<int> pos = await ViewUtil.positionFromEvent(_rootNode, e); | |
139 int _boatEndX = pos[0]; | |
140 int _boatEndY = pos[1]; | |
141 | |
142 int deltaX = _boatEndX - _boatStartX; | |
143 int deltaY = _boatEndY - _boatStartY; | |
144 Boat boat; | |
145 | |
146 if (deltaX.abs() >= deltaY.abs()) { | |
147 int boatSize = Math.max(2, Math.min(5, deltaX.abs() + 1)); | |
148 boat = new Boat(deltaX < 0 ? (_boatStartX - boatSize + 1) : _boatStartX, | |
149 _boatStartY, true, boatSize); | |
150 } else { | |
151 int boatSize = Math.max(2, Math.min(5, deltaY.abs() + 1)); | |
152 boat = new Boat(_boatStartX, | |
153 deltaY < 0 ? (_boatStartY - boatSize + 1) : _boatStartY, | |
154 false, boatSize); | |
155 } | |
156 | |
157 state.addBoat(boat); | |
158 } | |
159 } | |
160 | |
161 /** The view displayed to the player for its enemy's grid. */ | |
162 class EnemyGridView extends View { | |
163 PlayerState state; | |
164 bool _enemyReady; | |
165 Element _rootNode; | |
166 ShootingStatusView statusBar; | |
167 | |
168 EnemyGridView( | |
169 PlayerState this.state, Element rootNode) | |
170 : super(rootNode.document), | |
171 _enemyReady = false, | |
172 _rootNode = rootNode { | |
173 | |
174 String cell = "<div class='icons water'></div>"; | |
175 StringBuffer _cells = new StringBuffer(); | |
176 for (int i = 0 ; i < state.enemyGrid.cells.length; i++) { | |
177 _cells.add(cell); | |
178 } | |
179 String cells = _cells.toString(); | |
180 String row = "<div class='hbox'>${cells}</div>"; | |
181 StringBuffer _rows = new StringBuffer(); | |
182 for (int i = 0 ; i < state.enemyGrid.cells.length; i++) { | |
183 _rows.add(row); | |
184 } | |
185 String rows = _rows.toString(); | |
186 String table = "<div class='vbox'>${rows}</div>"; | |
187 _rootNode.innerHTML = | |
188 "${table}<div class='notready'>ENEMY IS NOT READY</div>"; | |
189 statusBar = new ShootingStatusView(state, doc); | |
190 _rootNode.nodes.add(statusBar._rootNode); | |
191 _rootNode.on.click.add((Event e) { | |
192 MouseEvent mouseEvent = e; | |
193 handleClick(mouseEvent); | |
194 }, false); | |
195 } | |
196 | |
197 /** Interpret clicks as a shooting action. */ | |
198 handleClick(MouseEvent e) { | |
199 List<int> pos = await ViewUtil.positionFromEvent(_rootNode, e); | |
200 state.shoot(pos[0], pos[1]); | |
201 } | |
202 | |
203 /** Update the view to indicate the enemy is ready. */ | |
204 void setEnemyReady() { | |
205 if (!_enemyReady) { | |
206 _enemyReady = true; | |
207 _rootNode.query(".notready").remove(); | |
208 } | |
209 } | |
210 | |
211 | |
212 /** Update the view to indicate a shot that hit an enemy's boat. */ | |
213 void addHit(int x, int y) { | |
214 Element node = ViewUtil.createDiv("icons hit"); | |
215 ViewUtil.placeNodeAt(node, x, y); | |
216 _rootNode.nodes.add(node); | |
217 } | |
218 | |
219 /** Update the view to indicate a shot that missed an enemy's boat. */ | |
220 void addMiss(int x, int y) { | |
221 Element node = ViewUtil.createDiv("icons miss"); | |
222 ViewUtil.placeNodeAt(node, x, y); | |
223 _rootNode.nodes.add(node); | |
224 } | |
225 | |
226 /** Update the view to indicate a shot is in progress. */ | |
227 void addMaybeHit(int x, int y) { | |
228 Element node = ViewUtil.createDiv("icons maybe-hit"); | |
229 ViewUtil.placeNodeAt(node, x, y); | |
230 _rootNode.nodes.add(node); | |
231 } | |
232 | |
233 /** | |
234 * Remove the icon indicating that a shot is in progress (only called when | |
235 * shots failed due to network errors). | |
236 */ | |
237 void removeMaybeHit(int x, int y) { | |
238 for (Element node in _rootNode.queryAll(".maybe-hit")) { | |
239 int xoffset = x * 50; | |
240 int yoffset = y * 50; | |
241 if (node.style.getPropertyValue("top") == "${yoffset}px" | |
242 && node.style.getPropertyValue("left") == "${xoffset}px") { | |
243 node.remove(); | |
244 return; | |
245 } | |
246 } | |
247 } | |
248 } | |
249 | |
250 class ShootingStatusView extends View { | |
251 PlayerState state; | |
252 Element _rootNode; | |
253 | |
254 ShootingStatusView(this.state, Document doc) | |
255 : super(doc) { | |
256 _rootNode = ViewUtil.createDiv("shooting-status"); | |
257 updateStatus(); | |
258 } | |
259 | |
260 /** Update the view to indicate we sunk another enemy's boat. */ | |
261 void updateStatus() { | |
262 final total = state.totalShots; | |
263 final hit = state.totalHits; | |
264 final miss = state.totalMisses; | |
265 final accounted = hit + miss; | |
266 final sunk = state.boatsSunk; | |
267 _rootNode.innerHTML = | |
268 "${total} <= ${accounted} (${hit} + ${miss}); ${sunk}"; | |
269 } | |
270 } | |
271 | |
272 /** Utility methods used by the views above. */ | |
273 class ViewUtil { | |
274 | |
275 /** Extract the position of a mouse event in a containing 500x500 grid. */ | |
276 static Future<List<int>> positionFromEvent(Element gridNode, MouseEvent e) { | |
277 ElementRect rect = await gridNode.rect; | |
278 int x = (e.pageX - rect.offset.left) ~/ 50; | |
279 int y = (e.pageY - rect.offset.top) ~/ 50; | |
280 return [x, y]; | |
281 } | |
282 | |
283 /** Given a grid node (square or boat) place it at a grid coordinate. */ | |
284 static void placeNodeAt(Element node, int x, int y) { | |
285 int xoffset = x * 50; | |
286 int yoffset = y * 50; | |
287 node.style.setProperty("top", yoffset.toString() + "px"); | |
288 node.style.setProperty("left", xoffset.toString() + "px"); | |
289 } | |
290 | |
291 /** Create a div node with a given class name. */ | |
292 static Element createDiv(String className) { | |
293 Element node = new Element.tag("div"); | |
294 node.attributes["class"] = className; | |
295 return node; | |
296 } | |
297 } | |
OLD | NEW |