mirror of
https://github.com/Earth-Genesis-Games/Ajedrez_Purgatorio.git
synced 2026-09-11 09:47:35 +00:00
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:
@@ -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(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+13068
-3
File diff suppressed because it is too large
Load Diff
+14137
-1072
File diff suppressed because it is too large
Load Diff
+16924
-3859
File diff suppressed because it is too large
Load Diff
+3296
-1
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user