From e4eb27a6a76f39d4255433f422c9804f5ef7fb4b Mon Sep 17 00:00:00 2001 From: Boris Gavrikov Date: Sun, 23 Aug 2026 14:33:16 +0200 Subject: [PATCH 1/5] Add second player buttons according to https://pico-8.fandom.com/wiki/Btn --- src/api.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/api.c b/src/api.c index f1e8245..ae814cb 100644 --- a/src/api.c +++ b/src/api.c @@ -1501,7 +1501,7 @@ static int pico8_btn(lua_State* L) if (argc == 0) { - lua_pushunsigned(L, fix32_value(pico8_ram[0x5f4c], 0)); + lua_pushunsigned(L, fix32_value(pico8_ram[0x5f4c] | (pico8_ram[0x5f4d] << 8), 0)); return 1; } @@ -1542,13 +1542,16 @@ static int pico8_btnp(lua_State* L) if (argc == 0) { - uint8_t result = 0; - for (int b = 0; b < 6; b++) + uint16_t result = 0; + for (int p = 0; p < 2; p++) { - uint8_t f = btn_held_frames[0][b]; - if (f == 1 || (f > 15 && ((f - 15) % 4 == 0))) + for (int b = 0; b < 6; b++) { - result |= (1 << b); + uint8_t f = btn_held_frames[p][b]; + if (f == 1 || (f > 15 && ((f - 15) % 4 == 0))) + { + result |= (1 << (b + p * 8)); + } } } lua_pushunsigned(L, fix32_value(result, 0)); @@ -2727,16 +2730,30 @@ void update_input(SDL_Renderer* renderer) state |= (1 << 5); } #else - if (keys[SDL_SCANCODE_Z] || keys[SDL_SCANCODE_Y] || keys[SDL_SCANCODE_C]) + if (keys[SDL_SCANCODE_Z] || keys[SDL_SCANCODE_Y] || keys[SDL_SCANCODE_C] || + keys[SDL_SCANCODE_N] || keys[SDL_SCANCODE_KP_MULTIPLY]) { state |= (1 << 4); } - if (keys[SDL_SCANCODE_X] || keys[SDL_SCANCODE_V]) + if (keys[SDL_SCANCODE_X] || keys[SDL_SCANCODE_V] || keys[SDL_SCANCODE_M] || + keys[SDL_SCANCODE_8]) { state |= (1 << 5); } #endif } + else + { + int num_keys = 0; + const bool* keys = SDL_GetKeyboardState(&num_keys); + + if (keys[SDL_SCANCODE_S]) state |= (1 << 0); + if (keys[SDL_SCANCODE_F]) state |= (1 << 1); + if (keys[SDL_SCANCODE_E]) state |= (1 << 2); + if (keys[SDL_SCANCODE_D]) state |= (1 << 3); + if (keys[SDL_SCANCODE_LSHIFT] || keys[SDL_SCANCODE_TAB]) state |= (1 << 4); + if (keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_Q]) state |= (1 << 5); + } // Gamepad input: first connected gamepad -> player 0, second -> player 1. SDL_Gamepad* gp = SDL_GetGamepadFromPlayerIndex(p); From 43fe6a169ef475c54424892fba5fc4c4ea4669e9 Mon Sep 17 00:00:00 2001 From: Boris Gavrikov Date: Sun, 23 Aug 2026 14:34:38 +0200 Subject: [PATCH 2/5] Player 2 keyboard test --- tests/tests.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/tests.lua b/tests/tests.lua index a4e331d..c008be7 100644 --- a/tests/tests.lua +++ b/tests/tests.lua @@ -1306,6 +1306,7 @@ end function test_btn() -- No buttons pressed: btn() bitfield == 0. poke(0x5f4c, 0x00) + poke(0x5f4d, 0x00) assert_equal(btn(), 0, "btn() no buttons") -- Single button: btn(b) returns true when bit b is set. @@ -1334,6 +1335,11 @@ function test_btn() assert_true(btn(0, 1) == true, "btn(0,1) p1 pressed") assert_true(btn(0, 0) == false, "btn(0,0) p0 not pressed") + -- No-argument btn() includes player 1 in bits 8-13. + poke(0x5f4c, 0x3f) + poke(0x5f4d, 0x3f) + assert_equal(btn(), 0x3f3f, "btn() both players") + -- Clean up. poke(0x5f4c, 0x00) poke(0x5f4d, 0x00) @@ -1379,11 +1385,19 @@ function test_btnp() assert_true(btnp(6) == false, "btnp(6) out of range") assert_true(btnp(-1) == false, "btnp(-1) out of range") + for b = 0, 5 do + set_btnp_frames(b, 0) + end + -- Player 1: set frame count for p1 button 3. set_btnp_frames(3, 1, 1) assert_true(btnp(3, 1) == true, "btnp(3,1) p1 first press") assert_true(btnp(3, 0) == false, "btnp(3,0) p0 not pressed") + -- No-argument btnp() includes player 1 in bits 8-13. + set_btnp_frames(3, 19, 1) + assert_equal(btnp(), 0x0800, "btnp() p1 mask") + -- Clean up. for b = 0, 5 do set_btnp_frames(b, 0) From fb0d79cb6bfae72d3383fe3bb4fecc3c5ac6ccaa Mon Sep 17 00:00:00 2001 From: Boris Gavrikov Date: Sun, 23 Aug 2026 22:49:22 +0200 Subject: [PATCH 3/5] Theoretically allow 2 touch players --- src/api.c | 12 ++++++++---- src/core.h | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/api.c b/src/api.c index ae814cb..45ec025 100644 --- a/src/api.c +++ b/src/api.c @@ -39,7 +39,8 @@ uint32_t pico8_frame_start = 0; uint32_t pico8_frame_ms = 0; // Touch button state (when SDL_HINT_MOUSE_TOUCH_EVENTS is used). -uint8_t touch_button_state = 0; +uint8_t touch_button_state_player_0 = 0; +uint8_t touch_button_state_player_1 = 0; // Input state: tracks how many consecutive frames each button has been held. // Index [player][button], players 0-1, buttons 0-5. @@ -2785,11 +2786,14 @@ void update_input(SDL_Renderer* renderer) } } - // Touch button state (when SDL_HINT_MOUSE_TOUCH_EVENTS is used) - // Only applied to player 0. + // Touch button state (when SDL_HINT_MOUSE_TOUCH_EVENTS is used). if (p == 0) { - state |= touch_button_state; + state |= touch_button_state_player_0; + } + else + { + state |= touch_button_state_player_1; } pico8_ram[0x5f4c + p] = state; diff --git a/src/core.h b/src/core.h index 5019749..5fd8729 100644 --- a/src/core.h +++ b/src/core.h @@ -27,8 +27,9 @@ typedef enum state } state_t; -// Touch button state (when SDL_HINT_MOUSE_TOUCH_EVENTS is used) -extern uint8_t touch_button_state; +// Touch button state (when SDL_HINT_MOUSE_TOUCH_EVENTS is used). +extern uint8_t touch_button_state_player_0; +extern uint8_t touch_button_state_player_1; void handle_resize(SDL_Renderer* renderer); From aff38cccbeb1c9d60da735c1d06a0ba5199c3a04 Mon Sep 17 00:00:00 2001 From: Boris Gavrikov Date: Sun, 23 Aug 2026 22:54:17 +0200 Subject: [PATCH 4/5] Rename touch_button_state everywhere --- src/core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core.c b/src/core.c index 66388e7..53ad8cb 100644 --- a/src/core.c +++ b/src/core.c @@ -128,7 +128,7 @@ static void update_touch_input(SDL_Renderer* renderer) } // Reset button state and accumulate from all active fingers. - touch_button_state = 0; + touch_button_state_player_0 = 0; // Get touch regions for button detection. int touch_count = 0; @@ -165,7 +165,7 @@ static void update_touch_input(SDL_Renderer* renderer) { if (point_in_touch_region(region_x, region_y, ®ions[r])) { - touch_button_state |= (1 << regions[r].bit); + touch_button_state_player_0 |= (1 << regions[r].bit); } } } @@ -1182,7 +1182,7 @@ bool handle_events(SDL_Renderer* renderer, SDL_Event* event) { if (point_in_touch_region(region_x, region_y, ®ions[i])) { - touch_button_state |= (1 << regions[i].bit); + touch_button_state_player_0 |= (1 << regions[i].bit); uint8_t bit = regions[i].bit; if (bit == 99) @@ -1236,7 +1236,7 @@ bool handle_events(SDL_Renderer* renderer, SDL_Event* event) { if (point_in_touch_region(region_x, region_y, ®ions[i])) { - touch_button_state &= ~(1 << regions[i].bit); + touch_button_state_player_0 &= ~(1 << regions[i].bit); } } } @@ -1335,7 +1335,7 @@ bool handle_events(SDL_Renderer* renderer, SDL_Event* event) { if (point_in_touch_region(region_x, region_y, ®ions[i])) { - touch_button_state |= (1 << regions[i].bit); + touch_button_state_player_0 |= (1 << regions[i].bit); uint8_t bit = regions[i].bit; if (bit == 99) @@ -1355,7 +1355,7 @@ bool handle_events(SDL_Renderer* renderer, SDL_Event* event) // Clear touch button state when touch is released. if (event->button.button == SDL_BUTTON_LEFT && state == STATE_EMULATOR) { - touch_button_state = 0; + touch_button_state_player_0 = 0; } break; } From ac81bbd41933a35d0be9c322e54e354c4db83f6c Mon Sep 17 00:00:00 2001 From: Boris Gavrikov Date: Mon, 24 Aug 2026 11:17:59 +0200 Subject: [PATCH 5/5] Added brackets --- src/api.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/api.c b/src/api.c index 45ec025..f989301 100644 --- a/src/api.c +++ b/src/api.c @@ -2748,12 +2748,30 @@ void update_input(SDL_Renderer* renderer) int num_keys = 0; const bool* keys = SDL_GetKeyboardState(&num_keys); - if (keys[SDL_SCANCODE_S]) state |= (1 << 0); - if (keys[SDL_SCANCODE_F]) state |= (1 << 1); - if (keys[SDL_SCANCODE_E]) state |= (1 << 2); - if (keys[SDL_SCANCODE_D]) state |= (1 << 3); - if (keys[SDL_SCANCODE_LSHIFT] || keys[SDL_SCANCODE_TAB]) state |= (1 << 4); - if (keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_Q]) state |= (1 << 5); + if (keys[SDL_SCANCODE_S]) + { + state |= (1 << 0); + } + if (keys[SDL_SCANCODE_F]) + { + state |= (1 << 1); + } + if (keys[SDL_SCANCODE_E]) + { + state |= (1 << 2); + } + if (keys[SDL_SCANCODE_D]) + { + state |= (1 << 3); + } + if (keys[SDL_SCANCODE_LSHIFT] || keys[SDL_SCANCODE_TAB]) + { + state |= (1 << 4); + } + if (keys[SDL_SCANCODE_A] || keys[SDL_SCANCODE_Q]) + { + state |= (1 << 5); + } } // Gamepad input: first connected gamepad -> player 0, second -> player 1.