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