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
)
21 import app
::data_store
29 # higher means more dense grid
32 # Various grid sizes from large to small (16x16, 12x12, 8x8, 6x6)
33 var ratios
: Array[Int] = [200, 150, 100, 75]
39 if w
*100/r
<= 8 and h
*100/r
<= 8 then self.ratio
= r
44 # * ENTITIES ****************************************************************
46 # A game entity is something that is displayed and may interact with the player.
51 # X left coordinate (in pixel).
54 # Y top coordinate (in pixel).
57 # X right coordinate (in pixel).
58 fun x2
: Int do return x
+ width
60 # Y bottom coordinate (in pixel).
61 fun y2
: Int do return y
+ height
69 # Tool tip text (if any)
70 var over
: nullable String = null
72 # can the entity intercepts drag ang drop events?
75 # Draw function. To implement
76 fun draw
(ctx
: Display) do end
78 # Update function. Called each loop. To implement
81 # Enter function. Called when the cursor enter in the element. To implement
82 fun enter
(ev
: Event) do end
84 # Click function. Called when the player click in the element.
85 # (or activate it with a shortcut).
86 fun click
(ev
: Event) do end
88 # keyboard shortcut do activate the entity, if any
89 var shortcut
: nullable String = null
91 # Are events received?
94 fun bw
: Int do return game
.bw
95 fun bh
: Int do return game
.bh
97 # Should the entity be redrawn
101 # TEXT BUTTONS ***********************************************************/
103 # Button entity displayed as a simple text.
104 # if `over1` is null, then the button is a simple pasive label
105 # if `over1` is set but `over2` is null, then the button is a normal button
106 # if both `over1` and `over2` arew set, then the button is a toggleable button with two states
110 init(game
: Game, str
: String, x
,y
: Int, color
: nullable String, over
, over2
: nullable String)
113 super(game
, x
,y
,w
,24)
115 self.color
= color
or else "purple"
120 if self.toggleable
then
127 # The description of the button action
128 var over1
: nullable String
129 # The description of the state2 button action
130 var over2
: nullable String
132 # is the button a two-state button
133 fun toggleable
: Bool do return over2
!= null
135 # is the toggleable button in its second state?
138 # ttl for highlighting
141 # position of the start of the text
142 # in a toggleable button, there is space for the mark between `x` and `textx`
145 redef fun draw
(ctx
) do
146 if self.toggleable
then
148 if self.toggled
or not self.enabled
then w
= 6 else w
= 7
149 ctx
.blit
(game
.img2
[w
,0], self.x
, self.y
)
152 if self.enabled
then c
= self.color
else c
= "gray"
154 if self.ttl
> 0 then c2
= "rgba(255,255,255,{self.ttl/10})"
155 ctx
.textx
(self.str
, self.textx
, self.y
, self.height
, c
, c2
)
156 self.width
= ctx
.measureText
(self.str
, self.height
)
157 if self.toggleable
then self.width
+= bw
/2 + 4
162 if game
.statusbar
.over_what
!= self and self.ttl
> 0 then
170 if over1
== null then return
171 if not self.enabled
then return
178 # Called by `enter` do perform additionnal work if the button is active
179 # Specific button should implement this instead of `enter`
184 if not self.enabled
then
187 if self.toggleable
then
188 self.toggled
= not self.toggled
189 if self.toggled
then self.over
= self.over2
else self.over
= self.over1
190 game
.statusbar
.over_txt
= self.over
197 # Called by `click` do perform additionnal work if the button is active
198 # Specific button should implement this instead of `click`
199 fun click2
(ev
: Event) do end
203 # LEVEL BUTTONS ***********************************************************/
205 # button to play a level in the menu screen
209 # The associated level to play
216 super(l
.game
, (i
%5)*56 + 54, (i
/5)*56 + 55, l
.game
.bw
, l
.game
.bh
)
218 self.over
= self.level
.fullname
219 if self.level
.get_state
>= l
.l_won
then
220 if game
.levels
[9].get_state
>= l
.l_won
then self.over
+= " --- {self.level.score}/{self.level.gold}"
221 else if self.level
.get_state
>= l
.l_open
then
222 if game
.levels
[9].get_state
>= l
.l_open
then self.over
+= " --- ?/{self.level.gold}"
224 #self.enabled = l.get_state >= l.l_open
230 var s
= self.level
.get_state
231 var ix
= 5 + l
.number
% 2
233 if s
== l
.l_disabled
then
236 else if s
== l
.l_open
then
239 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
243 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
245 if s
== l
.l_gold
then
246 ctx
.blit
(game
.img2
[7,0], self.x
+ bw
*5/8, self.y-bh
*1/8)
248 ctx
.textx
(self.level
.name
, self.x
+5, self.y
+5, 24, null, null)
255 game
.play
(self.level
)
258 game
.statusbar
.set_tmp
("Locked level", "red")
264 # ACHIEVEMENTS ************************************************************/
266 # Achievement (monster-like) button in the menu screen
270 # The number of the achievement (0 is first)
273 # The name of the achievement
276 init(game
: Game, i
: Int, name
: String)
278 super(game
, 5*56 + 54, i
*56 + 55, game
.bw
, game
.bh
)
282 var l
= game
.levels
[number
*5+4]
283 enabled
= l
.get_state
>= l
.l_won
284 if self.enabled
then self.over
= name
+ " (unlocked)" else self.over
= name
+ " (locked)"
290 if self.enabled
then w
= 5 else w
= 3
291 ctx
.blit
(game
.img
[w
,self.number
+5], self.x
, self.y
)
296 if not self.enabled
then
298 game
.statusbar
.set_tmp
("Locked achievement!", "red")
305 fun click2
(ev
: Event) do
311 # BOARD (THE GRID) *******************************************************/
313 # The board game element.
318 super(game
, game
.xpad
, game
.ypad
, 8*game
.bw
, 8*game
.bh
)
325 var bwr
= bw
*100/grid
.ratio
326 var bhr
= bh
*100/grid
.ratio
329 if game
.selected_button
== game
.button_size
then
335 self.x
= game
.xpad
+(48*8/2)-w
*bwr
/2
336 self.y
= game
.ypad
+(48*8/2)-h
*bhr
/2
341 var t
= grid
.grid
[i
][j
]
342 var dx
= i
* bwr
+ self.x
343 var dy
= j
* bhr
+ self.y
345 ctx
.blit_scaled
(game
.img
[5,0], dx
, dy
, bwr
, bhr
)
347 ctx
.blit_scaled
(game
.img
[6,0], dx
, dy
, bwr
, bhr
)
350 if t
.shape
!= null and not game
.editing
then
351 #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)
352 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
354 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
358 var m
= grid
.monsters
[t
.kind
]
360 if t
.blink
> 0 then s
= 1
361 if t
.nexts
> 2 then s
= 3
362 if t
.nexts
== 0 then s
= 6
363 if m
.chain
then s
= 5
364 if t
.shocked
>0 then s
= 2
365 ctx
.blit_scaled
(game
.img
[s
,(4+t
.kind
)], dx
, dy
, bwr
, bhr
)
367 #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
370 if game
.selected_button
== game
.button_size
then
372 var x1
= (grid
.width
) * bwr
- bwr
/2 + self.x
374 var y1
= (grid
.height
) * bhr
- bhr
/2 + self.y
375 ctx
.blit_scaled
(game
.img2
[0,0], x0
, y0
, bwr
/2, bhr
/2)
376 ctx
.blit_scaled
(game
.img2
[1,0], x1
, y0
, bwr
/2, bhr
/2)
377 ctx
.blit_scaled
(game
.img2
[1,1], x1
, y1
, bwr
/2, bhr
/2)
378 ctx
.blit_scaled
(game
.img2
[0,1], x0
, y1
, bwr
/2, bhr
/2)
379 ctx
.textx
("{grid.width}x{grid.height}",self.x
+ grid
.width
*bwr
/2,self.y
+grid
.height
*bhr
/2,20,"orange",null)
386 for i
in [0..grid
.width
[ do
387 for j
in [0..grid
.height
[ do
388 var t
= grid
.grid
[i
][j
]
389 if t
.kind
== 0 then continue
394 if t
.shocked
> 0 then
397 else if 100.rand
== 0 then
406 # Uded to filter drag events
407 private var last
: nullable Tile = null
413 if game
.selected_button
== game
.button_size
then r
= 200
414 var x
= ev
.game_x
* r
/ bw
/ 100
415 var y
= ev
.game_y
* r
/ bh
/ 100
416 var t
= grid
.grid
[x
][y
]
418 if ev
.drag
and last
== t
then return
421 if game
.selected_button
!= game
.button_size
and (x
>=grid
.width
or y
>=grid
.height
) then return
423 # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
425 if game
.selected_button
!= null then
426 game
.selected_button
.click_board
(ev
, t
)
431 # BUTTONS *****************************************************************/
433 # A in-game selectable button for monsters or tools
443 # The associated monster tile
444 # >0 for monsters, <=0 for tools
449 ctx
.blit
(game
.img
[self.imgx
, self.imgy
], self.x
, self.y
)
450 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
455 var sel
= game
.selected_button
456 if game
.selected_button
== game
.button_size
then game
.board
.dirty
=true
457 if sel
!= null then sel
.dirty
=true
458 game
.selected_button
= self
462 # Current inputed chain
464 private var chain
= new Array[Tile]
466 # Board click. Called when the player click on the board with the button selected.
467 fun click_board
(ev
: Event, t
: Tile)
469 game
.score
.dirty
= true
470 if ev
.drag
and self.kind
>0 and not chain
.is_empty
then
471 if self.chain
.length
>= 2 and self.chain
[1] == t
then
472 var t2
= self.chain
.shift
474 if t2
.fixed
and not game
.editing
then return
478 if t
.fixed
and t
.kind
== self.kind
then
479 self.chain
.unshift
(t
)
483 if (self.chain
[0].x
- t
.x
).abs
+ (self.chain
[0].y
- t
.y
).abs
!= 1 then return
484 if t
.fixed
and not game
.editing
then
488 if t
.kind
!= 0 and t
.kind
!= self.kind
then
493 self.chain
.unshift
(t
)
494 if t
.kind
== self.kind
then return
500 if t
.fixed
and not game
.editing
then
505 if t
.kind
!= self.kind
and not ev
.drag
then
506 game
.buttons
[t
.kind
].click
(ev
)
507 game
.buttons
[t
.kind
].chain
= [t
]
514 if t
.fixed
and game
.editing
and self == game
.button_erase
and t
.kind
== 0 then
520 var nkind
= 0 # the new kind
523 if t
.kind
== 0 then return
524 if self.kind
!= 0 and t
.kind
!= self.kind
then
530 else if t
.kind
!= self.kind
then
533 else if t
.kind
!= 0 then
537 if nkind
== t
.kind
then return
547 # TTL for the monster being angry
549 # TTL for the monster being happy
551 # TTL for the monster being shocked
553 # TTL for the monster blinking
556 init(game
: Game, i
: Int)
559 var x
= 440 + 58 * ((i-1
).abs
%3)
560 var y
= 150 + (bh
+5) * ((i-1
)/3)
561 super(game
, x
, y
, game
.bw
, game
.bh
)
562 if i
== 0 then return
566 over
= game
.colors
[i
] + " monster ({i})"
567 shortcut
= i
.to_s
# code for 1 trough 9
578 if self.happy
> 0 then
582 if self.shocked
> 0 then
586 if self.blink
> 0 then
589 else if 100.rand
== 0 then
598 if self.angries
>0 then
600 else if self.happy
> 5 then
602 else if self.shocked
> 0 then
604 else if self.blink
> 0 then
607 ctx
.blit
(game
.img
[s
, self.imgy
], self.x
, self.y
)
608 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
616 super(game
, 440, 92, game
.bh
, 22+game
.bh
)
625 # Metal (fixed) button.
630 super(game
, 498, 92, game
.bh
, 20+game
.bh
)
634 over
= "Metal block (q)"
638 private var fixed
= false
640 redef fun click_board
(ev
,t
)
642 if not ev
.drag
then self.fixed
= not t
.fixed
643 if t
.fixed
== self.fixed
then return
655 super(game
,556, 92, game
.bh
, 20+game
.bh
)
657 over
= "Resize the grid"
664 var x
= self.x
+ i
*bw
/3
665 var y
= self.y
+ j
*bh
/3
666 ctx
.blit_scaled
(game
.img
[5+(i
+j
)%2,0], x
, y
, bw
/3, bh
/3)
669 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
674 if game
.selected_button
!= game
.button_size
then
677 game
.selected_button
= null
678 game
.board
.dirty
=true
682 redef fun click_board
(evt
, t
)
687 if w
< 3 or h
< 3 then
689 game
.statusbar
.set_tmp
("Too small!", "red")
693 for i
in [0..grid
.width
[ do
694 for j
in [0..grid
.height
[ do
696 var t2
= grid
.grid
[i
][j
]
706 game
.statusbar
.set_tmp
("Monsters on the way!", "red")
714 # Inactive area used to display the score
719 super(game
,440,310,199,62)
723 ctx
.textx
("MONSTERS: {game.grid.number}",self.x
,self.y
+1,21,"cyan",null)
724 var level
= game
.level
725 if level
== null then return
726 if level
.get_state
>= level
.l_won
then
727 ctx
.textx
("BEST: {level.score}",self.x
,self.y
+22,21,"pink", null)
729 ctx
.textx
("BEST: -",self.x
,self.y
+22,21,"pink", null)
731 if game
.levels
[9].get_state
>= level
.l_won
then
732 if level
.is_challenge
then
733 ctx
.textx
("GOAL: {level.gold}",self.x
,self.y
+44,21,"yellow",null)
735 ctx
.textx
("GOLD: {level.gold}",self.x
,self.y
+44,21,"yellow",null)
741 # Status bar element.
746 super(game
,24, 440, 418-24, 30)
749 # Permanant text, if any
750 var main_txt
: nullable String = null
752 # Text to display when the cursor if over an entity (`over_what`), if any
753 var over_txt
: nullable String = null
755 # What is the entity for `over_txt`
756 var over_what
: nullable Entity
758 # Text to temporally display, for some game event, if any
759 var tmp_txt
: nullable String = null
761 # time-to-live for the `tmp_txt`
764 # Color used to display `tmp_txt`
765 var tmp_txt_color
: nullable String = null
775 # set a temporary text
776 fun set_tmp
(txt
, color
: String)
778 print
"***STATUS** {txt}"
780 self.tmp_txt_ttl
= 20
781 self.tmp_txt_color
= color
786 var tmp_txt
= self.tmp_txt
787 var over_txt
= self.over_txt
788 var main_txt
= self.main_txt
789 if tmp_txt
!= null and self.tmp_txt_ttl
>0 then
790 ctx
.textx
(tmp_txt
,24,442,24,self.tmp_txt_color
,null)
791 else if over_txt
!= null then
792 ctx
.textx
(over_txt
,24,442,24,"yellow",null)
793 else if main_txt
!= null then
794 ctx
.textx
(main_txt
,24,442,24,"white",null)
800 if self.tmp_txt_ttl
>0 then
807 # ************************************************************************/
811 fun textx
(str
: String, x
, y
, height
: Int, color
, color2
: nullable String)
813 #var w = measureText(str, height)
815 text
(str
.to_upper
, app
.game
.font
, x
, y
)
818 # give the width for a giver text
819 fun measureText
(str
: String, height
: Int): Int
821 var font
= app
.game
.font
822 return str
.length
* (font
.width
+ font
.hspace
.to_i
)
825 # displays a debug rectangle
826 fun rect
(x
,y
,w
,h
:Int)
828 var image
= once app
.load_image
("hitbox.png")
829 blit_scaled
(image
, x
, y
, w
, h
)
833 # Simple basic class for event
846 var char_code
: String
850 # width of a tile, used for most width reference in the game
852 # height a tile, used for most width reference in the game
854 # x-coordinate of the board (padding)
856 # y-coordinate of the board (padding)
862 var img
= new TileSet(app
.load_image
("tiles2.png"),48,48)
864 # Sub tileset (for marks or other)
865 var img2
= new TileSet(app
.load_image
("tiles2.png"),24,24)
868 var back
: Image = app
.load_image
("background.png")
871 var logo
: Image = app
.load_image
("logo.png")
874 var font
= new TileSetFont(app
.load_image
("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
876 # DISPLAY *****************************************************************
878 # Is the game in editing mode
881 # The selected button, if any
882 var selected_button: nullable Button = null
886 # Is the music muted?
887 var music_muted: Bool = app.data_store["music_muted"] == true
889 # Is the sound effects muted?
890 var sfx_muted: Bool = app.data_store["sfx_muted"] == true
892 # The background music resource. */
893 var music = new Music("music.ogg")
896 var snd_click = new Sound("click.wav")
899 var snd_win = new Sound("level.wav")
902 var snd_duh = new Sound("duh.wav")
905 var snd_bing = new Sound("bing.wav")
908 var snd_whip = new Sound("whip.wav")
911 # INPUT ******************************************************************
913 # Current grid edited (if any).
914 var grid_edit: nullable Grid = null
916 # Sequence of current entities
917 var entities = new Array[Entity]
919 # The current statusbar
920 var statusbar = new StatusBar(self)
923 var board = new Board(self)
925 # The current score board
926 var score = new Score(self)
928 # Monster button game elements.
929 var buttons = new Array[MonsterButton]
932 var button_wall = new MetalButton(self)
935 var button_erase = new EraseButton(self)
938 var button_size = new ResizeButton(self)
947 if not music_muted then music.play
954 buttons[i] = new MonsterButton(self, i)
958 # Play a level in player mode.
963 init_play_menu(false)
964 if level.status != "" then
965 statusbar.main_txt = level.status
967 statusbar.main_txt = level.fullname
969 var t = new NextLevelButton(self)
974 # Play the next level.
977 play(levels[level.number+1])
981 # Helper function to initialize all states.
982 # Set up buttons for music and SFX.
988 entities.push(new MusicButton(self))
989 entities.push(new SFXButton(self))
990 entities.push(new MenuButton(self))
992 entities.push(statusbar)
995 # Helper function to initialize monster menu entries.
996 fun init_play_menu(full: Bool)
1000 entities.push(new ResetButton(self))
1001 entities.push(button_erase)
1002 # Push monster buttons and determine the selected one
1003 var sel: nullable Button = null
1004 for i in [1..monsters] do
1005 if grid.monsters[i].number > 0 or full then
1006 if selected_button == buttons[i] or sel == null then
1009 entities.push(buttons[i])
1012 selected_button = sel
1013 entities.push(score)
1016 # Play a arbitrary grid in try mode.
1017 fun play_grid(g: Grid)
1020 init_play_menu(false)
1021 statusbar.main_txt = "User level"
1022 if grid_edit != null then
1023 entities.push(new EditButton(self))
1025 entities.push(new WonButton(self))
1029 # Launch the editor starting with a grid.
1030 fun edit_grid(g: Grid)
1033 init_play_menu(true)
1035 statusbar.main_txt = "Level editor"
1036 if level != null then statusbar.main_txt += ": level "+level.name
1037 entities.push(button_wall)
1038 entities.push(button_size)
1039 entities.push(new TestButton(self))
1040 entities.push(new SaveButton(self))
1041 entities.push(new LoadButton(self))
1045 # Launch the title screen
1049 entities.push(new Splash(self))
1053 # Helper function to initialize the menu (and tile) screen
1058 var i = levels.first
1061 if l.get_state == l.l_open then break
1063 entities.push(new StartButton(self, i))
1071 t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
1073 for i in [0..levels.length[ do
1074 var b = new LevelButton(levels[i])
1077 t = new Achievement(self, 0, "Training")
1079 t = new Achievement(self, 1, "Gold")
1081 t = new Achievement(self, 2, "Editor")
1083 t = new Achievement(self, 3, "Challenge")
1085 t = new Achievement(self, 4, "Congraturation")
1087 t = new Achievement(self, 5, "Awesome")
1092 # Last function called when the lauch state is ready
1097 # Should all entity redrawn?
1098 var dirty_all = true
1100 # Draw all game entities.
1101 fun draw(display: Display) do
1103 if dirty_all then display.blit(back, 0, 0)
1104 for g in entities do
1105 if g.dirty or dirty_all then
1107 #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)
1109 #ctx.rect(g.x, g.y, g.width, g.height)
1113 if ev isa Event then
1114 # Cursor, kept for debugging
1115 #display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
1120 # Update all game entities.
1122 if solver != null and not solver_pause then
1123 if solver.run_steps(solver_steps) != null then solver_pause = true
1125 if not solver.is_running then solver = null
1127 for g in entities do
1132 # Return the game entity located at a mouse event.
1133 fun get_game_element(ev: Event): nullable Entity
1137 for g in entities do
1138 if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
1148 # The game entlty the mouse went down on
1149 var drag: nullable Entity = null
1151 # Last mouse event. Used to dray the cursor
1152 var lastev: nullable Event = null
1154 # Callback when the mouse is pressed
1155 fun onMouseDown(ev: Event) do
1157 var g = get_game_element(ev)
1165 # Callback when the mouse is releassed
1166 fun onMouseUp(ev: Event) do
1170 # Callback when the mouse if moved while pressed
1171 fun onMouseMove(ev: Event) do
1173 var g = get_game_element(ev)
1175 statusbar.dirty = true
1176 statusbar.over_txt = null
1177 statusbar.over_what = null
1180 if statusbar.over_what != g then
1181 statusbar.dirty = true
1183 statusbar.over_txt = go
1184 statusbar.over_what = g
1186 if go != null then print "***OVER*** {go}"
1188 # We moved abode a element that accepts drag event
1189 if drag == g and g.draggable then
1197 # Current solver, if any
1198 var solver: nullable BacktrackSolver[Grid, Action] = null
1200 # Is the solver paused?
1201 var solver_pause = false
1203 # Number of solver steps played in a single game `update`
1204 var solver_steps = 20000
1206 # Callback when a keyboard event is recieved
1207 fun onKeyDown(ev: Event) do
1208 var kc = ev.char_code
1210 grid_edit = grid.copy(true)
1212 else if kc == "s" then
1213 if solver == null then
1214 solver = (new FriendzProblem(grid)).solve
1215 solver_pause = false
1217 solver_pause = not solver_pause
1220 else if kc == "d" then
1221 if solver == null then
1222 solver = (new FriendzProblem(grid)).solve
1227 else if kc == "+" then
1230 else if kc == "-" then
1233 else for g in entities do
1234 if kc == g.shortcut then
1241 redef fun load_levels
1245 for level in levels do
1246 var score = app.data_store["s{level.str.md5}"]
1247 if score isa Int then
1254 # The spash title image
1259 super(game,game.xpad,game.ypad,380,350)
1263 ctx.blit(game.logo, game.xpad, game.ypad)
1272 class NextLevelButton
1276 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1282 var w = game.level.check_won(game.grid)
1283 if self.enabled != w then
1288 game.statusbar.set_tmp("Level solved!", "cyan")
1295 if not self.enabled then
1297 var grid = game.grid
1298 var monsters = grid.monsters
1299 var angry = new Array[Tile]
1300 var lonely = new Array[Tile]
1301 var edges = new Array[Tile]
1302 for i in [0..grid.width[ do
1303 for j in [0..grid.height[ do
1304 var t = grid.grid[i][j]
1305 if t.kind == 0 then continue
1306 if t.nexts == 0 then lonely.push(t)
1307 if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
1308 if t.nexts > 2 then angry.push(t)
1313 if angry.length>0 then
1315 else if lonely.length>0 then
1320 for i in l do i.shocked=5
1322 if angry.length>0 then
1323 game.statusbar.set_tmp("Angry monsters!", "red")
1324 else if lonely.length>0 then
1325 game.statusbar.set_tmp("Lonely monsters!", "red")
1326 else if not grid.won then
1327 game.statusbar.set_tmp("Unconnected monsters!", "red")
1329 game.statusbar.set_tmp("Not enough monsters!", "red")
1343 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1344 toggled = game.music_muted
1346 redef fun click2(ev)
1348 game.music_muted = self.toggled
1349 if game.music_muted then game.music.pause else game.music.play
1350 app.data_store["music_muted"] = game.music_muted
1358 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1359 toggled = game.sfx_muted
1362 redef fun click2(ev)
1364 game.sfx_muted = self.toggled
1365 if not game.sfx_muted then game.snd_whip.play # Because the automatic one was muted
1366 app.data_store["sfx_muted"] = game.sfx_muted
1374 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1378 redef fun click2(ev)
1388 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1393 redef fun click2(ev)
1396 if self.count==1 then
1397 game.statusbar.set_tmp("Click again to reset","white")
1398 else if self.count==2 then
1399 game.grid.reset(false)
1400 if game.editing then
1401 game.statusbar.set_tmp("Click again to clear all","white")
1403 else if game.editing then
1404 game.grid.reset(true)
1406 game.dirty_all = true
1419 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1422 redef fun click2(ev)
1424 var ge = game.grid_edit
1434 super(game,"WON", 440, 24, "cyan", "", null)
1437 redef fun click2(ev)
1439 var ge = game.grid_edit
1440 if not self.enabled then
1441 game.statusbar.set_tmp("Solve the level first!", "red")
1442 else if ge != null then
1453 var w = game.grid.won
1454 if self.enabled != w then
1459 game.statusbar.set_tmp("Level solved!", "cyan")
1469 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1472 redef fun click2(ev)
1474 game.grid_edit = game.grid
1475 game.play_grid(game.grid.copy(false))
1483 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1486 redef fun click2(ev)
1488 var res = game.grid.save
1497 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1500 redef fun click2(ev)
1502 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1503 if grid2.load("") then
1506 game.dirty_all = true
1510 class ContinueButton
1514 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1517 redef fun click2(ev)
1526 init(game: Game, level: Level)
1529 if level.number == 0 then
1530 super(game,"START", 440, 24, "purple", "Play the first level", null)
1532 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1536 redef fun click2(ev)
1549 # Wanted screen width
1550 var screen_width = 640
1552 # Wanted screen height
1553 var screen_height = 480
1559 game.font.hspace = -2
1560 if args.length > 0 then
1561 game.play(game.levels[args.first.to_i])
1566 # Maximum wanted frame per second
1569 # clock used to track FPS
1570 private var clock = new Clock
1572 redef fun frame_core(display)
1576 var dt = clock.lapse
1577 var target_dt = 1000000000 / max_fps
1578 if dt.sec == 0 and dt.nanosec < target_dt then
1579 var sleep_t = target_dt - dt.nanosec
1580 sys.nanosleep(0, sleep_t)
1584 redef fun input(input_event)
1587 if input_event isa QuitEvent then # close window button
1588 quit = true # orders system to quit
1589 else if input_event isa PointerEvent then
1590 var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
1591 if input_event.is_motion then
1592 game.onMouseMove(ev)
1593 else if input_event.pressed then
1594 game.onMouseDown(ev)
1599 else if input_event isa KeyEvent and input_event.is_down then
1600 var ev = new Event(0, 0, input_event.key_name)
1609 redef class PointerEvent
1610 fun is_motion: Bool do return false
1613 redef class KeyEvent
1614 fun key_name: String
1617 if c != null then return c.to_s
1625 app.data_store["s{str.md5}"] = if score > 0 then score else null