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