fixed cooperate play

This commit is contained in:
2025-12-22 21:26:56 +01:00
parent c14e305a4a
commit 953d6af701
11 changed files with 699 additions and 61 deletions

View File

@ -38,6 +38,7 @@
#include "gameplay/core/Game.h"
#include "gameplay/coop/CoopGame.h"
#include "gameplay/coop/CoopAIController.h"
#include "gameplay/effects/LineEffect.h"
#include "graphics/effects/SpaceWarp.h"
@ -239,6 +240,11 @@ struct TetrisApp::Impl {
bool suppressLineVoiceForLevelUp = false;
bool skipNextLevelUpJingle = false;
// COOPERATE option: when true, right player is AI-controlled.
bool coopVsAI = false;
CoopAIController coopAI;
AppState state = AppState::Loading;
double loadingProgress = 0.0;
Uint64 loadStart = 0;
@ -567,6 +573,7 @@ int TetrisApp::Impl::init()
ctx.mainScreenW = mainScreenW;
ctx.mainScreenH = mainScreenH;
ctx.musicEnabled = &musicEnabled;
ctx.coopVsAI = &coopVsAI;
ctx.startLevelSelection = &startLevelSelection;
ctx.hoveredButton = &hoveredButton;
ctx.showSettingsPopup = &showSettingsPopup;
@ -628,10 +635,17 @@ int TetrisApp::Impl::init()
return;
}
if (state != AppState::Menu) {
if (game && game->getMode() == GameMode::Cooperate && coopGame && coopVsAI) {
coopAI.reset();
}
state = AppState::Playing;
ctx.stateManager->setState(state);
return;
}
if (game && game->getMode() == GameMode::Cooperate && coopGame && coopVsAI) {
coopAI.reset();
}
beginStateFade(AppState::Playing, true);
};
ctx.startPlayTransition = startMenuPlayTransition;
@ -894,27 +908,45 @@ void TetrisApp::Impl::runLoop()
if (!showHelpOverlay && state == AppState::GameOver && e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
if (isNewHighScore) {
if (game && game->getMode() == GameMode::Cooperate && coopGame) {
// Two-name entry flow
if (e.key.scancode == SDL_SCANCODE_BACKSPACE) {
if (highScoreEntryIndex == 0 && !playerName.empty()) playerName.pop_back();
else if (highScoreEntryIndex == 1 && !player2Name.empty()) player2Name.pop_back();
} else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
if (highScoreEntryIndex == 0) {
if (playerName.empty()) playerName = "P1";
highScoreEntryIndex = 1; // move to second name
} else {
if (coopVsAI) {
// One-name entry flow (CPU is LEFT, human enters RIGHT name)
if (e.key.scancode == SDL_SCANCODE_BACKSPACE) {
if (!player2Name.empty()) player2Name.pop_back();
} else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
if (player2Name.empty()) player2Name = "P2";
// Submit combined name
std::string combined = playerName + " & " + player2Name;
std::string combined = std::string("CPU") + " & " + player2Name;
int leftScore = coopGame->score(CoopGame::PlayerSide::Left);
int rightScore = coopGame->score(CoopGame::PlayerSide::Right);
int combinedScore = leftScore + rightScore;
ensureScoresLoaded();
scores.submit(combinedScore, coopGame->lines(), coopGame->level(), coopGame->elapsed(), combined, "cooperate");
Settings::instance().setPlayerName(playerName);
Settings::instance().setPlayerName(player2Name);
isNewHighScore = false;
SDL_StopTextInput(window);
}
} else {
// Two-name entry flow
if (e.key.scancode == SDL_SCANCODE_BACKSPACE) {
if (highScoreEntryIndex == 0 && !playerName.empty()) playerName.pop_back();
else if (highScoreEntryIndex == 1 && !player2Name.empty()) player2Name.pop_back();
} else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
if (highScoreEntryIndex == 0) {
if (playerName.empty()) playerName = "P1";
highScoreEntryIndex = 1; // move to second name
} else {
if (player2Name.empty()) player2Name = "P2";
// Submit combined name
std::string combined = playerName + " & " + player2Name;
int leftScore = coopGame->score(CoopGame::PlayerSide::Left);
int rightScore = coopGame->score(CoopGame::PlayerSide::Right);
int combinedScore = leftScore + rightScore;
ensureScoresLoaded();
scores.submit(combinedScore, coopGame->lines(), coopGame->level(), coopGame->elapsed(), combined, "cooperate");
Settings::instance().setPlayerName(playerName);
isNewHighScore = false;
SDL_StopTextInput(window);
}
}
}
} else {
if (e.key.scancode == SDL_SCANCODE_BACKSPACE && !playerName.empty()) {
@ -972,11 +1004,9 @@ void TetrisApp::Impl::runLoop()
startMenuPlayTransition();
break;
case ui::BottomMenuItem::Cooperate:
if (game) {
game->setMode(GameMode::Cooperate);
game->reset(startLevelSelection);
if (menuState) {
menuState->showCoopSetupPanel(true);
}
startMenuPlayTransition();
break;
case ui::BottomMenuItem::Challenge:
if (game) {
@ -1288,13 +1318,44 @@ void TetrisApp::Impl::runLoop()
p2LeftHeld = false;
p2RightHeld = false;
} else {
handleSide(CoopGame::PlayerSide::Left, p1LeftHeld, p1RightHeld, p1MoveTimerMs, SDL_SCANCODE_A, SDL_SCANCODE_D, SDL_SCANCODE_S);
handleSide(CoopGame::PlayerSide::Right, p2LeftHeld, p2RightHeld, p2MoveTimerMs, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_DOWN);
// Define canonical key mappings for left and right players
const SDL_Scancode leftLeftKey = SDL_SCANCODE_A;
const SDL_Scancode leftRightKey = SDL_SCANCODE_D;
const SDL_Scancode leftDownKey = SDL_SCANCODE_S;
p1LeftHeld = ks[SDL_SCANCODE_A];
p1RightHeld = ks[SDL_SCANCODE_D];
p2LeftHeld = ks[SDL_SCANCODE_LEFT];
p2RightHeld = ks[SDL_SCANCODE_RIGHT];
const SDL_Scancode rightLeftKey = SDL_SCANCODE_LEFT;
const SDL_Scancode rightRightKey = SDL_SCANCODE_RIGHT;
const SDL_Scancode rightDownKey = SDL_SCANCODE_DOWN;
if (!coopVsAI) {
// Standard two-player: left uses WASD, right uses arrow keys
handleSide(CoopGame::PlayerSide::Left, p1LeftHeld, p1RightHeld, p1MoveTimerMs, leftLeftKey, leftRightKey, leftDownKey);
handleSide(CoopGame::PlayerSide::Right, p2LeftHeld, p2RightHeld, p2MoveTimerMs, rightLeftKey, rightRightKey, rightDownKey);
p1LeftHeld = ks[leftLeftKey];
p1RightHeld = ks[leftRightKey];
p2LeftHeld = ks[rightLeftKey];
p2RightHeld = ks[rightRightKey];
} else {
// Coop vs CPU: AI controls LEFT, human controls RIGHT (arrow keys).
// Handle continuous input for the human on the right side.
handleSide(CoopGame::PlayerSide::Right, p2LeftHeld, p2RightHeld, p2MoveTimerMs, rightLeftKey, rightRightKey, rightDownKey);
// Mirror the human soft-drop to the AI-controlled left board so both fall together.
const bool pRightSoftDrop = ks[rightDownKey];
coopGame->setSoftDropping(CoopGame::PlayerSide::Left, pRightSoftDrop);
// Reset left continuous timers/held flags (AI handles movement)
p1MoveTimerMs = 0.0;
p1LeftHeld = false;
p1RightHeld = false;
// Update AI for the left side
coopAI.update(*coopGame, CoopGame::PlayerSide::Left, frameMs);
// Update human-held flags for right-side controls so DAS/ARR state is tracked
p2LeftHeld = ks[rightLeftKey];
p2RightHeld = ks[rightRightKey];
}
coopGame->tickGravity(frameMs);
coopGame->updateVisualEffects(frameMs);
@ -1307,14 +1368,22 @@ void TetrisApp::Impl::runLoop()
int combinedScore = leftScore + rightScore;
if (combinedScore > 0) {
isNewHighScore = true;
playerName.clear();
player2Name.clear();
highScoreEntryIndex = 0;
if (coopVsAI) {
// AI is left, prompt human (right) for name
playerName = "CPU";
player2Name.clear();
highScoreEntryIndex = 1; // enter P2 (human)
} else {
playerName.clear();
player2Name.clear();
highScoreEntryIndex = 0;
}
SDL_StartTextInput(window);
} else {
isNewHighScore = false;
ensureScoresLoaded();
scores.submit(combinedScore, coopGame->lines(), coopGame->level(), coopGame->elapsed(), "P1 & P2", "cooperate");
// When AI is present, label should indicate CPU left and human right
scores.submit(combinedScore, coopGame->lines(), coopGame->level(), coopGame->elapsed(), coopVsAI ? "CPU & P2" : "P1 & P2", "cooperate");
}
state = AppState::GameOver;
stateMgr->setState(state);