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