Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS OOP Frogger Game Task #450

Merged
merged 8 commits into from
Oct 9, 2022
Merged

JS OOP Frogger Game Task #450

merged 8 commits into from
Oct 9, 2022

Conversation

franchukv
Copy link
Contributor

@franchukv franchukv commented Sep 5, 2022

JS OOP Frogger Game

Demo | Code base

The code is submitted in a dedicated feature branch.

Only code files are submitted.

Fixed all bugs or defects which other students received:

  1. Now enemies don't 'blink' when creating.
  2. Now user visually can see that he crossed the road successfully.

Please, review.

@github-actions
Copy link

github-actions bot commented Sep 5, 2022

Hey!

Congratulations on your PR! 😎😎😎

Let's do some self-checks to fix most common issues and to make some improvements to the code before reviewers put their hands on the code.

Go through the requirements/most common mistakes listed/linked below and fix the code as appropriate.

If you have any questions to requirements/common mistakes feel free asking them here or in Students' chat.

When you genuinely believe you are done put a comment stating that you have completed self-checks and fixed code accordingly.

Also, be aware, that if you would silently ignore this recommendation, a mentor can think that you are still working on fixes. And your PR will not be reviewed. 😒

Frogger Arcade Game -- JS OO exercise check list

Relates to Object-Oriented JavaScript task.

Check-list - definition of done

  • employ ES6 features like const, let etc. (with exclusion of ES6 class syntax)

  • the code is very DRY

  • Requirements re Constants:

    • all numbers like block dimensions, initial locations are defined as named constants (e.g. const STEP = 101;) as otherwise numbers scattered across code base look cryptic; named constants add semantic meaning and improve readability
    • every number that has a semantic purpose (like those listed above) should be defined as constants; think of how your code reads - the closer to plain English the better
    • there are core constants and derived constants
      (e.g. derived constant const FIELD_WIDTH = BLOCK_WIDTH * BLOCKS_NUMBER;)
    • arrays of constants are also constants
      (e.g. const INITIAL_POSITIONS = [1,2,3,4].map(rowNumber => rowNumber * BLOCK_HEIGHT);)
    • const objects help organizing and structure const data even better
      (e.g. const PLAYER_CONF = { initialPosition: {x: 1, y: 5}, sprite: '...', ...etc... };
  • Requirements re OOP:

    • OO is implemented using JS prototype chain object model (not ES6 classes syntax)
    • classes do not refer to any global variables, like global variable player, which is an instance of Player class
      (referring to global constants and globals provided by the gaming platform like Resources is OK);
      Hint: pass Player instance as an argument to every enemy
    • Separation of Concerns principle is followed
      (e.g. update method does only rendering and doesn't contain any unrelated inline code; for example collision check is defined as a dedicated method and only called from inside update)
    • Nice To Have: properties common for some classes are generalized into a base class
      (e.g. there is Character base class, which is extended by Enemy and Player classes)
    • class extension is implemented using Subclass.prototype = Object.create(Superclass.prototype), not Subclass.prototype = new Superclass(params);; Helpful resource
  • Most common mistakes

    • Make sure target = condition ? valueWhenConditionTrue : valueWhenConditionFalse is used instead of condition ? target = valueWhenConditionTrue : target = valueWhenConditionFalse; Conditional (ternary) operator

Universal recommendations:

  • Give variables and functions meaningful names. Avoid generic names like item, element, key, object, array or their variations. Exception: helper functions that are specifically and intentionally designed to be multipurpose.
  • Function names should start with a verb as they denote actions; variables are normally nouns; boolean variables/functions start with is, does, has etc; variable containing multiple entities and functions returning lists contain entity name in plural form.
  • Have consistent code style and formatting. Employ Prettier to do all dirty work for you.
  • Use common sense or seek for an advice whenever requirements look ambiguous or unclear.

Also take a note of the requirements above and follow them in all your future projects.

By the way, you may proceed to the next task before this one is reviewed and merged.

Sincerely yours,
Submissions Kottachecker 😺

@franchukv
Copy link
Contributor Author

Self-check done.

@stale
Copy link

stale bot commented Sep 19, 2022

This issue has been automatically marked as stale because there were no activity during last 14 days. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

А. Чому так?
Найбільш розповсюджена причина: Студент не реагує на коментарі змінами коду і не задає запитань через брак часу або зміну життєвих пріоритетів. Покинуті піари відволікають менторів. Коли у студента з'явиться час, він/вона зможе перевідкрити той самий піар і продовжити роботу.

Б. Що робити, якщо в піарі нема оновлень, оскільки не зрозуміло, що треба зробити?
Варіант 1. Задати питання в самому PR.
Варіант 2. Задати питання в студентському чаті.

В. А якщо я все зробив(ла) і це ментор не рев'юває мої зміни?

  1. Переконайся, що ти відреагував(ла) на всі коментарі або кодом, або запитаннями, або відповідями. Напиши в PR і в чаті, що чесно вважаєш, що все зроблено і попроси повторне рев'ю. Якщо щось не зрозуміло, задай запитання.
  2. Реагуй на коментарі як менторів, так і інших учасників, включаючи ботів.
  3. Не ігноруй прохання типу * "Let's do some self-checks ..." * "Go through the checklist below..." * "mark fulfilled requirements..." * "if you would silently ignore this recommendation, a mentor may think that you are still working on fixes"
    навіть якщо вони написані ботом. Боти помічники і ментори покладаються на те, що прохання і пропозиції бота дотримуються.
    Не лінись піти по лінках в коментарях, погуглити термінологію та скористатись Google Translate.
  4. Можливо, у менторів склалися інші пріоритети через роботу, сімейні обставини і т.п. В такому разі, якщо ти зробив(ла) рекомендоване вище, то волай в чаті, що PR позначений stale, наче, все зроблено, а ментори чомусь не реагують - рятуйте!

Г. Хіба недостатньо того, що я додав(ла) коміт із змінами?
Часто буває так, що бачиш новий коміт, ідеш перевіряти, змін багато, доводиться перечитувати весь код. А потім з'ясовується, що одна невеличка зміна "відкладена на потім" чи з'являється ще один коміт і знов треба перечитувати все. Любіть нас, спілкуйтеся з нами - і ми відповімо повною взаємністю.

Традиційна пропозиція: задай питання по вищенаписаному в студентському чаті.

@stale stale bot added the 💤 Stale label Sep 19, 2022
@OleksiyRudenko OleksiyRudenko added the self-check-done Student confirmed that self-checks against requirements/common-mistakes are done label Sep 20, 2022
@stale stale bot removed the 💤 Stale label Sep 20, 2022
Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franchukv pleas double-check yourself against requirements.
Feel free asking specific questions in Students' chat if anything is not clear.

Comment on lines 79 to 85
Enemy.prototype.setStartEnemiesPosition = function () {
for (let i = 0; i < 3; i++) {
let y = i * TILE_HEIGHT + 50;
let x = i * -TILE_WIDTH;
allEnemies.push(new Enemy(x, y));
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class should handle its instances. Class is designated to handle a single instance.
Also from the check-list:

classes do not refer to any global variables, like global variable player, which is an instance of Player class
(referring to global constants and globals provided by the gaming platform like Resources is OK);
Hint: pass Player instance as an argument to every enemy.

The code has this issue in the fragments below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With hope that I correctly understand this requirement, I fixed this. Please review again.

Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franchukv need another iteration of fixes.

for (let i = 0; i < 3; i++) {
let y = i * TILE_HEIGHT + 50;
let x = i * -TILE_WIDTH;
allEnemies.push(new Enemy(x, y, player));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with allEnemies is exactly the same as it was with player.
You need to recognize the problems of the same class.
Not illegal but a bit wierd to let an enemy to add itself to a naturally external container.
We can call this behavior "enlist yourself in squad" here, but normally we want entities being decoupled from containers for them. This way we separate concerns (management of an individual vs management of a set of individuals).
You may keep this as an Enemy's method, but need to solve global variable reference problem.

Also player here is a global variable. Do not expect mentors to red-pen problems of the same class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, now I understand what does mean 'illegal' variables in this context. Rewrote app, please review again)

)
) {

player.resetPlayerPosition();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check your code for "illegal" global variables.

Copy link
Contributor Author

@franchukv franchukv Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked and fixed

};

const allEnemies = [];
const enemy = new Enemy();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WHy do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this, fixed

const TILE_HEIGHT = 83;
const TILE_WIDTH = 101;
const ENEMY_SPEED = 80;
const PLAYER_STAT = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "STAT" mean?
Do not use abbreviations that are not conventional in software development.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means "characteristics", just a wrong naming, fixed

Comment on lines 59 to 60
this.width = 80;
this.height = 40;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like enemy and player share some features.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yes, but only 'height', not sure that a reason to create parent class for them or a variable which will contains their equal value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting constants is yet another way to keep similar things in sync.

P.S. Shared properties and behaviour is a good enough reason to have a base class to encapsulate behaviour and properties handling. We just do not focus on this in this task. Just "a really-really nice to have". In OOP Exercise there are no excuses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, got it, thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review again, I rewrote the app using constants.

Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franchukv pretty well done!
Let's add some clarity.
Code is written for other people.

ctx.drawImage(Resources.get(this.sprite), this.x, this.y);
};

const Enemy = function ({ size }, enemy, player) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function declaration says that the function expects and enemy as one of the parameters.
Is it actually an instance of class Enemy expected here?

Copy link
Contributor Author

@franchukv franchukv Oct 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood the question right, the answer will be: actually no, we expect basis properties of 'enemy' for creating an instance of class Enemy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, renamed.

Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franchukv yet another thing.

Comment on lines 118 to 122
const allEnemies = [
new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[0], player),
new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[1], player),
new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[2], player),
];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a peer developer I am tasked to add 20 more enemies.
Adding 20 more nearly identical rows of code is a no-go.
Can we make this code DRYer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can use loop into 'addEnemiesToInitArray' function to add instance of enemies from 'ENEMIES CONF.enemy' to 'allEnemies'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

2. Added function 'addEnemiesToInitArray'
2. Now user visually can see that he crossed the road successfully.
@OleksiyRudenko OleksiyRudenko added the UAT-done Student confirmed User Acceptance Tests are done and collected feedback is processed label Oct 3, 2022
Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franchukv let's polish it in order to practise best practices in software development.

Comment on lines 117 to 121
function addEnemiesToInitArray() {
ENEMIES_CONF.enemies.forEach((enemy) => {
allEnemies.push(new Enemy(ENEMIES_CONF, enemy, player));
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extension to the requirement re global variables. It is good to avoid global variables as much as possible.
Well designed function relies on explicitly passed parameters only.
Read about pure functions to learn why.
You do not need to fix this in this code.

width: 40
},
position: {
x: 202,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could calculate this from TILE_WIDTH programmatically. So whenever tile width changes, this param changes accordingly and automagically.

width: 80
},
enemies: [{
x: -80,
Copy link
Member

@OleksiyRudenko OleksiyRudenko Oct 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could pass repeating value at new Enemy moment.
E.g. new Enemy({ x: -80, ...ENEMY_CONF}, ....) new Enemy(size, {x: WHATEVER_CONST, ...enemy} player). Upd: corrected due to misread of not obvious code.

Would make enemies config 33% shorter. This would also help to change the number in a single place should we need to do this.

this.checkCollision();

if (this.x >= CANVAS_WIDTH) {
this.x = -80;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants help make code DRY and numbers semantically comprehensible.


function addEnemiesToInitArray() {
ENEMIES_CONF.enemies.forEach((enemy) => {
allEnemies.push(new Enemy(ENEMIES_CONF, enemy, player));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, why do we send all enemies configuration to every enemy?
I see that size is extracted at receiving party. But is would much more obvious if we use the opportunity to send exactly what is needed

Copy link
Member

@OleksiyRudenko OleksiyRudenko Oct 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And enemy here, actually, is not an instance of Enemy but rather enemy physical parameters.
It would be great if this was clear from reading the code without need to scroll up to class definition to understand what is going on in this line of code. On big codebases it would be a real pain.

const ENEMIES_CONF = {
size: {
height: 40,
width: 80
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any correlation with -80?

},
{
x: -80,
y: 220,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can y be calculated from row number?

@@ -0,0 +1,136 @@
const CANVAS_HEIGHT = 606;
const CANVAS_WIDTH = 505;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there is correlation between tile width and canvas dimensions. Let computer do the calcs.

1. Rewrote game engine for visual demonstration of new features (I'm not sure is it okay or not for this task).
2. Added simple functionality to choose size of game desk.
3. Now all math calculating by a computer.
4. Enemies added automatically into game based on game desk size.
@franchukv
Copy link
Contributor Author

I did a little junior magic, and I'm not sure that everything is ok, please review..)

Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franchukv great job!
Employ passing whatever a function needs to deal with as arguments to functions and return from functions whatever they do rather than directly use/change global variables.
It makes code cleaner.

Comment on lines +133 to +134
const allEnemies = [];
createEnemies(ENEMIES_CONF, TILE_HEIGHT, TILE_WIDTH);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const allEnemies = createEnemies(...) would be more obvious as it would self-explain the effect of createEnemies

@OleksiyRudenko OleksiyRudenko merged commit a896250 into kottans:main Oct 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
self-check-done Student confirmed that self-checks against requirements/common-mistakes are done Stage0.1 task-Frogger UAT-done Student confirmed User Acceptance Tests are done and collected feedback is processed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants