Skip to content

Added Text Based RPG Game in Python folder#123

Merged
x0lg0n merged 1 commit intox0lg0n:mainfrom
Arni005:add-text_based_RPG_game
Oct 27, 2025
Merged

Added Text Based RPG Game in Python folder#123
x0lg0n merged 1 commit intox0lg0n:mainfrom
Arni005:add-text_based_RPG_game

Conversation

@Arni005
Copy link
Contributor

@Arni005 Arni005 commented Oct 26, 2025

Added a terminal-based Role-Playing Game built entirely in Python with no external dependencies. This game features character classes, turn-based combat, level progression, inventory management, and a complete save/load system.

Summary by Sourcery

Add a complete terminal-based RPG game in Python featuring character classes, turn-based combat, exploration, inventory management, and game persistence.

New Features:

  • Introduce character creation with Warrior, Mage, and Thief classes each having unique stats and special abilities
  • Implement turn-based combat including attacks, critical hits, dodge mechanics, class-specific specials, item use, and fleeing
  • Add experience-based leveling with stat upgrades, full heal on level-up, and random loot drops after defeating enemies
  • Include exploration and rest mechanics with random events (treasure, traps, ambushes) and dynamic enemy encounters
  • Provide inventory management with health potions and JSON-based save/load functionality to persist game state

Summary by CodeRabbit

  • New Features
    • A complete text-based RPG game with three playable character classes
    • Turn-based combat system with special abilities and item usage
    • Character progression through leveling with class-specific stat growth
    • Inventory management with consumable items
    • Random events during exploration and rest
    • Game save/load functionality to persist progress
    • Enemy scaling and rewarding system with gold and experience

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 26, 2025

Reviewer's Guide

This PR introduces a standalone terminal-based text RPG in Python by adding a single script that defines character and enemy classes, a turn-based combat engine with special abilities and item management, a main game loop with exploration and random events, and a JSON-based save/load system.

Sequence diagram for save/load game process

sequenceDiagram
    actor User
    participant "main()"
    participant "save_game(player)"
    participant "load_game()"
    participant "rpg_save.json"
    User->>main(): Choose to save or load game
    main->>save_game: Save game (on user request)
    save_game->>rpg_save.json: Write player data
    rpg_save.json-->>save_game: Confirm save
    save_game-->>main: Save complete
    main->>load_game: Load game (on user request)
    load_game->>rpg_save.json: Read player data
    rpg_save.json-->>load_game: Return data
    load_game-->>main: Player loaded
Loading

Sequence diagram for turn-based combat

sequenceDiagram
    actor User
    participant combat
    participant Player
    participant Enemy
    User->>combat: Choose action (attack/special/item/flee)
    combat->>Player: Execute chosen action
    Player->>Enemy: Attack or use special ability
    Enemy->>Player: Perform special ability or attack
    combat->>User: Show combat results
    alt Player wins
        combat->>Player: Award gold/XP, check level up
    else Player loses
        combat->>User: Show defeat message
    end
Loading

Class diagram for Player, Enemy, and CharacterClass

classDiagram
    class CharacterClass {
        <<enum>>
        WARRIOR
        MAGE
        THIEF
    }
    class Player {
        - name: str
        - character_class: CharacterClass
        - level: int
        - experience: int
        - gold: int
        - inventory: list[str]
        - max_health: int
        - health: int
        - attack: int
        - defense: int
        - dodge_chance: float
        - special_charges: int
        + is_alive(): bool
        + level_up(): bool
        + use_special_ability(enemy: Enemy): bool
        + use_item(item_index: int): bool
    }
    class Enemy {
        - name: str
        - level: int
        - max_health: int
        - health: int
        - attack: int
        - defense: int
        - gold_reward: int
        - exp_reward: int
        - special_ability: str|None
        + is_alive(): bool
        + perform_special_ability(player: Player): bool
    }
    Player --> CharacterClass
    Enemy --> Player
Loading

File-Level Changes

Change Details Files
Define player model with class‐based stats, leveling, abilities, and inventory
  • Add CharacterClass enum and Player constructor with base stats mapping
  • Implement level_up logic with class‐specific stat growth and special charge gains
  • Provide use_special_ability and use_item methods for in-combat actions
Python/text_based_RPG_game.py
Implement enemy model with dynamic scaling and random special skills
  • Add Enemy constructor with non-linear health, attack, defense scaling by level
  • Assign random special_ability per enemy and perform_special_ability method
  • Define gold and experience reward calculation
Python/text_based_RPG_game.py
Build turn-based combat engine with attack mechanics
  • Create attack_target function handling dodge, criticals, damage calculation
  • Develop combat loop prompting player choices (attack, special, item, flee)
  • Implement victory_rewards to grant gold, XP, items and trigger level up
Python/text_based_RPG_game.py
Add game loop, exploration, random events, and UI helpers
  • Implement main function with menu for explore, rest, item use, save, quit
  • Add random_event function for map events affecting player state
  • Provide display_stats, get_combat_choice, use_item_in_combat, create_character utilities
Python/text_based_RPG_game.py
Enable game persistence via JSON save and load
  • Add save_game writing player state to rpg_save.json
  • Implement load_game reading JSON and reconstructing Player instance
  • Handle file not found and error cases with user feedback
Python/text_based_RPG_game.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Oct 26, 2025

Walkthrough

A self-contained text-based RPG game is added with character creation, level-based progression, turn-based combat, inventory management, random exploration events, JSON-based save/load functionality, and a main game loop supporting exploration, resting, combat, and item usage.

Changes

Cohort / File(s) Summary
Text-based RPG Game
Python/text_based_RPG_game.py
New file implementing a complete RPG system with: Player and Enemy classes featuring stats, leveling, and combat; turn-based combat loop with Attack, Special Ability, Use Item, and Flee actions; random event system for exploration and resting; inventory and item usage mechanics; save/load via JSON; character creation flow; and main game loop.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Game as Main Loop
    participant Explore as Exploration
    participant Combat as Combat System
    participant Event as Random Events
    participant Save as Save/Load

    User->>Game: Start Game
    Game->>Save: Load existing or create new character
    Save-->>Game: Player instance
    
    loop Main Loop
        Game->>User: Display menu (Explore/Rest/Use Item/Save/Quit)
        User->>Game: Select action
        
        alt Explore
            Game->>Explore: Roll for event/enemy
            alt Random Event
                Explore->>Event: Trigger event
                Event-->>Game: Effect applied
            else Combat
                Explore->>Combat: Initialize with generated enemy
                
                loop Combat Turn
                    Combat->>User: Display options
                    User->>Combat: Choose action
                    
                    alt Attack/Special
                        Combat->>Combat: Calculate damage
                        Combat-->>User: Show result
                    else Use Item
                        Combat->>Combat: Apply item effect
                    else Flee
                        Combat-->>Game: Return to explore
                    end
                    
                    Combat->>Combat: Enemy counter-attack
                    Combat-->>User: Show counter damage
                    
                    alt Player/Enemy defeated
                        Combat-->>Game: End combat (Victory/Defeat)
                    end
                end
                
                Game->>Game: Process rewards & level-up if victory
            end
        else Rest
            Game->>Event: Trigger rest event
            Event-->>Game: Effect applied
        else Save
            Game->>Save: Serialize player state to JSON
        else Quit
            Game-->>User: Exit game
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Combat system logic: Verify damage calculations, critical hit mechanics, special ability balancing, and turn alternation are implemented correctly
  • Class-based progression: Confirm character class stats and growth multipliers are consistent and properly applied during level-ups
  • State persistence: Review save/load JSON serialization, error handling, and edge cases (missing file, corrupted data)
  • Random event system: Validate event variety, probability distributions, and effect application logic
  • Player action validation: Ensure all user inputs are properly validated and handled in combat and exploration contexts

Poem

🐰✨ A new quest unfolds in code so bright,

Warriors clash and Mages cast their might,

Through dungeons deep and treasures bright,

Save your progress through the night!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Added Text Based RPG Game in Python folder" directly and accurately reflects the primary change in this pull request. The changeset introduces a complete text-based RPG game implementation in the Python/text_based_RPG_game.py file, which is exactly what the title conveys. The title is specific, clear, and concise—it identifies both the type of addition (a text-based RPG game) and its location (Python folder), avoiding vague terminology or noise. A teammate scanning the repository history would immediately understand that this PR adds a new game implementation to the Python directory.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Insecure file handling

Description: The save/load feature uses a fixed filename 'rpg_save.json' in the working directory
without path validation or file permission controls, which can be abused for unintended
file overwrite or reading if the working directory is untrusted or shared.
text_based_RPG_game.py [341-370]

Referred Code
    with open('rpg_save.json', 'w') as f:
        json.dump(data, f)
    print("💾 Game saved successfully!")

def load_game():
    """Load game from file"""
    try:
        with open('rpg_save.json', 'r') as f:
            data = json.load(f)

        player = Player(data['name'], CharacterClass(data['character_class']))
        player.level = data['level']
        player.experience = data['experience']
        player.gold = data['gold']
        player.inventory = data['inventory']
        player.health = data['health']
        player.max_health = data['max_health']
        player.attack = data['attack']
        player.defense = data['defense']
        player.dodge_chance = data['dodge_chance']
        player.special_charges = data['special_charges']


 ... (clipped 9 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The file currently mixes game logic, I/O prompts, and data definitions in one 475-line script—consider splitting into modules (e.g., combat engine, UI layer, persistence) for better organization and testability.
  • There’s a lot of duplicated patterns in special abilities, combat flow, and event handling—extract shared behavior into helper functions or strategy classes to reduce repetition.
  • Hard-coded constants (enemy names, item tables, balance numbers) could be moved into a config or constants module to make future balancing and tweaking easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The file currently mixes game logic, I/O prompts, and data definitions in one 475-line script—consider splitting into modules (e.g., combat engine, UI layer, persistence) for better organization and testability.
- There’s a lot of duplicated patterns in special abilities, combat flow, and event handling—extract shared behavior into helper functions or strategy classes to reduce repetition.
- Hard-coded constants (enemy names, item tables, balance numbers) could be moved into a config or constants module to make future balancing and tweaking easier.

## Individual Comments

### Comment 1
<location> `Python/text_based_RPG_game.py:435` </location>
<code_context>
+            if random.random() < 0.3:  # 30% chance for random event
+                random_event(player)
+            else:
+                enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
+                if not combat(player, enemy):
+                    break
</code_context>

<issue_to_address>
**issue:** Enemy level can be less than 1, which may cause unintended behavior.

random.randint(-1, 2) can produce enemy levels below 1, which may not be supported by the Enemy class. Please ensure enemy level is at least 1.
</issue_to_address>

### Comment 2
<location> `Python/text_based_RPG_game.py:345-354` </location>
<code_context>
+    def load_game():
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Generic exception handling may mask specific errors during game load.

Consider handling specific exceptions like JSONDecodeError to give clearer feedback and prevent hiding underlying issues.

Suggested implementation:

```python
def load_game():
    """Load game from file"""
    try:
        with open('rpg_save.json', 'r') as f:
            data = json.load(f)

        player = Player(data['name'], CharacterClass(data['character_class']))
        player.level = data['level']
        player.experience = data['experience']
        player.gold = data['gold']
        player.inventory = data['inventory']
    except json.JSONDecodeError:
        print("❌ Failed to load game: Save file is corrupted or not valid JSON.")
        return None
    except FileNotFoundError:
        print("❌ Save file not found. Start a new game first.")
        return None
    except Exception as e:
        print(f"❌ An unexpected error occurred while loading the game: {e}")
        return None

```

If `json` is not already imported at the top of the file, add:
```python
import json
```
Also, ensure that the function returns the `player` object at the end if loading is successful.
</issue_to_address>

### Comment 3
<location> `Python/text_based_RPG_game.py:402-411` </location>
<code_context>
+    def main():
</code_context>

<issue_to_address>
**suggestion:** Game loop does not handle KeyboardInterrupt gracefully.

Wrap the main loop in a try/except block to catch KeyboardInterrupt and exit cleanly, optionally prompting the user to save before quitting.

Suggested implementation:

```python
def main():
    """Main game loop"""
    print("🐉 Welcome to Enhanced Python RPG! 🐉")
    try:
        # Load or create character
        player = None
        if os.path.exists('rpg_save.json'):
            load_choice = input("Load saved game? (y/n): ").strip().lower()
            if load_choice == 'y':
                player = load_game()
        # ... rest of the main loop logic goes here ...
    except KeyboardInterrupt:
        print("\n⚠️  Game interrupted by user (Ctrl+C).")
        save_choice = input("Would you like to save your progress before quitting? (y/n): ").strip().lower()
        if save_choice == 'y':
            if player is not None:
                save_game(player)
                print("💾 Game saved successfully.")
            else:
                print("No game in progress to save.")
        print("👋 Goodbye!")
        sys.exit(0)

```

- Ensure that `save_game` and `player` are accessible in this scope.
- Make sure to import `sys` at the top of the file if not already present: `import sys`
- If the main loop logic is not yet inside the try block, move it there.
</issue_to_address>

### Comment 4
<location> `Python/text_based_RPG_game.py:80` </location>
<code_context>
+            return True
+        return False
+    
+    def use_special_ability(self, enemy):
+        """Class-specific special abilities"""
+        if self.special_charges <= 0:
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring special ability logic and the combat loop using dispatch maps and a CombatEngine class to flatten nesting and reduce duplication.

Here are three small focused refactor ideas that keep all of your new functionality but drastically flatten nesting, remove duplication, and split responsibilities.

1) **Consolidate Player special‐ability logic via a dispatch map**  
   Move each class’s special into its own private method, then call via a simple dict instead of repeating `if/elif`:

```python
# inside Player:
def use_special_ability(self, enemy):
    if self.special_charges <= 0:
        print("❌ No special charges remaining!")
        return False
    self.special_charges -= 1

    # map class ⇒ handler method
    handlers = {
        CharacterClass.WARRIOR: self._warrior_strike,
        CharacterClass.MAGE:    self._mage_fireball,
        CharacterClass.THIEF:   self._thief_poison,
    }
    handlers[self.character_class](enemy)
    return True

def _warrior_strike(self, enemy):
    dmg = int(self.attack * 1.8 - enemy.defense * 0.5)
    enemy.health -= max(1, dmg)
    print(f"💥 WARRIOR'S MIGHT! You strike for {dmg} damage!")

def _mage_fireball(self, enemy):
    dmg = self.attack + random.randint(5, 15)
    enemy.health -= dmg
    print(f"🔥 FIREBALL! You burn for {dmg} damage!")

def _thief_poison(self, enemy):
    base = self.attack // 2
    extra = base // 2
    enemy.health -= (base + extra)
    print(f"☠️ POISON! You deal {base}+{extra} damage!")
```

2) **Extract Enemy special abilities into a lookup**  
   Same idea for enemies—no more long `if/elif` chains:

```python
# at module top:
ENEMY_SPECIALS = {
    "double_attack": lambda e, p: (
        setattr(p, 'health', p.health - max(1, e.attack - p.defense + random.randint(-2,2))),
        print(f"{e.name} DOUBLE STRIKE!")
    ),
    "heal": lambda e, p: (
        setattr(e, 'health', min(e.max_health, e.health + e.max_health//4)),
        print(f"💚 {e.name} heals!")
    ),
    "poison": lambda e, p: (
        setattr(p, 'health', p.health - e.attack//3),
        print(f"☠️ {e.name} poisons you!")
    ),
}

class Enemy:
    def perform_special_ability(self, player):
        if not self.special_ability or random.random() > 0.3:
            return False
        ENEMY_SPECIALS[self.special_ability](self, player)
        return True
```

3) **Encapsulate the combat loop in a CombatEngine class**  
   Break out the 30‐line nested loop into clear `player_turn()` / `enemy_turn()` methods:

```python
class CombatEngine:
    def __init__(self, player, enemy):
        self.p, self.e = player, enemy

    def player_turn(self):
        choice = get_combat_choice(self.p)
        {
            '1': lambda: self._attack(self.p, self.e),
            '2': lambda: self.p.use_special_ability(self.e),
            '3': lambda: use_item_in_combat(self.p),
            '4': lambda: self._try_flee()
        }[choice]()

    def enemy_turn(self):
        if not self.e.perform_special_ability(self.p):
            damage = attack_target(self.e, self.p)
            print(f"💢 {self.e.name} deals {damage} damage!")

    def start(self):
        turn = 0
        while self.p.is_alive() and self.e.is_alive():
            turn += 1
            print(f"\n— Turn {turn}")
            self.player_turn()
            if not self.e.is_alive(): break
            print(f"\n{self.e.name}'s turn...")
            time.sleep(1)
            self.enemy_turn()
        return self.p.is_alive()

# then in combat():
def combat(player, enemy):
    print(f"\n⚔️ A wild {enemy.name} appears!")
    engine = CombatEngine(player, enemy)
    if engine.start():
        victory_rewards(player, enemy)
        return True
    print("💀 You have been defeated…")
    return False
```

By pulling out these three pieces:

- you remove deep nesting in `combat()`
- you eliminate duplicated `if/elif` blocks
- you isolate event/ability logic into self-contained units

You can do the same for `random_event` (e.g. an `Event` base class + subclasses) or even move each into its own file without touching any existing game behavior.
</issue_to_address>

### Comment 5
<location> `Python/text_based_RPG_game.py:375-377` </location>
<code_context>
    name = input("Enter your character's name: ").strip()
    if not name:
        name = "Hero"

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use `or` for providing a fallback value ([`use-or-for-fallback`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/use-or-for-fallback))

```suggestion
    name = input("Enter your character's name: ").strip() or "Hero"

```

<br/><details><summary>Explanation</summary>Thanks to the flexibility of Python's `or` operator, you can use a single
assignment statement, even if a variable can retrieve its value from various
sources. This is shorter and easier to read than using multiple assignments with
`if not` conditions.
</details>
</issue_to_address>

### Comment 6
<location> `Python/text_based_RPG_game.py:54` </location>
<code_context>
    def level_up(self):
        levels_gained = 0
        while self.experience >= self.level * XP_MULTIPLIER:
            self.experience -= self.level * XP_MULTIPLIER
            self.level += 1
            levels_gained += 1

            # Stat increases based on class
            if self.character_class == CharacterClass.WARRIOR:
                self.max_health += 20
                self.attack += 4
                self.defense += 3
            elif self.character_class == CharacterClass.MAGE:
                self.max_health += 10
                self.attack += 6
                self.defense += 1
            else:  # THIEF
                self.max_health += 15
                self.attack += 3
                self.defense += 2
                self.dodge_chance = min(0.4, self.dodge_chance + 0.02)

            self.health = self.max_health  # Full heal on level up
            self.special_charges += 1

        if levels_gained > 0:
            print(f"🎉 {self.name} leveled up to level {self.level}!")
            print(f"❤️  Max Health: {self.max_health}")
            print(f"⚔️  Attack: {self.attack}")
            print(f"🛡️  Defense: {self.defense}")
            if self.character_class == CharacterClass.THIEF:
                print(f"🌀 Dodge Chance: {self.dodge_chance:.1%}")
            return True
        return False

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Extract duplicate code into method ([`extract-duplicate-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-duplicate-method/))
- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))
- Extract code out into method ([`extract-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-method/))
</issue_to_address>

### Comment 7
<location> `Python/text_based_RPG_game.py:153` </location>
<code_context>
    def perform_special_ability(self, player):
        """Enemy special abilities"""
        if not self.special_ability or random.random() > 0.3:  # 30% chance to use special
            return False

        if self.special_ability == "double_attack":
            damage = max(1, self.attack - player.defense + random.randint(-2, 2))
            player.health -= damage
            print(f"⚡ {self.name} uses DOUBLE STRIKE! Deals {damage} damage!")
            return True

        elif self.special_ability == "heal":
            heal_amount = self.max_health // 4
            self.health = min(self.max_health, self.health + heal_amount)
            print(f"💚 {self.name} heals for {heal_amount} HP!")
            return True

        elif self.special_ability == "poison":
            poison_damage = self.attack // 3
            player.health -= poison_damage
            print(f"☠️  {self.name} poisons you for {poison_damage} damage!")
            return True

        return False

</code_context>

<issue_to_address>
**issue (code-quality):** Extract duplicate code into method ([`extract-duplicate-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-duplicate-method/))
</issue_to_address>

### Comment 8
<location> `Python/text_based_RPG_game.py:196` </location>
<code_context>
def attack_target(attacker, defender, is_critical=False):
    """Handle attack between two entities"""
    if random.random() < getattr(defender, 'dodge_chance', 0):
        print(f"💨 {defender.name} dodged the attack!")
        return 0

    base_damage = attacker.attack - defender.defense + random.randint(-2, 3)
    damage = max(1, base_damage)

    if is_critical or random.random() < 0.1:  # 10% critical chance
        damage = int(damage * CRITICAL_MULTIPLIER)
        print(f"💥 CRITICAL HIT! ", end="")

    defender.health -= damage
    return damage

</code_context>

<issue_to_address>
**issue (code-quality):** Replace f-string with no interpolated values with string ([`remove-redundant-fstring`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-redundant-fstring/))
</issue_to_address>

### Comment 9
<location> `Python/text_based_RPG_game.py:285-286` </location>
<code_context>
def use_item_in_combat(player):
    """Handle item usage during combat"""
    if not player.inventory:
        print("❌ Your inventory is empty!")
        return False

    print("\n🎒 Inventory:")
    for i, item in enumerate(player.inventory, 1):
        print(f"{i}. {item}")
    print(f"{len(player.inventory) + 1}. Cancel")

    try:
        choice = int(input("Choose item to use: ")) - 1
        if choice == len(player.inventory):
            return False  # Cancel
        return player.use_item(choice)
    except (ValueError, IndexError):
        print("❌ Invalid choice.")
        return False

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))
</issue_to_address>

### Comment 10
<location> `Python/text_based_RPG_game.py:348` </location>
<code_context>
def load_game():
    """Load game from file"""
    try:
        with open('rpg_save.json', 'r') as f:
            data = json.load(f)

        player = Player(data['name'], CharacterClass(data['character_class']))
        player.level = data['level']
        player.experience = data['experience']
        player.gold = data['gold']
        player.inventory = data['inventory']
        player.health = data['health']
        player.max_health = data['max_health']
        player.attack = data['attack']
        player.defense = data['defense']
        player.dodge_chance = data['dodge_chance']
        player.special_charges = data['special_charges']

        print("📂 Game loaded successfully!")
        return player
    except FileNotFoundError:
        print("❌ No saved game found.")
        return None
    except Exception as e:
        print(f"❌ Error loading game: {e}")
        return None

</code_context>

<issue_to_address>
**issue (code-quality):** Extract code out into function ([`extract-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-method/))
</issue_to_address>

### Comment 11
<location> `Python/text_based_RPG_game.py:402` </location>
<code_context>
def main():
    """Main game loop"""
    print("🐉 Welcome to Enhanced Python RPG! 🐉")

    # Load or create character
    player = None
    if os.path.exists('rpg_save.json'):
        load_choice = input("Load saved game? (y/n): ").strip().lower()
        if load_choice == 'y':
            player = load_game()

    if not player:
        player = create_character()

    enemies = ["Goblin", "Skeleton", "Orc", "Dragon", "Witch", "Zombie", "Vampire", "Giant Spider"]

    while player.is_alive():
        display_stats(player)

        print("\nChoose your action:")
        print("1. 🗺️  Explore")
        print("2. 🛌 Rest (restore health)")
        print("3. 🎒 Use Item")
        print("4. 💾 Save Game")
        print("5. 🚪 Quit Game")

        choice = input("Enter your choice (1-5): ").strip()

        if choice == '1':  # Explore
            # Random event chance
            if random.random() < 0.3:  # 30% chance for random event
                random_event(player)
            else:
                enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
                if not combat(player, enemy):
                    break

        elif choice == '2':  # Rest
            heal_amount = min(30, player.max_health - player.health)
            player.health += heal_amount
            print(f"💤 You rest and recover {heal_amount} health")
            # Random encounter while resting
            if random.random() < 0.2:
                print("🌙 You were ambushed during rest!")
                enemy = Enemy(random.choice(enemies), player.level)
                if not combat(player, enemy):
                    break

        elif choice == '3':  # Use Item
            if not use_item_in_combat(player):
                print("❌ No valid item used.")

        elif choice == '4':  # Save Game
            save_game(player)

        elif choice == '5':  # Quit
            save_choice = input("Save before quitting? (y/n): ").strip().lower()
            if save_choice == 'y':
                save_game(player)
            print("Thanks for playing! 👋")
            break

        else:
            print("❌ Invalid choice. Please try again.")

        # Small delay for better pacing
        time.sleep(1)

    if not player.is_alive():
        print(f"\nGame Over! Final stats:")
        display_stats(player)

</code_context>

<issue_to_address>
**issue (code-quality):** Low code quality found in main - 25% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

<br/><details><summary>Explanation</summary>The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

if random.random() < 0.3: # 30% chance for random event
random_event(player)
else:
enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Enemy level can be less than 1, which may cause unintended behavior.

random.randint(-1, 2) can produce enemy levels below 1, which may not be supported by the Enemy class. Please ensure enemy level is at least 1.

Comment on lines +345 to +354
def load_game():
"""Load game from file"""
try:
with open('rpg_save.json', 'r') as f:
data = json.load(f)

player = Player(data['name'], CharacterClass(data['character_class']))
player.level = data['level']
player.experience = data['experience']
player.gold = data['gold']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Generic exception handling may mask specific errors during game load.

Consider handling specific exceptions like JSONDecodeError to give clearer feedback and prevent hiding underlying issues.

Suggested implementation:

def load_game():
    """Load game from file"""
    try:
        with open('rpg_save.json', 'r') as f:
            data = json.load(f)

        player = Player(data['name'], CharacterClass(data['character_class']))
        player.level = data['level']
        player.experience = data['experience']
        player.gold = data['gold']
        player.inventory = data['inventory']
    except json.JSONDecodeError:
        print("❌ Failed to load game: Save file is corrupted or not valid JSON.")
        return None
    except FileNotFoundError:
        print("❌ Save file not found. Start a new game first.")
        return None
    except Exception as e:
        print(f"❌ An unexpected error occurred while loading the game: {e}")
        return None

If json is not already imported at the top of the file, add:

import json

Also, ensure that the function returns the player object at the end if loading is successful.

Comment on lines +402 to +411
def main():
"""Main game loop"""
print("🐉 Welcome to Enhanced Python RPG! 🐉")

# Load or create character
player = None
if os.path.exists('rpg_save.json'):
load_choice = input("Load saved game? (y/n): ").strip().lower()
if load_choice == 'y':
player = load_game()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Game loop does not handle KeyboardInterrupt gracefully.

Wrap the main loop in a try/except block to catch KeyboardInterrupt and exit cleanly, optionally prompting the user to save before quitting.

Suggested implementation:

def main():
    """Main game loop"""
    print("🐉 Welcome to Enhanced Python RPG! 🐉")
    try:
        # Load or create character
        player = None
        if os.path.exists('rpg_save.json'):
            load_choice = input("Load saved game? (y/n): ").strip().lower()
            if load_choice == 'y':
                player = load_game()
        # ... rest of the main loop logic goes here ...
    except KeyboardInterrupt:
        print("\n⚠️  Game interrupted by user (Ctrl+C).")
        save_choice = input("Would you like to save your progress before quitting? (y/n): ").strip().lower()
        if save_choice == 'y':
            if player is not None:
                save_game(player)
                print("💾 Game saved successfully.")
            else:
                print("No game in progress to save.")
        print("👋 Goodbye!")
        sys.exit(0)
  • Ensure that save_game and player are accessible in this scope.
  • Make sure to import sys at the top of the file if not already present: import sys
  • If the main loop logic is not yet inside the try block, move it there.

return True
return False

def use_special_ability(self, enemy):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring special ability logic and the combat loop using dispatch maps and a CombatEngine class to flatten nesting and reduce duplication.

Here are three small focused refactor ideas that keep all of your new functionality but drastically flatten nesting, remove duplication, and split responsibilities.

  1. Consolidate Player special‐ability logic via a dispatch map
    Move each class’s special into its own private method, then call via a simple dict instead of repeating if/elif:
# inside Player:
def use_special_ability(self, enemy):
    if self.special_charges <= 0:
        print("❌ No special charges remaining!")
        return False
    self.special_charges -= 1

    # map class ⇒ handler method
    handlers = {
        CharacterClass.WARRIOR: self._warrior_strike,
        CharacterClass.MAGE:    self._mage_fireball,
        CharacterClass.THIEF:   self._thief_poison,
    }
    handlers[self.character_class](enemy)
    return True

def _warrior_strike(self, enemy):
    dmg = int(self.attack * 1.8 - enemy.defense * 0.5)
    enemy.health -= max(1, dmg)
    print(f"💥 WARRIOR'S MIGHT! You strike for {dmg} damage!")

def _mage_fireball(self, enemy):
    dmg = self.attack + random.randint(5, 15)
    enemy.health -= dmg
    print(f"🔥 FIREBALL! You burn for {dmg} damage!")

def _thief_poison(self, enemy):
    base = self.attack // 2
    extra = base // 2
    enemy.health -= (base + extra)
    print(f"☠️ POISON! You deal {base}+{extra} damage!")
  1. Extract Enemy special abilities into a lookup
    Same idea for enemies—no more long if/elif chains:
# at module top:
ENEMY_SPECIALS = {
    "double_attack": lambda e, p: (
        setattr(p, 'health', p.health - max(1, e.attack - p.defense + random.randint(-2,2))),
        print(f"⚡ {e.name} DOUBLE STRIKE!")
    ),
    "heal": lambda e, p: (
        setattr(e, 'health', min(e.max_health, e.health + e.max_health//4)),
        print(f"💚 {e.name} heals!")
    ),
    "poison": lambda e, p: (
        setattr(p, 'health', p.health - e.attack//3),
        print(f"☠️ {e.name} poisons you!")
    ),
}

class Enemy:
    def perform_special_ability(self, player):
        if not self.special_ability or random.random() > 0.3:
            return False
        ENEMY_SPECIALS[self.special_ability](self, player)
        return True
  1. Encapsulate the combat loop in a CombatEngine class
    Break out the 30‐line nested loop into clear player_turn() / enemy_turn() methods:
class CombatEngine:
    def __init__(self, player, enemy):
        self.p, self.e = player, enemy

    def player_turn(self):
        choice = get_combat_choice(self.p)
        {
            '1': lambda: self._attack(self.p, self.e),
            '2': lambda: self.p.use_special_ability(self.e),
            '3': lambda: use_item_in_combat(self.p),
            '4': lambda: self._try_flee()
        }[choice]()

    def enemy_turn(self):
        if not self.e.perform_special_ability(self.p):
            damage = attack_target(self.e, self.p)
            print(f"💢 {self.e.name} deals {damage} damage!")

    def start(self):
        turn = 0
        while self.p.is_alive() and self.e.is_alive():
            turn += 1
            print(f"\n— Turn {turn} —")
            self.player_turn()
            if not self.e.is_alive(): break
            print(f"\n{self.e.name}'s turn...")
            time.sleep(1)
            self.enemy_turn()
        return self.p.is_alive()

# then in combat():
def combat(player, enemy):
    print(f"\n⚔️ A wild {enemy.name} appears!")
    engine = CombatEngine(player, enemy)
    if engine.start():
        victory_rewards(player, enemy)
        return True
    print("💀 You have been defeated…")
    return False

By pulling out these three pieces:

  • you remove deep nesting in combat()
  • you eliminate duplicated if/elif blocks
  • you isolate event/ability logic into self-contained units

You can do the same for random_event (e.g. an Event base class + subclasses) or even move each into its own file without touching any existing game behavior.

Comment on lines +375 to +377
name = input("Enter your character's name: ").strip()
if not name:
name = "Hero"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use or for providing a fallback value (use-or-for-fallback)

Suggested change
name = input("Enter your character's name: ").strip()
if not name:
name = "Hero"
name = input("Enter your character's name: ").strip() or "Hero"


ExplanationThanks to the flexibility of Python's or operator, you can use a single
assignment statement, even if a variable can retrieve its value from various
sources. This is shorter and easier to read than using multiple assignments with
if not conditions.


if is_critical or random.random() < 0.1: # 10% critical chance
damage = int(damage * CRITICAL_MULTIPLIER)
print(f"💥 CRITICAL HIT! ", end="")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Replace f-string with no interpolated values with string (remove-redundant-fstring)

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent enemies from spawning with zero health

Prevent enemies from spawning with zero health by ensuring their level is always
at least 1. When creating an enemy, use max(1, player.level + random.randint(-1,
2)) to calculate its level.

Python/text_based_RPG_game.py [430-437]

 if choice == '1':  # Explore
     # Random event chance
     if random.random() < 0.3:  # 30% chance for random event
         random_event(player)
     else:
-        enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
+        enemy_level = max(1, player.level + random.randint(-1, 2))
+        enemy = Enemy(random.choice(enemies), enemy_level)
         if not combat(player, enemy):
             break
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion identifies a critical bug where an enemy can spawn with zero health, causing it to be instantly defeated, and provides a correct fix to ensure the enemy level is always at least 1.

High
Prevent losing a turn unnecessarily

In the combat function, restructure the player's turn with an inner loop. This
prevents the player from losing their turn if they select an action that cannot
be performed, such as using a special ability without charges.

Python/text_based_RPG_game.py [201-256]

 def combat(player, enemy):
     """Enhanced combat system"""
     print(f"\n⚔️ A wild {enemy.name} (Level {enemy.level}) appears!")
     
     turn = 0
     while player.is_alive() and enemy.is_alive():
         turn += 1
         print(f"\n——— Turn {turn} ———")
         print(f"{player.name}: {player.health}/{player.max_health} HP")
         print(f"{enemy.name}: {enemy.health}/{enemy.max_health} HP")
         
         # Player's turn
-        action = get_combat_choice(player)
-        
-        if action == '1':  # Attack
-            damage = attack_target(player, enemy)
-            if damage > 0:
-                print(f"💥 You deal {damage} damage to {enemy.name}!")
-        
-        elif action == '2':  # Special Ability
-            if not player.use_special_ability(enemy):
-                continue  # Invalid special, try again
-        
-        elif action == '3':  # Use Item
-            if not use_item_in_combat(player):
-                continue  # No valid item used, try again
-        
-        elif action == '4':  # Flee
-            if random.random() < 0.6:  # 60% chance to flee
-                print("🏃 You successfully fled!")
-                return False
-            else:
-                print("❌ You couldn't escape!")
+        while True:
+            action = get_combat_choice(player)
+            
+            if action == '1':  # Attack
+                damage = attack_target(player, enemy)
+                if damage > 0:
+                    print(f"💥 You deal {damage} damage to {enemy.name}!")
+                break  # End player's turn
+            
+            elif action == '2':  # Special Ability
+                if player.use_special_ability(enemy):
+                    break  # End player's turn
+                # If not successful, loop will ask for another action
+            
+            elif action == '3':  # Use Item
+                if use_item_in_combat(player):
+                    break  # End player's turn
+                # If not successful, loop will ask for another action
+            
+            elif action == '4':  # Flee
+                if random.random() < 0.6:  # 60% chance to flee
+                    print("🏃 You successfully fled!")
+                    return False
+                else:
+                    print("❌ You couldn't escape!")
+                    break # End player's turn
         
         # Check if enemy defeated
         if not enemy.is_alive():
             break
         
         # Enemy's turn
         print(f"\n{enemy.name}'s turn...")
         time.sleep(1)
         
         # Chance for special ability
         if not enemy.perform_special_ability(player):
             # Normal attack
             damage = attack_target(enemy, player)
             if damage > 0:
                 print(f"💢 {enemy.name} deals {damage} damage to you!")
     
     # Combat conclusion
     if player.is_alive():
         victory_rewards(player, enemy)
         return True
     else:
         print("💀 You have been defeated...")
         return False
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant gameplay logic bug where a player unfairly loses their turn after an invalid action, and the proposed fix correctly resolves this issue.

Medium
Remove unusable items from drops

In the victory_rewards function, remove unimplemented items like "Magic Scroll"
and "Iron Sword" from the items drop list, as they currently have no function in
the game.

Python/text_based_RPG_game.py [292-308]

 def victory_rewards(player, enemy):
     """Handle rewards after winning combat"""
     print(f"🎯 You defeated {enemy.name}!")
     player.gold += enemy.gold_reward
     player.experience += enemy.exp_reward
     print(f"💰 Gained {enemy.gold_reward} gold")
     print(f"🌟 Gained {enemy.exp_reward} experience")
     
     # Item drop chance
     if random.random() < 0.4:  # 40% chance for item
-        items = ["Health Potion", "Health Potion", "Health Potion", "Magic Scroll", "Iron Sword", "Wooden Shield"]
+        items = ["Health Potion"]
         found_item = random.choice(items)
         player.inventory.append(found_item)
         print(f"🎁 Found {found_item}!")
     
     # Level up check
     player.level_up()
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that the game drops items that have no implemented use, and the proposed change to remove them improves game consistency and player experience.

Low
High-level
Adopt a more modular, object-oriented structure

Refactor the single-file game into multiple modules to separate concerns. Create
distinct components for game entities (Player, Enemy), game logic (combat), and
user interface to improve maintainability.

Examples:

Python/text_based_RPG_game.py [1-475]
import random
import time
import json
import os
from enum import Enum

# Constants for game balance
XP_MULTIPLIER = 100
POTION_HEAL = 50
CRITICAL_MULTIPLIER = 1.5

 ... (clipped 465 lines)

Solution Walkthrough:

Before:

# Python/text_based_RPG_game.py

class Player:
    # ... player attributes and methods
    # ... methods contain print statements (UI)

class Enemy:
    # ... enemy attributes and methods

def combat(player, enemy):
    # ... combat logic mixed with print() and input()

def save_game(player):
    # ... file I/O logic

def main():
    # ... main game loop, handles all actions and UI

if __name__ == "__main__":
    main()

After:

# entities.py
class Player:
    # ...
class Enemy:
    # ...

# game_logic.py
import entities
def combat(player, enemy):
    # ... returns combat result object
def handle_random_event(player):
    # ...

# persistence.py
def save_game(player): #...
def load_game(): #...

# main.py (or ui.py)
import entities, game_logic, persistence
def main():
    # ... main loop
    # ... calls game_logic functions
    # ... handles all print() and input()
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant architectural issue in the new game, as the monolithic file structure mixes UI, data, and logic, which will hinder future maintenance and scalability.

Medium
  • More

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
Python/text_based_RPG_game.py (5)

301-305: Make dropped items usable or trim the drop table.

You award Magic Scroll, Iron Sword, Wooden Shield, but Player.use_item handles only Health Potion. Either remove them or add simple effects.

Apply this diff to implement basic effects:

@@ def victory_rewards(player, enemy):
-    if random.random() < 0.4:  # 40% chance for item
-        items = ["Health Potion", "Health Potion", "Health Potion", "Magic Scroll", "Iron Sword", "Wooden Shield"]
+    if random.random() < 0.4:  # 40% chance for item
+        items = ["Health Potion", "Health Potion", "Health Potion", "Magic Scroll", "Iron Sword", "Wooden Shield"]
         found_item = random.choice(items)
         player.inventory.append(found_item)
         print(f"🎁 Found {found_item}!")
@@ class Player:
-    def use_item(self, item_index):
+    def use_item(self, item_index):
         """Use item from inventory"""
         if item_index < 0 or item_index >= len(self.inventory):
             return False
@@
-        if item == "Health Potion":
+        if item == "Health Potion":
             heal_amount = min(POTION_HEAL, self.max_health - self.health)
             self.health += heal_amount
             self.inventory.pop(item_index)
             print(f"🧪 Used Health Potion! Healed {heal_amount} HP.")
             return True
+        elif item == "Magic Scroll":
+            self.special_charges += 2
+            self.inventory.pop(item_index)
+            print("📜 Used Magic Scroll! Gained +2 special charges.")
+            return True
+        elif item == "Iron Sword":
+            self.attack += 2
+            self.inventory.pop(item_index)
+            print("🗡️  Equipped Iron Sword! +2 Attack.")
+            return True
+        elif item == "Wooden Shield":
+            self.defense += 2
+            self.inventory.pop(item_index)
+            print("🛡️  Equipped Wooden Shield! +2 Defense.")
+            return True

Also applies to: 111-126


341-343: Make save atomic to avoid file corruption.

Crash/power loss mid-write can corrupt rpg_save.json. Write to a temp file and replace.

Apply this diff:

-    with open('rpg_save.json', 'w') as f:
-        json.dump(data, f)
-    print("💾 Game saved successfully!")
+    tmp_path = 'rpg_save.json.tmp'
+    with open(tmp_path, 'w') as f:
+        json.dump(data, f)
+    os.replace(tmp_path, 'rpg_save.json')
+    print("💾 Game saved successfully!")

258-270: Use the player arg to show special-charge status; also fix an f-string nit.

Small UX win and resolves the “unused arg” warning. Also remove unnecessary f-string.

Apply this diff:

 def get_combat_choice(player):
     """Get valid combat choice from player"""
     while True:
-        print(f"\nChoose action:")
+        print("\nChoose action:")
-        print("1. ⚔️  Attack")
-        print("2. ✨ Special Ability")
+        print("1. ⚔️  Attack")
+        special_label = "2. ✨ Special Ability" if player.special_charges > 0 else "2. ✨ Special Ability (0 left)"
+        print(special_label)
         print("3. 🎒 Use Item")
         print("4. 🏃 Flee")

Based on static analysis hints.


196-196: Remove unnecessary f-strings.

No placeholders present; drop the f prefix.

Apply this diff:

-        print(f"💥 CRITICAL HIT! ", end="")
+        print("💥 CRITICAL HIT! ", end="")
@@
-        print(f"\nGame Over! Final stats:")
+        print("\nGame Over! Final stats:")

Based on static analysis hints.

Also applies to: 471-471


1-6: About S311 (non-crypto RNG): safe to ignore; prefer config-level suppression.

random is appropriate for gameplay. To silence Ruff without cluttering code, add per-file ignore in pyproject:

# pyproject.toml
[tool.ruff.lint.per-file-ignores]
"Python/text_based_RPG_game.py" = ["S311"]

If you prefer inline, add “# noqa: S311” to the specific random.* lines. Based on static analysis hints.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e25c116 and acea1c9.

📒 Files selected for processing (1)
  • Python/text_based_RPG_game.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.1)
Python/text_based_RPG_game.py

96-96: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


137-137: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


141-141: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


148-148: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


152-152: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


187-187: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


191-191: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


194-194: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


196-196: f-string without any placeholders

Remove extraneous f prefix

(F541)


229-229: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


258-258: Unused function argument: player

(ARG001)


261-261: f-string without any placeholders

Remove extraneous f prefix

(F541)


301-301: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


303-303: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


319-319: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


364-364: Consider moving this statement to an else block

(TRY300)


368-368: Do not catch blind exception: Exception

(BLE001)


432-432: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


435-435: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


435-435: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


444-444: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


446-446: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


471-471: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (1)
Python/text_based_RPG_game.py (1)

171-184: Nice: clear stats view and straightforward save payload.

Readability is good and the saved fields cover everything needed to reconstruct Player state.

Also applies to: 324-344

Comment on lines +90 to +93
damage = int(self.attack * 1.8 - (enemy.defense * 0.5))
enemy.health -= max(1, damage)
print(f"💥 WARRIOR'S MIGHT! You strike for {damage} damage!")

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Print actual applied damage for Warrior special.

You clamp applied damage but print the unclamped value; can display negatives.

Apply this diff:

-            damage = int(self.attack * 1.8 - (enemy.defense * 0.5))
-            enemy.health -= max(1, damage)
-            print(f"💥 WARRIOR'S MIGHT! You strike for {damage} damage!")
+            damage = int(self.attack * 1.8 - (enemy.defense * 0.5))
+            actual_damage = max(1, damage)
+            enemy.health -= actual_damage
+            print(f"💥 WARRIOR'S MIGHT! You strike for {actual_damage} damage!")
🤖 Prompt for AI Agents
In Python/text_based_RPG_game.py around lines 90 to 93, the code computes damage
then clamps it with max(1, damage) but prints the original (unclamped) value;
change the logic so you compute damage = int(self.attack * 1.8 - (enemy.defense
* 0.5)), then compute applied_damage = max(1, damage), subtract applied_damage
from enemy.health, and print applied_damage in the message (e.g., use
applied_damage instead of damage) so negatives are not shown.

Comment on lines +228 to +233
elif action == '4': # Flee
if random.random() < 0.6: # 60% chance to flee
print("🏃 You successfully fled!")
return False
else:
print("❌ You couldn't escape!")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix flee flow: fleeing should not end the game loop.

combat() returns False both on death and on flee, and callers treat False as “exit.” Return a sentinel for flee and handle it at call sites.

Apply this diff:

@@
-        elif action == '4':  # Flee
-            if random.random() < 0.6:  # 60% chance to flee
-                print("🏃 You successfully fled!")
-                return False
+        elif action == '4':  # Flee
+            if random.random() < 0.6:  # 60% chance to flee
+                print("🏃 You successfully fled!")
+                return "fled"
             else:
                 print("❌ You couldn't escape!")
@@
-                enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
-                if not combat(player, enemy):
-                    break
+                enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
+                result = combat(player, enemy)
+                if result is False:  # defeated
+                    break
@@
-                enemy = Enemy(random.choice(enemies), player.level)
-                if not combat(player, enemy):
-                    break
+                enemy = Enemy(random.choice(enemies), player.level)
+                result = combat(player, enemy)
+                if result is False:  # defeated
+                    break

Also applies to: 436-438, 446-448

🧰 Tools
🪛 Ruff (0.14.1)

229-229: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)

Comment on lines +345 to +371
def load_game():
"""Load game from file"""
try:
with open('rpg_save.json', 'r') as f:
data = json.load(f)

player = Player(data['name'], CharacterClass(data['character_class']))
player.level = data['level']
player.experience = data['experience']
player.gold = data['gold']
player.inventory = data['inventory']
player.health = data['health']
player.max_health = data['max_health']
player.attack = data['attack']
player.defense = data['defense']
player.dodge_chance = data['dodge_chance']
player.special_charges = data['special_charges']

print("📂 Game loaded successfully!")
return player
except FileNotFoundError:
print("❌ No saved game found.")
return None
except Exception as e:
print(f"❌ Error loading game: {e}")
return None

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden load_game: narrow exceptions and use try/else.

Catching broad Exception (BLE001) and missing try/else (TRY300). Also handle JSON decoding errors explicitly.

Apply this diff (includes a small import addition):

@@
-import json
+import json
+from json import JSONDecodeError
@@
 def load_game():
     """Load game from file"""
-    try:
-        with open('rpg_save.json', 'r') as f:
-            data = json.load(f)
-        
-        player = Player(data['name'], CharacterClass(data['character_class']))
-        player.level = data['level']
-        player.experience = data['experience']
-        player.gold = data['gold']
-        player.inventory = data['inventory']
-        player.health = data['health']
-        player.max_health = data['max_health']
-        player.attack = data['attack']
-        player.defense = data['defense']
-        player.dodge_chance = data['dodge_chance']
-        player.special_charges = data['special_charges']
-        
-        print("📂 Game loaded successfully!")
-        return player
-    except FileNotFoundError:
-        print("❌ No saved game found.")
-        return None
-    except Exception as e:
-        print(f"❌ Error loading game: {e}")
-        return None
+    try:
+        with open('rpg_save.json', 'r') as f:
+            data = json.load(f)
+    except FileNotFoundError:
+        print("❌ No saved game found.")
+        return None
+    except (JSONDecodeError, KeyError, ValueError) as e:
+        print(f"❌ Error loading game: {e}")
+        return None
+    else:
+        player = Player(data['name'], CharacterClass(data['character_class']))
+        player.level = data['level']
+        player.experience = data['experience']
+        player.gold = data['gold']
+        player.inventory = data['inventory']
+        player.health = data['health']
+        player.max_health = data['max_health']
+        player.attack = data['attack']
+        player.defense = data['defense']
+        player.dodge_chance = data['dodge_chance']
+        player.special_charges = data['special_charges']
+        print("📂 Game loaded successfully!")
+        return player

Based on static analysis hints.

Also applies to: 3-3

🧰 Tools
🪛 Ruff (0.14.1)

364-364: Consider moving this statement to an else block

(TRY300)


368-368: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In Python/text_based_RPG_game.py around lines 345 to 371, replace the broad
try/except block with a narrow, explicit handling flow: open the file in a try
that catches FileNotFoundError, then call json.load() inside a try/except that
catches json.JSONDecodeError (or import JSONDecodeError) and KeyError for
missing fields; use a try/else so that player construction and attribute
assignment occur only in the else branch when loading succeeded, and avoid a
blanket except Exception—also catch OSError if you want IO errors; return None
on handled failures and print specific error messages for each case.

Comment on lines +435 to +436
enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
if not combat(player, enemy):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clamp enemy level to at least 1.

Random offset can produce level 0. Clamp to avoid zero-level enemies and zero rewards.

Apply this diff:

-                enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
+                enemy_level = max(1, player.level + random.randint(-1, 2))
+                enemy = Enemy(random.choice(enemies), enemy_level)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
enemy = Enemy(random.choice(enemies), player.level + random.randint(-1, 2))
if not combat(player, enemy):
enemy_level = max(1, player.level + random.randint(-1, 2))
enemy = Enemy(random.choice(enemies), enemy_level)
if not combat(player, enemy):
🧰 Tools
🪛 Ruff (0.14.1)

435-435: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


435-435: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)

🤖 Prompt for AI Agents
In Python/text_based_RPG_game.py around lines 435 to 436, the enemy level
calculation can produce 0 because of the random offset; change the enemy
instantiation to clamp the computed level to a minimum of 1 (e.g., compute level
= max(1, player.level + random.randint(-1, 2)) and pass that to Enemy) so
enemies and rewards never use level 0.

@x0lg0n x0lg0n merged commit 39e1e6d into x0lg0n:main Oct 27, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants