Joel Rasdall
Joel Rasdall

Reputation: 25

How to run a while loop affected by multiple variables?

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

Answers (3)

Bill
Bill

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

constantstranger
constantstranger

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

jsstuball
jsstuball

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

Related Questions