using System.Collections; using System.Collections.Generic; using UnityEngine; // Clase para representar una regla del LCS public class Classifier { public string condition; // Condición de la regla (estado del entorno) public string action; // Acción que debe tomar el agente public float reward; // Recompensa asociada public Classifier(string condition, string action) { this.condition = condition; this.action = action; this.reward = 0.0f; // Inicialmente, la recompensa es 0 } public void UpdateReward(float reward) { this.reward += reward; } } // Clase principal del LCS public class LearningClassifierSystem : MonoBehaviour { public List population; public Transform agent; public Transform target; // Objetivo que el agente debe alcanzar public float learningRate = 0.1f; public float mutationRate = 0.05f; public float moveSpeed = 2f; public float turnSpeed = 90f; public Color raycastColor = Color.red; public Color pathLineColor = Color.green; public float raycastDistance = 1f; private string lastState; private string lastAction; private Vector3 lastPosition; private float stuckTimer = 0f; private LineRenderer pathLine; void Start() { population = new List(); InitializePopulation(); // Crear y configurar el LineRenderer para mostrar el camino al objetivo pathLine = agent.gameObject.AddComponent(); pathLine.startWidth = 0.1f; pathLine.endWidth = 0.1f; pathLine.material = new Material(Shader.Find("Sprites/Default")) { color = pathLineColor }; } void FixedUpdate() { string currentState = GetStateFromEnvironment(); if (!string.IsNullOrEmpty(lastState) && !string.IsNullOrEmpty(lastAction)) { float reward = EvaluateAction(lastState, lastAction, currentState); UpdateClassifier(lastState, lastAction, reward); } string action = GetAction(currentState); PerformAction(action); // Verifica si el agente está atascado if (Vector3.Distance(agent.position, lastPosition) < 0.01f) { stuckTimer += Time.fixedDeltaTime; if (stuckTimer > 0.5f && action == "MoveForward") { // Penalización extra por quedarse quieto al intentar avanzar UpdateClassifier(currentState, action, -1.5f); action = GetRandomAction(); // Fuerza una nueva decisión aleatoria PerformAction(action); stuckTimer = 0f; } } else { stuckTimer = 0f; } lastPosition = agent.position; lastState = currentState; lastAction = action; // Actualizar la línea al objetivo UpdatePathLine(); } void UpdatePathLine() { if (target != null) { pathLine.positionCount = 2; pathLine.SetPosition(0, agent.position); pathLine.SetPosition(1, target.position); } } void InitializePopulation() { population.Add(new Classifier("000", "MoveForward")); population.Add(new Classifier("100", "TurnRight")); population.Add(new Classifier("010", "TurnLeft")); population.Add(new Classifier("001", "TurnRight")); population.Add(new Classifier("101", "TurnLeft")); population.Add(new Classifier("110", "TurnRight")); population.Add(new Classifier("011", "TurnRight")); population.Add(new Classifier("111", "TurnLeft")); } public string GetAction(string state) { if (Random.value < 0.1f) // 10% exploración return GetRandomAction(); float bestReward = float.MinValue; string bestAction = GetRandomAction(); bool found = false; foreach (var classifier in population) { if (classifier.condition == state) { found = true; if (classifier.reward > bestReward) { bestReward = classifier.reward; bestAction = classifier.action; } } } return (!found || bestReward <= 0) ? GetRandomAction() : bestAction; } string GetRandomAction() { string[] actions = { "MoveForward", "TurnLeft", "TurnRight" }; return actions[Random.Range(0, actions.Length)]; } public void UpdateClassifier(string state, string action, float reward) { foreach (var classifier in population) { if (classifier.condition == state && classifier.action == action) { classifier.UpdateReward(reward * learningRate); if (Random.value < mutationRate) MutateClassifier(classifier); break; } } } void MutateClassifier(Classifier classifier) { string[] actions = { "MoveForward", "TurnLeft", "TurnRight" }; classifier.action = actions[Random.Range(0, actions.Length)]; char[] chars = classifier.condition.ToCharArray(); int i = Random.Range(0, chars.Length); chars[i] = chars[i] == '0' ? '1' : '0'; classifier.condition = new string(chars); } string GetStateFromEnvironment() { Vector3 pos = agent.position; bool front = Physics.Raycast(pos, agent.forward, raycastDistance); bool left = Physics.Raycast(pos, -agent.right, raycastDistance); bool right = Physics.Raycast(pos, agent.right, raycastDistance); return (front ? "1" : "0") + (left ? "1" : "0") + (right ? "1" : "0"); } void PerformAction(string action) { Debug.Log($"State: {GetStateFromEnvironment()} | Action: {action}"); switch (action) { case "MoveForward": agent.Translate(Vector3.forward * moveSpeed * Time.fixedDeltaTime); break; case "TurnLeft": agent.Rotate(Vector3.up, -turnSpeed * Time.fixedDeltaTime); break; case "TurnRight": agent.Rotate(Vector3.up, turnSpeed * Time.fixedDeltaTime); break; } } float EvaluateAction(string prevState, string action, string newState) { // Recompensa por acercarse al objetivo float distanceReward = 0f; if (target != null) { float prevDistance = Vector3.Distance(lastPosition, target.position); float currentDistance = Vector3.Distance(agent.position, target.position); distanceReward = (prevDistance - currentDistance) * 2f; // Multiplicador para hacerlo más significativo } // Penalización por chocar if (prevState[0] == '1' && action == "MoveForward") distanceReward -= 1.0f; // Si no se movió, penalizar if (prevState == newState && action == "MoveForward") distanceReward -= 1.0f; // Buenas decisiones básicas if (prevState == "000" && action == "MoveForward") distanceReward += 1.0f; if (prevState == "100" && action == "TurnRight") distanceReward += 1.0f; if (prevState == "010" && action == "TurnLeft") distanceReward += 1.0f; if (prevState == "111" && (action == "TurnLeft" || action == "TurnRight")) distanceReward += 0.5f; return distanceReward; } // Dibujar los raycasts en el editor void OnDrawGizmos() { if (agent == null) return; Gizmos.color = raycastColor; Vector3 pos = agent.position; // Rayo frontal Gizmos.DrawLine(pos, pos + agent.forward * raycastDistance); // Rayo izquierdo Gizmos.DrawLine(pos, pos - agent.right * raycastDistance); // Rayo derecho Gizmos.DrawLine(pos, pos + agent.right * raycastDistance); } }