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
28 # higher means more dense grid
31 # Various grid sizes from large to small (16x16, 12x12, 8x8, 6x6)
32 var ratios
: Array[Int] = [200, 150, 100, 75]
38 if w
*100/r
<= 8 and h
*100/r
<= 8 then self.ratio
= r
43 # * ENTITIES ****************************************************************
45 # A game entity is something that is displayed and may interact with the player.
50 # X left coordinate (in pixel).
53 # Y top coordinate (in pixel).
56 # X right coordinate (in pixel).
57 fun x2
: Int do return x
+ width
59 # Y bottom coordinate (in pixel).
60 fun y2
: Int do return y
+ height
68 # Tool tip text (if any)
69 var over
: nullable String = null
71 # can the entity intercepts drag ang drop events?
74 # Draw function. To implement
75 fun draw
(ctx
: Display) do end
77 # Update function. Called each loop. To implement
80 # Enter function. Called when the cursor enter in the element. To implement
81 fun enter
(ev
: Event) do end
83 # Click function. Called when the player click in the element.
84 # (or activate it with a shortcut).
85 fun click
(ev
: Event) do end
87 # keyboard shortcut do activate the entity, if any
88 var shortcut
: nullable String = null
90 # Are events received?
93 fun bw
: Int do return game
.bw
94 fun bh
: Int do return game
.bh
96 # Should the entity be redrawn
100 # TEXT BUTTONS ***********************************************************/
102 # Button entity displayed as a simple text.
103 # if `over1` is null, then the button is a simple pasive label
104 # if `over1` is set but `over2` is null, then the button is a normal button
105 # if both `over1` and `over2` arew set, then the button is a toggleable button with two states
109 init(game
: Game, str
: String, x
,y
: Int, color
: nullable String, over
, over2
: nullable String)
112 super(game
, x
,y
,w
,24)
114 self.color
= color
or else "purple"
119 if self.toggleable
then
126 # The description of the button action
127 var over1
: nullable String
128 # The description of the state2 button action
129 var over2
: nullable String
131 # is the button a two-state button
132 fun toggleable
: Bool do return over2
!= null
134 # is the toggleable button in its second state?
137 # ttl for highlighting
140 # position of the start of the text
141 # in a toggleable button, there is space for the mark between `x` and `textx`
144 redef fun draw
(ctx
) do
145 if self.toggleable
then
147 if self.toggled
or not self.enabled
then w
= 6 else w
= 7
148 ctx
.blit
(game
.img2
[w
,0], self.x
, self.y
)
151 if self.enabled
then c
= self.color
else c
= "gray"
153 if self.ttl
> 0 then c2
= "rgba(255,255,255,{self.ttl/10})"
154 ctx
.textx
(self.str
, self.textx
, self.y
, self.height
, c
, c2
)
155 self.width
= ctx
.measureText
(self.str
, self.height
)
156 if self.toggleable
then self.width
+= bw
/2 + 4
161 if game
.statusbar
.over_what
!= self and self.ttl
> 0 then
169 if over1
== null then return
170 if not self.enabled
then return
177 # Called by `enter` do perform additionnal work if the button is active
178 # Specific button should implement this instead of `enter`
183 if not self.enabled
then
186 if self.toggleable
then
187 self.toggled
= not self.toggled
188 if self.toggled
then self.over
= self.over2
else self.over
= self.over1
189 game
.statusbar
.over_txt
= self.over
196 # Called by `click` do perform additionnal work if the button is active
197 # Specific button should implement this instead of `click`
198 fun click2
(ev
: Event) do end
202 # LEVEL BUTTONS ***********************************************************/
204 # button to play a level in the menu screen
208 # The associated level to play
215 super(l
.game
, (i
%5)*56 + 54, (i
/5)*56 + 55, l
.game
.bw
, l
.game
.bh
)
217 self.over
= self.level
.fullname
218 if self.level
.get_state
>= l
.l_won
then
219 if game
.levels
[9].get_state
>= l
.l_won
then self.over
+= " --- {self.level.score}/{self.level.gold}"
220 else if self.level
.get_state
>= l
.l_open
then
221 if game
.levels
[9].get_state
>= l
.l_open
then self.over
+= " --- ?/{self.level.gold}"
223 self.enabled
= l
.get_state
>= l
.l_open
or game
.cheated
229 var s
= self.level
.get_state
230 var ix
= 5 + l
.number
% 2
232 if s
== l
.l_disabled
then
235 else if s
== l
.l_open
then
238 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
242 ctx
.blit
(game
.img
[ix
,iy
], self.x
, self.y
)
244 if s
== l
.l_gold
then
245 ctx
.blit
(game
.img2
[7,0], self.x
+ bw
*5/8, self.y-bh
*1/8)
247 ctx
.textx
(self.level
.name
, self.x
+5, self.y
+5, 24, null, null)
254 game
.play
(self.level
)
257 game
.statusbar
.set_tmp
("Locked level", "red")
263 # ACHIEVEMENTS ************************************************************/
265 # Achievement (monster-like) button in the menu screen
269 # The number of the achievement (0 is first)
272 # The name of the achievement
275 init(game
: Game, i
: Int, name
: String)
277 super(game
, 5*56 + 54, i
*56 + 55, game
.bw
, game
.bh
)
281 var l
= game
.levels
[number
*5+4]
282 enabled
= l
.get_state
>= l
.l_won
283 if self.enabled
then self.over
= name
+ " (unlocked)" else self.over
= name
+ " (locked)"
289 if self.enabled
then w
= 5 else w
= 3
290 ctx
.blit
(game
.img
[w
,self.number
+5], self.x
, self.y
)
295 if not self.enabled
then
297 game
.statusbar
.set_tmp
("Locked achievement!", "red")
304 fun click2
(ev
: Event) do
310 # BOARD (THE GRID) *******************************************************/
312 # The board game element.
317 super(game
, game
.xpad
, game
.ypad
, 8*game
.bw
, 8*game
.bh
)
324 var bwr
= bw
*100/grid
.ratio
325 var bhr
= bh
*100/grid
.ratio
328 if game
.selected_button
== game
.button_size
then
334 self.x
= game
.xpad
+(48*8/2)-w
*bwr
/2
335 self.y
= game
.ypad
+(48*8/2)-h
*bhr
/2
340 var t
= grid
.grid
[i
][j
]
341 var dx
= i
* bwr
+ self.x
342 var dy
= j
* bhr
+ self.y
344 ctx
.blit_scaled
(game
.img
[5,0], dx
, dy
, bwr
, bhr
)
346 ctx
.blit_scaled
(game
.img
[6,0], dx
, dy
, bwr
, bhr
)
349 if t
.shape
!= null and not game
.editing
then
350 #ctx.drawImage(game.img, t.shape.x*bw, (2+t.shape.y)*bh, bw, bh, i * bwr + self.x, j * bhr + self.y, bwr, bhr)
351 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
353 ctx
.blit_scaled
(game
.img
[3,3], dx
, dy
, bwr
, bhr
)
357 var m
= grid
.monsters
[t
.kind
]
359 if t
.blink
> 0 then s
= 1
360 if t
.nexts
> 2 then s
= 3
361 if t
.nexts
== 0 then s
= 6
362 if m
.chain
then s
= 5
363 if t
.shocked
>0 then s
= 2
364 ctx
.blit_scaled
(game
.img
[s
,(4+t
.kind
)], dx
, dy
, bwr
, bhr
)
366 #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
369 if game
.selected_button
== game
.button_size
then
371 var x1
= (grid
.width
) * bwr
- bwr
/2 + self.x
373 var y1
= (grid
.height
) * bhr
- bhr
/2 + self.y
374 ctx
.blit_scaled
(game
.img2
[0,0], x0
, y0
, bwr
/2, bhr
/2)
375 ctx
.blit_scaled
(game
.img2
[1,0], x1
, y0
, bwr
/2, bhr
/2)
376 ctx
.blit_scaled
(game
.img2
[1,1], x1
, y1
, bwr
/2, bhr
/2)
377 ctx
.blit_scaled
(game
.img2
[0,1], x0
, y1
, bwr
/2, bhr
/2)
378 ctx
.textx
("{grid.width}x{grid.height}",self.x
+ grid
.width
*bwr
/2,self.y
+grid
.height
*bhr
/2,20,"orange",null)
385 for i
in [0..grid
.width
[ do
386 for j
in [0..grid
.height
[ do
387 var t
= grid
.grid
[i
][j
]
388 if t
.kind
== 0 then continue
393 if t
.shocked
> 0 then
396 else if 100.rand
== 0 then
405 # Uded to filter drag events
406 private var last
: nullable Tile = null
412 if game
.selected_button
== game
.button_size
then r
= 200
413 var x
= ev
.game_x
* r
/ bw
/ 100
414 var y
= ev
.game_y
* r
/ bh
/ 100
415 var t
= grid
.grid
[x
][y
]
417 if ev
.drag
and last
== t
then return
420 if game
.selected_button
!= game
.button_size
and (x
>=grid
.width
or y
>=grid
.height
) then return
422 # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
424 if game
.selected_button
!= null then
425 game
.selected_button
.click_board
(ev
, t
)
430 # BUTTONS *****************************************************************/
432 # A in-game selectable button for monsters or tools
442 # The associated monster tile
443 # >0 for monsters, <=0 for tools
448 ctx
.blit
(game
.img
[self.imgx
, self.imgy
], self.x
, self.y
)
449 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
454 var sel
= game
.selected_button
455 if game
.selected_button
== game
.button_size
then game
.board
.dirty
=true
456 if sel
!= null then sel
.dirty
=true
457 game
.selected_button
= self
461 # Current inputed chain
463 private var chain
= new Array[Tile]
465 # Board click. Called when the player click on the board with the button selected.
466 fun click_board
(ev
: Event, t
: Tile)
468 game
.score
.dirty
= true
469 if ev
.drag
and self.kind
>0 and not chain
.is_empty
then
470 if self.chain
.length
>= 2 and self.chain
[1] == t
then
471 var t2
= self.chain
.shift
473 if t2
.fixed
and not game
.editing
then return
477 if t
.fixed
and t
.kind
== self.kind
then
478 self.chain
.unshift
(t
)
482 if (self.chain
[0].x
- t
.x
).abs
+ (self.chain
[0].y
- t
.y
).abs
!= 1 then return
483 if t
.fixed
and not game
.editing
then
487 if t
.kind
!= 0 and t
.kind
!= self.kind
then
492 self.chain
.unshift
(t
)
493 if t
.kind
== self.kind
then return
499 if t
.fixed
and not game
.editing
then
504 if t
.kind
!= self.kind
and not ev
.drag
then
505 game
.buttons
[t
.kind
].click
(ev
)
506 game
.buttons
[t
.kind
].chain
= [t
]
513 if t
.fixed
and game
.editing
and self == game
.button_erase
and t
.kind
== 0 then
519 var nkind
= 0 # the new kind
522 if t
.kind
== 0 then return
523 if self.kind
!= 0 and t
.kind
!= self.kind
then
529 else if t
.kind
!= self.kind
then
532 else if t
.kind
!= 0 then
536 if nkind
== t
.kind
then return
546 # TTL for the monster being angry
548 # TTL for the monster being happy
550 # TTL for the monster being shocked
552 # TTL for the monster blinking
555 init(game
: Game, i
: Int)
558 var x
= 440 + 58 * ((i-1
).abs
%3)
559 var y
= 150 + (bh
+5) * ((i-1
)/3)
560 super(game
, x
, y
, game
.bw
, game
.bh
)
561 if i
== 0 then return
565 over
= game
.colors
[i
] + " monster ({i})"
566 shortcut
= i
.to_s
# code for 1 trough 9
577 if self.happy
> 0 then
581 if self.shocked
> 0 then
585 if self.blink
> 0 then
588 else if 100.rand
== 0 then
597 if self.angries
>0 then
599 else if self.happy
> 5 then
601 else if self.shocked
> 0 then
603 else if self.blink
> 0 then
606 ctx
.blit
(game
.img
[s
, self.imgy
], self.x
, self.y
)
607 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
615 super(game
, 440, 92, game
.bh
, 22+game
.bh
)
624 # Metal (fixed) button.
629 super(game
, 498, 92, game
.bh
, 20+game
.bh
)
633 over
= "Metal block (q)"
637 private var fixed
= false
639 redef fun click_board
(ev
,t
)
641 if not ev
.drag
then self.fixed
= not t
.fixed
642 if t
.fixed
== self.fixed
then return
654 super(game
,556, 92, game
.bh
, 20+game
.bh
)
656 over
= "Resize the grid"
663 var x
= self.x
+ i
*bw
/3
664 var y
= self.y
+ j
*bh
/3
665 ctx
.blit_scaled
(game
.img
[5+(i
+j
)%2,0], x
, y
, bw
/3, bh
/3)
668 if game
.selected_button
== self then ctx
.blit
(game
.img
[0, 0], self.x
, self.y
)
673 if game
.selected_button
!= game
.button_size
then
676 game
.selected_button
= null
677 game
.board
.dirty
=true
681 redef fun click_board
(evt
, t
)
686 if w
< 3 or h
< 3 then
688 game
.statusbar
.set_tmp
("Too small!", "red")
692 for i
in [0..grid
.width
[ do
693 for j
in [0..grid
.height
[ do
695 var t2
= grid
.grid
[i
][j
]
705 game
.statusbar
.set_tmp
("Monsters on the way!", "red")
713 # Inactive area used to display the score
718 super(game
,440,310,199,62)
722 ctx
.textx
("MONSTERS: {game.grid.number}",self.x
,self.y
+1,21,"cyan",null)
723 var level
= game
.level
724 if level
== null then return
725 if level
.get_state
>= level
.l_won
then
726 ctx
.textx
("BEST: {level.score}",self.x
,self.y
+22,21,"pink", null)
728 ctx
.textx
("BEST: -",self.x
,self.y
+22,21,"pink", null)
730 if game
.levels
[9].get_state
>= level
.l_won
then
731 if level
.is_challenge
then
732 ctx
.textx
("GOAL: {level.gold}",self.x
,self.y
+44,21,"yellow",null)
734 ctx
.textx
("GOLD: {level.gold}",self.x
,self.y
+44,21,"yellow",null)
740 # Status bar element.
745 super(game
,24, 440, 418-24, 30)
748 # Permanant text, if any
749 var main_txt
: nullable String = null
751 # Text to display when the cursor if over an entity (`over_what`), if any
752 var over_txt
: nullable String = null
754 # What is the entity for `over_txt`
755 var over_what
: nullable Entity
757 # Text to temporally display, for some game event, if any
758 var tmp_txt
: nullable String = null
760 # time-to-live for the `tmp_txt`
763 # Color used to display `tmp_txt`
764 var tmp_txt_color
: nullable String = null
774 # set a temporary text
775 fun set_tmp
(txt
, color
: String)
777 print
"***STATUS** {txt}"
779 self.tmp_txt_ttl
= 60
780 self.tmp_txt_color
= color
785 var tmp_txt
= self.tmp_txt
786 var over_txt
= self.over_txt
787 var main_txt
= self.main_txt
788 if tmp_txt
!= null and self.tmp_txt_ttl
>0 then
789 ctx
.textx
(tmp_txt
,24,442,24,self.tmp_txt_color
,null)
790 else if over_txt
!= null then
791 ctx
.textx
(over_txt
,24,442,24,"yellow",null)
792 else if main_txt
!= null then
793 ctx
.textx
(main_txt
,24,442,24,"white",null)
799 if self.tmp_txt_ttl
>0 then
806 # ************************************************************************/
810 fun textx
(str
: String, x
, y
, height
: Int, color
, color2
: nullable String)
812 #var w = measureText(str, height)
814 text
(str
.to_upper
, app
.game
.font
, x
, y
)
817 # give the width for a giver text
818 fun measureText
(str
: String, height
: Int): Int
820 var font
= app
.game
.font
821 return str
.length
* (font
.width
+ font
.hspace
.to_i
)
824 # displays a debug rectangle
825 fun rect
(x
,y
,w
,h
:Int)
827 var image
= once app
.load_image
("hitbox.png")
828 blit_scaled
(image
, x
, y
, w
, h
)
832 # Simple basic class for event
845 var char_code
: String
849 # width of a tile, used for most width reference in the game
851 # height a tile, used for most width reference in the game
853 # x-coordinate of the board (padding)
855 # y-coordinate of the board (padding)
861 var img
= new TileSet(app
.load_image
("tiles2.png"),48,48)
863 # Sub tileset (for marks or other)
864 var img2
= new TileSet(app
.load_image
("tiles2.png"),24,24)
867 var back
: Image = app
.load_image
("background.png")
870 var logo
: Image = app
.load_image
("logo.png")
873 var font
= new TileSetFont(app
.load_image
("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
875 # DISPLAY *****************************************************************
877 # Is the game in editing mode
880 # The selected button, if any
881 var selected_button: nullable Button = null
885 # Is the music muted?
886 var music_muted: Bool = app.data_store["music_muted"] == true
888 # Is the sound effects muted?
889 var sfx_muted: Bool = app.data_store["sfx_muted"] == true
891 # The background music resource. */
892 var music = new Music("music.ogg")
895 var snd_click = new Sound("click.wav")
898 var snd_win = new Sound("level.wav")
901 var snd_duh = new Sound("duh.wav")
904 var snd_bing = new Sound("bing.wav")
907 var snd_whip = new Sound("whip.wav")
910 # INPUT ******************************************************************
912 # Current grid edited (if any).
913 var grid_edit: nullable Grid = null
915 # Sequence of current entities
916 var entities = new Array[Entity]
918 # The current statusbar
919 var statusbar = new StatusBar(self)
922 var board = new Board(self)
924 # The current score board
925 var score = new Score(self)
927 # Monster button game elements.
928 var buttons = new Array[MonsterButton]
931 var button_wall = new MetalButton(self)
934 var button_erase = new EraseButton(self)
937 var button_size = new ResizeButton(self)
939 # Cheat mode enabled?
948 if not music_muted then music.play
955 buttons[i] = new MonsterButton(self, i)
959 # Play a level in player mode.
962 save # save the previous level grid
964 grid.load(level.saved_str or else level.str)
965 init_play_menu(false)
966 if level.status != "" then
967 statusbar.main_txt = level.status
969 statusbar.main_txt = level.fullname
971 var t = new NextLevelButton(self)
976 # Play the next level.
979 play(levels[level.number+1])
983 # Helper function to initialize all states.
984 # Set up buttons for music and SFX.
990 entities.push(new MusicButton(self))
991 entities.push(new SFXButton(self))
992 entities.push(new MenuButton(self))
994 entities.push(statusbar)
997 # Helper function to initialize monster menu entries.
998 fun init_play_menu(full: Bool)
1001 entities.push(board)
1002 entities.push(new ResetButton(self))
1003 entities.push(button_erase)
1004 # Push monster buttons and determine the selected one
1005 var sel: nullable Button = null
1006 for i in [1..monsters] do
1007 if grid.monsters[i].number > 0 or full then
1008 if selected_button == buttons[i] or sel == null then
1011 entities.push(buttons[i])
1014 selected_button = sel
1015 entities.push(score)
1018 # Play a arbitrary grid in try mode.
1019 fun play_grid(g: Grid)
1022 init_play_menu(false)
1023 statusbar.main_txt = "User level"
1024 if grid_edit != null then
1025 entities.push(new EditButton(self))
1027 entities.push(new WonButton(self))
1031 # Launch the editor starting with a grid.
1032 fun edit_grid(g: Grid)
1035 init_play_menu(true)
1037 statusbar.main_txt = "Level editor"
1038 if level != null then statusbar.main_txt += ": level "+level.name
1039 entities.push(button_wall)
1040 entities.push(button_size)
1041 entities.push(new TestButton(self))
1042 entities.push(new SaveButton(self))
1043 entities.push(new LoadButton(self))
1047 # Launch the title screen
1051 entities.push(new Splash(self))
1055 # Helper function to initialize the menu (and tile) screen
1058 save # save the previous level grid
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 set_tmp("RUN EDITOR")
1214 grid_edit = grid.copy(true)
1216 else if kc == "c" then
1218 set_tmp("CHEAT: OFF")
1222 set_tmp("CHEAT: ON")
1226 else if kc == "s" then
1227 if solver == null then
1228 solver = (new FriendzProblem(grid)).solve
1229 solver_pause = false
1231 solver_pause = not solver_pause
1233 if solver_pause then
1234 set_tmp("SOLVER: PAUSED")
1236 set_tmp("SOLVER: ON")
1239 else if kc == "d" then
1240 if solver == null then
1241 solver = (new FriendzProblem(grid)).solve
1243 set_tmp("SOLVER: ON")
1247 set_tmp("SOLVER: ONE STEP")
1249 else if kc == "+" then
1251 set_tmp("SOLVER: {solver_steps} STEPS")
1252 else if kc == "-" then
1254 set_tmp("SOLVER: {solver_steps} STEPS")
1255 else for g in entities do
1256 if kc == g.shortcut then
1263 fun set_tmp(s: String)
1265 statusbar.set_tmp(s, "cyan")
1268 redef fun load_levels
1272 for level in levels do
1273 var score = app.data_store["s{level.str}"]
1274 if score isa Int then
1277 var saved_str = app.data_store["g{level.str}"]
1278 if saved_str isa String then
1279 print "LOAD {level.name}: {saved_str}"
1280 level.saved_str = saved_str
1294 # The spash title image
1299 super(game,game.xpad,game.ypad,380,350)
1303 ctx.blit(game.logo, game.xpad, game.ypad)
1312 class NextLevelButton
1316 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1322 var w = game.level.check_won(game.grid)
1323 if self.enabled != w then
1328 game.statusbar.set_tmp("Level solved!", "cyan")
1335 if not self.enabled then
1337 var grid = game.grid
1338 var monsters = grid.monsters
1339 var angry = new Array[Tile]
1340 var lonely = new Array[Tile]
1341 var edges = new Array[Tile]
1342 for i in [0..grid.width[ do
1343 for j in [0..grid.height[ do
1344 var t = grid.grid[i][j]
1345 if t.kind == 0 then continue
1346 if t.nexts == 0 then lonely.push(t)
1347 if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
1348 if t.nexts > 2 then angry.push(t)
1353 if angry.length>0 then
1355 else if lonely.length>0 then
1360 for i in l do i.shocked=5
1362 if angry.length>0 then
1363 game.statusbar.set_tmp("Angry monsters!", "red")
1364 else if lonely.length>0 then
1365 game.statusbar.set_tmp("Lonely monsters!", "red")
1366 else if not grid.won then
1367 game.statusbar.set_tmp("Unconnected monsters!", "red")
1369 game.statusbar.set_tmp("Not enough monsters!", "red")
1383 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1384 toggled = game.music_muted
1386 redef fun click2(ev)
1388 game.music_muted = self.toggled
1389 if game.music_muted then game.music.pause else game.music.play
1390 app.data_store["music_muted"] = game.music_muted
1398 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1399 toggled = game.sfx_muted
1402 redef fun click2(ev)
1404 game.sfx_muted = self.toggled
1405 if not game.sfx_muted then game.snd_whip.play # Because the automatic one was muted
1406 app.data_store["sfx_muted"] = game.sfx_muted
1414 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1418 redef fun click2(ev)
1428 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1433 redef fun click2(ev)
1436 if self.count==1 then
1437 game.statusbar.set_tmp("Click again to reset","white")
1438 else if self.count==2 then
1439 game.grid.reset(false)
1440 if game.editing then
1441 game.statusbar.set_tmp("Click again to clear all","white")
1443 else if game.editing then
1444 game.grid.reset(true)
1446 game.dirty_all = true
1459 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1462 redef fun click2(ev)
1464 var ge = game.grid_edit
1474 super(game,"WON", 440, 24, "cyan", "", null)
1477 redef fun click2(ev)
1479 var ge = game.grid_edit
1480 if not self.enabled then
1481 game.statusbar.set_tmp("Solve the level first!", "red")
1482 else if ge != null then
1493 var w = game.grid.won
1494 if self.enabled != w then
1499 game.statusbar.set_tmp("Level solved!", "cyan")
1509 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1512 redef fun click2(ev)
1514 game.grid_edit = game.grid
1515 game.play_grid(game.grid.copy(false))
1523 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1526 redef fun click2(ev)
1528 var res = game.grid.save
1537 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1540 redef fun click2(ev)
1542 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1543 if grid2.load("") then
1546 game.dirty_all = true
1550 class ContinueButton
1554 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1557 redef fun click2(ev)
1566 init(game: Game, level: Level)
1569 if level.number == 0 then
1570 super(game,"START", 440, 24, "purple", "Play the first level", null)
1572 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1576 redef fun click2(ev)
1589 # Wanted screen width
1590 var screen_width = 640
1592 # Wanted screen height
1593 var screen_height = 480
1599 game.font.hspace = -2
1600 if args.length > 0 then
1601 game.play(game.levels[args.first.to_i])
1612 redef fun frame_core(display)
1618 redef fun input(input_event)
1621 if input_event isa QuitEvent then # close window button
1622 quit = true # orders system to quit
1623 else if input_event isa PointerEvent then
1624 var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
1625 if input_event.is_motion then
1626 game.onMouseMove(ev)
1627 else if input_event.pressed then
1628 game.onMouseDown(ev)
1633 else if input_event isa KeyEvent and input_event.is_down then
1634 var ev = new Event(0, 0, input_event.key_name)
1643 redef class PointerEvent
1644 fun is_motion: Bool do return false
1647 redef class KeyEvent
1648 fun key_name: String
1651 if c != null then return c.to_s
1657 # Save the score and grid of the level
1660 app.data_store["s{str}"] = if score > 0 then score else null
1661 var saved = game.grid.save
1663 app.data_store["g{str}"] = saved
1664 print "SAVE: {name}: {saved}"
1667 # The saved player grid (to continue games)
1668 var saved_str: nullable String = null