Very simple Python RPG

Posted on

Problem

I am new to Python and am working on a small RPG game project to practice the language and OOP. I honestly don’t know what I am doing though, so before I get into developing the battle system I figured I would first show my code so far in, case I’m making a big mess!

Implemented so far:

  • Save/Load game

  • Player can choose a team to play (and is able to change the team afterwards, whenever he wants)

  • Player can see his team’s stats (character names, level, moves)

main.py

import jsonpickle
import os
import sys
from world import World
from player_team import Player_team

# Initialization 
world = World()
player_team = Player_team()
SAVEGAME_FILENAME = 'rpg_lqsa.json'
game_state = dict()     

def save_game():
    global game_state
    with open(SAVEGAME_FILENAME, 'w') as savegame:
        savegame.write(jsonpickle.encode(game_state))

def load_game():
    with open(SAVEGAME_FILENAME, 'r') as savegame:
        state = jsonpickle.decode(savegame.read())
    return state

def menu():

    # Display menu
    print ("***MENU***n")
    print ("1. Fight")
    print ("2. Team")
    print ("3. Exit")

    choice = input("Command: ")
    validate_choice(choice, 1, 3)

    # Battle
    if choice == '1':
        print()

    # Team submenu    
    elif choice == '2':
        team_menu()

    # Save and exit game
    elif choice == '3':
        save_game()
        print("nGame saved automatically. Bye!")
        sys.exit(0)

    else:
        print ("nUnexpected error occurred.")
        sys.exit(0)

def team_menu():

    # Display team submenu
    print ("n***TEAM SUBMENU***n")
    print ("1. Show team stats")
    print ("2. Switch team")

    choice = input("Comman: ")
    validate_choice(choice, 1, 2)

    # Show team stats
    if choice == '1':
        player_team.display_team()
        input()

    # Change player's team
    elif choice == '2':
        print()
        choose_team()

    else:
        print ("nUnexpected error occurred.")
        sys.exit(0)

def validate_choice(choice, first_num, last_num):
    error_message = 'nYou should provide a number between {} and {}.'.format(first_num, last_num)

    while True:    
        if choice.isdigit():
            if int(choice) not in range(first_num, last_num+1):
                print (error_message)
                choice = input()
            else:
                break   
        else:
            print (error_message)
            choice = input()  

def choose_team():

    # Display available teams
    print("Pick a team:n")

    for index, team in enumerate(world.teams):
        print ('{}. {} y {}'.format(index+1, team[0].name, team[1].name))

    choice = input("Command: ")
    validate_choice(choice, 1, len(world.teams))

    # Add team
    chosen_team = world.teams[int(choice)-1]
    player_team.set_team(chosen_team[0], chosen_team[1])

    # Save player team
    global game_state
    game_state['player_team'] = player_team

    # Team added
    print("nDone! Your new team is ready to kick ass. Good luck!")
    input()

def new_game():

    # Introduction
    introMessage = (
        'nVirtual Reality arrived to Montepinar '
        'and our dear neighbors decided to create '
        'a fighting tournament during the next month.n'
        'The winner couple will earn 3 quotes as a price. Let's start!')
    print(introMessage)
    input()

    # First player team
    choose_team()

    # Show menu loop
    run_game()

def start_menu():
    global game_state
    global player_team

    while True:
        print("***RPG La Que Se Avecina***n")
        print("1. New game")
        print("2. Load game")
        print("3. Exit")

        choice = input("Command: ")
        validate_choice(choice, 1, 3)

        # New game
        if choice == '1':
            if not os.path.isfile(SAVEGAME_FILENAME):
                game_state["player_team"] = player_team
                new_game()
                return False
            else:
                answer = input("nThere's a save game. Do you want to overwrite it and start again? ('yes' to confirm)nn")
                if answer.lower() == 'yes':
                    new_game()
                    return False
                print()

        # Load game
        elif choice == '2':
            if not os.path.isfile(SAVEGAME_FILENAME):
                print("nNo saved games.")
                input()
            else:
                game_state = load_game()
                print()
                run_game()
                return False

        # Exit game
        elif choice == '3':
            print("nBye!")
            sys.exit(0)
            return False

        else:
            print ("nUnexpected error occurred.")
            sys.exit(0) 
            return False   

# Game loop
def run_game():

    global game_state
    global player_team

    # Load player's team
    player_team = game_state["player_team"]

    while True:

        # Menu loop
        menu()

# Start with New/Load game
if __name__ == '__main__':
    start_menu()

character.py

import random

class Character:

    def __init__(self, name, moves = None, combos = None):
        if moves == None:
            moves = []
        if combos == None:
            combos = []
        self.name = name
        self.moves = moves
        self.combos = combos
        self.partner = None
        self.health = 1000
        self.max_health = 1000
        self.exp_points = 0
        self.level = 1

    def is_alive(self):
        return self.health > 0

    def get_move_names(self):
        move_names = ""
        for move in self.moves:
            move_names += "{} ({}), ".format(move.name, move.level)
        move_names = move_names.rstrip(', ')
        return move_names

    # User interface   
    def do_damage(self, move, rival):
        move.damage = random(0, 100 * move.level)

        if move.damage == 0: 
            print ("{} avoided attack from {}!".format(rival.name, self.name))
        else: 
            print ("{} did {} damage to {}.".format(self.name, move.damage, rival.name))
        return rival.health <= 0

lqsa_character.py

from character import Character
from move import Move

class Lqsa_character:
    Antonio = Character("Antonio", moves = [Move("Centollazo"), Move("Calambrazo")])
    Enrique = Character("Enrique", moves = [Move("Dialog"), Move("Ataque2")])
    Fermin = Character("Fermín", moves = [Move("Kick"), Move("Hit")])
    Vicente = Character("Vicente", moves = [Move("Controller hit"), Move("Salto Suicida")])
    Amador = Character("Amador", moves = [Move("Pinchito"), Move("Cabezazo")])
    Teodoro = Character("Teodoro", moves = [Move("Pinchito"), Move("Cabezazo")])
    Javi = Character("Javi", moves = [Move("Golpe de Ansiolíticos"), Move("Ataque2")])
    Lola = Character("Lola", moves = [Move("Attack1"), Move("Attack2")])

    def __init__(self):
        self.assignPartners()

    def assignPartners(self):
        self.Antonio.partner = self.Enrique
        self.Enrique.partner = self.Antonio
        self.Fermin.partner = self.Vicente
        self.Vicente.partner = self.Fermin
        self.Amador.partner = self.Teodoro
        self.Teodoro.partner = self.Amador
        self.Javi.partner = self.Lola
        self.Lola.partner = self.Javi 

move.py

class Move:

    def __init__(self, name):
        self.name = name
        self.level = 1
        self.damage = 0

item.py

class Item:

    def __init__(self, name, quantity, price):
        self.name = name
        self.quantity = quantity
        self.price = price   
        self.utility = ""

    def display_info(self, item):
        return "{} - Units: {}, Utility: {}".format(item.name, item.quantity, item.utility)

player_team.py

from item import Item

class Player_team:

    def __init__(self):
        self.members = []
        self.inventory = []

    def set_team(self, character1, character2):
        self.members = [character1, character2]        

    def is_alive(self):
        for member in self.members:
            if member.health > 0:
                return True
        return False

    def add_item(self, item):
        self.inventory.append(item)

    # User Interface

    def display_team(self):
        if len(self.members) <= 0 or self.members[0] == None or self.members[1] == None:
            raise Exception("Unexpected error occurred.")

        print ("n***Team***n")
        for member in self.members:
            print("-> {} ({})n   Moves: [{}]n".format(member.name, member.level, member.get_move_names()))

    def display_inventory(self):
        for item in self.inventory:
            print(Item.display_info(item), 'n')

Solution

Welcome to Code Review. This is a really awesome piece of program you
have 🙂 And thanks to A. Romeu for translations.

Because I noticed the awkward syntax highlighting towards the end of main.py here around introduction section; I would suggest multi-line strings; which is simply wrapping it all in triple-quotes.

enter image description here

    # Introduction
    introMessage = """
Virtual Reality arrived to Montepinar
and our dear neighbors decided to create
a fighting tournament during the next month.

The winner couple will earn 3 quotes as a price. Let's start!
"""
    print(introMessage)
    input()

Naming conventions should be consistent throughout the project, and with Python’s PEP-8 RFCs, you have it:

  • variables and methods are named in snake_lower_case
  • class names are defined as CamelUpperFirstCase

So it will be PlayerTeam, assign_partners etc.

Following this makes your code really easy to read for fellow
pythoners. If you violate these conventions, the worst you’ll get is
some dirty looks


Avoid using global variables references inside functions. Pass them as arguments instead (referring to global game_state; global player_team etc.).


Ideal import order (according to PEP8) is

  • standard library imports
  • related third party imports
  • local application/library specific imports

os/sys etc. are standard library, and should be imported first, followed by jsonpickle and lastly placing world and team.


Try to use docstrings for functions/class definitions. It helps in maintaining your application over a long course of time.


Your item class can be replaced by a simple namedtuple, and overriding the __str__ method is really easy.

Item = namedtuple('Item', "name, quantity, price, utility")
Item.__str__ = lambda item: f"{item.name} - Units: {item.quantity}, Utility: {item.utility}"

However, it might not be the case as there could be some methods that might need to be introduced to particular items. Still, __str__ method is to be noted.


Checking for None should be done with is and is not instead of == checks.

Something can not be equal to nothing.


You should also implement a stricter check against a character’s moves. They should all be an instance of Move class.

assert all(isinstance(move, Move) for move in moves)

def get_move_names(self):
    move_names = ""
    for move in self.moves:
        move_names += "{} ({}), ".format(move.name, move.level)
    move_names = move_names.rstrip(', ')
    return move_names

can be rewritten as:

def get_move_names(self):
    return ", ".join([str(move) for move in self.moves])

and in your Move class:

def __str__(self):
    return "{} ({})".format(self.name, self.level)

Your current validate_choice takes the first input and a range of integer valid values. This can be improved

def validate_choice(prompt, valid_values):
    error_message = "Valid values are: " + ', '.join((str(x) for x in valid_values))
    while True:
        choice = input(prompt)
        if choice in valid_values:
            return choice
        else:
            print(error_message)

and usage is really simple, instead of:

choice = input("Command: ")
validate_choice(choice, 1, 3)

you now have:

choice = validate_choice("Command: ", list(range(1, 4)))

The benefit of this style is, you can also support:

choice = validate_choice("Command: ", 'A', 'B', 'E', 'X')

I do not understand why you have a Lqsa_character class.

Leave a Reply

Your email address will not be published. Required fields are marked *