10
\$\begingroup\$

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)\n\n")
                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')
\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to Code Review! Neat project, I wish I knew Spanish, but this is a fun way to learn for sure! \$\endgroup\$ – Phrancis Mar 3 '18 at 21:52
  • 1
    \$\begingroup\$ Moved most of spanish words to english, should be more understandable now Interesting project btw, about an important part of spanish folklore :) \$\endgroup\$ – A. Romeu Mar 4 '18 at 2:03
  • \$\begingroup\$ Is translating code into English an exception to the rule of never editing the code in a question? Even though it certainly helps getting more answers, I can imagine there might be valid points about the original that get lost in translation. Also, coding in English might be one of the most important suggestions in a review. \$\endgroup\$ – Raimund Krämer Mar 5 '18 at 8:53
7
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ I mainly program in English but since this RPG game is based on a Spanish TV show it wouldn't really make sense. I tried to write everything logic related in English though. Sorry for the inconvenience and thanks A. Romeu for the translations! @hjpotter92, I don't know how to thank you enough for such a great answer! The class "lqsa_character" is meant just for generating the tv show characters. Where should I create them (and assign their partners) instead? In world.py maybe? or character.py? I had doubts about it so I just made a new class, but now I'm not sure what's best. :S \$\endgroup\$ – Guest43242 Mar 4 '18 at 10:48
  • \$\begingroup\$ @Guest43242 The reason you can't accept an answer is because you have created duplicate accounts, the question was asked with one account but now you are using another. I have asked the community team to merge these accounts so that you should be able to accept the answer. \$\endgroup\$ – Simon Forsberg Mar 4 '18 at 14:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.