contrib/friends: fixes unread variables
[nit.git] / contrib / friendz / src / friendz.nit
1 # Monsterz - Chains of Friends
2 #
3 # 2010-2014 (c) Jean Privat <jean@pryen.org>
4 #
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.
9
10 # Full UI for the game
11 module friendz is
12 app_name("ChainZ of FriendZ")
13 app_version(0, 1, git_revision)
14 end
15
16 import mnit
17 import realtime
18 import solver
19 import mnit::tileset
20
21 intrude import grid
22 intrude import level
23
24 redef class Grid
25 # Zoom level in %
26 # higher means more dense grid
27 var ratio = 100
28
29 # Various grid sizes from large to small (16x16, 12x12, 8x8, 6x6)
30 var ratios: Array[Int] = [200, 150, 100, 75]
31
32 redef fun resize(w,h)
33 do
34 super
35 for r in ratios do
36 if w*100/r <= 8 and h*100/r <= 8 then self.ratio = r
37 end
38 end
39 end
40
41 # * ENTITIES ****************************************************************
42
43 # A game entity is something that is displayed and may interact with the player.
44 abstract class Entity
45 # The associated game
46 var game: Game
47
48 # X left coordinate (in pixel).
49 var x: Int
50
51 # Y top coordinate (in pixel).
52 var y: Int
53
54 # X right coordinate (in pixel).
55 fun x2: Int do return x + width
56
57 # Y bottom coordinate (in pixel).
58 fun y2: Int do return y + height
59
60 # Width
61 var width: Int
62
63 # Height
64 var height: Int
65
66 # Tool tip text (if any)
67 var over: nullable String = null
68
69 # can the entity intercepts drag ang drop events?
70 var draggable = false
71
72 # Draw function. To implement
73 fun draw(ctx: Display) do end
74
75 # Update function. Called each loop. To implement
76 fun update do end
77
78 # Enter function. Called when the cursor enter in the element. To implement
79 fun enter(ev: Event) do end
80
81 # Click function. Called when the player click in the element.
82 # (or activate it with a shortcut).
83 fun click(ev: Event) do end
84
85 # keyboard shortcut do activate the entity, if any
86 var shortcut: nullable String = null
87
88 # Are events received?
89 var enabled = true
90
91 fun bw: Int do return game.bw
92 fun bh: Int do return game.bh
93
94 # Should the entity be redrawn
95 var dirty = true
96 end
97
98 # TEXT BUTTONS ***********************************************************/
99
100 # Button entity displayed as a simple text.
101 # if `over1` is null, then the button is a simple pasive label
102 # if `over1` is set but `over2` is null, then the button is a normal button
103 # if both `over1` and `over2` arew set, then the button is a toggleable button with two states
104 class TextButton
105 super Entity
106 var str: String
107 init(game: Game, str: String, x,y: Int, color: nullable String, over, over2: nullable String)
108 do
109 var w = 10 # TODO
110 super(game, x,y,w,24)
111 self.str = str
112 self.color = color or else "purple"
113 self.over = over
114 self.over1 = over
115 self.over2 = over2
116 self.textx = x
117 if self.toggleable then
118 self.x -= bw/2 + 4
119 end
120 end
121
122 var color: String
123
124 # The description of the button action
125 var over1: nullable String
126 # The description of the state2 button action
127 var over2: nullable String
128
129 # is the button a two-state button
130 fun toggleable: Bool do return over2 != null
131
132 # is the toggleable button in its second state?
133 var toggled = false
134
135 # ttl for highlighting
136 var ttl = 0
137
138 # position of the start of the text
139 # in a toggleable button, there is space for the mark between `x` and `textx`
140 var textx: Int
141
142 redef fun draw(ctx) do
143 if self.toggleable then
144 var w
145 if self.toggled or not self.enabled then w = 6 else w = 7
146 ctx.blit(game.img2[w,0], self.x, self.y)
147 end
148 var c
149 if self.enabled then c = self.color else c = "gray"
150 var c2= null
151 if self.ttl > 0 then c2 = "rgba(255,255,255,{self.ttl/10})"
152 ctx.textx(self.str, self.textx, self.y, self.height, c, c2)
153 self.width = ctx.measureText(self.str, self.height)
154 if self.toggleable then self.width += bw/2 + 4
155 end
156
157 redef fun update
158 do
159 if game.statusbar.over_what != self and self.ttl > 0 then
160 self.ttl-=1
161 self.dirty = true
162 end
163 end
164
165 redef fun enter(ev)
166 do
167 if over1 == null then return
168 if not self.enabled then return
169 game.snd_click.replay
170 self.ttl = 10
171 self.dirty = true
172 self.enter2
173 end
174
175 # Called by `enter` do perform additionnal work if the button is active
176 # Specific button should implement this instead of `enter`
177 fun enter2 do end
178
179 redef fun click(ev)
180 do
181 if not self.enabled then
182 game.snd_bing.replay
183 else
184 if self.toggleable then
185 self.toggled = not self.toggled
186 if self.toggled then self.over = self.over2 else self.over = self.over1
187 game.statusbar.over_txt = self.over
188 end
189 game.snd_whip.replay
190 end
191 self.click2(ev)
192 end
193
194 # Called by `click` do perform additionnal work if the button is active
195 # Specific button should implement this instead of `click`
196 fun click2(ev: Event) do end
197
198 end
199
200 # LEVEL BUTTONS ***********************************************************/
201
202 # button to play a level in the menu screen
203 class LevelButton
204 super Entity
205
206 # The associated level to play
207 var level: Level
208
209 init(l: Level)
210 do
211 self.level = l
212 var i = l.number
213 super(l.game, (i%5)*56 + 54, (i/5)*56 + 55, l.game.bw, l.game.bh)
214
215 self.over = self.level.fullname
216 if self.level.get_state >= l.l_won then
217 if game.levels[9].get_state >= l.l_won then self.over += " --- {self.level.score}/{self.level.par}"
218 else if self.level.get_state >= l.l_open then
219 if game.levels[9].get_state >= l.l_open then self.over += " --- ?/{self.level.par}"
220 end
221 #self.enabled = l.get_state >= l.l_open
222 end
223
224 redef fun draw(ctx)
225 do
226 var l = level
227 var s = self.level.get_state
228 var ix = 5 + l.number % 2
229 var iy = 0
230 if s == l.l_disabled then
231 ix = 3
232 iy = 3
233 else if s == l.l_open then
234 ix = 1
235 iy = 1
236 ctx.blit(game.img[ix,iy], self.x, self.y)
237 ix = 0
238 iy = 0
239 end
240 ctx.blit(game.img[ix,iy], self.x, self.y)
241
242 if s == l.l_par then
243 ctx.blit(game.img2[7,0], self.x + bw*5/8, self.y-bh*1/8)
244 end
245 ctx.textx(self.level.name, self.x+5, self.y+5, 24, null, null)
246 end
247
248 redef fun click(ev)
249 do
250 if self.enabled then
251 game.snd_whip.replay
252 game.play(self.level)
253 else
254 game.snd_bing.replay
255 game.statusbar.set_tmp("Locked level", "red")
256 end
257 end
258
259 end
260
261 # ACHIEVEMENTS ************************************************************/
262
263 # Achievement (monster-like) button in the menu screen
264 class Achievement
265 super Button
266
267 # The number of the achievement (0 is first)
268 var number: Int
269
270 # The name of the achievement
271 var name: String
272
273 init(game: Game, i: Int, name: String)
274 do
275 super(game, 5*56 + 54, i*56 + 55, game.bw, game.bh)
276 self.over = name
277 self.number = i
278 self.name = name
279 var l = game.levels[number*5+4]
280 enabled = l.get_state >= l.l_won
281 if self.enabled then self.over = name + " (unlocked)" else self.over = name + " (locked)"
282 end
283
284 redef fun draw(ctx)
285 do
286 var w
287 if self.enabled then w = 5 else w = 3
288 ctx.blit(game.img[w,self.number+5], self.x, self.y)
289 end
290
291 redef fun click(ev)
292 do
293 if not self.enabled then
294 game.snd_bing.replay
295 game.statusbar.set_tmp("Locked achievement!", "red")
296 else
297 game.snd_whip.replay
298 self.click2(ev)
299 end
300 end
301
302 fun click2(ev: Event) do
303 # TODO
304 end
305 end
306
307
308 # BOARD (THE GRID) *******************************************************/
309
310 # The board game element.
311 class Board
312 super Entity
313 init(game: Game)
314 do
315 super(game, game.xpad, game.ypad, 8*game.bw, 8*game.bh)
316 draggable = true
317 end
318
319 redef fun draw(ctx)
320 do
321 var grid = game.grid
322 var bwr = bw*100/grid.ratio
323 var bhr = bh*100/grid.ratio
324 var w = grid.width
325 var h = grid.height
326 if game.selected_button == game.button_size then
327 bwr = bw/2
328 bhr = bh/2
329 w = game.gw
330 h = game.gh
331 end
332 self.x = game.xpad+(48*8/2)-w*bwr/2
333 self.y = game.ypad+(48*8/2)-h*bhr/2
334 self.width = w*bwr
335 self.height = h*bhr
336 for i in [0..w[ do
337 for j in [0..h[ do
338 var t = grid.grid[i][j]
339 var dx = i * bwr + self.x
340 var dy = j * bhr + self.y
341 if (i+j)%2 == 0 then
342 ctx.blit_scaled(game.img[5,0], dx, dy, bwr, bhr)
343 else
344 ctx.blit_scaled(game.img[6,0], dx, dy, bwr, bhr)
345 end
346 if t.fixed then
347 if t.shape != null and not game.editing then
348 #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)
349 ctx.blit_scaled(game.img[3,3], dx, dy, bwr, bhr)
350 else
351 ctx.blit_scaled(game.img[3,3], dx, dy, bwr, bhr)
352 end
353 end
354 if t.kind>0 then
355 var m = grid.monsters[t.kind]
356 var s = 0
357 if t.blink > 0 then s = 1
358 if t.nexts > 2 then s = 3
359 if t.nexts == 0 then s = 6
360 if m.chain then s = 5
361 if t.shocked>0 then s = 2
362 ctx.blit_scaled(game.img[s,(4+t.kind)], dx, dy, bwr, bhr)
363 end
364 #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
365 end
366 end
367 if game.selected_button == game.button_size then
368 var x0 = self.x
369 var x1 = (grid.width) * bwr - bwr/2 + self.x
370 var y0 = self.y
371 var y1 = (grid.height) * bhr - bhr/2 + self.y
372 ctx.blit_scaled(game.img2[0,0], x0, y0, bwr/2, bhr/2)
373 ctx.blit_scaled(game.img2[1,0], x1, y0, bwr/2, bhr/2)
374 ctx.blit_scaled(game.img2[1,1], x1, y1, bwr/2, bhr/2)
375 ctx.blit_scaled(game.img2[0,1], x0, y1, bwr/2, bhr/2)
376 ctx.textx("{grid.width}x{grid.height}",self.x + grid.width*bwr/2,self.y+grid.height*bhr/2,20,"orange",null)
377 end
378 end
379
380 redef fun update
381 do
382 var grid = game.grid
383 for i in [0..grid.width[ do
384 for j in [0..grid.height[ do
385 var t = grid.grid[i][j]
386 if t.kind == 0 then continue
387 if t.blink > 0 then
388 t.blink-=1
389 self.dirty=true
390 end
391 if t.shocked > 0 then
392 t.shocked-=1
393 self.dirty=true
394 else if 100.rand == 0 then
395 t.blink = 5
396 self.dirty=true
397 end
398 end
399 end
400 end
401
402 # Last clicked tile
403 # Uded to filter drag events
404 private var last: nullable Tile = null
405
406 redef fun click(ev)
407 do
408 var grid = game.grid
409 var r = grid.ratio
410 if game.selected_button == game.button_size then r = 200
411 var x = ev.game_x * r / bw / 100
412 var y = ev.game_y * r / bh / 100
413 var t = grid.grid[x][y]
414
415 if ev.drag and last == t then return
416 last = t
417
418 if game.selected_button != game.button_size and (x>=grid.width or y>=grid.height) then return
419
420 # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
421
422 if game.selected_button != null then
423 game.selected_button.click_board(ev, t)
424 end
425 end
426 end
427
428 # BUTTONS *****************************************************************/
429
430 # A in-game selectable button for monsters or tools
431 class Button
432 super Entity
433
434 # The x tile
435 var imgx: Int = 0
436
437 # The y tile
438 var imgy: Int = 0
439
440 # The associated monster tile
441 # >0 for monsters, <=0 for tools
442 var kind = 0
443
444 redef fun draw(ctx)
445 do
446 ctx.blit(game.img[self.imgx, self.imgy], self.x, self.y)
447 if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
448 end
449
450 redef fun click(ev)
451 do
452 var sel = game.selected_button
453 if game.selected_button == game.button_size then game.board.dirty=true
454 if sel != null then sel.dirty=true
455 game.selected_button = self
456 game.snd_click.replay
457 end
458
459 # Current inputed chain
460 # Used for drag
461 private var chain = new Array[Tile]
462
463 # Board click. Called when the player click on the board with the button selected.
464 fun click_board(ev: Event, t: Tile)
465 do
466 game.score.dirty = true
467 if ev.drag and self.kind>0 and not chain.is_empty then
468 if self.chain.length >= 2 and self.chain[1] == t then
469 var t2 = self.chain.shift
470 game.snd_click.replay
471 if t2.fixed and not game.editing then return
472 t2.update(0)
473 return
474 end
475 if t.fixed and t.kind == self.kind then
476 self.chain.unshift(t)
477 game.snd_click.replay
478 return
479 end
480 if (self.chain[0].x - t.x).abs + (self.chain[0].y - t.y).abs != 1 then return
481 if t.fixed and not game.editing then
482 game.snd_bing.replay
483 return
484 end
485 if t.kind != 0 and t.kind != self.kind then
486 t.shocked = 5
487 game.snd_duh.replay
488 return
489 end
490 self.chain.unshift(t)
491 if t.kind == self.kind then return
492 game.snd_click.replay
493 t.update(self.kind)
494 return
495 end
496
497 if t.fixed and not game.editing then
498 if t.kind == 0 then
499 game.snd_bing.replay
500 return
501 end
502 if t.kind != self.kind and not ev.drag then
503 game.buttons[t.kind].click(ev)
504 game.buttons[t.kind].chain = [t]
505 else
506 self.chain = [t]
507 game.snd_bing.replay
508 end
509 return
510 end
511 if t.fixed and game.editing and self == game.button_erase and t.kind == 0 then
512 t.fixed = false
513 game.snd_click.replay
514 return
515 end
516
517 var nkind = 0 # the new kind
518 if ev.drag then
519 # Here we clean
520 if t.kind == 0 then return
521 if self.kind != 0 and t.kind != self.kind then
522 t.shocked = 5
523 game.snd_duh.replay
524 return
525 end
526 nkind = 0
527 else if t.kind != self.kind then
528 nkind = self.kind
529 self.chain = [t]
530 else if t.kind != 0 then
531 nkind = 0
532 self.chain.clear
533 end
534 if nkind == t.kind then return
535 game.snd_click.replay
536 t.update(nkind)
537 end
538 end
539
540 # A monster button
541 class MonsterButton
542 super Button
543
544 # TTL for the monster being angry
545 var angries = 0
546 # TTL for the monster being happy
547 var happy = 0
548 # TTL for the monster being shocked
549 var shocked = 0
550 # TTL for the monster blinking
551 var blink = 0
552
553 init(game: Game, i: Int)
554 do
555 self.game = game
556 var x = 440 + 58 * ((i-1).abs%3)
557 var y = 150 + (bh+5) * ((i-1)/3)
558 super(game, x, y, game.bw, game.bh)
559 if i == 0 then return
560 kind = i
561 imgx = 0
562 imgy = (4+i)
563 over = game.colors[i] + " monster ({i})"
564 shortcut = i.to_s # code for 1 trough 9
565 end
566
567 redef fun click(ev)
568 do
569 super
570 self.shocked = 5
571 end
572
573 redef fun update
574 do
575 if self.happy > 0 then
576 self.happy-=1
577 self.dirty=true
578 end
579 if self.shocked > 0 then
580 self.shocked-=1
581 self.dirty=true
582 end
583 if self.blink > 0 then
584 self.blink-=1
585 self.dirty=true
586 else if 100.rand == 0 then
587 self.blink = 5
588 self.dirty=true
589 end
590 end
591
592 redef fun draw(ctx)
593 do
594 var s = self.imgx
595 if self.angries>0 then
596 s += 3
597 else if self.happy > 5 then
598 s += 5
599 else if self.shocked > 0 then
600 s += 5
601 else if self.blink > 0 then
602 s += 1
603 end
604 ctx.blit(game.img[s, self.imgy], self.x, self.y)
605 if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
606 end
607 end
608
609 # Erase button.
610 class EraseButton
611 super Button
612 init(game: Game) do
613 super(game, 440, 92, game.bh, 22+game.bh)
614 imgx = 4
615 imgy = 13
616 kind = 0
617 over = "Eraser (0)"
618 shortcut = "0"
619 end
620 end
621
622 # Metal (fixed) button.
623 class MetalButton
624 super Button
625 init(game: Game)
626 do
627 super(game, 498, 92, game.bh, 20+game.bh)
628 imgx = 3
629 imgy = 3
630 kind = -1
631 over = "Metal block (q)"
632 shortcut = "q"
633 end
634
635 private var fixed = false
636
637 redef fun click_board(ev,t)
638 do
639 if not ev.drag then self.fixed = not t.fixed
640 if t.fixed == self.fixed then return
641 t.fixed = self.fixed
642 game.snd_click.replay
643 end
644 end
645
646 # Resize button.
647 class ResizeButton
648 super Button
649
650 init(game: Game)
651 do
652 super(game,556, 92, game.bh, 20+game.bh)
653 kind = -2
654 over = "Resize the grid"
655 end
656
657 redef fun draw(ctx)
658 do
659 for i in [0..3[ do
660 for j in [0..3[ do
661 var x = self.x + i*bw/3
662 var y = self.y + j*bh/3
663 ctx.blit_scaled(game.img[5+(i+j)%2,0], x, y, bw/3, bh/3)
664 end
665 end
666 if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
667 end
668
669 redef fun click(ev)
670 do
671 if game.selected_button != game.button_size then
672 super
673 else
674 game.selected_button = null
675 game.board.dirty=true
676 end
677 end
678
679 redef fun click_board(evt, t)
680 do
681 var grid = t.grid
682 var w = t.x+1
683 var h = t.y+1
684 if w < 3 or h < 3 then
685 game.snd_bing.replay
686 game.statusbar.set_tmp("Too small!", "red")
687 return
688 end
689 var aborts = false
690 for i in [0..grid.width[ do
691 for j in [0..grid.height[ do
692 if i>=w or j>=h then
693 var t2 = grid.grid[i][j]
694 if t2.kind > 0 then
695 aborts = true
696 t2.shocked = 5
697 end
698 end
699 end
700 end
701 if aborts then
702 game.snd_duh.replay
703 game.statusbar.set_tmp("Monsters on the way!", "red")
704 return
705 end
706 game.snd_click.replay
707 grid.resize(w,h)
708 end
709 end
710
711 # Inactive area used to display the score
712 class Score
713 super Entity
714 init(game: Game)
715 do
716 super(game,440,310,199,62)
717 end
718 redef fun draw(ctx)
719 do
720 ctx.textx("MONSTERS: {game.grid.number}",self.x,self.y+1,21,"cyan",null)
721 var level = game.level
722 if level == null then return
723 if level.get_state >= level.l_won then
724 ctx.textx("BEST: {level.score}",self.x,self.y+22,21,"pink", null)
725 else
726 ctx.textx("BEST: -",self.x,self.y+22,21,"pink", null)
727 end
728 if game.levels[9].get_state >= level.l_won then
729 if level.is_challenge then
730 ctx.textx("GOAL: {level.par}",self.x,self.y+44,21,"yellow",null)
731 else
732 ctx.textx("PAR: {level.par}",self.x,self.y+44,21,"yellow",null)
733 end
734 end
735 end
736 end
737
738 # Status bar element.
739 class StatusBar
740 super Entity
741 init(game: Game)
742 do
743 super(game,24, 440, 418-24, 30)
744 end
745
746 # Permanant text, if any
747 var main_txt: nullable String = null
748
749 # Text to display when the cursor if over an entity (`over_what`), if any
750 var over_txt: nullable String = null
751
752 # What is the entity for `over_txt`
753 var over_what: nullable Entity
754
755 # Text to temporally display, for some game event, if any
756 var tmp_txt: nullable String = null
757
758 # time-to-live for the `tmp_txt`
759 var tmp_txt_ttl = 0
760
761 # Color used to display `tmp_txt`
762 var tmp_txt_color: nullable String = null
763
764 # reset the status
765 fun clear do
766 self.main_txt = null
767 self.over_txt = null
768 self.tmp_txt = null
769 self.over = null
770 end
771
772 # set a temporary text
773 fun set_tmp(txt, color: String)
774 do
775 print "***STATUS** {txt}"
776 self.tmp_txt = txt
777 self.tmp_txt_ttl = 20
778 self.tmp_txt_color = color
779 end
780
781 redef fun draw(ctx)
782 do
783 var tmp_txt = self.tmp_txt
784 var over_txt = self.over_txt
785 var main_txt = self.main_txt
786 if tmp_txt != null and self.tmp_txt_ttl>0 then
787 ctx.textx(tmp_txt,24,442,24,self.tmp_txt_color,null)
788 else if over_txt != null then
789 ctx.textx(over_txt,24,442,24,"yellow",null)
790 else if main_txt != null then
791 ctx.textx(main_txt,24,442,24,"white",null)
792 end
793 end
794
795 redef fun update
796 do
797 if self.tmp_txt_ttl>0 then
798 self.tmp_txt_ttl-=1
799 self.dirty=true
800 end
801 end
802 end
803
804 # ************************************************************************/
805
806 # Simple audio asset
807 class Audio
808 var path: String
809
810 # placebo
811 fun play do end
812
813 # placebo
814 fun pause do end
815
816 # Play a sound.
817 fun replay
818 do
819 sys.system("aplay assets/{path} &")
820 end
821 end
822
823 redef class Display
824 # Display a text
825 fun textx(str: String, x, y, height: Int, color, color2: nullable String)
826 do
827 #var w = measureText(str, height)
828 #rect(x,y,w,height)
829 text(str.to_upper, app.game.font, x, y)
830 end
831
832 # give the width for a giver text
833 fun measureText(str: String, height: Int): Int
834 do
835 var font = app.game.font
836 return str.length * (font.width + font.hspace.to_i)
837 end
838
839 # displays a debug rectangle
840 fun rect(x,y,w,h:Int)
841 do
842 var image = once app.load_image("hitbox.png")
843 blit_scaled(image, x, y, w, h)
844 end
845 end
846
847 # Simple basic class for event
848 class Event
849 # Is a drag event?
850 var drag = false
851 # screen x
852 var offset_x: Int
853 # screen y
854 var offset_y: Int
855 # entity x
856 var game_x = 0
857 # entity y
858 var game_y = 0
859 # key pressed
860 var char_code: String
861 end
862
863 redef class Game
864 # width of a tile, used for most width reference in the game
865 var bw = 48
866 # height a tile, used for most width reference in the game
867 var bh = 48
868 # x-coordinate of the board (padding)
869 var xpad = 24
870 # y-coordinate of the board (padding)
871 var ypad = 24
872
873 # Load tiles
874
875 # Basic tileset
876 var img = new TileSet(app.load_image("tiles2.png"),48,48)
877
878 # Sub tileset (for marks or other)
879 var img2 = new TileSet(app.load_image("tiles2.png"),24,24)
880
881 # background image
882 var back: Image = app.load_image("background.png")
883
884 # Logo image
885 var logo: Image = app.load_image("logo.png")
886
887 # Font
888 var font = new TileSetFont(app.load_image("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
889
890 var xxx = """
891 fun save_cookie(name, val:String) do
892 var days = 365
893 var date = new Date()
894 date.setTime(date.getTime()+(days*24*60*60*1000))
895 document.cookie = name+"="+val+"; expires="+date.toGMTString()+"; path=/"
896 end
897
898 fun read_cookie(name:String):String do
899 var key = name + "="
900 var ca = document.cookie.split(';')
901 for(var i=0; i<ca.length; i++) do
902 var c = ca[i]
903 while (c[0]==' ') c = c.substring(1, c.length)
904 if (c.indexOf(key) == 0) return c.substring(key.length)
905 end
906 return null
907 end
908 """
909
910 # DISPLAY *****************************************************************
911
912 # Is the game in editing mode
913 var editing = false
914
915 # The selected button, if any
916 var selected_button: nullable Button = null
917
918 # SOUND
919
920 # Is the music muted?
921 var music_muted: Bool = true #read_cookie("music_muted")
922
923 # Is the sound effects muted?
924 var sfx_muted: Bool = true #read_cookie("sfx_muted")
925
926 # The background music resource. */
927 var music = new Audio("music.ogg")
928
929 # Click sound
930 var snd_click = new Audio("click.wav")
931
932 # Wining soulf
933 var snd_win = new Audio("level.wav")
934
935 # Shocked sound
936 var snd_duh = new Audio("duh.wav")
937
938 # metal sound
939 var snd_bing = new Audio("bing.wav")
940
941 # transition sound
942 var snd_whip = new Audio("whip.wav")
943
944 # INPUT ******************************************************************
945
946 # Current grid edited (if any).
947 var grid_edit: nullable Grid = null
948
949 # Sequence of current entities
950 var entities = new Array[Entity]
951
952 # The current statusbar
953 var statusbar = new StatusBar(self)
954
955 # The grid board
956 var board = new Board(self)
957
958 # The current score board
959 var score = new Score(self)
960
961 # Monster button game elements.
962 var buttons = new Array[MonsterButton]
963
964 # MetalButton
965 var button_wall = new MetalButton(self)
966
967 # EraseButton
968 var button_erase = new EraseButton(self)
969
970 # ResizeButton
971 var button_size = new ResizeButton(self)
972
973 # fill `buttons`
974 fun init_buttons
975 do
976 for i in [0..9] do
977 buttons[i] = new MonsterButton(self, i)
978 end
979 end
980
981 # Play a level in player mode.
982 fun play(l: Level)
983 do
984 level = l
985 grid.load(level.str)
986 init_play_menu(false)
987 if level.status != "" then
988 statusbar.main_txt = level.status
989 else
990 statusbar.main_txt = level.fullname
991 end
992 var t = new NextLevelButton(self)
993 entities.push(t)
994 run
995 end
996
997 # Play the next level.
998 fun play_next
999 do
1000 play(levels[level.number+1])
1001 end
1002
1003
1004 # Helper function to initialize all states.
1005 # Set up buttons for music and SFX.
1006 fun init_game
1007 do
1008 editing = false
1009 solver = null
1010 entities.clear
1011 entities.push(new MusicButton(self))
1012 entities.push(new SFXButton(self))
1013 entities.push(new MenuButton(self))
1014 statusbar.clear
1015 entities.push(statusbar)
1016 end
1017
1018 # Helper function to initialize monster menu entries.
1019 fun init_play_menu(full: Bool)
1020 do
1021 init_game
1022 entities.push(board)
1023 entities.push(new ResetButton(self))
1024 entities.push(button_erase)
1025 # Push monster buttons and determine the selected one
1026 var sel: nullable Button = null
1027 for i in [1..monsters] do
1028 if grid.monsters[i].number > 0 or full then
1029 if selected_button == buttons[i] or sel == null then
1030 sel = buttons[i]
1031 end
1032 entities.push(buttons[i])
1033 end
1034 end
1035 selected_button = sel
1036 entities.push(score)
1037 end
1038
1039 # Play a arbitrary grid in try mode.
1040 fun play_grid(g: Grid)
1041 do
1042 grid = g
1043 init_play_menu(false)
1044 statusbar.main_txt = "User level"
1045 if grid_edit != null then
1046 entities.push(new EditButton(self))
1047 end
1048 entities.push(new WonButton(self))
1049 run
1050 end
1051
1052 # Launch the editor starting with a grid.
1053 fun edit_grid(g: Grid)
1054 do
1055 grid = g
1056 init_play_menu(true)
1057 editing = true
1058 statusbar.main_txt = "Level editor"
1059 if level != null then statusbar.main_txt += ": level "+level.name
1060 entities.push(button_wall)
1061 entities.push(button_size)
1062 entities.push(new TestButton(self))
1063 entities.push(new SaveButton(self))
1064 entities.push(new LoadButton(self))
1065 run
1066 end
1067
1068 # Launch the title screen
1069 fun title
1070 do
1071 init_menu
1072 entities.push(new Splash(self))
1073 run
1074 end
1075
1076 # Helper function to initialize the menu (and tile) screen
1077 fun init_menu
1078 do
1079 init_game
1080 level = null
1081 var i = levels.first
1082 for l in levels do
1083 if l.get_state == l.l_open then break
1084 i = l
1085 end
1086 entities.push(new StartButton(self, i))
1087 end
1088
1089 # Launch the menu.
1090 fun menu
1091 do
1092 init_menu
1093 var t
1094 t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
1095 entities.push(t)
1096 for i in [0..levels.length[ do
1097 var b = new LevelButton(levels[i])
1098 entities.push(b)
1099 end
1100 t = new Achievement(self, 0, "Training")
1101 entities.push(t)
1102 t = new Achievement(self, 1, "Par")
1103 entities.push(t)
1104 t = new Achievement(self, 2, "Editor")
1105 entities.push(t)
1106 t = new Achievement(self, 3, "Challenge")
1107 entities.push(t)
1108 t = new Achievement(self, 4, "Congraturation")
1109 entities.push(t)
1110 t = new Achievement(self, 5, "Awesome")
1111 entities.push(t)
1112 run
1113 end
1114
1115 # Last function called when the lauch state is ready
1116 fun run do
1117 dirty_all = true
1118 end
1119
1120 init
1121 do
1122 load_levels
1123 init_buttons
1124 entities.clear
1125 title
1126 end
1127
1128 # Should all entity redrawn?
1129 var dirty_all = true
1130
1131 # Draw all game entities.
1132 fun draw(display: Display) do
1133 dirty_all = true
1134 if dirty_all then display.blit(back, 0, 0)
1135 for g in entities do
1136 if g.dirty or dirty_all then
1137 g.dirty = false
1138 #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)
1139 g.draw(display)
1140 #ctx.rect(g.x, g.y, g.width, g.height)
1141 end
1142 end
1143 var ev = lastev
1144 if ev isa Event then
1145 display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
1146 end
1147 dirty_all = false
1148 end
1149
1150 # Update all game entities.
1151 fun step do
1152 if solver != null and not solver_pause then
1153 if solver.run_steps(solver_steps) != null then solver_pause = true
1154 print solver.to_s
1155 if not solver.is_running then solver = null
1156 end
1157 for g in entities do
1158 g.update
1159 end
1160 end
1161
1162 # Return the game entity located at a mouse event.
1163 fun get_game_element(ev: Event): nullable Entity
1164 do
1165 var x = ev.offset_x
1166 var y = ev.offset_y
1167 for g in entities do
1168 if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
1169 ev.game_x = x-g.x
1170 ev.game_y = y-g.y
1171 #print "get {g}"
1172 return g
1173 end
1174 end
1175 return null
1176 end
1177
1178 # The game entlty the mouse went down on
1179 var drag: nullable Entity = null
1180
1181 # Last mouse event. Used to dray the cursor
1182 var lastev: nullable Event = null
1183
1184 # Callback when the mouse is pressed
1185 fun onMouseDown(ev: Event) do
1186 lastev = ev
1187 var g = get_game_element(ev)
1188 if g != null then
1189 g.click(ev)
1190 g.dirty = true
1191 end
1192 drag = g
1193 end
1194
1195 # Callback when the mouse is releassed
1196 fun onMouseUp(ev: Event) do
1197 drag = null
1198 end
1199
1200 # Callback when the mouse if moved while pressed
1201 fun onMouseMove(ev: Event) do
1202 lastev = ev
1203 var g = get_game_element(ev)
1204 if g == null then
1205 statusbar.dirty = true
1206 statusbar.over_txt = null
1207 statusbar.over_what = null
1208 return
1209 end
1210 if statusbar.over_what != g then
1211 statusbar.dirty = true
1212 var go = g.over
1213 statusbar.over_txt = go
1214 statusbar.over_what = g
1215 g.enter(ev)
1216 if go != null then print "***OVER*** {go}"
1217 end
1218 # We moved abode a element that accepts drag event
1219 if drag == g and g.draggable then
1220 # print "DRAG {g}"
1221 ev.drag = true
1222 g.click(ev)
1223 g.dirty = true
1224 end
1225 end
1226
1227 # Current solver, if any
1228 var solver: nullable BacktrackSolver[Grid, Action] = null
1229
1230 # Is the solver paused?
1231 var solver_pause = false
1232
1233 # Number of solver steps played in a single game `update`
1234 var solver_steps = 20000
1235
1236 # Callback when a keyboard event is recieved
1237 fun onKeyDown(ev: Event) do
1238 var kc = ev.char_code
1239 if kc == "e" then
1240 grid_edit = grid.copy(true)
1241 edit_grid(grid)
1242 else if kc == "s" then
1243 if solver == null then
1244 solver = (new FriendzProblem(grid)).solve
1245 solver_pause = false
1246 else
1247 solver_pause = not solver_pause
1248 end
1249 #solver.step
1250 else if kc == "d" then
1251 if solver == null then
1252 solver = (new FriendzProblem(grid)).solve
1253 solver_pause = true
1254 else
1255 solver.run_steps(1)
1256 end
1257 else if kc == "+" then
1258 solver_steps += 100
1259 print solver_steps
1260 else if kc == "-" then
1261 solver_steps -= 100
1262 print solver_steps
1263 else for g in entities do
1264 if kc == g.shortcut then
1265 g.click(ev)
1266 g.dirty = true
1267 end
1268 end
1269 end
1270 end
1271
1272 # The spash title image
1273 class Splash
1274 super Entity
1275 init(game: Game)
1276 do
1277 super(game,game.xpad,game.ypad,380,350)
1278 end
1279 redef fun draw(ctx)
1280 do
1281 ctx.blit(game.logo, game.xpad, game.ypad)
1282 end
1283 redef fun click(ev)
1284 do
1285 game.snd_whip.replay
1286 game.menu
1287 end
1288 end
1289
1290 class NextLevelButton
1291 super TextButton
1292 init(game: Game)
1293 do
1294 super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
1295 enabled = false
1296 end
1297
1298 redef fun update
1299 do
1300 var w = game.level.check_won(game.grid)
1301 if self.enabled != w then
1302 self.dirty = true
1303 self.enabled = w
1304 if w then
1305 game.snd_win.replay
1306 game.statusbar.set_tmp("Level solved!", "cyan")
1307 end
1308 end
1309 end
1310
1311 redef fun click(ev)
1312 do
1313 if not self.enabled then
1314 game.snd_duh.replay
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)
1327 end
1328 end
1329
1330 var l
1331 if angry.length>0 then
1332 l = angry
1333 else if lonely.length>0 then
1334 l = lonely
1335 else
1336 l = edges
1337 end
1338 for i in l do i.shocked=5
1339
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")
1346 else
1347 game.statusbar.set_tmp("Not enough monsters!", "red")
1348 end
1349 return
1350 end
1351
1352 game.snd_whip.replay
1353 game.play_next
1354 end
1355 end
1356
1357 class MusicButton
1358 super TextButton
1359 init(game: Game)
1360 do
1361 super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
1362 end
1363 redef fun click2(ev)
1364 do
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":"")
1368 end
1369 end
1370
1371 class SFXButton
1372 super TextButton
1373 init(game: Game)
1374 do
1375 super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
1376 end
1377
1378 redef fun click2(ev)
1379 do
1380 game.sfx_muted = self.toggled
1381 if not game.sfx_muted then game.snd_whip.replay # Because the automatic one was muted
1382 #save_cookie("sfx_muted",sfx_muted?"true":"")
1383 end
1384 end
1385
1386 class MenuButton
1387 super TextButton
1388 init(game: Game)
1389 do
1390 super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
1391 shortcut = "back"
1392 end
1393
1394 redef fun click2(ev)
1395 do
1396 game.menu
1397 end
1398 end
1399
1400 class ResetButton
1401 super TextButton
1402 init(game: Game)
1403 do
1404 super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
1405 end
1406
1407 var count = 0
1408
1409 redef fun click2(ev)
1410 do
1411 self.count += 1
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")
1418 end
1419 else if game.editing then
1420 game.grid.reset(true)
1421 end
1422 game.dirty_all = true
1423 end
1424
1425 redef fun enter2
1426 do
1427 self.count = 0
1428 end
1429 end
1430
1431 class EditButton
1432 super TextButton
1433 init(game: Game)
1434 do
1435 super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
1436 end
1437
1438 redef fun click2(ev)
1439 do
1440 var ge = game.grid_edit
1441 assert ge != null
1442 game.edit_grid(ge)
1443 end
1444 end
1445
1446 class WonButton
1447 super TextButton
1448 init(game: Game)
1449 do
1450 super(game,"WON", 440, 24, "cyan", "", null)
1451 enabled = false
1452 end
1453 redef fun click2(ev)
1454 do
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
1459 game.snd_whip.replay
1460 game.edit_grid(ge)
1461 else
1462 game.snd_whip.replay
1463 game.menu
1464 end
1465 end
1466
1467 redef fun update
1468 do
1469 var w = game.grid.won
1470 if self.enabled != w then
1471 self.dirty = true
1472 self.enabled = w
1473 if w then
1474 game.snd_win.replay
1475 game.statusbar.set_tmp("Level solved!", "cyan")
1476 end
1477 end
1478 end
1479 end
1480
1481 class TestButton
1482 super TextButton
1483 init(game: Game)
1484 do
1485 super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
1486 end
1487
1488 redef fun click2(ev)
1489 do
1490 game.grid_edit = game.grid
1491 game.play_grid(game.grid.copy(false))
1492 end
1493 end
1494
1495 class SaveButton
1496 super TextButton
1497 init(game: Game)
1498 do
1499 super(game, "SAVE", 540, 24, "purple", "Save the level", null)
1500 end
1501
1502 redef fun click2(ev)
1503 do
1504 var res = game.grid.save
1505 print "SAVE: {res}"
1506 end
1507 end
1508
1509 class LoadButton
1510 super TextButton
1511 init(game: Game)
1512 do
1513 super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
1514 end
1515
1516 redef fun click2(ev)
1517 do
1518 var grid2 = new Grid(game.gw,game.gh,game.monsters)
1519 if grid2.load("") then
1520 game.grid = grid2
1521 end
1522 game.dirty_all = true
1523 end
1524 end
1525
1526 class ContinueButton
1527 super TextButton
1528 init(game: Game)
1529 do
1530 super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
1531 end
1532
1533 redef fun click2(ev)
1534 do
1535 game.play_next
1536 end
1537 end
1538
1539 class StartButton
1540 super TextButton
1541 var level: Level
1542 init(game: Game, level: Level)
1543 do
1544 self.level = level
1545 if level.number == 0 then
1546 super(game,"START", 440, 24, "purple", "Play the first level", null)
1547 else
1548 super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
1549 end
1550 end
1551
1552 redef fun click2(ev)
1553 do
1554 game.play(level)
1555 end
1556 end
1557
1558 #
1559
1560 redef class App
1561
1562 # The game
1563 var game: Game
1564
1565 # Wanted screen width
1566 var screen_width = 640
1567
1568 # Wanted screen height
1569 var screen_height = 480
1570
1571 redef fun window_created
1572 do
1573 super
1574 game = new Game
1575 game.font.hspace = -2
1576 if args.length > 0 then
1577 game.play(game.levels[args.first.to_i])
1578 end
1579 # img loading?
1580 end
1581
1582 # Maximum wanted frame per second
1583 var max_fps = 30
1584
1585 # clock used to track FPS
1586 private var clock = new Clock
1587
1588 redef fun frame_core(display)
1589 do
1590 game.step
1591 game.draw(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)
1597 end
1598 end
1599
1600 redef fun input(input_event)
1601 do
1602 #print 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)
1611 else
1612 game.onMouseUp(ev)
1613 end
1614 return true
1615 else if input_event isa KeyEvent and input_event.is_down then
1616 var ev = new Event(0, 0, input_event.key_name)
1617 game.onKeyDown(ev)
1618 return true
1619 end
1620
1621 return false
1622 end
1623 end
1624
1625 redef class PointerEvent
1626 fun is_motion: Bool do return false
1627 end
1628
1629 redef class KeyEvent
1630 fun key_name: String
1631 do
1632 var c = to_c
1633 if c != null then return c.to_s
1634 return "unknown"
1635 end
1636 end