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
144 if self.toggleable
then
146 if self.toggled
or not self.enabled
then w
= 6 else w
= 7
147 ctx
.blit
(game
.img2
[w
,0], self.x
, self.y
)
150 if self.enabled
then c
= self.color
else c
= "gray"
152 if self.ttl
> 0 then c2
= "rgba(255,255,255,{self.ttl/10})"
153 ctx
.textx
(self.str
, self.textx
, self.y
, self.height
, c
, c2
)
154 self.width
= ctx
.measureText
(self.str
, self.height
)
155 if self.toggleable
then self.width
+= bw
/2 + 4
160 if game
.statusbar
.over_what
!= self and self.ttl
> 0 then
168 if over1
== null then return
169 if not self.enabled
then return
170 game
.snd_click
.replay
176 # Called by `enter` do perform additionnal work if the button is active
177 # Specific button should implement this instead of `enter`
182 if not self.enabled
then
185 if self.toggleable
then
186 self.toggled
= not self.toggled
187 if self.toggled
then self.over
= self.over2
else self.over
= self.over1
188 game
.statusbar
.over_txt
= self.over
195 # Called by `click` do perform additionnal work if the button is active
196 # Specific button should implement this instead of `click`
197 fun click2
(ev
: Event) do end
201 # LEVEL BUTTONS ***********************************************************/
203 # button to play a level in the menu screen
207 # The associated level to play
214 super(l
.game
, (i
%5)*56 + 54, (i
/5)*56 + 55, l
.game
.bw
, l
.game
.bh
)
216 self.over
= self.level
.fullname
217 if self.level
.get_state
>= l
.l_won
then
218 if game
.levels
[9].get_state
>= l
.l_won
then self.over
+= " --- {self.level.score}/{self.level.par}"
219 else if self.level
.get_state
>= l
.l_open
then
220 if game
.levels
[9].get_state
>= l
.l_open
then self.over
+= " --- ?/{self.level.par}"
222 #self.enabled = l.get_state >= l.l_open
228 var s
= self.level
.get_state
229 var ix
= 5 + l
.number
% 2
231 if s
== l
.l_disabled
then
234 else if s
== l
.l_open
then
237 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
241 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
244 ctx
.blit
(game
.img2
[7,0], self.x
+ bw
*5/8, self.y-bh
*1/8)
246 ctx
.textx
(self.level
.name
, self.x
+5, self.y
+5, 24, null, null)
253 game
.play
(self.level
)
256 game
.statusbar
.set_tmp
("Locked level", "red")
262 # ACHIEVEMENTS ************************************************************/
264 # Achievement (monster-like) button in the menu screen
268 # The number of the achievement (0 is first)
271 # The name of the achievement
274 init(game
: Game, i
: Int, name
: String)
276 super(game
, 5*56 + 54, i
*56 + 55, game
.bw
, game
.bh
)
280 var l
= game
.levels
[number
*5+4]
281 enabled
= l
.get_state
>= l
.l_won
282 if self.enabled
then self.over
= name
+ " (unlocked)" else self.over
= name
+ " (locked)"
288 if self.enabled
then w
= 5 else w
= 3
289 ctx
.blit
(game
.img
[w
,self.number
+5], self.x
, self.y
)
294 if not self.enabled
then
296 game
.statusbar
.set_tmp
("Locked achievement!", "red")
303 fun click2
(ev
: Event) do
309 # BOARD (THE GRID) *******************************************************/
311 # The board game element.
316 super(game
, game
.xpad
, game
.ypad
, 8*game
.bw
, 8*game
.bh
)
323 var bwr
= bw
*100/grid
.ratio
324 var bhr
= bh
*100/grid
.ratio
327 if game
.selected_button
== game
.button_size
then
333 self.x
= game
.xpad
+(48*8/2)-w
*bwr
/2
334 self.y
= game
.ypad
+(48*8/2)-h
*bhr
/2
339 var t
= grid
.grid
[i
][j
]
340 var dx
= i
* bwr
+ self.x
341 var dy
= j
* bhr
+ self.y
343 ctx
.blit_scaled
(game
.img
[5,0], dx
, dy
, bwr
, bhr
)
345 ctx
.blit_scaled
(game
.img
[6,0], dx
, dy
, bwr
, bhr
)
348 if t
.shape
!= null and not game
.editing
then
349 #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)
350 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
352 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
356 var m
= grid
.monsters
[t
.kind
]
358 if t
.blink
> 0 then s
= 1
359 if t
.nexts
> 2 then s
= 3
360 if t
.nexts
== 0 then s
= 6
361 if m
.chain
then s
= 5
362 if t
.shocked
>0 then s
= 2
363 ctx
.blit_scaled
(game
.img
[s
,(4+t
.kind
)], dx
, dy
, bwr
, bhr
)
365 #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
368 if game
.selected_button
== game
.button_size
then
370 var x1
= (grid
.width
) * bwr
- bwr
/2 + self.x
372 var y1
= (grid
.height
) * bhr
- bhr
/2 + self.y
373 ctx
.blit_scaled
(game
.img2
[0,0], x0
, y0
, bwr
/2, bhr
/2)
374 ctx
.blit_scaled
(game
.img2
[1,0], x1
, y0
, bwr
/2, bhr
/2)
375 ctx
.blit_scaled
(game
.img2
[1,1], x1
, y1
, bwr
/2, bhr
/2)
376 ctx
.blit_scaled
(game
.img2
[0,1], x0
, y1
, bwr
/2, bhr
/2)
377 ctx
.textx
("{grid.width}x{grid.height}",self.x
+ grid
.width
*bwr
/2,self.y
+grid
.height
*bhr
/2,20,"orange",null)
384 for i
in [0..grid
.width
[ do
385 for j
in [0..grid
.height
[ do
386 var t
= grid
.grid
[i
][j
]
387 if t
.kind
== 0 then continue
392 if t
.shocked
> 0 then
395 else if 100.rand
== 0 then
404 # Uded to filter drag events
405 private var last
: nullable Tile = null
411 if game
.selected_button
== game
.button_size
then r
= 200
412 var x
= ev
.game_x
* r
/ bw
/ 100
413 var y
= ev
.game_y
* r
/ bh
/ 100
414 var t
= grid
.grid
[x
][y
]
416 if ev
.drag
and last
== t
then return
419 if game
.selected_button
!= game
.button_size
and (x
>=grid
.width
or y
>=grid
.height
) then return
421 # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
423 if game
.selected_button
!= null then
424 game
.selected_button
.click_board
(ev
, t
)
429 # BUTTONS *****************************************************************/
431 # A in-game selectable button for monsters or tools
441 # The associated monster tile
442 # >0 for monsters, <=0 for tools
447 ctx
.blit
(game
.img
[self.imgx
, self.imgy
], self.x
, self.y
)
448 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
453 var sel
= game
.selected_button
454 if game
.selected_button
== game
.button_size
then game
.board
.dirty
=true
455 if sel
!= null then sel
.dirty
=true
456 game
.selected_button
= self
457 game
.snd_click
.replay
460 # Current inputed chain
462 private var chain
= new Array[Tile]
464 # Board click. Called when the player click on the board with the button selected.
465 fun click_board
(ev
: Event, t
: Tile)
467 game
.score
.dirty
= true
468 if ev
.drag
and self.kind
>0 and not chain
.is_empty
then
469 if self.chain
.length
>= 2 and self.chain
[1] == t
then
470 var t2
= self.chain
.shift
471 game
.snd_click
.replay
472 if t2
.fixed
and not game
.editing
then return
476 if t
.fixed
and t
.kind
== self.kind
then
477 self.chain
.unshift
(t
)
478 game
.snd_click
.replay
481 if (self.chain
[0].x
- t
.x
).abs
+ (self.chain
[0].y
- t
.y
).abs
!= 1 then return
482 if t
.fixed
and not game
.editing
then
486 if t
.kind
!= 0 and t
.kind
!= self.kind
then
491 self.chain
.unshift
(t
)
492 if t
.kind
== self.kind
then return
493 game
.snd_click
.replay
498 if t
.fixed
and not game
.editing
then
503 if t
.kind
!= self.kind
and not ev
.drag
then
504 game
.buttons
[t
.kind
].click
(ev
)
505 game
.buttons
[t
.kind
].chain
= [t
]
512 if t
.fixed
and game
.editing
and self == game
.button_erase
and t
.kind
== 0 then
514 game
.snd_click
.replay
518 var nkind
= 0 # the new kind
521 if t
.kind
== 0 then return
522 if self.kind
!= 0 and t
.kind
!= self.kind
then
528 else if t
.kind
!= self.kind
then
531 else if t
.kind
!= 0 then
535 if nkind
== t
.kind
then return
536 game
.snd_click
.replay
545 # TTL for the monster being angry
547 # TTL for the monster being happy
549 # TTL for the monster being shocked
551 # TTL for the monster blinking
554 init(game
: Game, i
: Int)
557 var x
= 440 + 58 * ((i-1
).abs
%3)
558 var y
= 150 + (bh
+5) * ((i-1
)/3)
559 super(game
, x
, y
, game
.bw
, game
.bh
)
560 if i
== 0 then return
564 over
= game
.colors
[i
] + " monster ({i})"
565 shortcut
= i
.to_s
# code for 1 trough 9
576 if self.happy
> 0 then
580 if self.shocked
> 0 then
584 if self.blink
> 0 then
587 else if 100.rand
== 0 then
596 if self.angries
>0 then
598 else if self.happy
> 5 then
600 else if self.shocked
> 0 then
602 else if self.blink
> 0 then
605 ctx
.blit
(game
.img
[s
, self.imgy
], self.x
, self.y
)
606 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
614 super(game
, 440, 92, game
.bh
, 22+game
.bh
)
623 # Metal (fixed) button.
628 super(game
, 498, 92, game
.bh
, 20+game
.bh
)
632 over
= "Metal block (q)"
636 private var fixed
= false
638 redef fun click_board
(ev
,t
)
640 if not ev
.drag
then self.fixed
= not t
.fixed
641 if t
.fixed
== self.fixed
then return
643 game
.snd_click
.replay
653 super(game
,556, 92, game
.bh
, 20+game
.bh
)
655 over
= "Resize the grid"
662 var x
= self.x
+ i
*bw
/3
663 var y
= self.y
+ j
*bh
/3
664 ctx
.blit_scaled
(game
.img
[5+(i
+j
)%2,0], x
, y
, bw
/3, bh
/3)
667 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
672 if game
.selected_button
!= game
.button_size
then
675 game
.selected_button
= null
676 game
.board
.dirty
=true
680 redef fun click_board
(evt
, t
)
685 if w
< 3 or h
< 3 then
687 game
.statusbar
.set_tmp
("Too small!", "red")
691 for i
in [0..grid
.width
[ do
692 for j
in [0..grid
.height
[ do
694 var t2
= grid
.grid
[i
][j
]
704 game
.statusbar
.set_tmp
("Monsters on the way!", "red")
707 game
.snd_click
.replay
712 # Inactive area used to display the score
717 super(game
,440,310,199,62)
721 ctx
.textx
("MONSTERS: {game.grid.number}",self.x
,self.y
+1,21,"cyan",null)
722 var level
= game
.level
723 if level
== null then return
724 if level
.get_state
>= level
.l_won
then
725 ctx
.textx
("BEST: {level.score}",self.x
,self.y
+22,21,"pink", null)
727 ctx
.textx
("BEST: -",self.x
,self.y
+22,21,"pink", null)
729 if game
.levels
[9].get_state
>= level
.l_won
then
730 if level
.is_challenge
then
731 ctx
.textx
("GOAL: {level.par}",self.x
,self.y
+44,21,"yellow",null)
733 ctx
.textx
("PAR: {level.par}",self.x
,self.y
+44,21,"yellow",null)
739 # Status bar element.
744 super(game
,24, 440, 418-24, 30)
747 # Permanant text, if any
748 var main_txt
: nullable String = null
750 # Text to display when the cursor if over an entity (`over_what`), if any
751 var over_txt
: nullable String = null
753 # What is the entity for `over_txt`
754 var over_what
: nullable Entity
756 # Text to temporally display, for some game event, if any
757 var tmp_txt
: nullable String = null
759 # time-to-live for the `tmp_txt`
762 # Color used to display `tmp_txt`
763 var tmp_txt_color
: nullable String = null
773 # set a temporary text
774 fun set_tmp
(txt
, color
: String)
776 print
"***STATUS** {txt}"
778 self.tmp_txt_ttl
= 20
779 self.tmp_txt_color
= color
784 var tmp_txt
= self.tmp_txt
785 var over_txt
= self.over_txt
786 var main_txt
= self.main_txt
787 if tmp_txt
!= null and self.tmp_txt_ttl
>0 then
788 ctx
.textx
(tmp_txt
,24,442,24,self.tmp_txt_color
,null)
789 else if over_txt
!= null then
790 ctx
.textx
(over_txt
,24,442,24,"yellow",null)
791 else if main_txt
!= null then
792 ctx
.textx
(main_txt
,24,442,24,"white",null)
798 if self.tmp_txt_ttl
>0 then
805 # ************************************************************************/
820 sys
.system
("aplay assets/{path} &")
826 fun textx
(str
: String, x
, y
, height
: Int, color
, color2
: nullable String)
828 #var w = measureText(str, height)
830 text
(str
.to_upper
, app
.game
.font
, x
, y
)
833 # give the width for a giver text
834 fun measureText
(str
: String, height
: Int): Int
836 var font
= app
.game
.font
837 return str
.length
* (font
.width
+ font
.hspace
.to_i
)
840 # displays a debug rectangle
841 fun rect
(x
,y
,w
,h
:Int)
843 var image
= once app
.load_image
("hitbox.png")
844 blit_scaled
(image
, x
, y
, w
, h
)
848 # Simple basic class for event
861 var char_code
: String
865 # width of a tile, used for most width reference in the game
867 # height a tile, used for most width reference in the game
869 # x-coordinate of the board (padding)
871 # y-coordinate of the board (padding)
877 var img
= new TileSet(app
.load_image
("tiles2.png"),48,48)
879 # Sub tileset (for marks or other)
880 var img2
= new TileSet(app
.load_image
("tiles2.png"),24,24)
883 var back
: Image = app
.load_image
("background.png")
886 var logo
: Image = app
.load_image
("logo.png")
889 var font
= new TileSetFont(app
.load_image
("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
892 fun save_cookie(name, val:String) do
894 var date = new Date()
895 date.setTime(date.getTime()+(days*24*60*60*1000))
896 document.cookie = name+"="+val+"; expires="+date.toGMTString()+"; path=/"
899 fun read_cookie(name:String):String do
901 var ca = document.cookie.split(';')
902 for(var i=0; i<ca.length; i++) do
904 while (c[0]==' ') c = c.substring(1, c.length)
905 if (c.indexOf(key) == 0) return c.substring(key.length)
911 # DISPLAY *****************************************************************
913 # Is the game in editing mode
916 # The selected button, if any
917 var selected_button: nullable Button = null
921 # Is the music muted?
922 var music_muted: Bool = true #read_cookie("music_muted")
924 # Is the sound effects muted?
925 var sfx_muted: Bool = true #read_cookie("sfx_muted")
927 # The background music resource. */
928 var music = new Audio("music.ogg")
931 var snd_click = new Audio("click.wav")
934 var snd_win = new Audio("level.wav")
937 var snd_duh = new Audio("duh.wav")
940 var snd_bing = new Audio("bing.wav")
943 var snd_whip = new Audio("whip.wav")
945 # INPUT ******************************************************************
947 # Current grid edited (if any).
948 var grid_edit: nullable Grid = null
950 # Sequence of current entities
951 var entities = new Array[Entity]
953 # The current statusbar
954 var statusbar = new StatusBar(self)
957 var board = new Board(self)
959 # The current score board
960 var score = new Score(self)
962 # Monster button game elements.
963 var buttons = new Array[MonsterButton]
966 var button_wall = new MetalButton(self)
969 var button_erase = new EraseButton(self)
972 var button_size = new ResizeButton(self)
978 buttons[i] = new MonsterButton(self, i)
982 # Play a level in player mode.
987 init_play_menu(false)
988 if level.status != "" then
989 statusbar.main_txt = level.status
991 statusbar.main_txt = level.fullname
993 var t = new NextLevelButton(self)
998 # Play the next level.
1001 play(levels[level.number+1])
1005 # Helper function to initialize all states.
1006 # Set up buttons for music and SFX.
1012 entities.push(new MusicButton(self))
1013 entities.push(new SFXButton(self))
1014 entities.push(new MenuButton(self))
1016 entities.push(statusbar)
1019 # Helper function to initialize monster menu entries.
1020 fun init_play_menu(full: Bool)
1023 entities.push(board)
1024 entities.push(new ResetButton(self))
1025 entities.push(button_erase)
1026 # Push monster buttons and determine the selected one
1027 var sel: nullable Button = null
1028 for i in [1..monsters] do
1029 if grid.monsters[i].number > 0 or full then
1030 if selected_button == buttons[i] or sel == null then
1033 entities.push(buttons[i])
1036 selected_button = sel
1037 entities.push(score)
1040 # Play a arbitrary grid in try mode.
1041 fun play_grid(g: Grid)
1044 init_play_menu(false)
1045 statusbar.main_txt = "User level"
1046 if grid_edit != null then
1047 entities.push(new EditButton(self))
1049 entities.push(new WonButton(self))
1053 # Launch the editor starting with a grid.
1054 fun edit_grid(g: Grid)
1057 init_play_menu(true)
1059 statusbar.main_txt = "Level editor"
1060 if level != null then statusbar.main_txt += ": level "+level.name
1061 entities.push(button_wall)
1062 entities.push(button_size)
1063 entities.push(new TestButton(self))
1064 entities.push(new SaveButton(self))
1065 entities.push(new LoadButton(self))
1069 # Launch the title screen
1073 entities.push(new Splash(self))
1077 # Helper function to initialize the menu (and tile) screen
1082 var i = levels.first
1084 if l.get_state == l.l_open then break
1087 entities.push(new StartButton(self, i))
1095 t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
1097 for i in [0..levels.length[ do
1098 var b = new LevelButton(levels[i])
1101 t = new Achievement(self, 0, "Training")
1103 t = new Achievement(self, 1, "Par")
1105 t = new Achievement(self, 2, "Editor")
1107 t = new Achievement(self, 3, "Challenge")
1109 t = new Achievement(self, 4, "Congraturation")
1111 t = new Achievement(self, 5, "Awesome")
1116 # Last function called when the lauch state is ready
1129 # Should all entity redrawn?
1130 var dirty_all = true
1132 # Draw all game entities.
1133 fun draw(display: Display) do
1135 if dirty_all then display.blit(back, 0, 0)
1136 for g in entities do
1137 if g.dirty or dirty_all then
1139 #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)
1141 #ctx.rect(g.x, g.y, g.width, g.height)
1145 if ev isa Event then
1146 display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
1151 # Update all game entities.
1153 if solver != null and not solver_pause then
1154 if solver.run_steps(solver_steps) != null then solver_pause = true
1156 if not solver.is_running then solver = null
1158 for g in entities do
1163 # Return the game entity located at a mouse event.
1164 fun get_game_element(ev: Event): nullable Entity
1168 for g in entities do
1169 if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
1179 # The game entlty the mouse went down on
1180 var drag: nullable Entity = null
1182 # Last mouse event. Used to dray the cursor
1183 var lastev: nullable Event = null
1185 # Callback when the mouse is pressed
1186 fun onMouseDown(ev: Event) do
1188 var g = get_game_element(ev)
1196 # Callback when the mouse is releassed
1197 fun onMouseUp(ev: Event) do
1201 # Callback when the mouse if moved while pressed
1202 fun onMouseMove(ev: Event) do
1204 var g = get_game_element(ev)
1206 statusbar.dirty = true
1207 statusbar.over_txt = null
1208 statusbar.over_what = null
1211 if statusbar.over_what != g then
1212 statusbar.dirty = true
1214 statusbar.over_txt = go
1215 statusbar.over_what = g
1217 if go != null then print "***OVER*** {go}"
1219 # We moved abode a element that accepts drag event
1220 if drag == g and g.draggable then
1228 # Current solver, if any
1229 var solver: nullable BacktrackSolver[Grid, Action] = null
1231 # Is the solver paused?
1232 var solver_pause = false
1234 # Number of solver steps played in a single game `update`
1235 var solver_steps = 20000
1237 # Callback when a keyboard event is recieved
1238 fun onKeyDown(ev: Event) do
1239 var kc = ev.char_code
1241 grid_edit = grid.copy(true)
1243 else if kc == "s" then
1244 if solver == null then
1245 solver = (new FriendzProblem(grid)).solve
1246 solver_pause = false
1248 solver_pause = not solver_pause
1251 else if kc == "d" then
1252 if solver == null then
1253 solver = (new FriendzProblem(grid)).solve
1258 else if kc == "+" then
1261 else if kc == "-" then
1264 else for g in entities do
1265 if kc == g.shortcut then
1273 # The spash title image
1278 super(game,game.xpad,game.ypad,380,350)
1282 ctx.blit(game.logo, game.xpad, game.ypad)
1286 game.snd_whip.replay
1291 class NextLevelButton
1295 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1301 var w = game.level.check_won(game.grid)
1302 if self.enabled != w then
1307 game.statusbar.set_tmp("Level solved!", "cyan")
1314 if not self.enabled then
1316 var grid = game.grid
1317 var monsters = grid.monsters
1318 var angry = new Array[Tile]
1319 var lonely = new Array[Tile]
1320 var edges = new Array[Tile]
1321 for i in [0..grid.width[ do
1322 for j in [0..grid.height[ do
1323 var t = grid.grid[i][j]
1324 if t.kind == 0 then continue
1325 if t.nexts == 0 then lonely.push(t)
1326 if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
1327 if t.nexts > 2 then angry.push(t)
1332 if angry.length>0 then
1334 else if lonely.length>0 then
1339 for i in l do i.shocked=5
1341 if angry.length>0 then
1342 game.statusbar.set_tmp("Angry monsters!", "red")
1343 else if lonely.length>0 then
1344 game.statusbar.set_tmp("Lonely monsters!", "red")
1345 else if not grid.won then
1346 game.statusbar.set_tmp("Unconnected monsters!", "red")
1348 game.statusbar.set_tmp("Not enough monsters!", "red")
1353 game.snd_whip.replay
1362 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1364 redef fun click2(ev)
1366 game.music_muted = self.toggled
1367 if game.music_muted then game.music.pause else game.music.play
1368 #game.save_cookie("music_muted",music_muted?"true":"")
1376 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1379 redef fun click2(ev)
1381 game.sfx_muted = self.toggled
1382 if not game.sfx_muted then game.snd_whip.replay # Because the automatic one was muted
1383 #save_cookie("sfx_muted",sfx_muted?"true":"")
1391 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1395 redef fun click2(ev)
1405 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1410 redef fun click2(ev)
1413 if self.count==1 then
1414 game.statusbar.set_tmp("Click again to reset","white")
1415 else if self.count==2 then
1416 game.grid.reset(false)
1417 if game.editing then
1418 game.statusbar.set_tmp("Click again to clear all","white")
1420 else if game.editing then
1421 game.grid.reset(true)
1423 game.dirty_all = true
1436 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1439 redef fun click2(ev)
1441 var ge = game.grid_edit
1451 super(game,"WON", 440, 24, "cyan", "", null)
1454 redef fun click2(ev)
1456 var ge = game.grid_edit
1457 if not self.enabled then
1458 game.statusbar.set_tmp("Solve the level first!", "red")
1459 else if ge != null then
1460 game.snd_whip.replay
1463 game.snd_whip.replay
1470 var w = game.grid.won
1471 if self.enabled != w then
1476 game.statusbar.set_tmp("Level solved!", "cyan")
1486 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1489 redef fun click2(ev)
1491 game.grid_edit = game.grid
1492 game.play_grid(game.grid.copy(false))
1500 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1503 redef fun click2(ev)
1505 var res = game.grid.save
1514 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1517 redef fun click2(ev)
1519 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1520 if grid2.load("") then
1523 game.dirty_all = true
1527 class ContinueButton
1531 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1534 redef fun click2(ev)
1543 init(game: Game, level: Level)
1546 if level.number == 0 then
1547 super(game,"START", 440, 24, "purple", "Play the first level", null)
1549 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1553 redef fun click2(ev)
1566 # Wanted screen width
1567 var screen_width = 640
1569 # Wanted screen height
1570 var screen_height = 480
1572 redef fun window_created
1576 game.font.hspace = -2
1577 if args.length > 0 then
1578 game.play(game.levels[args.first.to_i])
1583 # Maximum wanted frame per second
1586 # clock used to track FPS
1587 private var clock = new Clock
1589 redef fun frame_core(display)
1593 var dt = clock.lapse
1594 var target_dt = 1000000000 / max_fps
1595 if dt.sec == 0 and dt.nanosec < target_dt then
1596 var sleep_t = target_dt - dt.nanosec
1597 sys.nanosleep(0, sleep_t)
1601 redef fun input(input_event)
1604 if input_event isa QuitEvent then # close window button
1605 quit = true # orders system to quit
1606 else if input_event isa PointerEvent then
1607 var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
1608 if input_event.is_motion then
1609 game.onMouseMove(ev)
1610 else if input_event.pressed then
1611 game.onMouseDown(ev)
1616 else if input_event isa KeyEvent and input_event.is_down then
1617 var ev = new Event(0, 0, input_event.key_name)
1626 redef class PointerEvent
1627 fun is_motion: Bool do return false
1630 redef class KeyEvent
1631 fun key_name: String
1634 if c != null then return c.to_s