« first day (388 days earlier)      last day (809 days later) » 

4:42 AM
@Sciborg, I'm going to brain-dump about my python drawing code in here and if you come in I'd appreciate advice for restructuring.
Okay, so there are a few levels of objects to consider here. There's the Game screen (inherits from pygame.Screen), which has a Board, which has a list of Blocks, which have lists of Points
I'm planning to make Points inherit from pygame.Rect (or I might make a Square class to do that, because I use Point for other reasons which would not involve Rects, e.g. tracking where the center is of a tetris piece)
That's the objects. Now onto drawing the tetris pieces
Each frame, after all keypresses/mouseclicks and subsequent cascading events occur, the main loop calls cur_screen.draw() (for tetris-drawing purposes cur_screen can be assumed to be a Game) to draw the current screen's current state.
The .draw() method in Game in turn will call self.board.draw(self.window) with self.window being something it gets from inheritance, basically it's what everything should be drawn onto
Now, to draw a thing you need:
- something to draw on
- location
- color
- size
I can pass the aforementioned window down as many calls as I want, but I need to figure out where the rest of these come from/should be stored
Tracking color makes sense on a the level of Blocks, I think, because colors are on a by-block basis in Tetris: every part of a piece is the same color
Size is something that I think I'll be storing/calculating on the level of the Board because it knows how big the play area it is (which is not the size of the whole window because other things are on the screen, e.g. a quit button)
Location is twofold: I have to know the Tetris-coordinate based location (a row/column within the board), and then I need to be able to translate that to a pixel-based location
Points are what know Tetris-coordinate location: they're currently serving as glorified tuples for Tetris-coordinate handling abstraction
Boards, however, are what know how to translate Tetris-coordinates to pixel-coordinates, since (again) they know how much space the board should be taking up
If the goal of this overhaul is to not create the Rects just to draw them, they have to be stored somewhere
On creation they'll be given size and color (?) and then stored in Points (?) and moved as the Points move (?)
I could make a class-level variable for Point that would store size, and then set its value from the Board during its instantiation
But that feels a little cheaty in a way? Or bad? Because I'd be assuming that all Points no matter what must abide by the Board's sizing standards, which is valid here because there is but one Board at a time and all Points are associated with it
Also I just realized that besides how big they are the Points (to choose location) would also need to know how much offset to use from the corner of the screen; that's something that the Board knows but not them. And the Board also knows how many columns/rows there are which may affect offset calculations
If the Points have the Rects then I'd expect the .draw for the Board to call one on each Block (?) which would call on each Point to draw its Rect
Or wait, if I make the Point class inherit from Rect I could directly pass it to pygame.draw.rectso it doesn't have to draw itself, the Block could handle drawing all of them
Or I could just leave the Board to do all the drawing by looping through each of it's Block's Points and drawing the corresponding Rects
My design questions boil down to: who knows what, when/how do they update/learn that, and who uses this to draw things, and how do they do that?
All of this is open-ended and probably not thought through well; I'll leave my brain-dump here to perlocate
 
 
16 hours later…
8:53 PM
This is a very common structure issue in code planning - deciding what needs to know what information and how that information should be passed up and down. It sounds like the way you have things set up makes sense but I'm not an expert in Python structuring and I know it can be different from C++/Java in terms of what is considered "good structure."
In theory you always want to follow the principle of least knowledge: if something doesn't need to know something, it shouldn't have access to it. If it does, it should be given access for only as long as needed. Most well-structured programs follow that philosophy and it tends to result in good memory usage.
In this case, you have a situation where drawing requires knowledge of a lot of disparate information from multiple different classes.
That seems to suggest that you should have an overarching drawing or Window class of some kind that takes a Board state and its contained Blocks/rects and draws them, or some other structural change that would not require all kinds of spaghetti-esque passing things around willy nilly.
i.e. you have a Window class -> takes in a Board class that contains:
- its list of Blocks, which in turn each contain
- - lists of Points
and iterates or does some other means of drawing everything that allows you to only pass in one single Board object.
In turn the Board will know its own size and all the Blocks it contains, which allows the Window to request that information as needed for each draw call.
And each Block will know its own Point and be able to draw its own Rect when requested by the higher class.
(Possibly being given parameters to do so from higher up, or maybe storing its current coordinates, or etc.)
So in terms of the call stack you'd have: Window requests the Board to draw everything inside itself, Board iterates over its contained Blocks and requests them to draw themselves, each Block has its contained rect and coordinate information and calls a self-contained draw function given the passed-down Window information.
That makes sense in my head but obviously I can't see your full code layout and this is just a guess of how things could be laid out.
 
9:13 PM
@Sciborg my problem is that too draw themselves, points/blocks need to know how big the Board is and its offset, and how big they are So how would they learn that? A parameter? Class variable? Do lower level things have to calculate it themselves, and if so how do they get the needed information?
 
9:34 PM
@bobble You would pass that down as parameters, in theory - boardWidth, boardHeight or something. My concern is why do blocks need to know that though? Couldn't they just store their own internal coordinates or similar and bypass it?
It wouldn't be something they should calculate themselves, that'd be overkill.
In my head each Block knows where it is on the board and doesn't need anything more than a passed-down rendering object
i.e. a Block has internal class fields to know its own width, height, x-coordinate, and y-coordinate.
(or it gets that info from its contained rect or something)
and that info is updated each time the Block moves
 
9:50 PM
@Sciborg That's the difference between Tetris-coordinates and pixel-coordinates
I store row/col within the tetris board in the Point so they know that, but they don't know how that corresponds to pixels
 
You said that the Board has the function to convert Tetris-coordinates to pixel-coordinates right?
 
The Board currently does the conversion on the fly each draw
 
okay, definitely don't do that - what if you pre-calculated it on creation, stored pixel-coordinates inside each Block, and then each time that Block moves the Board updates its internal coordinates?
and then the Block knows where it is on the board and you can skip the whole problem altogether?
 
because it's way easier for me to understand tetris coordinates than pixel coordinates?
I can move a point one cell by adding or subtracting one from row or col
 
if it's easier for you to look at, the Block could know both its sets of coordinates - it'd be a little more memory but that's okay in a game.
 
9:54 PM
And I can use it for debugging too
And also why does the Block know its coordinates? I thought it would know its Points which would know their coordinates
 
the Points already know their coordinates?
 
The original point of Points were to store and handle row/col coordinates
 
then there ya go, the Block can just reference its Points to know its own coordinates. and if the Block needs the Board's helper function to translate between coordinate sets it can have the function. i'm confused by what the problem is
is it that the translation function needs the Board's information?
 
Yes
 
and you can't pre-calculate the result of the translation function using the Board when it creates Blocks, and store the results in said Blocks?
 
10:01 PM
Store what results? And again, there isn't a function, there's just unsectioned-off calculations within the instantiation of Rects which I'm trying to stop
 
the row/col coordinates translated into pixel coordinates. so that the Blocks can know their own pixel coordinates and not have to reference upwards
 
def _draw_block(self, screen: pygame.Surface, bl: Block, square_size: Tuple[int, int]) -> None:
    for sq in bl.squares:
        # (left, top) coordinate of current square
        top_left = (self.play_area.left + (square_size[0] * (sq.pos[1] - 1)),
                    self.play_area.top + (square_size[1] * (sq.pos[0] - 1)))
        # draw square itself
        pygame.draw.rect(screen, bl.color,
                         pygame.Rect(top_left[0], top_left[1], square_size[0], square_size[1]))
square_size is calculated elsewhere, this is how each block is drawn
 
okay, so you're passing in the screen, one single Block to draw, and its size. my proposition would be that the Block object should contain its size
then you have left top coordinates of the current Block. the Block should also be able to know that
i.e. instead of calculating it within the draw function each time, the Block already has that ready
 
are you confusing Block and Point?
 
where in this function is a Point?
 
10:05 PM
sq is a Point
 
ahhhh okay. so it's saying "for each Point in this block's squares", right?
 
yup, I call the block's points squares for a lot of things because originally Point and Square were separate classes and then I merged them
 
okay, i was confused. so then you're getting the "pos" field from the current Point
and you're doing a calculation with the play area's corners
which is why you needed to know Board info
 
    """A point in space

    For external use:
    - .pos is (row, col) position
    - .row() and .col() to get such position information
    - .down(), .up(), .left(), and .right() to move the Point 1 unit in such direction
    - .teleport(new_pos: Tuple[float, float]) to move the Point to a specified location
    - .__eq__(other), .__ne__(other), .__hash__(), and .__str__() overridden
    """
Point's docstring
 
understanding someone else's code is often tricky, sorry you're bearing with me a bit.
 
10:09 PM
It's probably not the cleanest organization of code but it's well-explained
I'm sorry I'm not good at explaining
 
So, the issue is that to do that calculation with the size of the square and the Point (row, col) stuff, you need to know play_area.left and play_area.top
right?
 
I should probably change it to use .row() and .col(), that way I don't have to worry about which is first or second outside of the Point class
@Sciborg yup
I also am squeamish at .teleport but couldn't figure out a better way to implement rotation
(unrelated)
 
i still think the way to go here is to pre-calculate this. it's not good to be calculating this every frame for every block. i would say you run that calculation once on block creation and only run it once again each time you need to update the Block on movement/teleport/etc.
that can happen in Board if it needs to
 
pre-calculate what?
 
> top_left = (self.play_area.left + (square_size[0] * (sq.pos[1] - 1)),
self.play_area.top + (square_size[1] * (sq.pos[0] - 1)))
this
it gives you the top left corner right?
 
10:12 PM
yes
 
i would say just calculate this once in like an "update-coords" function in Board, each time it adds a new Block to itself, with the parameters being self and the Block object being affected. and then each time a Block moves it recalls the function with the Block and its new position. since the Block knows its own square_size and Point info in this scenario, it doesn't need any other parameters.
store it in a field inside of that Block. then the draw function only needs to have the draw and nothing else, and it can access the Block's top_left field or something
does that vaguely make sense?
 
bobble processing noises
 
> def update_coords(self, bl: Block):
bl.top_left = (self.play_area.left + (square_size[0] * (sq.pos[1] - 1)),
self.play_area.top + (square_size[1] * (sq.pos[0] - 1)))
or something like this
and then draw can just say "bl.top_left"
 
bl or sq?
 
i dunno. however makes sense in your layout
this way you're doing less calculations per frame in exchange for adding a little bit of memory to each Block
 
10:17 PM
Something else I was considering was having the Point class store information like the top left of the play area or how big squares should be
Though it might make more sense if I un-merge Point and Square
 
That would work too if you're comfortable with expanding Point a bit
But the main goal here is to not have to calculate stuff every frame and have some object just know it
 
If the Point class knows some things, or even just the __init__, it can calculate pixel-location on creation. Though it would still need to know size for when it moves
 
That works. Maybe you can pass down size when it moves so it doesn't need to store it in itself.
The architecture needs only to work for you at this stage, so do what you feel makes sense in your head
 
I'm (probably unreasonably) averse to passing down parameters for things because someone told me in one of the intro-to-code classes that passing information down a bunch of calls was a sign the information was in the wrong place
 
In theory yes, if you're passing a whole clump of stuff that's a red flag that you need some class to tie all that stuff together. But it's okay to pass one or two things on a need-to-know basis if that works in your architecture.
The issue of "coupling" and how much classes should communicate with each other will always be something people argue about
There's usually a balance in between being too tightly coupled and not coupled whatsoever
 
10:23 PM
Is it wrong to use the Point class to store information about the surrounding Board?
I'd be making the assumption that only one Board exists and all Points are associated with it
 
i mean, it's not ideal, yes. in theory you'd pass Board information down to it and not store it i guess
but this is a lil Tetris game and it's fine to not have 100% decoupled perfection code
if you know you'll only ever have one Board then it's okay to make that assumption in your architecture
 
Also, why is it bad for the calculations to run every frame? I don't think I caught it. I thought the original problem was creating Rects every frame
 
The number one rule of computer graphics is to minimize calculations-per-frame as much as possible to improve performance and reduce resource usage. That includes anything that has object creation and deletion, property access, etc. - it all creates tiny amounts of milliseconds that add up rapidly if you expand. So any time you have once-per-frame code that has computationally heavy stuff it's good to comb through it and try to reduce it on principle.
It's fine 90% of the time, but it's just the principle of "does this really need to happen every frame or can i precalculate instead?"
That's why games try to pre-calculate shaders, pre-bake lights, etc. - so that light and shading calculations don't constantly happen every frame
 
this shall be placed in the brain-box to marinate
 
make sure to stir the brain-box once every 20 minutes so it doesn't boil over
 
10:32 PM
too late, I'm afraid - it started leaking in high school
Also, Meta this morning - posted a report going "hey, useful thing X is missing" and got a response "yeah we took that out last month"
 

« first day (388 days earlier)      last day (809 days later) »