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
or game
.cheated
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)
940 # Cheat mode enabled?
950 if not music_muted then music.play
957 buttons[i] = new MonsterButton(self, i)
961 # Play a level in player mode.
966 init_play_menu(false)
967 if level.status != "" then
968 statusbar.main_txt = level.status
970 statusbar.main_txt = level.fullname
972 var t = new NextLevelButton(self)
977 # Play the next level.
980 play(levels[level.number+1])
984 # Helper function to initialize all states.
985 # Set up buttons for music and SFX.
991 entities.push(new MusicButton(self))
992 entities.push(new SFXButton(self))
993 entities.push(new MenuButton(self))
995 entities.push(statusbar)
998 # Helper function to initialize monster menu entries.
999 fun init_play_menu(full: Bool)
1002 entities.push(board)
1003 entities.push(new ResetButton(self))
1004 entities.push(button_erase)
1005 # Push monster buttons and determine the selected one
1006 var sel: nullable Button = null
1007 for i in [1..monsters] do
1008 if grid.monsters[i].number > 0 or full then
1009 if selected_button == buttons[i] or sel == null then
1012 entities.push(buttons[i])
1015 selected_button = sel
1016 entities.push(score)
1019 # Play a arbitrary grid in try mode.
1020 fun play_grid(g: Grid)
1023 init_play_menu(false)
1024 statusbar.main_txt = "User level"
1025 if grid_edit != null then
1026 entities.push(new EditButton(self))
1028 entities.push(new WonButton(self))
1032 # Launch the editor starting with a grid.
1033 fun edit_grid(g: Grid)
1036 init_play_menu(true)
1038 statusbar.main_txt = "Level editor"
1039 if level != null then statusbar.main_txt += ": level "+level.name
1040 entities.push(button_wall)
1041 entities.push(button_size)
1042 entities.push(new TestButton(self))
1043 entities.push(new SaveButton(self))
1044 entities.push(new LoadButton(self))
1048 # Launch the title screen
1052 entities.push(new Splash(self))
1056 # Helper function to initialize the menu (and tile) screen
1061 var i = levels.first
1064 if l.get_state == l.l_open then break
1066 entities.push(new StartButton(self, i))
1074 t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
1076 for i in [0..levels.length[ do
1077 var b = new LevelButton(levels[i])
1080 t = new Achievement(self, 0, "Training")
1082 t = new Achievement(self, 1, "Gold")
1084 t = new Achievement(self, 2, "Editor")
1086 t = new Achievement(self, 3, "Challenge")
1088 t = new Achievement(self, 4, "Congraturation")
1090 t = new Achievement(self, 5, "Awesome")
1095 # Last function called when the lauch state is ready
1100 # Should all entity redrawn?
1101 var dirty_all = true
1103 # Draw all game entities.
1104 fun draw(display: Display) do
1106 if dirty_all then display.blit(back, 0, 0)
1107 for g in entities do
1108 if g.dirty or dirty_all then
1110 #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)
1112 #ctx.rect(g.x, g.y, g.width, g.height)
1116 if ev isa Event then
1117 # Cursor, kept for debugging
1118 #display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
1123 # Update all game entities.
1125 if solver != null and not solver_pause then
1126 if solver.run_steps(solver_steps) != null then solver_pause = true
1128 if not solver.is_running then solver = null
1130 for g in entities do
1135 # Return the game entity located at a mouse event.
1136 fun get_game_element(ev: Event): nullable Entity
1140 for g in entities do
1141 if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
1151 # The game entlty the mouse went down on
1152 var drag: nullable Entity = null
1154 # Last mouse event. Used to dray the cursor
1155 var lastev: nullable Event = null
1157 # Callback when the mouse is pressed
1158 fun onMouseDown(ev: Event) do
1160 var g = get_game_element(ev)
1168 # Callback when the mouse is releassed
1169 fun onMouseUp(ev: Event) do
1173 # Callback when the mouse if moved while pressed
1174 fun onMouseMove(ev: Event) do
1176 var g = get_game_element(ev)
1178 statusbar.dirty = true
1179 statusbar.over_txt = null
1180 statusbar.over_what = null
1183 if statusbar.over_what != g then
1184 statusbar.dirty = true
1186 statusbar.over_txt = go
1187 statusbar.over_what = g
1189 if go != null then print "***OVER*** {go}"
1191 # We moved abode a element that accepts drag event
1192 if drag == g and g.draggable then
1200 # Current solver, if any
1201 var solver: nullable BacktrackSolver[Grid, Action] = null
1203 # Is the solver paused?
1204 var solver_pause = false
1206 # Number of solver steps played in a single game `update`
1207 var solver_steps = 20000
1209 # Callback when a keyboard event is recieved
1210 fun onKeyDown(ev: Event) do
1211 var kc = ev.char_code
1213 grid_edit = grid.copy(true)
1215 else if kc == "c" then
1223 else if kc == "s" then
1224 if solver == null then
1225 solver = (new FriendzProblem(grid)).solve
1226 solver_pause = false
1228 solver_pause = not solver_pause
1231 else if kc == "d" then
1232 if solver == null then
1233 solver = (new FriendzProblem(grid)).solve
1238 else if kc == "+" then
1241 else if kc == "-" then
1244 else for g in entities do
1245 if kc == g.shortcut then
1252 redef fun load_levels
1256 for level in levels do
1257 var score = app.data_store["s{level.str.md5}"]
1258 if score isa Int then
1265 # The spash title image
1270 super(game,game.xpad,game.ypad,380,350)
1274 ctx.blit(game.logo, game.xpad, game.ypad)
1283 class NextLevelButton
1287 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1293 var w = game.level.check_won(game.grid)
1294 if self.enabled != w then
1299 game.statusbar.set_tmp("Level solved!", "cyan")
1306 if not self.enabled then
1308 var grid = game.grid
1309 var monsters = grid.monsters
1310 var angry = new Array[Tile]
1311 var lonely = new Array[Tile]
1312 var edges = new Array[Tile]
1313 for i in [0..grid.width[ do
1314 for j in [0..grid.height[ do
1315 var t = grid.grid[i][j]
1316 if t.kind == 0 then continue
1317 if t.nexts == 0 then lonely.push(t)
1318 if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
1319 if t.nexts > 2 then angry.push(t)
1324 if angry.length>0 then
1326 else if lonely.length>0 then
1331 for i in l do i.shocked=5
1333 if angry.length>0 then
1334 game.statusbar.set_tmp("Angry monsters!", "red")
1335 else if lonely.length>0 then
1336 game.statusbar.set_tmp("Lonely monsters!", "red")
1337 else if not grid.won then
1338 game.statusbar.set_tmp("Unconnected monsters!", "red")
1340 game.statusbar.set_tmp("Not enough monsters!", "red")
1354 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1355 toggled = game.music_muted
1357 redef fun click2(ev)
1359 game.music_muted = self.toggled
1360 if game.music_muted then game.music.pause else game.music.play
1361 app.data_store["music_muted"] = game.music_muted
1369 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1370 toggled = game.sfx_muted
1373 redef fun click2(ev)
1375 game.sfx_muted = self.toggled
1376 if not game.sfx_muted then game.snd_whip.play # Because the automatic one was muted
1377 app.data_store["sfx_muted"] = game.sfx_muted
1385 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1389 redef fun click2(ev)
1399 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1404 redef fun click2(ev)
1407 if self.count==1 then
1408 game.statusbar.set_tmp("Click again to reset","white")
1409 else if self.count==2 then
1410 game.grid.reset(false)
1411 if game.editing then
1412 game.statusbar.set_tmp("Click again to clear all","white")
1414 else if game.editing then
1415 game.grid.reset(true)
1417 game.dirty_all = true
1430 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1433 redef fun click2(ev)
1435 var ge = game.grid_edit
1445 super(game,"WON", 440, 24, "cyan", "", null)
1448 redef fun click2(ev)
1450 var ge = game.grid_edit
1451 if not self.enabled then
1452 game.statusbar.set_tmp("Solve the level first!", "red")
1453 else if ge != null then
1464 var w = game.grid.won
1465 if self.enabled != w then
1470 game.statusbar.set_tmp("Level solved!", "cyan")
1480 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1483 redef fun click2(ev)
1485 game.grid_edit = game.grid
1486 game.play_grid(game.grid.copy(false))
1494 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1497 redef fun click2(ev)
1499 var res = game.grid.save
1508 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1511 redef fun click2(ev)
1513 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1514 if grid2.load("") then
1517 game.dirty_all = true
1521 class ContinueButton
1525 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1528 redef fun click2(ev)
1537 init(game: Game, level: Level)
1540 if level.number == 0 then
1541 super(game,"START", 440, 24, "purple", "Play the first level", null)
1543 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1547 redef fun click2(ev)
1560 # Wanted screen width
1561 var screen_width = 640
1563 # Wanted screen height
1564 var screen_height = 480
1570 game.font.hspace = -2
1571 if args.length > 0 then
1572 game.play(game.levels[args.first.to_i])
1577 # Maximum wanted frame per second
1580 # clock used to track FPS
1581 private var clock = new Clock
1583 redef fun frame_core(display)
1587 var dt = clock.lapse
1588 var target_dt = 1000000000 / max_fps
1589 if dt.sec == 0 and dt.nanosec < target_dt then
1590 var sleep_t = target_dt - dt.nanosec
1591 sys.nanosleep(0, sleep_t)
1595 redef fun input(input_event)
1598 if input_event isa QuitEvent then # close window button
1599 quit = true # orders system to quit
1600 else if input_event isa PointerEvent then
1601 var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
1602 if input_event.is_motion then
1603 game.onMouseMove(ev)
1604 else if input_event.pressed then
1605 game.onMouseDown(ev)
1610 else if input_event isa KeyEvent and input_event.is_down then
1611 var ev = new Event(0, 0, input_event.key_name)
1620 redef class PointerEvent
1621 fun is_motion: Bool do return false
1624 redef class KeyEvent
1625 fun key_name: String
1628 if c != null then return c.to_s
1636 app.data_store["s{str.md5}"] = if score > 0 then score else null