-
Notifications
You must be signed in to change notification settings - Fork 0
python quickstart
# Game Variables
player_name = "Hero" # String
player_level = 5 # Integer
player_health = 100.0 # Float
has_sword = True # Boolean
# Game Logic
damage = 20
player_health -= damage # Subtract damage
# Printing and formatting strings
# Many ways exist but f-strings are usually preferred in terms of readability and performance
print(f"{player_name} took {damage} damage! Health: {player_health}")
message = f"{player_name} is level " + str(player_level) + "." # Not using f-strings can be more cumbersome but sometimes there are reasons for it
print(message)
Use for conditions; elif
(else-if) is optional.
enemy_level = 7
if player_level >= enemy_level:
print("You are strong enough to fight!")
elif player_level + 2 >= enemy_level:
print("You can try your luck!")
else:
print("You are too weak!")
# For loop (inventory)
inventory = ["sword", "potion", "shield"] # List of strings
for item in inventory:
print(f"You have: {item}")
# While loop (leveling up)
experience = 0
level_up_threshold = 100
while experience < level_up_threshold:
experience += 25
print(f"Current experience: {experience}")
print("Level up!")
# While loop with break statement
count = 0
while True:
print(f"enemy attacked {count} times")
count +=1
if count >=3:
break # break out of the attack loop.
def calculate_damage(attack_power, defense_power): # Arguments passed; feels as passed by value (=copy) (but pay attention about mutability!)
"""Calculates damage dealt."""
return max(0, attack_power - defense_power)
damage_dealt = calculate_damage(30, 15)
print(f"Damage dealt: {damage_dealt}")
def attack_with_sword():
print("You slash with your sword!")
def attack_with_magic():
print("You cast a magic spell!")
def attack_with_bow():
print("You shoot an arrow!")
# Lookup table for attack types
attack_lut = {
"sword": attack_with_sword,
"magic": attack_with_magic,
"bow": attack_with_bow,
}
attack_type = "magic"
attack_function = attack_lut.get(attack_type, lambda: print("Invalid attack type."))
attack_function()
def describe_item(item):
match item:
case ("sword", level) if level > 10: # Guard condition
print(f"A powerful sword of level {level}!")
case ("sword", level):
print(f"A sword of level {level}.")
case ("potion", health) if health > 50:
print(f"A major healing potion that heals {health} health!")
case ("potion", health):
print(f"A potion that heals {health} health.")
case ("shield", durability, "metal"): # Matching multiple items in tuple
print(f"A metal shield with {durability} durability.")
case ("shield", durability):
print(f"A shield with {durability} durability.")
case {"name": str(name), "level": int(level)}: # Matching a dictionary
print(f"A named item {name} of level {level}")
case ["monster", *rest]: # Catching lists, and rest of list
print(f"A monster encountered, with {len(rest)} associated modifiers.")
case _:
print("Unknown item.")
describe_item(("sword", 15))
describe_item(("potion", 60))
describe_item(("potion", 20))
describe_item(("shield", 100, "metal"))
describe_item(("shield", 50))
describe_item({"name": "Fireball", "level": 3})
describe_item(["monster", "fire", "fast"])
describe_item(("unknown", 1))
Know where something can fail and make sure you deal with it. Depending on the context you might want to raise yourself exceptions and let others handle it (e.g. libraries). Unit tests help covering all cases.
Good:
def load_game_data(filename):
try:
with open(filename, 'r') as file:
return file.read()
except FileNotFoundError:
print(f"Error: Game data file '{filename}' not found.")
return None
Bad:
def load_game_data(filename):
# No error handling; loading the file could fail
file = open(filename)
return file.read()
class Character:
def __init__(self, name, level, health):
self.name = name
self.level = level
self.health = health
def take_damage(self, damage):
damage = self._apply_defense(damage) # Apply defense before taking damage
self.health -= damage
print(f"{self.name} took {damage} damage! Health: {self.health}")
def _apply_defense(self, damage): # Private function (in Python by naming convention)
defense_modifier = self.level * 2
reduced_damage = max(0, damage - defense_modifier) # Damage must be positive
return reduced_damage
player = Character("Hero", 5, 100)
enemy = Character("Goblin", 7, 50)
enemy_damage = 25
player.take_damage(enemy_damage)
class Enemy(Character): #Enemy inherits from character
def __init__(self, name, level, health, attack_power):
super().__init__(name, level, health) # call parent constructor.
self.attack_power = attack_power
def attack(self, target):
damage = calculate_damage(self.attack_power, target.level * 5)
target.take_damage(damage)
goblin = Enemy("Goblin", 7, 50, 20)
goblin.attack(player)
from abc import ABC, abstractmethod
from typing import List, Any
class Character:
def __init__(self, name: str, health: int, mana: int):
self.name = name
self.health = health
self.mana = mana
def __str__(self) -> str:
# Overwrite methode string representation of the object
return f"{self.name} (H:{self.health}, M:{self.mana})"
class Action(ABC):
# Abstract base class (ABC) to define a common interface for actions
@abstractmethod # type: ignore
def execute(self, caster: Character, target: Character) -> Any:
# Abstract method (pure virtual function) that must be implemented by subclasses
...
def perform(self, caster: Character, target: Character) -> Any:
# Concrete method in the base class providing a common algorithm
print(f"{caster.name} -> {self.__class__.__name__} -> {target.name}.")
self.execute(caster, target)
print(f"{caster} | {target}")
class Attack(Action):
# Subclass implementing the abstract method, demonstrating polymorphism
def execute(self, caster: Character, target: Character) -> Any:
target.health -= 20
print(f"{target.name} -20 HP.")
return None # Explicit return None to avoid type errors.
class Heal(Action):
# Another subclass implementing the abstract method, demonstrating polymorphism
def execute(self, caster: Character, target: Character) -> Any:
target.health += 30
print(f"{target.name} +30 HP.")
return None
class ManaDrain(Action):
# Yet another subclass implementing the abstract method, demonstrating polymorphism
def execute(self, caster: Character, target: Character) -> Any:
target.mana -= 15
caster.mana += 15
print(f"{target.name} -15 Mana, {caster.name} +15 Mana.")
return None
def apply_actions(caster: Character, target: Character, actions: List[Action]) -> Any:
# Function using the abstract class to apply a list of actions (demonstrates polymorphism)
for action in actions:
action.perform(caster, target)
player = Character("Hero", 100, 50)
enemy = Character("Goblin", 80, 20)
actions = [Attack(), Heal(), ManaDrain()]
apply_actions(player, enemy, actions)
Explanation: Polymorphism allows objects of different classes to be treated as objects of a common type. This is often used in combination with inheritance.
Example:
class Character:
def __init__(self, name, health):
self.name = name
self.health = health
def attack(self, target):
print(f"{self.name} attacks {target.name}!")
class Warrior(Character):
def attack(self, target):
print(f"{self.name} swings their sword at {target.name}!")
class Mage(Character):
def attack(self, target):
print(f"{self.name} casts a spell at {target.name}!")
player1 = Warrior("Arthur", 100)
player2 = Mage("Merlin", 80)
characters = [player1, player2]
for character in characters:
character.attack(player2) # Polymorphic call.
Different character classes (Warrior
, Mage
, Archer
) can all have an attack()
method, but each class implements it differently.
Polymorphism lets you treat them all as Character
objects and call attack()
without knowing the specific class.
Decorators modify the behaviour of functions or classes. It acts similarly like a wrapper.
def log_action(func):
def wrapper(*args, **kwargs):
print(f"Action: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_action
def use_potion(character):
print(f"{character.name} uses a potion.")
use_potion(player1)
You could use decorators to log player actions, apply status effects, or add special effects to abilities.
Type hints specify the expected data types of variables and function parameters. Note that type hinting is no type enforcement (like in strongly typed languages (C++)).
def calculate_damage(attack_power: int, defense: int) -> int:
return max(0, attack_power - defense)
damage = calculate_damage(30, 10)
Type hints make it clear what types of data functions expect and return (e.g., int
for damage
, str
for player names).
Data classes simplify the creation of data-centric classes.
from dataclasses import dataclass
@dataclass
class Item:
name: str
value: int
weight: float
sword = Item("Excalibur", 100, 5.0)
print(sword)
In general check the coding guidelines of your project; also PEP8 and most widespread guidelines (like the Google Style Guides) are a good choice. The following only highlights some important points.
As simple as it sounds good naming is very important and you should spend some time to think about it.
Good:
player_health = 100
enemy_attack_power = 20
class GameCharacter:
pass
def calculate_damage(attack, defence):
pass
Good naming makes code self-explanatory...
Bad:
a = 100
b = 20
class GC:
pass
def calc_dmg(x,y):
pass
... bad naming requires extra mental effort to understand.
Ideally one function should do one thing.
Good:
def validate_player_input(input_string):
# simple input validation
pass
def apply_status_effect(player, effect):
# apply status effect logic
pass
def handle_player_turn(player, command):
if validate_player_input(command):
apply_status_effect(player, command)
#more simple logic
Bad:
def handle_player_turn(player, command):
# 50 lines of complex logic here...
if ...:
...
for ...:
...
return result
Short, focused functions are easier to read and test. Long functions are harder to maintain and understand.
Code should be self-explanatory through good naming and structure. When this is not enough add comments where appropriate (often you explain the why of what you are doing if it's not clear). Good codes should always include docstings.
Good:
def calculate_damage(attack_power: int, defense: int) -> int:
"""
Calculates the damage dealt to a target.
Args:
attack_power: The attacker's attack power.
defense: The target's defense.
Returns:
The damage dealt.
"""
return max(0, attack_power - defense)
Bad:
def calc_dmg(x,y):
# does stuff
return max(0, x-y)
Design patterns help to solve common problems.
Check out any favourite book about design patterns and/or the standard work Design Patterns: Elements of Reusable Object-Oriented Software from the Gang of Four (GoF).
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License *.
Code (snippets) are licensed under a MIT License *.
* Unless stated otherwise