tests: Added test for basename and related methods
[nit.git] / contrib / memory / src / memory.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # A game of memory using shapes and colors
16 #
17 # # Features and TODO
18 #
19 # * [X] Various shapes, colors and sounds
20 # * [X] 3 difficulty modes
21 # * [X] Saved high scores
22 # * [ ] Level selection
23 #
24 # The remaining issues are
25 #
26 # * Crappy event system
27 # * Crappy UI element placement
28 module memory is
29 app_name("Memorize Shapes and Colors")
30 app_version(0, 1, git_revision)
31 end
32
33 import mnit
34 import app::audio
35 import mnit::opengles1
36 import app::data_store
37
38 import drawing
39
40 # A figure to click on
41 class Button
42 # The place, starting from 0.
43 # Will be used to derive the display place.
44 var place: Int
45
46 # The color of the figure
47 var color: Color
48
49 # The shape of the figure
50 var shape: Image
51
52 # The sound of the figure
53 var sound: Sound
54
55 # x-coordinate on the display
56 var x: Float = 0.0
57 # y-coordinate on the display
58 var y: Float = 0.0
59 # width on the display
60 var w: Float = 0.0
61 # height the display
62 var h: Float = 0.0
63
64 # Event time to live (from 1.0 downto 0.0)
65 var ttl: Float = 0.0
66
67 # Is there a big error on the button?
68 var error = false
69
70 # The initial position (according to shuffle)
71 var from: Pos is noinit
72
73 # The current path if shuffling
74 var path: nullable BPath = null
75
76 # The second path if hard shuffling
77 var path2: nullable BPath = null
78
79 # Is there an hard shuffling?
80 var hard = false
81
82 # The optional text on the button (in the menu)
83 var text: nullable Image = null
84
85 # The color of the text
86 var text_color: nullable Color = null
87
88 # The high score on the menu button
89 var text_max: Int = 0
90
91 # Draw on the display
92 fun blit_on(display: Display)
93 do
94 if ttl > 0.0 then
95 ttl -= 0.1
96 if ttl <= 0.0 then
97 ttl = 0.0
98 path = path2
99 path2 = null
100 if path != null then ttl = path.duration
101 error = false
102 end
103 end
104
105 var x = self.x
106 var y = self.y
107 var p = 0.0
108 if ttl > 0.0 then
109 if path != null then
110 var pos = to_pos
111 path.update(pos, ttl)
112 x = pos.x
113 y = pos.y
114 if hard then
115 p = ttl/5.0
116 if path2 != null then
117 p = 1.0 - p
118 end
119 end
120 else if error then
121 # nothing
122 else
123 y -= ttl * h / 10.0
124 end
125 end
126
127 if not app.player then
128 p = 0.2.lerp(p, 1.0)
129 end
130
131 color.set(display, p)
132 display.blit_centered(shape, x, y)
133 var text = self.text
134 if text != null then
135 text.scale = shape.scale
136 text_color.set(display, p)
137 display.blit_centered(text, x, y - h/8.0)
138 if text_max > 0 then
139 app.blit_number(text_max, app.scale, x, y + h/8.0)
140 end
141 end
142 if display isa Opengles1Display then
143 display.reset_color
144 end
145 if error then
146 app.drawing.error.scale = app.scale
147 display.blit_centered(app.drawing.error, x, y)
148 end
149 end
150
151 redef fun to_s do
152 return "{place},{color},{shape},{sound}"
153 end
154
155 # Check collision
156 fun has(x,y: Float): Bool
157 do
158 return (self.x - x).abs*2.0 <= w and (self.y - y).abs*2.0 <= h
159 end
160
161 # Return a new pos centered on the button
162 fun to_pos: Pos do return new Pos(x, y)
163 end
164
165 # A rbg color
166 class Color
167 # red (from 0.0 to 1.0)
168 var r: Float
169 # green (from 0.0 to 1.0)
170 var g: Float
171 # blue (from 0.0 to 1.0)
172 var b: Float
173
174 # Globally change the color of the display.
175 # The color will be used for the next blit operations.
176 # The color of the display has to be reseted manually (see `Opengles1Display::reset_color`).
177 fun set(display: Display, p: Float)
178 do
179 if display isa Opengles1Display then
180 display.color(p.lerp(r,1.0),p.lerp(g,1.0),p.lerp(b,1.0),p.lerp(1.0,0.0))
181 end
182 end
183 end
184
185 # A point in the display coordinates
186 class Pos
187 # x coordinate
188 var x: Float
189 # y coordinate
190 var y: Float
191 redef fun to_s do return "({x},{y})"
192 end
193
194 # A cubic Bézier path between two points with two handles.
195 class BPath
196 # The origin point
197 var from: Pos
198 # The handle of the origin point
199 var from_handle: Pos
200 # The handle of the destination point
201 var to_handle: Pos
202 # The destination point
203 var to: Pos
204 # The duration on the path
205 var duration: Float
206
207 # Update the coordinates of `cursor` for an absolute time to destination `ttd`
208 fun update(cursor: Pos, ttd: Float)
209 do
210 var p = 1.0 - ttd / duration
211 if p <= 0.0 then
212 cursor.x = from.x
213 cursor.y = from.y
214 return
215 end
216 if p >= 1.1 then
217 cursor.x = to.x
218 cursor.y = to.y
219 end
220 var bx = p.cerp(from.x, from_handle.x, to_handle.x, to.x)
221 var by = p.cerp(from.y, from_handle.y, to_handle.y, to.y)
222 cursor.x = bx
223 cursor.y = by
224 end
225 end
226
227 redef class App
228
229 # # Assets and resources
230
231 # All the images assets
232 var drawing = new DrawingImages
233
234 # Array of all available colors for the figures
235 var colors = new Array[Color]
236
237 # Array of all available shapes for the figures
238 var shapes = new Array[Image]
239
240 # Array of all available sounds for the figures
241 var sounds = new Array[Sound]
242
243 # The sound to play on error (error)
244 var snd_penalty: Sound is noautoinit
245
246 # The sound of other ui element
247 var snd_click: Sound is noautoinit
248
249 redef fun on_create
250 do
251 colors.clear
252 colors.add new Color(0.9, 0.6, 0.0)
253 colors.add new Color(0.6, 0.0, 0.9)
254 colors.add new Color(0.6, 0.5, 0.4)
255 colors.add new Color(1.0, 0.0, 0.0)
256 colors.add new Color(1.0, 1.0, 0.0)
257 colors.add new Color(1.0, 0.0, 1.0)
258 colors.add new Color(0.0, 1.0, 0.0)
259 colors.add new Color(0.0, 1.0, 1.0)
260 colors.add new Color(0.0, 0.0, 1.0)
261
262 drawing.load_all(self)
263 shapes.clear
264 shapes.add drawing.circle
265 shapes.add drawing.rect
266 shapes.add drawing.cross
267 shapes.add drawing.penta
268 shapes.add drawing.star
269 shapes.add drawing.triangle
270 shapes.add drawing.heart
271 shapes.add drawing.diamond
272 shapes.add drawing.moon
273 shapes.add drawing.spiral
274
275 number_images = new NumberImages(drawing.n)
276
277 sounds.clear
278 sounds.add new Sound("bing.wav")
279 sounds.add new Sound("boing.wav")
280 sounds.add new Sound("cymbal.wav")
281 sounds.add new Sound("dart.wav")
282 sounds.add new Sound("duh.wav")
283 sounds.add new Sound("grunt.wav")
284 sounds.add new Sound("honkhonk.wav")
285 sounds.add new Sound("line_end.wav")
286 sounds.add new Sound("squishy-hit.wav")
287 sounds.add new Sound("woodthunk.wav")
288 sounds.add new Sound("whip.wav")
289
290 snd_penalty = new Sound("penalty.wav")
291 snd_click = new Sound("click.wav")
292
293 # Force load the sounds. Required because bug #1728
294 for s in sounds do s.load
295 snd_penalty.load
296
297 is_menu = data_store["game"] != true
298 mode = data_int("mode") or else 0
299 current_level = data_int("level") or else 0
300
301 max_levels[0] = data_int("max_0") or else 0
302 max_levels[1] = data_int("max_1") or else 0
303 max_levels[2] = data_int("max_2") or else 0
304
305 print "max_levels: {max_levels}"
306
307 reload = new Button(-1, new Color(1.0,1.0,1.0), drawing.reload, snd_click)
308
309 if is_menu then
310 new_menu
311 else
312 new_game
313 end
314 end
315
316 # Get a positive numeric value from the store
317 fun data_int(name: String): nullable Int
318 do
319 var x = data_store[name]
320 if x isa Int then return x else return null
321 end
322
323 # # Level information
324
325 # Number of buttons for the next game
326 var size = 5
327
328 # Length of the memory sequence for the next game
329 var length = 5
330
331 # Do a hard deal?
332 var hard_deal = false
333
334 # No shuffle (0), easy shuffle (1), or hard shuffle (2)?
335 var shuffling = 0
336
337 # Is a new deal make on replay?
338 # If true, a new set of figures and a new sequence is produced
339 # If false, the same is reused.
340 var deal_on_replay = true
341
342 # Current buttons in the game
343 var buttons = new Array[Button]
344
345 # The sequence of the buttons to memorize
346 var level = new Array[Button]
347
348 # The number of errors (crosses) in the current level. (in [0..3])
349 var error = 0
350
351 # Is the player playing?
352 # If false it means that the game is showing the sequence to memorize
353 var player = false
354
355 # Next button on the level (to show or guess according to `player`)
356 var cpt = 0
357
358 # Time to live before the next event
359 var ttl = 0.0
360
361 # Are we in the menu?
362 var is_menu = true
363
364 # In the end of game, is this a win of a lose?
365 var is_win = false
366
367 # Reset everything and create a menu
368 fun new_menu
369 do
370 is_menu = true
371 size = 3
372 length = 0
373 shuffling = 0
374
375 data_store["game"] = false
376
377 colors.shuffle
378 shapes.shuffle
379 sounds.shuffle
380
381 buttons.clear
382 for i in [0..size[ do
383 var b = new Button(i, colors[i], shapes[i], sounds[i])
384 buttons.add b
385 b.text = drawing.hard[i]
386 b.text_color = colors[3+i]
387 b.text_max = max_levels[i]
388 end
389
390 # Start the scene
391 start_scene
392 end
393
394 # The current mode: easy (0), medium (1), hard (2)
395 var mode = 0
396
397 # The current level (from 0)
398 var current_level = 0
399
400 # Hight scores of each mode
401 var max_levels: Array[Int] = [0, 0, 0]
402
403 # Reset everything and create a new game using `mode` and `level`
404 fun new_game
405 do
406 print "Next game: mode={mode} level={current_level}"
407 data_store["game"] = true
408 data_store["mode"] = mode
409 data_store["level"] = current_level
410 if max_levels[mode] < current_level then
411 max_levels[mode] = current_level
412 data_store["max_{mode}"] = current_level
413 end
414
415 if mode == 0 then
416 hard_deal = false
417 shuffling = 0
418 deal_on_replay = false
419 size = 2
420 length = 1
421 else if mode == 1 then
422 hard_deal = false
423 shuffling = 1
424 deal_on_replay = true
425 size = 3
426 length = 3
427 else
428 hard_deal = true
429 shuffling = 2
430 deal_on_replay = true
431 size = 3
432 length = 3
433 end
434 for i in [0..current_level[ do
435 length += 1
436 if length > size + 2 then
437 size += 1
438 length -= 1
439 end
440 if size > 16 then size = 16
441 end
442
443 deal_game
444 end
445
446 # Reset the buttons and deal a new game using `size` and `length`
447 fun deal_game
448 do
449 is_menu = false
450
451 # Randomize the deal
452 colors.shuffle
453 shapes.shuffle
454 sounds.shuffle
455
456 # Setup the figure
457 buttons.clear
458 if not hard_deal then
459 # With the easy deal, each button is easily distinguishable
460 for i in [0..size[ do
461 var b = new Button(i, colors[i%colors.length], shapes[i%shapes.length], sounds[i%sounds.length])
462 buttons.add b
463 end
464 else
465 # With the hard deal, use overlapping combinations of colors and shapes
466 var sqrt = size.to_f.sqrt
467 var ncol = sqrt.floor.to_i
468 var nsha = sqrt.ceil.to_i
469 while ncol*nsha < size do ncol += 1
470
471 # Randomly swap the numbers of colors/shapes
472 if 2.rand == 0 then
473 var t = ncol
474 ncol = nsha
475 nsha = t
476 end
477
478 # Deal combinations (up to `size`)
479 for i in [0..ncol[ do
480 for j in [0..nsha[ do
481 if buttons.length >= size then break
482 var b = new Button(buttons.length, colors[i], shapes[j], sounds.rand)
483 buttons.add b
484 end
485 end
486
487 # A last shuffle to break the colors/shapes grid
488 buttons.shuffle
489 end
490
491 # Deal the level (i.e. sequence to memorize)
492 # To increase distribution, determinate a maximum number of repetition
493 # of a single button
494 var rep = (length.to_f / size.to_f).ceil.to_i
495 var pool = buttons * rep
496 pool.shuffle
497
498 level.clear
499 for i in [0..length[ do
500 level.add pool[i]
501 end
502
503 print "newgame size={size} length={length}"
504
505 # Start the scene
506 start_scene
507 end
508
509 # Cause a replay on the same level
510 # On easy mode, the same level is replayed exactly
511 # On other modes, a new deal is made
512 fun replay_game
513 do
514 if deal_on_replay then
515 deal_game
516 else
517 start_scene
518 end
519 end
520
521 # Reset the state of the scene and start with `fly_in`
522 fun start_scene
523 do
524 player = false
525 cpt = -1
526 path = null
527 error = 0
528
529 # Ask for a redraw
530 first_frame = true
531 end
532
533 # # Placement and moves
534
535 # Locations used to place buttons on the screen
536 private var locations: Array[Array[Float]] = [
537 [0.0, 1.0],
538 [0.0, 1.0, 0.5],
539 [0.0, 1.0, 0.0, 1.0],
540 [0.0, 1.0, 2.0, 0.5, 1.5],
541 [0.0, 1.0, 2.0, 0.0, 1.0, 2.0],
542 [0.5, 1.5, 0.0, 1.0, 2.0, 0.5, 1.5],
543 [0.0, 1.0, 2.0, 0.0, 2.0, 0.0, 1.0, 2.0],
544 [0.0, 1.0, 2.0, 0.0, 1.0, 2.0, 0.0, 1.0, 2.0],
545 [0.5, 1.5, 2.5, 0.0, 1.0, 2.0, 3.0, 0.5, 1.5, 2.5],
546 [0.0, 1.0, 2.0, 3.0, 0.0, 1.5, 3.0, 0.0, 1.0, 2.0, 3.0],
547 [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0],
548 [0.5, 1.5, 2.5, 0.0, 1.5, 3.0, 0.0, 1.0, 2.0, 3.0, 0.5, 1.5, 2.5],
549 [0.5, 1.5, 2.5, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.5, 1.5, 2.5],
550 [0.5, 1.5, 2.5, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0],
551 [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0]]
552
553
554 # The scale of the figures.
555 # According to the screen dimensions and the number of figures
556 var scale = 0.0
557
558 # The scale of the UI
559 # According to the screen dimensions
560 var ui_scale = 0.0
561
562 # Compute then location on the display for each button
563 #
564 # The method can be called when there is a change in the buttons (or the display).
565 fun locate(display: Display)
566 do
567 # The locations depend of the number of buttons (from 2 to 9)
568 var n = buttons.length
569 var locs = locations[n-2]
570 var columns = if n <= 4 then 2 else if n <= 9 then 3 else 4
571 var rows = if n <= 2 then 1 else if n <= 6 then 2 else if n <= 12 then 3 else 4
572
573 # Compute basic dimensions according to the screen
574 var slotw = display.width / columns
575 var sloth = display.height / rows
576 var subw = slotw - slotw/5
577 var subh = sloth - sloth/5
578
579 # Compute the figure scale
580 var img = drawing.circle
581 var xs = subw.to_f / img.width.to_f
582 var ys = subh.to_f / img.height.to_f
583 scale = xs.min(ys)
584
585 # Compute the UI scale
586 xs = display.width.to_f / img.width.to_f
587 ys = display.height.to_f / img.height.to_f
588 ui_scale = xs.min(ys) / 4.0
589
590 var last = -1.0
591 var row = 0.0
592 var cpt = 0
593 for b in buttons do
594 b.place = cpt
595 var col = locs[cpt]
596 if col <= last then
597 row += 1.0
598 end
599 last = col
600
601 b.x = (col + 0.5) * slotw.to_f
602 b.y = (row + 0.5) * sloth.to_f
603 img = b.shape
604 img.scale = scale
605 b.w = (img.width.to_f * scale)
606 b.h = (img.height.to_f * scale)
607
608 cpt += 1
609 end
610
611 left.x = -150.0 * scale
612 left.y = (display.height / 2).to_f
613 right.x = display.width.to_f + 150.0 * scale
614 right.y = left.y
615
616 # Other UI elements
617
618 if not is_menu then
619 var reload = self.reload
620 drawing.reload.scale = ui_scale
621 reload.x = display.width.to_f - (drawing.reload.width.to_f / 2.0 * 1.2 ) * ui_scale
622 reload.y = drawing.reload.height.to_f / 2.0 * 1.2 * ui_scale
623 reload.w = drawing.reload.width.to_f * ui_scale
624 reload.h = drawing.reload.height.to_f * ui_scale
625 end
626 end
627
628 # The origin point of the cursor on the left
629 var left = new Pos(0.0, 0.0)
630
631 # The destination point of the cursor on the right
632 var right = new Pos(0.0, 0.0)
633
634 # The current cursor position
635 var cursor = new Pos(0.0, 0.0)
636
637 # The current cursor path
638 var path: nullable BPath = null
639
640 # The reload button
641 var reload: Button is noautoinit
642
643 # Safe point for a cursor on the i-th button of the level
644 fun path_pos(i: Int): Pos
645 do
646 if i < 0 then return left
647 if i >= level.length then return right
648 return level[i].to_pos
649 end
650
651 # A random point outside of the screen
652 fun far_away(display: Display): Pos
653 do
654 var a = (2.0*pi).rand
655 var w = display.width.to_f / 2.0
656 var h = display.height.to_f / 2.0
657 var x = w + a.cos * w * 1.8
658 var y = h + a.sin * h * 1.8
659 return new Pos(x, y)
660 end
661
662 # Create a BPath between two point with some nice handle values
663 fun new_path(from, to: Pos, ttl: Float): BPath
664 do
665 var a = atan2(to.y-from.y, to.x-from.x)
666 a += pi * (2.0.rand - 1.0)
667 var radius = 300.0 * scale
668 var fh = new Pos(from.x + a.cos*radius, from.y + a.sin*radius)
669 #var th = new Pos(to.x - a.cos*radius, to.y - a.sin*radius)
670 var path = new BPath(from, fh, to, to, ttl)
671 return path
672 end
673
674 # Initial placement of buttons
675 fun fly_in(display: Display)
676 do
677 for b in buttons do
678 var from = far_away(display)
679 var to = b.to_pos
680 var path = new_path(from, to, 5.0)
681 b.path = path
682 b.ttl = 5.0
683 end
684 ttl = 6.0
685 end
686
687 # Final leaving of buttons
688 fun fly_out(display: Display)
689 do
690 for b in buttons do
691 var from = b.to_pos
692 var to = far_away(display)
693 b.x = to.x
694 b.y = to.y
695 var path = new_path(from, to, 5.0)
696 b.path = path
697 b.ttl = 5.0
698 b.hard = false
699 end
700 ttl = 6.0
701 end
702
703 # Randomly permute the content of `buttons` such that no element appears in its original position.
704 fun derangement
705 do
706 # The simplest algorithm is to shuffle until no buttons is at the same place
707 # This is also quite efficient and converges extremely quickly
708 var redo = true
709 while redo do
710 redo = false
711 buttons.shuffle
712 for i in [0..size[ do
713 if i == buttons[i].place then
714 redo = true
715 break
716 end
717 end
718 end
719 end
720
721 # Shuffling the place of each button on the screen
722 fun shuffle(display: Display)
723 do
724 for b in buttons do
725 b.from = b.to_pos
726 end
727
728 derangement
729
730 locate(display)
731 for b in buttons do
732 var from = b.from
733 var to = b.to_pos
734 #print "shuffle move {b.place}: {from} -> {to}"
735 b.path = new_path(from, to, 5.0)
736 b.ttl = 5.0
737 end
738 ttl = 5.0
739 end
740
741 # Shuffle the place of each button in a hard way
742 fun hard_shuffle(display: Display)
743 do
744 for b in buttons do
745 b.from = b.to_pos
746 b.hard = true
747 end
748
749 derangement
750
751 locate(display)
752 for b in buttons do
753 var from = b.from
754 var to = b.to_pos
755 var midx = display.width.to_f / 2.0
756 var midy = display.height.to_f / 2.0
757 var mid = new Pos(midx, midy)
758 #print "shuffle move {b.place}: {from} -> {to}"
759 b.path = new_path(from, mid, 5.0)
760 b.path2 = new_path(mid, to, 5.0)
761 b.ttl = 5.0
762 end
763 ttl = 5.0
764 end
765
766 # Setup the next cursor path
767 fun setpath
768 do
769 if is_menu then return
770 var from = path_pos(cpt-1)
771 var to = path_pos(cpt)
772 #print "cursor {cpt-1}->{cpt}: {from} -> {to}"
773 path = new_path(from, to, 4.0)
774 cursor.x = from.x
775 cursor.y = from.y
776 ttl = 5.0
777 end
778
779 # Main loop, drawing and inputs
780
781 # Flag used to ask for a (re-)computation of the display layout
782 private var first_frame = true
783
784 redef fun frame_core(display)
785 do
786 if first_frame then
787 locate(display)
788 if cpt == -1 then
789 fly_in(display)
790 end
791 first_frame = false
792 end
793
794 # Clear the screen
795 display.clear(1.0, 1.0, 1.0)
796
797 # Manage events
798 # This is a crappy ad hoc organic implementation
799 if not player then
800 ttl -= 0.1
801 if path != null then path.update(cursor, ttl)
802 if ttl <= 0.0 then
803 ttl = 0.0
804 if is_menu then
805 # Menu animation is over
806 player = true
807 else if cpt < 0 then
808 # Level place animation is over
809 cpt += 1
810 setpath
811 else if cpt < level.length then
812 # The cursor is playing
813 var b = level[cpt]
814 b.ttl = 1.0
815 b.sound.play
816 cpt += 1
817 setpath
818 else if cpt == level.length then
819 # The cursor is out, run the shuffle
820 path = null
821 if shuffling == 1 then
822 shuffle(display)
823 else if shuffling > 1 then
824 hard_shuffle(display)
825 end
826 cpt += 1
827 else
828 # The shuffling is over, start playing
829 player = true
830 cpt = 0
831 end
832 end
833 else if ttl > 0.0 then
834 ttl -= 0.1
835 if ttl <= 0.0 then
836 ttl = 0.0
837 if cpt == level.length then
838 fly_out(display)
839 cpt += 1
840 else
841 if is_menu then
842 new_game
843 else if is_win then
844 current_level += 1
845 new_game
846 else
847 replay_game
848 end
849 end
850 end
851 end
852
853 # Display each button
854 for b in buttons do
855 b.blit_on(display)
856 end
857
858 # Display the cursor
859 if path != null then
860 drawing.cursor.scale = scale
861 display.blit(drawing.cursor, cursor.x, cursor.y)
862 end
863
864 if not is_menu then
865 blit_number(current_level, ui_scale, 10.0 * scale, 10.0 * scale)
866 reload.blit_on(display)
867 end
868 end
869
870 # Blit a number somewhere
871 fun blit_number(number: Int, scale: Float, x, y: Float)
872 do
873 for img in number_images.imgs do img.scale = scale
874 display.blit_number(number_images, number, x.to_i, y.to_i)
875 end
876
877 # Images with the numbers
878 private var number_images: NumberImages is noautoinit
879
880 # A player click on a button
881 fun action(b: Button)
882 do
883 if is_menu then
884 b.sound.play
885 mode = b.place
886 current_level = 0
887 ttl = 0.1
888 cpt = level.length
889 is_win = true
890 return
891 end
892 if cpt >= level.length then return
893 if b == level[cpt] then
894 b.sound.play
895 b.ttl = 1.0
896
897 cpt += 1
898 if cpt >= level.length then
899 is_win = true
900 print "Won!"
901 ttl = 2.0
902 end
903 else
904 error += 1
905 print "Err {error}"
906 b.error = true
907 b.ttl = 3.0
908 snd_penalty.play
909 if error > 2 then
910 is_win = false
911 print "Lose!"
912 for b2 in buttons do
913 b2.error = true
914 b2.ttl = 3.0
915 end
916 ttl = 3.0
917 cpt = level.length
918 end
919 end
920 end
921
922 redef fun input(ie)
923 do
924 # Quit?
925 if ie isa QuitEvent then
926 quit = true
927 return true
928 end
929
930 # On click (or tap)
931 if ie isa PointerEvent and ie.depressed then
932 if player then
933 for b in buttons do
934 if b.has(ie.x, ie.y) then
935 action(b)
936 return true
937 end
938 end
939 end
940
941 if not is_menu then
942 if reload.has(ie.x, ie.y) then
943 reload.sound.play
944 reload.ttl = 1.0
945 replay_game
946 return true
947 end
948 end
949 end
950
951 # Special commands
952 if ie isa KeyEvent and ie.is_down then
953 var c = ie.name
954
955 if c == "4" or c == "escape" then
956 # 4 is *back* on android
957 if is_menu then
958 # quit = true # broken
959 new_menu
960 else
961 new_menu
962 end
963 return true
964 end
965
966 if is_menu then
967 return false
968 end
969
970 if c == "[+]" or c == "=" then
971 # [+] is keypad `+`
972 size += 1
973 deal_game
974 return true
975 else if c == "[-]" or c == "-" then
976 size -= 1
977 deal_game
978 return true
979 else if c == "[*]" or c == "]" then
980 length += 1
981 deal_game
982 return true
983 else if c == "[/]" or c == "[" then
984 length -= 1
985 deal_game
986 return true
987 else if c == "space" or c == "82" then
988 # 82 is *menu* on android
989 reload.sound.play
990 reload.ttl = 1.0
991 replay_game
992 return true
993 end
994
995 print "got keydown: `{c}`"
996 end
997
998 return false
999 end
1000 end