i;nint
This commit is contained in:
+61
-49
@@ -1,77 +1,89 @@
|
|||||||
# Ako Hrat PokerRogue (Prototyp)
|
# Ako hrat PokerRogue (prototyp)
|
||||||
|
|
||||||
PokerRogue je singleplayer hra, kde sa boj riesi pokerom.
|
PokerRogue je singleplayer poker-combat hra. Kazda handa je jeden suboj.
|
||||||
Kazde kolo boja je jedna poker handa.
|
|
||||||
|
|
||||||
## Ciel
|
## Ciel
|
||||||
|
|
||||||
- Znizit HP nepriatela na 0 skor, ako nepriatel znizi tvoje HP na 0.
|
- Znizit HP nepriatela na 0 skor, ako nepriatel znizi tvoje HP na 0.
|
||||||
- Boj trva obmedzeny pocet hand (`Hand X/Y` hore na obrazovke).
|
- Boj ma limit hand (`Hand X/Y` v hornom paneli).
|
||||||
|
|
||||||
## Zakladny priebeh
|
## Priebeh handy (aktualna logika)
|
||||||
|
|
||||||
1. Zobrazi sa tvoja ruka (2 karty), board (5 kariet) a zamer nepriatela.
|
1. Dostanes 2 karty, nepriatel ma 2 karty, na stole je board 5 kariet.
|
||||||
2. Vyberies akciu (`1`, `2`, `3`).
|
2. Rozhodujes sa po streetoch:
|
||||||
3. Vyhodnoti sa handa:
|
- `Flop` (3 karty),
|
||||||
- kto ma silnejsiu kombinaciu, vyhrava handu,
|
- `Turn` (4 karty),
|
||||||
- podla Pot/Edge/Shield sa vypocita damage.
|
- `River` (5 kariet).
|
||||||
4. Na 5 sekund sa ukaze showdown:
|
3. Na kazdom kroku vyberas akciu (`1` az `7`), okrem `Fold`, ktory handu okamzite ukonci.
|
||||||
- nepriatelove karty,
|
4. Po River sa z troch vybranych akcii vypocita finalna bojova akcia.
|
||||||
- zlate obrysy vitaznej 5-kartovej kombinacie.
|
5. Vyhodnoti sa showdown (najlepsich 5 zo 7 kariet), aplikuje sa damage.
|
||||||
5. Zacne dalsia handa.
|
6. Vysledok ostane na obrazovke, pokracujes az po `Space`/`Enter`.
|
||||||
|
|
||||||
## Ovládanie
|
## Ovládanie
|
||||||
|
|
||||||
- `1` = `Strike` (agresivne)
|
- `1` = `Strike`
|
||||||
- `2` = `Guard` (defenzivne)
|
- `2` = `Guard`
|
||||||
- `3` = `Dodge` (znizenie rizika)
|
- `3` = `Dodge`
|
||||||
- `R` = restart boja
|
- `4` = `Check`
|
||||||
- `Esc` = ukoncit hru
|
- `5` = `Probe`
|
||||||
|
- `6` = `All-In`
|
||||||
|
- `7` = `Fold` (okamzite skonci handu)
|
||||||
|
- `Space` / `Enter` = pokracovat po showdowne
|
||||||
|
- `R` = novy run
|
||||||
|
- `Esc` = koniec hry
|
||||||
|
|
||||||
## Co robia akcie
|
## Co robia akcie
|
||||||
|
|
||||||
### Strike (`1`)
|
- `Strike`: `Pot +3`, `PlayerEdge +10`
|
||||||
|
- `Guard`: `Shield +6`, `PlayerEdge +5`
|
||||||
|
- `Dodge`: `Pot -2` (minimum 1), `Shield +2`
|
||||||
|
- `Check`: `Shield +1`, `PlayerEdge +2`
|
||||||
|
- `Probe`: `Pot +1`, `PlayerEdge +6`
|
||||||
|
- `All-In`: `Pot +8`, `PlayerEdge +18`, `EnemyEdge +8`
|
||||||
|
- `Fold`: okamzita prehra handy, ziskavas vacsi `Shield`, damage je znizeny oproti beznej prehre
|
||||||
|
|
||||||
- zvysi `Pot`
|
## Edge a Shield (dolezite)
|
||||||
- zvysi tvoj `Edge`
|
|
||||||
- vhodne, ked chces tlacit na vysoky damage
|
|
||||||
|
|
||||||
### Guard (`2`)
|
### Edge
|
||||||
|
|
||||||
- prida `Shield`
|
`Edge` je percent bonus k damage:
|
||||||
- mierne zvysi tvoj `Edge`
|
|
||||||
- vhodne, ked cakas silny utok nepriatela
|
|
||||||
|
|
||||||
### Dodge (`3`)
|
- ak vyhras handu ty: `EnemyDamage = Pot * (100 + PlayerEdge) / 100`
|
||||||
|
- ak vyhra nepriatel: `RawPlayerDamage = Pot * (100 + EnemyEdge) / 100`
|
||||||
|
|
||||||
- znizi `Pot` (mensie riziko aj odmena)
|
Cim vyssi `Edge`, tym vacsie potencialne zranenie.
|
||||||
- da mensi `Shield`
|
|
||||||
- vhodne na stabilizaciu pri nevyhodnej hande
|
### Shield
|
||||||
|
|
||||||
|
`Shield` znizuje damage, ktory dostanes pri prehratej hande:
|
||||||
|
|
||||||
|
- `PlayerDamage = max(0, RawPlayerDamage - Shield)`
|
||||||
|
|
||||||
|
`Shield` sa pocita len pri damage do hraca v danej hande.
|
||||||
|
|
||||||
## Ako sa urcuje vitaz handy
|
## Ako sa urcuje vitaz handy
|
||||||
|
|
||||||
Hra vyhodnocuje najlepsiu 5-kartovu kombinaciu zo 7 kariet:
|
Vyhodnocuje sa najlepsia 5-kartova kombinacia zo 7 kariet:
|
||||||
|
|
||||||
- 2 hole cards + 5 board cards
|
- tvoje 2 karty + 5 kariet na stole,
|
||||||
|
- nepriatelove 2 karty + tych istych 5 kariet na stole.
|
||||||
|
|
||||||
Porovnanie funguje ako v pokri:
|
Porovnanie:
|
||||||
|
|
||||||
1. najprv typ kombinacie (napr. Pair, Two Pair, Straight, Flush...)
|
1. Typ kombinacie (Pair, Two Pair, Straight, Flush, ...)
|
||||||
2. pri rovnakom type rozhoduju kickery
|
2. Pri rovnosti rozhoduju kickery (rank vector).
|
||||||
|
|
||||||
Panel vpravo ukazuje:
|
## Vizualne pomocky v hre
|
||||||
|
|
||||||
- nazov kombinacie hraca a nepriatela,
|
- Hore vpravo su animovane bary:
|
||||||
- dovod vyhry/prehry,
|
- `Player Edge`,
|
||||||
- vysledny vypocet damage.
|
- `Enemy Edge`,
|
||||||
|
- `Shield`.
|
||||||
|
- Po showdowne su vitazne karty zvyraznene zlatym obrysom.
|
||||||
|
- Nepriatelove karty sa odkryju pri showdowne.
|
||||||
|
|
||||||
## Tipy pre zaciatok
|
## Tipy
|
||||||
|
|
||||||
- Ak mas dobru kombinaciu, casto sa oplati `Strike`.
|
- Silna handa + vyssi `PlayerEdge` = velky tlak na HP nepriatela.
|
||||||
- Ak je nepriatel agresivny, `Guard` ti casto zachrani HP.
|
- Pri riziku prehry pomaha `Guard`, `Check` alebo `Dodge` (kvoli `Shield`).
|
||||||
- `Dodge` pouzi, ked nechces prehrat velky pot.
|
- `Fold` je takticky ustupok: prehras handu, ale casto prezijes lacnejsie.
|
||||||
- Sleduj zlate obrysy po hande, rychlo sa naucis, ktore karty rozhodli.
|
|
||||||
|
|
||||||
## Poznamka k verzii
|
|
||||||
|
|
||||||
Toto je prototyp. Balans, grafika a pravidla sa budu postupne menil.
|
|
||||||
|
|||||||
+460
-66
@@ -24,13 +24,14 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
private FightState _fightState = null!;
|
private FightState _fightState = null!;
|
||||||
private KeyboardState _previousKeyboardState;
|
private KeyboardState _previousKeyboardState;
|
||||||
|
|
||||||
private string _feedbackText = "Press 1/2/3 to act.";
|
private string _feedbackText = "Press 1..7 to act.";
|
||||||
private float _feedbackSecondsRemaining;
|
private float _feedbackSecondsRemaining;
|
||||||
private float _showdownSecondsRemaining;
|
private float _showdownSecondsRemaining;
|
||||||
private bool _awaitingContinue;
|
private bool _awaitingContinue;
|
||||||
private const float ShowdownDurationSeconds = 5.0f;
|
private const float ShowdownDurationSeconds = 5.0f;
|
||||||
private const float TurnRevealAtSeconds = 1.2f;
|
private const float TurnRevealAtSeconds = 1.2f;
|
||||||
private const float RiverRevealAtSeconds = 2.4f;
|
private const float RiverRevealAtSeconds = 2.4f;
|
||||||
|
private const float EnemyTurnDelaySeconds = 0.55f;
|
||||||
|
|
||||||
private Card[] _showdownPlayerCards = Array.Empty<Card>();
|
private Card[] _showdownPlayerCards = Array.Empty<Card>();
|
||||||
private Card[] _showdownEnemyCards = Array.Empty<Card>();
|
private Card[] _showdownEnemyCards = Array.Empty<Card>();
|
||||||
@@ -42,11 +43,37 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
private int _runMaxHp = 100;
|
private int _runMaxHp = 100;
|
||||||
private int _runPlayerHp = 100;
|
private int _runPlayerHp = 100;
|
||||||
private int _runPotBonus;
|
private int _runPotBonus;
|
||||||
|
private int _runPlayerDexterity = 10;
|
||||||
private string _encounterText = string.Empty;
|
private string _encounterText = string.Empty;
|
||||||
private DecisionStage _decisionStage = DecisionStage.Flop;
|
private DecisionStage _decisionStage = DecisionStage.Preflop;
|
||||||
|
private TurnActor _activeTurn = TurnActor.Player;
|
||||||
|
private bool _playerActsSecond;
|
||||||
|
private bool _playerActedThisStreet;
|
||||||
|
private bool _enemyActedThisStreet;
|
||||||
|
private float _enemyTurnSecondsRemaining;
|
||||||
|
private TurnActor _lastSecondActor = TurnActor.None;
|
||||||
|
private readonly List<string> _currentHandActionLog = new();
|
||||||
|
private int _playerDexterity = 10;
|
||||||
|
private int _enemyDexterity = 10;
|
||||||
|
private int _lastPlayerInitiative;
|
||||||
|
private int _lastEnemyInitiative;
|
||||||
|
private readonly Random _initiativeRng = new();
|
||||||
|
private PlayerAction? _preflopAction;
|
||||||
private PlayerAction? _flopAction;
|
private PlayerAction? _flopAction;
|
||||||
private PlayerAction? _turnAction;
|
private PlayerAction? _turnAction;
|
||||||
private PlayerAction? _riverAction;
|
private float _displayPlayerEdge;
|
||||||
|
private float _displayEnemyEdge;
|
||||||
|
private float _displayShield;
|
||||||
|
private int _lastPlayerEdge = int.MinValue;
|
||||||
|
private int _lastEnemyEdge = int.MinValue;
|
||||||
|
private int _lastShield = int.MinValue;
|
||||||
|
private float _playerEdgePulse;
|
||||||
|
private float _enemyEdgePulse;
|
||||||
|
private float _shieldPulse;
|
||||||
|
private int _pendingPlayerEdgeDelta;
|
||||||
|
private int _pendingEnemyEdgeDelta;
|
||||||
|
private int _pendingShieldDelta;
|
||||||
|
private readonly List<FloatingStatText> _floatingStatTexts = new();
|
||||||
|
|
||||||
public Game1()
|
public Game1()
|
||||||
{
|
{
|
||||||
@@ -79,6 +106,7 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
|
|
||||||
protected override void Update(GameTime gameTime)
|
protected override void Update(GameTime gameTime)
|
||||||
{
|
{
|
||||||
|
var dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
|
||||||
var keyboardState = Keyboard.GetState();
|
var keyboardState = Keyboard.GetState();
|
||||||
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || keyboardState.IsKeyDown(Keys.Escape))
|
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || keyboardState.IsKeyDown(Keys.Escape))
|
||||||
{
|
{
|
||||||
@@ -127,6 +155,11 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
_runPlayerHp = Math.Min(_runMaxHp, _runPlayerHp + 20);
|
_runPlayerHp = Math.Min(_runMaxHp, _runPlayerHp + 20);
|
||||||
AdvanceToNextNode();
|
AdvanceToNextNode();
|
||||||
}
|
}
|
||||||
|
else if (Pressed(keyboardState, Keys.D4) || Pressed(keyboardState, Keys.NumPad4))
|
||||||
|
{
|
||||||
|
_runPlayerDexterity += 1;
|
||||||
|
AdvanceToNextNode();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case RunScreen.Fight:
|
case RunScreen.Fight:
|
||||||
UpdateFightScreen(keyboardState, gameTime);
|
UpdateFightScreen(keyboardState, gameTime);
|
||||||
@@ -140,14 +173,17 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateHudAnimations(dt);
|
||||||
|
UpdateFloatingTexts(dt);
|
||||||
|
|
||||||
if (_feedbackSecondsRemaining > 0f)
|
if (_feedbackSecondsRemaining > 0f)
|
||||||
{
|
{
|
||||||
_feedbackSecondsRemaining -= (float)gameTime.ElapsedGameTime.TotalSeconds;
|
_feedbackSecondsRemaining -= dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_showdownSecondsRemaining > 0f)
|
if (_showdownSecondsRemaining > 0f)
|
||||||
{
|
{
|
||||||
_showdownSecondsRemaining -= (float)gameTime.ElapsedGameTime.TotalSeconds;
|
_showdownSecondsRemaining -= dt;
|
||||||
if (_showdownSecondsRemaining <= 0f)
|
if (_showdownSecondsRemaining <= 0f)
|
||||||
{
|
{
|
||||||
_showdownSecondsRemaining = 0f;
|
_showdownSecondsRemaining = 0f;
|
||||||
@@ -209,12 +245,16 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
_runMaxHp = 100;
|
_runMaxHp = 100;
|
||||||
_runPlayerHp = 100;
|
_runPlayerHp = 100;
|
||||||
_runPotBonus = 0;
|
_runPotBonus = 0;
|
||||||
|
_runPlayerDexterity = 10;
|
||||||
_runScreen = RunScreen.Map;
|
_runScreen = RunScreen.Map;
|
||||||
_encounterText = string.Empty;
|
_encounterText = string.Empty;
|
||||||
_decisionStage = DecisionStage.Flop;
|
_decisionStage = DecisionStage.Preflop;
|
||||||
|
_activeTurn = TurnActor.Player;
|
||||||
|
_preflopAction = null;
|
||||||
_flopAction = null;
|
_flopAction = null;
|
||||||
_turnAction = null;
|
_turnAction = null;
|
||||||
_riverAction = null;
|
_currentHandActionLog.Clear();
|
||||||
|
ResetPendingHudDeltas();
|
||||||
ResetShowdownState();
|
ResetShowdownState();
|
||||||
ShowFeedback("New run started.");
|
ShowFeedback("New run started.");
|
||||||
}
|
}
|
||||||
@@ -243,7 +283,8 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
_runPlayerHp = Math.Min(_runMaxHp, _runPlayerHp + 18);
|
_runPlayerHp = Math.Min(_runMaxHp, _runPlayerHp + 18);
|
||||||
_encounterText = "Shop: Purchased provisions (+18 HP).";
|
_runPlayerDexterity += 1;
|
||||||
|
_encounterText = "Shop: Purchased light gear (+18 HP, +1 DEX).";
|
||||||
_runScreen = RunScreen.Encounter;
|
_runScreen = RunScreen.Encounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,12 +318,17 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
MaxHands: maxHands,
|
MaxHands: maxHands,
|
||||||
Enemy: enemy);
|
Enemy: enemy);
|
||||||
|
|
||||||
|
_enemyDexterity = node.Type switch
|
||||||
|
{
|
||||||
|
NodeType.Boss => 13,
|
||||||
|
NodeType.Elite => 12,
|
||||||
|
_ => 10
|
||||||
|
};
|
||||||
|
_playerDexterity = _runPlayerDexterity;
|
||||||
|
|
||||||
_fightState = FightEngine.StartFight(config, seed: Environment.TickCount);
|
_fightState = FightEngine.StartFight(config, seed: Environment.TickCount);
|
||||||
_decisionStage = DecisionStage.Flop;
|
|
||||||
_flopAction = null;
|
|
||||||
_turnAction = null;
|
|
||||||
_riverAction = null;
|
|
||||||
ResetShowdownState();
|
ResetShowdownState();
|
||||||
|
SetupHandOrder();
|
||||||
_runScreen = RunScreen.Fight;
|
_runScreen = RunScreen.Fight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,33 +337,34 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
var showdownActive = IsShowdownVisible;
|
var showdownActive = IsShowdownVisible;
|
||||||
if (_fightState.Phase != HandPhase.End && !showdownActive)
|
if (_fightState.Phase != HandPhase.End && !showdownActive)
|
||||||
{
|
{
|
||||||
var pickedAction = ReadActionKey(keyboardState);
|
if (_activeTurn == TurnActor.Enemy)
|
||||||
if (pickedAction is not null)
|
|
||||||
{
|
{
|
||||||
if (_decisionStage == DecisionStage.Flop)
|
_enemyTurnSecondsRemaining -= (float)gameTime.ElapsedGameTime.TotalSeconds;
|
||||||
|
if (_enemyTurnSecondsRemaining <= 0f)
|
||||||
{
|
{
|
||||||
_flopAction = pickedAction.Value;
|
ResolveEnemyStreetAction();
|
||||||
_decisionStage = DecisionStage.Turn;
|
|
||||||
ShowFeedback($"Flop action: {_flopAction.Value}. Pick turn action.");
|
|
||||||
}
|
}
|
||||||
else if (_decisionStage == DecisionStage.Turn)
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pickedAction = ReadActionKey(keyboardState);
|
||||||
|
if (pickedAction is not null)
|
||||||
{
|
{
|
||||||
_turnAction = pickedAction.Value;
|
if (pickedAction.Value == PlayerAction.Fold)
|
||||||
_decisionStage = DecisionStage.River;
|
{
|
||||||
ShowFeedback($"Turn action: {_turnAction.Value}. Pick river action.");
|
_currentHandActionLog.Add($"{StageLabel(_decisionStage)}: Player Fold");
|
||||||
}
|
SpawnActionFloaters(PlayerAction.Fold);
|
||||||
else
|
ApplyActionAndFeedback(PlayerAction.Fold);
|
||||||
{
|
ResetStreetState();
|
||||||
_riverAction = pickedAction.Value;
|
return;
|
||||||
var combined = CombineActions(
|
}
|
||||||
_flopAction ?? PlayerAction.Check,
|
|
||||||
_turnAction ?? PlayerAction.Check,
|
StorePlayerAction(pickedAction.Value);
|
||||||
_riverAction.Value);
|
ApplyPendingDeltaForAction(pickedAction.Value);
|
||||||
ApplyActionAndFeedback(combined, _flopAction, _turnAction, _riverAction);
|
SpawnActionFloaters(pickedAction.Value);
|
||||||
_flopAction = null;
|
_currentHandActionLog.Add($"{StageLabel(_decisionStage)}: Player {pickedAction.Value}");
|
||||||
_turnAction = null;
|
_playerActedThisStreet = true;
|
||||||
_riverAction = null;
|
AdvanceStreetIfReady();
|
||||||
_decisionStage = DecisionStage.Flop;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,10 +375,11 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
_showdownPlayerCards = Array.Empty<Card>();
|
_showdownPlayerCards = Array.Empty<Card>();
|
||||||
_showdownEnemyCards = Array.Empty<Card>();
|
_showdownEnemyCards = Array.Empty<Card>();
|
||||||
_showdownBoardCards = Array.Empty<Card>();
|
_showdownBoardCards = Array.Empty<Card>();
|
||||||
_decisionStage = DecisionStage.Flop;
|
_currentHandActionLog.Clear();
|
||||||
_flopAction = null;
|
if (_fightState.Phase != HandPhase.End)
|
||||||
_turnAction = null;
|
{
|
||||||
_riverAction = null;
|
SetupHandOrder();
|
||||||
|
}
|
||||||
ShowFeedback("Next hand.");
|
ShowFeedback("Next hand.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +424,7 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
|
|
||||||
private void DrawMapScreen()
|
private void DrawMapScreen()
|
||||||
{
|
{
|
||||||
DrawLine(new Vector2(40, 24), $"Map - Node {_currentNodeIndex + 1}/{_runNodes.Count} | HP {_runPlayerHp}/{_runMaxHp} | Pot bonus +{_runPotBonus}");
|
DrawLine(new Vector2(40, 24), $"Map - Node {_currentNodeIndex + 1}/{_runNodes.Count} | HP {_runPlayerHp}/{_runMaxHp} | Pot bonus +{_runPotBonus} | DEX {_runPlayerDexterity}");
|
||||||
for (var i = 0; i < _runNodes.Count; i++)
|
for (var i = 0; i < _runNodes.Count; i++)
|
||||||
{
|
{
|
||||||
var node = _runNodes[i];
|
var node = _runNodes[i];
|
||||||
@@ -391,17 +439,18 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
{
|
{
|
||||||
DrawLine(new Vector2(50, 60), "Node Result");
|
DrawLine(new Vector2(50, 60), "Node Result");
|
||||||
DrawLine(new Vector2(50, 110), _encounterText);
|
DrawLine(new Vector2(50, 110), _encounterText);
|
||||||
DrawLine(new Vector2(50, 160), $"Current HP: {_runPlayerHp}/{_runMaxHp}");
|
DrawLine(new Vector2(50, 160), $"Current HP: {_runPlayerHp}/{_runMaxHp} | DEX: {_runPlayerDexterity}");
|
||||||
DrawLine(new Vector2(50, 220), "Press Space to continue.");
|
DrawLine(new Vector2(50, 220), "Press Space to continue.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRewardScreen()
|
private void DrawRewardScreen()
|
||||||
{
|
{
|
||||||
DrawLine(new Vector2(50, 44), "Reward Choice");
|
DrawLine(new Vector2(50, 44), "Reward Choice");
|
||||||
DrawLine(new Vector2(50, 88), $"HP: {_runPlayerHp}/{_runMaxHp} | Pot bonus +{_runPotBonus}");
|
DrawLine(new Vector2(50, 88), $"HP: {_runPlayerHp}/{_runMaxHp} | Pot bonus +{_runPotBonus} | DEX {_runPlayerDexterity}");
|
||||||
DrawLine(new Vector2(50, 148), "1 - Max HP +10 and heal +12");
|
DrawLine(new Vector2(50, 148), "1 - Max HP +10 and heal +12");
|
||||||
DrawLine(new Vector2(50, 182), "2 - Pot bonus +2 for next fights");
|
DrawLine(new Vector2(50, 182), "2 - Pot bonus +2 for next fights");
|
||||||
DrawLine(new Vector2(50, 216), "3 - Immediate heal +20");
|
DrawLine(new Vector2(50, 216), "3 - Immediate heal +20");
|
||||||
|
DrawLine(new Vector2(50, 250), "4 - Dexterity +1 (better initiative)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawEndScreen(string title, string subtitle)
|
private void DrawEndScreen(string title, string subtitle)
|
||||||
@@ -418,10 +467,7 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
_showdownPlayerCards = Array.Empty<Card>();
|
_showdownPlayerCards = Array.Empty<Card>();
|
||||||
_showdownEnemyCards = Array.Empty<Card>();
|
_showdownEnemyCards = Array.Empty<Card>();
|
||||||
_showdownBoardCards = Array.Empty<Card>();
|
_showdownBoardCards = Array.Empty<Card>();
|
||||||
_decisionStage = DecisionStage.Flop;
|
ResetStreetState();
|
||||||
_flopAction = null;
|
|
||||||
_turnAction = null;
|
|
||||||
_riverAction = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Layout BuildLayout()
|
private Layout BuildLayout()
|
||||||
@@ -483,21 +529,30 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
private void DrawTopHud(Layout layout)
|
private void DrawTopHud(Layout layout)
|
||||||
{
|
{
|
||||||
var x = layout.Margin;
|
var x = layout.Margin;
|
||||||
|
var viewportWidth = GraphicsDevice.Viewport.Width;
|
||||||
|
var statsTotalWidth = 240 + 16 + 240 + 16 + 170;
|
||||||
|
var statsX = Math.Max(x + 520, viewportWidth - layout.Margin - statsTotalWidth);
|
||||||
|
const int statsY = 124;
|
||||||
DrawLine(new Vector2(x, 16), $"Hand {_fightState.HandNumber}/{_fightState.Config.MaxHands} | Phase: {_fightState.Phase}");
|
DrawLine(new Vector2(x, 16), $"Hand {_fightState.HandNumber}/{_fightState.Config.MaxHands} | Phase: {_fightState.Phase}");
|
||||||
DrawLine(new Vector2(x, 44), $"Player HP: {_fightState.PlayerHp} | Enemy HP: {_fightState.EnemyHp} | Pot: {_fightState.Pot}");
|
DrawLine(new Vector2(x, 44), $"Player HP: {_fightState.PlayerHp} | Enemy HP: {_fightState.EnemyHp} | Pot: {_fightState.Pot}");
|
||||||
DrawLine(new Vector2(x, 72), $"Edge: Player {_fightState.PlayerEdge} | Enemy {_fightState.EnemyEdge} | Enemy Intent: {_fightState.EnemyIntent}");
|
DrawLine(new Vector2(x, 72), $"Edge: Player {_fightState.PlayerEdge} | Enemy {_fightState.EnemyEdge} | Enemy Intent: {_fightState.EnemyIntent}");
|
||||||
DrawLine(new Vector2(x, 100), $"Decision stage: {BuildDecisionStageText()}");
|
DrawLine(new Vector2(x, 100), $"Decision stage: {BuildDecisionStageText()} | Turn: {_activeTurn}");
|
||||||
DrawLine(new Vector2(x, 124), "Action Tooltips", Color.LightGray);
|
DrawLine(new Vector2(x, 124), $"Initiative P:{_lastPlayerInitiative} E:{_lastEnemyInitiative} | Player position: {(_playerActsSecond ? "Second" : "First")}", Color.LightGray);
|
||||||
DrawLine(new Vector2(x, 148), "1 Strike: Pot +3, Edge +10 | 2 Guard: Shield +6, Edge +5", Color.LightGray);
|
DrawStatBar("Player Edge", _displayPlayerEdge, GetTargetPlayerEdge(), 0, 100, new Rectangle(statsX, statsY, 240, 12), new Color(210, 170, 60), _playerEdgePulse);
|
||||||
DrawLine(new Vector2(x, 172), "3 Dodge: Pot -2 (min 1), Shield +2 | 4 Check: Shield +1, Edge +2", Color.LightGray);
|
DrawStatBar("Enemy Edge", _displayEnemyEdge, GetTargetEnemyEdge(), 0, 100, new Rectangle(statsX + 256, statsY, 240, 12), new Color(184, 90, 90), _enemyEdgePulse);
|
||||||
DrawLine(new Vector2(x, 196), "5 Probe: Pot +1, Edge +6 | 6 All-In: Pot +8, Edge +18, EnemyEdge +8", Color.LightGray);
|
DrawStatBar("Shield", _displayShield, GetTargetShield(), 0, 12, new Rectangle(statsX + 512, statsY, 170, 12), new Color(88, 176, 212), _shieldPulse);
|
||||||
DrawLine(new Vector2(x, 220), "7 Fold: Lose hand early, reduced damage, keep HP for later", Color.LightGray);
|
DrawFloatingTexts();
|
||||||
DrawLine(new Vector2(x, 244), "Per hand: pick Flop, Turn, River action", Color.LightGray);
|
DrawLine(new Vector2(x, 152), "Action Tooltips", Color.LightGray);
|
||||||
DrawLine(new Vector2(x, 268), "Global: Space Continue, R Restart, Esc Exit", Color.LightGray);
|
DrawLine(new Vector2(x, 176), "1 Strike: Pot +3, Edge +10 | 2 Guard: Shield +6, Edge +5", Color.LightGray);
|
||||||
|
DrawLine(new Vector2(x, 200), "3 Dodge: Pot -2 (min 1), Shield +2 | 4 Check: Shield +1, Edge +2", Color.LightGray);
|
||||||
|
DrawLine(new Vector2(x, 224), "5 Probe: Pot +1, Edge +6 | 6 All-In: Pot +8, Edge +18, EnemyEdge +8", Color.LightGray);
|
||||||
|
DrawLine(new Vector2(x, 248), "7 Fold: instant hand end, enemy wins this hand, reduced incoming damage", Color.LightGray);
|
||||||
|
DrawLine(new Vector2(x, 272), "Flow: each street is Player <-> Enemy (position set by initiative)", Color.LightGray);
|
||||||
|
DrawLine(new Vector2(x, 296), "Global: Space Continue, R Restart, Esc Exit", Color.LightGray);
|
||||||
|
|
||||||
if (_feedbackSecondsRemaining > 0f)
|
if (_feedbackSecondsRemaining > 0f)
|
||||||
{
|
{
|
||||||
DrawLine(new Vector2(x, 292), _feedbackText, Color.Gold);
|
DrawLine(new Vector2(x, 320), _feedbackText, Color.Gold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,6 +597,24 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
DrawLine(new Vector2(x, y), "Why this result");
|
DrawLine(new Vector2(x, y), "Why this result");
|
||||||
y += 30;
|
y += 30;
|
||||||
|
|
||||||
|
DrawLine(new Vector2(x, y), "Action timeline:");
|
||||||
|
y += 22;
|
||||||
|
if (_currentHandActionLog.Count == 0)
|
||||||
|
{
|
||||||
|
DrawLine(new Vector2(x, y), "-");
|
||||||
|
y += 20;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var start = Math.Max(0, _currentHandActionLog.Count - 6);
|
||||||
|
for (var i = start; i < _currentHandActionLog.Count; i++)
|
||||||
|
{
|
||||||
|
DrawLine(new Vector2(x, y), _currentHandActionLog[i], Color.LightGray);
|
||||||
|
y += 18;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y += 12;
|
||||||
|
|
||||||
DrawLine(new Vector2(x, y), "Poker evaluator (best 5 of 7):");
|
DrawLine(new Vector2(x, y), "Poker evaluator (best 5 of 7):");
|
||||||
y += 24;
|
y += 24;
|
||||||
DrawLine(new Vector2(x, y), "1) Detect category (pair/straight/flush...)");
|
DrawLine(new Vector2(x, y), "1) Detect category (pair/straight/flush...)");
|
||||||
@@ -648,9 +721,9 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
|
|
||||||
private void ApplyActionAndFeedback(
|
private void ApplyActionAndFeedback(
|
||||||
PlayerAction action,
|
PlayerAction action,
|
||||||
PlayerAction? flopAction = null,
|
PlayerAction? firstAction = null,
|
||||||
PlayerAction? turnAction = null,
|
PlayerAction? secondAction = null,
|
||||||
PlayerAction? riverAction = null)
|
PlayerAction? thirdAction = null)
|
||||||
{
|
{
|
||||||
_showdownPlayerCards = _fightState.PlayerHoleCards.ToArray();
|
_showdownPlayerCards = _fightState.PlayerHoleCards.ToArray();
|
||||||
_showdownEnemyCards = _fightState.EnemyHoleCards.ToArray();
|
_showdownEnemyCards = _fightState.EnemyHoleCards.ToArray();
|
||||||
@@ -659,9 +732,9 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
_fightState = FightEngine.ApplyPlayerAction(_fightState, action);
|
_fightState = FightEngine.ApplyPlayerAction(_fightState, action);
|
||||||
_showdownSecondsRemaining = ShowdownDurationSeconds;
|
_showdownSecondsRemaining = ShowdownDurationSeconds;
|
||||||
_awaitingContinue = false;
|
_awaitingContinue = false;
|
||||||
if (flopAction is not null && turnAction is not null && riverAction is not null)
|
if (firstAction is not null && secondAction is not null && thirdAction is not null)
|
||||||
{
|
{
|
||||||
ShowFeedback($"Flop {flopAction.Value} + Turn {turnAction.Value} + River {riverAction.Value} => {action} | {_fightState.LastOutcome}");
|
ShowFeedback($"Preflop {firstAction.Value} + Flop {secondAction.Value} + Turn {thirdAction.Value} => {action} | {_fightState.LastOutcome}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -669,6 +742,135 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetupHandOrder()
|
||||||
|
{
|
||||||
|
ResetStreetState();
|
||||||
|
_currentHandActionLog.Clear();
|
||||||
|
|
||||||
|
_lastPlayerInitiative = _playerDexterity + _initiativeRng.Next(0, 6);
|
||||||
|
_lastEnemyInitiative = _enemyDexterity + _initiativeRng.Next(0, 6);
|
||||||
|
var secondActor = _lastPlayerInitiative >= _lastEnemyInitiative ? TurnActor.Player : TurnActor.Enemy;
|
||||||
|
if (_lastSecondActor != TurnActor.None && secondActor == _lastSecondActor)
|
||||||
|
{
|
||||||
|
secondActor = secondActor == TurnActor.Player ? TurnActor.Enemy : TurnActor.Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastSecondActor = secondActor;
|
||||||
|
_playerActsSecond = secondActor == TurnActor.Player;
|
||||||
|
_activeTurn = _playerActsSecond ? TurnActor.Enemy : TurnActor.Player;
|
||||||
|
_enemyTurnSecondsRemaining = _activeTurn == TurnActor.Enemy ? EnemyTurnDelaySeconds : 0f;
|
||||||
|
|
||||||
|
_currentHandActionLog.Add($"Initiative: Player {_lastPlayerInitiative} vs Enemy {_lastEnemyInitiative}");
|
||||||
|
_currentHandActionLog.Add($"Position: Player {(_playerActsSecond ? "Second" : "First")} this hand");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetStreetState()
|
||||||
|
{
|
||||||
|
_decisionStage = DecisionStage.Preflop;
|
||||||
|
_preflopAction = null;
|
||||||
|
_flopAction = null;
|
||||||
|
_turnAction = null;
|
||||||
|
_playerActedThisStreet = false;
|
||||||
|
_enemyActedThisStreet = false;
|
||||||
|
_enemyTurnSecondsRemaining = 0f;
|
||||||
|
_activeTurn = TurnActor.Player;
|
||||||
|
ResetPendingHudDeltas();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StorePlayerAction(PlayerAction action)
|
||||||
|
{
|
||||||
|
if (_decisionStage == DecisionStage.Preflop)
|
||||||
|
{
|
||||||
|
_preflopAction = action;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_decisionStage == DecisionStage.Flop)
|
||||||
|
{
|
||||||
|
_flopAction = action;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_turnAction = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveEnemyStreetAction()
|
||||||
|
{
|
||||||
|
var enemyAction = GetEnemyActionForStage(_decisionStage, _fightState.EnemyIntent);
|
||||||
|
_currentHandActionLog.Add($"{StageLabel(_decisionStage)}: Enemy {enemyAction}");
|
||||||
|
_enemyActedThisStreet = true;
|
||||||
|
AdvanceStreetIfReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AdvanceStreetIfReady()
|
||||||
|
{
|
||||||
|
if (!_playerActedThisStreet || !_enemyActedThisStreet)
|
||||||
|
{
|
||||||
|
_activeTurn = _playerActedThisStreet ? TurnActor.Enemy : TurnActor.Player;
|
||||||
|
if (_activeTurn == TurnActor.Enemy)
|
||||||
|
{
|
||||||
|
_enemyTurnSecondsRemaining = EnemyTurnDelaySeconds;
|
||||||
|
ShowFeedback("Enemy turn...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowFeedback("Your turn.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_decisionStage == DecisionStage.Preflop)
|
||||||
|
{
|
||||||
|
_decisionStage = DecisionStage.Flop;
|
||||||
|
_playerActedThisStreet = false;
|
||||||
|
_enemyActedThisStreet = false;
|
||||||
|
_activeTurn = _playerActsSecond ? TurnActor.Enemy : TurnActor.Player;
|
||||||
|
_enemyTurnSecondsRemaining = _activeTurn == TurnActor.Enemy ? EnemyTurnDelaySeconds : 0f;
|
||||||
|
ShowFeedback("Flop revealed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_decisionStage == DecisionStage.Flop)
|
||||||
|
{
|
||||||
|
_decisionStage = DecisionStage.Turn;
|
||||||
|
_playerActedThisStreet = false;
|
||||||
|
_enemyActedThisStreet = false;
|
||||||
|
_activeTurn = _playerActsSecond ? TurnActor.Enemy : TurnActor.Player;
|
||||||
|
_enemyTurnSecondsRemaining = _activeTurn == TurnActor.Enemy ? EnemyTurnDelaySeconds : 0f;
|
||||||
|
ShowFeedback("Turn revealed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var combined = CombineActions(
|
||||||
|
_preflopAction ?? PlayerAction.Check,
|
||||||
|
_flopAction ?? PlayerAction.Check,
|
||||||
|
_turnAction ?? PlayerAction.Check);
|
||||||
|
ApplyActionAndFeedback(combined, _preflopAction, _flopAction, _turnAction);
|
||||||
|
ResetStreetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string StageLabel(DecisionStage stage)
|
||||||
|
{
|
||||||
|
return stage switch
|
||||||
|
{
|
||||||
|
DecisionStage.Preflop => "Preflop",
|
||||||
|
DecisionStage.Flop => "Flop",
|
||||||
|
DecisionStage.Turn => "Turn",
|
||||||
|
_ => "Street"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlayerAction GetEnemyActionForStage(DecisionStage stage, EnemyIntent intent)
|
||||||
|
{
|
||||||
|
return intent switch
|
||||||
|
{
|
||||||
|
EnemyIntent.Strike => stage == DecisionStage.Turn ? PlayerAction.AllIn : PlayerAction.Strike,
|
||||||
|
EnemyIntent.Guard => stage == DecisionStage.Preflop ? PlayerAction.Check : PlayerAction.Guard,
|
||||||
|
EnemyIntent.Dodge => stage == DecisionStage.Turn ? PlayerAction.Check : PlayerAction.Dodge,
|
||||||
|
_ => PlayerAction.Check
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowFeedback(string text)
|
private void ShowFeedback(string text)
|
||||||
{
|
{
|
||||||
_feedbackText = text;
|
_feedbackText = text;
|
||||||
@@ -717,6 +919,189 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
_spriteBatch.Draw(_pixel, rect, color);
|
_spriteBatch.Draw(_pixel, rect, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawStatBar(string label, float displayValue, int actualValue, int min, int max, Rectangle barRect, Color fillColor, float pulse)
|
||||||
|
{
|
||||||
|
var pulseAmount = Math.Clamp(pulse / 0.4f, 0f, 1f);
|
||||||
|
var glow = Color.Lerp(Color.Black, Color.White, pulseAmount * 0.45f);
|
||||||
|
var expanded = new Rectangle(barRect.X - (int)(2 * pulseAmount), barRect.Y - (int)(2 * pulseAmount), barRect.Width + (int)(4 * pulseAmount), barRect.Height + (int)(4 * pulseAmount));
|
||||||
|
|
||||||
|
DrawFilled(expanded, new Color(20, 20, 20, 180));
|
||||||
|
DrawBorder(expanded, glow);
|
||||||
|
|
||||||
|
var clamped = Math.Clamp(displayValue, min, max);
|
||||||
|
var normalized = max > min ? (clamped - min) / (float)(max - min) : 0f;
|
||||||
|
var fillWidth = Math.Max(0, (int)((expanded.Width - 2) * normalized));
|
||||||
|
if (fillWidth > 0)
|
||||||
|
{
|
||||||
|
var animatedFill = Color.Lerp(fillColor, Color.White, pulseAmount * 0.25f);
|
||||||
|
DrawFilled(new Rectangle(expanded.X + 1, expanded.Y + 1, fillWidth, expanded.Height - 2), animatedFill);
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelSize = _debugFont.MeasureString(label);
|
||||||
|
_spriteBatch.DrawString(_debugFont, label, new Vector2(expanded.X, expanded.Y - labelSize.Y - 2), Color.LightGray);
|
||||||
|
_spriteBatch.DrawString(_debugFont, actualValue.ToString(), new Vector2(expanded.Right + 6, expanded.Y - 4), Color.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHudAnimations(float dt)
|
||||||
|
{
|
||||||
|
var targetPlayerEdge = GetTargetPlayerEdge();
|
||||||
|
var targetEnemyEdge = GetTargetEnemyEdge();
|
||||||
|
var targetShield = GetTargetShield();
|
||||||
|
|
||||||
|
if (_lastPlayerEdge == int.MinValue)
|
||||||
|
{
|
||||||
|
_displayPlayerEdge = targetPlayerEdge;
|
||||||
|
_displayEnemyEdge = targetEnemyEdge;
|
||||||
|
_displayShield = targetShield;
|
||||||
|
_lastPlayerEdge = targetPlayerEdge;
|
||||||
|
_lastEnemyEdge = targetEnemyEdge;
|
||||||
|
_lastShield = targetShield;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastPlayerEdge != targetPlayerEdge)
|
||||||
|
{
|
||||||
|
_playerEdgePulse = 0.4f;
|
||||||
|
_lastPlayerEdge = targetPlayerEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastEnemyEdge != targetEnemyEdge)
|
||||||
|
{
|
||||||
|
_enemyEdgePulse = 0.4f;
|
||||||
|
_lastEnemyEdge = targetEnemyEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastShield != targetShield)
|
||||||
|
{
|
||||||
|
_shieldPulse = 0.4f;
|
||||||
|
_lastShield = targetShield;
|
||||||
|
}
|
||||||
|
|
||||||
|
_displayPlayerEdge = SmoothTowards(_displayPlayerEdge, targetPlayerEdge, dt, 12f);
|
||||||
|
_displayEnemyEdge = SmoothTowards(_displayEnemyEdge, targetEnemyEdge, dt, 12f);
|
||||||
|
_displayShield = SmoothTowards(_displayShield, targetShield, dt, 18f);
|
||||||
|
|
||||||
|
_playerEdgePulse = Math.Max(0f, _playerEdgePulse - dt);
|
||||||
|
_enemyEdgePulse = Math.Max(0f, _enemyEdgePulse - dt);
|
||||||
|
_shieldPulse = Math.Max(0f, _shieldPulse - dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetTargetPlayerEdge()
|
||||||
|
{
|
||||||
|
return _fightState.PlayerEdge + _pendingPlayerEdgeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetTargetEnemyEdge()
|
||||||
|
{
|
||||||
|
return _fightState.EnemyEdge + _pendingEnemyEdgeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetTargetShield()
|
||||||
|
{
|
||||||
|
return _fightState.PlayerShield + _pendingShieldDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetPendingHudDeltas()
|
||||||
|
{
|
||||||
|
_pendingPlayerEdgeDelta = 0;
|
||||||
|
_pendingEnemyEdgeDelta = 0;
|
||||||
|
_pendingShieldDelta = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPendingDeltaForAction(PlayerAction action)
|
||||||
|
{
|
||||||
|
var (edge, enemyEdge, shield) = GetActionDelta(action);
|
||||||
|
_pendingPlayerEdgeDelta += edge;
|
||||||
|
_pendingEnemyEdgeDelta += enemyEdge;
|
||||||
|
_pendingShieldDelta += shield;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int Edge, int EnemyEdge, int Shield) GetActionDelta(PlayerAction action)
|
||||||
|
{
|
||||||
|
return action switch
|
||||||
|
{
|
||||||
|
PlayerAction.Strike => (10, 0, 0),
|
||||||
|
PlayerAction.Guard => (5, 0, 6),
|
||||||
|
PlayerAction.Dodge => (0, 0, 2),
|
||||||
|
PlayerAction.Check => (2, 0, 1),
|
||||||
|
PlayerAction.Probe => (6, 0, 0),
|
||||||
|
PlayerAction.AllIn => (18, 8, 0),
|
||||||
|
PlayerAction.Fold => (0, 0, 10),
|
||||||
|
_ => (0, 0, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnActionFloaters(PlayerAction action)
|
||||||
|
{
|
||||||
|
var viewportWidth = GraphicsDevice.Viewport.Width;
|
||||||
|
var margin = Math.Max(14, viewportWidth / 90);
|
||||||
|
var statsTotalWidth = 240 + 16 + 240 + 16 + 170;
|
||||||
|
var statsX = Math.Max(margin + 520, viewportWidth - margin - statsTotalWidth);
|
||||||
|
const int statsY = 124;
|
||||||
|
var (edge, enemyEdge, shield) = GetActionDelta(action);
|
||||||
|
|
||||||
|
if (edge != 0)
|
||||||
|
{
|
||||||
|
SpawnFloatingText(FormatDelta(edge), new Vector2(statsX + 112, statsY - 18), edge > 0 ? Color.LawnGreen : Color.OrangeRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enemyEdge != 0)
|
||||||
|
{
|
||||||
|
SpawnFloatingText(FormatDelta(enemyEdge), new Vector2(statsX + 368, statsY - 18), enemyEdge > 0 ? Color.Orange : Color.CornflowerBlue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shield != 0)
|
||||||
|
{
|
||||||
|
SpawnFloatingText(FormatDelta(shield), new Vector2(statsX + 564, statsY - 18), shield > 0 ? Color.LightSkyBlue : Color.OrangeRed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnFloatingText(string text, Vector2 position, Color color)
|
||||||
|
{
|
||||||
|
_floatingStatTexts.Add(new FloatingStatText(text, position, color, 0.9f, 0.9f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFloatingTexts(float dt)
|
||||||
|
{
|
||||||
|
for (var i = _floatingStatTexts.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var item = _floatingStatTexts[i];
|
||||||
|
var updated = item with
|
||||||
|
{
|
||||||
|
Remaining = item.Remaining - dt,
|
||||||
|
Position = item.Position + new Vector2(0f, -20f * dt)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (updated.Remaining <= 0f)
|
||||||
|
{
|
||||||
|
_floatingStatTexts.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_floatingStatTexts[i] = updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFloatingTexts()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _floatingStatTexts.Count; i++)
|
||||||
|
{
|
||||||
|
var item = _floatingStatTexts[i];
|
||||||
|
var alpha = Math.Clamp(item.Remaining / item.Lifetime, 0f, 1f);
|
||||||
|
_spriteBatch.DrawString(_debugFont, item.Text, item.Position, item.Color * alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatDelta(int value)
|
||||||
|
{
|
||||||
|
return value >= 0 ? $"+{value}" : value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float SmoothTowards(float current, float target, float dt, float speed)
|
||||||
|
{
|
||||||
|
var alpha = 1f - MathF.Exp(-speed * Math.Max(0f, dt));
|
||||||
|
return current + ((target - current) * alpha);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawBorder(Rectangle rect, Color color)
|
private void DrawBorder(Rectangle rect, Color color)
|
||||||
{
|
{
|
||||||
const int thickness = 2;
|
const int thickness = 2;
|
||||||
@@ -820,9 +1205,9 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
|
|
||||||
return _decisionStage switch
|
return _decisionStage switch
|
||||||
{
|
{
|
||||||
DecisionStage.Flop => "Flop action (1st)",
|
DecisionStage.Preflop => "Preflop action (1st)",
|
||||||
DecisionStage.Turn => "Turn action (2nd)",
|
DecisionStage.Flop => "Flop action (2nd)",
|
||||||
DecisionStage.River => "River action (3rd)",
|
DecisionStage.Turn => "Turn action (3rd)",
|
||||||
_ => "-"
|
_ => "-"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -928,9 +1313,9 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
{
|
{
|
||||||
return _decisionStage switch
|
return _decisionStage switch
|
||||||
{
|
{
|
||||||
|
DecisionStage.Preflop => 0,
|
||||||
DecisionStage.Flop => 3,
|
DecisionStage.Flop => 3,
|
||||||
DecisionStage.Turn => 4,
|
DecisionStage.Turn => 4,
|
||||||
DecisionStage.River => 5,
|
|
||||||
_ => 3
|
_ => 3
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -985,9 +1370,16 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
|
|
||||||
private enum DecisionStage
|
private enum DecisionStage
|
||||||
{
|
{
|
||||||
|
Preflop,
|
||||||
Flop,
|
Flop,
|
||||||
Turn,
|
Turn
|
||||||
River
|
}
|
||||||
|
|
||||||
|
private enum TurnActor
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Player,
|
||||||
|
Enemy
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum RunScreen
|
private enum RunScreen
|
||||||
@@ -1025,4 +1417,6 @@ public class Game1 : Microsoft.Xna.Framework.Game
|
|||||||
Vector2 BoardCardsPos,
|
Vector2 BoardCardsPos,
|
||||||
Vector2 PlayerLabelPos,
|
Vector2 PlayerLabelPos,
|
||||||
Vector2 PlayerCardsPos);
|
Vector2 PlayerCardsPos);
|
||||||
|
|
||||||
|
private readonly record struct FloatingStatText(string Text, Vector2 Position, Color Color, float Remaining, float Lifetime);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user