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 # Full UI for the game
12 app_name
("ChainZ of FriendZ")
13 app_version
(0, 1, git_revision
)
26 # higher means more dense grid
29 # Various grid sizes from large to small (16x16, 12x12, 8x8, 6x6)
30 var ratios
: Array[Int] = [200, 150, 100, 75]
36 if w
*100/r
<= 8 and h
*100/r
<= 8 then self.ratio
= r
41 # * ENTITIES ****************************************************************
43 # A game entity is something that is displayed and may interact with the player.
48 # X left coordinate (in pixel).
51 # Y top coordinate (in pixel).
54 # X right coordinate (in pixel).
55 fun x2
: Int do return x
+ width
57 # Y bottom coordinate (in pixel).
58 fun y2
: Int do return y
+ height
66 # Tool tip text (if any)
67 var over
: nullable String = null
69 # can the entity intercepts drag ang drop events?
72 # Draw function. To implement
73 fun draw
(ctx
: Display) do end
75 # Update function. Called each loop. To implement
78 # Enter function. Called when the cursor enter in the element. To implement
79 fun enter
(ev
: Event) do end
81 # Click function. Called when the player click in the element.
82 # (or activate it with a shortcut).
83 fun click
(ev
: Event) do end
85 # keyboard shortcut do activate the entity, if any
86 var shortcut
: nullable String = null
88 # Are events received?
91 fun bw
: Int do return game
.bw
92 fun bh
: Int do return game
.bh
94 # Should the entity be redrawn
98 # TEXT BUTTONS ***********************************************************/
100 # Button entity displayed as a simple text.
101 # if `over1` is null, then the button is a simple pasive label
102 # if `over1` is set but `over2` is null, then the button is a normal button
103 # if both `over1` and `over2` arew set, then the button is a toggleable button with two states
107 init(game
: Game, str
: String, x
,y
: Int, color
: nullable String, over
, over2
: nullable String)
110 super(game
, x
,y
,w
,24)
112 self.color
= color
or else "purple"
117 if self.toggleable
then
124 # The description of the button action
125 var over1
: nullable String
126 # The description of the state2 button action
127 var over2
: nullable String
129 # is the button a two-state button
130 fun toggleable
: Bool do return over2
!= null
132 # is the toggleable button in its second state?
135 # ttl for highlighting
138 # position of the start of the text
139 # in a toggleable button, there is space for the mark between `x` and `textx`
142 redef fun draw
(ctx
) do
143 if self.toggleable
then
145 if self.toggled
or not self.enabled
then w
= 6 else w
= 7
146 ctx
.blit
(game
.img2
[w
,0], self.x
, self.y
)
149 if self.enabled
then c
= self.color
else c
= "gray"
151 if self.ttl
> 0 then c2
= "rgba(255,255,255,{self.ttl/10})"
152 ctx
.textx
(self.str
, self.textx
, self.y
, self.height
, c
, c2
)
153 self.width
= ctx
.measureText
(self.str
, self.height
)
154 if self.toggleable
then self.width
+= bw
/2 + 4
159 if game
.statusbar
.over_what
!= self and self.ttl
> 0 then
167 if over1
== null then return
168 if not self.enabled
then return
169 game
.snd_click
.replay
175 # Called by `enter` do perform additionnal work if the button is active
176 # Specific button should implement this instead of `enter`
181 if not self.enabled
then
184 if self.toggleable
then
185 self.toggled
= not self.toggled
186 if self.toggled
then self.over
= self.over2
else self.over
= self.over1
187 game
.statusbar
.over_txt
= self.over
194 # Called by `click` do perform additionnal work if the button is active
195 # Specific button should implement this instead of `click`
196 fun click2
(ev
: Event) do end
200 # LEVEL BUTTONS ***********************************************************/
202 # button to play a level in the menu screen
206 # The associated level to play
213 super(l
.game
, (i
%5)*56 + 54, (i
/5)*56 + 55, l
.game
.bw
, l
.game
.bh
)
215 self.over
= self.level
.fullname
216 if self.level
.get_state
>= l
.l_won
then
217 if game
.levels
[9].get_state
>= l
.l_won
then self.over
+= " --- {self.level.score}/{self.level.par}"
218 else if self.level
.get_state
>= l
.l_open
then
219 if game
.levels
[9].get_state
>= l
.l_open
then self.over
+= " --- ?/{self.level.par}"
221 #self.enabled = l.get_state >= l.l_open
227 var s
= self.level
.get_state
228 var ix
= 5 + l
.number
% 2
230 if s
== l
.l_disabled
then
233 else if s
== l
.l_open
then
236 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
240 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
243 ctx
.blit
(game
.img2
[7,0], self.x
+ bw
*5/8, self.y-bh
*1/8)
245 ctx
.textx
(self.level
.name
, self.x
+5, self.y
+5, 24, null, null)
252 game
.play
(self.level
)
255 game
.statusbar
.set_tmp
("Locked level", "red")
261 # ACHIEVEMENTS ************************************************************/
263 # Achievement (monster-like) button in the menu screen
267 # The number of the achievement (0 is first)
270 # The name of the achievement
273 init(game
: Game, i
: Int, name
: String)
275 super(game
, 5*56 + 54, i
*56 + 55, game
.bw
, game
.bh
)
279 var l
= game
.levels
[number
*5+4]
280 enabled
= l
.get_state
>= l
.l_won
281 if self.enabled
then self.over
= name
+ " (unlocked)" else self.over
= name
+ " (locked)"
287 if self.enabled
then w
= 5 else w
= 3
288 ctx
.blit
(game
.img
[w
,self.number
+5], self.x
, self.y
)
293 if not self.enabled
then
295 game
.statusbar
.set_tmp
("Locked achievement!", "red")
302 fun click2
(ev
: Event) do
308 # BOARD (THE GRID) *******************************************************/
310 # The board game element.
315 super(game
, game
.xpad
, game
.ypad
, 8*game
.bw
, 8*game
.bh
)
322 var bwr
= bw
*100/grid
.ratio
323 var bhr
= bh
*100/grid
.ratio
326 if game
.selected_button
== game
.button_size
then
332 self.x
= game
.xpad
+(48*8/2)-w
*bwr
/2
333 self.y
= game
.ypad
+(48*8/2)-h
*bhr
/2
338 var t
= grid
.grid
[i
][j
]
339 var dx
= i
* bwr
+ self.x
340 var dy
= j
* bhr
+ self.y
342 ctx
.blit_scaled
(game
.img
[5,0], dx
, dy
, bwr
, bhr
)
344 ctx
.blit_scaled
(game
.img
[6,0], dx
, dy
, bwr
, bhr
)
347 if t
.shape
!= null and not game
.editing
then
348 #ctx.drawImage(game.img, t.shape.x*bw, (2+t.shape.y)*bh, bw, bh, i * bwr + self.x, j * bhr + self.y, bwr, bhr)
349 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
351 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
355 var m
= grid
.monsters
[t
.kind
]
357 if t
.blink
> 0 then s
= 1
358 if t
.nexts
> 2 then s
= 3
359 if t
.nexts
== 0 then s
= 6
360 if m
.chain
then s
= 5
361 if t
.shocked
>0 then s
= 2
362 ctx
.blit_scaled
(game
.img
[s
,(4+t
.kind
)], dx
, dy
, bwr
, bhr
)
364 #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
367 if game
.selected_button
== game
.button_size
then
369 var x1
= (grid
.width
) * bwr
- bwr
/2 + self.x
371 var y1
= (grid
.height
) * bhr
- bhr
/2 + self.y
372 ctx
.blit_scaled
(game
.img2
[0,0], x0
, y0
, bwr
/2, bhr
/2)
373 ctx
.blit_scaled
(game
.img2
[1,0], x1
, y0
, bwr
/2, bhr
/2)
374 ctx
.blit_scaled
(game
.img2
[1,1], x1
, y1
, bwr
/2, bhr
/2)
375 ctx
.blit_scaled
(game
.img2
[0,1], x0
, y1
, bwr
/2, bhr
/2)
376 ctx
.textx
("{grid.width}x{grid.height}",self.x
+ grid
.width
*bwr
/2,self.y
+grid
.height
*bhr
/2,20,"orange",null)
383 for i
in [0..grid
.width
[ do
384 for j
in [0..grid
.height
[ do
385 var t
= grid
.grid
[i
][j
]
386 if t
.kind
== 0 then continue
391 if t
.shocked
> 0 then
394 else if 100.rand
== 0 then
403 # Uded to filter drag events
404 private var last
: nullable Tile = null
410 if game
.selected_button
== game
.button_size
then r
= 200
411 var x
= ev
.game_x
* r
/ bw
/ 100
412 var y
= ev
.game_y
* r
/ bh
/ 100
413 var t
= grid
.grid
[x
][y
]
415 if ev
.drag
and last
== t
then return
418 if game
.selected_button
!= game
.button_size
and (x
>=grid
.width
or y
>=grid
.height
) then return
420 # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
422 if game
.selected_button
!= null then
423 game
.selected_button
.click_board
(ev
, t
)
428 # BUTTONS *****************************************************************/
430 # A in-game selectable button for monsters or tools
440 # The associated monster tile
441 # >0 for monsters, <=0 for tools
446 ctx
.blit
(game
.img
[self.imgx
, self.imgy
], self.x
, self.y
)
447 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
452 var sel
= game
.selected_button
453 if game
.selected_button
== game
.button_size
then game
.board
.dirty
=true
454 if sel
!= null then sel
.dirty
=true
455 game
.selected_button
= self
456 game
.snd_click
.replay
459 # Current inputed chain
461 private var chain
= new Array[Tile]
463 # Board click. Called when the player click on the board with the button selected.
464 fun click_board
(ev
: Event, t
: Tile)
466 game
.score
.dirty
= true
467 if ev
.drag
and self.kind
>0 and not chain
.is_empty
then
468 if self.chain
.length
>= 2 and self.chain
[1] == t
then
469 var t2
= self.chain
.shift
470 game
.snd_click
.replay
471 if t2
.fixed
and not game
.editing
then return
475 if t
.fixed
and t
.kind
== self.kind
then
476 self.chain
.unshift
(t
)
477 game
.snd_click
.replay
480 if (self.chain
[0].x
- t
.x
).abs
+ (self.chain
[0].y
- t
.y
).abs
!= 1 then return
481 if t
.fixed
and not game
.editing
then
485 if t
.kind
!= 0 and t
.kind
!= self.kind
then
490 self.chain
.unshift
(t
)
491 if t
.kind
== self.kind
then return
492 game
.snd_click
.replay
497 if t
.fixed
and not game
.editing
then
502 if t
.kind
!= self.kind
and not ev
.drag
then
503 game
.buttons
[t
.kind
].click
(ev
)
504 game
.buttons
[t
.kind
].chain
= [t
]
511 if t
.fixed
and game
.editing
and self == game
.button_erase
and t
.kind
== 0 then
513 game
.snd_click
.replay
517 var nkind
= 0 # the new kind
520 if t
.kind
== 0 then return
521 if self.kind
!= 0 and t
.kind
!= self.kind
then
527 else if t
.kind
!= self.kind
then
530 else if t
.kind
!= 0 then
534 if nkind
== t
.kind
then return
535 game
.snd_click
.replay
544 # TTL for the monster being angry
546 # TTL for the monster being happy
548 # TTL for the monster being shocked
550 # TTL for the monster blinking
553 init(game
: Game, i
: Int)
556 var x
= 440 + 58 * ((i-1
).abs
%3)
557 var y
= 150 + (bh
+5) * ((i-1
)/3)
558 super(game
, x
, y
, game
.bw
, game
.bh
)
559 if i
== 0 then return
563 over
= game
.colors
[i
] + " monster ({i})"
564 shortcut
= i
.to_s
# code for 1 trough 9
575 if self.happy
> 0 then
579 if self.shocked
> 0 then
583 if self.blink
> 0 then
586 else if 100.rand
== 0 then
595 if self.angries
>0 then
597 else if self.happy
> 5 then
599 else if self.shocked
> 0 then
601 else if self.blink
> 0 then
604 ctx
.blit
(game
.img
[s
, self.imgy
], self.x
, self.y
)
605 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
613 super(game
, 440, 92, game
.bh
, 22+game
.bh
)
622 # Metal (fixed) button.
627 super(game
, 498, 92, game
.bh
, 20+game
.bh
)
631 over
= "Metal block (q)"
635 private var fixed
= false
637 redef fun click_board
(ev
,t
)
639 if not ev
.drag
then self.fixed
= not t
.fixed
640 if t
.fixed
== self.fixed
then return
642 game
.snd_click
.replay
652 super(game
,556, 92, game
.bh
, 20+game
.bh
)
654 over
= "Resize the grid"
661 var x
= self.x
+ i
*bw
/3
662 var y
= self.y
+ j
*bh
/3
663 ctx
.blit_scaled
(game
.img
[5+(i
+j
)%2,0], x
, y
, bw
/3, bh
/3)
666 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
671 if game
.selected_button
!= game
.button_size
then
674 game
.selected_button
= null
675 game
.board
.dirty
=true
679 redef fun click_board
(evt
, t
)
684 if w
< 3 or h
< 3 then
686 game
.statusbar
.set_tmp
("Too small!", "red")
690 for i
in [0..grid
.width
[ do
691 for j
in [0..grid
.height
[ do
693 var t2
= grid
.grid
[i
][j
]
703 game
.statusbar
.set_tmp
("Monsters on the way!", "red")
706 game
.snd_click
.replay
711 # Inactive area used to display the score
716 super(game
,440,310,199,62)
720 ctx
.textx
("MONSTERS: {game.grid.number}",self.x
,self.y
+1,21,"cyan",null)
721 var level
= game
.level
722 if level
== null then return
723 if level
.get_state
>= level
.l_won
then
724 ctx
.textx
("BEST: {level.score}",self.x
,self.y
+22,21,"pink", null)
726 ctx
.textx
("BEST: -",self.x
,self.y
+22,21,"pink", null)
728 if game
.levels
[9].get_state
>= level
.l_won
then
729 if level
.is_challenge
then
730 ctx
.textx
("GOAL: {level.par}",self.x
,self.y
+44,21,"yellow",null)
732 ctx
.textx
("PAR: {level.par}",self.x
,self.y
+44,21,"yellow",null)
738 # Status bar element.
743 super(game
,24, 440, 418-24, 30)
746 # Permanant text, if any
747 var main_txt
: nullable String = null
749 # Text to display when the cursor if over an entity (`over_what`), if any
750 var over_txt
: nullable String = null
752 # What is the entity for `over_txt`
753 var over_what
: nullable Entity
755 # Text to temporally display, for some game event, if any
756 var tmp_txt
: nullable String = null
758 # time-to-live for the `tmp_txt`
761 # Color used to display `tmp_txt`
762 var tmp_txt_color
: nullable String = null
772 # set a temporary text
773 fun set_tmp
(txt
, color
: String)
775 print
"***STATUS** {txt}"
777 self.tmp_txt_ttl
= 20
778 self.tmp_txt_color
= color
783 var tmp_txt
= self.tmp_txt
784 var over_txt
= self.over_txt
785 var main_txt
= self.main_txt
786 if tmp_txt
!= null and self.tmp_txt_ttl
>0 then
787 ctx
.textx
(tmp_txt
,24,442,24,self.tmp_txt_color
,null)
788 else if over_txt
!= null then
789 ctx
.textx
(over_txt
,24,442,24,"yellow",null)
790 else if main_txt
!= null then
791 ctx
.textx
(main_txt
,24,442,24,"white",null)
797 if self.tmp_txt_ttl
>0 then
804 # ************************************************************************/
819 sys
.system
("aplay assets/{path} &")
825 fun textx
(str
: String, x
, y
, height
: Int, color
, color2
: nullable String)
827 #var w = measureText(str, height)
829 text
(str
.to_upper
, app
.game
.font
, x
, y
)
832 # give the width for a giver text
833 fun measureText
(str
: String, height
: Int): Int
835 var font
= app
.game
.font
836 return str
.length
* (font
.width
+ font
.hspace
.to_i
)
839 # displays a debug rectangle
840 fun rect
(x
,y
,w
,h
:Int)
842 var image
= once app
.load_image
("hitbox.png")
843 blit_scaled
(image
, x
, y
, w
, h
)
847 # Simple basic class for event
860 var char_code
: String
864 # width of a tile, used for most width reference in the game
866 # height a tile, used for most width reference in the game
868 # x-coordinate of the board (padding)
870 # y-coordinate of the board (padding)
876 var img
= new TileSet(app
.load_image
("tiles2.png"),48,48)
878 # Sub tileset (for marks or other)
879 var img2
= new TileSet(app
.load_image
("tiles2.png"),24,24)
882 var back
: Image = app
.load_image
("background.png")
885 var logo
: Image = app
.load_image
("logo.png")
888 var font
= new TileSetFont(app
.load_image
("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
891 fun save_cookie(name, val:String) do
893 var date = new Date()
894 date.setTime(date.getTime()+(days*24*60*60*1000))
895 document.cookie = name+"="+val+"; expires="+date.toGMTString()+"; path=/"
898 fun read_cookie(name:String):String do
900 var ca = document.cookie.split(';')
901 for(var i=0; i<ca.length; i++) do
903 while (c[0]==' ') c = c.substring(1, c.length)
904 if (c.indexOf(key) == 0) return c.substring(key.length)
910 # DISPLAY *****************************************************************
912 # Is the game in editing mode
915 # The selected button, if any
916 var selected_button: nullable Button = null
920 # Is the music muted?
921 var music_muted: Bool = true #read_cookie("music_muted")
923 # Is the sound effects muted?
924 var sfx_muted: Bool = true #read_cookie("sfx_muted")
926 # The background music resource. */
927 var music = new Audio("music.ogg")
930 var snd_click = new Audio("click.wav")
933 var snd_win = new Audio("level.wav")
936 var snd_duh = new Audio("duh.wav")
939 var snd_bing = new Audio("bing.wav")
942 var snd_whip = new Audio("whip.wav")
944 # INPUT ******************************************************************
946 # Current grid edited (if any).
947 var grid_edit: nullable Grid = null
949 # Sequence of current entities
950 var entities = new Array[Entity]
952 # The current statusbar
953 var statusbar = new StatusBar(self)
956 var board = new Board(self)
958 # The current score board
959 var score = new Score(self)
961 # Monster button game elements.
962 var buttons = new Array[MonsterButton]
965 var button_wall = new MetalButton(self)
968 var button_erase = new EraseButton(self)
971 var button_size = new ResizeButton(self)
977 buttons[i] = new MonsterButton(self, i)
981 # Play a level in player mode.
986 init_play_menu(false)
987 if level.status != "" then
988 statusbar.main_txt = level.status
990 statusbar.main_txt = level.fullname
992 var t = new NextLevelButton(self)
997 # Play the next level.
1000 play(levels[level.number+1])
1004 # Helper function to initialize all states.
1005 # Set up buttons for music and SFX.
1011 entities.push(new MusicButton(self))
1012 entities.push(new SFXButton(self))
1013 entities.push(new MenuButton(self))
1015 entities.push(statusbar)
1018 # Helper function to initialize monster menu entries.
1019 fun init_play_menu(full: Bool)
1022 entities.push(board)
1023 entities.push(new ResetButton(self))
1024 entities.push(button_erase)
1025 # Push monster buttons and determine the selected one
1026 var sel: nullable Button = null
1027 for i in [1..monsters] do
1028 if grid.monsters[i].number > 0 or full then
1029 if selected_button == buttons[i] or sel == null then
1032 entities.push(buttons[i])
1035 selected_button = sel
1036 entities.push(score)
1039 # Play a arbitrary grid in try mode.
1040 fun play_grid(g: Grid)
1043 init_play_menu(false)
1044 statusbar.main_txt = "User level"
1045 if grid_edit != null then
1046 entities.push(new EditButton(self))
1048 entities.push(new WonButton(self))
1052 # Launch the editor starting with a grid.
1053 fun edit_grid(g: Grid)
1056 init_play_menu(true)
1058 statusbar.main_txt = "Level editor"
1059 if level != null then statusbar.main_txt += ": level "+level.name
1060 entities.push(button_wall)
1061 entities.push(button_size)
1062 entities.push(new TestButton(self))
1063 entities.push(new SaveButton(self))
1064 entities.push(new LoadButton(self))
1068 # Launch the title screen
1072 entities.push(new Splash(self))
1076 # Helper function to initialize the menu (and tile) screen
1081 var i = levels.first
1083 if l.get_state == l.l_open then break
1086 entities.push(new StartButton(self, i))
1094 t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
1096 for i in [0..levels.length[ do
1097 var b = new LevelButton(levels[i])
1100 t = new Achievement(self, 0, "Training")
1102 t = new Achievement(self, 1, "Par")
1104 t = new Achievement(self, 2, "Editor")
1106 t = new Achievement(self, 3, "Challenge")
1108 t = new Achievement(self, 4, "Congraturation")
1110 t = new Achievement(self, 5, "Awesome")
1115 # Last function called when the lauch state is ready
1128 # Should all entity redrawn?
1129 var dirty_all = true
1131 # Draw all game entities.
1132 fun draw(display: Display) do
1134 if dirty_all then display.blit(back, 0, 0)
1135 for g in entities do
1136 if g.dirty or dirty_all then
1138 #if g.x2-g.x>0 and g.y2-g.y>0 then ctx.drawImage(back, g.x, g.y, g.x2-g.x, g.y2-g.y, g.x, g.y, g.x2-g.x, g.y2-g.y)
1140 #ctx.rect(g.x, g.y, g.width, g.height)
1144 if ev isa Event then
1145 display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
1150 # Update all game entities.
1152 if solver != null and not solver_pause then
1153 if solver.run_steps(solver_steps) != null then solver_pause = true
1155 if not solver.is_running then solver = null
1157 for g in entities do
1162 # Return the game entity located at a mouse event.
1163 fun get_game_element(ev: Event): nullable Entity
1167 for g in entities do
1168 if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
1178 # The game entlty the mouse went down on
1179 var drag: nullable Entity = null
1181 # Last mouse event. Used to dray the cursor
1182 var lastev: nullable Event = null
1184 # Callback when the mouse is pressed
1185 fun onMouseDown(ev: Event) do
1187 var g = get_game_element(ev)
1195 # Callback when the mouse is releassed
1196 fun onMouseUp(ev: Event) do
1200 # Callback when the mouse if moved while pressed
1201 fun onMouseMove(ev: Event) do
1203 var g = get_game_element(ev)
1205 statusbar.dirty = true
1206 statusbar.over_txt = null
1207 statusbar.over_what = null
1210 if statusbar.over_what != g then
1211 statusbar.dirty = true
1213 statusbar.over_txt = go
1214 statusbar.over_what = g
1216 if go != null then print "***OVER*** {go}"
1218 # We moved abode a element that accepts drag event
1219 if drag == g and g.draggable then
1227 # Current solver, if any
1228 var solver: nullable BacktrackSolver[Grid, Action] = null
1230 # Is the solver paused?
1231 var solver_pause = false
1233 # Number of solver steps played in a single game `update`
1234 var solver_steps = 20000
1236 # Callback when a keyboard event is recieved
1237 fun onKeyDown(ev: Event) do
1238 var kc = ev.char_code
1240 grid_edit = grid.copy(true)
1242 else if kc == "s" then
1243 if solver == null then
1244 solver = (new FriendzProblem(grid)).solve
1245 solver_pause = false
1247 solver_pause = not solver_pause
1250 else if kc == "d" then
1251 if solver == null then
1252 solver = (new FriendzProblem(grid)).solve
1257 else if kc == "+" then
1260 else if kc == "-" then
1263 else for g in entities do
1264 if kc == g.shortcut then
1272 # The spash title image
1277 super(game,game.xpad,game.ypad,380,350)
1281 ctx.blit(game.logo, game.xpad, game.ypad)
1285 game.snd_whip.replay
1290 class NextLevelButton
1294 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1300 var w = game.level.check_won(game.grid)
1301 if self.enabled != w then
1306 game.statusbar.set_tmp("Level solved!", "cyan")
1313 if not self.enabled then
1315 var grid = game.grid
1316 var monsters = grid.monsters
1317 var angry = new Array[Tile]
1318 var lonely = new Array[Tile]
1319 var edges = new Array[Tile]
1320 for i in [0..grid.width[ do
1321 for j in [0..grid.height[ do
1322 var t = grid.grid[i][j]
1323 if t.kind == 0 then continue
1324 if t.nexts == 0 then lonely.push(t)
1325 if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
1326 if t.nexts > 2 then angry.push(t)
1331 if angry.length>0 then
1333 else if lonely.length>0 then
1338 for i in l do i.shocked=5
1340 if angry.length>0 then
1341 game.statusbar.set_tmp("Angry monsters!", "red")
1342 else if lonely.length>0 then
1343 game.statusbar.set_tmp("Lonely monsters!", "red")
1344 else if not grid.won then
1345 game.statusbar.set_tmp("Unconnected monsters!", "red")
1347 game.statusbar.set_tmp("Not enough monsters!", "red")
1352 game.snd_whip.replay
1361 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1363 redef fun click2(ev)
1365 game.music_muted = self.toggled
1366 if game.music_muted then game.music.pause else game.music.play
1367 #game.save_cookie("music_muted",music_muted?"true":"")
1375 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1378 redef fun click2(ev)
1380 game.sfx_muted = self.toggled
1381 if not game.sfx_muted then game.snd_whip.replay # Because the automatic one was muted
1382 #save_cookie("sfx_muted",sfx_muted?"true":"")
1390 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1394 redef fun click2(ev)
1404 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1409 redef fun click2(ev)
1412 if self.count==1 then
1413 game.statusbar.set_tmp("Click again to reset","white")
1414 else if self.count==2 then
1415 game.grid.reset(false)
1416 if game.editing then
1417 game.statusbar.set_tmp("Click again to clear all","white")
1419 else if game.editing then
1420 game.grid.reset(true)
1422 game.dirty_all = true
1435 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1438 redef fun click2(ev)
1440 var ge = game.grid_edit
1450 super(game,"WON", 440, 24, "cyan", "", null)
1453 redef fun click2(ev)
1455 var ge = game.grid_edit
1456 if not self.enabled then
1457 game.statusbar.set_tmp("Solve the level first!", "red")
1458 else if ge != null then
1459 game.snd_whip.replay
1462 game.snd_whip.replay
1469 var w = game.grid.won
1470 if self.enabled != w then
1475 game.statusbar.set_tmp("Level solved!", "cyan")
1485 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1488 redef fun click2(ev)
1490 game.grid_edit = game.grid
1491 game.play_grid(game.grid.copy(false))
1499 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1502 redef fun click2(ev)
1504 var res = game.grid.save
1513 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1516 redef fun click2(ev)
1518 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1519 if grid2.load("") then
1522 game.dirty_all = true
1526 class ContinueButton
1530 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1533 redef fun click2(ev)
1542 init(game: Game, level: Level)
1545 if level.number == 0 then
1546 super(game,"START", 440, 24, "purple", "Play the first level", null)
1548 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1552 redef fun click2(ev)
1565 # Wanted screen width
1566 var screen_width = 640
1568 # Wanted screen height
1569 var screen_height = 480
1571 redef fun window_created
1575 game.font.hspace = -2
1576 if args.length > 0 then
1577 game.play(game.levels[args.first.to_i])
1582 # Maximum wanted frame per second
1585 # clock used to track FPS
1586 private var clock = new Clock
1588 redef fun frame_core(display)
1592 var dt = clock.lapse
1593 var target_dt = 1000000000 / max_fps
1594 if dt.sec == 0 and dt.nanosec < target_dt then
1595 var sleep_t = target_dt - dt.nanosec
1596 sys.nanosleep(0, sleep_t)
1600 redef fun input(input_event)
1603 if input_event isa QuitEvent then # close window button
1604 quit = true # orders system to quit
1605 else if input_event isa PointerEvent then
1606 var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
1607 if input_event.is_motion then
1608 game.onMouseMove(ev)
1609 else if input_event.pressed then
1610 game.onMouseDown(ev)
1615 else if input_event isa KeyEvent and input_event.is_down then
1616 var ev = new Event(0, 0, input_event.key_name)
1625 redef class PointerEvent
1626 fun is_motion: Bool do return false
1629 redef class KeyEvent
1630 fun key_name: String
1633 if c != null then return c.to_s