a097ff554658301a760f781a3f47333344e67426
[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"
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 = 0
201 if t.fixed then tk = t.kind + 1
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 t.fixed = true
247 x += 1
248 else if c >= 'A' and c <= 'I' then
249 var t = self.get(x,y)
250 assert t != null
251 t.update(c.ascii-'A'.ascii+1)
252 t.fixed = true
253 x += 1
254 else if c >= '1' and c <= '9' then
255 rle = c.to_i
256 else
257 abort
258 end
259 end
260 end
261 if x>0 then y += 1
262 if x > mx then mx = x
263 if y > my then my = y
264 if mx<3 or my<3 or mx>=max_width or my>=max_height then
265 return false
266 end
267 self.resize(mx,my)
268 self.metalize
269 return true
270 end
271
272 # A ASCII version of the grid.
273 redef fun to_s: String
274 do
275 var ansicols = once ["37;1","31","36;1","32;1","35;1","33;1","33","34;1","31;1","37"]
276 var b = new FlatBuffer
277 b.append("{width}x{height}\n")
278 for j in [0..height[ do
279 for i in [0..width[ do
280 var t = grid[i][j]
281 var k = t.kind
282 var c = ' '
283 if k == 0 then
284 if t.fixed then c = '#'
285 else
286 b.add(0x1b.ascii)
287 b.add('[')
288 b.append ansicols[k]
289 c = (k + 'a'.ascii - 1).ascii
290 if t.fixed then c = c.to_upper
291 b.append("m")
292 end
293 b.add(c)
294 if k != 0 then
295 b.add(0x1b.ascii)
296 b.append("[0m")
297
298 end
299 end
300 b.append "|\n"
301 end
302 return b.to_s
303 end
304
305 # Return a copy of the current grid.
306 # if (!no_fixed) copy only the fixed tiles.
307 fun copy(no_fixed: Bool): Grid
308 do
309 var g = new Grid(self.max_width, self.max_height, self.nb_monsters)
310 g.resize(width, height)
311 for y in [0..height[ do
312 for x in [0..width[ do
313 var t = self.grid[x][y]
314 if no_fixed or t.fixed then
315 var t2 = g.grid[x][y]
316 t2.update(t.kind)
317 t2.fixed = t.fixed
318 end
319 end
320 end
321 g.metalize
322 return g
323 end
324
325 # Internal check of the validity of tile and monster informations
326 fun check_grid
327 do
328 var m2 = new Array[MonsterInfo]
329 for m in [0..nb_monsters] do
330 m2[m] = new MonsterInfo
331 end
332 for x in [0..width[ do
333 for y in [0..height[ do
334 var n = 0
335 var f = 0
336 var t = get(x,y)
337 assert t != null
338 assert t.x == x
339 assert t.y == y
340 var k = t.kind
341 if k == 0 then continue
342
343 for i in [-1..1] do
344 for j in [-1..1] do
345 if i == j or (i != 0 and j != 0) then continue
346 var t2 = get(x+i, y+j)
347 if t2 == null then continue
348 if t2.kind == k then
349 n += 1
350 else if t2.kind == 0 and not t2.fixed then
351 f += 1
352 end
353 end
354 end
355 assert n == t.nexts else
356 print self
357 print "{t} says {t.nexts} nexts, found {n}"
358 end
359 #assert f == t.frees else
360
361 var m = m2[k]
362 m.number += 1
363 if n == 0 then
364 m.lonely += 1
365 else if n == 1 then
366 m.single += 1
367 else if n > 2 then
368 m.angry += 1
369 end
370 end
371 end
372 for m in [1..nb_monsters] do
373 assert m2[m].number == monsters[m].number
374 assert m2[m].lonely == monsters[m].lonely
375 assert m2[m].single == monsters[m].single
376 assert m2[m].angry == monsters[m].angry
377 end
378 end
379 end
380
381 # Information about each kind of monsters
382 class MonsterInfo
383 # number of monsters of this kind on board
384 var number = 0
385 # number of monsters of this kind to place, -1 if no limit
386 var remains: Int = -1
387 # number of monsters that have exactly 1 next
388 var single = 0
389 # number of monsters that have exactly 0 next
390 var lonely = 0
391 # number of monsters that have 3 or more next
392 var angry = 0
393 # Are all monsters form a wining chain?
394 var chain = false
395 end
396
397 # A localized tile of a grid, can contain a monster and be fixed.
398 class Tile
399 # The grid of the tile.
400 var grid: Grid
401
402 # The x coordinate in the grid (starting from 0).
403 var x: Int
404
405 # The y coordinate in the grid (starting from 0).
406 var y: Int
407
408 # The kind of monster in the grid. 0 means empty.
409 var kind = 0
410
411 # blink time to live (0 means no blinking).
412 var blink = 0
413
414 # shocked time to live (0 means not shocked)
415 var shocked = 0
416
417 # number of neighbors of the same kind.
418 var nexts = 0
419
420 # number of free non fixed next tiles
421 var frees = 0
422
423 # is the tile editable (metal block)
424 var fixed = false
425
426 redef fun to_s
427 do
428 var s
429 if fixed then
430 s = "#ABCDEFGHI"
431 else
432 s = ".abcdefghi"
433 end
434 return "\{{x},{y}:{s.chars[kind]}\}"
435 end
436
437 # Shape for metal block
438 var shape: nullable Object = null
439
440 # Flag for `count_chain` computation.
441 private var chain_mark = 0
442
443 # Set a new kind of monster on tile
444 # Return true is the move made the grid unsolvable (bad move)
445 fun update(nkind: Int): Bool
446 do
447 var t = self
448 var g = self.grid
449 var res = false
450 var okind = t.kind
451 if okind == nkind then return false
452
453
454 # First, remove it and update info.
455 if okind > 0 then
456 var m = g.monsters[okind]
457 var n = t.nexts
458 if n > 2 then
459 g.error -= 1
460 m.angry -= 1
461 else if n == 1 then
462 m.single -= 1
463 else if n == 0 then
464 g.error -= 1
465 m.lonely -= 1
466 end
467 m.number -= 1
468 g.number -= 1
469 end
470 t.nexts = 0
471 t.blink = 5
472 t.frees = 0
473
474 var a_neigbor: nullable Tile = null
475 # update neighbors
476 for i in [-1..1] do
477 for j in [-1..1] do
478 if (i==0 and j==0) or (i!=0 and j!=0) then continue
479 var t2 = g.get(t.x+i,t.y+j)
480 if t2 == null then continue
481 if t2.kind == 0 then
482 if not t2.fixed then t.frees += 1
483 continue
484 end
485 var m = g.monsters[t2.kind]
486
487 if t2.kind == okind then
488 if a_neigbor == null then a_neigbor = t2
489 # same than old, thus dec neighbors
490 t2.nexts -=1
491 var n = t2.nexts
492 if n == 2 then
493 g.error -= 1
494 m.angry -= 1
495 else if n == 1 then
496 m.single += 1
497 g.error += 1
498 else if n == 0 then
499 m.single -= 1
500 m.lonely += 1
501 end
502 # print "+ {t} one less next: {t2} ; +({i}x{j})"
503 end
504
505 if t2.kind == nkind then
506 # Same than new, thus inc neighbors
507 t2.nexts += 1
508 t.nexts += 1
509 var n = t2.nexts
510 if n > 3 then
511 res = true
512 else if n == 3 then
513 g.error += 1
514 m.angry += 1
515 res = true
516 else if n == 2 then
517 m.single -= 1
518 g.error -= 1
519 else if n == 1 then
520 m.single += 1
521 m.lonely -= 1
522 end
523 # print "+ {t} one more next: {t2}"
524 end
525 end
526 end
527
528 # Add and update neighbors info
529 t.kind = nkind
530 if nkind > 0 then
531 var m = g.monsters[nkind]
532 var n = t.nexts
533 if n > 2 then
534 g.error += 1
535 m.angry += 1
536 else if n == 1 then
537 m.single += 1
538 g.error += 1
539 else if n == 0 then
540 g.error += 1
541 m.lonely += 1
542 end
543 m.number+=1
544 g.number+=1
545
546 g.check_chain(nkind, t)
547 end
548
549 # check if the old kind broke, or create a chain
550 if okind > 0 then
551 g.check_chain(okind, a_neigbor)
552 end
553
554 # update win status
555 g.won = true
556 for m in g.monsters do
557 if m.number > 0 and not m.chain then g.won = false
558 end
559
560 #grid.check_grid
561
562 return res
563 end
564 end
565