Qt Quick Demo - Same Game▲
Sélectionnez
/**
**************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
***************************************************************************
*/
/* This script file handles the game logic */
.pragma library
.import
QtQuick.LocalStorage 2.0 as Sql
var maxColumn =
10
;
var maxRow =
13
;
var types =
3
;
var maxIndex =
maxColumn*
maxRow;
var board =
new
Array(maxIndex);
var blockSrc =
"Block.qml"
;
var gameDuration;
var component =
Qt.createComponent(blockSrc);
var gameCanvas;
var betweenTurns =
false
;
var puzzleLevel =
null;
var puzzlePath =
""
;
var gameMode =
"arcade"
; //Set in new game, then tweaks behaviour of other functions
var gameOver =
false
;
function changeBlock(src)
{
blockSrc =
src;
component =
Qt.createComponent(blockSrc);
}
// Index function used instead of a 2D array
function index(column, row)
{
return
column +
row *
maxColumn;
}
function timeStr(msecs)
{
var secs =
Math.floor(msecs/
1000
);
var m =
Math.floor(secs/
60
);
var ret =
""
+
m +
"m "
+
(secs%
60
) +
"s"
;
return
ret;
}
function cleanUp()
{
if
(gameCanvas ==
undefined)
return
;
// Delete blocks from previous game
for
(var i =
0
; i &
lt; maxIndex; i++
) {
if
(board[i] !=
null)
board[i].destroy();
board[i] =
null;
}
if
(puzzleLevel !=
null){
puzzleLevel.destroy();
puzzleLevel =
null;
}
gameCanvas.mode =
""
}
function startNewGame(gc, mode, map)
{
gameCanvas =
gc;
if
(mode ==
undefined)
gameMode =
"arcade"
;
else
gameMode =
mode;
gameOver =
false
;
cleanUp();
gc.gameOver =
false
;
gc.mode =
gameMode;
// Calculate board size
maxColumn =
Math.floor(gameCanvas.width/
gameCanvas.blockSize);
maxRow =
Math.floor(gameCanvas.height/
gameCanvas.blockSize);
maxIndex =
maxRow *
maxColumn;
if
(gameMode ==
"arcade"
) //Needs to be after board sizing
getHighScore();
// Initialize Board
board =
new
Array(maxIndex);
gameCanvas.score =
0
;
gameCanvas.score2 =
0
;
gameCanvas.moves =
0
;
gameCanvas.curTurn =
1
;
if
(gameMode ==
"puzzle"
)
loadMap(map);
else
//Note that we load them in reverse order for correct visual stacking
for
(var column =
maxColumn -
1
; column &
gt;=
0
; column--
)
for
(var row =
maxRow -
1
; row &
gt;=
0
; row--
)
createBlock(column, row);
if
(gameMode ==
"puzzle"
)
getLevelHistory();//Needs to be after map load
gameDuration =
new
Date();
}
var fillFound; // Set after a floodFill call to the number of blocks found
var floodBoard; // Set to 1 if the floodFill reaches off that node
// NOTE: Be careful with vars named x,y, as the calling object's x,y are still in scope
function handleClick(x,y)
{
if
(betweenTurns ||
gameOver ||
gameCanvas ==
undefined)
return
;
var column =
Math.floor(x/
gameCanvas.blockSize);
var row =
Math.floor(y/
gameCanvas.blockSize);
if
(column &
gt;=
maxColumn ||
column &
lt; 0
||
row &
gt;=
maxRow ||
row &
lt; 0
)
return
;
if
(board[index(column, row)] ==
null)
return
;
// If it's a valid block, remove it and all connected (does nothing if it's not connected)
floodFill(column,row, -
1
);
if
(fillFound &
lt;=
0
)
return
;
if
(gameMode ==
"multiplayer"
&
amp;&
amp; gameCanvas.curTurn ==
2
)
gameCanvas.score2 +=
(fillFound -
1
) *
(fillFound -
1
);
else
gameCanvas.score +=
(fillFound -
1
) *
(fillFound -
1
);
if
(gameMode ==
"multiplayer"
&
amp;&
amp; gameCanvas.curTurn ==
2
)
shuffleUp();
else
shuffleDown();
gameCanvas.moves +=
1
;
if
(gameMode ==
"endless"
)
refill();
else
if
(gameMode !=
"multiplayer"
)
victoryCheck();
if
(gameMode ==
"multiplayer"
&
amp;&
amp; !
gc.gameOver){
betweenTurns =
true
;
gameCanvas.swapPlayers();//signal, animate and call turnChange() when ready
}
}
function floodFill(column,row,type)
{
if
(board[index(column, row)] ==
null)
return
;
var first =
false
;
if
(type ==
-
1
) {
first =
true
;
type =
board[index(column,row)].type;
// Flood fill initialization
fillFound =
0
;
floodBoard =
new
Array(maxIndex);
}
if
(column &
gt;=
maxColumn ||
column &
lt; 0
||
row &
gt;=
maxRow ||
row &
lt; 0
)
return
;
if
(floodBoard[index(column, row)] ==
1
||
(!
first &
amp;&
amp; type !=
board[index(column, row)].type))
return
;
floodBoard[index(column, row)] =
1
;
floodFill(column +
1
, row, type);
floodFill(column -
1
, row, type);
floodFill(column, row +
1
, type);
floodFill(column, row -
1
, type);
if
(first ==
true
&
amp;&
amp; fillFound ==
0
)
return
; // Can't remove single blocks
board[index(column, row)].dying =
true
;
board[index(column, row)] =
null;
fillFound +=
1
;
}
function shuffleDown()
{
// Fall down
for
(var column =
0
; column &
lt; maxColumn; column++
) {
var fallDist =
0
;
for
(var row =
maxRow -
1
; row &
gt;=
0
; row--
) {
if
(board[index(column,row)] ==
null) {
fallDist +=
1
;
}
else
{
if
(fallDist &
gt; 0
) {
var obj =
board[index(column, row)];
obj.y =
(row +
fallDist) *
gameCanvas.blockSize;
board[index(column, row +
fallDist)] =
obj;
board[index(column, row)] =
null;
}
}
}
}
// Fall to the left
fallDist =
0
;
for
(column =
0
; column &
lt; maxColumn; column++
) {
if
(board[index(column, maxRow -
1
)] ==
null) {
fallDist +=
1
;
}
else
{
if
(fallDist &
gt; 0
) {
for
(row =
0
; row &
lt; maxRow; row++
) {
obj =
board[index(column, row)];
if
(obj ==
null)
continue
;
obj.x =
(column -
fallDist) *
gameCanvas.blockSize;
board[index(column -
fallDist,row)] =
obj;
board[index(column, row)] =
null;
}
}
}
}
}
function shuffleUp()
{
// Fall up
for
(var column =
0
; column &
lt; maxColumn; column++
) {
var fallDist =
0
;
for
(var row =
0
; row &
lt; maxRow; row++
) {
if
(board[index(column,row)] ==
null) {
fallDist +=
1
;
}
else
{
if
(fallDist &
gt; 0
) {
var obj =
board[index(column, row)];
obj.y =
(row -
fallDist) *
gameCanvas.blockSize;
board[index(column, row -
fallDist)] =
obj;
board[index(column, row)] =
null;
}
}
}
}
// Fall to the left (or should it be right, so as to be left for P2?)
fallDist =
0
;
for
(column =
0
; column &
lt; maxColumn; column++
) {
if
(board[index(column, 0
)] ==
null) {
fallDist +=
1
;
}
else
{
if
(fallDist &
gt; 0
) {
for
(row =
0
; row &
lt; maxRow; row++
) {
obj =
board[index(column, row)];
if
(obj ==
null)
continue
;
obj.x =
(column -
fallDist) *
gameCanvas.blockSize;
board[index(column -
fallDist,row)] =
obj;
board[index(column, row)] =
null;
}
}
}
}
}
function turnChange()//called by ui outside
{
betweenTurns =
false
;
if
(gameCanvas.curTurn ==
1
){
shuffleUp();
gameCanvas.curTurn =
2
;
victoryCheck();
}
else
{
shuffleDown();
gameCanvas.curTurn =
1
;
victoryCheck();
}
}
function refill()
{
for
(var column =
0
; column &
lt; maxColumn; column++
) {
for
(var row =
0
; row &
lt; maxRow; row++
) {
if
(board[index(column, row)] ==
null)
createBlock(column, row);
}
}
}
function victoryCheck()
{
// Awards bonuses for no blocks left
var deservesBonus =
true
;
if
(board[index(0
,maxRow -
1
)] !=
null ||
board[index(0
,0
)] !=
null)
deservesBonus =
false
;
// Checks for game over
if
(deservesBonus){
if
(gameCanvas.curTurn =
1
)
gameCanvas.score +=
1000
;
else
gameCanvas.score2 +=
1000
;
}
gameOver =
deservesBonus;
if
(gameCanvas.curTurn ==
1
){
if
(!
(floodMoveCheck(0
, maxRow -
1
, -
1
)))
gameOver =
true
;
}
else
{
if
(!
(floodMoveCheck(0
, 0
, -
1
, true
)))
gameOver =
true
;
}
if
(gameMode ==
"puzzle"
){
puzzleVictoryCheck(deservesBonus);//Takes it from here
return
;
}
if
(gameOver) {
var winnerScore =
Math.max(gameCanvas.score, gameCanvas.score2);
if
(gameMode ==
"multiplayer"
){
gameCanvas.score =
winnerScore;
saveHighScore(gameCanvas.score2);
}
saveHighScore(gameCanvas.score);
gameDuration =
new
Date() -
gameDuration;
gameCanvas.gameOver =
true
;
}
}
// Only floods up and right, to see if it can find adjacent same-typed blocks
function floodMoveCheck(column, row, type, goDownInstead)
{
if
(column &
gt;=
maxColumn ||
column &
lt; 0
||
row &
gt;=
maxRow ||
row &
lt; 0
)
return
false
;
if
(board[index(column, row)] ==
null)
return
false
;
var myType =
board[index(column, row)].type;
if
(type ==
myType)
return
true
;
if
(goDownInstead)
return
floodMoveCheck(column +
1
, row, myType, goDownInstead) ||
floodMoveCheck(column, row +
1
, myType, goDownInstead);
else
return
floodMoveCheck(column +
1
, row, myType) ||
floodMoveCheck(column, row -
1
, myType);
}
function createBlock(column,row,type)
{
// Note that we don't wait for the component to become ready. This will
// only work if the block QML is a local file. Otherwise the component will
// not be ready immediately. There is a statusChanged signal on the
// component you could use if you want to wait to load remote files.
if
(component.status ==
1
){
if
(type ==
undefined)
type =
Math.floor(Math.random() *
types);
if
(type &
lt; 0
||
type &
gt; 4
) {
console.log("Invalid type requested"
);//TODO: Is this triggered by custom levels much?
return
;
}
var dynamicObject =
component.createObject(gameCanvas,
{
"type"
: type,
"x"
: column*
gameCanvas.blockSize,
"y"
: -
1
*
gameCanvas.blockSize,
"width"
: gameCanvas.blockSize,
"height"
: gameCanvas.blockSize,
"particleSystem"
: gameCanvas.ps}
);
if
(dynamicObject ==
null){
console.log("error creating block"
);
console.log(component.errorString());
return
false
;
}
dynamicObject.y =
row*
gameCanvas.blockSize;
dynamicObject.spawned =
true
;
board[index(column,row)] =
dynamicObject;
}
else
{
console.log("error loading block component"
);
console.log(component.errorString());
return
false
;
}
return
true
;
}
function showPuzzleError(str)
{
//TODO: Nice user visible UI?
console.log(str);
}
function loadMap(map)
{
puzzlePath =
map;
var levelComp =
Qt.createComponent(puzzlePath);
if
(levelComp.status !=
1
){
console.log("Error loading level"
);
showPuzzleError(levelComp.errorString());
return
;
}
puzzleLevel =
levelComp.createObject();
if
(puzzleLevel ==
null ||
!
puzzleLevel.startingGrid instanceof Array) {
showPuzzleError("Bugger!"
);
return
;
}
gameCanvas.showPuzzleGoal(puzzleLevel.goalText);
//showPuzzleGoal should call finishLoadingMap as the next thing it does, before handling more events
}
function finishLoadingMap()
{
for
(var i in puzzleLevel.startingGrid)
if
(!
(puzzleLevel.startingGrid[i] &
gt;=
0
&
amp;&
amp; puzzleLevel.startingGrid[i] &
lt;=
9
) )
puzzleLevel.startingGrid[i] =
0
;
//TODO: Don't allow loading larger levels, leads to cheating
while
(puzzleLevel.startingGrid.length &
gt; maxIndex) puzzleLevel.startingGrid.shift();
while
(puzzleLevel.startingGrid.length &
lt; maxIndex) puzzleLevel.startingGrid.unshift(0
);
for
(var i in puzzleLevel.startingGrid)
if
(puzzleLevel.startingGrid[i] &
gt; 0
)
createBlock(i %
maxColumn, Math.floor(i /
maxColumn), puzzleLevel.startingGrid[i] -
1
);
//### Experimental feature - allow levels to contain arbitrary QML scenes as well!
//while (puzzleLevel.children.length)
// puzzleLevel.children[0].parent = gameCanvas;
gameDuration =
new
Date(); //Don't start until we finish loading
}
function puzzleVictoryCheck(clearedAll)//gameOver has also been set if no more moves
{
var won =
true
;
var soFar =
new
Date() -
gameDuration;
if
(puzzleLevel.scoreTarget !=
-
1
&
amp;&
amp; gameCanvas.score &
lt; puzzleLevel.scoreTarget){
won =
false
;
}
if
(puzzleLevel.scoreTarget !=
-
1
&
amp;&
amp; gameCanvas.score &
gt;=
puzzleLevel.scoreTarget &
amp;&
amp; !
puzzleLevel.mustClear){
gameOver =
true
;
}
if
(puzzleLevel.timeTarget !=
-
1
&
amp;&
amp; soFar/
1000.0
&
gt; puzzleLevel.timeTarget){
gameOver =
true
;
}
if
(puzzleLevel.moveTarget !=
-
1
&
amp;&
amp; gameCanvas.moves &
gt;=
puzzleLevel.moveTarget){
gameOver =
true
;
}
if
(puzzleLevel.mustClear &
amp;&
amp; gameOver &
amp;&
amp; !
clearedAll) {
won =
false
;
}
if
(gameOver) {
gameCanvas.gameOver =
true
;
gameCanvas.showPuzzleEnd(won);
if
(won) {
// Store progress
saveLevelHistory();
}
}
}
function getHighScore()
{
var db =
Sql.LocalStorage.openDatabaseSync(
"SameGame"
,
"2.0"
,
"SameGame Local Data"
,
100
);
db.transaction(
function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(game TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
// Only show results for the current grid size
var rs =
tx.executeSql('SELECT *
FROM Scores WHERE gridSize =
"'
+ maxColumn + "
x" + maxRow + '"
AND game =
"' + gameMode + '"
ORDER BY score desc');
if
(rs.rows.length &
gt; 0
)
gameCanvas.highScore =
rs.rows.item(0
).score;
else
gameCanvas.highScore =
0
;
}
);
}
function saveHighScore(score)
{
// Offline storage
var db =
Sql.LocalStorage.openDatabaseSync(
"SameGame"
,
"2.0"
,
"SameGame Local Data"
,
100
);
var dataStr =
"INSERT INTO Scores VALUES(?, ?, ?, ?)"
;
var data =
[
gameMode,
score,
maxColumn +
"x"
+
maxRow,
Math.floor(gameDuration /
1000
)
];
if
(score &
gt;=
gameCanvas.highScore)//Update UI field
gameCanvas.highScore =
score;
db.transaction(
function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(game TEXT, score NUMBER, gridSize TEXT, time NUMBER)');
tx.executeSql(dataStr, data);
}
);
}
function getLevelHistory()
{
var db =
Sql.LocalStorage.openDatabaseSync(
"SameGame"
,
"2.0"
,
"SameGame Local Data"
,
100
);
db.transaction(
function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS Puzzle(level TEXT, score NUMBER, moves NUMBER, time NUMBER)');
var rs =
tx.executeSql('SELECT *
FROM Puzzle WHERE level =
"' + puzzlePath + '"
ORDER BY score desc');
if
(rs.rows.length &
gt; 0
) {
gameCanvas.puzzleWon =
true
;
gameCanvas.highScore =
rs.rows.item(0
).score;
}
else
{
gameCanvas.puzzleWon =
false
;
gameCanvas.highScore =
0
;
}
}
);
}
function saveLevelHistory()
{
var db =
Sql.LocalStorage.openDatabaseSync(
"SameGame"
,
"2.0"
,
"SameGame Local Data"
,
100
);
var dataStr =
"INSERT INTO Puzzle VALUES(?, ?, ?, ?)"
;
var data =
[
puzzlePath,
gameCanvas.score,
gameCanvas.moves,
Math.floor(gameDuration /
1000
)
];
gameCanvas.puzzleWon =
true
;
db.transaction(
function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS Puzzle(level TEXT, score NUMBER, moves NUMBER, time NUMBER)');
tx.executeSql(dataStr, data);
}
);
}
function nuke() //For "Debug mode"
{
for
(var row =
1
; row &
lt;=
5
; row++
) {
for
(var col =
0
; col &
lt; 5
; col++
) {
if
(board[index(col, maxRow -
row)] !=
null) {
board[index(col, maxRow -
row)].dying =
true
;
board[index(col, maxRow -
row)] =
null;
}
}
}
if
(gameMode ==
"multiplayer"
&
amp;&
amp; gameCanvas.curTurn ==
2
)
shuffleUp();
else
shuffleDown();
if
(gameMode ==
"endless"
)
refill();
else
victoryCheck();
}