-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGuide.txt
186 lines (136 loc) · 10.7 KB
/
Guide.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
Guide on How to Build a Browser Game
Intro
Getting started and knowing how to structure the code in an application is one of the most difficult things for students to figure out.
There might be plenty of sources available to learn about how to use a forEach loop, but finding help on how to think like a developer and write code to implement an app's features is difficult at best.
Luckily, you've come to the right place!
In SEI, it's not just about learning how to use languages, libraries and frameworks - it's also about learning how to organize and structure code to implement the features of a web app.
This guide provides you with:
A process and data-centric approach that results in concise code, less bugs, and an app that is easier to add new features to.
How to start a project, and
How to organize/structure your code, in other words, how to "architect" your app.
Client-Side MVC Architectural Approach
Model-View-Controller (MVC) is a popular software architectural pattern that is used to organize code in both client and server applications.
The following diagrams a typical client-side MVC architecture:
Let's briefly review the Model, View and Controller components...
Model
The Model refers to the application's data that needs to be tracked/remembered - this data is often referred to as the application's state.
Data is the single-source of truth in an executing application!
By following a "data-centric" approach, developers can more easily test the application's logic - in fact, we can test out much of the app in the console (assuming you keep your functions and state in global scope while developing the app)! For example, you can type something like getWinner() in the console to check what value is being returned from that function.
An easy mistake new programmers make is using the DOM to hold state - instead, remember to use variables to hold all data that needs to be tracked during runtime.
By following this approach, a developer can re-use much of an application's code if/when the application needs to be ported to other platforms such as mobile and desktop.
View
The View is what the user sees and interacts with.
In a browser app, the View consists of the DOM elements created using HTML, CSS and JavaScript.
The View can be made to "listen" for user actions by adding event listeners to DOM elements for a multitude of DOM events.
Controller
The Controller is the bulk of your app's JavaScript, excluding the state variables (which represent the Model as described above).
The Controller provides the glue between the Model and View (notice how the Model and View don't "know" about each other).
In a browser app, it is the controller that adds event listeners to the View (DOM elements).
When an event occurs, e.g., the user clicks something, the Controller:
Updates the Model variables (state).
Updates the View (DOM), using the data contained in the Model variables (state).
Summary
To summarize, the MVC architectural pattern organizes and structures code in a way that enables:
Code to be more testable, reusable and extendable.
Separation of the View (display) logic and business (application) logic. For example, you might decide to model a game of tic-tac-toe using the values of 1, -1 or null to represent whether a square holds Player X, Player O, or nobody, respectively. However, when it comes time to transfer the app's state to the DOM, you can visualize the state anyway you want, e.g., a value of 1 is "rendered" with a certain image, etc.
Overall Application Flow
Let's see how we might apply the MVC pattern when writing a browser app such as a game.
The following diagram denotes one approach to structuring your code:
Key Points & Best Practices
Use constants instead of literal values to improve code readability and maintenance. For example, let's say you wanted to limit the number of guesses in a game to a certain number.
You could write code like this:
let lose = numGuesses > 5;
However, code like the following which would be more maintainable because you probably will need to use the maximum guesses value in more than one section of code:
let lose = numGuesses > MAX_GUESSES;
Instead of using several separate variables to hold state, consider using object properties when it makes sense to do so. For example, if you need to track info for two players, instead of using several variables like player1name, player2name, player1score, player2score, etc., consider using an object like:
const players = {
'1': {
name: '',
score: 0
},
'-1': {
name: '',
score: 0
}
};
Following this practice will result in more concise code and make it easier to implement certain features such as persisting the state of a game.
Don't store state data that can be computed as needed from other data - this avoids the risk of data becoming out of sync or inconsistent. For example, in Tic-Tac-Toe, it's not necessary to track the number of moves to determine if there's a tie game - the variable used to track the state of the board can already provide this info.
If your code needs to access a DOM element more than once during runtime - cache it (save it in a variable).
The render() function's responsibility is to transfer all state to the DOM. This includes the hiding/showing of parts of the UI based upon the application's state. For example, when a hand is in play in a game of Blackjack, the render() function would show the hit/stand buttons and hide the betting-related buttons. Also, if the render() function becomes too large, you can break it up into smaller functions, e.g., renderScores(),
The overreaching principle to keep in mind is...
In response to user interaction:
Update all state impacted by the interaction, then
Update the DOM by calling render().
Steps to Get Started on Your Browser Game
The following approach has been proven to help students write complex front-end web apps, such as games.
If you're concerned that using the following approach will result in you and your fellow students having code that is structured similarly - don't be! What matters is what prospective employers think when they look at your projects's code structure in GitHub!
Analyze the app's functionality
The app's features, from the user's point of view, should be described using User Stories. User stories follow this template: As a [role], I want [feature] because [reason]. Example user story: As a player, I want to see a list of high-scores so that I know what I have to score to make the list.
Think about the overall design (look & feel) of the app
Take the users (audience) of the app into consideration.
Should the app have a clean/minimalist UI (current trend), or should it be themed to match the app's purpose?
Wireframe the UI
Wireframes provide a blueprint for the HTML & CSS.
Wireframes also help reveal an application's data (state) and functionality.
Pseudocode
Some of the app's features may need to be pseudocoded, that is, outlining the app's logic in a plain language way.
Pseudocode the app's overall functionality first.
More detailed pseudocode for a given feature may be required later.
Identify the application's state (data)
What does the application need to "remember" throughout its execution?
Use the wireframe and pseudocode to help determine what state needs to be tracked.
Set up the project
Create project directory OUTSIDE of any existing git repo (nested repos cause problems).
Create the starting project files. Here's a good structure:
index.html
css/main.css
js/main.js
Create the HTML boilerplate within index.html using ![tab].
Link main.css in the <head>.
Add a <script> tag to load the main.js in the <head> and be sure to use the defer attribute to ensure that the DOM is ready before the script runs:
<script defer src="js/main.js"></script>
Create a local repo
Make your project a local repo with git init.
Next, think about the name for your remote repo on GitHub - using a name that represents your choice of game, e.g., "blackjack", is better than something like "project-1". It's also recommended that the name of the repo and the project directory match.
Create your remote repo in your PERSONAL GitHub account - be sure NOT to check the "Initialize this repository with a README" checkbox (you'll want to touch README.md locally).
Run the command that the GitHub instructions provides to add the remote in your local repo. It will look like this:
git remote add origin <the URL to your repo>
Make your first commit: git add -A, then git commit -m "Initial commit"
Push the commit to the repo for the first time: git push -u origin main. Future pushes can now be made using just git push.
Organize the app's JS into sections
Copy/paste the following comment headings to help you organize your app's code:
/*----- constants -----*/
/*----- app's state (variables) -----*/
/*----- cached element references -----*/
/*----- event listeners -----*/
/*----- functions -----*/
Code away!
Start with some markup for the basic layout of the UI. If an element's content is going to come from the render function, you may want to temporarily add mock content to help with layout and styling, however, once the content is being provided by the render function, you should remove the mock content from the index.html.
Declare, but don't initialize, the application-wide state variables. The initialization of the variables to their "initial" state should be done within an initialize, or similarly named function, e.g., init.
Write that initialize function.
Invoke initialize() to "kick off" the app.
Now that the initialize function has set the state variables, the last line in initialize should be render(); to render the state to the DOM for the first time.
Stub up that render function.
After state has been updated in an event listener, the last line of code in the listener function should be a call to render(); again, to render the state to the DOM.
Register event listeners - be sure to use event delegation!
Code the event listener(s)...
More recommendations for interactive browser app's, such as games
If the render function becomes lengthy, add additional rendering oriented functions, for example:
function render() {
renderHands();
renderControls();
if (winner) {
renderWinnerMessage();
} else {
renderTurnMessage();
}
}
Avoid accessing the DOM from outside render-oriented functions. However, "eye candy" animations, a ticking time display, etc. are exceptions to this tip.
Data (state) is the single source of truth of the app - when implementing an app's logic, the DOM is secondary to data manipulation. Get used to thinking about how state changes when the player interacts, e.g., clicks something.
Again, as the user interacts with the application code the event listener such that it:
Updates state, then...
Calls render()
Make frequent git commits of working code
At a minimum, commit each "milestone" or feature implementation.
Experiment and refactor code as necessary
Have fun!