generated from mwc/project_game
Compare commits
3 Commits
95bb89f422
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e3f92e723 | ||
|
|
c3b2775a80 | ||
|
|
12d0763a95 |
BIN
__pycache__/dungeon.cpython-310.pyc
Normal file
BIN
__pycache__/dungeon.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/enemies.cpython-310.pyc
Normal file
BIN
__pycache__/enemies.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/map.cpython-310.pyc
Normal file
BIN
__pycache__/map.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/monster_spawner.cpython-310.pyc
Normal file
BIN
__pycache__/monster_spawner.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/player.cpython-310.pyc
Normal file
BIN
__pycache__/player.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/projectile.cpython-310.pyc
Normal file
BIN
__pycache__/projectile.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/strategy.cpython-310.pyc
Normal file
BIN
__pycache__/strategy.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/wall.cpython-310.pyc
Normal file
BIN
__pycache__/wall.cpython-310.pyc
Normal file
Binary file not shown.
49
angband.py
Normal file
49
angband.py
Normal file
@@ -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()
|
||||
13
assessment.md
Normal file
13
assessment.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Game Project Assessment
|
||||
|
||||
Pat,
|
||||
|
||||
This is wonderful! The UI is really smooth (given the constraints!),
|
||||
and the game is quite complex. I'm curious about whether you found the
|
||||
framework intuitive and helpful for your thinking.
|
||||
|
||||
The bug you mention in your commit message, of needing to check collisions
|
||||
from both sides, is very common in games like this. I found a funny bug: The
|
||||
game crashes if you manage to get shot with your own bullet.
|
||||
|
||||
Chris
|
||||
27
dungeon.py
Normal file
27
dungeon.py
Normal file
@@ -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"
|
||||
85
enemies.py
Normal file
85
enemies.py
Normal file
@@ -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
|
||||
70
map.py
Normal file
70
map.py
Normal file
@@ -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
|
||||
46
monster_spawner.py
Normal file
46
monster_spawner.py
Normal file
@@ -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
|
||||
120
player.py
Normal file
120
player.py
Normal file
@@ -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
|
||||
7
poetry.lock
generated
Normal file
7
poetry.lock
generated
Normal file
@@ -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"
|
||||
75
projectile.py
Normal file
75
projectile.py
Normal file
@@ -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
|
||||
57
strategy.py
Normal file
57
strategy.py
Normal file
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user