Representing the quest composition
In this task, we declare the quest level and then display the level in the quest composition view.
Prepare for lift off
We will need three more JavaScript files in this task, so let's create them. These files are as follows:
- patch.js: This file is used for adding methods to existing built-in objects to enhance convenience
- composition.js: We use this file for logic that represents the composition
- quest.js: These files represent the data of a quest, including the level and quest data manipulating methods
At the end of this task, we should be able to create a composition of patterns according to our level. For example, the following quest composition is composed of four patterns: a circle, the left and right trapezoid, and lines, as shown in the following screenshot:
Engage thrusters
We put patterns into composition using the following steps:
- To start with, in HTML, we want to remove the dummy
gameover
link and the finish link from the game scene. As we are now putting real content in the game scene, these two links may affect the placement of our game elements:<div id="game-scene" class="scene out"> <div id="stage">Stage 1</div> <div id='quest' class="quest"> <div id="quest-composition" class="composition"></div> </div> </div> <div id='element-template'> <!-- for composition view --> <div class="pattern" data-pattern='1'></div> </div>
- We will import the three files at the end of HTML file. They are
patch.js
,composition.js
, andquest.js
:<script src='js/patch.js'></script> <script src='js/composition.js'></script> <script src='js/quest.js'></script> <script src='js/scenes.js'></script> <script src='js/game.js'></script>
- We want to make it easy to remove all the DOM elements from the quest view. This is why we have a patch file. Put the following code in the
patch.js
file to add theremoveAllChildren
method to all the DOM nodes:// add removeAllChildren to Node object. Node.prototype.removeAllChildren = function() { while(this.firstChild) { this.removeChild(this.firstChild); } };
- Then, we add a basic skeleton to the
composition.js
file:(function(){ var game = this.colorQuestGame = this.colorQuestGame || {}; // composition model definition // composition is a deck of pattern put together var Composition = game.Composition = (function(){ function Composition(){ this.data = []; } return Composition; })(); })();
- In the
quest.js
file, we represent the level of data in arrays. The number is the pattern. We will discuss how we come up with this special array structure later:(function(){ var game = this.colorQuestGame = this.colorQuestGame || {}; // level data var questLevels = [ [ [5, 6], [3] ], [ [6], [1, 2]], [ [5, 6] ], [ [3], [1, 2], [4] ], [ [1, 2], [3], [4], [5, 6]], ]; // quest model definition // quest is a kind of composition, the difference is that quest is specifically used as the question for player to give the answer. // so it comes with comparing logic. var Quest = game.Quest = (function(){ function Quest(level){ var questData = questLevels[level]; this.data = questData; } Quest.prototype = new game.Composition(); // extends the Quest prototype from Composition. return Quest; })(); })();
- Since we have removed the dummy
gameover
and have finished with the link of the game scene, now, from thescenes.js
file, we will also remove theonclick
event for these two links inside thehandleInput
method. - We add a new method to the
gameScene
instance that displays the data in the game scene. This method creates the patterns in the quest area of the game according to the given data:gameScene.visualize = function(quest) { var questData = quest.data; var patternsToShow = []; for (var i in questData) { for (var j in questData[i]) { patternsToShow.push(questData[i][j]); } } // Quest // visualize the quest composition view: var questCompositionNode = document.getElementById('quest-composition'); // empty the DOM view questCompositionNode.removeAllChildren(); // visualize the pattern view: for (var i in patternsToShow) { var patternNode = document.querySelector('#element-template .pattern').cloneNode(/*clone children=*/true); patternNode.setAttribute('data-pattern', patternsToShow[i]); questCompositionNode.appendChild(patternNode); } };
- We need to modify the game flow in the
game.js
file to show the quest:game.flow = { currentLevel: 3, startLevel: function() { game.quest = new game.Quest(this.currentLevel); game.gameScene.visualize(game.quest); }, ... } var init = function() { ... game.flow.startLevel(); }
- Finally, we will create the view of the patterns and the quest in CSS:
#stage { position: absolute; width: 100%; height: 30px; line-height: 30px; } /* Template */ #element-template {display: none;} /* quest */ #quest { width: 200px; height: 200px; position: absolute; left: 30px; top: 70px; } /* individual pattern */ .pattern { width: 200px; height: 200px; background-size: contain; } .pattern[data-pattern='1'] { background-image: url(images/pattern1.png); } /* pattern 2-5 puts here */ .pattern[data-pattern='6'] { background-image: url(images/pattern6.png); } .composition { position: relative; height: 200px; background: white; } .composition > .pattern { position: absolute; }
Objective complete – mini debriefing
We created the visual and data composition of a quest. Let's go in detail on what we have done in this task.
Separating the data and view
While designing games, we usually want to separate the logic of manipulating data and the logic of displaying and drawing elements. In our logic, the composition and quest are data. The scenes are for displaying. That's why we use the gameScene.visualize
method to display data into the DOM element once we declare the quest composition data.
We need to dynamically create elements to represent the pattern in the quest DOM node. Sometimes we create HTML directly in JavaScript and append it to the node. A better approach is to have the HTML placed inside, well, HTML. That's why we have the template element for JavaScript to clone it and put it back into the quest node.
Note
Using the data-* attribute
It is often useful to use the data-*
attribute to embed extra information when we use DOM elements to represent game objects. Take the card as an instance. We can define data-pattern='3'
to indicate that element is a visual of pattern number 3. We can define whatever we like as long as it begins with data-
. Then, we can use the getAttribute
method to access it and use the setAttribute
method to update it. Alternatively, we can use the dataset
method to access the data-*
attribute.
Visualizing the quest patterns
A pattern is a stack of background-transparent cards. We can represent each card as a DIV and overlay them together in one container. In the composition node, we overlap the pattern by setting the position to absolute, top-left position to 0.
Whenever we use absolute elements, we want to make sure that we have control of the reference point of the top and left properties; this means controlling where the top 0 and left 0 positions are.
Elements that are positioned at the absolute point reference the top-left point in the following way:
- They find the first parent with a position and set it as absolute or relative
- They use the body's top-left point if no parents are found with the position's setting
Therefore, what we need to do is set a position that is relative to the container, namely, .composition
.
The position styling for the quest and pattern has been defined in the CSS. What we need to do is append the newly created HTML node to the quest node from the gameScene.visualize
method. The pattern HTML nodes are created from the template and with the class defined that match the CSS rules.
Quest level
In this game, we require the player to select the pattern in the correct sequence to match the quest. However, some patterns are not overlapped with other patterns. In this case, we will put the two non-overlapped pairs together so that the order of choosing among these patterns will not be treated in the wrong order.
We would like to come up with an approach to compare the player's composition with the quest's composition.
The quest is composited by a sequence of patterns. A straightforward approach is to store the pattern sequence in an array. Then, all we need to do is compare whether the player's sequence is exactly the same as the given one.
Sounds good, but it fails in one case. In our patterns, there are some patterns that don't overlap with the others. Take the following pattern shown in the screenshot as an example:
The trapezoids to the left and right fit together without overlapping. We require the player to match the pattern visually so the sequence of these two selections does not change the effect, as shown in the following screenshot:
However, in the following pattern, the circle does overlap with the triangle:
Therefore, a simple sequence array does not work. Let's improve how we store the patterns in an array. How about using a 2-dimensional array?
The first dimension is the z index of the patterns, which is the sequence that players must match.
In order to represent the same level of relationship between the two trapezoids, we put them into one slot inside our array; thus, the array inside the code becomes the following:
[ [Left Trapezoid, Right Trapezoid], [Triangle] ]
To make the array item type consistent, we can force a single item to be wrapped inside the group too. Let's call each group of non-overlapped pattern layer.
Now, the final data array will be as follows:
[ [Left Trapezoid, Right Trapezoid], [Triangle] ]
Here, each array inside the quest level represents one layer with patterns that are interchangeable. Any overlapping pattern will be another new layer. Moreover, we put all the levels into an array to represent all the levels.
Composition and quest modules
The composition module contains only a data instance variable in the current task. The data has the same format as the multilayered patterns. Quest modules inherit the composition with logic that is related to the quest levels.
In future tasks, we are going to add more logic to the composition and quest modules to help in manipulating the format of a multilayer pattern.