using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections;
using System.Collections.Generic;
public class StraightPoolTwoPlayersAnimated : MonoBehaviour
{
[Header("Ball Buttons")]
[SerializeField] private Button[] ballButtons;
[SerializeField] private Button foulButton;
[SerializeField] private Button foulMinusTwoButton;
[Header("UI Texts")]
[SerializeField] private TMP_Text player1ScoreText;
[SerializeField] private TMP_Text player2ScoreText;
[SerializeField] private TMP_Text inningText;
[SerializeField] private TMP_Text player1AvgText;
[SerializeField] private TMP_Text player2AvgText;
[SerializeField] private TMP_Text player1HighrunText;
[SerializeField] private TMP_Text player2HighrunText;
[SerializeField] private TMP_Text player1ToWinText;
[SerializeField] private TMP_Text player2ToWinText;
[Header("Settings")]
[SerializeField] private int targetScore = 100;
[Header("References")]
[SerializeField] private FoulSystem foulSystem;
[SerializeField] private UndoRedoManager undoRedoManager;
[SerializeField] private ClassicViewController classicViewController; // Panel-Controller für Classic-Ansicht
// --- Game State ---
private int player1Score;
private int player2Score;
private int inning = 1;
private int currentPlayer = 0; // 0 = P1, 1 = P2
private int remainingBalls = 15;
private int player1Highrun;
private int player2Highrun;
private int player1CurrentRun;
private int player2CurrentRun;
// Bälle pro Aufnahme (für ClassicView, unabhängig von Serien-Reset)
private int p1BallsInCurrentInning;
private int p2BallsInCurrentInning;
private bool p1FoulInCurrentInning; // NEU
private bool p2FoulInCurrentInning; // NEU
// ===== ClassicView-Zustand für Undo/Redo =====
[System.Serializable]
public class ClassicRowState
{
public int inningNumber;
public int p1BallsInning;
public int p1Total;
public bool p1Foul;
public int p2BallsInning;
public int p2Total;
public bool p2Foul;
}
// Modell der ClassicView (eine Zeile pro Aufnahme)
private readonly List<ClassicRowState> classicRows = new List<ClassicRowState>();
private void Start()
{
foulSystem.Initialize(this);
for (int i = 0; i < ballButtons.Length; i++)
{
int index = i + 1;
ballButtons[i].onClick.AddListener(() => OnBallButtonClicked(index));
}
foulButton.onClick.AddListener(OnFoulButtonClicked);
foulMinusTwoButton.onClick.AddListener(OnMinusTwoFoulButtonClicked);
// Startpunkte für Spieler 1 speichern
foulSystem.StartTurn(currentPlayer, player1Score);
UpdateUI();
// Ersten Snapshot anlegen (falls Manager vorhanden)
if (undoRedoManager != null) undoRedoManager.PushCheckpoint("Start");
}
private void OnBallButtonClicked(int buttonValue)
{
if (undoRedoManager != null) undoRedoManager.PushCheckpoint("Ball " + buttonValue);
int points = remainingBalls - buttonValue;
remainingBalls = buttonValue;
// Punkte & Serien
if (currentPlayer == 0)
{
player1Score += points;
player1CurrentRun += points;
if (player1CurrentRun > player1Highrun) player1Highrun = player1CurrentRun;
// Bälle von Spieler 1 in dieser Aufnahme mitzählen
p1BallsInCurrentInning += points;
}
else
{
player2Score += points;
player2CurrentRun += points;
if (player2CurrentRun > player2Highrun) player2Highrun = player2CurrentRun;
// Bälle von Spieler 2 in dieser Aufnahme mitzählen
p2BallsInCurrentInning += points;
}
// Foul anwenden oder zurücksetzen
if (foulSystem.HasPendingFoul(currentPlayer))
foulSystem.ApplyPendingFoul(currentPlayer);
else
foulSystem.ResetConsecutiveFoulsIfLegal(currentPlayer);
// Buttons oberhalb des Wertes deaktivieren
for (int i = 0; i < ballButtons.Length; i++)
{
int btnValue = i + 1;
ballButtons[i].interactable = btnValue <= remainingBalls;
}
// Punkte animieren
StartCoroutine(AnimateScore());
// Classic-Modell & UI für diese Aufnahme aktualisieren
UpdateClassicStateAndView();
// Spielerwechsel oder neues Rack
if (buttonValue != 1)
SwitchPlayer();
else
ResetBalls();
}
private IEnumerator AnimateScore()
{
int start1 = int.Parse(player1ScoreText.text);
int start2 = int.Parse(player2ScoreText.text);
int target1 = player1Score;
int target2 = player2Score;
float duration = 0.3f;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
t = Mathf.SmoothStep(0f, 1f, t);
player1ScoreText.text = Mathf.RoundToInt(Mathf.Lerp(start1, target1, t)).ToString();
player2ScoreText.text = Mathf.RoundToInt(Mathf.Lerp(start2, target2, t)).ToString();
yield return null;
}
player1ScoreText.text = target1.ToString();
player2ScoreText.text = target2.ToString();
}
private void OnFoulButtonClicked()
{
foulSystem.MarkFoul(currentPlayer);
// Foul-Flag für die aktuelle Aufnahme setzen
if (currentPlayer == 0)
p1FoulInCurrentInning = true;
else
p2FoulInCurrentInning = true;
// KEIN direktes Setzen von foulButton.interactable hier!
// foulButton.interactable = false; <-- RAUS
UpdateUI(); // sorgt u.a. für korrekten Button-Zustand
}
private void OnMinusTwoFoulButtonClicked()
{
foulSystem.MarkMinusTwoFoul();
// Break-Foul zählt für P1 in dieser Aufnahme
p1FoulInCurrentInning = true;
// KEIN direktes Setzen von foulMinusTwoButton.interactable hier!
// foulMinusTwoButton.interactable = false; <-- RAUS
UpdateUI();
}
private void UpdateFoulButtons()
{
// Foul-Button: gesperrt, wenn aktuell ein vorgemerktes Foul existiert
bool hasPendingFoul = foulSystem.HasPendingFoul(currentPlayer);
foulButton.interactable = !hasPendingFoul;
// -2-Foul nur am Break (Beispiel: 1. Inning, Spieler 1 am Zug, alle 15 Bälle auf dem Tisch,
// noch keine Bälle in der Aufnahme gelocht)
bool isBreakSituation =
inning == 1 &&
currentPlayer == 0 &&
remainingBalls == 15 &&
p1BallsInCurrentInning == 0 &&
p2BallsInCurrentInning == 0;
foulMinusTwoButton.gameObject.SetActive(isBreakSituation);
foulMinusTwoButton.interactable = isBreakSituation;
}
private void SwitchPlayer()
{
currentPlayer = 1 - currentPlayer;
foulButton.interactable = true;
// Wenn wieder Spieler 1 dran ist, beginnt eine NEUE Aufnahme
if (currentPlayer == 0)
{
inning++;
// Aufnahme-Zähler für beide Spieler zurücksetzen
p1BallsInCurrentInning = 0;
p2BallsInCurrentInning = 0;
// NEU: Foul-Flags für neue Aufnahme zurücksetzen
p1FoulInCurrentInning = false;
p2FoulInCurrentInning = false;
}
ResetCurrentRun(currentPlayer);
int currentScore = currentPlayer == 0 ? player1Score : player2Score;
foulSystem.StartTurn(currentPlayer, currentScore);
UpdateUI();
}
private void ResetCurrentRun(int activePlayer)
{
if (activePlayer == 0) player2CurrentRun = 0;
else player1CurrentRun = 0;
}
public void ResetBalls()
{
remainingBalls = 15;
foreach (Button btn in ballButtons)
btn.interactable = true;
foulMinusTwoButton.interactable = (inning == 1 && currentPlayer == 0);
UpdateUI();
}
public void ResetBallsExternal() => ResetBalls();
public void SetCurrentPlayer(int index)
{
currentPlayer = index;
UpdateUI();
}
public void ApplyScoreChange(int p1Delta, int p2Delta)
{
player1Score += p1Delta;
player2Score += p2Delta;
UpdateUI();
}
public void UpdateUI()
{
player1ScoreText.text = player1Score.ToString();
player2ScoreText.text = player2Score.ToString();
inningText.text = inning.ToString();
float avg1 = inning > 0 ? (float)player1Score / inning : 0f;
float avg2 = inning > 0 ? (float)player2Score / inning : 0f;
player1AvgText.text = avg1.ToString("0.00");
player2AvgText.text = avg2.ToString("0.00");
player1HighrunText.text = player1Highrun.ToString();
player2HighrunText.text = player2Highrun.ToString();
player1ToWinText.text = Mathf.Max(0, targetScore - player1Score).ToString();
player2ToWinText.text = Mathf.Max(0, targetScore - player2Score).ToString();
player1ScoreText.color = currentPlayer == 0 ? Color.green : Color.white;
player2ScoreText.color = currentPlayer == 1 ? Color.green : Color.white;
// Diese Zeile kannst du ENTFERNEN, weil wir das in UpdateFoulButtons sauberer machen:
// foulMinusTwoButton.gameObject.SetActive(inning == 1 && currentPlayer == 0);
// NEU:
UpdateFoulButtons();
}
// Getter für FoulSystem
public int Player1Score => player1Score;
public int Player2Score => player2Score;
// ===== Classic-Modell + View aktualisieren =====
private void UpdateClassicStateAndView()
{
// ❗ NICHT mehr HasPendingFoul hier verwenden,
// sondern unsere eigenen Flags pro Aufnahme:
bool p1Foul = p1FoulInCurrentInning;
bool p2Foul = p2FoulInCurrentInning;
// vorhandene Zeile für dieses Inning suchen oder neu anlegen
ClassicRowState row = classicRows.Find(r => r.inningNumber == inning);
if (row == null)
{
row = new ClassicRowState { inningNumber = inning };
classicRows.Add(row);
}
// aktuellen Inning-Stand ins Modell schreiben
row.p1BallsInning = p1BallsInCurrentInning;
row.p1Total = player1Score;
row.p1Foul = p1Foul;
row.p2BallsInning = p2BallsInCurrentInning;
row.p2Total = player2Score;
row.p2Foul = p2Foul;
if (classicViewController == null) return;
// UI für diese Zeile aktualisieren
classicViewController.UpdateRow(
row.inningNumber,
row.p1BallsInning,
row.p1Total,
row.p1Foul,
row.p2BallsInning,
row.p2Total,
row.p2Foul
);
}
// ======= STATE-API für Undo/Redo =======
[System.Serializable]
public class GameState
{
public int player1Score, player2Score;
public int inning, currentPlayer, remainingBalls;
public int player1Highrun, player2Highrun;
public int player1CurrentRun, player2CurrentRun;
public bool foulBtnInteractable;
public bool minusTwoInteractable;
public bool minusTwoVisible;
public int p1BallsInCurrentInning;
public int p2BallsInCurrentInning;
public List<ClassicRowState> classicRows;
}
public GameState CaptureState()
{
// tiefe Kopie der ClassicRows
var rowsCopy = new List<ClassicRowState>();
foreach (var r in classicRows)
{
rowsCopy.Add(new ClassicRowState
{
inningNumber = r.inningNumber,
p1BallsInning = r.p1BallsInning,
p1Total = r.p1Total,
p1Foul = r.p1Foul,
p2BallsInning = r.p2BallsInning,
p2Total = r.p2Total,
p2Foul = r.p2Foul
});
}
return new GameState
{
player1Score = player1Score,
player2Score = player2Score,
inning = inning,
currentPlayer = currentPlayer,
remainingBalls = remainingBalls,
player1Highrun = player1Highrun,
player2Highrun = player2Highrun,
player1CurrentRun = player1CurrentRun,
player2CurrentRun = player2CurrentRun,
foulBtnInteractable = foulButton != null ? foulButton.interactable : true,
minusTwoInteractable = foulMinusTwoButton != null ? foulMinusTwoButton.interactable : false,
minusTwoVisible = foulMinusTwoButton != null ? foulMinusTwoButton.gameObject.activeSelf : false,
p1BallsInCurrentInning = p1BallsInCurrentInning,
p2BallsInCurrentInning = p2BallsInCurrentInning,
classicRows = rowsCopy
};
}
public void RestoreState(GameState s)
{
player1Score = s.player1Score;
player2Score = s.player2Score;
inning = s.inning;
currentPlayer = s.currentPlayer;
remainingBalls = s.remainingBalls;
player1Highrun = s.player1Highrun;
player2Highrun = s.player2Highrun;
player1CurrentRun = s.player1CurrentRun;
player2CurrentRun = s.player2CurrentRun;
p1BallsInCurrentInning = s.p1BallsInCurrentInning;
p2BallsInCurrentInning = s.p2BallsInCurrentInning;
// Buttons laut remainingBalls
for (int i = 0; i < ballButtons.Length; i++)
{
int val = i + 1;
ballButtons[i].interactable = val <= remainingBalls;
}
if (foulButton != null) foulButton.interactable = s.foulBtnInteractable;
if (foulMinusTwoButton != null)
{
foulMinusTwoButton.interactable = s.minusTwoInteractable;
foulMinusTwoButton.gameObject.SetActive(s.minusTwoVisible);
}
// ClassicRows aus State übernehmen
classicRows.Clear();
if (s.classicRows != null)
{
foreach (var r in s.classicRows)
{
classicRows.Add(new ClassicRowState
{
inningNumber = r.inningNumber,
p1BallsInning = r.p1BallsInning,
p1Total = r.p1Total,
p1Foul = r.p1Foul,
p2BallsInning = r.p2BallsInning,
p2Total = r.p2Total,
p2Foul = r.p2Foul
});
}
}
// Classic-Panel aus dem Modell neu aufbauen
if (classicViewController != null)
{
classicViewController.ClearAll();
foreach (var r in classicRows)
{
classicViewController.UpdateRow(
r.inningNumber,
r.p1BallsInning,
r.p1Total,
r.p1Foul,
r.p2BallsInning,
r.p2Total,
r.p2Foul
);
}
}
UpdateUI();
}
}
