6
\$\begingroup\$

I am making a pvz simulator. We have a game board with 1 = peashooter and 2 = zombie:

[[(1, <__main__.Plant object at 0x0000011F0FDDA4C0>), (2, <__main__.Zombie object at 0x0000011F0F73D100>), 0, 0], 
[0, 0, 0, 0], 
[0, 0, 0, 0]]

When the peashooter object shoots a pea ten times, the zombie is defeated and disappears. If they are not in the same row or the peashooter is in front of the zombie, the pea has no effect on that particular zombie.

My code:

class Object:
    def __init__(self, objtype, row, column, name=None):
        self.pos = self.row, self.column = row, column
        self.name = name
        self.setup = setup
        setup[row][column] = (objtype, self)


class Plant(Object):
    def __init__(self,row, column, name=None, aoe=1, damage=1):
        super().__init__(1, row, column, name)
        self.aoe = aoe
        self.damage = damage

    def shoot(self):
        location = self.setup[self.row][self.column]
        curr_column = self.column
        while True:
            try:
                if location[0] == 2:  # zombie
                    location[1].hit(self)
                    return
            except IndexError:
                pass

            try:
                location = self.setup[self.row][curr_column]
            except IndexError:
                return
            curr_column += 1


class PlantTypes:
    @staticmethod
    def peashooter(row, column, name=None):
        return Plant(row, column, name)


class Zombie(Object):
    def __init__(self, row, column, name=None, health=10):
        super().__init__(2, row, column, name)
        self.health = health

    def hit(self, plant):
        self.health -= plant.damage
        if self.health <= 0:
            setup[self.row][self.column] = 0


class ZombieTypes:
    @staticmethod
    def basezombie(row, column, name=None):
        return Zombie(row, column, name)


setup = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]

p0 = PlantTypes.peashooter(0, 0)
z0 = ZombieTypes.basezombie(0, 1)
print(setup)
for i in range(10):
    p0.shoot()

print(setup)

Any suggestions for readability/performance/other?

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

shoot is currently a method on the Plant, but I think this is a mis-allocation of responsibility. The majority of the work in that function is on the grid instead of on the plant. Consider making a grid class, and obligating it with collision work such as shots.

Similarly, having a zombie delete itself from the grid on death (re-death?) is a mis-allocation of responsibility.

The objtype integer is inconvenient to work with and inadequately constrained. Consider just keeping bare references to the object instances instead of type-object tuples in your grid.

Catching IndexError is a code smell and an indication of an incorrect algorithm. This can be avoided with proper iteration.

Your plant and zombie type classes are redundant and can be deleted.

0 as a default entry in your grid doesn't make much sense since you expect all entries to be type-object pairs. A single None would make more sense when the grid is to contain object references only.

Consider adding pretty-print methods to your classes.

Suggested

from numbers import Real
from typing import Optional


class Object:
    def __init__(self, row: int, column: int, name: Optional[str] = None) -> None:
        self.row, self.column = row, column
        self.name = name


class Plant(Object):
    def __init__(self, row: int, column: int, name: Optional[str] = None, aoe: Real = 1, damage: Real = 1) -> None:
        super().__init__(row, column, name)
        self.aoe = aoe
        self.damage = damage

    def __str__(self) -> str:
        return self.name[:3] if self.name else 'pla'


class Zombie(Object):
    def __init__(self, row: int, column: int, name: Optional[str] = None, health: Real = 10) -> None:
        super().__init__(row, column, name)
        self.health = health

    def hit(self, damage: Real) -> None:
        self.health -= damage

    def __str__(self) -> str:
        return f'Z{self.health:2d}'


class Grid:
    def __init__(self, m: int, n: int) -> None:
        self.m, self.n = m, n
        self.grid: list[list[Optional[Object]]] = [
            [None]*n
            for _ in range(m)
        ]

    def add(self, obj: Object) -> None:
        self.grid[obj.row][obj.column] = obj

    def shoot(self, plant: Plant) -> None:
        row = self.grid[plant.row]
        for col in range(1+plant.column, self.n):
            zombie = row[col]
            if isinstance(zombie, Zombie):
                zombie.hit(plant.damage)
                if zombie.health <= 0:
                    row[col] = None
                break

    def describe(self) -> str:
        return '\n'.join(
            ' '.join(
                f'{str(o):3s}' if o else ' . '
                for o in row
            )
            for row in self.grid
        )


def main() -> None:
    grid = Grid(m=3, n=4)

    p0 = Plant(row=0, column=0, name='peashooter')
    z0 = Zombie(row=0, column=1, name='basezombie')
    grid.add(p0)
    grid.add(z0)
    print(grid.describe())

    for _ in range(10):
        grid.shoot(p0)

    print()
    print(grid.describe())


if __name__ == '__main__':
    main()

Output

pea Z10  .   . 
 .   .   .   . 
 .   .   .   . 

pea  .   .   . 
 .   .   .   . 
 .   .   .   . 
\$\endgroup\$
8
  • \$\begingroup\$ I would prefer the plant shoot to the right. \$\endgroup\$
    – Alan Bagel
    Feb 10, 2022 at 19:57
  • \$\begingroup\$ @Reinderin The output still shows shooting up-to-down. \$\endgroup\$
    – Alan Bagel
    Feb 11, 2022 at 11:58
  • \$\begingroup\$ @BgilMidol Really? The output shows that the zombie to the side disappears. \$\endgroup\$
    – Reinderien
    Feb 11, 2022 at 13:44
  • \$\begingroup\$ ooppss.......... \$\endgroup\$
    – Alan Bagel
    Feb 11, 2022 at 13:56
  • \$\begingroup\$ How would I take the aoe into effect? For example, threepeater shoots in three rows. \$\endgroup\$
    – Alan Bagel
    Feb 11, 2022 at 14:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.