| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, 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 part of pop_pop_win.game; | |
| 5 | |
| 6 class Game { | |
| 7 final Field field; | |
| 8 final Array2d<SquareState> _states; | |
| 9 final StreamController _updatedEvent = new StreamController(); | |
| 10 final StreamController<GameState> _gameStateEvent = | |
| 11 new StreamController<GameState>(); | |
| 12 | |
| 13 GameState _state; | |
| 14 int _bombsLeft; | |
| 15 int _revealsLeft; | |
| 16 DateTime _startTime; | |
| 17 DateTime _endTime; | |
| 18 | |
| 19 Game(Field field) | |
| 20 : this.field = field, | |
| 21 _state = GameState.reset, | |
| 22 _states = new Array2d<SquareState>(field.width, field.height, | |
| 23 SquareState.hidden) { | |
| 24 assert(field != null); | |
| 25 _bombsLeft = field.bombCount; | |
| 26 _revealsLeft = field.length - field.bombCount; | |
| 27 } | |
| 28 | |
| 29 int get bombsLeft => _bombsLeft; | |
| 30 | |
| 31 int get revealsLeft => _revealsLeft; | |
| 32 | |
| 33 GameState get state => _state; | |
| 34 | |
| 35 Stream get updated => _updatedEvent.stream; | |
| 36 | |
| 37 Stream get stateChanged => _gameStateEvent.stream; | |
| 38 | |
| 39 SquareState getSquareState(int x, int y) => _states.get(x, y); | |
| 40 | |
| 41 bool get gameEnded => _state == GameState.won || _state == GameState.lost; | |
| 42 | |
| 43 Duration get duration { | |
| 44 if (_startTime == null) { | |
| 45 assert(state == GameState.reset); | |
| 46 return null; | |
| 47 } else { | |
| 48 assert((state == GameState.started) == (_endTime == null)); | |
| 49 final end = (_endTime == null) ? new DateTime.now() : _endTime; | |
| 50 return end.difference(_startTime); | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 bool canToggleFlag(int x, int y) { | |
| 55 final currentSS = _states.get(x, y); | |
| 56 return currentSS == SquareState.hidden || currentSS == SquareState.flagged; | |
| 57 } | |
| 58 | |
| 59 void setFlag(int x, int y, bool value) { | |
| 60 _ensureStarted(); | |
| 61 assert(value != null); | |
| 62 | |
| 63 final currentSS = _states.get(x, y); | |
| 64 if (value) { | |
| 65 require(currentSS == SquareState.hidden); | |
| 66 _states.set(x, y, SquareState.flagged); | |
| 67 _bombsLeft--; | |
| 68 } else { | |
| 69 require(currentSS == SquareState.flagged); | |
| 70 _states.set(x, y, SquareState.hidden); | |
| 71 _bombsLeft++; | |
| 72 } | |
| 73 _update(); | |
| 74 } | |
| 75 | |
| 76 bool canReveal(int x, int y) { | |
| 77 final currentSS = _states.get(x, y); | |
| 78 if (currentSS == SquareState.hidden) { | |
| 79 return true; | |
| 80 } else if (_canChord(x, y)) { | |
| 81 return true; | |
| 82 } | |
| 83 return false; | |
| 84 } | |
| 85 | |
| 86 List<Point> reveal(int x, int y) { | |
| 87 _ensureStarted(); | |
| 88 require(canReveal(x, y), "Item cannot be revealed."); | |
| 89 final currentSS = _states.get(x, y); | |
| 90 | |
| 91 List<Point> reveals; | |
| 92 | |
| 93 // normal reveal | |
| 94 if (currentSS == SquareState.hidden) { | |
| 95 if (field.get(x, y)) { | |
| 96 _setLost(); | |
| 97 reveals = <Point>[]; | |
| 98 } else { | |
| 99 reveals = _doReveal(x, y); | |
| 100 } | |
| 101 } else if (_canChord(x, y)) { | |
| 102 reveals = _doChord(x, y); | |
| 103 } | |
| 104 _update(); | |
| 105 | |
| 106 if (_state == GameState.lost) { | |
| 107 return null; | |
| 108 } else { | |
| 109 return reveals; | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 String toBoardString() { | |
| 114 final buffer = new StringBuffer(); | |
| 115 for (var y = -2; y < field.height; y++) { | |
| 116 if (y > -2) { | |
| 117 buffer.write('\n'); | |
| 118 } | |
| 119 for (var x = -2; x < field.width; x++) { | |
| 120 var char = null; | |
| 121 if (y == -2) { | |
| 122 if (x == -2) { | |
| 123 char = ' '; | |
| 124 } else if (x == -1) { | |
| 125 char = '|'; | |
| 126 } else { | |
| 127 char = (x % 10).toString(); | |
| 128 } | |
| 129 } else if (y == -1) { | |
| 130 if (x == -1) { | |
| 131 char = '+'; | |
| 132 } else { | |
| 133 char = '-'; | |
| 134 } | |
| 135 } else { | |
| 136 if (x == -2) { | |
| 137 char = (y % 10).toString(); | |
| 138 } else if (x == -1) { | |
| 139 char = '|'; | |
| 140 } else { | |
| 141 switch (getSquareState(x, y)) { | |
| 142 case SquareState.flagged: | |
| 143 char = '\u2611'; | |
| 144 break; | |
| 145 case SquareState.revealed: | |
| 146 var count = field.getAdjacentCount(x, y); | |
| 147 char = count.toString(); | |
| 148 break; | |
| 149 case SquareState.hidden: | |
| 150 char = '?'; | |
| 151 break; | |
| 152 } | |
| 153 } | |
| 154 } | |
| 155 assert(char != null); | |
| 156 buffer.write(char); | |
| 157 } | |
| 158 } | |
| 159 return buffer.toString(); | |
| 160 } | |
| 161 | |
| 162 bool _canChord(int x, int y) { | |
| 163 final currentSS = _states.get(x, y); | |
| 164 if (currentSS == SquareState.revealed) { | |
| 165 // might be a 'chord' reveal | |
| 166 final adjCount = field.getAdjacentCount(x, y); | |
| 167 if (adjCount > 0) { | |
| 168 final adjHidden = _getAdjacentCount(x, y, SquareState.hidden); | |
| 169 if (adjHidden > 0) { | |
| 170 final adjFlags = _getAdjacentCount(x, y, SquareState.flagged); | |
| 171 if (adjFlags == adjCount) { | |
| 172 return true; | |
| 173 } | |
| 174 } | |
| 175 } | |
| 176 } | |
| 177 return false; | |
| 178 } | |
| 179 | |
| 180 List<Point> _doChord(int x, int y) { | |
| 181 // this does not repeat a bunch of validations that have already happened | |
| 182 // be careful | |
| 183 final currentSS = _states.get(x, y); | |
| 184 assert(currentSS == SquareState.revealed); | |
| 185 | |
| 186 final flagged = new List<int>(); | |
| 187 final hidden = new List<int>(); | |
| 188 final adjCount = field.getAdjacentCount(x, y); | |
| 189 assert(adjCount > 0); | |
| 190 | |
| 191 bool failed = false; | |
| 192 | |
| 193 for (final i in field.getAdjacentIndices(x, y)) { | |
| 194 if (_states[i] == SquareState.hidden) { | |
| 195 hidden.add(i); | |
| 196 if (field[i]) { | |
| 197 failed = true; | |
| 198 } | |
| 199 } else if (_states[i] == SquareState.flagged) { | |
| 200 flagged.add(i); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 // for now we assume counts have been checked | |
| 205 assert(flagged.length == adjCount); | |
| 206 | |
| 207 var reveals = <Point>[]; | |
| 208 | |
| 209 // if any of the hidden are bombs, we've failed | |
| 210 if (failed) { | |
| 211 _setLost(); | |
| 212 } else { | |
| 213 for (final i in hidden) { | |
| 214 final c = field.getCoordinate(i); | |
| 215 if (canReveal(c.item1, c.item2)) { | |
| 216 reveals.addAll(reveal(c.item1, c.item2)); | |
| 217 } | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 return reveals; | |
| 222 } | |
| 223 | |
| 224 List<Point> _doReveal(int x, int y) { | |
| 225 assert(_states.get(x, y) == SquareState.hidden); | |
| 226 _states.set(x, y, SquareState.revealed); | |
| 227 _revealsLeft--; | |
| 228 assert(_revealsLeft >= 0); | |
| 229 var reveals = [new Point(x, y)]; | |
| 230 if (_revealsLeft == 0) { | |
| 231 _setWon(); | |
| 232 } else if (field.getAdjacentCount(x, y) == 0) { | |
| 233 for (final i in field.getAdjacentIndices(x, y)) { | |
| 234 if (_states[i] == SquareState.hidden) { | |
| 235 final c = field.getCoordinate(i); | |
| 236 reveals.addAll(_doReveal(c.item1, c.item2)); | |
| 237 assert(state == GameState.started || state == GameState.won); | |
| 238 } | |
| 239 } | |
| 240 } | |
| 241 return reveals; | |
| 242 } | |
| 243 | |
| 244 void _setWon() { | |
| 245 assert(state == GameState.started); | |
| 246 for (int i = 0; i < field.length; i++) { | |
| 247 if (field[i]) { | |
| 248 _states[i] = SquareState.safe; | |
| 249 } | |
| 250 } | |
| 251 _setState(GameState.won); | |
| 252 } | |
| 253 | |
| 254 void _setLost() { | |
| 255 assert(state == GameState.started); | |
| 256 for (int i = 0; i < field.length; i++) { | |
| 257 if (field[i]) { | |
| 258 _states[i] = SquareState.bomb; | |
| 259 } | |
| 260 } | |
| 261 _setState(GameState.lost); | |
| 262 } | |
| 263 | |
| 264 void _update() => _updatedEvent.add(null); | |
| 265 | |
| 266 void _setState(GameState value) { | |
| 267 assert(value != null); | |
| 268 assert(_state != null); | |
| 269 assert((_state == GameState.reset) == (_startTime == null)); | |
| 270 if (_state != value) { | |
| 271 _state = value; | |
| 272 if (_state == GameState.started) { | |
| 273 _startTime = new DateTime.now(); | |
| 274 } else if (gameEnded) { | |
| 275 _endTime = new DateTime.now(); | |
| 276 } | |
| 277 _gameStateEvent.add(_state); | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 void _ensureStarted() { | |
| 282 if (state == GameState.reset) { | |
| 283 assert(_startTime == null); | |
| 284 _setState(GameState.started); | |
| 285 } | |
| 286 assert(state == GameState.started); | |
| 287 assert(_startTime != null); | |
| 288 } | |
| 289 | |
| 290 int _getAdjacentCount(int x, int y, SquareState state) { | |
| 291 int val = 0; | |
| 292 for (final i in field.getAdjacentIndices(x, y)) { | |
| 293 if (_states[i] == state) { | |
| 294 val++; | |
| 295 } | |
| 296 } | |
| 297 return val; | |
| 298 } | |
| 299 } | |
| OLD | NEW |