Here is the class NPC:
class NPC:
def __init__(self):
self.x = randint(0,800)
self.y = randint(0,60)
self.velocity_x = 5
self.drop_y = 60
self.img = "my image path here"
npc = NPC()
num_npc = 5
list = []
for i in range(num_npc):
list.append(npc)
In the game loop only one image is shown and is stationary.
I'm working on trying to write old code to be object oriented and can't figure out the best way to render the npcs
Below is the old code I was using and it worked as expected
npc_img = []
npc_x = []
npc_y = []
npc_vel_x = []
npc_vel_y = []
num_of_npc = 5
for i in range(num_of_npc):
npc_img.append("my img path")
npc_x.append(random.randint(0, 800))
npc_y.append(random.randint(0, 60))
npc_vel_x.append(4)
npc_vel_y.append(40)
Your code is pretty much correct already. However the way you are creating instances of NPC objects is not quite correct. I guess you meant to add 5 NPCs to the list, not 5 references to the same NPC object. That is what your question title says though!
npc = NPC()
...
for i in range(num_npc):
list.append(npc) # <<-- HERE, same object, 5 times
The code should call the NPC constructor in the loop, rather than outside it.
for i in range( num_npc ):
new_npc = NPC()
list.append( new_npc )
While you're rewriting code, it might be worth keeping the co-ordinates and image dimensions in a Pygame Rect, since this allows for easy collision detection and other nice things.
Something like:
class NPC:
def __init__(self):
self.image = pygame.image.load( "image path here" ).convert_alpha()
self.rect = self.image.get_rect()
self.rect.x = randint(0,800)
self.rect.y = randint(0,60)
self.velocity_x = 5
self.drop_y = 60
def draw( self, screen ):
screen.blit( self.image, self.rect )
def hitBy( self, arrow_rect ):
hit = self.rect.colliderect( arrow_rect )
return hit
If I understood correctly, something like this should work:
class NPC:
def __init__(self):
self.x = randint(0,800)
self.y = randint(0,60)
self.velocity_x = 5
self.drop_y = 60
self.image_list = []
self_image_load_dict = {}
def add_image(self, image_path):
self.image_list.append(image_path)
def load_images(self):
self.image_load_dict[]
for i in len(self.get_image_list()):
self.image_load_dict[i] = pygame.image.load(self.get_image_list()[i])
def get_image_list(self):
return self.image_list
def get_image_load_dict(self):
return self.image_load_dict
I used fstring so it would be easier to load images and keep track of the image number:
npc = NPC()
for i in range(NUMBER_OF_NPC):
npc.add_image(f"image_path_{i}")
Now you have image_paths in the object's list, I assume you want to load them, hence the load_images method.
NOTE: If needed you can create additional method(s) for loading images. E.g. if you have animations for "left" and "right" movement
I hope this answers your question, if I omitted something please say in comment.
Related
I've noticed that QFormLayout in Pyside2 does not have the takeRow method like its PyQt5 counterpart. I've attempted to subclass QFormLayout to incorporate a similar method, but I've run into Runtime Errors, as the removal behavor of the LabelRole item is different than the FieldRole item. Another issue being that the LabelRole item does not actually get taken off the row even when the row itself is removed.
The following is the test sample I've been working with using Python 3.8.6:
from PySide2.QtWidgets import *
import sys
class MyFormLayout(QFormLayout):
def __init__(self, *args, **kwargs):
super(MyFormLayout, self).__init__(*args, **kwargs)
self.cache = []
print(f"Formlayout's identity: {self=}\nwith parent {self.parent()=}")
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row) ## <-- This seems necessary to make the rowCount() decrement. Alternative?
label_item.widget().setParent(None) ## <-- Runtime Error Here?
field_item.layout().setParent(None)
self.cache.append(label_item.widget(), field_item)
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label_item, field_item
def restoreRow(self, insert_idx: int):
print(f"Called {self.restoreRow.__name__}")
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
to_insert = self.cache.pop()
self.insertRow(insert_idx, to_insert[0], to_insert[1])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
class MyWindow(QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.mainlay = MyFormLayout(self)
self.cmb = QComboBox()
self.cmb.addItems(["Placeholder", "Remove 1 and 2"])
self.cmb.currentTextChanged.connect(self.remove_rows_via_combo)
self.current_text = self.cmb.currentText()
self.hlay1, self.le1, self.btn1 = self.le_and_btn(placeholderText="1")
self.hlay2, self.le2, self.btn2 = self.le_and_btn(placeholderText="2")
self.hlay3, self.le3, self.btn3 = self.le_and_btn(placeholderText="3")
self.hlay4, self.le4, self.btn4 = self.le_and_btn(placeholderText="4")
self.remove_btn = QPushButton("Remove", clicked=self.remove_row_via_click)
self.restore_btn = QPushButton("Restore", clicked=self.restore_a_row_via_click)
self.mainlay.addRow("Combobox", self.cmb)
for ii, hlayout in zip(range(1, 5), [self.hlay1, self.hlay2, self.hlay3, self.hlay4]):
self.mainlay.addRow(f"Row {ii}", hlayout)
self.mainlay.addRow(self.remove_btn)
self.mainlay.addRow(self.restore_btn)
#staticmethod
def le_and_btn(**kwargs):
hlay, le, btn = QHBoxLayout(), QLineEdit(**kwargs), QPushButton()
hlay.addWidget(le)
hlay.addWidget(btn)
return hlay, le, btn
def remove_row_via_click(self):
self.mainlay.takeRow(1)
def restore_a_row_via_click(self):
self.mainlay.restoreRow(1)
def remove_rows_via_combo(self, text):
print(f"{self.remove_rows_via_combo.__name__} received the text: {text}")
if text == "Remove 1 and 2":
self.mainlay.takeRow(1)
self.mainlay.takeRow(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
I would like to understand why the behavior of the role items is different and how the method may be properly re-implemented.
The problem is that the label was created internally by Qt from a string, rather than by explicitly creating a QLabel in Python. This means that when the row is removed, the last remaining reference is also removed, which deletes the label on the C++ side. After that, all that's left on the Python side is an empty PyQt wrapper - so when you try to call setParent on it, a RuntimeError will be raised, because the underlying C++ part no longer exists.
Your example can therefore be fixed by getting python references to the label/field objects before the layout-item is removed:
class MyFormLayout(QFormLayout):
...
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
# get refs before removal
label = label_item.widget()
field = field_item.layout() or field_item.widget()
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row)
label.setParent(None)
field.setParent(None)
self.cache.append((label, field))
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label, field
Assuming we have the following object (in reality many more parameters):
class Room:
def __init__(self, h, w, color):
self.h = h
self.w = w
self.color = color
print(f'Room is {self.h}*{self.w} and painted in {self.color}')
Room(0, 1, 'red') # Room is 0*1 and painted in red
Further assume we need to create this object hundreds of times. A few of the parameters need to be changed each time. However, most of the parameters stay consistent within one "session". Therefore, I would like to fix those parameters at the beginning of my session, to make the subsequent calls much simpler.
My current approach looks like this. Surely there is something better out there?
class RoomFactory:
def __init__(self, h=None, w=None, color=None):
self.h = h
self.w = w
self.color = color
# TODO: Get arguments from Room.__init__ automatically?
def create(self, **kwargs):
kwargs_already_set = {k: v for k, v in self.__dict__.items() if v}
return Room(**kwargs_already_set, **kwargs)
# Define some parameters at session beginning. Which parameters depends on session!
rf = RoomFactory()
rf.h = 5
rf.w = 5
# Later, create hundreds of objects with different remaining arguments
rf.create(color='green') # Room is 5*5 and painted in green
rf.create(color='black') # Room is 5*5 and painted in black
I have below Car class. I would like to be able to reset the mileage for all car class instances to 0. How can I do this?
class Car():
carpark = []
# initialising instance
def __init__(self, brand, color, fuel, mileage):
self.brand = brand
self.color = color
self.fuel = fuel
self.mileage = mileage
self.drives = []
Car.carpark.append(self)
#classmethod
def purchase(cls, brand, color):
cls(brand, color, "Diesel", 0)
Since I have the carpark list, i could do something like:
#classmethod
def reset_mileage(cls):
for car in cls.carpark:
car.mileage = 0
This works but I am wondering if there is a better, cleaner way to do this?
It is possible to use Python's property feature to update the attributes lazily - just keep a tick counter on the class, and the corresponding tick on each instance:
class Car:
cls_mileage = 0
cls_mileage_tick = 0
#classmethod
def reset_mileage(cls, value=0):
cls.cls_mileage = value
cls.cls_mileage_tick = value
def __init__(self):
self.mileage_tick = self.cls_mileage_tick
self.mileage = 0
#property
def mileage(self):
if self._mileage_tick < self.cls_mileage_tick:
self._mileage_tick = self.cls_mileage_tick
self._mileage = self.cls_mileage
return self._mileage
#mileage.setter
def mileage(self, value):
self._mileage_tick = self.cls_mileage_tick
self._mileage = value
Here is some explanation on the #property decorator. This is some sort of "reactive pattern" on top of that. - https://www.programiz.com/python-programming/property
Of course, if you need this for a lot of attributes, this pattern requires a lot of code for each one, there are ways to make this generic using the __getattribute__ and __setattr__ magic methods.
Also, if you can be sure of whether you are getting and setting an attribute on the instance or the class itself , there is no need for the extra cls_ prefix on both class attributes: the names can be re-used.
I have been trying to build my skills in Python and am trying to create a risk style game.
I am not far in to it at the moment as I am trying to get to grips with classes and Tkinter.
My first trial is to create a series of buttons to take the place of the different countries. I then want these buttons to update the amount of armies on the country when they are clicked.
So far I have been able to get the map to generate from the class I have created and the buttons are clickable. When a button is clicked it updates the amount of armies but always for the last button.
How do I get it so that the button I click updates and not the last one?
Have I gone about this in entirely the wrong way?
from tkinter import *
import random
class territory:
def __init__ (self, country, player = "1", current_armies = 0, x=0, y=0):
self.country = country
self.current_armies = current_armies
self.player = player
self.y = y
self.x = x
def get_armies(self):
print(self.country + " has " + str( self.current_armies)+ " armies.")
def add_armies (self, armies):
self.current_armies += armies
def roll_dice (self, dice=1):
rolls = []
for i in range(0, dice):
rolls.append(random.randint(1,6))
rolls.sort()
rolls.reverse()
print (self.country + " has rolled " + str(rolls))
return rolls
def owner(self):
print (self.country + " is owned by " + self.player)
def get_country(self):
print(country)
def button (self):
Button(window, text = territories[0].current_armies, width = 10, command = click1(territories, 0)).grid(row=y,column=x)
window = Tk()
def create_territories():
countries = ["UK", "GER", "SPA", "RUS"]
terr_pos = [[1,0],[2,0],[1,5],[4,1]]
sta_arm = [1,1,1,1]
terr = []
player = "1"
for i in range(len(countries)):
terr.append(territory(countries[i],player, sta_arm [i] , terr_pos[i][0],terr_pos[i][1]))
if player == "1":
player = "2"
else:
player = "1"
return terr
def click1(territory, i):
territory[i].current_armies += 1
build_board(territory)
def build_board(territories):
for i in range(0,4):
Button(window, text = territories[i].country+"\n"+str(territories[i].current_armies), width = 10, command = lambda: click1(territories, i)).grid(row=territories[i].y,column=territories[i].x)
territories = create_territories()
window.title ("Domination")
create_territories()
build_board(territories)
window.mainloop()
In your def button(self):... you are always referencing territories[0]:
Button(window, text=territories[0].current_armies,... command=click1(territories, 0)...
As such, you are always using the first territory as your reference, so you ought to initialize each territory with its index in territories[] so you can pass that into your Button constructor.
On your question of "entirely the wrong way," I'd personally send that question over to CodeReview, since that's more of their domain (we fix broken code, they address smelly code), though there is significant overlap. We do prefer one question per question, however, and "is this whole thing wrong?" is a little broad for StackOverflow.
from tkinter import *
import pygame.mixer
app = Tk()
app.title("Head First Mix")
sound_file = "50459_M_RED_Nephlimizer.wav"
mixer = pygame.mixer
mixer.init()
def track_start():
track.play(loops = -1)
def track_stop():
track.stop()
def shutdown():
track.stop()
app.destroy()
def track_toogle():
if track_playing.get() == 1:
track_start()
else:
track_stop()
def change_volume(v):
track.set_volume(volume.get())
track_playing = IntVar()
track = mixer.Sound(sound_file)
track_button = Checkbutton(app,
variable = track_playing,
command = track_toogle,
text = "50459_M_RED_Nephlimizer.wav")
track_button.pack(side = LEFT)
volume = DoubleVar()
volume.set(track.get_volume())
volume_scale = Scale(app,
variable = volume,
from_ = 0.0,
to = 1.0,
resolution = 0.1,
command = change_volume,
label = "Volume",
orient = HORIZONTAL)
volume_scale.pack(side = RIGHT)
app.protocol("WM_DELETE_WINDOW", shutdown)
app.mainloop()
for the moment i read a book called head first python. i understoop everything until i reached chapter 9. why dont my app work if i dont place something in the change_volume functions brackets
why do i need the"v" -
def change_volume(v):
The let you stop, start and adjust volume for a track btw
The code
def change_volume(v):
track.set_volume(volume.get())
is written to require a parameter v. However, as written, it never actually uses the parameter. Instead, since you called volume = track.get_volume() elsewhere in the code, unless you modify volume (or the value of the track's volume) somewhere else, your code is effectively equivalent to
def change_volume(v):
track.set_volume(track.get_volume())
If you want to actually change the value, you need to use the value of the parameter to set the volume:
def change_volume(v):
track.set_volume(v.get())