2D Run-and-gun shooter inspired by One Man's Doomsday, Counter-Strike, and Metal Slug.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

input.test.c 17 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. #include <bdd-for-c.h>
  2. #include <subprojects/SDL/SDL_keyboard.mock.h>
  3. #include <subprojects/SDL/SDL_joystick.mock.h>
  4. #include <subprojects/SDL/SDL_stdinc.mock.h>
  5. #include <subprojects/minIni/minIni.mock.h>
  6. #include <subprojects/portmidi/portmidi.mock.h>
  7. #include <stdinc/IZ_string.mock.h>
  8. #include <stdinc/IZ_stdlib.mock.h>
  9. #include <config/IZ_config.mock.h>
  10. #include <game/input/IZ_keyboard.h>
  11. #include <game/input/IZ_joystick.h>
  12. #include <game/input/IZ_midi.h>
  13. i16 GenerateAxisValueWithinThreshold(u16 threshold) {
  14. return rand() % threshold;
  15. }
  16. i16 GenerateAxisValueOutsideThreshold(u16 threshold) {
  17. return threshold + (rand() % (RAND_MAX - threshold - 1)) + 1;
  18. }
  19. spec("input") {
  20. describe("joystick") {
  21. describe("Initialize") {
  22. static IZ_JoystickState state[IZ_PLAYERS];
  23. after_each() {
  24. mock_reset(IZ_memcpy);
  25. }
  26. after_each() {
  27. mock_reset(SDL_NumJoysticks);
  28. }
  29. after_each() {
  30. mock_reset(SDL_JoystickOpen);
  31. }
  32. after_each() {
  33. mock_reset(IZ_ConfigSave);
  34. }
  35. after_each() {
  36. mock_reset(IZ_ConfigInitialize);
  37. }
  38. it("sets initial state") {
  39. IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  40. check(mock_is_called(IZ_memcpy), "Initial state not loaded.");
  41. check(mock_is_called(SDL_NumJoysticks), "Connected joysticks not checked.");
  42. }
  43. it("calls load method") {
  44. IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  45. check(mock_is_called(IZ_ConfigInitialize), "Config load function not called.");
  46. }
  47. it("calls save method") {
  48. IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  49. check(mock_is_called(IZ_ConfigSave), "Config save function not called.");
  50. }
  51. it("opens device handles") {
  52. mock_set_expected_calls(SDL_JoystickOpen, MOCK_OPEN_JOYSTICKS);
  53. IZ_JoystickInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  54. check(
  55. mock_get_expected_calls(SDL_JoystickOpen) == mock_get_actual_calls(SDL_JoystickOpen),
  56. "Call count mismatch for SDL_JoystickOpen() (expected %u, received %u).",
  57. mock_get_expected_calls(SDL_JoystickOpen),
  58. mock_get_actual_calls(SDL_JoystickOpen)
  59. );
  60. }
  61. }
  62. describe("HandleEvents") {
  63. static SDL_Event e;
  64. static IZ_JoystickState state[IZ_PLAYERS] = {};
  65. static IZ_Action action[IZ_PLAYERS] = {};
  66. u8 p;
  67. for (p = 0; p < IZ_PLAYERS; p += 1) {
  68. describe("on player %u", p) {
  69. describe("on axis motion events") {
  70. before_each() {
  71. e.type = SDL_JOYAXISMOTION;
  72. state[p].config.axis_threshold = 8000u;
  73. }
  74. describe("on primary horizontal direction") {
  75. before_each() {
  76. e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_HORIZONTAL1;
  77. }
  78. it("handles positive motion") {
  79. e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  80. action[p] = 0;
  81. printf("(axis value: %d) ", e.jaxis.value);
  82. IZ_JoystickHandleEvents(&state, &action, e);
  83. check(
  84. action[p] == (0x1 << IZ_ACTION_INDEX_RIGHT),
  85. "Action not set."
  86. );
  87. }
  88. it("handles negative motion") {
  89. e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  90. action[p] = 0;
  91. printf("(axis value: %d) ", e.jaxis.value);
  92. IZ_JoystickHandleEvents(&state, &action, e);
  93. check(
  94. action[p] == (0x1 << IZ_ACTION_INDEX_LEFT),
  95. "Action not set."
  96. );
  97. }
  98. it("handles neutral motion") {
  99. e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold);
  100. action[p] = 0;
  101. printf("(axis value: %d) ", e.jaxis.value);
  102. IZ_JoystickHandleEvents(&state, &action, e);
  103. check(
  104. action[p] == 0,
  105. "Action not set."
  106. );
  107. }
  108. }
  109. describe("on secondary horizontal direction") {
  110. before_each() {
  111. e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_HORIZONTAL2;
  112. }
  113. it("handles positive motion") {
  114. e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  115. action[p] = 0;
  116. printf("(axis value: %d) ", e.jaxis.value);
  117. IZ_JoystickHandleEvents(&state, &action, e);
  118. check(
  119. action[p] == (0x1 << IZ_ACTION_INDEX_RIGHT),
  120. "Action not set."
  121. );
  122. }
  123. it("handles negative motion") {
  124. e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  125. action[p] = 0;
  126. printf("(axis value: %d) ", e.jaxis.value);
  127. IZ_JoystickHandleEvents(&state, &action, e);
  128. check(
  129. action[p] == (0x1 << IZ_ACTION_INDEX_LEFT),
  130. "Action not set."
  131. );
  132. }
  133. it("handles neutral motion") {
  134. e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold);;
  135. action[p] = 0;
  136. printf("(axis value: %d) ", e.jaxis.value);
  137. IZ_JoystickHandleEvents(&state, &action, e);
  138. check(
  139. action[p] == 0,
  140. "Action not set."
  141. );
  142. }
  143. }
  144. describe("on primary vertical direction") {
  145. before_each() {
  146. e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_VERTICAL1;
  147. }
  148. it("handles positive motion") {
  149. e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  150. action[p] = 0;
  151. printf("(axis value: %d) ", e.jaxis.value);
  152. IZ_JoystickHandleEvents(&state, &action, e);
  153. check(
  154. action[p] == (0x1 << IZ_ACTION_INDEX_DOWN),
  155. "Action not set."
  156. );
  157. }
  158. it("handles negative motion") {
  159. e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  160. action[p] = 0;
  161. printf("(axis value: %d) ", e.jaxis.value);
  162. IZ_JoystickHandleEvents(&state, &action, e);
  163. check(
  164. action[p] == (0x1 << IZ_ACTION_INDEX_UP),
  165. "Action not set."
  166. );
  167. }
  168. it("handles neutral motion") {
  169. e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold);;
  170. action[p] = 0;
  171. printf("(axis value: %d) ", e.jaxis.value);
  172. IZ_JoystickHandleEvents(&state, &action, e);
  173. check(
  174. action[p] == 0,
  175. "Action not set."
  176. );
  177. }
  178. }
  179. describe("on secondary vertical direction") {
  180. before_each() {
  181. e.jaxis.axis = IZ_JOY_AXIS_DIRECTION_VERTICAL2;
  182. }
  183. it("handles positive motion") {
  184. e.jaxis.value = GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  185. action[p] = 0;
  186. printf("(axis value: %d) ", e.jaxis.value);
  187. IZ_JoystickHandleEvents(&state, &action, e);
  188. check(
  189. action[p] == (0x1 << IZ_ACTION_INDEX_DOWN),
  190. "Action not set."
  191. );
  192. }
  193. it("handles negative motion") {
  194. e.jaxis.value = -GenerateAxisValueOutsideThreshold(state[p].config.axis_threshold);
  195. action[p] = 0;
  196. printf("(axis value: %d) ", e.jaxis.value);
  197. IZ_JoystickHandleEvents(&state, &action, e);
  198. check(
  199. action[p] == (0x1 << IZ_ACTION_INDEX_UP),
  200. "Action not set."
  201. );
  202. }
  203. it("handles neutral motion") {
  204. e.jaxis.value = GenerateAxisValueWithinThreshold(state[p].config.axis_threshold);;
  205. action[p] = 0;
  206. printf("(axis value: %d) ", e.jaxis.value);
  207. IZ_JoystickHandleEvents(&state, &action, e);
  208. check(
  209. action[p] == 0,
  210. "Action not set."
  211. );
  212. }
  213. }
  214. }
  215. describe("on hat motion events") {
  216. before_each() {
  217. e.type = SDL_JOYHATMOTION;
  218. }
  219. for (u8 i = 0; i < 4; i += 1) {
  220. it("handles motion for %s action", IZ_ACTION_NAMES[i]) {
  221. e.jhat.value = (0x1u << i);
  222. action[p] = 0;
  223. IZ_JoystickHandleEvents(&state, &action, e);
  224. check(
  225. action[p] == (0x1u << i),
  226. "Action not set."
  227. );
  228. }
  229. it("handles motion for %s deactivation", IZ_ACTION_NAMES[i]) {
  230. e.jhat.value = 0;
  231. action[p] = ~0;
  232. IZ_JoystickHandleEvents(&state, &action, e);
  233. check(
  234. !(action[p] & (0x1 << i)),
  235. "Action not unset."
  236. );
  237. }
  238. }
  239. }
  240. describe("on button events") {
  241. for (u8 i = 4; i < IZ_CONTROLS; i += 1) {
  242. it("handles %s action activation", IZ_ACTION_NAMES[i]) {
  243. e.type = SDL_JOYBUTTONDOWN;
  244. e.jbutton.button = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i];
  245. state[p].config.control_mapping[i] = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i];
  246. action[p] = 0;
  247. IZ_JoystickHandleEvents(&state, &action, e);
  248. check(
  249. action[p] == (0x1u << i),
  250. "Action not set."
  251. );
  252. }
  253. it("handles %s action deactivation", IZ_ACTION_NAMES[i]) {
  254. e.type = SDL_JOYBUTTONUP;
  255. e.jbutton.button = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i];
  256. state[p].config.control_mapping[i] = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i];
  257. action[p] = ~0;
  258. IZ_JoystickHandleEvents(&state, &action, e);
  259. check(
  260. !(action[p] & (0x1 << i)),
  261. "Action not unset."
  262. );
  263. }
  264. }
  265. }
  266. }
  267. }
  268. }
  269. describe("SaveConfig") {
  270. static IZ_JoystickState state[IZ_PLAYERS];
  271. after_each() {
  272. mock_reset(IZ_ConfigSave);
  273. }
  274. before_each() {
  275. for (u8 p = 0; p < IZ_PLAYERS; p += 1) {
  276. for (u8 i = 0; i < IZ_CONTROLS; i += 1) {
  277. state[p].config.control_mapping[i] = IZ_JOYSTICK_DEFAULT_STATE[p].config.control_mapping[i];
  278. }
  279. }
  280. }
  281. it("calls save method") {
  282. IZ_JoystickSaveConfig(&state, IZ_CONFIG_GAME_PATH);
  283. check(mock_is_called(IZ_ConfigSave), "Config save function not called.");
  284. }
  285. }
  286. describe("Teardown") {
  287. static void* device = (void*) 1;
  288. static IZ_JoystickState state[IZ_PLAYERS] = {};
  289. before_each() {
  290. for (u8 p = 0; p < IZ_PLAYERS; p += 1) {
  291. state[p].device = device;
  292. }
  293. }
  294. after_each() {
  295. mock_reset(SDL_JoystickClose);
  296. }
  297. it("closes opened devices") {
  298. mock_set_expected_calls(SDL_JoystickClose, IZ_PLAYERS);
  299. IZ_JoystickTeardown(&state);
  300. check(
  301. mock_get_expected_calls(SDL_JoystickClose) == mock_get_actual_calls(SDL_JoystickClose),
  302. "Call count mismatch for SDL_JoystickClose() (expected %u, received %u).",
  303. mock_get_expected_calls(SDL_JoystickClose),
  304. mock_get_actual_calls(SDL_JoystickClose)
  305. );
  306. }
  307. }
  308. }
  309. describe("keyboard") {
  310. describe("Initialize") {
  311. static IZ_KeyboardState state[IZ_PLAYERS] = {};
  312. after_each() {
  313. mock_reset(IZ_memcpy);
  314. }
  315. after_each() {
  316. mock_reset(IZ_ConfigInitialize);
  317. }
  318. after_each() {
  319. mock_reset(IZ_ConfigSave);
  320. }
  321. before_each() {
  322. for (u8 p = 0; p < IZ_PLAYERS; p += 1) {
  323. for (u8 i = 0; i < IZ_CONTROLS; i += 1) {
  324. state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i];
  325. }
  326. }
  327. }
  328. it("sets initial state") {
  329. IZ_KeyboardInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  330. check(mock_is_called(IZ_memcpy), "Initial state not loaded.");
  331. }
  332. it("calls load method") {
  333. IZ_KeyboardInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  334. check(mock_is_called(IZ_ConfigInitialize), "Config load function not called.");
  335. }
  336. it("calls save method") {
  337. IZ_KeyboardInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  338. check(mock_is_called(IZ_ConfigSave), "Config save function not called.");
  339. }
  340. }
  341. describe("HandleEvents") {
  342. static SDL_Event e;
  343. static IZ_KeyboardState state[IZ_PLAYERS] = {};
  344. static IZ_Action action[IZ_PLAYERS] = {};
  345. for (u8 p = 0; p < IZ_PLAYERS; p += 1) {
  346. describe("on player %u", p) {
  347. for (u8 i = 0; i < IZ_CONTROLS; i += 1) {
  348. it("handles %s action activation", IZ_ACTION_NAMES[i]) {
  349. e.type = SDL_KEYDOWN;
  350. e.key.keysym.sym = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i];
  351. state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i];
  352. action[p] = 0;
  353. IZ_KeyboardHandleEvents(&state, &action, e);
  354. check(
  355. action[p] == (0x1 << i),
  356. "Action not set."
  357. );
  358. }
  359. it("handles %s action deactivation", IZ_ACTION_NAMES[i]) {
  360. e.type = SDL_KEYUP;
  361. e.key.keysym.sym = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i];
  362. state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i];
  363. action[p] = ~0;
  364. IZ_KeyboardHandleEvents(&state, &action, e);
  365. check(
  366. !(action[p] & (0x1 << i)),
  367. "Action not unset."
  368. );
  369. }
  370. }
  371. }
  372. }
  373. }
  374. describe("SaveConfig") {
  375. static IZ_KeyboardState state[IZ_PLAYERS] = {};
  376. after_each() {
  377. mock_reset(IZ_ConfigSave);
  378. }
  379. before_each() {
  380. for (u8 p = 0; p < IZ_PLAYERS; p += 1) {
  381. for (u8 i = 0; i < IZ_CONTROLS; i += 1) {
  382. state[p].config.control_mapping[i] = IZ_KEYBOARD_DEFAULT_STATE[p].config.control_mapping[i];
  383. }
  384. }
  385. }
  386. it("calls save method") {
  387. IZ_KeyboardSaveConfig(&state, IZ_CONFIG_GAME_PATH);
  388. check(mock_is_called(IZ_ConfigSave), "Config save function not called.");
  389. }
  390. }
  391. }
  392. describe("midi") {
  393. describe("Initialize") {
  394. static IZ_MIDIInputState state[IZ_PLAYERS];
  395. after_each() {
  396. mock_reset(IZ_memcpy);
  397. }
  398. after_each() {
  399. mock_reset(Pm_CountDevices);
  400. }
  401. after_each() {
  402. mock_reset(Pm_OpenInput);
  403. }
  404. after_each() {
  405. mock_reset(IZ_ConfigSave);
  406. }
  407. after_each() {
  408. mock_reset(IZ_ConfigInitialize);
  409. }
  410. after_each() {
  411. mock_reset(IZ_ConfigSave);
  412. }
  413. after_each() {
  414. mock_reset(IZ_ConfigInitialize);
  415. }
  416. it("sets initial state") {
  417. IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  418. check(mock_is_called(IZ_memcpy), "Initial state not loaded.");
  419. check(mock_is_called(Pm_CountDevices), "Connected MIDI devices not checked.");
  420. }
  421. it("calls load method") {
  422. IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  423. check(mock_is_called(IZ_ConfigInitialize), "Config load function not called.");
  424. }
  425. it("calls save method") {
  426. IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  427. check(mock_is_called(IZ_ConfigSave), "Config save function not called.");
  428. }
  429. it("opens device handles") {
  430. mock_set_expected_calls(Pm_OpenInput, MOCK_OPEN_JOYSTICKS);
  431. IZ_MIDIInputInitialize(&state, IZ_CONFIG_GAME_PATH, 0, NULL);
  432. check(
  433. mock_get_expected_calls(Pm_OpenInput) == mock_get_actual_calls(Pm_OpenInput),
  434. "Call count mismatch for Pm_OpenInput() (expected %u, received %u).",
  435. mock_get_expected_calls(Pm_OpenInput),
  436. mock_get_actual_calls(Pm_OpenInput)
  437. );
  438. }
  439. }
  440. describe("SaveConfig") {
  441. static IZ_MIDIInputState state[IZ_PLAYERS];
  442. after_each() {
  443. mock_reset(IZ_ConfigSave);
  444. }
  445. after_each() {
  446. mock_reset(IZ_ConfigSave);
  447. }
  448. it("calls save method") {
  449. IZ_MIDIInputSaveConfig(&state, IZ_CONFIG_GAME_PATH);
  450. check(mock_is_called(IZ_ConfigSave), "Config save function not called.");
  451. }
  452. }
  453. describe("HandleEvents") {
  454. static PmEvent e;
  455. static IZ_MIDIInputState state[IZ_PLAYERS] = {};
  456. static IZ_Action action[IZ_PLAYERS] = {};
  457. for (u8 p = 0; p < IZ_PLAYERS; p += 1) {
  458. describe("on player %u", p) {
  459. for (u8 i = 0; i < IZ_CONTROLS; i += 1) {
  460. it("handles %s action activation", IZ_ACTION_NAMES[i]) {
  461. e.message = IZ_MIDI_NOTE_ON | (IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i] << 8);
  462. state[p].config.control_mapping[i] = IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i];
  463. action[p] = 0;
  464. IZ_MIDIInputHandleEvents(&state, &action, e);
  465. check(
  466. action[p] == (0x1 << i),
  467. "Action not set."
  468. );
  469. }
  470. it("handles %s action deactivation", IZ_ACTION_NAMES[i]) {
  471. e.message = IZ_MIDI_NOTE_OFF | (IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i] << 8);
  472. state[p].config.control_mapping[i] = IZ_MIDI_INPUT_DEFAULT_STATE[p].config.control_mapping[i];
  473. action[p] = ~0;
  474. IZ_MIDIInputHandleEvents(&state, &action, e);
  475. check(
  476. !(action[p] & (0x1 << i)),
  477. "Action not unset."
  478. );
  479. }
  480. }
  481. }
  482. }
  483. }
  484. describe("Teardown") {
  485. static PmStream* stream;
  486. static IZ_MIDIInputState state[IZ_PLAYERS] = {};
  487. before_each() {
  488. for (u8 p = 0; p < IZ_PLAYERS; p += 1) {
  489. state[p].stream = &stream;
  490. }
  491. }
  492. after_each() {
  493. mock_reset(Pm_Close);
  494. }
  495. it("closes opened devices") {
  496. mock_set_expected_calls(Pm_Close, IZ_PLAYERS);
  497. IZ_MIDIInputTeardown(&state);
  498. check(
  499. mock_get_expected_calls(Pm_Close) == mock_get_actual_calls(Pm_Close),
  500. "Call count mismatch for Pm_Close() (expected %u, received %u).",
  501. mock_get_expected_calls(Pm_Close),
  502. mock_get_actual_calls(Pm_Close)
  503. );
  504. }
  505. }
  506. }
  507. }