friendz: fix grid deserialization
[nit.git] / contrib / friendz / src / grid.nit
1 # Monsterz - Chains of Friends
2 #
3 # 2010-2014 (c) Jean Privat <jean@pryen.org>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the Do What The Fuck You Want To
7 # Public License, Version 2, as published by Sam Hocevar. See
8 # http://sam.zoy.org/projects/COPYING.WTFPL for more details.
9
10 # Game login on the grid of monsters
11 module grid
12
13 # Grid of monsters.
14 class Grid
15 # width of the current grid
16 var width: Int is noinit
17
18 # maximum width of the grid
19 var max_width: Int
20
21 # height of the current grid
22 var height: Int is noinit
23
24 # maximum height of the grid
25 var max_height: Int
26
27 # nm is the number of monster + 1 for the empty tile
28 var nb_monsters: Int
29
30 # the data grid
31 private var grid = new Array[Array[Tile]]
32
33 init do clear
34
35 # Reinitialize the grid with new empty tiles and monsters info
36 fun clear
37 do
38 self.number = 0
39 self.error = 0
40 self.won = false
41 for i in [0..max_width[ do
42 self.grid[i] = new Array[Tile]
43 for j in [0..max_height[ do
44 var t = new Tile(self, i, j)
45 self.grid[i][j] = t
46 end
47 end
48 self.monsters = new Array[MonsterInfo]
49 for i in [0..nb_monsters] do
50 self.monsters[i] = new MonsterInfo
51 end
52 self.resize(max_width,max_height)
53 end
54
55 # Clear all monsters
56 # if `fixed` is false, fixed monsters remains
57 fun reset(fixed: Bool): Bool
58 do
59 var r = false
60 for i in [0..width[ do
61 for j in [0..height[ do
62 var t = self.grid[i][j]
63 if fixed then t.fixed = false
64 if not t.fixed and t.kind>0 then
65 t.update(0)
66 r = true
67 end
68 end
69 end
70 return r
71 end
72
73 # Total number of monsters in the grid
74 var number = 0
75
76 # Number of monsters alone or with >=3 next
77 var error = 0
78
79 # information about each kind of monsters
80 var monsters = new Array[MonsterInfo]
81
82 # Last result of win.
83 var won = false
84
85 # Check that the monster of `kind` form a complete chain.
86 # if not null, `t` is used as a starting tile to check the chain
87 fun check_chain(kind: Int, t: nullable Tile): Bool
88 do
89 var m = monsters[kind]
90 if m.angry > 0 or m.lonely > 0 or m.single > 2 then
91 # easy case for no chain
92 # print "easy {kind} chain: false"
93 m.chain = false
94 return false
95 else
96 if t == null then
97 # Search for a starting
98 for x in [0..width[ do for y in [0..height[ do
99 var t2 = get(x,y)
100 if t2 == null or t2.kind != kind then continue
101 t = t2
102 break label found
103 end label found
104 if t == null then
105 assert m.number == 0
106 m.chain = true
107 return m.chain
108 end
109 # print "get neighbor {t}"
110 end
111 assert t.kind == kind
112 var c = count_chain(t, 1000.rand)
113 # print "old {kind} chain? {c} / {m.number}"
114 m.chain = c == m.number
115 return m.chain
116 end
117 end
118
119 # The total number of monsters connected to the chain of `t`
120 # `mark` is a uniq number used to mark tiles
121 fun count_chain(t: Tile, mark: Int): Int
122 do
123 t.chain_mark = mark
124 var res = 1
125 for i in [-1..1] do
126 for j in [-1..1] do
127 if (i==0 and j==0) or (i!=0 and j!=0) then continue
128 var t2 = get(t.x+i,t.y+j)
129 if t2 == null then continue
130 if t2.chain_mark == mark or t2.kind != t.kind then continue
131 res += count_chain(t2, mark)
132 end
133 end
134 return res
135 end
136
137 # Resize the grid. Do not touch the content.
138 fun resize(w,h: Int)
139 do
140 self.width = w
141 self.height = h
142 end
143
144 # Try to get the tila at `x`,`y`.
145 # Returns null if the position is out of bound.
146 fun get(x,y: Int): nullable Tile
147 do
148 if x<0 or x>=self.width or y<0 or y>=self.height then return null
149 return self.grid[x][y]
150 end
151
152
153 var fixed_shaped = """[
154 [{x:1,y:0},{x:2,y:0},{x:1,y:1},{x:2,y:1}],
155 [{x:0,y:0},{x:0,y:1},{x:0,y:2}],
156 [{x:1,y:2},{x:2,y:2},{x:3,y:2}],
157 [{x:4,y:1},{x:4,y:2}],
158 [{x:3,y:0},{x:4,y:0}],
159 [{x:3,y:1}]
160 ]"""
161
162 # Set shapes for the fixed blocks.
163 fun metalize
164 do
165 for i in [0..width[ do
166 for j in [0..height[ do
167 var t = self.grid[i][j]
168 if t.fixed then t.shape = null
169 end
170 end
171 for shape in fixed_shaped.split(",") do
172 for i in [0..width[ do
173 for j in [0..height[ do
174 var ts = new Array[Tile]
175 for l in [0..shape.length[ do
176 #var t = self.get(i+shape[l].x-shape[0].x,j+shape[l].y-shape[0].y)
177 var t = self.get(i,j)
178 if t != null and t.fixed and t.shape == null then ts.push(t)
179 end
180 if ts.length == shape.length then
181 for l in [0..shape.length[ do
182 ts[l].shape = shape[l]
183 end
184 end
185 end
186 end
187 end
188 end
189
190 # Return the serialization of the fixed tiles. */
191 fun save: String
192 do
193 var res = ""
194 var str = ".abcdefghi#ABCDEFGHI"
195 for y in [0..height[ do
196 var rle = 0
197 var last: nullable Int = null
198 for x in [0..width[ do
199 var t = self.grid[x][y]
200 var tk = t.kind
201 if t.fixed then tk += 10
202 if tk == last and rle<9 then
203 rle += 1
204 else
205 if last != null then
206 if rle>1 then res += rle.to_s
207 res += str.chars[last].to_s
208 end
209 rle = 1
210 last = tk
211 end
212 end
213 if last != null then
214 if rle>1 then res += rle.to_s
215 res += str.chars[last].to_s
216 end
217 res += "|"
218 end
219 return res
220 end
221
222 # Load a new grid from a seialization.
223 fun load(str: String): Bool
224 do
225 self.clear
226 var l = str.length
227 var x = 0
228 var y = 0
229 var mx = 1
230 var my = 1
231 var rle = 1
232 for i in [0..l[ do
233 var z = rle
234 while z > 0 do
235 z -= 1
236 rle = 1
237 var c = str.chars[i]
238 if c == '|' then
239 if x > mx then mx = x
240 x = 0
241 y += 1
242 else if c == '.' then
243 x += 1
244 else if c == '#' then
245 var t = self.get(x,y)
246 assert t != null
247 t.fixed = true
248 x += 1
249 else if c >= 'A' and c <= 'I' then
250 var t = self.get(x,y)
251 assert t != null
252 t.update(c.ascii-'A'.ascii+1)
253 t.fixed = true
254 x += 1
255 else if c >= 'a' and c <= 'i' then
256 var t = self.get(x,y)
257 assert t != null
258 t.update(c.ascii-'a'.ascii+1)
259 x += 1
260 else if c >= '1' and c <= '9' then
261 rle = c.to_i
262 else
263 abort
264 end
265 end
266 end
267 if x>0 then y += 1
268 if x > mx then mx = x
269 if y > my then my = y
270 if mx<3 or my<3 or mx>max_width or my>max_height then
271 return false
272 end
273 self.resize(mx,my)
274 self.metalize
275 return true
276 end
277
278 # A ASCII version of the grid.
279 redef fun to_s: String
280 do
281 var ansicols = once ["37;1","31","36;1","32;1","35;1","33;1","33","34;1","31;1","37"]
282 var b = new FlatBuffer
283 b.append("{width}x{height}\n")
284 for j in [0..height[ do
285 for i in [0..width[ do
286 var t = grid[i][j]
287 var k = t.kind
288 var c = ' '
289 if k == 0 then
290 if t.fixed then c = '#'
291 else
292 b.add(0x1b.ascii)
293 b.add('[')
294 b.append ansicols[k]
295 c = (k + 'a'.ascii - 1).ascii
296 if t.fixed then c = c.to_upper
297 b.append("m")
298 end
299 b.add(c)
300 if k != 0 then
301 b.add(0x1b.ascii)
302 b.append("[0m")
303
304 end
305 end
306 b.append "|\n"
307 end
308 return b.to_s
309 end
310
311 # Return a copy of the current grid.
312 # if (!no_fixed) copy only the fixed tiles.
313 fun copy(no_fixed: Bool): Grid
314 do
315 var g = new Grid(self.max_width, self.max_height, self.nb_monsters)
316 g.resize(width, height)
317 for y in [0..height[ do
318 for x in [0..width[ do
319 var t = self.grid[x][y]
320 if no_fixed or t.fixed then
321 var t2 = g.grid[x][y]
322 t2.update(t.kind)
323 t2.fixed = t.fixed
324 end
325 end
326 end
327 g.metalize
328 return g
329 end
330
331 # Internal check of the validity of tile and monster informations
332 fun check_grid
333 do
334 var m2 = new Array[MonsterInfo]
335 for m in [0..nb_monsters] do
336 m2[m] = new MonsterInfo
337 end
338 for x in [0..width[ do
339 for y in [0..height[ do
340 var n = 0
341 var f = 0
342 var t = get(x,y)
343 assert t != null
344 assert t.x == x
345 assert t.y == y
346 var k = t.kind
347 if k == 0 then continue
348
349 for i in [-1..1] do
350 for j in [-1..1] do
351 if i == j or (i != 0 and j != 0) then continue
352 var t2 = get(x+i, y+j)
353 if t2 == null then continue
354 if t2.kind == k then
355 n += 1
356 else if t2.kind == 0 and not t2.fixed then
357 f += 1
358 end
359 end
360 end
361 assert n == t.nexts else
362 print self
363 print "{t} says {t.nexts} nexts, found {n}"
364 end
365 #assert f == t.frees else
366
367 var m = m2[k]
368 m.number += 1
369 if n == 0 then
370 m.lonely += 1
371 else if n == 1 then
372 m.single += 1
373 else if n > 2 then
374 m.angry += 1
375 end
376 end
377 end
378 for m in [1..nb_monsters] do
379 assert m2[m].number == monsters[m].number
380 assert m2[m].lonely == monsters[m].lonely
381 assert m2[m].single == monsters[m].single
382 assert m2[m].angry == monsters[m].angry
383 end
384 end
385 end
386
387 # Information about each kind of monsters
388 class MonsterInfo
389 # number of monsters of this kind on board
390 var number = 0
391 # number of monsters of this kind to place, -1 if no limit
392 var remains: Int = -1
393 # number of monsters that have exactly 1 next
394 var single = 0
395 # number of monsters that have exactly 0 next
396 var lonely = 0
397 # number of monsters that have 3 or more next
398 var angry = 0
399 # Are all monsters form a wining chain?
400 var chain = false
401 end
402
403 # A localized tile of a grid, can contain a monster and be fixed.
404 class Tile
405 # The grid of the tile.
406 var grid: Grid
407
408 # The x coordinate in the grid (starting from 0).
409 var x: Int
410
411 # The y coordinate in the grid (starting from 0).
412 var y: Int
413
414 # The kind of monster in the grid. 0 means empty.
415 var kind = 0
416
417 # blink time to live (0 means no blinking).
418 var blink = 0
419
420 # shocked time to live (0 means not shocked)
421 var shocked = 0
422
423 # number of neighbors of the same kind.
424 var nexts = 0
425
426 # number of free non fixed next tiles
427 var frees = 0
428
429 # is the tile editable (metal block)
430 var fixed = false
431
432 redef fun to_s
433 do
434 var s
435 if fixed then
436 s = "#ABCDEFGHI"
437 else
438 s = ".abcdefghi"
439 end
440 return "\{{x},{y}:{s.chars[kind]}\}"
441 end
442
443 # Shape for metal block
444 var shape: nullable Object = null
445
446 # Flag for `count_chain` computation.
447 private var chain_mark = 0
448
449 # Set a new kind of monster on tile
450 # Return true is the move made the grid unsolvable (bad move)
451 fun update(nkind: Int): Bool
452 do
453 var t = self
454 var g = self.grid
455 var res = false
456 var okind = t.kind
457 if okind == nkind then return false
458
459
460 # First, remove it and update info.
461 if okind > 0 then
462 var m = g.monsters[okind]
463 var n = t.nexts
464 if n > 2 then
465 g.error -= 1
466 m.angry -= 1
467 else if n == 1 then
468 m.single -= 1
469 else if n == 0 then
470 g.error -= 1
471 m.lonely -= 1
472 end
473 m.number -= 1
474 g.number -= 1
475 end
476 t.nexts = 0
477 t.blink = 5
478 t.frees = 0
479
480 var a_neigbor: nullable Tile = null
481 # update neighbors
482 for i in [-1..1] do
483 for j in [-1..1] do
484 if (i==0 and j==0) or (i!=0 and j!=0) then continue
485 var t2 = g.get(t.x+i,t.y+j)
486 if t2 == null then continue
487 if t2.kind == 0 then
488 if not t2.fixed then t.frees += 1
489 continue
490 end
491 var m = g.monsters[t2.kind]
492
493 if t2.kind == okind then
494 if a_neigbor == null then a_neigbor = t2
495 # same than old, thus dec neighbors
496 t2.nexts -=1
497 var n = t2.nexts
498 if n == 2 then
499 g.error -= 1
500 m.angry -= 1
501 else if n == 1 then
502 m.single += 1
503 g.error += 1
504 else if n == 0 then
505 m.single -= 1
506 m.lonely += 1
507 end
508 # print "+ {t} one less next: {t2} ; +({i}x{j})"
509 end
510
511 if t2.kind == nkind then
512 # Same than new, thus inc neighbors
513 t2.nexts += 1
514 t.nexts += 1
515 var n = t2.nexts
516 if n > 3 then
517 res = true
518 else if n == 3 then
519 g.error += 1
520 m.angry += 1
521 res = true
522 else if n == 2 then
523 m.single -= 1
524 g.error -= 1
525 else if n == 1 then
526 m.single += 1
527 m.lonely -= 1
528 end
529 # print "+ {t} one more next: {t2}"
530 end
531 end
532 end
533
534 # Add and update neighbors info
535 t.kind = nkind
536 if nkind > 0 then
537 var m = g.monsters[nkind]
538 var n = t.nexts
539 if n > 2 then
540 g.error += 1
541 m.angry += 1
542 else if n == 1 then
543 m.single += 1
544 g.error += 1
545 else if n == 0 then
546 g.error += 1
547 m.lonely += 1
548 end
549 m.number+=1
550 g.number+=1
551
552 g.check_chain(nkind, t)
553 end
554
555 # check if the old kind broke, or create a chain
556 if okind > 0 then
557 g.check_chain(okind, a_neigbor)
558 end
559
560 # update win status
561 g.won = true
562 for m in g.monsters do
563 if m.number > 0 and not m.chain then g.won = false
564 end
565
566 #grid.check_grid
567
568 return res
569 end
570 end
571