]> gitweb.ps.run Git - npengine/blob - src/main.cpp
update for win11
[npengine] / src / main.cpp
1 #include <fstream>\r
2 #include <stdio.h>\r
3 #include <stdint.h>\r
4 #include <string>\r
5 #include <time.h>\r
6 #include <windows.h>\r
7 #include <Richedit.h>\r
8 \r
9 #pragma comment(lib, "user32.lib")\r
10 \r
11 #define CONSOLE\r
12 \r
13 #ifdef CONSOLE\r
14 #pragma comment(linker, "/subsystem:console")\r
15 #else\r
16 #pragma comment(linker, "/subsystem:windows")\r
17 #endif\r
18 \r
19 using namespace std;\r
20 \r
21 \r
22 const int WIDTH = 56, HEIGHT = 29;\r
23 \r
24 std::string map;\r
25 \r
26 clock_t game_clock;\r
27 double frame_time = 30;\r
28 \r
29 int lvl = 0;\r
30 \r
31 // Util\r
32 \r
33 double get_dur(clock_t then) {\r
34   double result = (double)(clock() - then) / CLOCKS_PER_SEC;\r
35   return result * 1000;\r
36 }\r
37 \r
38 // Edit\r
39 \r
40 HWND hwnd_notepad = NULL;\r
41 HWND hwnd_edit = NULL;\r
42 \r
43 string get_text() {\r
44   int len = SendMessageA(hwnd_edit, WM_GETTEXTLENGTH, 0, 0);\r
45   string result;\r
46   result.resize(len + 1);\r
47   int read = SendMessageA(hwnd_edit, WM_GETTEXT, len + 1, (LPARAM)result.data());\r
48   if (read != len)\r
49     puts("???");\r
50   result.data()[read] = 0;\r
51   return result;\r
52 }\r
53 \r
54 void select_all() {\r
55   SendMessage(hwnd_edit, EM_SETSEL, 0, get_text().size());\r
56 }\r
57 \r
58 void select_length(int from, int length) {\r
59   SendMessage(hwnd_edit, EM_SETSEL, from, from + length);\r
60 }\r
61 \r
62 void select_from_to(int from, int to) {\r
63   SendMessage(hwnd_edit, EM_SETSEL, from, to);\r
64 }\r
65 \r
66 void replace(string str) {\r
67   SendMessage(hwnd_edit, EM_REPLACESEL, FALSE, (LPARAM)str.c_str());\r
68 }\r
69 \r
70 int get_pos(int x, int y) {\r
71   return (y + 1) * (WIDTH + 3) + x + 1;\r
72 }\r
73 \r
74 char get_block(int x, int y) {\r
75   return map[get_pos(x, y)];\r
76 }\r
77 \r
78 // Player\r
79 struct Player {\r
80   bool left = false;\r
81   int x, y;\r
82   int x_spawn, y_spawn;\r
83   int jumping = 0;\r
84 \r
85   void clear() {\r
86     int pos = get_pos(x, y);\r
87     select_length(pos, 1);\r
88     replace({ map[pos], 0 });\r
89   }\r
90   \r
91   void draw() {\r
92     int pos = get_pos(x, y);\r
93     select_length(pos, 1);\r
94     replace({ left ? '<' : '>', 0 });\r
95   }\r
96 \r
97   void move_to(int x, int y) {\r
98     clear();\r
99     if (this->x > x) left = true;\r
100     if (this->x < x) left = false;\r
101     this->x = x;\r
102     this->y = y;\r
103     draw();\r
104   }\r
105 \r
106   void move(int dx, int dy) {\r
107     move_to(x + dx, y + dy);\r
108   }\r
109 \r
110   bool collision_x(int n) {\r
111     if (x + n < 0 || x + n >= WIDTH)\r
112       return true;\r
113     for (int i = 0; i != n; i += (n < 0 ? -1 : 1))\r
114       if (get_block(x + i + (n < 0 ? -1 : 1), y) == 'X')\r
115         return true;\r
116     return false;\r
117   }\r
118   bool collision_y(int n) {\r
119     if (y + n < 0 || y + n >= HEIGHT)\r
120       return true;\r
121     for (int i = 0; i != n; i += (n < 0 ? -1 : 1))\r
122       if (get_block(x, y + i + (n < 0 ? -1 : 1)) == 'X')\r
123         return true;\r
124     return false;\r
125   }\r
126 };\r
127 Player player { 0, 0 };\r
128 \r
129 // Lvl\r
130 \r
131 std::string read_map(int lvl) {\r
132   std::ifstream ifs("lvl/" + std::to_string(lvl) + ".txt",\r
133                     std::ios::in | std::ios::binary);\r
134   if (!ifs.good())\r
135     puts("mist");\r
136   ifs.seekg(0, std::ios::end);\r
137   int length = ifs.tellg();\r
138   ifs.seekg(0, std::ios::beg);\r
139   char *buffer = new char[length + 1];\r
140   ifs.read(buffer, length);\r
141   ifs.close();\r
142   buffer[length] = 0;\r
143   std::string result(buffer);\r
144   delete[] buffer;\r
145   return result;\r
146 }\r
147 \r
148 void redraw() {\r
149   select_all();\r
150   replace(map);\r
151   player.draw();\r
152 }\r
153 \r
154 void load_level(int l) {\r
155   lvl = l;\r
156   map = read_map(l);\r
157   redraw();\r
158   for (int x = 0; x < WIDTH; x++) {\r
159     for (int y = 0; y < HEIGHT; y++) {\r
160       if (map[get_pos(x, y)] == 'S') {\r
161         player.x_spawn = x;\r
162         player.y_spawn = y;\r
163         player.move_to(x, y);\r
164         return;\r
165       }\r
166     }\r
167   }\r
168 }\r
169 \r
170 // Notepad\r
171 \r
172 STARTUPINFOA si;\r
173 PROCESS_INFORMATION pi;\r
174 \r
175 bool check_class(HWND hwnd, const char *name) {\r
176     char b[64];\r
177     int n = GetClassNameA(hwnd, b, 64);\r
178     return (n && strncmp(b, name, n) == 0);\r
179 }\r
180 \r
181 void start_notepad() {\r
182   ZeroMemory(&si, sizeof(si));\r
183   si.cb = sizeof(si);\r
184   ZeroMemory(&pi, sizeof(pi));\r
185 \r
186   if (!CreateProcessA(NULL,  // No module name (use command line)\r
187                       "notepad.exe lvl/0.txt",   // Command line\r
188                       NULL,  // Process handle not inheritable\r
189                       NULL,  // Thread handle not inheritable\r
190                       FALSE, // Set handle inheritance to FALSE\r
191                       0,     // No creation flags\r
192                       NULL,  // Use parent's environment block\r
193                       NULL,  // Use parent's starting directory\r
194                       &si,   // Pointer to STARTUPINFO structure\r
195                       &pi)   // Pointer to PROCESS_INFORMATION structure\r
196   ) {\r
197     printf("CreateProcess failed (%d).\n", GetLastError());\r
198   }\r
199   Sleep(1000);\r
200   EnumWindows([](HWND hwnd, LPARAM lp)->BOOL{\r
201     if (check_class(hwnd, "Notepad")) {\r
202       *(HWND*)lp = hwnd;\r
203       return false;\r
204     }\r
205     return true;\r
206   }, (LPARAM)&hwnd_notepad);\r
207   EnumChildWindows(hwnd_notepad, [](HWND hwnd, LPARAM lp)->BOOL{\r
208     printf("looking at %p\n", hwnd);\r
209     if (check_class(hwnd, "NotepadTextBox")) {\r
210       EnumChildWindows(hwnd, [](HWND hwnd, LPARAM lp)->BOOL{\r
211         printf("  looking at %p\n", hwnd);\r
212         const size_t size = (WIDTH+4)*(HEIGHT+2);\r
213         char b[size];\r
214         DWORD_PTR dwResult;\r
215         b[0] = 0;\r
216         SendMessageTimeoutA(hwnd, WM_GETTEXT, size, (LPARAM)b,\r
217                 SMTO_ABORTIFHUNG, 100, &dwResult);\r
218         if (b[0] == '+') {\r
219           // TODO: check lvl\r
220           printf("%.*s.\n", size, b);\r
221           *(HWND*)lp = hwnd;\r
222           return false;\r
223         }\r
224         return true;\r
225       }, lp);\r
226       if (lp) return false;\r
227     }\r
228     return true;\r
229   }, (LPARAM)&hwnd_edit);\r
230   SendMessage(hwnd_edit, EM_SETREADONLY, TRUE, NULL);\r
231 }\r
232 void close_notepad() {\r
233   SendMessage(hwnd_edit, EM_SETREADONLY, FALSE, NULL);\r
234   //TerminateProcess(pi.hProcess, 0);\r
235   //CloseHandle(pi.hProcess);\r
236   //CloseHandle(pi.hThread);\r
237 }\r
238 \r
239 // Keys\r
240 \r
241 enum class Key {\r
242   Left,\r
243   Right,\r
244   Jump,\r
245   Exit,\r
246   Redraw,\r
247   COUNT\r
248 };\r
249 \r
250 bool key_state[(uint64_t)Key::COUNT];\r
251 bool key_state_old[(uint64_t)Key::COUNT];\r
252 \r
253 int key_get_vk(Key key) {\r
254   switch (key) {\r
255     case Key::Left: return 'A';\r
256     case Key::Right: return 'D';\r
257     case Key::Jump: return VK_SPACE;\r
258     case Key::Exit: return VK_ESCAPE;\r
259     case Key::Redraw: return 'R';\r
260     default: return 0;\r
261   }\r
262 }\r
263 \r
264 void update_key_state() {\r
265   for (int i = 0; i < (int)Key::COUNT; i++) {\r
266     key_state[i] = GetAsyncKeyState(key_get_vk((Key)i));\r
267   }\r
268 }\r
269 \r
270 void update_key_state_old() {\r
271   for (int i = 0; i < (int)Key::COUNT; i++) {\r
272     key_state_old[i] = key_state[i];\r
273   }\r
274 }\r
275 \r
276 bool key_pressed(Key key) {\r
277   return key_state[(int)key] && !key_state_old[(int)key];\r
278 }\r
279 \r
280 bool key_down(Key key) {\r
281   return key_state[(int)key];\r
282 }\r
283 \r
284 bool key_up(Key key) {\r
285   return !key_state[(int)key];\r
286 }\r
287 \r
288 // Gameplay\r
289   \r
290 clock_t jump_clock = clock();\r
291 int jump_height = 3;\r
292 double jump_time1 = 50;\r
293 double jump_time2 = 100;\r
294 int text_speed = 50;\r
295 \r
296 void update_play(bool can_jump = true, int x_min = 0, int x_max = WIDTH - 1) {\r
297   if (key_down(Key::Left) &&\r
298       !player.collision_x(-1) &&\r
299       player.x > x_min)\r
300       player.move(-1, 0);\r
301   if (key_down(Key::Right) &&\r
302       !player.collision_x(1) &&\r
303       player.x < x_max)\r
304     player.move(+1, 0);\r
305 \r
306   if (key_pressed(Key::Jump) &&\r
307       can_jump &&\r
308       player.jumping == 0 &&\r
309       !player.collision_y(-1)) {\r
310     player.jumping = 1;\r
311     player.move(0, -1);\r
312     jump_clock = clock();\r
313   }\r
314   if (player.jumping != 0) {\r
315     if (player.jumping < jump_height && get_dur(jump_clock) > jump_time1) {\r
316       if (!player.collision_y(-1)) {\r
317         player.move(0, -1);\r
318         player.jumping++;\r
319         jump_clock = clock();\r
320       } else {\r
321         player.jumping = jump_height;\r
322       }\r
323     } else if (player.jumping == jump_height && get_dur(jump_clock) > jump_time2) {\r
324       player.jumping = 0;\r
325     }\r
326   }\r
327   if (!player.jumping && !player.collision_y(1))\r
328     player.move(0, +1);\r
329 \r
330   char b = get_block(player.x, player.y);\r
331   if (b == '/' || b == '\\' || b == '<' || b == '>')\r
332     player.move_to(player.x_spawn, player.y_spawn);\r
333 }\r
334 \r
335 void print_text(int x, int y, string text, int delay) {\r
336   for (int i = 0; i < text.size(); i++) {\r
337     select_length(get_pos(x + i, y), 1);\r
338     replace(text.substr(i, 1));\r
339     Sleep(delay);\r
340   }\r
341 }\r
342 \r
343 void intro() {\r
344   static int progress = 0;\r
345   switch (progress) {\r
346   case 0:\r
347     print_text(4, 2, "Move with A/D.", text_speed);\r
348 \r
349     progress++;\r
350     break;\r
351   case 1:\r
352     update_play(false);\r
353     if (player.x == 17) {\r
354       print_text(4, 4, "Jump with up.", text_speed);\r
355       print_text(4, 6, "Stand on x.", text_speed);\r
356       progress++;\r
357     }\r
358     break;\r
359   case 2:\r
360     update_play();\r
361     if (player.x == 22) {\r
362       print_text(4, 8, "Collect ? for ???.", text_speed);\r
363       progress++;\r
364     }\r
365     break;\r
366   case 3:\r
367     update_play(true, 0, 33);\r
368     if (get_block(player.x, player.y) == '?') {\r
369       print_text(4, 10, "Avoid /\\.", text_speed);\r
370       progress++;\r
371     }\r
372     break;\r
373   case 4:\r
374     update_play();\r
375     if (player.x == 39) {\r
376       print_text(4, 14, "Finish lvl by reaching O.", text_speed);\r
377       progress++;\r
378     }\r
379     break;\r
380   case 5:\r
381     update_play();\r
382     if (get_block(player.x, player.y) == 'O') {\r
383       load_level(1);\r
384     }\r
385     break;\r
386   }\r
387 }\r
388 \r
389 void lvl1() {\r
390   static int progress = 0;\r
391   switch (progress) {\r
392   case 0:\r
393     print_text(4, 2, "Also avoid > and <.", text_speed);\r
394     progress++;\r
395     break;\r
396   case 1:\r
397     update_play();\r
398     break;\r
399   }\r
400 }\r
401 \r
402 void update_game() {\r
403   switch (lvl) {\r
404   case 0:\r
405     intro();\r
406     break;\r
407   case 1:\r
408     lvl1();\r
409     break;\r
410   }\r
411 }\r
412 \r
413 #ifdef CONSOLE\r
414 int main(int argc, char **argv) {\r
415 #else\r
416 int WinMain(HINSTANCE a0, HINSTANCE a1, LPSTR a2, int a3) {\r
417 #endif\r
418   start_notepad();\r
419   if (hwnd_notepad == NULL || hwnd_edit == NULL) {\r
420     puts("error");\r
421     return 1;\r
422   }\r
423 \r
424   load_level(0);\r
425   \r
426   while (true) {\r
427     //dt = ((double)clock() - game_clock) / CLOCKS_PER_SEC * 1000;\r
428     if (get_dur(game_clock) < frame_time) continue;\r
429     game_clock = clock();\r
430     update_key_state();\r
431 \r
432     if (key_pressed(Key::Exit))\r
433       break;\r
434     if (key_pressed(Key::Redraw))\r
435       redraw();\r
436     \r
437     update_game();\r
438 \r
439     update_key_state_old();\r
440   }\r
441   close_notepad();\r
442 \r
443   return 0;\r
444 }\r