overengineerd tutorial 10 rectangle

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
MrKligson
Posts: 2
Joined: April 17th, 2021, 11:07 pm

overengineerd tutorial 10 rectangle

Post by MrKligson » April 20th, 2021, 2:00 pm

Never thought i'd have fun sliding a white rectangle around the screen, but i did :D
After completing chapter 10's homework i thought i'd set myself an extra challenge.
rules:
1 there should always be at least a 5x5 square of white pixels one the screen
2 you can move the rect as far off screen as you want, al long as rule one is maintained. If you move the opposite way, is should come back in the same size.
3 same as rule 2 but for growing/shrinking (coming back in should keep the new size)

Things i tried / found out:
- can you pass and object a a function parameter? Yep
- tried making all member variables private and make function to access/update them
- I was getting crazy having to update 2 separate blocks of code that were doing the same thing, one for each axis. I used a struct in some function calls and some "? :" conditionals to get the proper values with regard to the axis in the body, which works, but i don't like the look of it... Is there a better way to do this? Is it a bad idea? (telling me to be patient works as well :D)
I've attached the relevant files, if anyone is willing to take a look...

The poo game was a lot of fun as well. My son is starting a programming course next year, so i'll point him to this series to keep him busy during the summer
Attachments
code.zip
(2.73 KiB) Downloaded 135 times

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: overengineerd tutorial 10 rectangle

Post by albinopapa » April 21st, 2021, 11:30 am

Honestly, what you have looks good in general. For instance, if the Rect class was an abstract object then the usage would be fine. The only reason I say it like that is because you've pigeonholed the Rect class to a specific object and traditionally it would be more of a utility object.

By keeping it a utility class you can reuse it for anything from a drawable rectangle to a physical bounding shape used for collision detection. Keeping with that line of thinking, utility classes are usually just simple data structures with public members.

You could keep the Rect class a simple utility class and create another class that has the specific functionality that you want to apply to a Rect object. This way, you get to reuse the Rect class for other tasks, while separating the movement and clipping/clamping responsibilities to the new class.

You could also create utility functions that are agnostic to objects using the Rect class as a utility class. For instance, a clamp function might be a good option.

Code: Select all

// Rect.h
#pragma once
struct Rect
{
	int x = 0;
	int y = 0;
	int width = 0;
	int height = 0;
};

Code: Select all

// Point.h
struct Point{
	int x = 0;
	int y = 0;
};

Code: Select all

// Dimension.h
#pragma once

struct Dimansion{
	int width = 0;
	int height = 0;
};

Code: Select all

// Utility.h
#pragma once

int clamp( int value, int min_value, int max_value ){
	return std::max( min_value, std::min( value, max_value ) );
}

Code: Select all

// Object.h
#pragma once

#include "Keyboard.h"
#include "Graphics.h"
#include "Rect.h"
#include "Point.h"
#include "Dimension.h"
#include "Utility.h"
#include <algorithm>

class Object{
public:
	Object() = default;
	Object( int posx, int posy, int width_, int height_ )
		:
		rect( posx, posy, width_, height_ )
	{}
	void Move( Keyboard& kbd ){
		int dx = 0;
		int dy = 0;

		if( kbd.KeyIsPressed( VK_LEFT ) )	dx -= 1;
		else if( kbd.KeyIsPressed( VK_RIGHT ) )	dx += 1;
		if( kbd.KeyIsPressed( VK_UP ) )		dy -= 1;
		else if( kbd.KeyIsPressed( VK_DOWN ) )	dy += 1;

		rect.x = clamp( rect.x + dx, rect.width, Graphics::ScreenWidth - rect.width - 1 );
		rect.y = clamp( rect.y + dy, rect.height, Graphics::ScreenHeight - rect.height - 1 );
	}
	void Resize( Keyboard& kbd ){
		int dwidth = 0;
		int dheight = 0;

		if( kbd.KeyIsPressed( 'A' ) )		dwidth -= 1;
		else if( kbd.KeyIsPressed( 'D' ) )	dwidth += 1;
		if( kbd.KeyIsPressed( 'W' ) )		dheight -= 1;
		else if( kbd.KeyIsPressed( 'S' ) )	dheight += 1;

		rect.width = clamp( rect.width + dwidth, minWidth, Graphics::ScreenWidth );
		rect.height = clamp( rect.height + dheight, minHeight, Graphics::ScreenHeight );
	}
	void Update( Keyboard& kbd )
	{
		Resize( kbd );
		Move( kbd );
	}
	Rect const& BoundingBox()const{ return rect; }
	Point Position()const{ return { rect.x, rect.y }; }
	Dimansion Size()const{ return { rect.width, rect.height }; }

	void Draw( Graphics& gfx )const{
		gfx.DrawRect( rect, color );
	}

private:
	static constexpr int minWidth = 5;
	static constexpr int minHeight = 5;
	Rect rect = {
		( Graphics::ScreenWidth - 100 ) / 2,
		( Graphics::ScreenHeight - 100 ) / 2,
		100,
		100
	};
	Color color = Colors::White;
};

Code: Select all

// Game.cpp ( just the UpdateModel() and ComposeFrame() functions )
void Game::UpdateModel()
{
	object.Update( wnd.kbd );
}

void Game::ComposeFrame()
{
	object.Draw( gfx );
}

Code: Select all

// Graphics.cpp ( just the DrawRect() function
void Graphics::DrawRect( Rect const& rect, Color color ){
     for( int j = rect.y; j < rect.y + rect.height; ++j ){
          for( int i = rect.x; i < rect.x + rect.width; ++i ){
               PutPixel( i, j, color );
          }
     }
}
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: overengineerd tutorial 10 rectangle

Post by albinopapa » April 21st, 2021, 11:51 am

A few things to note here.

Moved the DrawRect to the Graphics class since it's a generic draw procedure. This way you wouldn't have to recreate it every time you wanted to use it for some rect somewhere else other than Game.

The addition of Point and Dimension is just to give more expression and flexibility to the code. With that you could just pass a Point object around instead of the individual X and Y.

I didn't compose the Rect using Point and Dimension because you'd then have to write:
rect.position.x or rect.dimensions.width. Not a huge deal, but I don't like having to type all that all the time.

I also moved the input handling to the Object class. This is mostly just to clean up the Game::UpdateModel() function, but there is another reason. It keeps the concerns of updating the object with the object itself instead of relying on Game to move and resize the object. Doing stuff like this prevents exposing private data to other parts of the program. Also, you don't have to write accessor functions like X(), Width(), etc...

What I wrote may not behave exactly as you had it, I may have misunderstood some of your code. While the code layout is nicely laid out, the intention of the code wasn't very clear to me and that may fall on me. The best way to write code is to be as expressive as possible. Using concise variable and function names is one way, another is to break code into chunks. This is where the Object::Move() and Object::Resize() functions come in. The ConstrainSize() function you had is another good example of using concise names, however, CheckSize() and returning a value isn't very helpful to me.

So, when I said what you have is fine for an abstract object, what I mean is there are times when using enums to determine behavior is perfectly reasonable and one of the easier ways to handle different behavioral states.

Using enums in this way is often used in what is called a state machine. While an object is in a specific state, it only deals with code concerning that state.

An example would be a boss battle. Surely you've played games where the boss has a few attacks and they might cycle from one attack pattern to another or at a certain point when their health drops to a certain level they use a different attack pattern.

I'm sure there are things I'm missing, but too tired to keep going. Keep the conversation going if you need more info, I or someone will respond eventually.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

MrKligson
Posts: 2
Joined: April 17th, 2021, 11:07 pm

Re: overengineerd tutorial 10 rectangle

Post by MrKligson » April 21st, 2021, 6:47 pm

Oh wow, that was a very helpful reply, thank you!

Some off the stuff goes well over my head on first read (still at beginner's course chapter 11) so i need to digest it a bit.
The tips on how to put the keyboard handling to into my rect class made me very happy. Finally able to remove all that junk from Game.cpp
I did rewrite it a bit today, with some lessons learned from chapter 11 and more thinking on how to name things and keep it more logical for me, but that was before your post:
code.zip
(2.76 KiB) Downloaded 154 times
I tried wrapping my head around programming and object oriented stuff, but things just didn't make sense. I do think i'm getting the hang of it a bit more now. I think i'm sticking with this nice and simple class for a few day and see if i can incorporate some of your tips.

Thanks!

oh, btw, the homework this is based on was to draw a rectangle on screen that you could move with your arrow keys a resize with you WASD (only the bottom right corner would move on resize).
I made it so you can move (of resize) the rectangle of partly screen and back on, retaining it's (re)size. And made it so that even if you move parts offscreen, there would always be, at the very least 5x5 pixels of it still onscreen

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: overengineerd tutorial 10 rectangle

Post by albinopapa » April 22nd, 2021, 7:46 am

Your code looked so nice, I forgot where you were. LOL.

No worries, I wrote that after being up for way too long. Everything looks good, keep it up.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply