fix: piece movement, gameplay bugs, and test fixes

- GameManager.MovePiece: add visual position update (transform.position), logical position (currentPos), and hasMoved flag — pieces were static because grid updated but piece state never did
- GameManager.MovePiece: add castling rook movement (was detected but never executed)
- BoardManager: add _campaignSetupDone guard to prevent double piece placement
- GameManager: fix RecordMove hash to use next player (!whiteTurn)
- CheckDetector: use GetAttackSquares instead of GetAvailableMoves to prevent StackOverflow in IsInCheck recursion
- King: use IsSquareAttackedBy for castling check instead of GameManager.IsInCheck
- AIController: add IsMoveLegal validation before executing moves
- PurgatoryManager: fix state transitions to GameState.Purgatory/Playing
- CampaignState: call identity.ResetState() on Reset()
- GameManager: fire OnPieceMoved before promotion logic
- PromotionUI: auto-promote to Queen if promotionPanel is null
- CampaignManager: transition to Playing when chapter is null
- BoardManager: force GameState.Playing fallback after board setup
- PurgatorySetupValidator: add Setup Purgatorio en Escena menu item with inline setup
- Fix 13 test failures: add LogAssert.Expect for null-input tests, fix stalemate test layout
This commit is contained in:
2026-08-20 01:09:43 -03:00
parent fdb8c6eef7
commit 389f5272b5
17 changed files with 47785 additions and 4972 deletions
@@ -166,7 +166,10 @@ public class CampaignState : ScriptableObject
foreach (var identity in _allIdentities)
{
if (identity != null && !string.IsNullOrEmpty(identity.characterName))
{
_alivePieces.Add(identity.characterName.ToLower());
identity.ResetState();
}
}
Debug.Log($"[CampaignState] Estado reseteado. {_alivePieces.Count} piezas inicializadas como vivas.");
@@ -1,12 +1,14 @@
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using TMPro;
using System.Text;
/// <summary>
/// Herramienta de validación de setup del Sprint 3 - Sistema de Purgatorio.
/// Verifica que todas las referencias y configuraciones estén correctas.
/// Herramienta de validación y setup del Sprint 3 - Sistema de Purgatorio.
///
/// Uso: Unity Editor → Tools → Ajedrez Purgatorio → Validate Purgatorio Setup
/// Unity Editor → Tools → Ajedrez Purgatorio → Setup Purgatorio en Escena
/// </summary>
public class PurgatorySetupValidator : EditorWindow
{
@@ -20,6 +22,173 @@ public class PurgatorySetupValidator : EditorWindow
GetWindow<PurgatorySetupValidator>("Purgatorio Setup Validator");
}
[MenuItem("Tools/Ajedrez Purgatorio/Setup Purgatorio en Escena")]
public static void SetupPurgatoryInScene()
{
var scene = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene();
Debug.Log($"[PurgatorySetup] Configurando Purgatorio en escena: {scene.name}");
SetupPurgatorySystem();
SetupManagers();
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(scene);
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene);
Debug.Log("[PurgatorySetup] ✅ Purgatorio configurado. Ejecutando validación...");
var window = GetWindow<PurgatorySetupValidator>("Purgatorio Setup Validator");
window.RunValidation();
}
static void SetupPurgatorySystem()
{
if (GameObject.Find("PurgatoryCanvas") != null) { Debug.Log(" ⏭ PurgatoryCanvas ya existe."); return; }
var cv = MakeCanvas("PurgatoryCanvas", 120);
// — Offer Panel —
var offerP = MakePanel("OfferPanel", cv.transform, new Vector2(700, 480), new Color(.05f,.02f,.08f,.97f));
var oTitle = MakeTMP("TitleText", offerP.transform, "¿Desafiar a la Muerte?", 36f);
var oPName = MakeTMP("PieceNameText", offerP.transform, "Nombre", 30f);
var oRole = MakeTMP("RoleText", offerP.transform, "Rol", 22f);
var oRel = MakeTMP("RelationshipText", offerP.transform, "Relación", 18f);
var oFlav = MakeTMP("FlavorText", offerP.transform, "...", 18f);
AnchorNorm(oTitle.rectTransform, .05f,.80f,.95f,.97f); oTitle.color = new Color(.9f,.1f,.1f); oTitle.fontStyle = FontStyles.Bold; oTitle.alignment = TextAlignmentOptions.Center;
AnchorNorm(oPName.rectTransform, .05f,.64f,.95f,.80f); oPName.color = new Color(1f,.85f,.2f); oPName.fontStyle = FontStyles.Bold; oPName.alignment = TextAlignmentOptions.Center;
AnchorNorm(oRole.rectTransform, .05f,.52f,.95f,.64f); oRole.color = new Color(.85f,.65f,.65f); oRole.alignment = TextAlignmentOptions.Center;
AnchorNorm(oRel.rectTransform, .05f,.42f,.95f,.52f); oRel.color = new Color(.75f,.75f,.75f); oRel.alignment = TextAlignmentOptions.Center;
AnchorNorm(oFlav.rectTransform, .05f,.24f,.95f,.42f); oFlav.color = new Color(.7f,.7f,.85f); oFlav.fontStyle = FontStyles.Italic; oFlav.alignment = TextAlignmentOptions.Center;
var acceptB = MakeButton("AcceptButton", offerP.transform, "⚄ DESAFIAR");
var declineB = MakeButton("DeclineButton", offerP.transform, "✕ DEJAR IR");
AnchorNorm(acceptB.GetComponent<RectTransform>(), .08f,.04f,.45f,.20f); acceptB.GetComponent<Image>().color = new Color(.45f,.08f,.08f);
AnchorNorm(declineB.GetComponent<RectTransform>(), .55f,.04f,.92f,.20f); declineB.GetComponent<Image>().color = new Color(.18f,.18f,.18f);
var offerUI = offerP.AddComponent<PurgatoryOfferUI>();
var offerSO = new SerializedObject(offerUI);
Ref(offerSO, "_offerPanel", offerP);
Ref(offerSO, "_titleText", oTitle);
Ref(offerSO, "_pieceNameText", oPName);
Ref(offerSO, "_roleText", oRole);
Ref(offerSO, "_relationshipText", oRel);
Ref(offerSO, "_flavorText", oFlav);
Ref(offerSO, "_acceptButton", acceptB.GetComponent<Button>());
Ref(offerSO, "_declineButton", declineB.GetComponent<Button>());
offerSO.ApplyModifiedPropertiesWithoutUndo();
offerP.SetActive(false);
// — Dice Roll Panel —
var diceP = MakePanel("DiceRollPanel", cv.transform, new Vector2(700, 460), new Color(.03f,.03f,.1f,.97f));
var dStatus = MakeTMP("StatusText", diceP.transform, "Tirando dados...", 28f);
var dPRoll = MakeTMP("PlayerRollText", diceP.transform, "?", 72f);
var dDRoll = MakeTMP("DeathRollText", diceP.transform, "?", 72f);
var dPMod = MakeTMP("PlayerModText", diceP.transform, "+0", 18f);
var dPTot = MakeTMP("PlayerTotalText", diceP.transform, "Total: ?", 22f);
var dDTot = MakeTMP("DeathTotalText", diceP.transform, "Total: ?", 22f);
AnchorNorm(dStatus.rectTransform, .05f,.84f,.95f,.97f); dStatus.alignment = TextAlignmentOptions.Center; dStatus.color = Color.white;
AnchorNorm(dPRoll.rectTransform, .05f,.52f,.45f,.84f); dPRoll.color = new Color(.3f,.85f,.3f); dPRoll.fontStyle = FontStyles.Bold; dPRoll.alignment = TextAlignmentOptions.Center;
AnchorNorm(dDRoll.rectTransform, .55f,.52f,.95f,.84f); dDRoll.color = new Color(.85f,.2f,.2f); dDRoll.fontStyle = FontStyles.Bold; dDRoll.alignment = TextAlignmentOptions.Center;
AnchorNorm(dPMod.rectTransform, .05f,.36f,.45f,.52f); dPMod.color = new Color(.8f,.8f,.4f); dPMod.alignment = TextAlignmentOptions.Center;
AnchorNorm(dPTot.rectTransform, .05f,.22f,.45f,.36f); dPTot.color = new Color(.3f,.9f,.3f); dPTot.alignment = TextAlignmentOptions.Center;
AnchorNorm(dDTot.rectTransform, .55f,.22f,.95f,.36f); dDTot.color = new Color(.9f,.2f,.2f); dDTot.alignment = TextAlignmentOptions.Center;
var diceRollUI = diceP.AddComponent<DiceRollUI>();
var diceSO = new SerializedObject(diceRollUI);
Ref(diceSO, "_dicePanel", diceP);
Ref(diceSO, "_playerRollText", dPRoll);
Ref(diceSO, "_playerModifiersText", dPMod);
Ref(diceSO, "_playerTotalText", dPTot);
Ref(diceSO, "_deathRollText", dDRoll);
Ref(diceSO, "_deathTotalText", dDTot);
Ref(diceSO, "_statusText", dStatus);
diceSO.ApplyModifiedPropertiesWithoutUndo();
diceP.SetActive(false);
// — Result Panel —
var resP = MakePanel("ResultPanel", cv.transform, new Vector2(700, 440), new Color(.04f,.04f,.04f,.97f));
var rOut = MakeTMP("OutcomeText", resP.transform, "RESULTADO", 56f);
var rQuote = MakeTMP("DeathQuoteText", resP.transform, "\"...\"", 22f);
var rDet = MakeTMP("DetailsText", resP.transform, "...", 20f);
AnchorNorm(rOut.rectTransform, .05f,.74f,.95f,.97f); rOut.fontStyle = FontStyles.Bold; rOut.alignment = TextAlignmentOptions.Center;
AnchorNorm(rQuote.rectTransform, .08f,.48f,.92f,.74f); rQuote.fontStyle = FontStyles.Italic; rQuote.color = new Color(.7f,.7f,.85f); rQuote.alignment = TextAlignmentOptions.Center;
AnchorNorm(rDet.rectTransform, .05f,.30f,.95f,.48f); rDet.color = new Color(.75f,.75f,.75f); rDet.alignment = TextAlignmentOptions.Center;
var flashGO = new GameObject("FlashOverlay");
flashGO.transform.SetParent(resP.transform, false);
var flashImg = flashGO.AddComponent<Image>(); flashImg.color = new Color(1,1,1,0);
Stretch(flashGO.GetComponent<RectTransform>()); flashGO.SetActive(false);
var contResBtn = MakeButton("ContinueButton", resP.transform, "CONTINUAR");
AnchorNorm(contResBtn.GetComponent<RectTransform>(), .3f,.04f,.7f,.20f);
var resultUI = resP.AddComponent<DiceResultUI>();
var resSO = new SerializedObject(resultUI);
Ref(resSO, "_resultPanel", resP);
Ref(resSO, "_outcomeText", rOut);
Ref(resSO, "_deathQuoteText",rQuote);
Ref(resSO, "_detailsText", rDet);
Ref(resSO, "_continueButton",contResBtn.GetComponent<Button>());
Ref(resSO, "_flashOverlay", flashImg);
resSO.ApplyModifiedPropertiesWithoutUndo();
resP.SetActive(false);
// — PurgatoryManager GO —
var pmGO = new GameObject("PurgatoryManager");
var pmgr = pmGO.AddComponent<PurgatoryManager>();
var dSys = pmGO.AddComponent<DiceSystem>();
var cs = AssetDatabase.LoadAssetAtPath<CampaignState>("Assets/Game/Data/Resources/CampaignState.asset");
var pmSO = new SerializedObject(pmgr);
Ref(pmSO, "_diceSystem", dSys);
Ref(pmSO, "_offerUI", offerUI);
Ref(pmSO, "_diceRollUI", diceRollUI);
Ref(pmSO, "_resultUI", resultUI);
if (cs != null) Ref(pmSO, "_campaignState", cs);
pmSO.ApplyModifiedPropertiesWithoutUndo();
Debug.Log(" ✅ PurgatoryCanvas + PurgatoryManager creados.");
}
static void SetupManagers()
{
var cs = AssetDatabase.LoadAssetAtPath<CampaignState>("Assets/Game/Data/Resources/CampaignState.asset");
var cfg = AssetDatabase.LoadAssetAtPath<CampaignConfig>("Assets/Game/Data/CampaignConfig.asset");
if (GameObject.Find("CampaignManager") == null)
{
var go = new GameObject("CampaignManager");
var cm = go.AddComponent<CampaignManager>();
var so = new SerializedObject(cm);
if (cfg != null) Ref(so, "_campaignConfig", cfg);
if (cs != null) Ref(so, "_campaignState", cs);
so.ApplyModifiedPropertiesWithoutUndo();
Debug.Log(" ✅ CampaignManager.");
}
if (Object.FindFirstObjectByType<SceneTransitionManager>() == null)
{
new GameObject("SceneTransitionManager").AddComponent<SceneTransitionManager>();
Debug.Log(" ✅ SceneTransitionManager.");
}
if (Object.FindFirstObjectByType<AjedrezPurgatorio.Meta.SaveSystem>() == null)
{
new GameObject("SaveSystem").AddComponent<AjedrezPurgatorio.Meta.SaveSystem>();
Debug.Log(" ✅ SaveSystem.");
}
if (Object.FindFirstObjectByType<AjedrezPurgatorio.Audio.AudioManager>() == null)
{
new GameObject("AudioManager").AddComponent<AjedrezPurgatorio.Audio.AudioManager>();
Debug.Log(" ✅ AudioManager.");
}
if (Object.FindFirstObjectByType<PieceIdentityManager>() == null)
{
var go = new GameObject("PieceIdentityManager");
var pim = go.AddComponent<PieceIdentityManager>();
if (cs != null) Ref(pim, "_campaignState", cs);
Debug.Log(" ✅ PieceIdentityManager.");
}
}
void OnGUI()
{
GUILayout.Label("Sistema de Purgatorio - Validación de Setup", EditorStyles.boldLabel);
@@ -293,4 +462,73 @@ public class PurgatorySetupValidator : EditorWindow
{
_report.AppendLine($"❌ ERROR: {message}");
}
// ══════════════════════════════════════════════════════
// UI HELPERS
// ══════════════════════════════════════════════════════
static Canvas MakeCanvas(string name, int order)
{
var go = new GameObject(name);
var c = go.AddComponent<Canvas>(); c.renderMode = RenderMode.ScreenSpaceOverlay; c.sortingOrder = order;
var sc = go.AddComponent<CanvasScaler>(); sc.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; sc.referenceResolution = new Vector2(1920,1080); sc.matchWidthOrHeight = .5f;
go.AddComponent<GraphicRaycaster>();
return c;
}
static GameObject MakePanel(string name, Transform parent, Vector2 size, Color color)
{
var go = new GameObject(name); go.transform.SetParent(parent, false);
var img = go.AddComponent<Image>(); img.color = color;
var rt = go.GetComponent<RectTransform>();
rt.anchorMin = rt.anchorMax = rt.pivot = new Vector2(.5f,.5f);
rt.sizeDelta = size == Vector2.zero ? new Vector2(1920,1080) : size;
rt.anchoredPosition = Vector2.zero;
return go;
}
static TextMeshProUGUI MakeTMP(string name, Transform parent, string text, float size)
{
var go = new GameObject(name); go.transform.SetParent(parent, false);
var tmp = go.AddComponent<TextMeshProUGUI>();
tmp.text = text; tmp.fontSize = size; tmp.color = Color.white; tmp.enableWordWrapping = true;
return tmp;
}
static GameObject MakeButton(string name, Transform parent, string label)
{
var go = new GameObject(name); go.transform.SetParent(parent, false);
var img = go.AddComponent<Image>(); img.color = new Color(.22f,.12f,.08f);
var btn = go.AddComponent<Button>();
var col = btn.colors; col.highlightedColor = new Color(.45f,.3f,.18f); col.pressedColor = new Color(.12f,.06f,.03f); btn.colors = col;
var tGO = new GameObject("Text (TMP)"); tGO.transform.SetParent(go.transform, false);
var tmp = tGO.AddComponent<TextMeshProUGUI>();
tmp.text = label; tmp.fontSize = 26f; tmp.fontStyle = FontStyles.Bold; tmp.color = Color.white; tmp.alignment = TextAlignmentOptions.Center;
Stretch(tGO.GetComponent<RectTransform>());
return go;
}
static void AnchorNorm(RectTransform rt, float xMin, float yMin, float xMax, float yMax)
{
rt.anchorMin = new Vector2(xMin, yMin); rt.anchorMax = new Vector2(xMax, yMax);
rt.offsetMin = rt.offsetMax = Vector2.zero;
}
static void Stretch(RectTransform rt)
{
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
rt.offsetMin = rt.offsetMax = Vector2.zero;
}
static void Ref(SerializedObject so, string field, Object val)
{
var p = so.FindProperty(field);
if (p != null) p.objectReferenceValue = val;
}
static void Ref(Component comp, string field, Object val)
{
var so = new SerializedObject(comp);
var p = so.FindProperty(field);
if (p != null) { p.objectReferenceValue = val; so.ApplyModifiedPropertiesWithoutUndo(); }
}
}
+7 -2
View File
@@ -109,7 +109,6 @@ public class AIController : MonoBehaviour
return;
}
// Obtener mejor movimiento según estrategia
var move = _currentStrategy.GetBestMove(GameManager.Instance.board, false);
if (move == null)
@@ -120,9 +119,15 @@ public class AIController : MonoBehaviour
var (piece, targetPos) = move.Value;
// Validate move through MoveValidator before executing
if (!GameManager.Instance.IsMoveLegal(piece, targetPos))
{
Debug.LogWarning($"[AIController] Movimiento ilegal detectado: {piece.GetType().Name} → {targetPos}. Saltando.");
return;
}
Debug.Log($"[AIController] {_currentStrategy.StrategyName}: {piece.GetType().Name} → {targetPos}");
// Ejecutar movimiento
GameManager.Instance.MovePiece(piece, targetPos);
}
+16 -3
View File
@@ -36,6 +36,7 @@ public GameObject kingPrefab; // Prefab del Rey usado como "jefe" en el purgator
public float tileSize => _tileSize;
private GameObject[,] _squares = new GameObject[8, 8];
private bool _campaignSetupDone = false;
public static BoardManager Instance { get; private set; }
@@ -57,9 +58,19 @@ public GameObject kingPrefab; // Prefab del Rey usado como "jefe" en el purgator
GenerateBoard();
GenerateIndicators();
// Always place standard pieces for traditional chess.
// CampaignManager.SetupBoard() can override later if needed.
PlacePieces();
if (!_campaignSetupDone)
{
PlacePieces();
}
// Fallback: ensure GameState is Playing so SquareClick input works.
// CampaignManager.InitializeChapter handles this normally via dialogue flow,
// but if anything in that chain fails the board would be frozen.
if (GameStateManager.Instance != null && GameStateManager.Instance.CurrentState != GameState.Playing)
{
Debug.LogWarning("[BoardManager] GameState was not Playing after board setup. Forcing Playing state.");
GameStateManager.Instance.TransitionTo(GameState.Playing);
}
}
/// <summary>
@@ -68,6 +79,8 @@ public GameObject kingPrefab; // Prefab del Rey usado como "jefe" en el purgator
/// </summary>
public void SetupBoard(CampaignState campaignState)
{
_campaignSetupDone = true;
if (campaignState == null)
{
Debug.Log("[BoardManager] CampaignState es null. Usando setup estándar.");
@@ -212,7 +212,8 @@ public class CampaignManager : MonoBehaviour
if (chapter == null)
{
Debug.LogError($"[CampaignManager] No se pudo cargar capítulo en índice {chapterIndex}");
Debug.LogError($"[CampaignManager] No se pudo cargar capítulo en índice {chapterIndex}. Forzando estado Playing.");
TransitionToPlaying();
return;
}
@@ -107,25 +107,46 @@ public class CheckDetector
if (board == null || piece == null)
{
Debug.LogError("[CheckDetector] Board o piece es null.");
return true; // Asumir movimiento ilegal por seguridad
return true;
}
// Guardar estado
Vector2Int oldPos = piece.currentPos;
Piece captured = board[targetPos.x, targetPos.y];
// Simular movimiento
board[oldPos.x, oldPos.y] = null;
board[targetPos.x, targetPos.y] = piece;
piece.currentPos = targetPos;
// En passant: also remove the captured pawn on the adjacent square
Piece enPassantCaptured = null;
Vector2Int enPassantPos = default;
if (piece is Pawn && captured == null && Mathf.Abs(targetPos.x - oldPos.x) == 1)
{
int captureY = oldPos.y;
enPassantPos = new Vector2Int(targetPos.x, captureY);
enPassantCaptured = board[enPassantPos.x, enPassantPos.y];
if (enPassantCaptured != null && enPassantCaptured is Pawn)
{
board[enPassantPos.x, enPassantPos.y] = null;
}
else
{
enPassantCaptured = null;
}
}
bool leavesKingInCheck = IsInCheck(board, piece.isWhite);
// Restaurar
piece.currentPos = oldPos;
board[oldPos.x, oldPos.y] = piece;
board[targetPos.x, targetPos.y] = captured;
// Restore en passant captured pawn
if (enPassantCaptured != null)
{
board[enPassantPos.x, enPassantPos.y] = enPassantCaptured;
}
return leavesKingInCheck;
}
+32 -7
View File
@@ -247,9 +247,32 @@ public class GameManager : MonoBehaviour
_board[oldPos.x, oldPos.y] = null;
_board[newPos.x, newPos.y] = piece;
// Actualizar estado visual y lógico de la pieza
float tileSize = BoardManager.Instance != null ? BoardManager.Instance.tileSize : 10.24f;
piece.transform.position = new Vector3(newPos.x * tileSize, newPos.y * tileSize, -1f);
piece.currentPos = newPos;
piece.hasMoved = true;
// Enroque: mover la torre
if (isCastling)
{
int rookOldX = (newPos.x == 6) ? 7 : 0;
int rookNewX = (newPos.x == 6) ? 5 : 3;
Piece rook = _board[rookOldX, oldPos.y];
if (rook != null)
{
_board[rookOldX, oldPos.y] = null;
_board[rookNewX, oldPos.y] = rook;
rook.transform.position = new Vector3(rookNewX * tileSize, oldPos.y * tileSize, -1f);
rook.currentPos = new Vector2Int(rookNewX, oldPos.y);
rook.hasMoved = true;
}
}
// Registrar movimiento en DrawDetector (maneja historial y halfmove clock)
bool wasCapture = captured != null || isEnPassant;
_drawDetector.RecordMove(_board, _whiteTurn, piece, wasCapture);
// Hash debe reflejar el turno del SIGUIENTE jugador (después de cambiar)
_drawDetector.RecordMove(_board, !_whiteTurn, piece, wasCapture);
// Track campaign stats
_totalMoves++;
@@ -309,34 +332,36 @@ public class GameManager : MonoBehaviour
OnCheckResolved?.Invoke();
}
// Disparar evento de movimiento completado
OnPieceMoved?.Invoke(piece, oldPos, newPos);
// AudioManager: play move SFX
if (AudioManager.Instance != null)
AudioManager.Instance.PlayMove();
// ✅ PROMOCIÓN DE PEÓN
// ✅ PROMOCIÓN DE PEÓN (antes de OnPieceMoved para que el handler vea la pieza correcta)
if (piece is Pawn)
{
int finalRow = piece.isWhite ? 7 : 0;
if (piece.currentPos.y == finalRow)
{
// Disparar evento de movimiento completado ANTES de la promoción
// para que los handlers (HUD, etc.) vean el movimiento correcto
OnPieceMoved?.Invoke(piece, oldPos, newPos);
if (piece.isWhite && _promotionUI != null)
{
// Jugador elige
_promotionUI.ShowPromotionMenu(piece, (pieceType) => PromotePawnTo(piece, pieceType));
return;
}
else
{
// IA: promoción automática a Reina
PromotePawn(piece);
return;
}
}
}
// Disparar evento de movimiento completado
OnPieceMoved?.Invoke(piece, oldPos, newPos);
// Cambiar turno
_whiteTurn = !_whiteTurn;
+1 -5
View File
@@ -30,11 +30,7 @@ public class King : Piece
}
}
bool kingInCheck = false;
if (GameManager.Instance != null)
kingInCheck = GameManager.Instance.IsInCheck(isWhite);
else if (GameManagerClassic.Instance != null)
kingInCheck = GameManagerClassic.Instance.IsInCheck(isWhite);
bool kingInCheck = IsSquareAttackedBy(board, currentPos, !isWhite);
if (!hasMoved && !kingInCheck)
{
@@ -84,9 +84,10 @@ public class PurgatoryManager : MonoBehaviour
// Pausar juego
Time.timeScale = 0f;
// NO transicionar escena - solo mostramos UI overlay
// Bloquear input del tablero
if (GameStateManager.Instance != null)
GameStateManager.Instance.TransitionTo(GameState.Purgatory);
// Mostrar UI de oferta
_offerUI.Show(_currentCapturedPiece, () => OnPlayerAcceptsPurgatory(), () => OnPlayerDeclinesPurgatory());
}
@@ -177,7 +178,9 @@ public class PurgatoryManager : MonoBehaviour
// Reanudar juego
Time.timeScale = 1f;
// NO cambiar estado - ya estamos en Playing
// Restaurar estado a Playing
if (GameStateManager.Instance != null)
GameStateManager.Instance.TransitionTo(GameState.Playing);
}
/// <summary>
@@ -62,6 +62,11 @@ public class PromotionUI : MonoBehaviour
// Pausar el juego mientras el jugador elige
Time.timeScale = 0f;
}
else
{
Debug.LogWarning("[PromotionUI] _promotionPanel no asignado. Promoviendo a Reina automáticamente.");
OnPieceSelected(PieceType.Queen);
}
}
private void OnPieceSelected(PieceType pieceType)
File diff suppressed because it is too large Load Diff
+14137 -1072
View File
File diff suppressed because it is too large Load Diff
+16924 -3859
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+14 -10
View File
@@ -1,5 +1,6 @@
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
/// <summary>
/// Unit tests for CheckDetector — null safety, FindKing, basic check/checkmate detection.
@@ -25,12 +26,14 @@ public class CheckDetectorTests
[Test]
public void IsInCheck_NullBoard_ReturnsFalse()
{
LogAssert.Expect(LogType.Error, "[CheckDetector] Board es null.");
Assert.IsFalse(_detector.IsInCheck(null, true));
}
[Test]
public void IsCheckmate_NullBoard_ReturnsFalse()
{
LogAssert.Expect(LogType.Error, "[CheckDetector] Board es null.");
Assert.IsFalse(_detector.IsCheckmate(null, true));
}
@@ -38,6 +41,7 @@ public class CheckDetectorTests
public void WouldLeaveKingInCheck_NullBoard_ReturnsTrue()
{
var pawn = CreatePiece<Pawn>(3, 3, true);
LogAssert.Expect(LogType.Error, "[CheckDetector] Board o piece es null.");
Assert.IsTrue(_detector.WouldLeaveKingInCheck(null, pawn, new Vector2Int(3, 4)));
}
@@ -45,12 +49,14 @@ public class CheckDetectorTests
public void WouldLeaveKingInCheck_NullPiece_ReturnsTrue()
{
var board = new Piece[8, 8];
LogAssert.Expect(LogType.Error, "[CheckDetector] Board o piece es null.");
Assert.IsTrue(_detector.WouldLeaveKingInCheck(board, null, new Vector2Int(3, 4)));
}
[Test]
public void HasAnyLegalMove_NullBoard_ReturnsFalse()
{
LogAssert.Expect(LogType.Error, "[CheckDetector] Board es null.");
Assert.IsFalse(_detector.HasAnyLegalMove(null, true));
}
@@ -155,16 +161,14 @@ public class CheckDetectorTests
public void HasAnyLegalMove_KingSurroundedByOwnPieces_ReturnsFalse()
{
var board = new Piece[8, 8];
board[4, 4] = CreatePiece<King>(4, 4, true);
// Surround with own pieces (blocking all moves)
board[3, 3] = CreatePiece<Pawn>(3, 3, true);
board[3, 4] = CreatePiece<Pawn>(3, 4, true);
board[3, 5] = CreatePiece<Pawn>(3, 5, true);
board[4, 3] = CreatePiece<Pawn>(4, 3, true);
board[4, 5] = CreatePiece<Pawn>(4, 5, true);
board[5, 3] = CreatePiece<Pawn>(5, 3, true);
board[5, 4] = CreatePiece<Pawn>(5, 4, true);
board[5, 5] = CreatePiece<Pawn>(5, 5, true);
board[4, 7] = CreatePiece<King>(4, 7, true);
// Surround with own pawns on the last rank — pawns can't move forward (off board)
// and forward squares are occupied, so no piece has legal moves.
board[3, 6] = CreatePiece<Pawn>(3, 6, true);
board[3, 7] = CreatePiece<Pawn>(3, 7, true);
board[4, 6] = CreatePiece<Pawn>(4, 6, true);
board[5, 6] = CreatePiece<Pawn>(5, 6, true);
board[5, 7] = CreatePiece<Pawn>(5, 7, true);
Assert.IsFalse(_detector.HasAnyLegalMove(board, true));
}
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
/// <summary>
/// Unit tests for DrawDetector — position hashing, halfmove clock, insufficient material, reset.
@@ -203,6 +204,7 @@ public class DrawDetectorTests
[Test]
public void IsInsufficientMaterial_NullBoard_ReturnsFalse()
{
LogAssert.Expect(LogType.Error, "[DrawDetector] Board es null.");
Assert.IsFalse(_detector.IsInsufficientMaterial(null));
}
@@ -1,5 +1,6 @@
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
/// <summary>
/// Unit tests for MoveValidator — constructor, board bounds, null safety.
@@ -33,6 +34,7 @@ public class MoveValidatorTests
public void IsMoveLegal_NullBoard_ReturnsFalse()
{
var pawn = CreatePiece<Pawn>(3, 1, true);
LogAssert.Expect(LogType.Error, "[MoveValidator] Board o piece es null.");
Assert.IsFalse(_validator.IsMoveLegal(null, pawn, new Vector2Int(3, 2)));
}
@@ -40,6 +42,7 @@ public class MoveValidatorTests
public void IsMoveLegal_NullPiece_ReturnsFalse()
{
var board = new Piece[8, 8];
LogAssert.Expect(LogType.Error, "[MoveValidator] Board o piece es null.");
Assert.IsFalse(_validator.IsMoveLegal(board, null, new Vector2Int(3, 2)));
}
@@ -60,6 +63,7 @@ public class MoveValidatorTests
public void GetLegalMovesForPiece_NullBoard_ReturnsEmpty()
{
var pawn = CreatePiece<Pawn>(3, 1, true);
LogAssert.Expect(LogType.Error, "[MoveValidator] Board o piece es null.");
var moves = _validator.GetLegalMovesForPiece(null, pawn);
Assert.IsNotNull(moves);
Assert.AreEqual(0, moves.Count);
@@ -69,6 +73,7 @@ public class MoveValidatorTests
public void GetLegalMovesForPiece_NullPiece_ReturnsEmpty()
{
var board = new Piece[8, 8];
LogAssert.Expect(LogType.Error, "[MoveValidator] Board o piece es null.");
var moves = _validator.GetLegalMovesForPiece(board, null);
Assert.IsNotNull(moves);
Assert.AreEqual(0, moves.Count);
@@ -77,6 +82,7 @@ public class MoveValidatorTests
[Test]
public void GetAllLegalMoves_NullBoard_ReturnsEmpty()
{
LogAssert.Expect(LogType.Error, "[MoveValidator] Board es null.");
var moves = _validator.GetAllLegalMoves(null, true);
Assert.IsNotNull(moves);
Assert.AreEqual(0, moves.Count);
@@ -85,6 +91,7 @@ public class MoveValidatorTests
[Test]
public void CountLegalMoves_NullBoard_ReturnsZero()
{
LogAssert.Expect(LogType.Error, "[MoveValidator] Board es null.");
Assert.AreEqual(0, _validator.CountLegalMoves(null, true));
}