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