Juicy Squash - Match-3 Game
import Felgo 4.0
import QtQuick 2.0
Item {
id: gameArea
clip: true
property double blockSize
property int rows: 12
property int columns: 8
width: blockSize * columns
height: blockSize * rows
visible: gameArea.opacity > 0
enabled: gameArea.opacity == 1
property int maxTypes
property int clicks
property var field: []
property bool fieldLocked
property bool playerMoveInProgress
property var helperBlocks: []
property double comboFactor
property int comboScore
property bool overload
property bool gameEnded: false
signal gameOver()
signal initFinished()
onFieldLockedChanged: {
if(!fieldLocked && !gameEnded) {
findHelperBlocks()
if(helperBlocks.length === 0 || scene.remainingTime <= 0) {
gameEnded = true
gameOver()
}
else {
helperTimer.start()
}
}
else {
helperTimer.stop()
hideHelperBlocks()
}
}
Timer {
id: fallDownTimer
onTriggered: {
recursiveBlockRemoval()
}
}
Timer {
id: helperTimer
interval: 4000
onTriggered: {
showHelperBlocks()
}
}
function index(row, column) {
return row * columns + column
}
function initializeField() {
gameArea.clicks = 0
gameArea.maxTypes = 4
if(field.length != rows * columns)
createField()
fillField()
fieldLocked = false
playerMoveInProgress = false
comboFactor = 1
comboScore = 0
overload = false
gameEnded = false
hideHelperBlocks()
findHelperBlocks()
helperTimer.start()
initFinished()
}
function createField() {
for(var i = 0; i < rows; i++) {
for(var j = 0; j < columns; j++) {
gameArea.field[index(i, j)] = createBlock(i, j, 0)
}
}
}
function fillField() {
for(var i = 0; i < rows; i++) {
for(var j = 0; j < columns; j++) {
var currType = null
currType = Math.floor(utils.generateRandomValueBetween(0, gameArea.maxTypes))
var bannedType1 = -1
var bannedType2 = -1
if(j >= 2) {
bannedType1 = gameArea.field[index(i, j - 2)].type
}
if(currType !== null && i >= 2) {
bannedType2 = gameArea.field[index(i - 2, j)].type
}
while(currType === bannedType1 || currType === bannedType2) {
currType = (currType + 1) % gameArea.maxTypes
}
gameArea.field[index(i, j)].type = currType
}
}
}
function replaceFieldWithNewField() {
for(var i = 0; i < gameArea.field.length; i++) {
gameArea.field[i].opacity = 0
gameArea.field[i].type = utils.generateRandomValueBetween(0, gameArea.maxTypes - 1)
gameArea.field[i].fadeIn()
}
}
function removeAllBlocks() {
fieldLocked = true
overload = true
var counts = []
for(var row = 0; row < rows; row++) {
for(var col = 0; col < columns; col++) {
counts.push(columns/2)
}
}
removeConnectedBlocks(null, counts, true)
}
function createBlock(row, column, type) {
var properties = {
width: blockSize,
height: blockSize,
x: column * blockSize,
y: row * blockSize,
row: row,
column: column,
opacity: 1,
type: (type !== null)
? type
: Math.floor(utils.generateRandomValueBetween(0, gameArea.maxTypes))
}
var blockId = entityManager.createEntityFromEntityTypeAndVariationType({entityType: "block"})
var block = entityManager.getEntityById(blockId)
for (var propertyName in properties) {
block[propertyName] = properties[propertyName]
}
block.swapBlock.disconnect(handleSwap)
block.swapBlock.connect(handleSwap)
return block
}
function handleSwap(row, column, targetRow, targetColumn) {
if(fieldLocked || gameEnded)
return
if(targetRow >= 0 && targetRow < rows && targetColumn >= 0 && targetColumn < columns) {
fieldLocked = true
scene.gameSound.playMoveBlock()
swapBlocks(row, column, targetRow, targetColumn)
}
else {
scene.gameSound.playMoveBlockBack()
}
}
function handleSwapFinished(row, column, swapRow, swapColumn) {
if(!playerMoveInProgress) {
playerMoveInProgress = true
if(!startRemovalOfBlocks(row, column) && !startRemovalOfBlocks(swapRow, swapColumn)) {
scene.gameSound.playMoveBlockBack()
swapBlocks(row, column, swapRow, swapColumn)
}
else {
gameArea.clicks++
if((gameArea.maxTypes < 8) && (gameArea.clicks % 10 == 0))
gameArea.maxTypes++
playerMoveInProgress = false
}
}
else {
playerMoveInProgress = false
fieldLocked = false
}
}
function startRemovalOfBlocks(row, column) {
var type = field[index(row, column)].type
var fieldCopy = field.slice()
var blockCount = findHorizontallyConnectedBlocks(fieldCopy, row, column, type)
if(blockCount < 3) {
fieldCopy = field.slice()
blockCount = findVerticallyConnectedBlocks(fieldCopy, row, column, type)
}
if(blockCount >= 3) {
removeConnectedBlocks(fieldCopy, [blockCount], false)
return true
}
else
return false
}
function findHorizontallyConnectedBlocks(fieldCopy, row, col, type) {
var nrLeft = 1
while((col - nrLeft >= 0) && (fieldCopy[index(row, col - nrLeft)] !== null) && (fieldCopy[index(row, col - nrLeft)].type === type)) {
fieldCopy[index(row, col - nrLeft)] = null
nrLeft++
}
var nrRight = 1
while((col + nrRight < columns) && (fieldCopy[index(row, col + nrRight)] !== null) && (fieldCopy[index(row, col + nrRight)].type === type)) {
fieldCopy[index(row, col + nrRight)] = null
nrRight++
}
fieldCopy[index(row, col)] = null
return nrLeft + nrRight - 1
}
function findVerticallyConnectedBlocks(fieldCopy, row, col, type) {
var nrUp = 1
while((row - nrUp >= 0) && (fieldCopy[index(row - nrUp, col)] !== null) && (fieldCopy[index(row - nrUp, col)].type === type)) {
fieldCopy[index(row - nrUp, col)] = null
nrUp++
}
var nrDown = 1
while((row + nrDown < rows) && (fieldCopy[index(row + nrDown, col)] !== null) && (fieldCopy[index(row + nrDown, col)].type === type)) {
fieldCopy[index(row + nrDown, col)] = null
nrDown++
}
fieldCopy[index(row, col)] = null
return nrUp + nrDown - 1
}
function removeConnectedBlocks(fieldCopy, blockCounts, clearAll) {
if(clearAll) {
replaceFieldWithNewField()
}
else {
for(var i = 0; i < fieldCopy.length; i++) {
if(fieldCopy[i] === null) {
var block = gameArea.field[i]
if(block !== null) {
gameArea.field[i] = null
block.remove()
}
}
}
}
if(overload)
scene.gameSound.playOverloadClear()
else
scene.gameSound.playFruitClear()
for(var groupNr = 0; groupNr < blockCounts.length; groupNr++) {
var blockCount = blockCounts[groupNr]
var score = blockCount * (blockCount + 1) / 2
var totalScore = score * comboFactor
gameArea.comboScore += totalScore
scene.score += totalScore
}
if(clearAll) {
fallDownTimer.interval = 1000
fallDownTimer.start()
}
else {
moveBlocksToBottom()
}
}
function moveBlocksToBottom() {
var maxDistance = 0
var longestFallBlock = null
for(var col = 0; col < columns; col++) {
for(var row = rows - 1; row >= 0; row--) {
if(gameArea.field[index(row, col)] === null) {
var moveBlock = null
for(var moveRow = row - 1; moveRow >= 0; moveRow--) {
moveBlock = gameArea.field[index(moveRow,col)]
if(moveBlock !== null) {
gameArea.field[index(moveRow,col)] = null
gameArea.field[index(row, col)] = moveBlock
moveBlock.row = row
moveBlock.fallDown(row - moveRow)
break
}
}
if(moveBlock === null) {
var distance = row + 1
for(var newRow = row; newRow >= 0; newRow--) {
var newBlock = createBlock(newRow - distance, col, null)
gameArea.field[index(newRow, col)] = newBlock
newBlock.row = newRow
newBlock.fallDown(distance)
if(distance > maxDistance) {
maxDistance = distance
longestFallBlock = newBlock
}
}
break
}
}
}
}
if(longestFallBlock != null) {
longestFallBlock.fallDownFinished.connect(handleFallFinished)
}
}
function handleFallFinished(block) {
block.fallDownFinished.disconnect(handleFallFinished)
fallDownTimer.interval = 250
fallDownTimer.start()
}
function recursiveBlockRemoval() {
var blockCounts = []
var nextGameField = field.slice()
for(var row = rows-1; row >= 0; row--) {
for(var col = 0; col < columns; col++) {
var block = nextGameField[index(row, col)]
if(block !== null) {
var fieldCopy = nextGameField.slice()
var blockCount = findHorizontallyConnectedBlocks(fieldCopy, row, col, block.type)
if(blockCount < 3) {
fieldCopy = nextGameField.slice()
blockCount = findVerticallyConnectedBlocks(fieldCopy, row, col, block.type)
}
if(blockCount >= 3) {
nextGameField = fieldCopy
blockCounts.push(blockCount)
}
}
}
}
if(blockCounts.length === 0) {
var newJuicyLevel = scene.juicyMeterPercentage + (gameArea.comboScore / 5)
if(overload) {
newJuicyLevel = scene.juicyMeterPercentage + (gameArea.comboScore / 40)
overload = false
if(Math.round(utils.generateRandomValueBetween(0,1)) == 0)
scene.overlayText.showSmooth()
else
scene.overlayText.showDelicious()
}
if(newJuicyLevel > 100)
newJuicyLevel = 100
scene.juicyMeterPercentage = newJuicyLevel
scene.gameSound.playUpgrade()
comboFactor = 1
comboScore = 0
fieldLocked = false
return
}
else {
comboFactor += 0.5
if(!overload) {
if(comboFactor == 1.5) {
if(Math.round(utils.generateRandomValueBetween(0,1)) == 0)
scene.overlayText.showFruity()
else
scene.overlayText.showSweet()
}
else if(comboFactor == 2.5) {
scene.overlayText.showYummy()
}
else if(comboFactor == 3) {
scene.overlayText.showRefreshing()
}
}
removeConnectedBlocks(nextGameField, blockCounts, false)
}
}
function swapBlocks(row, column, row2, column2) {
var block = field[index(row, column)]
var block2 = field[index(row2, column2)]
block.swapFinished.disconnect(handleSwapFinished)
block2.swapFinished.disconnect(handleSwapFinished)
block2.swapFinished.connect(handleSwapFinished)
block.swap(row2, column2)
block2.swap(row, column)
field[index(row, column)] = block2
field[index(row2, column2)] = block
}
function findHelperBlocks() {
for(var row = rows - 1; row >= 0; row --) {
for (var col = 0; col < columns; col ++) {
var block = field[index(row, col)]
var type = block.type
if(row + 1 < rows) {
if(getLengthOfHorizontalBlock(row + 1, col, type, 0) >= 3
|| getLengthOfVerticalBlock(row + 1, col, type, 1) >= 3) {
helperBlocks.push(block)
return
}
}
if(row - 1 >= 0) {
if(getLengthOfHorizontalBlock(row - 1, col, type, 0) >= 3
|| getLengthOfVerticalBlock(row - 1, col, type, -1) >= 3) {
helperBlocks.push(block)
return
}
}
if(col + 1 < columns) {
if(getLengthOfVerticalBlock(row, col + 1, type, 0) >= 3
|| getLengthOfHorizontalBlock(row, col + 1, type, 1) >= 3) {
helperBlocks.push(block)
return
}
}
if(col - 1 >= 0) {
if(getLengthOfVerticalBlock(row, col - 1, type, 0) >= 3
|| getLengthOfHorizontalBlock(row, col - 1, type, -1) >= 3) {
helperBlocks.push(block)
return
}
}
}
}
}
function getLengthOfHorizontalBlock(row, col, type, directions) {
helperBlocks = []
var nr = 1
while(directions < 1 && col - nr >= 0 && field[index(row, col - nr)].type === type) {
helperBlocks.push(field[index(row, col - nr)])
nr++
}
nr = 1
while(directions > -1 && col + nr < columns && field[index(row, col + nr)].type === type) {
helperBlocks.push(field[index(row, col+nr)])
nr++
}
return helperBlocks.length + 1
}
function getLengthOfVerticalBlock(row, col, type, directions) {
helperBlocks = []
var nr = 1
while(directions < 1 && row - nr >= 0 && field[index(row - nr, col)].type === type) {
helperBlocks.push(field[index(row - nr, col)])
nr++
}
nr = 1
while(directions > -1 && row + nr < rows && field[index(row + nr, col)].type === type) {
helperBlocks.push(field[index(row + nr, col)])
nr++
}
return helperBlocks.length + 1
}
function showHelperBlocks() {
for(var i = 0; i < helperBlocks.length; i++) {
var block = helperBlocks[i]
block.highlight(true)
}
}
function hideHelperBlocks() {
for(var i = 0; i < helperBlocks.length; i++) {
var block = helperBlocks[i]
block.highlight(false)
}