Skip to content

Instantly share code, notes, and snippets.

@Sh0cko
Last active November 11, 2025 20:58
Show Gist options
  • Select an option

  • Save Sh0cko/74098b7c15487fe3f5c9fac18c003bdf to your computer and use it in GitHub Desktop.

Select an option

Save Sh0cko/74098b7c15487fe3f5c9fac18c003bdf to your computer and use it in GitHub Desktop.

Contexto didáctico: Simulador de modos de juego con niveles de dificultad y estados del jugador

Joel Cuevas Estrada - 22210298

Contexto en situación realista

Un videojuego tipo Shooter o RPG, donde:

  • El jugador puede cambiar el modo de juego (por ejemplo: Fácil, Normal o Difícil).

  • Además, el jugador tiene estados dinámicos durante la partida (Normal, Envenenado, Enfurecido, Muerto).

  • Queremos que ambos comportamientos puedan cambiar en tiempo de ejecución, sin romper el código principal del juego.

Aplicación de los patrones

Strategy – “Estrategias de dificultad” El patrón Strategy nos permite cambiar el algoritmo de comportamiento sin modificar al jugador.

Por ejemplo:

  • En modo Fácil, los enemigos hacen menos daño.
  • En modo Difícil, los enemigos atacan más rápido y con más precisión.

Cada “modo de juego” es una estrategia diferente que puede intercambiarse en tiempo real.

Diagrama UML

UML-PDD2

Explicación de los beneficios del patrón,

  • Cambio de comportamiento en tiempo de ejecución:

  • Permite modificar cómo actúan los objetos sin alterar su estructura.

El jugador puede cambiar de modo o estado durante la ejecución del programa sin necesidad de reescribir código.

  • Bajo acoplamiento

Las estrategias (modos de juego) y los estados del jugador están desacoplados del contexto principal. Esto facilita mantener y ampliar el código sin afectar otras partes del sistema.

  • Cumplimiento del principio de abierto/cerrado (OCP)

Es posible agregar nuevos modos de juego o nuevos estados sin modificar las clases existentes, solo añadiendo nuevas implementaciones de las interfaces.

  • Reutilización de código

Cada comportamiento está encapsulado en su propia clase, lo que permite reutilizar y combinar comportamientos según las necesidades del contexto.

  • Mayor legibilidad y mantenimiento

Separar las responsabilidades en clases específicas evita código monolítico o condicionales extensos (if-else o switch), haciendo el sistema más claro y escalable.

  • Extensibilidad del sistema

Se pueden añadir fácilmente nuevas estrategias (como un modo “experto”) o estados adicionales (como “congelado” o “invencible”) sin alterar el código existente.

Ejemplo de mala práctica y propuesta de refactorización

Mala practica

#include <iostream>
#include <string>
using namespace std;

class Player {
private:
    string modo;   // Puede ser "facil", "normal", "dificil"
    string estado; // Puede ser "normal", "envenenado", "muerto"

public:
    Player() {
        modo = "facil";
        estado = "normal";
    }

    void cambiarModo(string nuevoModo) {
        modo = nuevoModo;
    }

    void cambiarEstado(string nuevoEstado) {
        estado = nuevoEstado;
    }

    void luchar() {
        if (modo == "facil") {
            cout << "[Modo Fácil] El enemigo ataca débilmente y con lentitud.\n";
        } else if (modo == "normal") {
            cout << "[Modo Normal] El enemigo ataca con fuerza moderada.\n";
        } else if (modo == "dificil") {
            cout << "[Modo Difícil] El enemigo ataca rápidamente y con gran daño.\n";
        } else {
            cout << "[Modo Desconocido] El enemigo no sabe qué hacer.\n";
        }
    }

    void moverse() {
        if (estado == "normal") {
            cout << "El jugador se mueve normalmente.\n";
        } else if (estado == "envenenado") {
            cout << "El jugador se mueve lentamente (envenenado).\n";
        } else if (estado == "muerto") {
            cout << "El jugador no puede moverse (muerto).\n";
        } else {
            cout << "El jugador está en un estado desconocido.\n";
        }
    }

    void mostrarEstado() {
        if (estado == "normal") {
            cout << "Estado: Saludable.\n";
        } else if (estado == "envenenado") {
            cout << "Estado: Envenenado, pierde salud lentamente.\n";
        } else if (estado == "muerto") {
            cout << "Estado: Muerto.\n";
        } else {
            cout << "Estado: Desconocido.\n";
        }
    }
};

int main() {
    Player jugador;

    cout << "=== Simulador de Juego (Código Espagueti) ===\n\n";

    jugador.mostrarEstado();
    jugador.luchar();
    jugador.moverse();

    cout << "\n--- Cambiando al modo Difícil ---\n";
    jugador.cambiarModo("dificil");
    jugador.luchar();

    cout << "\n--- El jugador ha sido envenenado ---\n";
    jugador.cambiarEstado("envenenado");
    jugador.mostrarEstado();
    jugador.moverse();

    cout << "\n--- El jugador muere ---\n";
    jugador.cambiarEstado("muerto");
    jugador.mostrarEstado();
    jugador.moverse();
    jugador.luchar(); // El jugador sigue luchando aunque está muerto

    cout << "\nFin de la simulación.\n";
    return 0;
}

Code Smells

Violación del SRP: La clase Player hace todo: maneja el estado, el modo y la lógica del juego.

Violación del OCP: Si se agrega un nuevo modo o estado, hay que modificar el código existente.

Falta de abstracción (violación del DIP): El código depende directamente de cadenas y condiciones, no de interfaces.

Complejidad condicional alta: Muchas estructuras if-else para controlar comportamientos.

Poca mantenibilidad y escalabilidad: Cada nuevo comportamiento aumenta el riesgo de errores.

Codigo refactorizado con Strategy para los modos de juego y State para los estados del jugador

#include <iostream>
#include <string>
using namespace std;

//
// ===== PATRÓN STRATEGY =====
// Permite cambiar el modo de juego (fácil, difícil, etc.) en tiempo de ejecución.
//

// Interfaz Strategy
class GameMode {
public:
    virtual void enemyAttack() = 0;
    virtual ~GameMode() {}
};

// Estrategias concretas
class EasyMode : public GameMode {
public:
    void enemyAttack() override {
        cout << "[Modo Fácil] El enemigo ataca débilmente y con lentitud.\n";
    }
};

class NormalMode : public GameMode {
public:
    void enemyAttack() override {
        cout << "[Modo Normal] El enemigo ataca con fuerza moderada.\n";
    }
};

class HardMode : public GameMode {
public:
    void enemyAttack() override {
        cout << "[Modo Difícil] El enemigo ataca rápidamente y con gran daño.\n";
    }
};


//
// ===== PATRÓN STATE =====
// Controla el estado del jugador (normal, envenenado, muerto, etc.)
//

// Interfaz State
class PlayerState {
public:
    virtual void move() = 0;
    virtual void status() = 0;
    virtual ~PlayerState() {}
};

// Estados concretos
class NormalState : public PlayerState {
public:
    void move() override { cout << "El jugador se mueve normalmente.\n"; }
    void status() override { cout << "Estado: Saludable.\n"; }
};

class PoisonedState : public PlayerState {
public:
    void move() override { cout << "El jugador se mueve lentamente (envenenado).\n"; }
    void status() override { cout << "Estado: Envenenado, pierde salud lentamente.\n"; }
};

class DeadState : public PlayerState {
public:
    void move() override { cout << "El jugador no puede moverse (muerto).\n"; }
    void status() override { cout << "Estado: Muerto.\n"; }
};


//
// ===== CLASE CONTEXTO =====
// Une ambos patrones: Strategy (modo de juego) + State (estado del jugador)
//

class Player {
private:
    GameMode* mode;       // Estrategia actual (modo de juego)
    PlayerState* state;   // Estado actual del jugador
public:
    Player(GameMode* m, PlayerState* s) : mode(m), state(s) {}

    void setMode(GameMode* m) { mode = m; }
    void setState(PlayerState* s) { state = s; }

    void fight() { mode->enemyAttack(); }
    void move() { state->move(); }
    void showStatus() { state->status(); }
};


//
// ===== FUNCIÓN PRINCIPAL =====
// Demostración didáctica: cambiar comportamientos en tiempo de ejecución.
//

int main() {
    // Creamos estrategias (modos de juego)
    EasyMode easy;
    NormalMode normal;
    HardMode hard;

    // Creamos estados posibles del jugador
    NormalState normalState;
    PoisonedState poisoned;
    DeadState dead;

    // Creamos jugador inicial con modo fácil y estado normal
    Player player(&easy, &normalState);

    cout << "=== Simulador de Juego: Strategy + State ===\n\n";

    player.showStatus();
    player.fight();
    player.move();

    cout << "\n--- Cambiando al modo Difícil ---\n";
    player.setMode(&hard);
    player.fight();

    cout << "\n--- El jugador ha sido envenenado ---\n";
    player.setState(&poisoned);
    player.showStatus();
    player.move();

    cout << "\n--- El jugador muere ---\n";
    player.setState(&dead);
    player.showStatus();
    player.move();
    player.fight(); // Aunque está muerto, el modo sigue activo

    cout << "\nFin de la simulación.\n";
    return 0;
}

Relación con los principios SOLID

La aplicación combinada de los patrones Strategy y State se relaciona directamente con varios principios SOLID, ya que ambos patrones promueven una arquitectura limpia, extensible y de bajo acoplamiento.

S – Single Responsibility Principle (SRP):

  • Cada clase tiene una única responsabilidad.
  • Las estrategias (modos de juego) solo definen cómo se comportan los enemigos.
  • Los estados del jugador solo controlan su comportamiento interno.
  • La clase Player se limita a coordinar ambos.
  • Esto evita clases con múltiples razones para cambiar.

O – Open/Closed Principle (OCP):

  • El sistema está abierto a la extensión pero cerrado a la modificación.
  • Se pueden agregar nuevos modos de juego o nuevos estados creando clases que implementen las interfaces existentes, sin modificar el código base.

L – Liskov Substitution Principle (LSP):

  • Las clases derivadas pueden sustituir a sus clases base sin alterar la funcionalidad del programa.
  • Cualquier GameMode (por ejemplo, EasyMode, HardMode) puede reemplazar a otra sin afectar al Player.
  • Lo mismo ocurre con PlayerState.

I – Interface Segregation Principle (ISP):

  • Las interfaces GameMode y PlayerState son específicas y pequeñas, cada una define solo los métodos necesarios para su función.
  • Esto evita interfaces grandes o con métodos innecesarios.

D – Dependency Inversion Principle (DIP):

  • El Player depende de abstracciones (GameMode, PlayerState), no de implementaciones concretas.
  • Esto permite cambiar fácilmente las estrategias o estados sin modificar la clase principal.
@IoTeacher
Copy link

Evaluación de 100

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment