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.par}"
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.par}"
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
)
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.par}",self.x
,self.y
+44,21,"yellow",null)
735 ctx
.textx
("PAR: {level.par}",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.:;!?\"'() -,/")
877 fun save_cookie(name, val:String) do
879 var date = new Date()
880 date.setTime(date.getTime()+(days*24*60*60*1000))
881 document.cookie = name+"="+val+"; expires="+date.toGMTString()+"; path=/"
884 fun read_cookie(name:String):String do
886 var ca = document.cookie.split(';')
887 for(var i=0; i<ca.length; i++) do
889 while (c[0]==' ') c = c.substring(1, c.length)
890 if (c.indexOf(key) == 0) return c.substring(key.length)
896 # DISPLAY *****************************************************************
898 # Is the game in editing mode
901 # The selected button, if any
902 var selected_button: nullable Button = null
906 # Is the music muted?
907 var music_muted: Bool = false #read_cookie("music_muted")
909 # Is the sound effects muted?
910 var sfx_muted: Bool = false #read_cookie("sfx_muted")
912 # The background music resource. */
913 var music = new Music("music.ogg")
916 var snd_click = new Sound("click.wav")
919 var snd_win = new Sound("level.wav")
922 var snd_duh = new Sound("duh.wav")
925 var snd_bing = new Sound("bing.wav")
928 var snd_whip = new Sound("whip.wav")
931 # INPUT ******************************************************************
933 # Current grid edited (if any).
934 var grid_edit: nullable Grid = null
936 # Sequence of current entities
937 var entities = new Array[Entity]
939 # The current statusbar
940 var statusbar = new StatusBar(self)
943 var board = new Board(self)
945 # The current score board
946 var score = new Score(self)
948 # Monster button game elements.
949 var buttons = new Array[MonsterButton]
952 var button_wall = new MetalButton(self)
955 var button_erase = new EraseButton(self)
958 var button_size = new ResizeButton(self)
972 buttons[i] = new MonsterButton(self, i)
976 # Play a level in player mode.
981 init_play_menu(false)
982 if level.status != "" then
983 statusbar.main_txt = level.status
985 statusbar.main_txt = level.fullname
987 var t = new NextLevelButton(self)
992 # Play the next level.
995 play(levels[level.number+1])
999 # Helper function to initialize all states.
1000 # Set up buttons for music and SFX.
1006 entities.push(new MusicButton(self))
1007 entities.push(new SFXButton(self))
1008 entities.push(new MenuButton(self))
1010 entities.push(statusbar)
1013 # Helper function to initialize monster menu entries.
1014 fun init_play_menu(full: Bool)
1017 entities.push(board)
1018 entities.push(new ResetButton(self))
1019 entities.push(button_erase)
1020 # Push monster buttons and determine the selected one
1021 var sel: nullable Button = null
1022 for i in [1..monsters] do
1023 if grid.monsters[i].number > 0 or full then
1024 if selected_button == buttons[i] or sel == null then
1027 entities.push(buttons[i])
1030 selected_button = sel
1031 entities.push(score)
1034 # Play a arbitrary grid in try mode.
1035 fun play_grid(g: Grid)
1038 init_play_menu(false)
1039 statusbar.main_txt = "User level"
1040 if grid_edit != null then
1041 entities.push(new EditButton(self))
1043 entities.push(new WonButton(self))
1047 # Launch the editor starting with a grid.
1048 fun edit_grid(g: Grid)
1051 init_play_menu(true)
1053 statusbar.main_txt = "Level editor"
1054 if level != null then statusbar.main_txt += ": level "+level.name
1055 entities.push(button_wall)
1056 entities.push(button_size)
1057 entities.push(new TestButton(self))
1058 entities.push(new SaveButton(self))
1059 entities.push(new LoadButton(self))
1063 # Launch the title screen
1067 entities.push(new Splash(self))
1071 # Helper function to initialize the menu (and tile) screen
1076 var i = levels.first
1079 if l.get_state == l.l_open then break
1081 entities.push(new StartButton(self, i))
1089 t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
1091 for i in [0..levels.length[ do
1092 var b = new LevelButton(levels[i])
1095 t = new Achievement(self, 0, "Training")
1097 t = new Achievement(self, 1, "Par")
1099 t = new Achievement(self, 2, "Editor")
1101 t = new Achievement(self, 3, "Challenge")
1103 t = new Achievement(self, 4, "Congraturation")
1105 t = new Achievement(self, 5, "Awesome")
1110 # Last function called when the lauch state is ready
1115 # Should all entity redrawn?
1116 var dirty_all = true
1118 # Draw all game entities.
1119 fun draw(display: Display) do
1121 if dirty_all then display.blit(back, 0, 0)
1122 for g in entities do
1123 if g.dirty or dirty_all then
1125 #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)
1127 #ctx.rect(g.x, g.y, g.width, g.height)
1131 if ev isa Event then
1132 # Cursor, kept for debugging
1133 #display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
1138 # Update all game entities.
1140 if solver != null and not solver_pause then
1141 if solver.run_steps(solver_steps) != null then solver_pause = true
1143 if not solver.is_running then solver = null
1145 for g in entities do
1150 # Return the game entity located at a mouse event.
1151 fun get_game_element(ev: Event): nullable Entity
1155 for g in entities do
1156 if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
1166 # The game entlty the mouse went down on
1167 var drag: nullable Entity = null
1169 # Last mouse event. Used to dray the cursor
1170 var lastev: nullable Event = null
1172 # Callback when the mouse is pressed
1173 fun onMouseDown(ev: Event) do
1175 var g = get_game_element(ev)
1183 # Callback when the mouse is releassed
1184 fun onMouseUp(ev: Event) do
1188 # Callback when the mouse if moved while pressed
1189 fun onMouseMove(ev: Event) do
1191 var g = get_game_element(ev)
1193 statusbar.dirty = true
1194 statusbar.over_txt = null
1195 statusbar.over_what = null
1198 if statusbar.over_what != g then
1199 statusbar.dirty = true
1201 statusbar.over_txt = go
1202 statusbar.over_what = g
1204 if go != null then print "***OVER*** {go}"
1206 # We moved abode a element that accepts drag event
1207 if drag == g and g.draggable then
1215 # Current solver, if any
1216 var solver: nullable BacktrackSolver[Grid, Action] = null
1218 # Is the solver paused?
1219 var solver_pause = false
1221 # Number of solver steps played in a single game `update`
1222 var solver_steps = 20000
1224 # Callback when a keyboard event is recieved
1225 fun onKeyDown(ev: Event) do
1226 var kc = ev.char_code
1228 grid_edit = grid.copy(true)
1230 else if kc == "s" then
1231 if solver == null then
1232 solver = (new FriendzProblem(grid)).solve
1233 solver_pause = false
1235 solver_pause = not solver_pause
1238 else if kc == "d" then
1239 if solver == null then
1240 solver = (new FriendzProblem(grid)).solve
1245 else if kc == "+" then
1248 else if kc == "-" then
1251 else for g in entities do
1252 if kc == g.shortcut then
1259 redef fun load_levels
1263 for level in levels do
1264 var score = app.data_store["s{level.str.md5}"]
1265 if score isa Int then
1272 # The spash title image
1277 super(game,game.xpad,game.ypad,380,350)
1281 ctx.blit(game.logo, game.xpad, game.ypad)
1290 class NextLevelButton
1294 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1300 var w = game.level.check_won(game.grid)
1301 if self.enabled != w then
1306 game.statusbar.set_tmp("Level solved!", "cyan")
1313 if not self.enabled then
1315 var grid = game.grid
1316 var monsters = grid.monsters
1317 var angry = new Array[Tile]
1318 var lonely = new Array[Tile]
1319 var edges = new Array[Tile]
1320 for i in [0..grid.width[ do
1321 for j in [0..grid.height[ do
1322 var t = grid.grid[i][j]
1323 if t.kind == 0 then continue
1324 if t.nexts == 0 then lonely.push(t)
1325 if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
1326 if t.nexts > 2 then angry.push(t)
1331 if angry.length>0 then
1333 else if lonely.length>0 then
1338 for i in l do i.shocked=5
1340 if angry.length>0 then
1341 game.statusbar.set_tmp("Angry monsters!", "red")
1342 else if lonely.length>0 then
1343 game.statusbar.set_tmp("Lonely monsters!", "red")
1344 else if not grid.won then
1345 game.statusbar.set_tmp("Unconnected monsters!", "red")
1347 game.statusbar.set_tmp("Not enough monsters!", "red")
1361 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1363 redef fun click2(ev)
1365 game.music_muted = self.toggled
1366 if game.music_muted then game.music.pause else game.music.play
1367 #game.save_cookie("music_muted",music_muted?"true":"")
1375 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1378 redef fun click2(ev)
1380 game.sfx_muted = self.toggled
1381 if not game.sfx_muted then game.snd_whip.play # Because the automatic one was muted
1382 #save_cookie("sfx_muted",sfx_muted?"true":"")
1390 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1394 redef fun click2(ev)
1404 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1409 redef fun click2(ev)
1412 if self.count==1 then
1413 game.statusbar.set_tmp("Click again to reset","white")
1414 else if self.count==2 then
1415 game.grid.reset(false)
1416 if game.editing then
1417 game.statusbar.set_tmp("Click again to clear all","white")
1419 else if game.editing then
1420 game.grid.reset(true)
1422 game.dirty_all = true
1435 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1438 redef fun click2(ev)
1440 var ge = game.grid_edit
1450 super(game,"WON", 440, 24, "cyan", "", null)
1453 redef fun click2(ev)
1455 var ge = game.grid_edit
1456 if not self.enabled then
1457 game.statusbar.set_tmp("Solve the level first!", "red")
1458 else if ge != null then
1469 var w = game.grid.won
1470 if self.enabled != w then
1475 game.statusbar.set_tmp("Level solved!", "cyan")
1485 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1488 redef fun click2(ev)
1490 game.grid_edit = game.grid
1491 game.play_grid(game.grid.copy(false))
1499 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1502 redef fun click2(ev)
1504 var res = game.grid.save
1513 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1516 redef fun click2(ev)
1518 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1519 if grid2.load("") then
1522 game.dirty_all = true
1526 class ContinueButton
1530 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1533 redef fun click2(ev)
1542 init(game: Game, level: Level)
1545 if level.number == 0 then
1546 super(game,"START", 440, 24, "purple", "Play the first level", null)
1548 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1552 redef fun click2(ev)
1565 # Wanted screen width
1566 var screen_width = 640
1568 # Wanted screen height
1569 var screen_height = 480
1575 game.font.hspace = -2
1576 if args.length > 0 then
1577 game.play(game.levels[args.first.to_i])
1582 # Maximum wanted frame per second
1585 # clock used to track FPS
1586 private var clock = new Clock
1588 redef fun frame_core(display)
1592 var dt = clock.lapse
1593 var target_dt = 1000000000 / max_fps
1594 if dt.sec == 0 and dt.nanosec < target_dt then
1595 var sleep_t = target_dt - dt.nanosec
1596 sys.nanosleep(0, sleep_t)
1600 redef fun input(input_event)
1603 if input_event isa QuitEvent then # close window button
1604 quit = true # orders system to quit
1605 else if input_event isa PointerEvent then
1606 var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
1607 if input_event.is_motion then
1608 game.onMouseMove(ev)
1609 else if input_event.pressed then
1610 game.onMouseDown(ev)
1615 else if input_event isa KeyEvent and input_event.is_down then
1616 var ev = new Event(0, 0, input_event.key_name)
1625 redef class PointerEvent
1626 fun is_motion: Bool do return false
1629 redef class KeyEvent
1630 fun key_name: String
1633 if c != null then return c.to_s
1641 app.data_store["s{str.md5}"] = if score > 0 then score else null