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.
# 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.