Reputation: 25
I'm very new at Python, and I've searched for this answer for a few days now and I'm not seeing an answer. If I missed a post, I apologize and will happily go read that instead. This is more of a conceptual question, so just skimming for answers hasn't gotten me far yet.
I'm trying to use a pen-and-paper RPG system I know as a springboard for learning Python, and I want to divide up turns not just by player order, but by character speed as well.
sample code (there's more, but this is the problem part):
class char:
def __init__(self,name,side,Spd):
self.name=name
self.side=side
self.Spd=Spd
hero=char("Jimbo","good",4)
helplessSidekick=char("Timmy","good",2)
thug1="Crusher","evil",3)
thug2="Bruiser","evil",3)
So Jimbo spd 4, Timmy spd 2, Crusher spd 3, Bruiser spd 3. For the listed characters, I'd want the turn order for one round of combat to be: J,C,B,T,J,C,B,T,J,C,B,J sort of counting down each one until they're all out of speed. I can use a for loop to generate the initial turn order, no problem. But I need a while loop to actually run this because one of them can be defeated and thus no longer in the turn order, natch--and of course that turn order can fall apart if Jimbo KOs Crusher on his first move.
I've been staring at this for a few days and I'm stumped. I apologize in advance for the beginner nature of the question; I've been having a ton of fun with this, but I definitely have a lot to learn. Thank you!
Upvotes: 1
Views: 462
Reputation: 11643
I like @constantstranger's answer but another simple way to achieve this would be to just drop each player from a list as the game progresses using list.remove
or list.pop
.
class Char:
def __init__(self, name, side, Spd):
self.name=name
self.side=side
self.Spd=Spd
def take_turn(char):
print(f"{char.name}'s turn...")
char.Spd -= 1
hero = Char("Jimbo","good",4)
helplessSidekick = Char("Timmy","good",2)
thug1 = Char("Crusher","evil",3)
thug2 = Char("Bruiser","evil",3)
in_game_chars = [hero, thug1, thug2, helplessSidekick]
i = 0 # index of character to go first
while len(in_game_chars) > 0:
i = i % len(in_game_chars)
char = in_game_chars[i]
take_turn(char)
if char.Spd < 1:
in_game_chars.pop(i)
else:
i += 1
print("Game over")
Output:
Jimbo's turn...
Crusher's turn...
Bruiser's turn...
Timmy's turn...
Jimbo's turn...
Crusher's turn...
Bruiser's turn...
Timmy's turn...
Jimbo's turn...
Crusher's turn...
Bruiser's turn...
Jimbo's turn...
Game over
The standard way to iterate indefinitely over a list of items in Python is the cycle iterable. Unfortunately, it does not allow items to be removed from the cycle after instantiation. So you have to rebuild the cycle iterator every time you change the list. And then you need a way to start the cycle at a specified index to make sure the order of the players is not disrupted:
from itertools import cycle, islice
in_game_chars = [hero, thug1, thug2, helplessSidekick]
i = 0 # index of character to go first
while len(in_game_chars) > 0:
for char in islice(cycle(in_game_chars), i, None):
take_turn(char)
if char.Spd < 1:
i = in_game_chars.index(char)
# Remove player
in_game_chars.pop(i)
break
print("Game over")
Finally, you could also use filter
or filterfalse
which conditionally include/drop items in the list:
in_game_chars = [hero, thug1, thug2, helplessSidekick]
while len(in_game_chars) > 0:
in_game_chars = list(filter(lambda x: x.Spd > 0, in_game_chars))
for char in in_game_chars:
take_turn(char)
print("Game over")
Upvotes: 1
Reputation: 9379
Here's one way to do it, with the collection of characters remaining unchanged, but a hash table (a set
in Python) of inactive characters growing as characters are killed off.
For illustrative purposes, I've hardcoded a game with 5 rounds and a predestined future in which Crusher meets their demise in turn 2 at the hands of Jimbo.
class char:
def __init__(self,name,side,Spd):
self.name=name
self.side=side
self.Spd=Spd
hero=char("Jimbo","good",4)
helplessSidekick=char("Timmy","good",2)
thug1=char("Crusher","evil",3)
thug2=char("Bruiser","evil",3)
global inactiveChars
inactiveChars = set()
global turns
turns = 5
def gameOver():
global turns
over = turns == 0
if turns > 0:
turns -= 1
return over
def killChar(name):
global inactiveChars
inactiveChars.add(name)
print(f"Oof! {name} was KO'ed")
def takeTurn(turnNumber, charName):
if turnNumber == 2 and charName == "Jimbo":
killChar("Crusher")
chars = [hero, helplessSidekick, thug1, thug2]
chars.sort(key=lambda character:character.Spd, reverse=True)
curTurn = 0
while not gameOver():
curTurn += 1
print(f"======== Turn number {curTurn}:")
for ch in chars:
if ch.name not in inactiveChars:
print(f"It's {ch.name}'s turn")
takeTurn(curTurn, ch.name)
Sample output:
======== Turn number 1:
It's Jimbo's turn
It's Crusher's turn
It's Bruiser's turn
It's Timmy's turn
======== Turn number 2:
It's Jimbo's turn
Oof! Crusher was KO'ed
It's Bruiser's turn
It's Timmy's turn
======== Turn number 3:
It's Jimbo's turn
It's Bruiser's turn
It's Timmy's turn
======== Turn number 4:
It's Jimbo's turn
It's Bruiser's turn
It's Timmy's turn
======== Turn number 5:
It's Jimbo's turn
It's Bruiser's turn
It's Timmy's turn
Upvotes: 1
Reputation: 4951
Your indentations are invalid syntax but I'm assuming this is just code formatting typos.
In any case you aren't using idiomatic capitalization conventions. In particular the class name should be upper case leading letter. Why is only Spd
upper case first letter and not the other parameters?
I suspect the substance of your query is about editing a container whilst iterating over it. Naively doing this, the iterator won't be privy to the changes you make to the container whilst iterating over it. Often best practice is to iterate over a copy of the container whilst editing the original.
Upvotes: 0