Ulepszenie kodu gry RPG.

Ulepszenie kodu gry RPG.
CR
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 8
1

Napisałem prostą konsolową grę rpg typu Diablo, Path of Exiloe, Titan Quest. Potrzebuję porady jak ulepszyć ten kod? Czy wykorzystać coś z nowszych standardów i funkcji języka C++?
Program kompiluje tak:

Kopiuj
"code-runner.executorMap": {
  "cpp": "clear && cd $dir && mkdir -p bin && cd bin && clang++ -stdlib=libc++ -std=c++23 ../$fileName ../Enemy.cpp ../Player.cpp ../Print.cpp -o $fileNameWithoutExt && ./$fileNameWithoutExt"
}

Main.cpp

Kopiuj
#include "Enemy.h"
#include "Player.h"
#include "Print.h"
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <string>
#include <vector>

// Stany gry
enum class GameState {
    MainMenu,
    CharacterSelect,
    Playing,
    Exit
};

// Losuje liczbę przeciwników
int GetEnemyCount()
{
    int roll = rand() % 100;

    if (roll < 50) {
        return 1;
    }

    else if (roll < 75) {
        return 2;
    }

    else if (roll < 90) {
        return 3;
    }

    else {
        return 4;
    }
}

// Losuje typ przeciwnika
int GetEnemyType()
{
    return rand() % 4;
}

// Losuje poziom przeciwnika
int GetEnemyLevel()
{
    return 1 + rand() % 3;
}

Player player;

int main()
{
    srand(time(0));

    bool running = true;
    GameState state = GameState::MainMenu;

    while (running) {
        if (state == GameState::MainMenu) {
            Print::WriteColor("====UpiornePotyczki====\n\n", Color::Red);
            Print::Write("1. Nowa Gra\n");
            Print::Write("2. Wyjście\n");
            Print::WriteColor("\nWybierz opcję: ", Color::Yellow);

            int choice;
            std::cin >> choice;

            if (choice == 1) {
                state = GameState::CharacterSelect;
            }

            if (choice == 2) {
                state = GameState::Exit;
            }

            Print::Clear();
        }

        else if (state == GameState::CharacterSelect) {
            Print::WriteColor("Wybierz swoją klasę:\n\n", Color::Yellow);
            Print::Write("1. Wojownik\n");
            Print::Write("2. Łotrzyk\n");
            Print::Write("3. Mag\n");
            Print::WriteColor("\nWybierz opcję: ", Color::Yellow);
            int choice;
            std::cin >> choice;

            player.SetClass(choice);

            player.DisplayPickedCharacter();

            int continueChoice;
            std::cin >> continueChoice;

            Print::Clear();
            state = GameState::Playing;
        }

        else if (state == GameState::Playing) {
            std::vector<Enemy> enemies;

            int enemyCount = GetEnemyCount();

            // Tworzenie przeciwników
            for (int i = 0; i < enemyCount; i++) {
                Enemy enemy;

                int type = GetEnemyType();
                int level = GetEnemyLevel();

                enemy.createEnemy(type);
                enemy.SetLevel(level);

                enemies.push_back(enemy);
            }

            bool inCombat = true;

            // Pętla walki
            while (inCombat) {
                Print::Clear();
                Print::WriteColor("Pojawiają się przeciwnicy!\n\n", Color::Red);

                for (int i = 0; i < enemies.size(); i++) {
                    Print::Write("Przeciwnik " + std::to_string(i + 1) + ": ");
                    Print::WriteColor(enemies[i].name, Color::Yellow);
                    Print::Write(" Poziom. " + std::to_string(enemies[i].level));
                    Print::Write(" - Zdrowie: " + std::to_string(enemies[i].currentHealth) + "\n");
                }

                Print::Write("\nTwoje zdrowie: ");
                Print::WriteColor(std::to_string(player.currentHealth), Color::Green);
                Print::Write("\n");

                Print::Write("\n1. Atak\n");
                Print::Write("2. Ucieczka\n");
                Print::WriteColor("\nWybierz opcję: ", Color::Yellow);

                int choice;
                std::cin >> choice;

                if (choice == 1) {
                    Print::WriteColor("\nWybierz przeciwnika do ataku: ", Color::Yellow);

                    int targetChoice;
                    std::cin >> targetChoice;

                    int targetIndex = targetChoice - 1;

                    if (targetIndex >= 0 && targetIndex < enemies.size()) {
                        enemies[targetIndex].currentHealth -= player.damage;

                        Print::Write("\nUderzasz ");
                        Print::WriteColor(enemies[targetIndex].name, Color::Yellow);
                        Print::Write(" za " + std::to_string(player.damage) + " obrażeń!\n");

                        if (enemies[targetIndex].currentHealth <= 0) {
                            Print::WriteColor("\n" + enemies[targetIndex].name + " został pokonany!\n", Color::Green);
                            enemies.erase(enemies.begin() + targetIndex);
                        }

                        if (enemies.empty()) {
                            Print::WriteColor("\nWygrałeś walkę!\n", Color::Green);
                            inCombat = false;
                        }

                        else {
                            Print::Write("\nPrzeciwnicy atakują!\n");

                            for (int i = 0; i < enemies.size(); i++) {
                                player.currentHealth -= enemies[i].damage;

                                if (player.currentHealth < 0) {
                                    player.currentHealth = 0;
                                    inCombat = false;
                                    state = GameState::Exit;
                                    Print::WriteColor("\nZostałeś pokonany\n", Color::Red);
                                    break;
                                }

                                Print::WriteColor(enemies[i].name, Color::Red);
                                Print::Write(" uderza cię za " + std::to_string(enemies[i].damage) + " obrażeń!\n");

                                if (player.currentHealth <= 0) {
                                    Print::WriteColor("\nZostałeś pokonany..\n", Color::Red);
                                    inCombat = false;
                                    state = GameState::Exit;
                                    break;
                                }
                            }
                        }
                    }

                    else {
                        Print::Write("\nNieprawidłowy cel!\n");
                    }
                }

                else if (choice == 2) {
                    Print::WriteColor("\nUciekasz jak tchórz!\n", Color::Yellow);
                    inCombat = false;
                }

                else {
                    Print::Write("\nNieprawidłowy wybór!\n");
                }
            }

            state = GameState::Exit;
        }

        else if (state == GameState::Exit) {
            running = false;
        }
    }

    Print::WriteColor("\n************************************\n", Color::Magenta);
    Print::WriteColor("* Dzięki za grę w UpiornePotyczki! *\n", Color::Magenta);
    Print::WriteColor("************************************\n", Color::Magenta);
}

Player.cpp

Kopiuj
#include "Player.h"

// Ustawia klasę postaci na podstawie wyboru gracza
void Player::SetClass(int choice)
{
    name = "Bohater";

    if (choice == 1) {
        playerClass = "Wojownik";
        strength = 10;
        intelligence = 2;
        vitality = 8;
    }

    else if (choice == 2) {
        playerClass = "Łotrzyk";
        strength = 6;
        intelligence = 5;
        vitality = 6;
    }

    else if (choice == 3) {
        playerClass = "Mag";
        strength = 2;
        intelligence = 10;
        vitality = 5;
    }

    // Przelicz statystyki po wyborze klasy
    CalculateStats();
}

// Oblicza statystyki postaci na podstawie atrybutów
void Player::CalculateStats()
{
    int baseHealth = 50;
    int baseMana = 30;

    // Obliczanie zdrowia
    maxHealth = baseHealth + (vitality * 10);
    currentHealth = maxHealth;

    // Obliczanie many
    maxMana = baseMana + (intelligence * 10);
    currentMana = maxMana;

    // Obliczanie obrażeń w zależności od klasy
    if (playerClass == "Wojownik") {
        damage = strength * 2;
    }

    else if (playerClass == "Łotrzyk") {
        damage = strength * 2 + 2;
    }

    else if (playerClass == "Mag") {
        damage = intelligence * 2;
    }

    else {
        damage = 5;
    }
}

// Wyświetla wybraną postać i jej statystyki
void Player::DisplayPickedCharacter()
{
    Print::Clear();

    Print::Write("Wybrałeś ");
    Print::WriteColor(playerClass, Color::Cyan);
    Print::Write("!\n\n");

    Print::Write("Siła: ");
    Print::Write(std::to_string(strength) + "\n");

    Print::Write("Inteligencja: ");
    Print::Write(std::to_string(intelligence) + "\n");

    Print::Write("Witalność: ");
    Print::Write(std::to_string(vitality) + "\n");

    Print::Write("Zdrowie: ");
    Print::WriteColor(std::to_string(currentHealth), Color::Green);
    Print::Write("\n");

    Print::Write("Mana: ");
    Print::WriteColor(std::to_string(currentMana), Color::Cyan);
    Print::Write("\n");

    Print::Write("\nNaciśnij 1, aby kontynuować: ");
}

Print.cpp

Kopiuj
#include "Print.h"

// Wyświetla tekst bez zmiany koloru
void Print::Write(const std::string& text)
{
    std::print("{}", text);
}

// Ustawia kolor tekstu w konsoli
void Print::SetColor(Color color)
{
    switch (color) {
    case Color::Red:
        std::print("\033[31m");
        break;
    case Color::Green:
        std::print("\033[32m");
        break;
    case Color::Yellow:
        std::print("\033[33m");
        break;
    case Color::Cyan:
        std::print("\033[36m");
        break;
    case Color::Blue:
        std::print("\033[34m");
        break;
    case Color::Magenta:
        std::print("\033[35m");
        break;
    case Color::White:
        std::print("\033[37m");
        break;
    case Color::Black:
        std::print("\033[30m");
        break;
    case Color::Default:
    default:
        std::print("\033[0m"); // reset do domyślnego koloru
        break;
    }
}

// Wyświetla tekst w wybranym kolorze
void Print::WriteColor(const std::string& text, Color color)
{
    SetColor(color);
    std::print("{}", text);
    SetColor(Color::Default); // przywraca domyślny kolor
}

// Czyści ekran konsoli
void Print::Clear()
{
    system("clear"); // działa głównie na Linux/Mac
}

Enemy.cpp

Kopiuj
#include "Enemy.h"

// Funkcja tworząca przeciwnika na podstawie typu
void Enemy::createEnemy(int type)
{
    if (type == 0) {
        name = "Zombiak";
        maxHealth = 50;
        damage = 5;
    }

    else if (type == 1) {
        name = "Szkieletor";
        maxHealth = 35;
        damage = 8;
    }

    else if (type == 2) {
        name = "Goblinek";
        maxHealth = 30;
        damage = 10;
    }

    else {
        // Domyślny przeciwnik, jeśli typ jest nieznany
        name = "Zombiak";
        maxHealth = 50;
        damage = 5;
    }

    // Ustaw aktualne zdrowie na maksymalne
    currentHealth = maxHealth;
}

// Funkcja ustawiająca poziom przeciwnika
void Enemy::SetLevel(int enemyLevel)
{
    level = enemyLevel;

    maxHealth += (level - 1) * 10;
    damage += (level - 1) * 2;

    // Po zmianie poziomu przeciwnik odzyskuje pełne zdrowie
    currentHealth = maxHealth;
}

Player.h

Kopiuj
#pragma once
#include <print>
#include "Print.h"

// Klasa reprezentująca gracza
class Player {
public:
    // Imię postaci
    std::string name;

    // Klasa postaci (np. Wojownik, Łotrzyk, Mag)
    std::string playerClass;

    // Podstawowe atrybuty
    int strength = 0;
    int intelligence = 0;
    int vitality = 0;

    // Zdrowie
    int maxHealth = 0;
    int currentHealth = 0;

    // Mana
    int maxMana = 0;
    int currentMana = 0;

    // Obrażenia zadawane przez gracza
    int damage = 0;

    // Ustawia klasę postaci na podstawie wyboru
    void SetClass(int choice);

    // Oblicza statystyki na podstawie atrybutów
    void CalculateStats();

    // Wyświetla informacje o wybranej postaci
    void DisplayPickedCharacter();
};

Print.h

Kopiuj
#pragma once
#include <print>

// Enum reprezentujący kolory tekstu w konsoli
enum class Color {
    Default, // domyślny kolor
    Red,
    Green,
    Yellow,
    Cyan,
    Blue,
    Magenta,
    White,
    Black
};

// Klasa odpowiedzialna za wyświetlanie tekstu w konsoli
class Print {
    public:

    // Wyświetla tekst w konsoli
    static void Write(const std::string& text);

    // Ustawia kolor tekstu
    static void SetColor(Color color);

    // Wyświetla tekst w wybranym kolorze
    static void WriteColor(const std::string& text, Color color);
    
    // Czyści ekran konsoli
    static void Clear();
};

Enemy.h

Kopiuj
#pragma once
#include <print>

// Klasa reprezentująca przeciwnika w grze
class Enemy {
public:
    // Nazwa przeciwnika
    std::string name;

     // Maksymalne zdrowie przeciwnika
    int maxHealth = 0;

    // Aktualne zdrowie przeciwnika
    int currentHealth = 0;

    // Poziom przeciwnika (domyślnie 1)
    int level = 1;

    // Obrażenia zadawane przez przeciwnika
    int damage = 0;

    // Tworzy przeciwnika na podstawie typu (np. 0 = Zombie, 1 = Szkielet, 2 = Goblin)
    void createEnemy(int type);

    // Ustawia poziom przeciwnika i skaluje jego statystyki
    void SetLevel(int enemyLevel);
};
SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1116
0

Spróbuj EJAJA. claude wrzucił mi takie review

Kopiuj
  Correctness Bugs                                                                                                                                    
  - No input validation — If std::cin >> choice receives non-numeric input, the stream enters a fail state and the game infinite-loops. Every cin >> call is vulnerable to this.
  - Invalid class choice not handled (Player.cpp:4-31) — If the user enters anything other than 1/2/3 in SetClass, all stats remain 0, playerClass is empty, and CalculateStats sets damage = 5 from the fallback. The game continues silently with a broken character.                     
  - Duplicate death check (Main.cpp:176-192) — Player death is checked twice: < 0 on line 176 and <= 0 on line 187. The first check sets health to 0 and breaks, but skips the "X hits you" message. The second check is unreachable after the first fires. The < 0 check should be <= 0 and
   the second check removed.                                                                                                                                                                                                                                                                
  - Game always exits after one fight (Main.cpp:212) — state = GameState::Exit is unconditional after combat, so the game ends after a single encounter. This seems unintentional for an RPG.                                                                                               

  ---                                                       
  Design & Code Quality
  - Global player variable (Main.cpp:52) — Should be local to main().
  - rand()/srand() usage — These are legacy C. Prefer <random> (std::mt19937 + distributions) for better randomness and no modulo bias.                                                                                                                                                     
  - Magic numbers everywhere — Enemy types (0-3), class choices (1-3), stat values, and probability thresholds are all hardcoded ints. Enums or constants would make this far more readable and maintainable.                                                                               
  - Naming inconsistency — createEnemy (camelCase) vs SetLevel (PascalCase) in the same class. Pick one convention.                                                                                                                                                                         
  - All members public — Both Player and Enemy expose everything. The combat loop directly mutates currentHealth and reads damage. Consider encapsulation if this project grows.                                                                                                            
  - system("clear") in Print.cpp:55 — Platform-dependent and invokes a shell (minor security concern). ANSI escape \033[2J\033[H is more portable and safer.                                                                                                                                
  - Monolithic main() — The 160-line main function handles menus, character creation, combat loop, enemy spawning, and UI. Extracting these into functions would improve readability significantly.                                                                                         
tBane
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Poznań
  • Postów: 566
0

Skoro stosujesz enum, to można go też użyć do enemy type, było by czytelniej :-)

Kopiuj
enum class GameState {
    MainMenu,
    CharacterSelect,
    Playing,
    Exit
};
Kopiuj
enum class EnemyType {
    Wojownik,
    Łotrzyk,
    Mag,
};

do Player.cpp trzeba dodać nagłówke string

Kopiuj
#include <string>

poza tym skoro już tak dobrze znasz C++ to może warto, by było zainteresować się grafiką? :-)

CR
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 8
0

Nie trzeba ponieważ #include <print> zawiera już string. Jak mój kompilator będzie wspierał import std; to go zastosuję.
Tak się zastanawiam czy nie przepisać tej gry na inny język systemowy aby sobie potrenować kodowanie w innym języku.
Która składnia jest najbardziej czytelna?

main.rust

Kopiuj
use std::time::Duration;
use cellophane::{Animation, Animator, Frame, Cell};
use cellophane::crossterm::style::Color;

struct Rainbow {
    tick: usize,
    rows: usize,
    cols: usize,
}

impl Animation for Rainbow {
    fn init(&mut self, initial: Frame) {
        let (rows, cols) = initial.dims().unwrap_or((0, 0));
        self.rows = rows;
        self.cols = cols;
    }

    fn update(&mut self, _dt: Duration) -> Frame {
        let mut frame = Frame::with_capacity(self.cols, self.rows);
        for row in 0..self.rows {
            for col in 0..self.cols {
                let hue = ((col + row + self.tick) % 256) as u8;
                if let Some(cell) = frame.get_cell_mut(row, col) {
                    *cell = Cell::default()
                        .with_bg(Color::Rgb { r: hue, g: 100, b: 255 - hue });
                }
            }
        }
        self.tick += 1;
        frame
    }

    fn is_done(&self) -> bool { false }
    fn resize(&mut self, w: usize, h: usize) {
        self.cols = w;
        self.rows = h;
    }
}

fn main() -> std::io::Result<()> {
    let anim = Box::new(Rainbow { tick: 0, rows: 0, cols: 0 });
    let mut animator = Animator::enter_with(anim)?;
    loop {
        match animator.tick() {
            Ok(true) => continue,
            Ok(false) => break,
            Err(e) if e.kind() == std::io::ErrorKind::Interrupted => break,
            Err(e) => return Err(e),
        }
    }
    Ok(())
}

main.zig

Kopiuj
const std = @import("std");
const cellophane = @import("cellophane"); // Assuming cellophane Zig library exists

const Duration = std.time.Duration;

const Animator = cellophane.Animator;
const Frame = cellophane.Frame;
const Cell = cellophane.Cell;
const Color = cellophane.crossterm.style.Color;

const Rainbow = struct {
    tick: usize,
    rows: usize,
    cols: usize,

    pub fn init(self: *Rainbow, initial: *Frame) void {
        const dims = initial.dims() orelse (0, 0);
        self.rows = dims[0];
        self.cols = dims[1];
    }

    pub fn update(self: *Rainbow, _dt: Duration) Frame {
        var frame = Frame.with_capacity(self.cols, self.rows);
        for (row: usize) in 0..self.rows {
            for (col: usize) in 0..self.cols {
                const hue = u8(((col + row + self.tick) % 256));
                const cell = frame.get_cell_mut(row, col) orelse null;
                if (cell != null) {
                    *cell = Cell.default().with_bg(Color.Rgb{ r: hue, g: 100, b: 255 - hue });
                }
            }
        }
        self.tick += 1;
        return frame;
    }

    pub fn is_done(self: *Rainbow) bool {
        return false;
    }

    pub fn resize(self: *Rainbow, w: usize, h: usize) void {
        self.cols = w;
        self.rows = h;
    }
};

pub fn main() !void {
    const anim = Rainbow{ .tick = 0, .rows = 0, .cols = 0 };
    var animator = Animator.enter_with(anim) catch return null;

    while (true) {
        const result = animator.tick();
        switch (result) {
            true => continue,
            false => break,
            null => break,
        }
    }
}

main.cpp

Kopiuj
#include <iostream>
#include <vector>
#include <chrono>
#include <thread>

using namespace std::chrono_literals;

// Simple Color structure to represent RGB values
struct Color {
    uint8_t r, g, b;
};

// Cell structure representing each "pixel" of the animation
struct Cell {
    Color bg;

    static Cell default_cell() {
        return Cell{ Color{0, 0, 0} };
    }

    void with_bg(const Color& color) {
        bg = color;
    }
};

// Frame structure to represent a grid of Cells
struct Frame {
    std::vector<std::vector<Cell>> cells;

    Frame(size_t width, size_t height) : cells(height, std::vector<Cell>(width)) {}

    Cell& get_cell_mut(size_t row, size_t col) {
        return cells[row][col];
    }

    size_t width() const {
        return cells.empty() ? 0 : cells[0].size();
    }

    size_t height() const {
        return cells.size();
    }
};

// Animator that controls the animation loop
class Animator {
public:
    virtual void init(Frame& initial) = 0;
    virtual Frame update(std::chrono::milliseconds dt) = 0;
    virtual bool is_done() const = 0;
    virtual void resize(size_t width, size_t height) = 0;
    virtual ~Animator() = default;
};

// Rainbow animation implementation
class Rainbow : public Animator {
private:
    size_t tick = 0;
    size_t rows = 0;
    size_t cols = 0;

public:
    void init(Frame& initial) override {
        rows = initial.height();
        cols = initial.width();
    }

    Frame update(std::chrono::milliseconds dt) override {
        Frame frame(cols, rows);
        for (size_t row = 0; row < rows; ++row) {
            for (size_t col = 0; col < cols; ++col) {
                uint8_t hue = static_cast<uint8_t>((col + row + tick) % 256);
                Color color{hue, 100, static_cast<uint8_t>(255 - hue)};
                frame.get_cell_mut(row, col).with_bg(color);
            }
        }
        ++tick;
        return frame;
    }

    bool is_done() const override {
        return false;
    }

    void resize(size_t width, size_t height) override {
        cols = width;
        rows = height;
    }
};

// Utility function to print frame for debugging (displaying as RGB values)
void print_frame(const Frame& frame) {
    for (const auto& row : frame.cells) {
        for (const auto& cell : row) {
            const Color& color = cell.bg;
            std::printf("rgb(%d, %d, %d) ", color.r, color.g, color.b);
        }
        std::cout << '\n';
    }
}

int main() {
    Rainbow anim;
    Frame initial_frame(10, 5);  // 10 columns, 5 rows
    anim.init(initial_frame);

    while (true) {
        auto frame = anim.update(0ms);  // No actual time-based logic here, just illustrative
        print_frame(frame);

        if (anim.is_done()) {
            break;
        }

        std::this_thread::sleep_for(100ms);  // Delay between frames
    }

    return 0;
}

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.