From c3b2775a80665215a1fa4baeaabce86961da6ac0 Mon Sep 17 00:00:00 2001 From: Pat Wick Date: Tue, 12 Mar 2024 20:13:02 -0400 Subject: [PATCH] added a monster spawner and modified lvl mechanics Monsters have a weighting on how many to spawn and roll which specific monster to spawn from the pool. I have not added the ability to advance levels yet, though that should work to add monsters if the Floor number advances. I did notice a bug where the player cannot be harmed by the monsters unless the player moves into them, not the other way around. The monsters seem to treat the player as a wall that cannot be passed through. This is confusing because the code for the player and monsters match as far as their ability to move into each other... --- __pycache__/enemies.cpython-310.pyc | Bin 1694 -> 1754 bytes __pycache__/map.cpython-310.pyc | Bin 2181 -> 2457 bytes __pycache__/monster_spawner.cpython-310.pyc | Bin 0 -> 1596 bytes __pycache__/player.cpython-310.pyc | Bin 2588 -> 3077 bytes __pycache__/projectile.cpython-310.pyc | Bin 2092 -> 2379 bytes angband.py | 23 ++++++---- enemies.py | 20 +++++++-- map.py | 11 ++++- monster_spawner.py | 46 ++++++++++++++++++++ player.py | 39 ++++++++++------- projectile.py | 24 +++++++++- 11 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 __pycache__/monster_spawner.cpython-310.pyc create mode 100644 monster_spawner.py diff --git a/__pycache__/enemies.cpython-310.pyc b/__pycache__/enemies.cpython-310.pyc index 268a583b317153a51a8573e7b6cd63ff24b0008e..e98aad46d213721aa42a0bd3d0f676a6b27f2ce0 100644 GIT binary patch delta 456 zcmZWl&o2W(6rMLbJKZizap(_a5uq+LR5X#0IEX}O!bSE%V>UvHT9s7O8*VO}cXJSR za}o!x{*R4+!O2~`*%L97_q{hW-<$8{yLTd|=hSg*Mr+Ug^k1#8=MjqG%Rt^2o~aB^ zD&ZxRspK{DER|3evmTkp)#h3q-}vHuilo1C1S#kS0XA6BPg`f3+mHkC-Eu0y{&c-oz1(eF z1SgwMHjW#`!C`6&1_>gm{Hm&525EiMM}R{z*Jn~8DZaFX#RMQ4H!y)&v7u)Xb29$H z#WQ2TogmH>MKDRwa<;0CUxGmaU76{fp(j^!rZvo9$H?>Zzh-eVODHooOR9u63(MvX zEMsVH%Q@W_>y2r>6@85LQrq=IHx7}@^p4^lW{ A9RL6T delta 424 zcmcb`JCBzypO=@50SLYdy-hvJK9O$`qt3*Q!V)RWQLHH}QEVx!DQqnaQS2$~DI7qW zW8#DLj69PkFd8woOg_w5!tGGQxPYUEVId)S?9So~G=D0Ku^!0mA}}EUVyOTLH4sA;NL29#C6*u=Tcidw zj9(WhBMVZZ10r-MpJ$d~Dn|H}3FuRn6jrcL*;3d+KAl{`8o_8a`7CR&wIN6kST`1n zZ9$S?iv@xUGE-8E(Cs!g0;(_psRG*#;`wQ^-Qp-NDM~C!O|L9s0?9Fh2v!gQa%z#$ PBv$ds``ILz#f8KHE(BZk diff --git a/__pycache__/map.cpython-310.pyc b/__pycache__/map.cpython-310.pyc index 0f1d3906f60fb0fcd14e352b2e53684c49123563..3e9fd19869dbe6ec6cb6bb9be2f3f40b45311726 100644 GIT binary patch delta 458 zcmXX?ze~eV5Y8p9Nz^vhR#X)A;UH90+}vCg+{E9TNa}mFscF)_q!lGaq3R;&@Fpj9 zb`mF7C(*@G`X6)={R0Fq#XIi1@3_0~aqqnECTsbrRJVe*Ui;z|^U>OdljG~nBBy>W zbR7?KfreP_bB_x<tnAtis|0xWr^ zGU%0UwE$_ks#Sr=7wr|YPnTo{U_?GArmQYJO2=5l2tMUc; zks+n2HjGZC{#RzC-9cnTWkH-){m+O+N zWGvf)-Igx!oL%QXm*j&!tWV-dFd^Udkz|rsU_ly)A*SVrHYyF{ZG0i__am4221Hs_6`X_`IBp+9>rcRT<9 delta 209 zcmbO!+$zYI&&$ij00f#GZ&Tm1ZR87MWb~UX$E-G4nyHYHYjQ7B8zc8*5#|&|p2?NW zX^gy+A21g(GENR+Il(BfS(;Uskx^o@C!5yf0v2Az1(O@u=JVAs2Qz3g`xOZQO?}BS zc^`+0Mt)wZGLTm+1td5aSr~~R$x?ssQ=5tQlvAPg)@*%11Jq-Msu38b8>KSaPtTO0L2<8*#H0l diff --git a/__pycache__/monster_spawner.cpython-310.pyc b/__pycache__/monster_spawner.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e2907202495de16e5bc45c3bf6bb74f879f1288 GIT binary patch literal 1596 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 delta 648 zcmZ8dzi-n(6ux)9TpXtfZqlD11fi)~s}rIV9g)}wB6MK03_gXH#75_)QYA}OrHZ}W zj6}yu-S{86bYP&FFz4zUB-(~AOZ03zd!0@@h_*3cXN%I@@ zPkwZtu9j>UQg5H>JEei=EB$;hRuPZPF?8Ei zRFAj@{AeHH7nJ5JJX@+0F+Z9%AHbUT;3-S2S!}1An4-iV&b!jZH6y9-tt7M;f38w%q3zrA)Id73?>w9CJ#o2iD zT4{Gz-9d>ne(eD3$=NpsY7}HlV_iB?BFGS$ApU%rlb>M2S$FxhaLY<}rD94&FX7(0 z`Cfgs;gnbKcdEsPZ<8z?s`yPl9PSi5dG^S}{_}oFt;+=S1Y}o}6DO(HsJe;lzimws e(Ob~G&jbkYxgYp#Uwo`}!s!w7u(s&27XJ%ND0(XZ diff --git a/angband.py b/angband.py index 4a15517..1ca13a1 100644 --- a/angband.py +++ b/angband.py @@ -1,3 +1,14 @@ +# 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 @@ -9,11 +20,7 @@ from map import ( level_one, random_empty_position ) -from enemies import ( - Orc, - Rat, - Spider -) +from monster_spawner import MonsterSpawner print("Welcome to AngBAD (a poor representation of Angband)!\n") @@ -35,10 +42,8 @@ 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_,}, board_size = 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.add_agent(Orc(random_empty_position(game))) -game.add_agent(Rat(random_empty_position(game))) -game.add_agent(Spider(random_empty_position(game))) game.play() \ No newline at end of file diff --git a/enemies.py b/enemies.py index a6dd311..629591f 100644 --- a/enemies.py +++ b/enemies.py @@ -4,8 +4,11 @@ from strategy import ( ) class Orc: + """Scary. + """ character = "O" - hp = 20 + maxHp = 20 + hp = maxHp deadly = True speed = 25 @@ -25,10 +28,14 @@ class Orc: if self.hp <= 0: game.remove_agent(self) + game.get_agent_by_name("player").xp += self.maxHp class Rat: + """Not so scary. + """ character = "R" - hp = 2 + maxHp = 2 + hp = maxHp deadly = True speed = 15 @@ -48,10 +55,14 @@ class Rat: if self.hp <= 0: game.remove_agent(self) + game.get_agent_by_name("player").xp += self.maxHp class Spider: + """Creepy-crawly. + """ character = "S" - hp = 5 + maxHp = 5 + hp = maxHp deadly = True speed = 5 @@ -70,4 +81,5 @@ class Spider: game.end() if self.hp <= 0: - game.remove_agent(self) \ No newline at end of file + game.remove_agent(self) + game.get_agent_by_name("player").xp += self.maxHp \ No newline at end of file diff --git a/map.py b/map.py index 89597ad..842a8ea 100644 --- a/map.py +++ b/map.py @@ -5,6 +5,9 @@ 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)] @@ -20,7 +23,7 @@ def inner_board(board_size): for j in range(1,y-1): positions.append((i,j)) return positions - + def random_empty_position(game): """Returns a random empty position. """ @@ -43,6 +46,12 @@ def level_one(board_size): 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: diff --git a/monster_spawner.py b/monster_spawner.py new file mode 100644 index 0000000..f164574 --- /dev/null +++ b/monster_spawner.py @@ -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 \ No newline at end of file diff --git a/player.py b/player.py index d6bfcc5..58949c8 100644 --- a/player.py +++ b/player.py @@ -19,7 +19,8 @@ class Player: damage = 0 def __init__(self, position, race, class_): - """Class and race will determine player stats and abilities.""" + """Class and race will determine player stats and abilities. + """ self.position = position self.race = race self.class_ = class_ @@ -47,16 +48,18 @@ class Player: self.damage = int(3 + (self.level / 2)) def attack(self,game): - 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") + """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 @@ -88,6 +91,8 @@ class Player: 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: @@ -96,16 +101,20 @@ class Player: 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): - xpToLevel = (self.level + self.level - 1) * 30 + """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() \ No newline at end of file + self.level_up() + game.state["Level"] = self.level diff --git a/projectile.py b/projectile.py index 2c7c749..633bf6e 100644 --- a/projectile.py +++ b/projectile.py @@ -1,3 +1,9 @@ +# 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: @@ -13,10 +19,18 @@ class Projectile: 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.""" + 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): @@ -34,8 +48,16 @@ class Projectile: 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.