generated from mwc/project_game
Compare commits
2 Commits
95bb89f422
...
c3b2775a80
Author | SHA1 | Date |
---|---|---|
|
c3b2775a80 | |
|
12d0763a95 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,49 @@
|
|||
# angband.py
|
||||
# ------------
|
||||
# By Pat Wick
|
||||
# This game is a redevelopment of the retro game "Angband". Named after
|
||||
# the stronghold of Morgoth, the Sauron before Sauron in the Lord of the Rings,
|
||||
# the game is a dungeon-crawler adventure game where the player is tasked with
|
||||
# delving into Angband to confront Morgoth. Defeating monsters earns the player
|
||||
# experience points (xp) which allow for more power as the player levels up.
|
||||
# In this, v0.2, a general movement and combat system exists, but level
|
||||
# generation, items, and monster spawning won't happen until future versions.
|
||||
|
||||
from retro.game import Game
|
||||
from player import Player
|
||||
from dungeon import Dungeon
|
||||
from random import sample
|
||||
from wall import Wall
|
||||
from map import (
|
||||
board_edges,
|
||||
inner_board,
|
||||
level_one,
|
||||
random_empty_position
|
||||
)
|
||||
from monster_spawner import MonsterSpawner
|
||||
|
||||
print("Welcome to AngBAD (a poor representation of Angband)!\n")
|
||||
|
||||
race = input("Choose your race (Human, Elf, Dwarf): ").capitalize()
|
||||
while race not in ["Human", "Elf", "Dwarf"]:
|
||||
print("Invalid race. Please choose Human, Elf, or Dwarf.")
|
||||
race = input("Choose your race (Human, Elf, Dwarf): ").capitalize()
|
||||
|
||||
class_ = input("Choose your class (Warrior, Mage, Rogue): ").capitalize()
|
||||
while class_ not in ["Warrior", "Mage", "Rogue"]:
|
||||
print("Invalid class. Please choose Warrior, Mage, or Rogue.")
|
||||
class_ = input("Choose your class (Warrior, Mage, Rogue): ").capitalize()
|
||||
|
||||
print(f"\nYou've chosen to play as a {race} {class_}.")
|
||||
input("Press Enter to continue. Good luck!")
|
||||
|
||||
board_size = (50,25)
|
||||
x,y = board_size
|
||||
|
||||
walls = [Wall(position) for position in board_edges(board_size)]
|
||||
level = [Wall(position) for position in level_one(board_size)]
|
||||
game = Game(walls + level, {"Race":race, "Class":class_,"CharLevel":1,"Floor":1}, board_size = board_size)
|
||||
game.add_agent(MonsterSpawner())
|
||||
game.add_agent(Player((x//2,y//2),race,class_))
|
||||
|
||||
game.play()
|
|
@ -0,0 +1,27 @@
|
|||
# dungeon.py
|
||||
# ------------
|
||||
# By Pat Wick
|
||||
# This module defines a dungeon generation algorithm. I
|
||||
# still need to figure out what that might actually mean
|
||||
|
||||
class Dungeon:
|
||||
board_size = (10,10)
|
||||
board_width = 10
|
||||
board_height = 10
|
||||
position = (0,0)
|
||||
dungeon_map = [["."] * board_width] * board_height
|
||||
|
||||
|
||||
for row in range(board_height):
|
||||
for col in range(board_width):
|
||||
if row == 0 or row == board_height-1:
|
||||
dungeon_map[row][col] = "#"
|
||||
else:
|
||||
if col == 0 or col == board_width-1:
|
||||
dungeon_map[row][col] = "#"
|
||||
else:
|
||||
dungeon_map[row][col] = "."
|
||||
|
||||
def __init__(self, position):
|
||||
self.position = position
|
||||
self.name = "dungeon"
|
|
@ -0,0 +1,85 @@
|
|||
from strategy import (
|
||||
random_move,
|
||||
move_toward_player,
|
||||
)
|
||||
|
||||
class Orc:
|
||||
"""Scary.
|
||||
"""
|
||||
character = "O"
|
||||
maxHp = 20
|
||||
hp = maxHp
|
||||
deadly = True
|
||||
speed = 25
|
||||
|
||||
def __init__(self,position):
|
||||
self.position = position
|
||||
|
||||
def play_turn(self, game):
|
||||
if game.turn_number % self.speed == 0:
|
||||
move = move_toward_player(self.position, game)
|
||||
if move:
|
||||
x, y = self.position
|
||||
dx, dy = move
|
||||
self.position = (x + dx, y + dy)
|
||||
if self.position == game.get_agent_by_name("player").position:
|
||||
game.state['message'] = "Yum."
|
||||
game.end()
|
||||
|
||||
if self.hp <= 0:
|
||||
game.remove_agent(self)
|
||||
game.get_agent_by_name("player").xp += self.maxHp
|
||||
|
||||
class Rat:
|
||||
"""Not so scary.
|
||||
"""
|
||||
character = "R"
|
||||
maxHp = 2
|
||||
hp = maxHp
|
||||
deadly = True
|
||||
speed = 15
|
||||
|
||||
def __init__(self, position):
|
||||
self.position = position
|
||||
|
||||
def play_turn(self, game):
|
||||
if game.turn_number % self.speed == 0:
|
||||
move = random_move(self.position, game)
|
||||
if move:
|
||||
x, y = self.position
|
||||
dx, dy = move
|
||||
self.position = (x + dx, y + dy)
|
||||
if self.position == game.get_agent_by_name("player").position:
|
||||
game.state['message'] = "Eep."
|
||||
game.end()
|
||||
|
||||
if self.hp <= 0:
|
||||
game.remove_agent(self)
|
||||
game.get_agent_by_name("player").xp += self.maxHp
|
||||
|
||||
class Spider:
|
||||
"""Creepy-crawly.
|
||||
"""
|
||||
character = "S"
|
||||
maxHp = 5
|
||||
hp = maxHp
|
||||
deadly = True
|
||||
speed = 5
|
||||
|
||||
def __init__(self,position):
|
||||
self.position = position
|
||||
|
||||
def play_turn(self, game):
|
||||
if game.turn_number % self.speed == 0:
|
||||
move = random_move(self.position, game)
|
||||
if move:
|
||||
x, y = self.position
|
||||
dx, dy = move
|
||||
self.position = (x + dx, y + dy)
|
||||
if self.position == game.get_agent_by_name("player").position:
|
||||
game.state['message'] = "Hsssss."
|
||||
game.end()
|
||||
|
||||
if self.hp <= 0:
|
||||
game.remove_agent(self)
|
||||
game.get_agent_by_name("player").xp += self.maxHp
|
|
@ -0,0 +1,70 @@
|
|||
from retro.game import Game
|
||||
from random import sample
|
||||
from player import Player
|
||||
from wall import Wall
|
||||
from random import randint
|
||||
|
||||
def board_edges(board_size):
|
||||
"""The outline of the generated board. Used in angband to surround
|
||||
the level with immovable objects to keep the enemies and player inside
|
||||
"""
|
||||
x,y = board_size
|
||||
positions = []
|
||||
top = [(i,0) for i in range(x)]
|
||||
bottom = [(i,y-1) for i in range(x)]
|
||||
left = [(0,j) for j in range(1,y-1)]
|
||||
right = [(x-1,j) for j in range(1,y-1)]
|
||||
return top + bottom + left + right
|
||||
|
||||
def inner_board(board_size):
|
||||
x,y = board_size
|
||||
positions = []
|
||||
for i in range(1,x-1):
|
||||
for j in range(1,y-1):
|
||||
positions.append((i,j))
|
||||
return positions
|
||||
|
||||
def random_empty_position(game):
|
||||
"""Returns a random empty position.
|
||||
"""
|
||||
agents_by_position = game.get_agents_by_position()
|
||||
while True:
|
||||
x, y = game.board_size
|
||||
i = randint(1, x-2)
|
||||
j = randint(1, y-2)
|
||||
if not agents_by_position[(i,j)]:
|
||||
return (i,j)
|
||||
|
||||
def level_one(board_size):
|
||||
x,y = board_size
|
||||
positions = []
|
||||
for i in range(1,x-1):
|
||||
for j in range(1,y//4):
|
||||
if i <= x // 4 or i >= x - (x // 4):
|
||||
positions.append((i,j))
|
||||
for i in range(1,x//4):
|
||||
for j in range((y - (y // 4)), y-1):
|
||||
positions.append((i,j))
|
||||
|
||||
# Introduce randomness within predefined pattern
|
||||
for _ in range(10): # Example: Add 10 random obstacles
|
||||
rand_i = randint(1, x - 2)
|
||||
rand_j = randint(1, y - 2)
|
||||
positions.append((rand_i, rand_j))
|
||||
|
||||
# for i in range(1,x-1):
|
||||
# for j in range(1,y-1):
|
||||
# if i >=4 and i <= 7 or i >= 13 and i <= 16:
|
||||
# if j >= 4 and j <= 7 or j >= 13 and j <= 16:
|
||||
# positions.append((i,j))
|
||||
return positions
|
||||
|
||||
def level_two(board_size):
|
||||
x,y = board_size
|
||||
positions = []
|
||||
for i in range(1,x-1):
|
||||
for j in range(1,y-1):
|
||||
if i >=4 and i <= 7 or i >= 13 and i <= 16:
|
||||
if j >= 4 and j <= 7 or j >= 13 and j <= 16:
|
||||
positions.append((i,j))
|
||||
return positions
|
|
@ -0,0 +1,46 @@
|
|||
# asteroid_spawner.py
|
||||
# -------------------
|
||||
# By MWC Contributors
|
||||
# This module defines an AsteroidSpawner agent class.
|
||||
|
||||
from random import (
|
||||
randint,
|
||||
choices,
|
||||
)
|
||||
from enemies import *
|
||||
from map import random_empty_position
|
||||
|
||||
class MonsterSpawner:
|
||||
display = False
|
||||
floor = 0
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def play_turn(self, game):
|
||||
"""Places each of the monsters on the board for that level.
|
||||
"""
|
||||
toSpawn = self.should_spawn_monsters(game.state["Floor"])
|
||||
|
||||
for i in range(toSpawn):
|
||||
monster = self.choose_monster()(random_empty_position(game))
|
||||
game.add_agent(monster)
|
||||
|
||||
|
||||
def should_spawn_monsters(self, floor_number):
|
||||
"""Returns the number of monsters to spawn, given the player
|
||||
advanced a floor.
|
||||
"""
|
||||
numMonsters = 0
|
||||
if floor_number != self.floor:
|
||||
numMonsters = randint(1, floor_number // 10 + 3)
|
||||
self.floor = floor_number
|
||||
return numMonsters
|
||||
|
||||
def choose_monster(self):
|
||||
"""Picks a random monster out of a weighted list of monsters.
|
||||
"""
|
||||
monsters = [Orc, Rat, Spider]
|
||||
monster = choices(monsters, weights = (10, 30, 60))
|
||||
monster = monster[0]
|
||||
return monster
|
|
@ -0,0 +1,120 @@
|
|||
# player.py
|
||||
# ------------
|
||||
# By Pat Wick
|
||||
# This module defines a player agent class. This is intended
|
||||
# to be used in an implementation of an adventure game but could
|
||||
# generally be adapted for other player character uses.
|
||||
|
||||
from retro.agent import ArrowKeyAgent
|
||||
from retro.game import Game
|
||||
from projectile import Projectile
|
||||
|
||||
class Player:
|
||||
name = "player"
|
||||
level = 1
|
||||
xp = 0
|
||||
direction = (1,0)
|
||||
class_ = ""
|
||||
speed = 0
|
||||
damage = 0
|
||||
|
||||
def __init__(self, position, race, class_):
|
||||
"""Class and race will determine player stats and abilities.
|
||||
"""
|
||||
self.position = position
|
||||
self.race = race
|
||||
self.class_ = class_
|
||||
if class_.capitalize() == "Warrior":
|
||||
self.color = "red"
|
||||
self.class_ == class_
|
||||
elif class_.capitalize() == "Rogue":
|
||||
self.color = "green"
|
||||
self.class_ == class_
|
||||
else:
|
||||
self.color = "blue"
|
||||
self.class_ == class_
|
||||
|
||||
if race.capitalize() == "Human":
|
||||
self.character = "H"
|
||||
self.speed = 3
|
||||
self.damage = int(2 + (self.level / 3))
|
||||
elif race.capitalize() == "Elf":
|
||||
self.character = "E"
|
||||
self.speed = 1
|
||||
self.damage = int(1 + (self.level / 4))
|
||||
else:
|
||||
self.character = "D"
|
||||
self.speed = 6
|
||||
self.damage = int(3 + (self.level / 2))
|
||||
|
||||
def attack(self,game):
|
||||
"""Warrior is a melee character, mage and rogue use ranged attacks.
|
||||
"""
|
||||
# if self.class_ == "Warrior":
|
||||
# if game.turn_number % self.speed == 0:
|
||||
# agent = self.get_agent_in_position((self.position[0] + self.direction[0],self.position[1] + self.direction[1]),game)
|
||||
# if agent:
|
||||
# if agent.deadly:
|
||||
# agent.hp -= game.get_agent_by_name("player").damage * 2
|
||||
# else:
|
||||
projectile = Projectile((self.position[0] + self.direction[0],self.position[1] + self.direction[1]), self.direction, self.speed, game)
|
||||
game.add_agent(projectile)
|
||||
#print("pew pew pew")
|
||||
|
||||
def handle_keystroke(self, keystroke, game):
|
||||
x, y = self.position
|
||||
if keystroke.name in ("KEY_LEFT", "KEY_RIGHT"):
|
||||
if keystroke.name == "KEY_LEFT":
|
||||
new_position = (x - 1, y)
|
||||
self.direction = (-1,0)
|
||||
else:
|
||||
new_position = (x + 1, y)
|
||||
self.direction = (1,0)
|
||||
if game.on_board(new_position):
|
||||
self.try_to_move(new_position,game)
|
||||
if game.is_empty(new_position):
|
||||
self.position = new_position
|
||||
|
||||
if keystroke.name in ("KEY_DOWN", "KEY_UP"):
|
||||
if keystroke.name == "KEY_DOWN":
|
||||
new_position = (x, y + 1)
|
||||
self.direction = (0,1)
|
||||
else:
|
||||
new_position = (x, y - 1)
|
||||
self.direction = (0,-1)
|
||||
if game.on_board(new_position):
|
||||
self.try_to_move(new_position,game)
|
||||
if game.is_empty(new_position):
|
||||
self.position = new_position
|
||||
|
||||
if keystroke == " ":
|
||||
self.attack(game)
|
||||
|
||||
def try_to_move(self, position, game):
|
||||
"""Check if player moved into an enemy and loses.
|
||||
"""
|
||||
agent = self.get_agent_in_position(position,game)
|
||||
if agent:
|
||||
if agent.deadly:
|
||||
game.state['message'] = "Monsters can be deadly..."
|
||||
game.end()
|
||||
|
||||
|
||||
def get_agent_in_position(self, position, game):
|
||||
"""Checks a location for current agents.
|
||||
"""
|
||||
agents = game.get_agents_by_position()[position]
|
||||
if agents:
|
||||
return agents[0]
|
||||
|
||||
def level_up(self):
|
||||
"""Player levelup is managed by an xp curve.
|
||||
"""
|
||||
xpToLevel = (self.level + self.level - 1) * 20
|
||||
if self.xp >= xpToLevel:
|
||||
self.xp -= xpToLevel
|
||||
self.level += 1
|
||||
|
||||
def play_turn(self,game):
|
||||
self.level_up()
|
||||
game.state["Level"] = self.level
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
|
||||
package = []
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "53f2eabc9c26446fbcc00d348c47878e118afc2054778c3c803a0a8028af27d9"
|
|
@ -0,0 +1,75 @@
|
|||
# projectile.py
|
||||
# ------------
|
||||
# By Pat Wick
|
||||
# This module defines a "casted" projectile. This is the basis
|
||||
# for ranged character types' attacks.
|
||||
|
||||
from retro.game import Game
|
||||
|
||||
class Projectile:
|
||||
character = "*"
|
||||
deadly = False
|
||||
|
||||
def __init__(self, position, direction, speed, game):
|
||||
self.position = position
|
||||
self.direction = direction
|
||||
self.speed = speed
|
||||
if game.get_agent_by_name("player").class_ == "Rogue":
|
||||
if self.direction in [(1,0), (-1,0)]:
|
||||
self.character = "-"
|
||||
elif self.direction in [(0,1), (0,-1)]:
|
||||
self.character = "|"
|
||||
if game.get_agent_by_name("player").class_ == "Warrior":
|
||||
if self.direction in [(0,1), (0,-1)]:
|
||||
self.character = "|"
|
||||
elif self.direction == (1,0):
|
||||
self.character = "/"
|
||||
else:
|
||||
self.character = "\\"
|
||||
|
||||
def move(self, game):
|
||||
"""Try to move in direction set by player when launched. If blocked,
|
||||
disappear. If projectile hits an enemy, lower hp by damage.
|
||||
"""
|
||||
dx, dy = self.direction
|
||||
new_position = (self.position[0] + dx, self.position[1] + dy)
|
||||
if game.on_board(new_position):
|
||||
if game.is_empty(new_position):
|
||||
self.position = new_position
|
||||
else:
|
||||
agent = self.get_agent_in_position(new_position,game)
|
||||
if agent:
|
||||
if agent.deadly:
|
||||
agent.hp -= game.get_agent_by_name("player").damage
|
||||
game.remove_agent(self)
|
||||
else:
|
||||
game.remove_agent(self)
|
||||
else:
|
||||
game.remove_agent(self)
|
||||
|
||||
def play_turn(self,game):
|
||||
"""Speed of projectiles depends on character race.
|
||||
"""
|
||||
if game.turn_number % self.speed == 0:
|
||||
self.move(game)
|
||||
try:
|
||||
if game.get_agent_by_name("player").class_ == "Warrior":
|
||||
game.remove_agent(self)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def get_agent_in_position(self, position, game):
|
||||
"""Returns an agent at the position, or returns None.
|
||||
game.get_agents_by_position always returns a list, which may
|
||||
contain zero, one, or multiple agents at the given position.
|
||||
In the Beast game, we never allow more than one agent to be in
|
||||
a position.
|
||||
"""
|
||||
agents = game.get_agents_by_position()[position]
|
||||
if agents:
|
||||
return agents[0]
|
||||
|
||||
def handle_collision(self, game):
|
||||
# need to fix this at some point
|
||||
pass
|
|
@ -0,0 +1,57 @@
|
|||
from random import choice
|
||||
|
||||
direction_vectors = [(0, 1), (1, 0), (0, -1), (-1, 0)]
|
||||
|
||||
def possible_moves(position, game):
|
||||
"Returns a list of vectors to empty spaces"
|
||||
agents_by_position = game.get_agents_by_position()
|
||||
possible_moves = []
|
||||
for vector in direction_vectors:
|
||||
x, y = position
|
||||
dx, dy = vector
|
||||
new_position = (x + dx, y + dy)
|
||||
if not agents_by_position[new_position]:
|
||||
possible_moves.append(vector)
|
||||
return possible_moves
|
||||
|
||||
def random_move(position, game):
|
||||
"Returns a random vector representing a move to an empty space from position"
|
||||
moves = possible_moves(position, game)
|
||||
if moves:
|
||||
return choice(moves)
|
||||
|
||||
def distance(p0, p1):
|
||||
"""Returns the 'manhattan distance' from one position to another
|
||||
The 'manhattan distance' describes the distance from one point to another
|
||||
on a city grid, where you can only go horizontally and vertically, not
|
||||
diagonally.
|
||||
"""
|
||||
x0, y0 = p0
|
||||
x1, y1 = p1
|
||||
return abs(x1 - x0) + abs(y1 - y0)
|
||||
|
||||
def move_toward_player(position, game):
|
||||
"Returns a move which will come closest to the player"
|
||||
player_position = game.get_agent_by_name("player").position
|
||||
moves = possible_moves(position, game)
|
||||
moves_with_distance = []
|
||||
for vector in moves:
|
||||
x, y = position
|
||||
dx, dy = vector
|
||||
new_position = (x + dx, y + dy)
|
||||
distance_to_player = distance(new_position, player_position)
|
||||
moves_with_distance.append((distance_to_player, vector))
|
||||
if moves_with_distance:
|
||||
shortest_distance, best_move = sorted(moves_with_distance)[0]
|
||||
return best_move
|
||||
|
||||
def move_to_player(position, game):
|
||||
player_position = game.get_agent_by_name("player").position
|
||||
for vector in direction_vectors:
|
||||
x, y = position
|
||||
dx, dy = vector
|
||||
new_position = (x + dx, y + dy)
|
||||
if new_position == player_position:
|
||||
return vector
|
||||
|
||||
|
Loading…
Reference in New Issue