basic gameplay for cooperative

This commit is contained in:
2025-12-21 15:33:37 +01:00
parent 5b9eb5f0e3
commit afd7fdf18d
20 changed files with 1534 additions and 263 deletions

View File

@ -144,4 +144,7 @@ void draw(SDL_Renderer* renderer, SDL_Texture*) {
double getLogoAnimCounter() { return logoAnimCounter; }
int getHoveredButton() { return hoveredButton; }
void spawn(float x, float y) {
fireworks.emplace_back(x, y);
}
} // namespace AppFireworks

View File

@ -6,4 +6,5 @@ namespace AppFireworks {
void update(double frameMs);
double getLogoAnimCounter();
int getHoveredButton();
void spawn(float x, float y);
}

View File

@ -37,6 +37,7 @@
#include "core/state/StateManager.h"
#include "gameplay/core/Game.h"
#include "gameplay/coop/CoopGame.h"
#include "gameplay/effects/LineEffect.h"
#include "graphics/effects/SpaceWarp.h"
@ -228,6 +229,7 @@ struct TetrisApp::Impl {
std::atomic<size_t> loadingStep{0};
std::unique_ptr<Game> game;
std::unique_ptr<CoopGame> coopGame;
std::vector<std::string> singleSounds;
std::vector<std::string> doubleSounds;
std::vector<std::string> tripleSounds;
@ -242,7 +244,13 @@ struct TetrisApp::Impl {
bool isFullscreen = false;
bool leftHeld = false;
bool rightHeld = false;
bool p1LeftHeld = false;
bool p1RightHeld = false;
bool p2LeftHeld = false;
bool p2RightHeld = false;
double moveTimerMs = 0.0;
double p1MoveTimerMs = 0.0;
double p2MoveTimerMs = 0.0;
double DAS = 170.0;
double ARR = 40.0;
SDL_Rect logicalVP{0, 0, LOGICAL_W, LOGICAL_H};
@ -421,6 +429,8 @@ int TetrisApp::Impl::init()
game->setGravityGlobalMultiplier(Config::Gameplay::GRAVITY_SPEED_MULTIPLIER);
game->reset(startLevelSelection);
coopGame = std::make_unique<CoopGame>(startLevelSelection);
// Define voice line banks for gameplay callbacks
singleSounds = {"well_played", "smooth_clear", "great_move"};
doubleSounds = {"nice_combo", "you_fire", "keep_that_ryhtm"};
@ -479,7 +489,10 @@ int TetrisApp::Impl::init()
isFullscreen = Settings::instance().isFullscreen();
leftHeld = false;
rightHeld = false;
p1LeftHeld = p1RightHeld = p2LeftHeld = p2RightHeld = false;
moveTimerMs = 0;
p1MoveTimerMs = 0.0;
p2MoveTimerMs = 0.0;
DAS = 170.0;
ARR = 40.0;
logicalVP = SDL_Rect{0, 0, LOGICAL_W, LOGICAL_H};
@ -506,6 +519,7 @@ int TetrisApp::Impl::init()
ctx = StateContext{};
ctx.stateManager = stateMgr.get();
ctx.game = game.get();
ctx.coopGame = coopGame.get();
ctx.scores = nullptr;
ctx.starfield = &starfield;
ctx.starfield3D = &starfield3D;
@ -858,6 +872,9 @@ void TetrisApp::Impl::runLoop()
if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER || e.key.scancode == SDL_SCANCODE_SPACE) {
if (game->getMode() == GameMode::Challenge) {
game->startChallengeRun(1);
} else if (game->getMode() == GameMode::Cooperate) {
game->setMode(GameMode::Cooperate);
game->reset(startLevelSelection);
} else {
game->setMode(GameMode::Endless);
game->reset(startLevelSelection);
@ -893,6 +910,13 @@ void TetrisApp::Impl::runLoop()
if (game) game->setMode(GameMode::Endless);
startMenuPlayTransition();
break;
case ui::BottomMenuItem::Cooperate:
if (game) {
game->setMode(GameMode::Cooperate);
game->reset(startLevelSelection);
}
startMenuPlayTransition();
break;
case ui::BottomMenuItem::Challenge:
if (game) {
game->setMode(GameMode::Challenge);
@ -1153,29 +1177,88 @@ void TetrisApp::Impl::runLoop()
if (state == AppState::Playing)
{
if (!game->isPaused()) {
game->tickGravity(frameMs);
game->updateElapsedTime();
const bool coopActive = game && game->getMode() == GameMode::Cooperate && coopGame;
if (lineEffect.isActive()) {
if (lineEffect.update(frameMs / 1000.0f)) {
game->clearCompletedLines();
if (coopActive) {
// Coop DAS/ARR handling (per-side)
const bool* ks = SDL_GetKeyboardState(nullptr);
auto handleSide = [&](CoopGame::PlayerSide side,
bool leftHeldPrev,
bool rightHeldPrev,
double& timer,
SDL_Scancode leftKey,
SDL_Scancode rightKey,
SDL_Scancode downKey) {
bool left = ks[leftKey];
bool right = ks[rightKey];
bool down = ks[downKey];
coopGame->setSoftDropping(side, down);
int moveDir = 0;
if (left && !right) moveDir = -1;
else if (right && !left) moveDir = +1;
if (moveDir != 0) {
if ((moveDir == -1 && !leftHeldPrev) || (moveDir == +1 && !rightHeldPrev)) {
coopGame->move(side, moveDir);
timer = DAS;
} else {
timer -= frameMs;
if (timer <= 0) {
coopGame->move(side, moveDir);
timer += ARR;
}
}
} else {
timer = 0.0;
}
};
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);
p1LeftHeld = ks[SDL_SCANCODE_A];
p1RightHeld = ks[SDL_SCANCODE_D];
p2LeftHeld = ks[SDL_SCANCODE_LEFT];
p2RightHeld = ks[SDL_SCANCODE_RIGHT];
if (!game->isPaused()) {
coopGame->tickGravity(frameMs);
coopGame->updateVisualEffects(frameMs);
}
if (coopGame->isGameOver()) {
state = AppState::GameOver;
stateMgr->setState(state);
}
} else {
if (!game->isPaused()) {
game->tickGravity(frameMs);
game->updateElapsedTime();
if (lineEffect.isActive()) {
if (lineEffect.update(frameMs / 1000.0f)) {
game->clearCompletedLines();
}
}
}
}
if (game->isGameOver())
{
if (game->score() > 0) {
isNewHighScore = true;
playerName.clear();
SDL_StartTextInput(window);
} else {
isNewHighScore = false;
ensureScoresLoaded();
scores.submit(game->score(), game->lines(), game->level(), game->elapsed());
if (game->isGameOver())
{
if (game->score() > 0) {
isNewHighScore = true;
playerName.clear();
SDL_StartTextInput(window);
} else {
isNewHighScore = false;
ensureScoresLoaded();
scores.submit(game->score(), game->lines(), game->level(), game->elapsed());
}
state = AppState::GameOver;
stateMgr->setState(state);
}
state = AppState::GameOver;
stateMgr->setState(state);
}
}
else if (state == AppState::Loading)