HTML  /  Tutorial

Blend Tutorial Part 2: Create the Project—Memory Game

This tutorial provides a walkthrough for creating the basic project files you need to start the Memory game project. In addition, this tutorial provides tips for working with project files.

This tutorial includes the HTML and JavaScript code for the Memory game project.

About this series: this tutorial series can help you get started with basic design tasks in Blend 5 for Windows Developer Preview. When you complete all the steps of this tutorial series, you will have created a lightweight, dynamic version of the memory game commonly referred to as “Concentration.” In the game, a number of cards are laid face down. The objective of the game is to turn over pairs of matching cards until all of the matching pairs have been revealed.

Posts in this Series:

Blend Tutorial Part 1: Design Your First Metro Style App with JavaScript, HTML5, CSS
Blend Tutorial Part 2: Create the Project—Memory Game [This post.]
Blend Tutorial Part 3: Add a Style Sheet—Memory Game
Blend Tutorial Part 4: Create a Flexible Layout—Memory Game
Blend Tutorial Part 5: Align Content to The Grid—Memory Game
Blend Tutorial Part 6: Style the Game Board—Memory Game
Blend Tutorial Part 7: Style the Cards—Memory Game
Blend Tutorial Part 8: Add CSS Transitions—Memory Game
Blend Tutorial Part 9: Build and Run Your App—Memory Game

This tutorial follows the demonstration entitled “A deep dive into Expression Blend for designing Metro style apps using HTML” on MSDN.

To work through the tutorial as demonstrated in the video, download the Memory game sample project from the Metro style app samples gallery. This project is the starting point for following this tutorial. The project includes the default files that are generated when you create a new project and all of the images necessary to create the app.

After you download the Memory game sample project, extract the files to a location on your computer, for example, \My Documents\Expression\Blend 5 Preview\Projects\.

To open an existing project

  1. On the File menu, click Open Project/Solution.
  2. In the Open Project dialog box, browse to the solution file (.sln) in your project folder, and click OK.

For this tutorial, browse to where you extracted the memory project files and locate the memory.sln file.

Tip: You can also open a project by right-clicking the project file (.csproj or .vbproj) in Windows Explorer, and then pointing to Open with and selecting Expression Blend for Windows Developer Preview.

If you look in the Projects panel, you’ll see the default files and folders that are automatically generated when you create a new project:

  • css A folder that holds any custom CSS files used by your app.
  • images A folder that holds image assets for your app.
  • js A folder that holds your customized JavaScript files.
  • winjs A folder that holds the read-only CSS and Windows Libraries for JavaScript.
  • default.html The default starting page of your app.
  • package.appxmanifest A file that lists your app and its assets. This file also defines the starting page of your app.

In addition to the default files and folders, you will also see a photos folder in the Projects panel. This folder includes the photos that you need to build the memory app.

Create the project source files

The first step is to paste the sample code included in the Memory game overview section into the appropriate project files, starting with default.html.

Both default.html and default.js include default code that you will replace. You can view the default code for a new project in Code view before you paste the sample code for the project into the file.

To add code to default.html

  1. In the Semantic Structure Defined in HTML section at the end of this post, manually select all of the HTML code and press Ctrl+C to copy it to the clipboard.
  2. Switch to Code view. In default.html, press CTRL+A to select the existing header code and then press Ctrl+V to paste the code sample from the clipboard.
  3. On the File menu, click Save to save default.html.

To add code to default.js

  1. In the Project panel, expand the js folder and double-click default.js.
  2. In the Game Logic in JavaScript section at the end of this post, manually select all of the JavaScript code and press Ctrl+C to copy it to the clipboard.
  3. In the Code view of the default.js file, press Ctrl+A to select the existing header code and then press Ctrl+V to paste the code sample from the clipboard.
  4. On the File menu, click Save to save default.js.

Tip:

It’s a good idea to save your project from time to time as you progress. To save the current document, on the File menu, click Save, or press Ctrl + S.

If multiple files have changed, you can save all of the changes by clicking Save All on the File menu, or pressing Shift + Ctrl + S.

An asterisk on the document tab indicates that a file has changed. If multiple files have changed, an asterisk will appear on each of the tabs.

The next step: Blend Tutorial Part 3: Add a Style Sheet—Memory Game.

Semantic Structure Defined in HTML

This is the HTML file handed off by the developer.

The default references in the <head> section are automatically added when a new Metro style app project is created, including references to the WinJS JavaScript library.

The HTML

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
<!DOCTYPE html>
<html style="position: fixed; top: 0px; left: 0px;">
<head>
    <meta charset="utf-8" />
    <title>Memory</title>
    <!-- WinJS references -->
    <script src="/winjs/js/base.js"></script>
    <script src="/winjs/js/ui.js"></script>
    <script src="/winjs/js/binding.js"></script>
    <script src="/winjs/js/controls.js"></script>
    <script src="/winjs/js/animations.js"></script>
    <script src="/winjs/js/uicollections.js"></script>
    <script src="/winjs/js/wwaapp.js"></script>
    <!-- Memory references -->
    <link rel="stylesheet" href="/css/default.css" />
    <script src="/js/default.js"></script>
    <script type="text/javascript">WinJS.UI.processAll();</script>
</head>
<body>
    <div id="gameBody">
        <h1 id="gameTitle">memory</h1>
        <p id="gameTagline">jog your memory - find matching cards as fast as you can</p>
        <div id="settings">
            <div id="startStop">
                <input id="startGame" type="button" value="Start the Game"/>
                <input id="stopGame" type="button" value="Stop the Game"/>
            </div>
            <div id="gameSize" class="settings">
                <h3>Game Size: </h3>
                <div>
                    <form id="sizeForm" name="size">
                        <input id="size4x4" name="size4x4" type="radio" value="4" checked/>
                        4x4
                        <input id="size6x6" name="size6x6" type="radio" value="6" />
                        6x6
                        <input id="size8x8" name="size8x8" type="radio" value="8" />
                        8x8
                    </form>
                </div>
            </div>
            <input id="selectCards" type="button" value="Select Card Style"/>
        </div>
        <div id="stats">
            <div id="time" class="timer">
                <h3 id="timeLabel">Time: </h3>
                <div id="timeDisplay" class="values">00:00</div>
            </div>
            <div id="score" class="score">
                <div id="found" class="found hBox">
                    <h3>Found: </h3>
                    <div id="foundDisplay" class="values">0</div>
                </div>
                <div id="attempts" class="attempts hBox">
                    <h3>Attempts: </h3>
                    <div id="attemptsDisplay" class="values">0</div>
                </div>
            </div>
        </div>
        <div id="gameBoard"> </div>
    </div>
    <div id="cardTemplate" style="width: 300px; height: 200px; display: none;" data-win-design-hidden="true">
        <p></p>
    </div>
    <div id="popupHolder" class="closeImagePopup" data-win-design-hidden="true">
        <div id="popupFrame"> <img id="popupImage"/> </div>
    </div>
    <div id="deckSelectionFlyout" class="hidden" data-win-design-hidden="true"> <img id="deckSelectionBg" alt="1366x768" src="/images/1366x768.png">
        <h1 id="deckSelectionTitle">memory</h1>
        <p id="deckSelectionTagline">select a deck of cards</p>
        <div id="deckTemplate" class="itemTemplate" data-win-control="WinJS.Binding.Template">
            <div class="deckItemContainer"> <img data-win-bind="src:thumbnail" src="" alt="" />
                <p data-win-bind="textContent:name"></p>
            </div>
        </div>
        <div id="deckList" class="itemList"
                            data-win-control="WinJS.UI.ListView" 
                            data-win-options="{layout: {type: WinJS.UI.ListLayout}, selectionMode: 'none', dataSource: decks, itemRenderer: deckTemplate, tap : 'invoke',  crossslide: 'none' }">
        </div>
    </div>
</body>
</html>

Game logic in JavaScript

This is the JavaScript code for the game. The major components of the game logic defined in this JavaScript are the following:

  • Timer
  • Game size
  • Number of attempts
  • Number of successful attempts

The JavaScript

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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
var decks = [
{ name: 'salta, argentina', dir: 'Argentina', thumbnail: '/photos/Argentina/17.jpg' },
{ name: 'china street food', dir: 'China', thumbnail: '/photos/China/21.jpg' },
{ name: 'india: jaipur to agra', dir: 'India', thumbnail: '/photos/India/03.jpg' },
{ name: 'yucatan, mexico', dir: 'Mexico', thumbnail: '/photos/Mexico/07.jpg' },
{ name: 'zoo animals', dir: 'Zoo', thumbnail: '/photos/Zoo/14.jpg' }
];
 
(function () {
    'use strict';
    // Uncomment the following line to enable first chance exceptions.
    Debug.enableFirstChanceException(true);
 
    var size = 16;
    var sizeStyle = "smallGame";
    var maxpics = 32;
 
    var gameRunning = false;
    var cardOpen = null;
    var time = 0;
    var timer;
 
    var s4;
    var s6;
    var s8;
 
    var attempts = 0;
    var found = 0;
 
    var foundDisp;
    var attDisp;
    var timeDisp;
 
    var template;
 
var popupImage;
var popupHolder;
 
//var gameGrid;
 
var currentCardSet = 'Zoo';
 
    function activated() {
 //WinJS.UI.processAll().then(function(){
s4 = document.getElementById("size4x4");
s6 = document.getElementById("size6x6");
s8 = document.getElementById("size8x8");
s4.addEventListener("click", gameSize, false);
s6.addEventListener("click", gameSize, false);
s8.addEventListener("click", gameSize, false);
 
var deckList = document.querySelector('#deckList');
var deckListView = WinJS.UI.getControl(deckList);
deckList.addEventListener("iteminvoked", deckSelected);
 
var selectDeckButton = document.querySelector('#selectCards');
selectDeckButton.addEventListener('click', showDeckSelector);
//loadCardsFromDeck('default');
 
var start = document.getElementById("startGame");
if (start) {
start.onclick = runGame;
}
var stop = document.getElementById("stopGame");
if (stop) {
stop.onclick = stopGame;
}
 
template = document.getElementById("cardTemplate");
 
popupImage = document.getElementById("popupImage");
popupHolder = document.getElementById("popupHolder");
popupHolder.addEventListener("click", closeImageView, false);
 
foundDisp = document.getElementById("foundDisplay");
attDisp = document.getElementById("attemptsDisplay");
timeDisp = document.getElementById("timeDisplay");
 
// Expose functions globally
window.updateClock = updateClock;
window.resetCards = resetCards;
window.gameTag1 = null;
window.gameTag2 = null;
 
//gameGrid = document.getElementById("gameGrid");
//addClass(gameGrid, "smallGame");
 
buildCards();
//});
    }
 
    function closeImageView() {
        //popupHolder.style.display = "none";
        removeClass(popupHolder, "openImagePopup");
    }
 
    function makePictureArray() {
        var pics = new Array();
        for (var i = 0; i &lt; maxpics; i++) {
            pics[i] = getDefaultURL(i);
        }
        return pics;
    }
 
    function removeAllChildren(element) {
        if (element.hasChildNodes()) {
            while (element.childNodes.length &gt; 0) {
                element.removeChild(element.firstChild);
            }
        }
    }
 
    function buildCards() {
//var gameGrid = document.getElementById("gameGrid");
        // Remove elements
//removeAllChildren(gameGrid);
 
        // Assumption: game grid size is a power of 2
var stride = Math.sqrt(size);
 
// Make picture selection
var pics = makePictureArray();
var sel = new Array();
for (var i = 0; i &lt; size / 2; i++) {
    var idx = parseInt(Math.random() * pics.length);
    sel[i] = pics[idx];
            // remove the used pic
    pics.splice(idx, 1);
}
 
// get an array with the card content
var content = new Array();
for (var i = 0; i &lt; size / 2; i++) {
    content[i] = sel[i];
    content[i + size / 2] = sel[i];
}
 
var gameBoard = document.querySelector('#gameBoard');
removeAllChildren(gameBoard);
var gameGrid = document.createElement("div");
gameGrid.id = "gameGrid";
addClass(gameGrid, sizeStyle);
gameBoard.appendChild(gameGrid);
 
for (var i=0; i= 1) {
var tchild = templateParent.children[0].cloneNode(true);
parent.appendChild(tchild);
}
}
 
    function gameSize() {
 
        //s4.removeAttribute("checked", 0);
        //s6.removeAttribute("checked", 0);
        //s8.removeAttribute("checked", 0);
        //this.setAttribute("checked", "");
        document.size.size4x4.checked = false;
        document.size.size6x6.checked = false;
        document.size.size8x8.checked = false;
 
        var newsize = 16;
var gSize = "smallGame";
        if (this.name == "size4x4") {
            document.size.size4x4.checked = true;
            newsize = 16;
gSize = "smallGame";
        }
        if (this.name == "size6x6") {
            document.size.size6x6.checked = true;
            newsize = 36;
gSize = "mediumGame";
        }
        if (this.name == "size8x8") {
            document.size.size8x8.checked = true;
            newsize = 64;
gSize = "largeGame";
        }
 
        if (!gameRunning == true) {
            size = newsize;
            sizeStyle = gSize;
            buildCards();
        }
    }
 
    function stopGame() {
        if (!gameRunning)
            return;
        gameRunning = false;
        foundDisp.innerText = "0";
        found = 0;
        attDisp.innerText = "0";
        attempts = 0;
        clearInterval(timer);
        timeDisp.innerText = "00:00";
 
s4.disabled = false;
s6.disabled = false;
s8.disabled = false;
 
    }
 
    function runGame() {
        //don't do anything if game already running
        if (gameRunning == true)
            return;
        //var gameGrid = document.getElementById("gameGrid");
 
window.gameTag1 = null;
        window.gameTag2 = null;
cardOpen = null;
        gameRunning = true;
buildCards();
        timeDisp.innerText = "00:00";
        time = 0;
        foundDisp.innerText = "0";
        found = 0;
        attDisp.innerText = "0";
        attempts = 0;
        timer = setInterval("updateClock()", 1000);
 
s4.disabled = true;
s6.disabled = true;
s8.disabled = true;
    }
 
    function getDefaultURL(i) {
        var idx = i+1;
        if (idx32)
            idx = 32;
        //var result = "/photos/default/";
        var result = "/photos/" + currentCardSet + "/";
        if (idx &lt; 10)
            result = result + "0";
        result = result + idx.toString() + ".jpg";
        return result;
    }
 
    function updateClock() {
        time = time + 1;
        var mins = parseInt(time / 60);
        var secs = time - mins * 60;
 
        mins = mins.toString();
        secs = secs.toString();
 
        if (mins.length &lt; 2)
            mins = "0" + mins;
        if (secs.length &lt; 2)
            secs = "0" + secs;
 
        timeDisp.innerText = mins + ":" + secs;
 
        //timeDisp.innerText = mins.toString() + ":" + secs.toString();
    }
 
    function cardClicked() {
 
        // If an open card is clicked, bring up the full size popup
        if (this.cardFoundFlag || cardOpen === this) {
            popupImage.setAttribute("src", this.contentIndex);
            popupHolder.style.display = "block";
            addClass(popupHolder, "openImagePopup");
            return;
        }
 
        // don't do anything if no game is running, or a move is in play...
        if (!gameRunning || window.gameTag1 !=null || window.gameTag2 != null)
            return;
 
        if (cardOpen == null) {
            //this.style.color = "red";
            setOpen(this);
            cardOpen = this;
        }
        else {
            if (this.contentIndex == cardOpen.contentIndex) {
                //this.style.color = "blue";
                setFound(this);
                //cardOpen.style.color = "blue";
                setFound(cardOpen);
                //this.removeEventListener("click", cardClicked, false);
                //cardOpen.removeEventListener("click", cardClicked, false);
                this.cardFoundFlag = true;
                cardOpen.cardFoundFlag = true;
                cardOpen = null;
                found++;
                foundDisp.innerText = found;
                updateAttempts();
                if (found == size / 2) {
                    gameRunning = false;
                    clearInterval(timer);
                    //resetAllCards();
                    foundDisp.innerText = "ALL!";
                }
            }
            else {
                //this.style.color = "red";
                setOpen(this);
                // Hack to get around insulation of these functions
                window.gameTag1 = this;
                window.gameTag2 = cardOpen;
                setTimeout("resetCards()", 1100);
                cardOpen = null;
                updateAttempts();
            }
        }
    }
 
    function setOpen(el) {
        removeClass(el, "closedCard");
        addClass(el, "openCard");
//el.style.backgroundColor = "orange";
    }
 
    function setClosed(el) {
        removeClass(el, "openCard");
        addClass(el, "closedCard");
    }
 
    function setFound(el) {
removeClass(el, "openCard");
removeClass(el, "closedCard");
        addClass(el, "foundCard");
    }
 
    function resetCardStyles(el) {
        removeClass(el, "openCard");
        removeClass(el, "foundCard");
        addClass(el, "closedCard");
    }
 
    function updateAttempts() {
        attempts++;
        attDisp.innerText = attempts;
    }
 
    function resetAllCards() {
        buildCards();
    }
 
    function resetCards() {
        //setClosed(gameTag1);
        //setClosed(gameTag2);
        //gameTag1 = null;
        //gameTag2 = null;
setClosed(window.gameTag1);
        setClosed(window.gameTag2);
        window.gameTag1 = null;
        window.gameTag2 = null;
    }
 
    function showDeckSelector() {
        var deckSelectorFlyout = document.querySelector('#deckSelectionFlyout');
        removeClass(deckSelectorFlyout, 'hidden');
        addClass(deckSelectorFlyout, 'shown');
    }
 
    function deckSelected(eventObject) {
        var deckSelectorFlyout = document.querySelector('#deckSelectionFlyout');
        var deckIndex = eventObject.detail.itemIndex;
        //if (!deckIndex || deckIndex &lt; 0 || deck) {
if ( deckIndex &lt; 0 || deckIndex &gt;= window.decks.length) {
removeClass(deckSelectorFlyout, 'shown');
addClass(deckSelectorFlyout, 'hidden');
            return;
        }
        var deck = window.decks[deckIndex];
        //loadCardsFromDeck(deck.name);
        currentCardSet = deck.dir;
        buildCards();
removeClass(deckSelectorFlyout, 'shown');
        addClass(deckSelectorFlyout, 'hidden');
    }
 
/*
    function loadCardsFromDeck(deckName) {
        for (var i = 0; i &lt; 32; i++) {
            var path = '/photos/' + deckName + '/';
            if (i &lt; 10) {
                path = path + '0';
            }
            path = path + i + '.jpg';
            currentCards[i] = path;
        }
        var deckSelectorFlyout = document.querySelector('#deckSelectionFlyout');
        removeClass(deckSelectorFlyout, 'shown');
        addClass(deckSelectorFlyout, 'hidden');
 
    }
*/
 
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    // Utilities
    //
    //////////////////////////////////////////////////////////////////////////////////////////////////////
function hasClass(el, className) {
        // authors can pass in either an element object or an ID
        el = (typeof (el) == 'object') ? el : document.getElementById(el);
 
        // no need to continue if there's no className
        if (!el.className) return false;
 
        // iterate through all the classes
        var classArray = el.className.split(' ');
        for (var i = 0; i &lt; classArray.length; i++) {
            if (className == classArray[i]) return true; // found? return true
        }
 
        // if we're still here, the class does not exist
        return false;
    }
 
    function addClass(el, className) {
        // authors can pass in either an element object or an ID
        el = (typeof (el) == 'object') ? el : document.getElementById(el);
 
        // if the class already exists, there's no need to add it
        //if (hasClass(el, className)) return;
 
        // simply append the className to the string
        el.className += ' ' + className;
        return;
    }
 
    function removeClass(el, className) {
        // authors can pass in either an element object or an ID
        el = (typeof (el) == 'object') ? el : document.getElementById(el);
 
        // if the class doesn't exist, there's no need to remove it
        if (!hasClass(el, className)) return;
 
        // iterate through all the classes
        var classArray = el.className.split(' ');
        for (var i = 0; i &lt; classArray.length; i++) {
 
            // found it!
            if (className == classArray[i]) {
                classArray.splice(i, 1); // remove it
                i--; // decrement so we don't skip over any future occurences
            }
        }
 
        // reassign the className
        el.className = classArray.join(' ');
        return;
    }
 
    WinJS.Application.onmainwindowactivated = function (e) {
        if (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
            activated();
        }
    }
 
    WinJS.Application.start();
})();