1 /*
2  *    This file is part of darktable,
3  *    Copyright (C) 2016-2021 darktable developers.
4  *
5  *    darktable is free software: you can redistribute it and/or modify
6  *    it under the terms of the GNU General Public License as published by
7  *    the Free Software Foundation, either version 3 of the License, or
8  *    (at your option) any later version.
9  *
10  *    darktable is distributed in the hope that it will be useful,
11  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *    GNU General Public License for more details.
14  *
15  *    You should have received a copy of the GNU General Public License
16  *    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "control/control.h"
20 #include "views/knight_font.h"
21 #include "views/view.h"
22 #include "views/view_api.h"
23 
24 DT_MODULE(1)
25 
26 #pragma GCC diagnostic ignored "-Wshadow"
27 
28 // tunables for how the game looks and reacts
29 #define ASPECT_RATIO 0.875 // the playground
30 #define LOOP_SPEED 50      // ms between event loop calls
31 #define STEP_SIZE 0.25     // factor wrt. sprite size for movement
32 
33 #define MAX_ALIEN_SHOTS 3 // max shots in the air from the big alien block. mystery goes extra
34 #define N_ALIENS_X 11     // number of aliens in the block in x direction
35 #define N_ALIENS_Y 5      // number of aliens in the block in y direction
36 #define ALIEN_DEATH_TIME                                                                                     \
37   (0.3 * 1000.0 / LOOP_SPEED)     // number frames to show explosions + freeze alien movement on hit
38 #define ALIEN_SHOT_PROBABILITY 20 // rand() % ALIEN_SHOT_PROBABILITY == 0 is the test
39 
40 #define LETTER_WIDTH (1.0 / 45.0)   // scale font so that 45 letters fit next to each other
41 #define LETTER_SPACING (1.0 / 28.0) // space text so that 28 letters fit next to each other
42 #define LETTER_HEIGHT (LETTER_WIDTH * FONT_HEIGHT / (float)FONT_WIDTH)
43 #define CELL_WIDTH (1.0 / 20.0)        // size factor for when nothing else is appropriate
44 #define GAP 1.5                        // space between aliens in the block + lifes
45 #define SHOT_LENGTH (0.4 * CELL_WIDTH) // length of the visible shot graphics
46 
47 #define TOP_MARGIN (5 * LETTER_HEIGHT)                         // start of the alien block from the top
48 #define BOTTOM_MARGIN (1.0 - 2 * LETTER_HEIGHT * ASPECT_RATIO) // ground plane
49 #define MYSTERY_SHIP_Y (3 * LETTER_HEIGHT)                     // height where the UFO flies
50 #define PLAYER_Y 0.85                                          // height where the player moves
51 
52 // *_[WIDTH|HEIGHT] is pixel size of the data
53 // *_TARGET_[WIDTH|HEIGHT] is size wrt. playground (0..1)
54 #define TARGET_HEIGHT(n) (((float)n##_TARGET_WIDTH / n##_WIDTH * n##_HEIGHT * ASPECT_RATIO))
55 
56 // clang-format off
57 
58 #define ALIEN_WIDTH 6 // pixel size of the bitmaps
59 #define ALIEN_HEIGHT 6
60 #define ALIEN_TARGET_WIDTH CELL_WIDTH
61 #define ALIEN_TARGET_HEIGHT TARGET_HEIGHT(ALIEN)
62 static const uint8_t alien[2][ALIEN_WIDTH * ALIEN_HEIGHT] = {
63   // first animation frame
64   {
65     0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
66     0xff, 0x00, 0xff, 0xff, 0x00, 0xff,
67     0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
68     0x00, 0xff, 0x00, 0x00, 0xff, 0x00,
69     0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
70     0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
71   },
72   // second animation frame
73   {
74     0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
75     0xff, 0x00, 0xff, 0xff, 0x00, 0xff,
76     0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
77     0x00, 0xff, 0x00, 0x00, 0xff, 0x00,
78     0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
79     0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
80   },
81 };
82 
83 #define PLAYER_WIDTH 13
84 #define PLAYER_HEIGHT 8
85 #define PLAYER_TARGET_WIDTH (1.2 * CELL_WIDTH)
86 #define PLAYER_TARGET_HEIGHT TARGET_HEIGHT(PLAYER)
87 static const uint8_t player[3][PLAYER_WIDTH * PLAYER_HEIGHT] = {
88   // normal graphic
89   {
90     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
91     0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
92     0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
93     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
94     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
95     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
96     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
97     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
98   },
99   // explosion 1
100   {
101     0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
102     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00,
103     0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff,
104     0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00,
105     0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00,
106     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
107     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
108     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
109   },
110   // explosion 2
111   {
112     0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
113     0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00,
114     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00,
115     0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
116     0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00,
117     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff,
118     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
119     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
120   },
121 };
122 
123 #define MYSTERY_SHIP_WIDTH 16
124 #define MYSTERY_SHIP_HEIGHT 7
125 #define MYSTERY_SHIP_TARGET_WIDTH CELL_WIDTH
126 #define MYSTERY_SHIP_TARGET_HEIGHT TARGET_HEIGHT(MYSTERY_SHIP)
127 static const uint8_t mystery_ship[MYSTERY_SHIP_WIDTH * MYSTERY_SHIP_HEIGHT] = {
128   0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
129   0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
130   0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
131   0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00,
132   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
133   0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
134   0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
135 };
136 
137 #define BUNKER_WIDTH 22
138 #define BUNKER_HEIGHT 16
139 #define BUNKER_TARGET_WIDTH (1.0 / 9.0)
140 #define BUNKER_TARGET_HEIGHT TARGET_HEIGHT(BUNKER)
141 #define BUNKER_Y (PLAYER_Y - PLAYER_TARGET_HEIGHT - BUNKER_TARGET_HEIGHT)
142 static const uint8_t bunker[BUNKER_WIDTH * BUNKER_HEIGHT] = {
143   0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
144   0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
145   0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
146   0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
147   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
148   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
149   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
150   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
151   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
152   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
153   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
154   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
155   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
156   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
157   0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
158   0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
159 };
160 
161 
162 #define EXPLOSION_WIDTH 12
163 #define EXPLOSION_HEIGHT 12
164 // keep this in sync to the bunker so that the damages look good later
165 #define EXPLOSION_TARGET_WIDTH (BUNKER_TARGET_WIDTH / BUNKER_WIDTH * EXPLOSION_WIDTH)
166 #define EXPLOSION_TARGET_HEIGHT TARGET_HEIGHT(EXPLOSION)
167 #define EXPLOSION_ALIEN 0
168 #define EXPLOSION_MYSTERY 1
169 #define EXPLOSION_SHOT 2
170 #define EXPLOSION_TOP 3
171 #define EXPLOSION_BOTTOM 4
172 #define EXPLOSION_AMOUNT 5
173 static const uint8_t explosions[EXPLOSION_AMOUNT][EXPLOSION_WIDTH * EXPLOSION_HEIGHT] = {
174   // aliens
175   {
176     0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
177     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
178     0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
179     0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
180     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
181     0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
182     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
183     0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
184     0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
185     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
186     0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
187     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
188   },
189   // mystery
190   {
191     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
192     0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
193     0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
194     0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff,
195     0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
196     0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
197     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
198     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
199     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
200     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
201     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
202     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
203   },
204   // shot
205   {
206     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
207     0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
208     0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
209     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00,
210     0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
211     0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00,
212     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
213     0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
214     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
215     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
216     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
217     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
218   },
219   // on the top
220   {
221     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00,
222     0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
223     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00,
224     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
225     0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
226     0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
227     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
228     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
229     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
230     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
231     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
232     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
233   },
234   // on the bottom
235   {
236     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
237     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
238     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
239     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
240     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
241     0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
242     0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
243     0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00,
244     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
245     0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
246     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
247     0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
248   },
249 };
250 
251 // clang-format on
252 
253 
254 typedef struct dt_knight_shot_t
255 {
256   gboolean active;
257   float x, y, start, direction;
258 } dt_knight_shot_t;
259 
260 typedef struct dt_knight_alien_t
261 {
262   gboolean alive;
263   float x, y;
264   int frame;
265   int points;
266 } dt_knight_alien_t;
267 
268 typedef struct dt_knight_explosion_t
269 {
270   float x, y, target_width;
271   int ttl;
272   cairo_pattern_t *sprite;
273 } dt_knight_explosion_t;
274 
275 typedef struct dt_knight_t
276 {
277   // control state
278   enum
279   {
280     INTRO,
281     START,
282     GAME,
283     WIN,
284     LOSE
285   } game_state;
286   unsigned int animation_loop; // animation frame counter for the non-interactive states
287   guint event_loop;
288   int freeze; // frames until the freeze is over
289   gboolean total_freeze, super_total_final_freeze;
290   GList *explosions;
291   int move; // we handle movement in the event loop. using key_pressed suffers from X's key repeat + delay
292 
293   // visible game state
294   int credit;
295   int lifes;
296   unsigned int score_1, score_2, high_score;
297 
298   // other state
299   float player_x;
300   dt_knight_shot_t player_shot;
301 
302   dt_knight_alien_t aliens[N_ALIENS_X * N_ALIENS_Y];
303   int n_aliens;
304   enum
305   {
306     ALIEN_LEFT,
307     ALIEN_RIGHT,
308     ALIEN_DOWN_THEN_LEFT,
309     ALIEN_DOWN_THEN_RIGHT
310   } alien_direction;
311   int alien_next_to_move;
312   dt_knight_shot_t alien_shots[MAX_ALIEN_SHOTS + 1]; // the mystery ship can shoot, too, so it's +1
313   int n_alien_shots;
314   float mystery_ship_x;
315   int time_until_mystery_ship;
316   float mystery_ship_potential_shot_x;
317 
318   // buffers to free in the end
319   GList *bufs, *surfaces, *patterns;
320   // sprites
321   cairo_pattern_t *alien_sprite[2];
322   cairo_pattern_t *player_sprite[3];
323   cairo_pattern_t *mystery_sprite;
324   cairo_pattern_t *explosion_sprite[EXPLOSION_AMOUNT];
325   cairo_pattern_t **letters;
326   cairo_pattern_t *bunker_sprite[4];
327   // needed to add explosions to the bunkers
328   int bunker_stride;
329   uint8_t *bunker_buf[4];
330 } dt_knight_t;
331 
name(const dt_view_t * self)332 const char *name(const dt_view_t *self)
333 {
334   return _("good knight");
335 }
336 
view(const dt_view_t * self)337 uint32_t view(const dt_view_t *self)
338 {
339   return DT_VIEW_KNIGHT;
340 }
341 
flags()342 uint32_t flags()
343 {
344   return VIEW_FLAGS_HIDDEN;
345 }
346 
347 // turn a monochrome pixel buffer into a cairo pattern for later usage
_new_sprite(const uint8_t * data,const int width,const int height,int * _stride,GList ** bufs,GList ** surfaces,GList ** patterns)348 static inline cairo_pattern_t *_new_sprite(const uint8_t *data, const int width, const int height,
349                                            int *_stride, GList **bufs, GList **surfaces, GList **patterns)
350 {
351   const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_A8, width);
352   uint8_t *buf = (uint8_t *)malloc((size_t)stride * height);
353   for(int y = 0; y < height; y++) memcpy(&buf[y * stride], &(data[y * width]), sizeof(uint8_t) * width);
354   cairo_surface_t *surface = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_A8, width, height, stride);
355   cairo_pattern_t *pattern = cairo_pattern_create_for_surface(surface);
356   cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
357   *bufs = g_list_append(*bufs, buf);
358   *surfaces = g_list_append(*surfaces, surface);
359   *patterns = g_list_append(*patterns, pattern);
360   if(_stride) *_stride = stride;
361   return pattern;
362 }
363 
364 // spawn a mystery ship every 25±3 seconds
_get_mystery_timeout()365 static int _get_mystery_timeout()
366 {
367   int r = (rand() % 7) - 3;
368   return (25.0 + r) * 1000.0 / LOOP_SPEED;
369 }
370 
371 // reset most but not all values of dt_knight_t
_reset_board(dt_knight_t * d)372 static void _reset_board(dt_knight_t *d)
373 {
374   d->player_x = 0.0;
375   d->player_shot.active = FALSE;
376 
377   for(int y = 0; y < N_ALIENS_Y; y++)
378     for(int x = 0; x < N_ALIENS_X; x++)
379     {
380       int i = y * N_ALIENS_X + x;
381       d->aliens[i].x = x * ALIEN_TARGET_WIDTH * GAP + 0.5 - (N_ALIENS_X - 1) * 0.5 * ALIEN_TARGET_WIDTH * GAP
382                        - 0.5 * ALIEN_TARGET_WIDTH;
383       d->aliens[i].y = y * ALIEN_TARGET_HEIGHT * GAP + TOP_MARGIN;
384       d->aliens[i].alive = TRUE;
385       d->aliens[i].frame = 0;
386       d->aliens[i].points = ((N_ALIENS_Y - y - 1) / 2 + 1) * 10; // bottom 2: 10, middle 2: 20, top: 30
387     }
388   d->n_aliens = N_ALIENS_Y * N_ALIENS_X;
389   d->alien_direction = ALIEN_RIGHT;
390   d->alien_next_to_move = 0 + (N_ALIENS_Y - 1) * N_ALIENS_X;
391   for(int i = 0; i < MAX_ALIEN_SHOTS + 1; i++) d->alien_shots[i].active = FALSE;
392   d->n_alien_shots = 0;
393   d->mystery_ship_x = -1.0;
394   d->time_until_mystery_ship = _get_mystery_timeout();
395   d->mystery_ship_potential_shot_x = 0.0;
396 
397   d->move = 0;
398   d->freeze = 0;
399   d->total_freeze = FALSE;
400   d->super_total_final_freeze = FALSE;
401   d->animation_loop = 0;
402   g_list_free_full(d->explosions, free);
403   d->explosions = NULL;
404 
405   d->lifes = 3;
406   d->score_1 = d->score_2 = 0;
407 }
408 
init(dt_view_t * self)409 void init(dt_view_t *self)
410 {
411   self->data = calloc(1, sizeof(dt_knight_t));
412   dt_knight_t *d = (dt_knight_t *)self->data;
413 
414   _reset_board(d);
415   d->game_state = INTRO;
416 
417   // create sprites
418   // good knight alien frames
419   for(int i = 0; i < 2; i++)
420     d->alien_sprite[i]
421         = _new_sprite(alien[i], ALIEN_WIDTH, ALIEN_HEIGHT, NULL, &(d->bufs), &(d->surfaces), &(d->patterns));
422   // player
423   for(int i = 0; i < 3; i++)
424     d->player_sprite[i] = _new_sprite(player[i], PLAYER_WIDTH, PLAYER_HEIGHT, NULL, &(d->bufs),
425                                       &(d->surfaces), &(d->patterns));
426   // mystery ship
427   d->mystery_sprite = _new_sprite(mystery_ship, MYSTERY_SHIP_WIDTH, MYSTERY_SHIP_HEIGHT, NULL, &(d->bufs),
428                                   &(d->surfaces), &(d->patterns));
429   // explosions
430   for(int i = 0; i < EXPLOSION_AMOUNT; i++)
431     d->explosion_sprite[i] = _new_sprite(explosions[i], EXPLOSION_WIDTH, EXPLOSION_HEIGHT, NULL, &(d->bufs),
432                                          &(d->surfaces), &(d->patterns));
433   // bunkers
434   for(int i = 0; i < 4; i++)
435   {
436     d->bunker_sprite[i] = _new_sprite(bunker, BUNKER_WIDTH, BUNKER_HEIGHT, &d->bunker_stride, &(d->bufs),
437                                       &(d->surfaces), &(d->patterns));
438     d->bunker_buf[i] = (uint8_t *)g_list_last(d->bufs)->data;
439   }
440   // font
441   d->letters = (cairo_pattern_t **)malloc(sizeof(cairo_pattern_t *) * n_letters);
442   for(int i = 0; i < n_letters; i++)
443     d->letters[i]
444         = _new_sprite(font[i], FONT_WIDTH, FONT_HEIGHT, NULL, &(d->bufs), &(d->surfaces), &(d->patterns));
445 }
446 
cleanup(dt_view_t * self)447 void cleanup(dt_view_t *self)
448 {
449   dt_knight_t *d = (dt_knight_t *)self->data;
450 
451   g_list_free_full(d->patterns, (GDestroyNotify)cairo_pattern_destroy);
452   g_list_free_full(d->surfaces, (GDestroyNotify)cairo_surface_destroy);
453   g_list_free_full(d->bufs, free);
454 
455   free(d->letters);
456   free(self->data);
457 }
458 
459 // get the next alien in move order: bottom left to top right
_next_alien(dt_knight_alien_t * aliens,int current)460 static int _next_alien(dt_knight_alien_t *aliens, int current)
461 {
462   for(int i = 0; i < N_ALIENS_Y * N_ALIENS_X; i++)
463   {
464     int x = (current % N_ALIENS_X) + 1;
465     int y = current / N_ALIENS_X;
466     if(x == N_ALIENS_X)
467     {
468       x = 0;
469       y = (y - 1 + N_ALIENS_Y) % N_ALIENS_Y;
470     }
471     current = x + y * N_ALIENS_X;
472     if(aliens[current].alive) return current;
473   }
474   return -1;
475 }
476 
477 // get the lowest alien in the left most column
_leftest(const dt_knight_alien_t * aliens)478 static float _leftest(const dt_knight_alien_t *aliens)
479 {
480   for(int x = 0; x < N_ALIENS_X; x++)
481     for(int y = N_ALIENS_Y - 1; y >= 0; y--)
482     {
483       const int i = x + y * N_ALIENS_X;
484       if(aliens[i].alive) return aliens[i].x;
485     }
486   return 0.0;
487 }
488 
489 // get the lowest alien in the rightmost column
_rightest(const dt_knight_alien_t * aliens)490 static float _rightest(const dt_knight_alien_t *aliens)
491 {
492   for(int x = N_ALIENS_X - 1; x >= 0; x--)
493     for(int y = N_ALIENS_Y - 1; y >= 0; y--)
494     {
495       const int i = x + y * N_ALIENS_X;
496       if(aliens[i].alive) return aliens[i].x;
497     }
498   return 0.0;
499 }
500 
501 // reset the spawn timer when removing the mystery ship
_kill_mystery_ship(dt_knight_t * d)502 static inline void _kill_mystery_ship(dt_knight_t *d)
503 {
504   d->mystery_ship_x = -1.0;
505   d->time_until_mystery_ship = _get_mystery_timeout();
506 }
507 
508 // roll a dice to see where the mystery ship will shoot when adding it
_add_mystery_ship(dt_knight_t * d)509 static inline void _add_mystery_ship(dt_knight_t *d)
510 {
511   d->mystery_ship_x = 0.0;
512   // only shoot once per occurrence
513   d->mystery_ship_potential_shot_x = (float)rand() / (float)RAND_MAX;
514 }
515 
516 // return a new explosion object with the fields initialized. has to be free()'d
_new_explosion(float x,float y,int ttl,cairo_pattern_t * sprite)517 static dt_knight_explosion_t *_new_explosion(float x, float y, int ttl, cairo_pattern_t *sprite)
518 {
519   dt_knight_explosion_t *explosion = (dt_knight_explosion_t *)malloc(sizeof(dt_knight_explosion_t));
520   explosion->x = x;
521   explosion->y = y;
522   explosion->ttl = ttl;
523   explosion->sprite = sprite;
524   return explosion;
525 }
526 
527 // change the bunker graphics by subtracting an explosion sprite
_destroy_bunker(dt_knight_t * d,int bunker_idx,int hit_x,int hit_y)528 static void _destroy_bunker(dt_knight_t *d, int bunker_idx, int hit_x, int hit_y)
529 {
530   uint8_t *buf = d->bunker_buf[bunker_idx];
531   // the explosion has stride == width
532   const uint8_t *ex = explosions[EXPLOSION_SHOT];
533 
534   const int ex_half = EXPLOSION_WIDTH / 2.0 + 0.5;
535   const int ex_x0 = MAX(ex_half - hit_x, 0);
536   const int ex_x1 = MIN(BUNKER_WIDTH - hit_x + ex_half, EXPLOSION_WIDTH);
537   const int ex_y0 = MAX(ex_half - hit_y, 0);
538   const int ex_y1 = MIN(BUNKER_HEIGHT - hit_y + ex_half, EXPLOSION_HEIGHT);
539 
540   const int buf_x0 = MAX(hit_x - ex_half, 0);
541   const int buf_y0 = MAX(hit_y - ex_half, 0);
542 
543   for(int y = ex_y0, j = 0; y < ex_y1; y++, j++)
544     for(int x = ex_x0, i = 0; x < ex_x1; x++, i++)
545     {
546       const int in = x + y * EXPLOSION_WIDTH;
547       const int out = buf_x0 + i + (buf_y0 + j) * d->bunker_stride;
548       buf[out] &= ~ex[in];
549     }
550 }
551 
552 // check if a shot hit a bunker and deal out damage if needed
_hit_bunker(dt_knight_t * d,const dt_knight_shot_t * shot)553 static gboolean _hit_bunker(dt_knight_t *d, const dt_knight_shot_t *shot)
554 {
555   const float top = BUNKER_Y;
556   const float bottom = BUNKER_Y + BUNKER_TARGET_HEIGHT;
557   if((shot->direction > 0.0 && shot->y <= bottom && shot->y + SHOT_LENGTH >= top)
558      || (shot->y >= top && shot->y - SHOT_LENGTH <= bottom))
559   {
560     // we might have hit a bunker
561     for(int i = 0; i < 4; i++)
562     {
563       const float bunker_x = (i * 2 + 1) * BUNKER_TARGET_WIDTH;
564       // check the bounding box
565       if(shot->x >= bunker_x && shot->x <= bunker_x + BUNKER_TARGET_WIDTH)
566       {
567         // we are in the bb, now check the pixels, we might have hit a hole
568         uint8_t *buf = d->bunker_buf[i];
569         int pixel_x = ((shot->x - bunker_x) / BUNKER_TARGET_WIDTH) * BUNKER_WIDTH + 0.5;
570         pixel_x = CLAMP(pixel_x, 0, BUNKER_WIDTH - 1);
571         for(int j = 0; j < BUNKER_HEIGHT; j++)
572         {
573           const int pixel_y = shot->direction > 0 ? BUNKER_HEIGHT - 1 - j : j;
574           const int pixel = pixel_x + pixel_y * d->bunker_stride;
575           if(buf[pixel] == 0xff)
576           {
577             // destroy it!
578             _destroy_bunker(d, i, pixel_x, pixel_y);
579             const float _x
580                 = bunker_x + pixel_x * BUNKER_TARGET_WIDTH / BUNKER_WIDTH - 0.5 * EXPLOSION_TARGET_WIDTH;
581             const float _y
582                 = BUNKER_Y + pixel_y * BUNKER_TARGET_HEIGHT / BUNKER_HEIGHT - 0.5 * EXPLOSION_TARGET_HEIGHT;
583             dt_knight_explosion_t *explosion
584                 = _new_explosion(_x, _y, ALIEN_DEATH_TIME, d->explosion_sprite[EXPLOSION_SHOT]);
585             d->explosions = g_list_append(d->explosions, explosion);
586             return TRUE;
587           }
588         }
589         break; // can't possibly hit any other bunker
590       }
591     }
592   }
593   return FALSE;
594 }
595 
596 // when an alien occupies the same space as a bunker the touched part gets removed
_walk_over_bunker(dt_knight_t * d,float x,float y,float w,float h)597 static void _walk_over_bunker(dt_knight_t *d, float x, float y, float w, float h)
598 {
599   const float top = BUNKER_Y;
600   const float bottom = BUNKER_Y + BUNKER_TARGET_HEIGHT;
601   if(y <= bottom && y + h >= top)
602   {
603     // we might have hit a bunker
604     for(int i = 0; i < 4; i++)
605     {
606       const float bunker_x = (i * 2 + 1) * BUNKER_TARGET_WIDTH;
607       // check the bounding box
608       if(x + w >= bunker_x && x <= bunker_x + BUNKER_TARGET_WIDTH)
609       {
610         // we are in the bb, clear the rectangle
611         uint8_t *buf = d->bunker_buf[i];
612 
613         // express x/y relative to bunker_x/bunker_y in bunker pixels
614         const int pixel_x = (x - bunker_x) / BUNKER_TARGET_WIDTH * BUNKER_WIDTH + 0.5;
615         const int pixel_y = (y - BUNKER_Y) / BUNKER_TARGET_HEIGHT * BUNKER_HEIGHT + 0.5;
616         const int pixel_w = w / BUNKER_TARGET_WIDTH * BUNKER_WIDTH + 0.5;
617         const int pixel_h = h / BUNKER_TARGET_HEIGHT * BUNKER_HEIGHT + 0.5;
618 
619         // overlap with bunker
620         const int overhang_left = MAX(-1 * pixel_x, 0);
621         const int overhang_right = MAX(pixel_x + pixel_w - BUNKER_WIDTH, 0);
622         const int overlap_x = pixel_w - overhang_left - overhang_right;
623 
624         const int overhang_top = MAX(-1 * pixel_y, 0);
625         const int overhang_bottom = MAX(pixel_y + pixel_h - BUNKER_HEIGHT, 0);
626         const int overlap_y = pixel_h - overhang_top - overhang_bottom;
627 
628         // the area to clear is (x0, y0) -> (x0 + overlap_x, y0 + overlap_y)
629         const int x0 = MAX(pixel_x, 0);
630         const int y0 = MAX(pixel_y, 0);
631 
632         for(int _y = y0; _y < y0 + overlap_y; _y++)
633         {
634           const int i = x0 + _y * d->bunker_stride;
635           memset(&buf[i], 0x00, overlap_x);
636         }
637         break; // can't possibly hit any other bunker
638       }
639     }
640   }
641 }
642 
643 // the control logic for the interactive part
_event_loop_game(dt_knight_t * d)644 static gboolean _event_loop_game(dt_knight_t *d)
645 {
646   // clean up explosions
647   for(GList *iter = d->explosions; iter; iter = g_list_next(iter))
648   {
649     dt_knight_explosion_t *explosion = (dt_knight_explosion_t *)iter->data;
650     explosion->ttl--;
651     if(explosion->ttl == 0)
652     {
653       free(explosion);
654       iter = d->explosions = g_list_delete_link(d->explosions, iter);
655     }
656   }
657 
658   if(d->freeze > 0)
659   {
660     d->freeze--;
661     if(d->freeze == 0 && d->total_freeze)
662     {
663       // the player was hit. move him to the left
664       d->total_freeze = FALSE;
665       d->player_x = 0.0;
666       d->lifes--;
667       if(d->super_total_final_freeze) d->lifes = 0;
668     }
669     if(d->super_total_final_freeze) goto end;
670   }
671 
672   // handle movement in the event loop to not be affected by X's keyboard repeat rates and delay
673   if(!d->total_freeze)
674     d->player_x
675         = CLAMP(d->player_x + d->move * PLAYER_TARGET_WIDTH * STEP_SIZE, 0.0, 1.0 - PLAYER_TARGET_WIDTH);
676 
677   // spawn a mystery ship roughly every 25 seconds
678   d->time_until_mystery_ship--;
679   if(d->time_until_mystery_ship == 0)
680     _add_mystery_ship(d);
681   else
682   {
683     if(d->mystery_ship_x >= 0.0) d->mystery_ship_x += MYSTERY_SHIP_TARGET_WIDTH * STEP_SIZE;
684     if(d->mystery_ship_x >= 1.0 - MYSTERY_SHIP_TARGET_WIDTH) _kill_mystery_ship(d);
685   }
686 
687   // don't fire in the first 1.5 seconds
688   if(d->animation_loop > 1.5 * 1000.0 / LOOP_SPEED)
689   {
690     if(d->freeze == 0)
691     {
692       // randomly shoot at the player
693       if(d->n_alien_shots < MAX_ALIEN_SHOTS && rand() % ALIEN_SHOT_PROBABILITY == 0)
694       {
695         int column = rand() % N_ALIENS_X;
696         for(int c = 0; c < N_ALIENS_X; c++)
697         {
698           // if the column has no alien left we try the next one
699           int column_candidate = (column + c) % N_ALIENS_X;
700           for(int row = N_ALIENS_Y - 1; row >= 0; row--)
701           {
702             const int i = row * N_ALIENS_X + column_candidate;
703             if(!d->aliens[i].alive) continue;
704 
705             // find an empty spot in the shot table
706             for(int s = 0; s < MAX_ALIEN_SHOTS; s++)
707             {
708               if(d->alien_shots[s].active) continue;
709               d->n_alien_shots++;
710               d->alien_shots[s].active = TRUE;
711               d->alien_shots[s].x = d->aliens[i].x + 0.5 * ALIEN_TARGET_WIDTH;
712               d->alien_shots[s].y = d->alien_shots[s].start
713                   = d->aliens[i].y + ALIEN_TARGET_HEIGHT + SHOT_LENGTH;
714               d->alien_shots[s].direction = -1.0;
715               goto alien_shots_fired;
716             }
717           }
718         }
719       }
720     }
721   alien_shots_fired:
722 
723     // the mystery ship can shoot, too
724     if(d->mystery_ship_x >= d->mystery_ship_potential_shot_x - 0.5 * MYSTERY_SHIP_TARGET_WIDTH
725        && !d->alien_shots[MAX_ALIEN_SHOTS].active)
726     {
727       d->mystery_ship_potential_shot_x = 2.0;
728       d->alien_shots[MAX_ALIEN_SHOTS].active = TRUE;
729       d->alien_shots[MAX_ALIEN_SHOTS].x = d->mystery_ship_x + 0.5 * MYSTERY_SHIP_TARGET_WIDTH;
730       d->alien_shots[MAX_ALIEN_SHOTS].y = d->alien_shots[MAX_ALIEN_SHOTS].start
731           = MYSTERY_SHIP_Y + MYSTERY_SHIP_TARGET_HEIGHT + SHOT_LENGTH;
732       d->alien_shots[MAX_ALIEN_SHOTS].direction = -1.0;
733     }
734   }
735   else
736     d->animation_loop++;
737 
738   // move shots
739   // the player shot
740   if(d->player_shot.active)
741   {
742     d->player_shot.y -= SHOT_LENGTH;
743 
744     // TODO: setting this to 0.0 means we can shoot between the aliens. is that what the original did?
745     const float half_gap = ALIEN_TARGET_WIDTH * (GAP - 1.0) / 2.0;
746 
747     // did the player hit something?
748     // check aliens
749     for(int i = 0; i < N_ALIENS_Y * N_ALIENS_X; i++)
750     {
751       dt_knight_alien_t *curr_alien = &d->aliens[i];
752       if(!curr_alien->alive) continue;
753       if(d->player_shot.x >= curr_alien->x - half_gap
754          && d->player_shot.x <= curr_alien->x + ALIEN_TARGET_WIDTH + half_gap
755          && d->player_shot.y >= curr_alien->y - SHOT_LENGTH && d->player_shot.y <= curr_alien->y + ALIEN_TARGET_HEIGHT)
756       {
757         // we hit an alien
758         d->freeze = ALIEN_DEATH_TIME;
759         d->player_shot.active = FALSE;
760         curr_alien->alive = FALSE;
761         d->n_aliens--;
762         d->score_1 += curr_alien->points;
763         dt_knight_explosion_t *explosion
764             = _new_explosion(curr_alien->x, curr_alien->y, ALIEN_DEATH_TIME, d->explosion_sprite[EXPLOSION_ALIEN]);
765         d->explosions = g_list_append(d->explosions, explosion);
766         if(d->alien_next_to_move == i) d->alien_next_to_move = _next_alien(d->aliens, d->alien_next_to_move);
767         break;
768       }
769     }
770 
771     // test other stuff
772     if(d->player_shot.y <= 2.5 * LETTER_HEIGHT)
773     {
774       // we hit the top of the board
775       d->player_shot.active = FALSE;
776       dt_knight_explosion_t *explosion
777           = _new_explosion(d->player_shot.x - 0.5 * EXPLOSION_TARGET_WIDTH, 2.5 * LETTER_HEIGHT,
778                            ALIEN_DEATH_TIME, d->explosion_sprite[EXPLOSION_TOP]);
779       d->explosions = g_list_append(d->explosions, explosion);
780     }
781     else if(d->player_shot.x >= d->mystery_ship_x
782             && d->player_shot.x <= d->mystery_ship_x + MYSTERY_SHIP_TARGET_WIDTH
783             && d->player_shot.y >= MYSTERY_SHIP_Y - SHOT_LENGTH
784             && d->player_shot.y <= MYSTERY_SHIP_Y + MYSTERY_SHIP_TARGET_HEIGHT)
785     {
786       // we hit the mystery ship
787       d->player_shot.active = FALSE;
788       d->score_1 += 50;
789       dt_knight_explosion_t *explosion = _new_explosion(d->mystery_ship_x, MYSTERY_SHIP_Y, ALIEN_DEATH_TIME,
790                                                         d->explosion_sprite[EXPLOSION_MYSTERY]);
791       d->explosions = g_list_append(d->explosions, explosion);
792       _kill_mystery_ship(d);
793     }
794     else if(_hit_bunker(d, &d->player_shot))
795     {
796       // we hit a bunker
797       d->player_shot.active = FALSE;
798     }
799 
800     // shot vs. shot is tested later
801   }
802 
803 
804   // now move the alien shots
805   gboolean was_hit = d->total_freeze; // guard against several hits at once
806   for(int s = 0; s < MAX_ALIEN_SHOTS + 1; s++)
807   {
808     dt_knight_shot_t *shot = &d->alien_shots[s];
809     if(!shot->active) continue;
810 
811     shot->y += SHOT_LENGTH;
812 
813     if(shot->x >= d->player_x - 0.2 * PLAYER_TARGET_WIDTH
814        && shot->x <= d->player_x + 1.2 * PLAYER_TARGET_WIDTH && shot->y >= PLAYER_Y
815        && shot->y <= PLAYER_Y + PLAYER_TARGET_HEIGHT + SHOT_LENGTH)
816     {
817       // we hit the player. he is immune when the alien was directly above him!
818       if(shot->start <= PLAYER_Y - ALIEN_TARGET_HEIGHT && !was_hit)
819       {
820         was_hit = TRUE;
821         d->freeze = 3.0 * 1000.0 / LOOP_SPEED;
822         d->total_freeze = TRUE;
823       }
824       shot->active = FALSE;
825       d->n_alien_shots--;
826     }
827     else if(d->player_shot.active && fabs(shot->x - d->player_shot.x) < 0.4 * CELL_WIDTH
828             && shot->y >= d->player_shot.y) // they can only meet from one direction
829     {
830       // the player hit the alien's shot. destroy it. there's a 50% chance that the player shot survives
831       // FIXME: is it this way or the other way round?
832       shot->active = FALSE;
833       d->n_alien_shots--;
834       if(rand() % 2 == 0) d->player_shot.active = FALSE;
835       dt_knight_explosion_t *explosion
836           = _new_explosion(d->player_shot.x - 0.5 * EXPLOSION_TARGET_WIDTH, d->player_shot.y,
837                            ALIEN_DEATH_TIME, d->explosion_sprite[EXPLOSION_SHOT]);
838       d->explosions = g_list_append(d->explosions, explosion);
839     }
840     else if(_hit_bunker(d, shot))
841     {
842       // we hit a bunker
843       shot->active = FALSE;
844       d->n_alien_shots--;
845     }
846     else if(shot->y >= BOTTOM_MARGIN)
847     {
848       // we hit the ground
849       shot->active = FALSE;
850       d->n_alien_shots--;
851       dt_knight_explosion_t *explosion
852           = _new_explosion(shot->x - 0.5 * EXPLOSION_TARGET_WIDTH, BOTTOM_MARGIN - EXPLOSION_TARGET_HEIGHT,
853                            ALIEN_DEATH_TIME, d->explosion_sprite[EXPLOSION_BOTTOM]);
854       d->explosions = g_list_append(d->explosions, explosion);
855     }
856   }
857 
858 
859   // move in blocks of 2
860   if(d->freeze == 0)
861   {
862     for(int i = 0; i < 2; i++)
863     {
864       if(d->alien_next_to_move == -1) break;
865       const int x = d->alien_next_to_move % N_ALIENS_X;
866       const int y = d->alien_next_to_move / N_ALIENS_X;
867       const int next = _next_alien(d->aliens, d->alien_next_to_move);
868       const int next_x = next % N_ALIENS_X;
869       const int next_y = next / N_ALIENS_X;
870       dt_knight_alien_t *alien_tm = &d->aliens[d->alien_next_to_move];
871       switch(d->alien_direction)
872       {
873         case ALIEN_LEFT:
874           alien_tm->x -= STEP_SIZE * ALIEN_TARGET_WIDTH;
875           if((next_y > y || (next_y == y && next_x < x) || next == d->alien_next_to_move)
876              && _leftest(d->aliens) - STEP_SIZE * ALIEN_TARGET_WIDTH < 0.0)
877             d->alien_direction = ALIEN_DOWN_THEN_RIGHT;
878           break;
879         case ALIEN_RIGHT:
880           alien_tm->x += STEP_SIZE * ALIEN_TARGET_WIDTH;
881           if((next_y > y || (next_y == y && next_x < x) || next == d->alien_next_to_move)
882              && _rightest(d->aliens) + ALIEN_TARGET_WIDTH + STEP_SIZE * ALIEN_TARGET_WIDTH > 1.0)
883             d->alien_direction = ALIEN_DOWN_THEN_LEFT;
884           break;
885         case ALIEN_DOWN_THEN_LEFT:
886         case ALIEN_DOWN_THEN_RIGHT:
887           alien_tm->y += 0.5 * ALIEN_TARGET_HEIGHT;
888           if(alien_tm->y + ALIEN_TARGET_HEIGHT >= PLAYER_Y + 0.5 * PLAYER_TARGET_HEIGHT)
889           {
890             d->freeze = 3.0 * 1000.0 / LOOP_SPEED;
891             d->total_freeze = TRUE;
892             d->super_total_final_freeze = TRUE;
893           }
894           if(next_y > y || (next_y == y && next_x < x) || next == d->alien_next_to_move)
895             d->alien_direction = (d->alien_direction == ALIEN_DOWN_THEN_LEFT ? ALIEN_LEFT : ALIEN_RIGHT);
896           break;
897       }
898 
899       // when going over a bunker it (the bunker) gets destroyed
900       _walk_over_bunker(d, alien_tm->x, alien_tm->y, ALIEN_TARGET_WIDTH, ALIEN_TARGET_HEIGHT);
901 
902       // allow the last one to go really fast, but keep it animating
903       if(!(i == 0 && d->alien_next_to_move == next)) alien_tm->frame = 1 - alien_tm->frame;
904       d->alien_next_to_move = next;
905     }
906   }
907 
908 end:
909   // finally, did one side win?
910   if(d->n_aliens == 0)
911   {
912     d->high_score = MAX(d->score_1, d->high_score);
913     d->game_state = WIN;
914     d->animation_loop = 0;
915   }
916 
917   if(d->lifes == 0)
918   {
919     d->game_state = LOSE;
920     d->animation_loop = 0;
921   }
922 
923   return TRUE;
924 }
925 
926 // the control logic for the non-interactive part: just count up the frames
_event_loop_animation(dt_knight_t * d)927 static gboolean _event_loop_animation(dt_knight_t *d)
928 {
929   d->animation_loop++;
930   return TRUE;
931 }
932 
933 // control dispatcher, makes sure that the screen is redrawn afterwards
_event_loop(gpointer user_data)934 static gboolean _event_loop(gpointer user_data)
935 {
936   dt_knight_t *d = (dt_knight_t *)user_data;
937   gboolean res = FALSE;  // silence warning about uninitialized res
938   switch(d->game_state)
939   {
940     case INTRO:
941     case START:
942     case WIN:
943     case LOSE:
944       res = _event_loop_animation(d);
945       break;
946     case GAME:
947       res = _event_loop_game(d);
948       break;
949   }
950   dt_control_queue_redraw_center();
951   return res;
952 }
953 
enter(dt_view_t * self)954 void enter(dt_view_t *self)
955 {
956   dt_knight_t *d = (dt_knight_t *)self->data;
957 
958   dt_control_change_cursor(GDK_BLANK_CURSOR);
959 
960   dt_ui_panel_show(darktable.gui->ui, DT_UI_PANEL_LEFT, FALSE, TRUE);
961   dt_ui_panel_show(darktable.gui->ui, DT_UI_PANEL_RIGHT, FALSE, TRUE);
962   dt_ui_panel_show(darktable.gui->ui, DT_UI_PANEL_TOP, FALSE, TRUE);
963   dt_ui_panel_show(darktable.gui->ui, DT_UI_PANEL_BOTTOM, FALSE, TRUE);
964   dt_ui_panel_show(darktable.gui->ui, DT_UI_PANEL_CENTER_TOP, FALSE, TRUE);
965   dt_ui_panel_show(darktable.gui->ui, DT_UI_PANEL_CENTER_BOTTOM, FALSE, TRUE);
966 
967   // set the initial game state
968   switch(d->game_state)
969   {
970     case GAME: // allow to pause by leaving the view
971       break;
972     case WIN:
973     case LOSE:
974       // don't show the full intro again. it gets annoying
975       d->game_state = START;
976     case INTRO:
977     case START:
978       // restart the current state
979       d->animation_loop = 0;
980       _reset_board(d);
981       break;
982   }
983 
984   // start event loop
985   d->event_loop = g_timeout_add(LOOP_SPEED, _event_loop, d);
986 }
987 
leave(dt_view_t * self)988 void leave(dt_view_t *self)
989 {
990   dt_knight_t *d = (dt_knight_t *)self->data;
991 
992   // show normal gui again
993   dt_control_change_cursor(GDK_LEFT_PTR);
994 
995   // stop event loop
996   if(d->event_loop > 0) g_source_remove(d->event_loop);
997   d->event_loop = 0;
998 }
999 
1000 // set the sprite's matrix to scale it up to the desired size to deal with the window size
_scale_sprite(cairo_pattern_t * pattern,const int width,const float target_width)1001 static void _scale_sprite(cairo_pattern_t *pattern, const int width, const float target_width)
1002 {
1003   cairo_matrix_t matrix;
1004   const float s = width / target_width;
1005   cairo_matrix_init_scale(&matrix, s, s);
1006   cairo_pattern_set_matrix(pattern, &matrix);
1007 }
1008 
1009 // show text using the font described in knight_font.h
1010 // text can be justified left ('l'), right ('r') or centered ('c')
_show_text(cairo_t * cr,cairo_pattern_t ** letters,const char * text,float x,float y,float w,float h,char justify)1011 static void _show_text(cairo_t *cr, cairo_pattern_t **letters, const char *text, float x, float y, float w,
1012                        float h, char justify)
1013 {
1014   const int l = strlen(text);
1015   const float spacing = LETTER_SPACING * w;
1016   cairo_save(cr);
1017   cairo_translate(cr, x, y);
1018   if(justify == 'c')
1019   {
1020     const float justify_offset
1021         = (-1 * (int)(l / 2.0 + 0.5) * LETTER_SPACING + LETTER_SPACING - LETTER_WIDTH) * w;
1022     cairo_translate(cr, justify_offset, 0);
1023   }
1024   else if(justify == 'r')
1025   {
1026     const float justify_offset = (-1 * l * LETTER_SPACING + LETTER_SPACING - LETTER_WIDTH) * w;
1027     cairo_translate(cr, justify_offset, 0);
1028   }
1029   for(int i = 0; i < l; i++)
1030   {
1031     unsigned int c = (text[i] - ' ') % n_letters;
1032     cairo_mask(cr, letters[c]);
1033     cairo_translate(cr, spacing, 0);
1034   }
1035   cairo_fill(cr);
1036   cairo_restore(cr);
1037 }
1038 
1039 // helper functions to draw specific parts of the GUI
_show_top_line(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1040 static void _show_top_line(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1041 {
1042   _show_text(cr, d->letters, "SCORE<1>", LETTER_WIDTH * w, 0.0, w, h, 'l');
1043   _show_text(cr, d->letters, "HI-SCORE", 0.5 * w, 0.0, w, h, 'c');
1044   _show_text(cr, d->letters, "SCORE<2>", (1.0 - LETTER_WIDTH) * w, 0.0, w, h, 'r');
1045 }
1046 
_show_score_1(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1047 static void _show_score_1(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1048 {
1049   char text[64];
1050   snprintf(text, sizeof(text), "%04u", d->score_1);
1051   _show_text(cr, d->letters, text, (LETTER_WIDTH + LETTER_SPACING * 2) * w, 2 * LETTER_HEIGHT * w, w, h, 'l');
1052 }
1053 
_show_score_2(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1054 static void _show_score_2(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1055 {
1056   char text[64];
1057   snprintf(text, sizeof(text), "%04u", d->score_2);
1058   _show_text(cr, d->letters, text, (1.0 - (LETTER_WIDTH + LETTER_SPACING * 2)) * w, 2 * LETTER_HEIGHT * w, w,
1059              h, 'r');
1060 }
1061 
_show_high_score(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1062 static void _show_high_score(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1063 {
1064   char text[64];
1065   snprintf(text, sizeof(text), "%04u", d->high_score);
1066   _show_text(cr, d->letters, text, 0.5 * w, 2 * LETTER_HEIGHT * w, w, h, 'c');
1067 }
1068 
_show_credit(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1069 static void _show_credit(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1070 {
1071   char text[64];
1072   snprintf(text, sizeof(text), "CREDIT %02d", d->credit);
1073   _show_text(cr, d->letters, text, (1.0 - LETTER_WIDTH - LETTER_SPACING) * w, h - (2 * LETTER_HEIGHT) * w, w,
1074              h, 'r');
1075 }
1076 
_show_lifes(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1077 static void _show_lifes(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1078 {
1079   char text[64];
1080 
1081   cairo_save(cr);
1082   cairo_translate(cr, 0, h - (2 * LETTER_HEIGHT) * w);
1083 
1084   cairo_set_source_rgb(cr, 1, 1, 1);
1085   snprintf(text, sizeof(text), "%d", d->lifes);
1086   _show_text(cr, d->letters, text, LETTER_WIDTH * w, 0.0, w, h, 'l');
1087 
1088   cairo_set_source_rgb(cr, 0, 1, 0);
1089   cairo_translate(cr, (LETTER_SPACING + GAP * PLAYER_TARGET_WIDTH) * w, 0);
1090   for(int i = 0; i < d->lifes - 1; i++)
1091   {
1092     cairo_mask(cr, d->player_sprite[0]);
1093     cairo_translate(cr, GAP * PLAYER_TARGET_WIDTH * w, 0);
1094   }
1095   cairo_restore(cr);
1096   cairo_fill(cr);
1097 }
1098 
_show_bunkers(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1099 static void _show_bunkers(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1100 {
1101   cairo_save(cr);
1102   cairo_set_source_rgb(cr, 0, 1, 0);
1103   cairo_translate(cr, BUNKER_TARGET_WIDTH * w, BUNKER_Y * h);
1104   for(int i = 0; i < 4; i++)
1105   {
1106     cairo_mask(cr, d->bunker_sprite[i]);
1107     cairo_translate(cr, 2 * BUNKER_TARGET_WIDTH * w, 0);
1108   }
1109   cairo_fill(cr);
1110   cairo_restore(cr);
1111 }
1112 
_show_aliens(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1113 static void _show_aliens(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1114 {
1115   cairo_save(cr);
1116   for(int y = 0; y < N_ALIENS_Y; y++)
1117     for(int x = 0; x < N_ALIENS_X; x++)
1118     {
1119       int i = y * N_ALIENS_X + x;
1120       if(!d->aliens[i].alive) continue;
1121       cairo_save(cr);
1122       cairo_translate(cr, d->aliens[i].x * w, d->aliens[i].y * h);
1123       cairo_mask(cr, d->alien_sprite[d->aliens[i].frame]);
1124       cairo_fill(cr);
1125       cairo_restore(cr);
1126     }
1127   cairo_restore(cr);
1128 }
1129 
_show_ground(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1130 static void _show_ground(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1131 {
1132   cairo_set_line_width(cr, h / 250.0);
1133   cairo_set_source_rgb(cr, 0, 1, 0);
1134   float y = BOTTOM_MARGIN * h;
1135   cairo_move_to(cr, 0, y);
1136   cairo_line_to(cr, w, y);
1137   cairo_stroke(cr);
1138 }
1139 
_show_shot(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h,dt_knight_shot_t * shot)1140 static void _show_shot(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h, dt_knight_shot_t *shot)
1141 {
1142   if(shot->active)
1143   {
1144     cairo_move_to(cr, shot->x * w, shot->y * h);
1145     cairo_rel_line_to(cr, 0, shot->direction * SHOT_LENGTH * w);
1146     cairo_stroke(cr);
1147   }
1148 }
1149 
1150 // display the running game, according to its state
_expose_game(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1151 static void _expose_game(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1152 {
1153   // draw the bottom ground line
1154   _show_ground(d, cr, w, h);
1155 
1156   // draw shots
1157   cairo_set_source_rgb(cr, 1, 1, 1);
1158   _show_shot(d, cr, w, h, &d->player_shot);
1159   for(int s = 0; s < MAX_ALIEN_SHOTS + 1; s++) _show_shot(d, cr, w, h, &d->alien_shots[s]);
1160 
1161 
1162   cairo_set_line_width(cr, 1); // was set by _show_ground()
1163 
1164 
1165   // draw player
1166   cairo_set_source_rgb(cr, 0, 1, 0);
1167   cairo_save(cr);
1168   cairo_translate(cr, d->player_x * w, PLAYER_Y * h);
1169   if(d->total_freeze)
1170     // explosion animation
1171     cairo_mask(cr, d->player_sprite[1 + (d->freeze % 4) / 2]);
1172   else
1173     // normal graphic
1174     cairo_mask(cr, d->player_sprite[0]);
1175   cairo_fill(cr);
1176   cairo_restore(cr);
1177 
1178 
1179   // draw bunkers
1180   _show_bunkers(d, cr, w, h);
1181 
1182 
1183   // draw the alien block
1184   cairo_set_source_rgb(cr, 1, 1, 1);
1185   _show_aliens(d, cr, w, h);
1186 
1187 
1188   // draw mystery ship
1189   if(d->mystery_ship_x >= 0.0)
1190   {
1191     cairo_save(cr);
1192     cairo_set_source_rgb(cr, 1, 0, 0);
1193     cairo_translate(cr, d->mystery_ship_x * w, MYSTERY_SHIP_Y * h);
1194     cairo_mask(cr, d->mystery_sprite);
1195     cairo_fill(cr);
1196     cairo_restore(cr);
1197   }
1198 
1199 
1200   // draw explosions
1201   cairo_set_source_rgb(cr, 1, 1, 1);
1202   for(GList *iter = d->explosions; iter; iter = g_list_next(iter))
1203   {
1204     dt_knight_explosion_t *explosion = (dt_knight_explosion_t *)iter->data;
1205     cairo_save(cr);
1206     cairo_translate(cr, explosion->x * w, explosion->y * h);
1207     cairo_mask(cr, explosion->sprite);
1208     cairo_fill(cr);
1209     cairo_restore(cr);
1210   }
1211 
1212 
1213   // draw overlay
1214   _show_top_line(d, cr, w, h);
1215   _show_score_1(d, cr, w, h);
1216   //   _show_score_2(d, cr, w, h); // TODO: 2nd player
1217   _show_high_score(d, cr, w, h);
1218   _show_credit(d, cr, w, h);
1219   _show_lifes(d, cr, w, h);
1220 }
1221 
1222 // draw the non-interactive part of the game: intro and win/lose screen
_expose_intro(dt_knight_t * d,cairo_t * cr,int32_t w,int32_t h)1223 static void _expose_intro(dt_knight_t *d, cairo_t *cr, int32_t w, int32_t h)
1224 {
1225   cairo_set_source_rgb(cr, 1, 1, 1);
1226 
1227   _show_top_line(d, cr, w, h);
1228   _show_high_score(d, cr, w, h);
1229   _show_credit(d, cr, w, h);
1230 
1231   const int wipe_duration = 1.0 * 1000.0 / (float)LOOP_SPEED; // i.e., 1 second
1232 
1233   if(d->game_state == INTRO)
1234   {
1235     _show_score_1(d, cr, w, h);
1236     _show_score_2(d, cr, w, h);
1237 
1238     if(d->animation_loop > 8.5 * 1000.0 / (float)LOOP_SPEED && d->player_shot.active)
1239     {
1240       d->game_state = START;
1241       d->animation_loop = 0;
1242     }
1243     else if(d->animation_loop > 7.5 * 1000.0 / (float)LOOP_SPEED)
1244     {
1245       // wait for player select
1246       _show_text(cr, d->letters, "PUSH", 0.5 * w, 11 * LETTER_HEIGHT * w, w, h, 'c');
1247       _show_text(cr, d->letters, "1 OR 2 PLAYERS BUTTON", 0.5 * w, 13 * LETTER_HEIGHT * w, w, h, 'c');
1248     }
1249     else if(d->animation_loop > 1.0 * 1000.0 / (float)LOOP_SPEED)
1250     {
1251       d->player_shot.active = FALSE;
1252       // 1s - 5s: show welcome text
1253       _show_text(cr, d->letters, "THE DARKTABLE TEAM", 0.5 * w, 6 * LETTER_HEIGHT * w, w, h, 'c');
1254       _show_text(cr, d->letters, "PRESENTS", 0.5 * w, 8 * LETTER_HEIGHT * w, w, h, 'c');
1255       _show_text(cr, d->letters, "THE GOOD KNIGHT", 0.5 * w, 10 * LETTER_HEIGHT * w, w, h, 'c');
1256 
1257       // 5s - 5.5s: wipe
1258       const int wipe_start = 6.0 * 1000.0 / (float)LOOP_SPEED;
1259       if(d->animation_loop > wipe_start)
1260       {
1261         const float wipe_progress = (float)(d->animation_loop - wipe_start) / wipe_duration;
1262         cairo_set_source_rgb(cr, 0, 0, 0);
1263         cairo_rectangle(cr, 0, 5 * LETTER_HEIGHT * w, wipe_progress * w, 9 * LETTER_HEIGHT * w);
1264         cairo_fill(cr);
1265       }
1266     }
1267   }
1268   else if(d->game_state == START)
1269   {
1270     if(d->animation_loop > 5.0 * 1000.0 / (float)LOOP_SPEED)
1271     {
1272       int n_aliens = 0;
1273       d->n_aliens = MIN(d->animation_loop - (5.0 * 1000.0 / (float)LOOP_SPEED), N_ALIENS_X * N_ALIENS_Y);
1274       for(int y = N_ALIENS_Y - 1; y >= 0; y--)
1275         for(int x = 0; x < N_ALIENS_X; x++)
1276         {
1277           const int i = x + y * N_ALIENS_X;
1278           d->aliens[i].alive = n_aliens++ < d->n_aliens;
1279         }
1280       if(d->n_aliens == N_ALIENS_X * N_ALIENS_Y)
1281       {
1282         d->game_state = GAME;
1283         d->player_shot.active = FALSE;
1284         d->player_x = 0.0;
1285         d->animation_loop = 0;
1286       }
1287       _show_score_1(d, cr, w, h);
1288       _show_aliens(d, cr, w, h);
1289       _show_bunkers(d, cr, w, h);
1290       _show_ground(d, cr, w, h);
1291       _show_lifes(d, cr, w, h);
1292     }
1293     else if(d->animation_loop > 1.5 * 1000.0 / (float)LOOP_SPEED)
1294     {
1295       _show_text(cr, d->letters, "PLAY PLAYER<1>", 0.5 * w, 13 * LETTER_HEIGHT * w, w, h, 'c');
1296       _show_lifes(d, cr, w, h);
1297       if(d->animation_loop % (int)(1000.0 / (LOOP_SPEED * 2.) + 0.5) < (1000.0 / (LOOP_SPEED * 4.0)))
1298         _show_score_1(d, cr, w, h);
1299     }
1300     else
1301     {
1302       _show_score_1(d, cr, w, h);
1303       _show_score_2(d, cr, w, h);
1304       if(d->animation_loop <= 1.0 * 1000.0 / (float)LOOP_SPEED)
1305       {
1306         const float wipe_progress = (float)d->animation_loop / wipe_duration;
1307 
1308         _show_text(cr, d->letters, "PUSH", 0.5 * w, 11 * LETTER_HEIGHT * w, w, h, 'c');
1309         _show_text(cr, d->letters, "1 OR 2 PLAYERS BUTTON", 0.5 * w, 13 * LETTER_HEIGHT * w, w, h, 'c');
1310 
1311         cairo_set_source_rgb(cr, 0, 0, 0);
1312         cairo_rectangle(cr, 0, 0, wipe_progress * w, h);
1313         cairo_fill(cr);
1314       }
1315     }
1316   }
1317   else if(d->game_state == LOSE)
1318   {
1319     _show_score_1(d, cr, w, h);
1320     _show_lifes(d, cr, w, h);
1321     cairo_set_source_rgb(cr, 1, 1, 1);
1322     _show_text(cr, d->letters, "GAME OVER", 0.5 * w, 6 * LETTER_HEIGHT * w, w, h, 'c');
1323     if(d->animation_loop > 2.0 * 1000.0 / LOOP_SPEED)
1324       _show_text(cr, d->letters, "NOW GET BACK TO WORK", 0.5 * w, 8 * LETTER_HEIGHT * w, w, h, 'c');
1325     const int wipe_start = 5.0 * 1000.0 / (float)LOOP_SPEED;
1326     if(d->animation_loop > wipe_start)
1327     {
1328       const float wipe_progress = (float)(d->animation_loop - wipe_start) / wipe_duration;
1329       cairo_set_source_rgb(cr, 0, 0, 0);
1330       cairo_rectangle(cr, 0, 0, wipe_progress * w, h);
1331       cairo_fill(cr);
1332     }
1333     if(d->animation_loop > wipe_start + wipe_duration * 2) dt_ctl_switch_mode_to("lighttable");
1334   }
1335   else if(d->game_state == WIN)
1336   {
1337     _show_score_1(d, cr, w, h);
1338     _show_lifes(d, cr, w, h);
1339     cairo_set_source_rgb(cr, 1, 1, 1);
1340     _show_text(cr, d->letters, "WELL DONE EARTHLING", 0.5 * w, 6 * LETTER_HEIGHT * w, w, h, 'c');
1341     if(d->animation_loop > 1.0 * 1000.0 / LOOP_SPEED)
1342       _show_text(cr, d->letters, "THIS TIME YOU WIN", 0.5 * w, 8 * LETTER_HEIGHT * w, w, h, 'c');
1343     if(d->animation_loop > 4.0 * 1000.0 / LOOP_SPEED)
1344       _show_text(cr, d->letters, "NOW GET BACK TO WORK", 0.5 * w, 11 * LETTER_HEIGHT * w, w, h, 'c');
1345     const int wipe_start = 7.0 * 1000.0 / (float)LOOP_SPEED;
1346     if(d->animation_loop > wipe_start)
1347     {
1348       const float wipe_progress = (float)(d->animation_loop - wipe_start) / wipe_duration;
1349       cairo_set_source_rgb(cr, 0, 0, 0);
1350       cairo_rectangle(cr, 0, 0, wipe_progress * w, h);
1351       cairo_fill(cr);
1352     }
1353     if(d->animation_loop > wipe_start + wipe_duration * 2) dt_ctl_switch_mode_to("lighttable");
1354   }
1355 }
1356 
expose(dt_view_t * self,cairo_t * cr,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)1357 void expose(dt_view_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
1358 {
1359   dt_knight_t *d = (dt_knight_t *)self->data;
1360 
1361   // we want a fixed playground aspect ratio
1362   int w = width, h = height;
1363   if(width / ASPECT_RATIO < height)
1364     h = (float)w / ASPECT_RATIO;
1365   else
1366     w = (float)h * ASPECT_RATIO;
1367 
1368   cairo_save(cr);
1369   // set 0/0 to the top left of the playground
1370   cairo_translate(cr, (width - w) / 2, (height - h) / 2);
1371 
1372   // prepare sprites
1373   for(int i = 0; i < 2; i++) _scale_sprite(d->alien_sprite[i], ALIEN_WIDTH, ALIEN_TARGET_WIDTH * w);
1374   for(int i = 0; i < 3; i++) _scale_sprite(d->player_sprite[i], PLAYER_WIDTH, PLAYER_TARGET_WIDTH * w);
1375   _scale_sprite(d->mystery_sprite, MYSTERY_SHIP_WIDTH, MYSTERY_SHIP_TARGET_WIDTH * w);
1376   for(int i = 0; i < EXPLOSION_AMOUNT; i++)
1377     _scale_sprite(d->explosion_sprite[i], EXPLOSION_WIDTH, EXPLOSION_TARGET_WIDTH * w);
1378   for(int i = 0; i < 4; i++) _scale_sprite(d->bunker_sprite[i], BUNKER_WIDTH, BUNKER_TARGET_WIDTH * w);
1379   for(int i = 0; i < n_letters; i++) _scale_sprite(d->letters[i], FONT_WIDTH, LETTER_WIDTH * w);
1380 
1381   // clear background
1382   cairo_set_source_rgb(cr, 0, 0, 0);
1383   cairo_paint(cr);
1384 
1385   switch(d->game_state)
1386   {
1387     case INTRO:
1388     case START:
1389     case LOSE:
1390     case WIN:
1391       _expose_intro(d, cr, w, h);
1392       break;
1393     case GAME:
1394       _expose_game(d, cr, w, h);
1395       break;
1396     default:
1397       break;
1398   }
1399 
1400   cairo_restore(cr);
1401 }
1402 
key_released(dt_view_t * self,guint key,guint state)1403 int key_released(dt_view_t *self, guint key, guint state)
1404 {
1405   dt_knight_t *d = (dt_knight_t *)self->data;
1406   switch(key)
1407   {
1408     case GDK_KEY_Left:
1409     case GDK_KEY_Right:
1410       d->move = 0;
1411       return 1;
1412   }
1413   return 0;
1414 }
1415 
key_pressed(dt_view_t * self,guint key,guint state)1416 int key_pressed(dt_view_t *self, guint key, guint state)
1417 {
1418   dt_knight_t *d = (dt_knight_t *)self->data;
1419 
1420   switch(key)
1421   {
1422     // do movement in the event loop
1423     case GDK_KEY_Left:
1424       d->move = -1;
1425       return 1;
1426     case GDK_KEY_Right:
1427       d->move = 1;
1428       return 1;
1429     case GDK_KEY_space:
1430       if(!d->player_shot.active && !d->total_freeze)
1431       {
1432         d->player_shot.active = TRUE;
1433         d->player_shot.x = d->player_x + 0.5 * PLAYER_TARGET_WIDTH;
1434         d->player_shot.y = d->player_shot.start = PLAYER_Y;
1435         d->player_shot.direction = 1.0;
1436       }
1437       return 1;
1438   }
1439 
1440   return 0;
1441 }
1442 
1443 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1444 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1445 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1446