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
)
20 import app
::data_store
28 # higher means more dense grid
31 # Various grid sizes from large to small (16x16, 12x12, 8x8, 6x6)
32 var ratios
: Array[Int] = [200, 150, 100, 75]
38 if w
*100/r
<= 8 and h
*100/r
<= 8 then self.ratio
= r
43 # * ENTITIES ****************************************************************
45 # A game entity is something that is displayed and may interact with the player.
50 # X left coordinate (in pixel).
53 # Y top coordinate (in pixel).
56 # X right coordinate (in pixel).
57 fun x2
: Int do return x
+ width
59 # Y bottom coordinate (in pixel).
60 fun y2
: Int do return y
+ height
68 # Tool tip text (if any)
69 var over
: nullable String = null
71 # can the entity intercepts drag ang drop events?
74 # Draw function. To implement
75 fun draw
(ctx
: Display) do end
77 # Update function. Called each loop. To implement
80 # Enter function. Called when the cursor enter in the element. To implement
81 fun enter
(ev
: Event) do end
83 # Click function. Called when the player click in the element.
84 # (or activate it with a shortcut).
85 fun click
(ev
: Event) do end
87 # keyboard shortcut do activate the entity, if any
88 var shortcut
: nullable String = null
90 # Are events received?
93 fun bw
: Int do return game
.bw
94 fun bh
: Int do return game
.bh
96 # Should the entity be redrawn
100 # TEXT BUTTONS ***********************************************************/
102 # Button entity displayed as a simple text.
103 # if `over1` is null, then the button is a simple pasive label
104 # if `over1` is set but `over2` is null, then the button is a normal button
105 # if both `over1` and `over2` arew set, then the button is a toggleable button with two states
109 init(game
: Game, str
: String, x
,y
: Int, color
: nullable String, over
, over2
: nullable String)
112 super(game
, x
,y
,w
,24)
114 self.color
= color
or else "purple"
119 if self.toggleable
then
126 # The description of the button action
127 var over1
: nullable String
128 # The description of the state2 button action
129 var over2
: nullable String
131 # is the button a two-state button
132 fun toggleable
: Bool do return over2
!= null
134 # is the toggleable button in its second state?
137 # ttl for highlighting
140 # position of the start of the text
141 # in a toggleable button, there is space for the mark between `x` and `textx`
144 redef fun draw
(ctx
) do
145 if self.toggleable
then
147 if self.toggled
or not self.enabled
then w
= 6 else w
= 7
148 ctx
.blit
(game
.img2
[w
,0], self.x
, self.y
)
151 if self.enabled
then c
= self.color
else c
= "gray"
153 if self.ttl
> 0 then c2
= "rgba(255,255,255,{self.ttl/10})"
154 ctx
.textx
(self.str
, self.textx
, self.y
, self.height
, c
, c2
)
155 self.width
= ctx
.measureText
(self.str
, self.height
)
156 if self.toggleable
then self.width
+= bw
/2 + 4
161 if game
.statusbar
.over_what
!= self and self.ttl
> 0 then
169 if over1
== null then return
170 if not self.enabled
then return
171 game
.snd_click
.replay
177 # Called by `enter` do perform additionnal work if the button is active
178 # Specific button should implement this instead of `enter`
183 if not self.enabled
then
186 if self.toggleable
then
187 self.toggled
= not self.toggled
188 if self.toggled
then self.over
= self.over2
else self.over
= self.over1
189 game
.statusbar
.over_txt
= self.over
196 # Called by `click` do perform additionnal work if the button is active
197 # Specific button should implement this instead of `click`
198 fun click2
(ev
: Event) do end
202 # LEVEL BUTTONS ***********************************************************/
204 # button to play a level in the menu screen
208 # The associated level to play
215 super(l
.game
, (i
%5)*56 + 54, (i
/5)*56 + 55, l
.game
.bw
, l
.game
.bh
)
217 self.over
= self.level
.fullname
218 if self.level
.get_state
>= l
.l_won
then
219 if game
.levels
[9].get_state
>= l
.l_won
then self.over
+= " --- {self.level.score}/{self.level.par}"
220 else if self.level
.get_state
>= l
.l_open
then
221 if game
.levels
[9].get_state
>= l
.l_open
then self.over
+= " --- ?/{self.level.par}"
223 #self.enabled = l.get_state >= l.l_open
229 var s
= self.level
.get_state
230 var ix
= 5 + l
.number
% 2
232 if s
== l
.l_disabled
then
235 else if s
== l
.l_open
then
238 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
242 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
245 ctx
.blit
(game
.img2
[7,0], self.x
+ bw
*5/8, self.y-bh
*1/8)
247 ctx
.textx
(self.level
.name
, self.x
+5, self.y
+5, 24, null, null)
254 game
.play
(self.level
)
257 game
.statusbar
.set_tmp
("Locked level", "red")
263 # ACHIEVEMENTS ************************************************************/
265 # Achievement (monster-like) button in the menu screen
269 # The number of the achievement (0 is first)
272 # The name of the achievement
275 init(game
: Game, i
: Int, name
: String)
277 super(game
, 5*56 + 54, i
*56 + 55, game
.bw
, game
.bh
)
281 var l
= game
.levels
[number
*5+4]
282 enabled
= l
.get_state
>= l
.l_won
283 if self.enabled
then self.over
= name
+ " (unlocked)" else self.over
= name
+ " (locked)"
289 if self.enabled
then w
= 5 else w
= 3
290 ctx
.blit
(game
.img
[w
,self.number
+5], self.x
, self.y
)
295 if not self.enabled
then
297 game
.statusbar
.set_tmp
("Locked achievement!", "red")
304 fun click2
(ev
: Event) do
310 # BOARD (THE GRID) *******************************************************/
312 # The board game element.
317 super(game
, game
.xpad
, game
.ypad
, 8*game
.bw
, 8*game
.bh
)
324 var bwr
= bw
*100/grid
.ratio
325 var bhr
= bh
*100/grid
.ratio
328 if game
.selected_button
== game
.button_size
then
334 self.x
= game
.xpad
+(48*8/2)-w
*bwr
/2
335 self.y
= game
.ypad
+(48*8/2)-h
*bhr
/2
340 var t
= grid
.grid
[i
][j
]
341 var dx
= i
* bwr
+ self.x
342 var dy
= j
* bhr
+ self.y
344 ctx
.blit_scaled
(game
.img
[5,0], dx
, dy
, bwr
, bhr
)
346 ctx
.blit_scaled
(game
.img
[6,0], dx
, dy
, bwr
, bhr
)
349 if t
.shape
!= null and not game
.editing
then
350 #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)
351 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
353 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
357 var m
= grid
.monsters
[t
.kind
]
359 if t
.blink
> 0 then s
= 1
360 if t
.nexts
> 2 then s
= 3
361 if t
.nexts
== 0 then s
= 6
362 if m
.chain
then s
= 5
363 if t
.shocked
>0 then s
= 2
364 ctx
.blit_scaled
(game
.img
[s
,(4+t
.kind
)], dx
, dy
, bwr
, bhr
)
366 #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
369 if game
.selected_button
== game
.button_size
then
371 var x1
= (grid
.width
) * bwr
- bwr
/2 + self.x
373 var y1
= (grid
.height
) * bhr
- bhr
/2 + self.y
374 ctx
.blit_scaled
(game
.img2
[0,0], x0
, y0
, bwr
/2, bhr
/2)
375 ctx
.blit_scaled
(game
.img2
[1,0], x1
, y0
, bwr
/2, bhr
/2)
376 ctx
.blit_scaled
(game
.img2
[1,1], x1
, y1
, bwr
/2, bhr
/2)
377 ctx
.blit_scaled
(game
.img2
[0,1], x0
, y1
, bwr
/2, bhr
/2)
378 ctx
.textx
("{grid.width}x{grid.height}",self.x
+ grid
.width
*bwr
/2,self.y
+grid
.height
*bhr
/2,20,"orange",null)
385 for i
in [0..grid
.width
[ do
386 for j
in [0..grid
.height
[ do
387 var t
= grid
.grid
[i
][j
]
388 if t
.kind
== 0 then continue
393 if t
.shocked
> 0 then
396 else if 100.rand
== 0 then
405 # Uded to filter drag events
406 private var last
: nullable Tile = null
412 if game
.selected_button
== game
.button_size
then r
= 200
413 var x
= ev
.game_x
* r
/ bw
/ 100
414 var y
= ev
.game_y
* r
/ bh
/ 100
415 var t
= grid
.grid
[x
][y
]
417 if ev
.drag
and last
== t
then return
420 if game
.selected_button
!= game
.button_size
and (x
>=grid
.width
or y
>=grid
.height
) then return
422 # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
424 if game
.selected_button
!= null then
425 game
.selected_button
.click_board
(ev
, t
)
430 # BUTTONS *****************************************************************/
432 # A in-game selectable button for monsters or tools
442 # The associated monster tile
443 # >0 for monsters, <=0 for tools
448 ctx
.blit
(game
.img
[self.imgx
, self.imgy
], self.x
, self.y
)
449 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
454 var sel
= game
.selected_button
455 if game
.selected_button
== game
.button_size
then game
.board
.dirty
=true
456 if sel
!= null then sel
.dirty
=true
457 game
.selected_button
= self
458 game
.snd_click
.replay
461 # Current inputed chain
463 private var chain
= new Array[Tile]
465 # Board click. Called when the player click on the board with the button selected.
466 fun click_board
(ev
: Event, t
: Tile)
468 game
.score
.dirty
= true
469 if ev
.drag
and self.kind
>0 and not chain
.is_empty
then
470 if self.chain
.length
>= 2 and self.chain
[1] == t
then
471 var t2
= self.chain
.shift
472 game
.snd_click
.replay
473 if t2
.fixed
and not game
.editing
then return
477 if t
.fixed
and t
.kind
== self.kind
then
478 self.chain
.unshift
(t
)
479 game
.snd_click
.replay
482 if (self.chain
[0].x
- t
.x
).abs
+ (self.chain
[0].y
- t
.y
).abs
!= 1 then return
483 if t
.fixed
and not game
.editing
then
487 if t
.kind
!= 0 and t
.kind
!= self.kind
then
492 self.chain
.unshift
(t
)
493 if t
.kind
== self.kind
then return
494 game
.snd_click
.replay
499 if t
.fixed
and not game
.editing
then
504 if t
.kind
!= self.kind
and not ev
.drag
then
505 game
.buttons
[t
.kind
].click
(ev
)
506 game
.buttons
[t
.kind
].chain
= [t
]
513 if t
.fixed
and game
.editing
and self == game
.button_erase
and t
.kind
== 0 then
515 game
.snd_click
.replay
519 var nkind
= 0 # the new kind
522 if t
.kind
== 0 then return
523 if self.kind
!= 0 and t
.kind
!= self.kind
then
529 else if t
.kind
!= self.kind
then
532 else if t
.kind
!= 0 then
536 if nkind
== t
.kind
then return
537 game
.snd_click
.replay
546 # TTL for the monster being angry
548 # TTL for the monster being happy
550 # TTL for the monster being shocked
552 # TTL for the monster blinking
555 init(game
: Game, i
: Int)
558 var x
= 440 + 58 * ((i-1
).abs
%3)
559 var y
= 150 + (bh
+5) * ((i-1
)/3)
560 super(game
, x
, y
, game
.bw
, game
.bh
)
561 if i
== 0 then return
565 over
= game
.colors
[i
] + " monster ({i})"
566 shortcut
= i
.to_s
# code for 1 trough 9
577 if self.happy
> 0 then
581 if self.shocked
> 0 then
585 if self.blink
> 0 then
588 else if 100.rand
== 0 then
597 if self.angries
>0 then
599 else if self.happy
> 5 then
601 else if self.shocked
> 0 then
603 else if self.blink
> 0 then
606 ctx
.blit
(game
.img
[s
, self.imgy
], self.x
, self.y
)
607 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
615 super(game
, 440, 92, game
.bh
, 22+game
.bh
)
624 # Metal (fixed) button.
629 super(game
, 498, 92, game
.bh
, 20+game
.bh
)
633 over
= "Metal block (q)"
637 private var fixed
= false
639 redef fun click_board
(ev
,t
)
641 if not ev
.drag
then self.fixed
= not t
.fixed
642 if t
.fixed
== self.fixed
then return
644 game
.snd_click
.replay
654 super(game
,556, 92, game
.bh
, 20+game
.bh
)
656 over
= "Resize the grid"
663 var x
= self.x
+ i
*bw
/3
664 var y
= self.y
+ j
*bh
/3
665 ctx
.blit_scaled
(game
.img
[5+(i
+j
)%2,0], x
, y
, bw
/3, bh
/3)
668 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
673 if game
.selected_button
!= game
.button_size
then
676 game
.selected_button
= null
677 game
.board
.dirty
=true
681 redef fun click_board
(evt
, t
)
686 if w
< 3 or h
< 3 then
688 game
.statusbar
.set_tmp
("Too small!", "red")
692 for i
in [0..grid
.width
[ do
693 for j
in [0..grid
.height
[ do
695 var t2
= grid
.grid
[i
][j
]
705 game
.statusbar
.set_tmp
("Monsters on the way!", "red")
708 game
.snd_click
.replay
713 # Inactive area used to display the score
718 super(game
,440,310,199,62)
722 ctx
.textx
("MONSTERS: {game.grid.number}",self.x
,self.y
+1,21,"cyan",null)
723 var level
= game
.level
724 if level
== null then return
725 if level
.get_state
>= level
.l_won
then
726 ctx
.textx
("BEST: {level.score}",self.x
,self.y
+22,21,"pink", null)
728 ctx
.textx
("BEST: -",self.x
,self.y
+22,21,"pink", null)
730 if game
.levels
[9].get_state
>= level
.l_won
then
731 if level
.is_challenge
then
732 ctx
.textx
("GOAL: {level.par}",self.x
,self.y
+44,21,"yellow",null)
734 ctx
.textx
("PAR: {level.par}",self.x
,self.y
+44,21,"yellow",null)
740 # Status bar element.
745 super(game
,24, 440, 418-24, 30)
748 # Permanant text, if any
749 var main_txt
: nullable String = null
751 # Text to display when the cursor if over an entity (`over_what`), if any
752 var over_txt
: nullable String = null
754 # What is the entity for `over_txt`
755 var over_what
: nullable Entity
757 # Text to temporally display, for some game event, if any
758 var tmp_txt
: nullable String = null
760 # time-to-live for the `tmp_txt`
763 # Color used to display `tmp_txt`
764 var tmp_txt_color
: nullable String = null
774 # set a temporary text
775 fun set_tmp
(txt
, color
: String)
777 print
"***STATUS** {txt}"
779 self.tmp_txt_ttl
= 20
780 self.tmp_txt_color
= color
785 var tmp_txt
= self.tmp_txt
786 var over_txt
= self.over_txt
787 var main_txt
= self.main_txt
788 if tmp_txt
!= null and self.tmp_txt_ttl
>0 then
789 ctx
.textx
(tmp_txt
,24,442,24,self.tmp_txt_color
,null)
790 else if over_txt
!= null then
791 ctx
.textx
(over_txt
,24,442,24,"yellow",null)
792 else if main_txt
!= null then
793 ctx
.textx
(main_txt
,24,442,24,"white",null)
799 if self.tmp_txt_ttl
>0 then
806 # ************************************************************************/
821 sys
.system
("aplay assets/{path} &")
827 fun textx
(str
: String, x
, y
, height
: Int, color
, color2
: nullable String)
829 #var w = measureText(str, height)
831 text
(str
.to_upper
, app
.game
.font
, x
, y
)
834 # give the width for a giver text
835 fun measureText
(str
: String, height
: Int): Int
837 var font
= app
.game
.font
838 return str
.length
* (font
.width
+ font
.hspace
.to_i
)
841 # displays a debug rectangle
842 fun rect
(x
,y
,w
,h
:Int)
844 var image
= once app
.load_image
("hitbox.png")
845 blit_scaled
(image
, x
, y
, w
, h
)
849 # Simple basic class for event
862 var char_code
: String
866 # width of a tile, used for most width reference in the game
868 # height a tile, used for most width reference in the game
870 # x-coordinate of the board (padding)
872 # y-coordinate of the board (padding)
878 var img
= new TileSet(app
.load_image
("tiles2.png"),48,48)
880 # Sub tileset (for marks or other)
881 var img2
= new TileSet(app
.load_image
("tiles2.png"),24,24)
884 var back
: Image = app
.load_image
("background.png")
887 var logo
: Image = app
.load_image
("logo.png")
890 var font
= new TileSetFont(app
.load_image
("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
893 fun save_cookie(name, val:String) do
895 var date = new Date()
896 date.setTime(date.getTime()+(days*24*60*60*1000))
897 document.cookie = name+"="+val+"; expires="+date.toGMTString()+"; path=/"
900 fun read_cookie(name:String):String do
902 var ca = document.cookie.split(';')
903 for(var i=0; i<ca.length; i++) do
905 while (c[0]==' ') c = c.substring(1, c.length)
906 if (c.indexOf(key) == 0) return c.substring(key.length)
912 # DISPLAY *****************************************************************
914 # Is the game in editing mode
917 # The selected button, if any
918 var selected_button: nullable Button = null
922 # Is the music muted?
923 var music_muted: Bool = true #read_cookie("music_muted")
925 # Is the sound effects muted?
926 var sfx_muted: Bool = true #read_cookie("sfx_muted")
928 # The background music resource. */
929 var music = new Audio("music.ogg")
932 var snd_click = new Audio("click.wav")
935 var snd_win = new Audio("level.wav")
938 var snd_duh = new Audio("duh.wav")
941 var snd_bing = new Audio("bing.wav")
944 var snd_whip = new Audio("whip.wav")
946 # INPUT ******************************************************************
948 # Current grid edited (if any).
949 var grid_edit: nullable Grid = null
951 # Sequence of current entities
952 var entities = new Array[Entity]
954 # The current statusbar
955 var statusbar = new StatusBar(self)
958 var board = new Board(self)
960 # The current score board
961 var score = new Score(self)
963 # Monster button game elements.
964 var buttons = new Array[MonsterButton]
967 var button_wall = new MetalButton(self)
970 var button_erase = new EraseButton(self)
973 var button_size = new ResizeButton(self)
979 buttons[i] = new MonsterButton(self, i)
983 # Play a level in player mode.
988 init_play_menu(false)
989 if level.status != "" then
990 statusbar.main_txt = level.status
992 statusbar.main_txt = level.fullname
994 var t = new NextLevelButton(self)
999 # Play the next level.
1002 play(levels[level.number+1])
1006 # Helper function to initialize all states.
1007 # Set up buttons for music and SFX.
1013 entities.push(new MusicButton(self))
1014 entities.push(new SFXButton(self))
1015 entities.push(new MenuButton(self))
1017 entities.push(statusbar)
1020 # Helper function to initialize monster menu entries.
1021 fun init_play_menu(full: Bool)
1024 entities.push(board)
1025 entities.push(new ResetButton(self))
1026 entities.push(button_erase)
1027 # Push monster buttons and determine the selected one
1028 var sel: nullable Button = null
1029 for i in [1..monsters] do
1030 if grid.monsters[i].number > 0 or full then
1031 if selected_button == buttons[i] or sel == null then
1034 entities.push(buttons[i])
1037 selected_button = sel
1038 entities.push(score)
1041 # Play a arbitrary grid in try mode.
1042 fun play_grid(g: Grid)
1045 init_play_menu(false)
1046 statusbar.main_txt = "User level"
1047 if grid_edit != null then
1048 entities.push(new EditButton(self))
1050 entities.push(new WonButton(self))
1054 # Launch the editor starting with a grid.
1055 fun edit_grid(g: Grid)
1058 init_play_menu(true)
1060 statusbar.main_txt = "Level editor"
1061 if level != null then statusbar.main_txt += ": level "+level.name
1062 entities.push(button_wall)
1063 entities.push(button_size)
1064 entities.push(new TestButton(self))
1065 entities.push(new SaveButton(self))
1066 entities.push(new LoadButton(self))
1070 # Launch the title screen
1074 entities.push(new Splash(self))
1078 # Helper function to initialize the menu (and tile) screen
1083 var i = levels.first
1086 if l.get_state == l.l_open then break
1088 entities.push(new StartButton(self, i))
1096 t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
1098 for i in [0..levels.length[ do
1099 var b = new LevelButton(levels[i])
1102 t = new Achievement(self, 0, "Training")
1104 t = new Achievement(self, 1, "Par")
1106 t = new Achievement(self, 2, "Editor")
1108 t = new Achievement(self, 3, "Challenge")
1110 t = new Achievement(self, 4, "Congraturation")
1112 t = new Achievement(self, 5, "Awesome")
1117 # Last function called when the lauch state is ready
1130 # Should all entity redrawn?
1131 var dirty_all = true
1133 # Draw all game entities.
1134 fun draw(display: Display) do
1136 if dirty_all then display.blit(back, 0, 0)
1137 for g in entities do
1138 if g.dirty or dirty_all then
1140 #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)
1142 #ctx.rect(g.x, g.y, g.width, g.height)
1146 if ev isa Event then
1147 display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
1152 # Update all game entities.
1154 if solver != null and not solver_pause then
1155 if solver.run_steps(solver_steps) != null then solver_pause = true
1157 if not solver.is_running then solver = null
1159 for g in entities do
1164 # Return the game entity located at a mouse event.
1165 fun get_game_element(ev: Event): nullable Entity
1169 for g in entities do
1170 if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
1180 # The game entlty the mouse went down on
1181 var drag: nullable Entity = null
1183 # Last mouse event. Used to dray the cursor
1184 var lastev: nullable Event = null
1186 # Callback when the mouse is pressed
1187 fun onMouseDown(ev: Event) do
1189 var g = get_game_element(ev)
1197 # Callback when the mouse is releassed
1198 fun onMouseUp(ev: Event) do
1202 # Callback when the mouse if moved while pressed
1203 fun onMouseMove(ev: Event) do
1205 var g = get_game_element(ev)
1207 statusbar.dirty = true
1208 statusbar.over_txt = null
1209 statusbar.over_what = null
1212 if statusbar.over_what != g then
1213 statusbar.dirty = true
1215 statusbar.over_txt = go
1216 statusbar.over_what = g
1218 if go != null then print "***OVER*** {go}"
1220 # We moved abode a element that accepts drag event
1221 if drag == g and g.draggable then
1229 # Current solver, if any
1230 var solver: nullable BacktrackSolver[Grid, Action] = null
1232 # Is the solver paused?
1233 var solver_pause = false
1235 # Number of solver steps played in a single game `update`
1236 var solver_steps = 20000
1238 # Callback when a keyboard event is recieved
1239 fun onKeyDown(ev: Event) do
1240 var kc = ev.char_code
1242 grid_edit = grid.copy(true)
1244 else if kc == "s" then
1245 if solver == null then
1246 solver = (new FriendzProblem(grid)).solve
1247 solver_pause = false
1249 solver_pause = not solver_pause
1252 else if kc == "d" then
1253 if solver == null then
1254 solver = (new FriendzProblem(grid)).solve
1259 else if kc == "+" then
1262 else if kc == "-" then
1265 else for g in entities do
1266 if kc == g.shortcut then
1273 redef fun load_levels
1277 for level in levels do
1278 var score = app.data_store["s{level.str.md5}"]
1279 if score isa Int then
1286 # The spash title image
1291 super(game,game.xpad,game.ypad,380,350)
1295 ctx.blit(game.logo, game.xpad, game.ypad)
1299 game.snd_whip.replay
1304 class NextLevelButton
1308 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1314 var w = game.level.check_won(game.grid)
1315 if self.enabled != w then
1320 game.statusbar.set_tmp("Level solved!", "cyan")
1327 if not self.enabled then
1329 var grid = game.grid
1330 var monsters = grid.monsters
1331 var angry = new Array[Tile]
1332 var lonely = new Array[Tile]
1333 var edges = new Array[Tile]
1334 for i in [0..grid.width[ do
1335 for j in [0..grid.height[ do
1336 var t = grid.grid[i][j]
1337 if t.kind == 0 then continue
1338 if t.nexts == 0 then lonely.push(t)
1339 if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
1340 if t.nexts > 2 then angry.push(t)
1345 if angry.length>0 then
1347 else if lonely.length>0 then
1352 for i in l do i.shocked=5
1354 if angry.length>0 then
1355 game.statusbar.set_tmp("Angry monsters!", "red")
1356 else if lonely.length>0 then
1357 game.statusbar.set_tmp("Lonely monsters!", "red")
1358 else if not grid.won then
1359 game.statusbar.set_tmp("Unconnected monsters!", "red")
1361 game.statusbar.set_tmp("Not enough monsters!", "red")
1366 game.snd_whip.replay
1375 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1377 redef fun click2(ev)
1379 game.music_muted = self.toggled
1380 if game.music_muted then game.music.pause else game.music.play
1381 #game.save_cookie("music_muted",music_muted?"true":"")
1389 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1392 redef fun click2(ev)
1394 game.sfx_muted = self.toggled
1395 if not game.sfx_muted then game.snd_whip.replay # Because the automatic one was muted
1396 #save_cookie("sfx_muted",sfx_muted?"true":"")
1404 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1408 redef fun click2(ev)
1418 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1423 redef fun click2(ev)
1426 if self.count==1 then
1427 game.statusbar.set_tmp("Click again to reset","white")
1428 else if self.count==2 then
1429 game.grid.reset(false)
1430 if game.editing then
1431 game.statusbar.set_tmp("Click again to clear all","white")
1433 else if game.editing then
1434 game.grid.reset(true)
1436 game.dirty_all = true
1449 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1452 redef fun click2(ev)
1454 var ge = game.grid_edit
1464 super(game,"WON", 440, 24, "cyan", "", null)
1467 redef fun click2(ev)
1469 var ge = game.grid_edit
1470 if not self.enabled then
1471 game.statusbar.set_tmp("Solve the level first!", "red")
1472 else if ge != null then
1473 game.snd_whip.replay
1476 game.snd_whip.replay
1483 var w = game.grid.won
1484 if self.enabled != w then
1489 game.statusbar.set_tmp("Level solved!", "cyan")
1499 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1502 redef fun click2(ev)
1504 game.grid_edit = game.grid
1505 game.play_grid(game.grid.copy(false))
1513 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1516 redef fun click2(ev)
1518 var res = game.grid.save
1527 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1530 redef fun click2(ev)
1532 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1533 if grid2.load("") then
1536 game.dirty_all = true
1540 class ContinueButton
1544 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1547 redef fun click2(ev)
1556 init(game: Game, level: Level)
1559 if level.number == 0 then
1560 super(game,"START", 440, 24, "purple", "Play the first level", null)
1562 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1566 redef fun click2(ev)
1579 # Wanted screen width
1580 var screen_width = 640
1582 # Wanted screen height
1583 var screen_height = 480
1585 redef fun window_created
1589 game.font.hspace = -2
1590 if args.length > 0 then
1591 game.play(game.levels[args.first.to_i])
1596 # Maximum wanted frame per second
1599 # clock used to track FPS
1600 private var clock = new Clock
1602 redef fun frame_core(display)
1606 var dt = clock.lapse
1607 var target_dt = 1000000000 / max_fps
1608 if dt.sec == 0 and dt.nanosec < target_dt then
1609 var sleep_t = target_dt - dt.nanosec
1610 sys.nanosleep(0, sleep_t)
1614 redef fun input(input_event)
1617 if input_event isa QuitEvent then # close window button
1618 quit = true # orders system to quit
1619 else if input_event isa PointerEvent then
1620 var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
1621 if input_event.is_motion then
1622 game.onMouseMove(ev)
1623 else if input_event.pressed then
1624 game.onMouseDown(ev)
1629 else if input_event isa KeyEvent and input_event.is_down then
1630 var ev = new Event(0, 0, input_event.key_name)
1639 redef class PointerEvent
1640 fun is_motion: Bool do return false
1643 redef class KeyEvent
1644 fun key_name: String
1647 if c != null then return c.to_s
1655 app.data_store["s{str.md5}"] = if score > 0 then score else null