1 # Monsterz - Chains of Friends
3 # 2010-2014 (c) Jean Privat <jean@pryen.org>
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.
10 # Game login on the grid of monsters
15 # width of the current grid
16 var width
: Int is noinit
18 # maximum width of the grid
21 # height of the current grid
22 var height
: Int is noinit
24 # maximum height of the grid
27 # nm is the number of monster + 1 for the empty tile
31 private var grid
= new Array[Array[Tile]]
35 # Reinitialize the grid with new empty tiles and monsters info
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
)
48 self.monsters
= new Array[MonsterInfo]
49 for i
in [0..nb_monsters
] do
50 self.monsters
[i
] = new MonsterInfo
52 self.resize
(max_width
,max_height
)
56 # if `fixed` is false, fixed monsters remains
57 fun reset
(fixed
: Bool): Bool
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
73 # Total number of monsters in the grid
76 # Number of monsters alone or with >=3 next
79 # information about each kind of monsters
80 var monsters
= new Array[MonsterInfo]
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
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"
97 # Search for a starting
98 for x
in [0..width
[ do for y
in [0..height
[ do
100 if t2
== null or t2
.kind
!= kind
then continue
109 # print "get neighbor {t}"
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
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
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
)
137 # Resize the grid. Do not touch the content.
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
148 if x
<0 or x
>=self.width
or y
<0 or y
>=self.height
then return null
149 return self.grid
[x
][y
]
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}],
162 # Set shapes for the fixed blocks.
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
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
)
180 if ts
.length
== shape
.length
then
181 for l
in [0..shape
.length
[ do
182 ts
[l
].shape
= shape
[l
]
190 # Return the serialization of the fixed tiles. */
194 var str
= ".abcdefghi#ABCDEFGHI"
195 for y
in [0..height
[ do
197 var last
: nullable Int = null
198 for x
in [0..width
[ do
199 var t
= self.grid
[x
][y
]
201 if t
.fixed
then tk
+= 10
202 if tk
== last
and rle
<9 then
206 if rle
>1 then res
+= rle
.to_s
207 res
+= str
.chars
[last
].to_s
214 if rle
>1 then res
+= rle
.to_s
215 res
+= str
.chars
[last
].to_s
222 # Load a new grid from a seialization.
223 fun load
(str
: String): Bool
239 if x
> mx
then mx
= x
242 else if c
== '.' then
244 else if c
== '#' then
245 var t
= self.get
(x
,y
)
249 else if c
>= 'A' and c
<= 'I' then
250 var t
= self.get
(x
,y
)
252 t
.update
(c
.code_point-
'A'.code_point
+1)
255 else if c
>= 'a' and c
<= 'i' then
256 var t
= self.get
(x
,y
)
258 t
.update
(c
.code_point-
'a'.code_point
+1)
260 else if c
>= '1' and c
<= '9' then
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
278 # A ASCII version of the grid.
279 redef fun to_s
: String
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
290 if t
.fixed
then c
= '#'
292 b
.add
(0x1b.code_point
)
295 c
= (k
+ 'a'.code_point
- 1).code_point
296 if t
.fixed
then c
= c
.to_upper
301 b
.add
(0x1b.code_point
)
311 # Return a copy of the current grid.
312 # if (!no_fixed) copy only the fixed tiles.
313 fun copy
(no_fixed
: Bool): Grid
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
]
331 # Internal check of the validity of tile and monster informations
334 var m2
= new Array[MonsterInfo]
335 for m
in [0..nb_monsters
] do
336 m2
[m
] = new MonsterInfo
338 for x
in [0..width
[ do
339 for y
in [0..height
[ do
347 if k
== 0 then continue
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
356 else if t2
.kind
== 0 and not t2
.fixed
then
361 assert n
== t
.nexts
else
363 print
"{t} says {t.nexts} nexts, found {n}"
365 #assert f == t.frees else
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
387 # Information about each kind of monsters
389 # number of monsters of this kind on board
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
395 # number of monsters that have exactly 0 next
397 # number of monsters that have 3 or more next
399 # Are all monsters form a wining chain?
403 # A localized tile of a grid, can contain a monster and be fixed.
405 # The grid of the tile.
408 # The x coordinate in the grid (starting from 0).
411 # The y coordinate in the grid (starting from 0).
414 # The kind of monster in the grid. 0 means empty.
417 # blink time to live (0 means no blinking).
420 # shocked time to live (0 means not shocked)
423 # number of neighbors of the same kind.
426 # number of free non fixed next tiles
429 # is the tile editable (metal block)
440 return "\{{x},{y}:{s.chars[kind]}\}"
443 # Shape for metal block
444 var shape
: nullable Object = null
446 # Flag for `count_chain` computation.
447 private var chain_mark
= 0
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
457 if okind
== nkind
then return false
460 # First, remove it and update info.
462 var m
= g
.monsters
[okind
]
480 var a_neigbor
: nullable Tile = null
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
488 if not t2
.fixed
then t
.frees
+= 1
491 var m
= g
.monsters
[t2
.kind
]
493 if t2
.kind
== okind
then
494 if a_neigbor
== null then a_neigbor
= t2
495 # same than old, thus dec neighbors
508 # print "+ {t} one less next: {t2} ; +({i}x{j})"
511 if t2
.kind
== nkind
then
512 # Same than new, thus inc neighbors
529 # print "+ {t} one more next: {t2}"
534 # Add and update neighbors info
537 var m
= g
.monsters
[nkind
]
552 g
.check_chain
(nkind
, t
)
555 # check if the old kind broke, or create a chain
557 g
.check_chain
(okind
, a_neigbor
)
562 for m
in g
.monsters
do
563 if m
.number
> 0 and not m
.chain
then g
.won
= false