1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
41 /* This script file handles the game logic */
43 .import QtQuick.LocalStorage 2.0 as Sql
47 var maxIndex = maxColumn*maxRow;
48 var board = new Array(maxIndex);
49 var blockSrc = "BoomBlock.qml";
52 var component = Qt.createComponent(blockSrc);
53 var highScoreBar = -1;
55 var nameInputDialog = null;
58 // Index function used instead of a 2D array
59 function index(column, row)
61 return column + row * maxColumn;
64 function timeStr(msecs)
66 var secs = Math.floor(msecs/1000);
67 var m = Math.floor(secs/60);
68 var ret = "" + m + "m " + (secs%60) + "s";
72 function startNewGame(gc)
75 // Delete blocks from previous game
76 for (var i = 0; i < maxIndex; i++) {
81 // Calculate board size
82 maxColumn = Math.floor(gameCanvas.width/gameCanvas.blockSize);
83 maxRow = Math.floor(gameCanvas.height/gameCanvas.blockSize);
84 maxIndex = maxRow * maxColumn;
87 if(nameInputDialog != null)
88 nameInputDialog.forceClose();
93 board = new Array(maxIndex);
95 for (var column = 0; column < maxColumn; column++) {
96 for (var row = 0; row < maxRow; row++) {
97 board[index(column, row)] = null;
98 createBlock(column, row);
101 gameDuration = new Date();
104 var fillFound; // Set after a floodFill call to the number of blocks found
105 var floodBoard; // Set to 1 if the floodFill reaches off that node
107 // NOTE: Be careful with vars named x,y, as the calling object's x,y are still in scope
108 function handleClick(x,y)
110 if(gameCanvas == undefined){
111 console.log("But the game hasn't started yet!");
114 var column = Math.floor(x/gameCanvas.blockSize);
115 var row = Math.floor(y/gameCanvas.blockSize);
116 if (column >= maxColumn || column < 0 || row >= maxRow || row < 0)
118 if (board[index(column, row)] == null)
120 // If it's a valid block, remove it and all connected (does nothing if it's not connected)
121 floodFill(column,row, -1);
124 gameCanvas.score += (fillFound - 1) * (fillFound - 1);
129 function floodFill(column,row,type)
131 if (board[index(column, row)] == null)
136 type = board[index(column,row)].type;
138 // Flood fill initialization
140 floodBoard = new Array(maxIndex);
142 if (column >= maxColumn || column < 0 || row >= maxRow || row < 0)
144 if (floodBoard[index(column, row)] == 1 || (!first && type != board[index(column, row)].type))
146 floodBoard[index(column, row)] = 1;
147 floodFill(column + 1, row, type);
148 floodFill(column - 1, row, type);
149 floodFill(column, row + 1, type);
150 floodFill(column, row - 1, type);
151 if (first == true && fillFound == 0)
152 return; // Can't remove single blocks
153 board[index(column, row)].dying = true;
154 board[index(column, row)] = null;
158 function shuffleDown()
161 for (var column = 0; column < maxColumn; column++) {
163 for (var row = maxRow - 1; row >= 0; row--) {
164 if (board[index(column,row)] == null) {
168 var obj = board[index(column, row)];
169 obj.y = (row + fallDist) * gameCanvas.blockSize;
170 board[index(column, row + fallDist)] = obj;
171 board[index(column, row)] = null;
178 for (column = 0; column < maxColumn; column++) {
179 if (board[index(column, maxRow - 1)] == null) {
183 for (row = 0; row < maxRow; row++) {
184 obj = board[index(column, row)];
187 obj.x = (column - fallDist) * gameCanvas.blockSize;
188 board[index(column - fallDist,row)] = obj;
189 board[index(column, row)] = null;
196 function victoryCheck()
198 // Awards bonuses for no blocks left
199 var deservesBonus = true;
200 for (var column = maxColumn - 1; column >= 0; column--)
201 if (board[index(column, maxRow - 1)] != null)
202 deservesBonus = false;
204 gameCanvas.score += 500;
205 // Checks for game over
206 if (deservesBonus || !(floodMoveCheck(0, maxRow - 1, -1))) {
207 gameDuration = new Date() - gameDuration;
208 if(nameInputDialog == null){
209 nameInputDialog = Qt.createQmlObject('import "."; import "samegame.js" as Logic; NameInputDialog{onAccepted: Logic.saveHighScore(name)}', gameCanvas, "highscoredialog.qml");
212 dialog = Qt.createComponent("Dialog.qml").createObject(gameCanvas);
215 if(gameCanvas.score > highScoreBar){
216 nameInputDialog.show("You won! Your name: ");
217 nameInputDialog.initialWidth = nameInputDialog.text.width + 20;
218 if (nameInputDialog.name == "")
219 nameInputDialog.width = nameInputDialog.initialWidth;
220 nameInputDialog.text.opacity = 0; // Just a spacer
222 dialog.show("You won!");
227 // Only floods up and right, to see if it can find adjacent same-typed blocks
228 function floodMoveCheck(column, row, type)
230 if (column >= maxColumn || column < 0 || row >= maxRow || row < 0)
232 if (board[index(column, row)] == null)
234 var myType = board[index(column, row)].type;
237 return floodMoveCheck(column + 1, row, myType) ||
238 floodMoveCheck(column, row - 1, board[index(column, row)].type);
241 function createBlock(column,row)
243 // Note that we don't wait for the component to become ready. This will
244 // only work if the block QML is a local file. Otherwise the component will
245 // not be ready immediately. There is a statusChanged signal on the
246 // component you could use if you want to wait to load remote files.
247 if(component.status == 1){
248 var dynamicObject = component.createObject(gameCanvas,
249 {"type": Math.floor(Math.random() * 3),
250 "x": column*gameCanvas.blockSize,
251 "width": gameCanvas.blockSize,
252 "height": gameCanvas.blockSize,
253 "particleSystem": gameCanvas.ps});
254 if(dynamicObject == null){
255 console.log("error creating block");
256 console.log(component.errorString());
259 dynamicObject.y = row*gameCanvas.blockSize;
260 dynamicObject.spawned = true;
262 board[index(column,row)] = dynamicObject;
264 console.log("error loading block component");
265 console.log(component.errorString());
271 function initHighScoreBar()
273 var db = Sql.openDatabaseSync(
276 "Local SameGame High Scores",
281 tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
282 // Only show results for the current grid size
283 var rs = tx.executeSql('SELECT * FROM Scores WHERE gridSize = "'
284 + maxColumn + "x" + maxRow + '" ORDER BY score desc LIMIT 10');
285 if(rs.rows.length < 10)
288 highScoreBar = rs.rows.item(rs.rows.length - 1).score;
293 function saveHighScore(name)
298 var db = Sql.openDatabaseSync(
301 "Local SameGame High Scores",
304 var dataStr = "INSERT INTO Scores VALUES(?, ?, ?, ?)";
308 maxColumn + "x" + maxRow,
309 Math.floor(gameDuration / 1000)
313 tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
314 tx.executeSql(dataStr, data);
316 // Only show results for the current grid size
317 var rs = tx.executeSql('SELECT * FROM Scores WHERE gridSize = "'
318 + maxColumn + "x" + maxRow + '" ORDER BY score desc LIMIT 10');
319 var r = "\nHIGH SCORES for this grid size\n\n"
320 for (var i = 0; i < rs.rows.length; i++) {
321 r += (i+1) + ". " + rs.rows.item(i).name + ' got '
322 + rs.rows.item(i).score + ' points in '
323 + rs.rows.item(i).time + ' seconds.\n';
325 if(rs.rows.length == 10)
326 highScoreBar = rs.rows.item(9).score;