320203583f9c58f8eaaa412f4e3ba2e651cfaf5b
[profile/ivi/qtdeclarative.git] / examples / demos / samegame / content / samegame.js
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the examples of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** You may use this file under the terms of the BSD license as follows:
10 **
11 ** "Redistribution and use in source and binary forms, with or without
12 ** modification, are permitted provided that the following conditions are
13 ** met:
14 **   * Redistributions of source code must retain the above copyright
15 **     notice, this list of conditions and the following disclaimer.
16 **   * Redistributions in binary form must reproduce the above copyright
17 **     notice, this list of conditions and the following disclaimer in
18 **     the documentation and/or other materials provided with the
19 **     distribution.
20 **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
21 **     of its contributors may be used to endorse or promote products derived
22 **     from this software without specific prior written permission.
23 **
24 **
25 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 /* This script file handles the game logic */
42 .pragma library
43 .import QtQuick.LocalStorage 2.0 as Sql
44
45 var maxColumn = 10;
46 var maxRow = 13;
47 var types = 3;
48 var maxIndex = maxColumn*maxRow;
49 var board = new Array(maxIndex);
50 var blockSrc = "Block.qml";
51 var gameDuration;
52 var component = Qt.createComponent(blockSrc);
53 var gameCanvas;
54 var betweenTurns = false;
55
56 var puzzleLevel = null;
57 var puzzlePath = "";
58
59 var gameMode = "arcade"; //Set in new game, then tweaks behaviour of other functions
60 var gameOver = false;
61
62 function changeBlock(src)
63 {
64     blockSrc = src;
65     component = Qt.createComponent(blockSrc);
66 }
67
68 // Index function used instead of a 2D array
69 function index(column, row)
70 {
71     return column + row * maxColumn;
72 }
73
74 function timeStr(msecs)
75 {
76     var secs = Math.floor(msecs/1000);
77     var m = Math.floor(secs/60);
78     var ret = "" + m + "m " + (secs%60) + "s";
79     return ret;
80 }
81
82 function cleanUp()
83 {
84     if (gameCanvas == undefined)
85         return;
86     // Delete blocks from previous game
87     for (var i = 0; i < maxIndex; i++) {
88         if (board[i] != null)
89             board[i].destroy();
90         board[i] = null;
91     }
92     if (puzzleLevel != null){
93         puzzleLevel.destroy();
94         puzzleLevel = null;
95     }
96     gameCanvas.mode = ""
97 }
98
99 function startNewGame(gc, mode, map)
100 {
101     gameCanvas = gc;
102     if (mode == undefined)
103         gameMode = "arcade";
104     else
105         gameMode = mode;
106     gameOver = false;
107
108     cleanUp();
109
110     gc.gameOver = false;
111     gc.mode = gameMode;
112     // Calculate board size
113     maxColumn = Math.floor(gameCanvas.width/gameCanvas.blockSize);
114     maxRow = Math.floor(gameCanvas.height/gameCanvas.blockSize);
115     maxIndex = maxRow * maxColumn;
116     if (gameMode == "arcade") //Needs to be after board sizing
117         getHighScore();
118
119
120     // Initialize Board
121     board = new Array(maxIndex);
122     gameCanvas.score = 0;
123     gameCanvas.score2 = 0;
124     gameCanvas.moves = 0;
125     gameCanvas.curTurn = 1;
126     if (gameMode == "puzzle")
127         loadMap(map);
128     else//Note that we load them in reverse order for correct visual stacking
129         for (var column = maxColumn - 1; column >= 0; column--)
130             for (var row = maxRow - 1; row >= 0; row--)
131                 createBlock(column, row);
132     if (gameMode == "puzzle")
133         getLevelHistory();//Needs to be after map load
134     gameDuration = new Date();
135 }
136
137 var fillFound;  // Set after a floodFill call to the number of blocks found
138 var floodBoard; // Set to 1 if the floodFill reaches off that node
139
140 // NOTE: Be careful with vars named x,y, as the calling object's x,y are still in scope
141 function handleClick(x,y)
142 {
143     if (betweenTurns || gameOver || gameCanvas == undefined)
144         return;
145     var column = Math.floor(x/gameCanvas.blockSize);
146     var row = Math.floor(y/gameCanvas.blockSize);
147     if (column >= maxColumn || column < 0 || row >= maxRow || row < 0)
148         return;
149     if (board[index(column, row)] == null)
150         return;
151     // If it's a valid block, remove it and all connected (does nothing if it's not connected)
152     floodFill(column,row, -1);
153     if (fillFound <= 0)
154         return;
155     if (gameMode == "multiplayer" && gameCanvas.curTurn == 2)
156         gameCanvas.score2 += (fillFound - 1) * (fillFound - 1);
157     else
158         gameCanvas.score += (fillFound - 1) * (fillFound - 1);
159     if (gameMode == "multiplayer" && gameCanvas.curTurn == 2)
160         shuffleUp();
161     else
162         shuffleDown();
163     gameCanvas.moves += 1;
164     if (gameMode == "endless")
165         refill();
166     else if (gameMode != "multiplayer")
167         victoryCheck();
168     if (gameMode == "multiplayer" && !gc.gameOver){
169         betweenTurns = true;
170         gameCanvas.swapPlayers();//signal, animate and call turnChange() when ready
171     }
172 }
173
174 function floodFill(column,row,type)
175 {
176     if (board[index(column, row)] == null)
177         return;
178     var first = false;
179     if (type == -1) {
180         first = true;
181         type = board[index(column,row)].type;
182
183         // Flood fill initialization
184         fillFound = 0;
185         floodBoard = new Array(maxIndex);
186     }
187     if (column >= maxColumn || column < 0 || row >= maxRow || row < 0)
188         return;
189     if (floodBoard[index(column, row)] == 1 || (!first && type != board[index(column, row)].type))
190         return;
191     floodBoard[index(column, row)] = 1;
192     floodFill(column + 1, row, type);
193     floodFill(column - 1, row, type);
194     floodFill(column, row + 1, type);
195     floodFill(column, row - 1, type);
196     if (first == true && fillFound == 0)
197         return; // Can't remove single blocks
198     board[index(column, row)].dying = true;
199     board[index(column, row)] = null;
200     fillFound += 1;
201 }
202
203 function shuffleDown()
204 {
205     // Fall down
206     for (var column = 0; column < maxColumn; column++) {
207         var fallDist = 0;
208         for (var row = maxRow - 1; row >= 0; row--) {
209             if (board[index(column,row)] == null) {
210                 fallDist += 1;
211             } else {
212                 if (fallDist > 0) {
213                     var obj = board[index(column, row)];
214                     obj.y = (row + fallDist) * gameCanvas.blockSize;
215                     board[index(column, row + fallDist)] = obj;
216                     board[index(column, row)] = null;
217                 }
218             }
219         }
220     }
221     // Fall to the left
222     fallDist = 0;
223     for (column = 0; column < maxColumn; column++) {
224         if (board[index(column, maxRow - 1)] == null) {
225             fallDist += 1;
226         } else {
227             if (fallDist > 0) {
228                 for (row = 0; row < maxRow; row++) {
229                     obj = board[index(column, row)];
230                     if (obj == null)
231                         continue;
232                     obj.x = (column - fallDist) * gameCanvas.blockSize;
233                     board[index(column - fallDist,row)] = obj;
234                     board[index(column, row)] = null;
235                 }
236             }
237         }
238     }
239 }
240
241
242 function shuffleUp()
243 {
244     // Fall up
245     for (var column = 0; column < maxColumn; column++) {
246         var fallDist = 0;
247         for (var row = 0; row < maxRow; row++) {
248             if (board[index(column,row)] == null) {
249                 fallDist += 1;
250             } else {
251                 if (fallDist > 0) {
252                     var obj = board[index(column, row)];
253                     obj.y = (row - fallDist) * gameCanvas.blockSize;
254                     board[index(column, row - fallDist)] = obj;
255                     board[index(column, row)] = null;
256                 }
257             }
258         }
259     }
260     // Fall to the left (or should it be right, so as to be left for P2?)
261     fallDist = 0;
262     for (column = 0; column < maxColumn; column++) {
263         if (board[index(column, 0)] == null) {
264             fallDist += 1;
265         } else {
266             if (fallDist > 0) {
267                 for (row = 0; row < maxRow; row++) {
268                     obj = board[index(column, row)];
269                     if (obj == null)
270                         continue;
271                     obj.x = (column - fallDist) * gameCanvas.blockSize;
272                     board[index(column - fallDist,row)] = obj;
273                     board[index(column, row)] = null;
274                 }
275             }
276         }
277     }
278 }
279
280 function turnChange()//called by ui outside
281 {
282     betweenTurns = false;
283     if (gameCanvas.curTurn == 1){
284         shuffleUp();
285         gameCanvas.curTurn = 2;
286         victoryCheck();
287     }else{
288         shuffleDown();
289         gameCanvas.curTurn = 1;
290         victoryCheck();
291     }
292 }
293
294 function refill()
295 {
296     for (var column = 0; column < maxColumn; column++) {
297         for (var row = 0; row < maxRow; row++) {
298             if (board[index(column, row)] == null)
299                 createBlock(column, row);
300         }
301     }
302 }
303
304 function victoryCheck()
305 {
306     // Awards bonuses for no blocks left
307     var deservesBonus = true;
308     if (board[index(0,maxRow - 1)] != null || board[index(0,0)] != null)
309         deservesBonus = false;
310     // Checks for game over
311     if (deservesBonus){
312         if (gameCanvas.curTurn = 1)
313             gameCanvas.score += 1000;
314         else
315             gameCanvas.score2 += 1000;
316     }
317     gameOver = deservesBonus;
318     if (gameCanvas.curTurn == 1){
319         if (!(floodMoveCheck(0, maxRow - 1, -1)))
320             gameOver = true;
321     }else{
322         if (!(floodMoveCheck(0, 0, -1, true)))
323             gameOver = true;
324     }
325     if (gameMode == "puzzle"){
326         puzzleVictoryCheck(deservesBonus);//Takes it from here
327         return;
328     }
329     if (gameOver) {
330         var winnerScore = Math.max(gameCanvas.score, gameCanvas.score2);
331         if (gameMode == "multiplayer"){
332             gameCanvas.score = winnerScore;
333             saveHighScore(gameCanvas.score2);
334         }
335         saveHighScore(gameCanvas.score);
336         gameDuration = new Date() - gameDuration;
337         gameCanvas.gameOver = true;
338     }
339 }
340
341 // Only floods up and right, to see if it can find adjacent same-typed blocks
342 function floodMoveCheck(column, row, type, goDownInstead)
343 {
344     if (column >= maxColumn || column < 0 || row >= maxRow || row < 0)
345         return false;
346     if (board[index(column, row)] == null)
347         return false;
348     var myType = board[index(column, row)].type;
349     if (type == myType)
350         return true;
351     if (goDownInstead)
352         return floodMoveCheck(column + 1, row, myType, goDownInstead) ||
353                floodMoveCheck(column, row + 1, myType, goDownInstead);
354     else
355         return floodMoveCheck(column + 1, row, myType) ||
356                floodMoveCheck(column, row - 1, myType);
357 }
358
359 function createBlock(column,row,type)
360 {
361     // Note that we don't wait for the component to become ready. This will
362     // only work if the block QML is a local file. Otherwise the component will
363     // not be ready immediately. There is a statusChanged signal on the
364     // component you could use if you want to wait to load remote files.
365     if (component.status == 1){
366         if (type == undefined)
367             type = Math.floor(Math.random() * types);
368         if (type < 0 || type > 4) {
369             console.log("Invalid type requested");//TODO: Is this triggered by custom levels much?
370             return;
371         }
372         var dynamicObject = component.createObject(gameCanvas,
373                 {"type": type,
374                 "x": column*gameCanvas.blockSize,
375                 "y": -1*gameCanvas.blockSize,
376                 "width": gameCanvas.blockSize,
377                 "height": gameCanvas.blockSize,
378                 "particleSystem": gameCanvas.ps});
379         if (dynamicObject == null){
380             console.log("error creating block");
381             console.log(component.errorString());
382             return false;
383         }
384         dynamicObject.y = row*gameCanvas.blockSize;
385         dynamicObject.spawned = true;
386
387         board[index(column,row)] = dynamicObject;
388     }else{
389         console.log("error loading block component");
390         console.log(component.errorString());
391         return false;
392     }
393     return true;
394 }
395
396 function showPuzzleError(str)
397 {
398     //TODO: Nice user visible UI?
399     console.log(str);
400 }
401
402 function loadMap(map)
403 {
404     puzzlePath = map;
405     var levelComp = Qt.createComponent(puzzlePath);
406     if (levelComp.status != 1){
407         console.log("Error loading level");
408         showPuzzleError(levelComp.errorString());
409         return;
410     }
411     puzzleLevel = levelComp.createObject();
412     if (puzzleLevel == null || !puzzleLevel.startingGrid instanceof Array) {
413         showPuzzleError("Bugger!");
414         return;
415     }
416     gameCanvas.showPuzzleGoal(puzzleLevel.goalText);
417     //showPuzzleGoal should call finishLoadingMap as the next thing it does, before handling more events
418 }
419
420 function finishLoadingMap()
421 {
422     for (var i in puzzleLevel.startingGrid)
423         if (! (puzzleLevel.startingGrid[i] >= 0 && puzzleLevel.startingGrid[i] <= 9) )
424             puzzleLevel.startingGrid[i] = 0;
425     //TODO: Don't allow loading larger levels, leads to cheating
426     while (puzzleLevel.startingGrid.length > maxIndex) puzzleLevel.startingGrid.shift();
427     while (puzzleLevel.startingGrid.length < maxIndex) puzzleLevel.startingGrid.unshift(0);
428     for (var i in puzzleLevel.startingGrid)
429         if (puzzleLevel.startingGrid[i] > 0)
430             createBlock(i % maxColumn, Math.floor(i / maxColumn), puzzleLevel.startingGrid[i] - 1);
431
432     //### Experimental feature - allow levels to contain arbitrary QML scenes as well!
433     //while (puzzleLevel.children.length)
434     //    puzzleLevel.children[0].parent = gameCanvas;
435     gameDuration = new Date(); //Don't start until we finish loading
436 }
437
438 function puzzleVictoryCheck(clearedAll)//gameOver has also been set if no more moves
439 {
440     var won = true;
441     var soFar = new Date() - gameDuration;
442     if (puzzleLevel.scoreTarget != -1 && gameCanvas.score < puzzleLevel.scoreTarget){
443         won = false;
444     } if (puzzleLevel.scoreTarget != -1 && gameCanvas.score >= puzzleLevel.scoreTarget && !puzzleLevel.mustClear){
445         gameOver = true;
446     } if (puzzleLevel.timeTarget != -1 && soFar/1000.0 > puzzleLevel.timeTarget){
447         gameOver = true;
448     } if (puzzleLevel.moveTarget != -1 && gameCanvas.moves >= puzzleLevel.moveTarget){
449         gameOver = true;
450     } if (puzzleLevel.mustClear && gameOver && !clearedAll) {
451         won = false;
452     }
453
454     if (gameOver) {
455         gameCanvas.gameOver = true;
456         gameCanvas.showPuzzleEnd(won);
457
458         if (won) {
459             // Store progress
460             saveLevelHistory();
461         }
462     }
463 }
464
465 function getHighScore()
466 {
467     var db = Sql.LocalStorage.openDatabaseSync(
468         "SameGame",
469         "2.0",
470         "SameGame Local Data",
471         100
472     );
473     db.transaction(
474         function(tx) {
475             tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(game TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
476             // Only show results for the current grid size
477             var rs = tx.executeSql('SELECT * FROM Scores WHERE gridSize = "'
478                 + maxColumn + "x" + maxRow + '" AND game = "' + gameMode + '" ORDER BY score desc');
479             if (rs.rows.length > 0)
480                 gameCanvas.highScore = rs.rows.item(0).score;
481             else
482                 gameCanvas.highScore = 0;
483         }
484     );
485 }
486
487 function saveHighScore(score)
488 {
489     // Offline storage
490     var db = Sql.LocalStorage.openDatabaseSync(
491         "SameGame",
492         "2.0",
493         "SameGame Local Data",
494         100
495     );
496     var dataStr = "INSERT INTO Scores VALUES(?, ?, ?, ?)";
497     var data = [
498         gameMode,
499         score,
500         maxColumn + "x" + maxRow,
501         Math.floor(gameDuration / 1000)
502     ];
503     if (score >= gameCanvas.highScore)//Update UI field
504         gameCanvas.highScore = score;
505
506     db.transaction(
507         function(tx) {
508             tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(game TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
509             tx.executeSql(dataStr, data);
510         }
511     );
512 }
513
514 function getLevelHistory()
515 {
516     var db = Sql.LocalStorage.openDatabaseSync(
517         "SameGame",
518         "2.0",
519         "SameGame Local Data",
520         100
521     );
522     db.transaction(
523         function(tx) {
524             tx.executeSql('CREATE TABLE IF NOT EXISTS Puzzle(level TEXT, score NUMBER, moves NUMBER, time NUMBER)');
525             var rs = tx.executeSql('SELECT * FROM Puzzle WHERE level = "' + puzzlePath + '" ORDER BY score desc');
526             if (rs.rows.length > 0) {
527                 gameCanvas.puzzleWon = true;
528                 gameCanvas.highScore = rs.rows.item(0).score;
529             } else {
530                 gameCanvas.puzzleWon = false;
531                 gameCanvas.highScore = 0;
532             }
533         }
534     );
535 }
536
537 function saveLevelHistory()
538 {
539     var db = Sql.LocalStorage.openDatabaseSync(
540         "SameGame",
541         "2.0",
542         "SameGame Local Data",
543         100
544     );
545     var dataStr = "INSERT INTO Puzzle VALUES(?, ?, ?, ?)";
546     var data = [
547         puzzlePath,
548         gameCanvas.score,
549         gameCanvas.moves,
550         Math.floor(gameDuration / 1000)
551     ];
552     gameCanvas.puzzleWon = true;
553
554     db.transaction(
555         function(tx) {
556             tx.executeSql('CREATE TABLE IF NOT EXISTS Puzzle(level TEXT, score NUMBER, moves NUMBER, time NUMBER)');
557             tx.executeSql(dataStr, data);
558         }
559     );
560 }
561
562 function nuke() //For "Debug mode"
563 {
564     for (var row = 1; row <= 5; row++) {
565         for (var col = 0; col < 5; col++) {
566             if (board[index(col, maxRow - row)] != null) {
567                 board[index(col, maxRow - row)].dying = true;
568                 board[index(col, maxRow - row)] = null;
569             }
570         }
571     }
572     if (gameMode == "multiplayer" && gameCanvas.curTurn == 2)
573         shuffleUp();
574     else
575         shuffleDown();
576     if (gameMode == "endless")
577         refill();
578     else
579         victoryCheck();
580 }