Skip to content

chore: run Prettier on games #19943

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

Merged
merged 4 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Just as with .eslintrc.js, I think it would be better to go with .prettierrc.js (using module.exports = { ... } syntax, as this allows adding comments (while JSON) doesn't allow this.

"bracketSameLine": true
}
115 changes: 63 additions & 52 deletions files/en-us/games/anatomy/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tags:
- Main Loop
- requestAnimationFrame
---

{{GamesSidebar}}

This article looks at the anatomy and workflow of the average video game from a technical point of view, in terms of how the main loop should run. It helps beginners to modern game development understand what is required when building a game and how web standards like JavaScript lend themselves as tools. Experienced game programmers who are new to web development could also benefit, too.
Expand Down Expand Up @@ -55,13 +56,14 @@ But do not immediately assume animations require frame-by-frame control. Simple

There are two obvious issues with our previous main loop: `main()` pollutes the `{{ domxref("window") }}` object (where all global variables are stored) and the example code did not leave us with a way to _stop_ the loop unless the whole tab is closed or refreshed. For the first issue, if you want the main loop to just run and you do not need easy (direct) access to it, you could create it as an Immediately-Invoked Function Expression (IIFE).

<!-- prettier-ignore-start -->
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious: When do you add these?

Copy link
Contributor

Choose a reason for hiding this comment

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

MMmh. When I reviewed this PR, this morning, it happened that GitHub hid some previously-changed files . I would really like to avoid these as much as possible (read maybe 3-4 places on the whole of MDN), but this can be fixed in a follow-up PR. (The way games/ is doing examples is unusual for MDN).

```js
/*
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*/
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*/

;(() => {
function main() {
Expand All @@ -73,22 +75,24 @@ There are two obvious issues with our previous main loop: `main()` pollutes the
main(); // Start the cycle
})();
```
<!-- prettier-ignore-end -->

When the browser comes across this IIFE, it will define your main loop and immediately queue it for the next frame. It will not be attached to any object and `main` (or `main()` for methods) will be a valid unused name in the rest of the application, free to be defined as something else.

> **Note:** In practice, it is more common to prevent the next `requestAnimationFrame()` with an if-statement, rather than calling `cancelAnimationFrame()`.

For the second issue, stopping the main loop, you will need to cancel the call to `main()` with `{{ domxref("window.cancelAnimationFrame()") }}`. You will need to pass `cancelAnimationFrame()` the ID token given by `requestAnimationFrame()` when it was last called. Let us assume that your game's functions and variables are built on a namespace that you called `MyGame`. Expanding our last example, the main loop would now look like:

<!-- prettier-ignore-start -->
```js
/*
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*/
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*/

;(() => {
function main() {
Expand All @@ -100,6 +104,7 @@ For the second issue, stopping the main loop, you will need to cancel the call t
main(); // Start the cycle
})();
```
<!-- prettier-ignore-end -->

We now have a variable declared in our `MyGame` namespace, which we call `stopMain`, that contains the ID returned from our main loop's most recent call to `requestAnimationFrame()`. At any point, we can stop the main loop by telling the browser to cancel the request that corresponds to our token.

Expand Down Expand Up @@ -134,15 +139,16 @@ const tNow = window.performance.now();

Back to the topic of the main loop. You will often want to know when your main function was invoked. Because this is common, `window.requestAnimationFrame()` always provides a `DOMHighResTimeStamp` to callbacks as an argument when they are executed. This leads to another enhancement to our previous main loops.

<!-- prettier-ignore-start -->
```js
/*
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*/
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*/

;(() => {
function main(tFrame) {
Expand All @@ -155,6 +161,7 @@ Back to the topic of the main loop. You will often want to know when your main f
main(); // Start the cycle
})();
```
<!-- prettier-ignore-end -->

Several other optimizations are possible and it really depends on what your game attempts to accomplish. Your game genre will obviously make a difference but it could even be more subtle than that. You could draw every pixel individually on a canvas or you could layer DOM elements (including multiple WebGL canvases with transparent backgrounds if you want) into a complex hierarchy. Each of these paths will lead to different opportunities and constraints.

Expand All @@ -168,15 +175,16 @@ You will need to make hard decisions about your main loop: how to simulate the a

If your game can hit the maximum refresh rate of any hardware you support then your job is fairly easy. You can update, render, and then do nothing until VSync.

<!-- prettier-ignore-start -->
```js
/*
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*/
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*/

;(() => {
function main(tFrame) {
Expand All @@ -189,6 +197,7 @@ If your game can hit the maximum refresh rate of any hardware you support then y
main(); // Start the cycle
})();
```
<!-- prettier-ignore-end -->

If the maximum refresh rate cannot be reached, quality settings could be adjusted to stay under your time budget. The most famous example of this concept is the game from id Software, RAGE. This game removed control from the user in order to keep its calculation time at roughly 16ms (or roughly 60fps). If computation took too long then rendered resolution would decrease, textures and other assets would fail to load or draw, and so forth. This (non-web) case study made a few assumptions and tradeoffs:

Expand Down Expand Up @@ -229,34 +238,35 @@ A separate update and draw method could look like the following example. For the

> **Warning:** This example, specifically, is in need of technical review.

<!-- prettier-ignore-start -->
```js
/*
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*
* MyGame.lastRender keeps track of the last provided requestAnimationFrame timestamp.
* MyGame.lastTick keeps track of the last update time. Always increments by tickLength.
* MyGame.tickLength is how frequently the game state updates. It is 20 Hz (50ms) here.
*
* timeSinceTick is the time between requestAnimationFrame callback and last update.
* numTicks is how many updates should have happened between these two rendered frames.
*
* render() is passed tFrame because it is assumed that the render method will calculate
* how long it has been since the most recently passed update tick for
* extrapolation (purely cosmetic for fast devices). It draws the scene.
*
* update() calculates the game state as of a given point in time. It should always
* increment by tickLength. It is the authority for game state. It is passed
* the DOMHighResTimeStamp for the time it represents (which, again, is always
* last update + MyGame.tickLength unless a pause feature is added, etc.)
*
* setInitialState() Performs whatever tasks are leftover before the main loop must run.
* It is just a generic example function that you might have added.
*/
* Starting with the semicolon is in case whatever line of code above this example
* relied on automatic semicolon insertion (ASI). The browser could accidentally
* think this whole example continues from the previous line. The leading semicolon
* marks the beginning of our new line if the previous one was not empty or terminated.
*
* Let us also assume that MyGame is previously defined.
*
* MyGame.lastRender keeps track of the last provided requestAnimationFrame timestamp.
* MyGame.lastTick keeps track of the last update time. Always increments by tickLength.
* MyGame.tickLength is how frequently the game state updates. It is 20 Hz (50ms) here.
*
* timeSinceTick is the time between requestAnimationFrame callback and last update.
* numTicks is how many updates should have happened between these two rendered frames.
*
* render() is passed tFrame because it is assumed that the render method will calculate
* how long it has been since the most recently passed update tick for
* extrapolation (purely cosmetic for fast devices). It draws the scene.
*
* update() calculates the game state as of a given point in time. It should always
* increment by tickLength. It is the authority for game state. It is passed
* the DOMHighResTimeStamp for the time it represents (which, again, is always
* last update + MyGame.tickLength unless a pause feature is added, etc.)
*
* setInitialState() Performs whatever tasks are leftover before the main loop must run.
* It is just a generic example function that you might have added.
*/

;(() => {
function main(tFrame) {
Expand Down Expand Up @@ -293,6 +303,7 @@ A separate update and draw method could look like the following example. For the
main(performance.now()); // Start the cycle
})();
```
<!-- prettier-ignore-end -->

Another alternative is to do certain things less often. If a portion of your update loop is difficult to compute but insensitive to time, you might consider scaling back its frequency and, ideally, spreading it out into chunks throughout that lengthened period. An implicit example of this was found over at The Artillery Blog for Artillery Games, where they [adjust their rate of garbage generation](https://web.archive.org/web/20161021030645/http://blog.artillery.com/2012/10/browser-garbage-collection-and-framerate.html) to optimize garbage collection. Obviously, cleaning up resources is not time sensitive (especially if tidying is more disruptive than the garbage itself).
Copy link
Member

@bsmth bsmth Aug 25, 2022

Choose a reason for hiding this comment

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

I have a general comment:

I find lines like this hard to read where you have a paragraph on one line, essentially. If there are changes to a word in a sentence in the middle, the entire line is affected in a diff.

Prettier can format these using proseWrap in prettier config:

{
  "proseWrap": "always",
}

An alternative is to write one sentence per line, but this is a manual effort.

Copy link
Contributor

Choose a reason for hiding this comment

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

We don't have a consensus on this:

  • Some authors write one (long) sentence per line
  • Some authors write one paragraph per line
  • Some tooling break anywhere between a word if the line is greater than 80 or 120 chars
  • Some authors put every clause on a different line. (There was a blog post about this, but I can't find this.)

I think we are far from a consensus. More, I'm not sure we can find a solution that we agree on, and that is enforceable easily to the long tail of editors.

To be fair, I don't think it is a coincidence that Prettier does nothing about this by default.

Copy link
Member

Choose a reason for hiding this comment

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

My two cents: we're taking the initiative to remove the ambiguity and discussions about code style in example code using Prettier, we could do the same for the .md prose so that there's consistency there, also. Thanks for the context, though, I appreciate this is not simple 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is a discussion worth having once we have set up Prettier and/or Markdownlint. "Stück für Stück" 🤣 .

Copy link
Member

Choose a reason for hiding this comment

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

Stück für Stück

Sounds good to me 😂

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we certainly had a strong consensus on this: #6936 .

Copy link
Collaborator

@wbamberg wbamberg Aug 25, 2022

Choose a reason for hiding this comment

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

we're taking the initiative to remove the ambiguity and discussions about code style in example code using Prettier, we could do the same for the .md prose so that there's consistency there, also. Thanks for the context, though, I appreciate this is not simple 😄

I agree with this though, we should run Prettier on Markdown. When we converted from HTML, we ran it through Prettier as well. There are things markdownlint does that Prettier does not (AFAIK things like italic syntax or heading syntax) so we want both.

(Apparently I'm wrong about this. I don't know whether there are things markdownlint does that Prettier does not.)

Copy link
Member

@Josh-Cena Josh-Cena Aug 25, 2022

Choose a reason for hiding this comment

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

I have a strong preference for not wrapping at all—or at least not wrap by column width. I configured my editor to soft wrap and I encourage everyone to do so (you can also do that on GitHub).

If you wrap by column width, and you add one word on the first line, you would possibly trigger tens of lines of diff due to the reflow.

I'm okay with soft breaking by sentences (in that I won't call it out or change it if others do that), but IMO it makes reading the content from the source a little "bumpy", like reading poetry or one of those poorly structured Twitter threads.


Expand Down
7 changes: 3 additions & 4 deletions files/en-us/games/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tags:
- Games
- Web
---

{{GamesSidebar}}

This page lists a number of impressive web technology demos for you to get inspiration from, and generally have fun with. A testament to what can be done with JavaScript, WebGL, and related technologies. The first two sections list playable games, while the second is a catch-all area to list demos that aren't necessarily interactive/games.
Expand Down Expand Up @@ -52,10 +53,8 @@ This page lists a number of impressive web technology demos for you to get inspi

- [A Wizard's Lizard](http://www.wizardslizard.com/)
- : Top down Zelda-esque exploration/RPG.

**[Bullet Force](https://www.crazygames.com/game/bullet-force-multiplayer)**
3D multiplayer first-person shooter.

- [Bullet Force](https://www.crazygames.com/game/bullet-force-multiplayer)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was the manual intervention

- : 3D multiplayer first-person shooter.
- [Elliot Quest](https://elliotquest.com/)
- : 8-bit graphic retro adventure game.
- [RPG MO](https://data.mo.ee/index2.html)
Expand Down
1 change: 1 addition & 0 deletions files/en-us/games/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ tags:
- JavaScript Games
- Web
---

{{GamesSidebar}}

Gaming is one of the most popular computer activities. New technologies are constantly arriving to make it possible to develop better and more powerful games that can be run in any standards-compliant web browser.
Expand Down
1 change: 1 addition & 0 deletions files/en-us/games/introduction/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tags:
- Guide
- Mobile
---

{{GamesSidebar}}

The modern web has quickly become a viable platform not only for creating stunning, high quality games, but also for distributing those games.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tags:
- HTML
- Mobile
---

{{GamesSidebar}}

## Advantages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ tags:
- Web Stores
- distribution
---

{{GamesSidebar}}

You've followed a [tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript) or [two](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser) and created an HTML game — that's great! This article covers all you need to know about the ways in which you can distribute your newly created game into the wild. This includes hosting it yourself online, submitting it to open marketplaces, and submitting it to closed ones like Google Play or the iOS App Store.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ tags:
- iap
- licensing
---

{{GamesSidebar}}

When you've spent your time building a game, [distributing](/en-US/docs/Games/Publishing_games/Game_distribution) it and [promoting](/en-US/docs/Games/Publishing_games/Game_promotion) it you should consider earning some money out of it. If your work is a serious endeavour on the path to becoming an independent game developer able to make a living, read on and see what your options are. The technology is mature enough; now it's just about choosing the right approach.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ tags:
- blog
- competitions
---

{{GamesSidebar}}

Developing and publishing your game is not enough. You have to let the world know that you have something interesting available that people will enjoy playing. There are many ways to promote your game — most of them being free, so even if you're struggling to make a living as an indie dev with zero budget you can still do a lot to let people know about your great new game. Promoting the game helps a lot when [monetizing](/en-US/docs/Games/Publishing_games/Game_monetization) it later on too, so it's important to do it correctly.
Expand All @@ -26,7 +27,7 @@ You should definitely create your own website containing all the information abo

You should also blog about everything related to your gamedev activities. Write about your development process, nasty bugs you encounter, funny stories, lessons learned, and the ups and downs of being a game developer. Continually publishing information about your games will help educate others, increase your reputation in the community, and further improve SEO. A further option is to publish [monthly reports](https://end3r.com/blog/?s=monthly+report) that summarize all your progress — it helps you see what you've accomplished throughout the month and what's still left to do, and it keeps reminding people that your game is coming out soon — building buzz is always good.

While you can create your website from scratch, there are also tools that can help make the process easier. [ManaKeep](https://manakeep.com) is a website builder made for indie game developers and provides a great starting point to create your website. [Presskit()](https://dopresskit.com/) is a press kit builder that helps you create a press page to share with the media.
While you can create your website from scratch, there are also tools that can help make the process easier. [ManaKeep](https://manakeep.com) is a website builder made for indie game developers and provides a great starting point to create your website. [Presskit()](https://dopresskit.com/) is a press kit builder that helps you create a press page to share with the media.

## Social media

Expand Down
1 change: 1 addition & 0 deletions files/en-us/games/publishing_games/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ tags:
- distribution
- publishing
---

{{GamesSidebar}}

HTML games have a huge advantage over native in terms of publishing and distribution — you have the freedom of distribution, promotion and monetization of your game on the Web, rather than each version being locked into a single store controlled by one company. You can benefit from the web being truly multiplatform. This series of articles looks at the options you have when you want to publish and distribute your game, and earn something out of it while you wait for it to become famous.
Expand Down
Loading