harbour-tetrafish/qml/pages/GamePage.qml
2025-05-31 11:45:54 +02:00

729 lines
27 KiB
QML

/*****************************************************************************
** **
** Created by Antonio Mancini **
** Contact: <ziobilly94@gmail.com> **
** This is a version of classic Tetris game for SailfishOS developed **
** entirely by me, no copyright infringement intended. **
** **
*****************************************************************************/
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtQuick.LocalStorage 2.0
import QtQuick.Particles 2.0
import "../js/settings.js" as Settings
import "../game"
Page {
id: page
HighScoreModel {
id: highScoreModel
game: hsdb
}
Functions {
id: functions
}
property bool onlineEnabled: Settings.getValue("onlineEnabled",0)
property string hsdb: onlineEnabled ? "online" : "local"
property int dots: Settings.getValue("dots",1)
property int ghostEnabled: Settings.getValue("ghostEnabled",1)
property int musicEnabled: Settings.getValue("musicEnabled",1)
property int sfxEnabled: Settings.getValue("sfxEnabled",1)
property string musicScore: "../data/tetris.ogg" //: Settings.getValue("musicScore","")
property int scoreValue
property int speedValue
property int linesValue
property int level: 1
property int linesTotal: 0
property int activeBlock
property int futureBlock: -1
property int combo: 1
property int gravityBreak: 1
property bool pauseVal
property int downSpeed: 2500
property int comboSpeed: 10000
// 0 = l_normal; 1 = l_reverse;
// 2 = s_normal; 3 = s_reverse;
// 4 = t_normal; 5 = square;
// 6 = line
property variant activeColor
property variant futureColor
property real centerX
property real centerY
Timer {
id: gameOverTimer
property int i: 15
property int j: 10
property bool clear: true
property bool done: false
interval: 10
repeat: true
onTriggered : {
if (clear) {
if ( j === 0) {
i--
j = 10
} else {
if ( i === 0) {
clear = false
done = true
j = 10
i = 15
} else {
var index = i*12+j
repeater.itemAt(index).color = Theme.secondaryHighlightColor//"black"//"transparent"//Theme.secondaryColor
repeater.itemAt(index).opacity = 0.5//0.5
j--
}
}
} else {
if ( j === 0) {
i--
j = 10
} else {
if ( i === 0) {
running = false
clear = true
if (highScoreModel.newScore) {
highScoreModel.newScore = false
getName.visible = true ;
} else {
pullDownMenu.enabled = true
splash.visible = true
}
i = 15
j = 10
}
else {
done = false
index = i*12+j
repeater.itemAt(index).opacity = 0.1
j--
}
}
}
}
}
function downSpeedCalc(level) {
if (level>1){
for ( var i = 1; i < level; i++) {
var ds = downSpeed/15
downSpeed -= ds
}
return downSpeed
}
}
function comboSpeedCalc(level) {
for ( var i = 1; i < level; i++) {
comboSpeed = comboSpeed/30
}
return comboSpeed+200
}
Timer {
id: downTimer
interval: downSpeed+150//interval-(interval/20)+150 //difficulty*(1338*Math.pow(Math.E,-0.07*level)+150) //difficulty*(1338*Math.pow(Math.E,-0.26*level)+150)
repeat: true
running: false
onTriggered: {
functions.flow()
speedValue += 1
}
}
SilicaFlickable {
id: root
anchors.fill: page
contentHeight : height
PushUpMenu {
id:pushUpMenu
enabled: false
visible: false
MenuItem {
id: pauseMenuItem
text: qsTr("Resume")
onClicked: functions.pause()
}
}
PullDownMenu {
id: pullDownMenu
MenuItem {
text: qsTr("About")
onClicked: pageStack.push("About.qml")
}
MenuItem {
text: qsTr("Settings")
onClicked: {
var dialog = pageStack.push("SettingsDialog.qml", {"dots": dots, "ghostEnabled": ghostEnabled, "musicEnabled": musicEnabled, "sfxEnabled": sfxEnabled, "musicScore": musicScore, "onlineEnabled": onlineEnabled})
dialog.accepted.connect(function() {
dots = dialog.dots
ghostEnabled = dialog.ghostEnabled
musicEnabled = dialog.musicEnabled
sfxEnabled = dialog.sfxEnabled
onlineEnabled = dialog.onlineEnabled
highScoreModel.place = 0
Settings.setValue("dots",dots)
Settings.setValue("ghostEnabled",ghostEnabled)
Settings.setValue("musicEnabled",musicEnabled)
Settings.setValue("sfxEnabled",sfxEnabled)
Settings.setValue("onlineEnabled",onlineEnabled)
// highScoreModel.game = "local"
})
}
}
MenuItem {
text: functions.garunning ? functions.gapaused ? qsTr("Resume") : qsTr("Pause") : qsTr("New Game")
onClicked: {
if (functions.garunning) {
functions.pause()
} else {
splash.visible = false
highscores.visible = false
functions.newGame()
}
}
}
}
Label {
id: score
text: qsTr("LEVEL ") + "\n" + qsTr("SCORE ") + "\n" + qsTr("LINES ")
color: Theme.secondaryHighlightColor
font.family: Theme.fontFamilyHeading
font.pixelSize: Theme.fontSizeMedium
anchors {
left: parent.left
leftMargin: Theme.paddingMedium
bottom: futureGrid.bottom
}
}
Label {
id: scoreValues
text: level + "\n" + scoreValue + "\n" + linesTotal
color: Theme.highlightColor
font.family: Theme.fontFamilyHeading
font.bold: true
font.pixelSize: Theme.fontSizeMedium
horizontalAlignment: Text.AlignRight
anchors {
right: bonusPanel.left
rightMargin: Theme.paddingLarge
bottom: futureGrid.bottom
}
}
Rectangle {
id: bonusPanel
radius: height / 2
height: futureGrid.height
width: height
color: Theme.highlightDimmerColor
opacity: bonusLabel.opacity > 0 ? 0.8 : 0//bonusLabel.opacity
anchors {
horizontalCenter: parent.horizontalCenter
top: futureGrid.top
}
}
Label {
id: bonusLabel
text: ""///functions.garunning ? "" : "PLAY"
color: Theme.highlightColor
font.pixelSize: functions.garunning ? bonusPanel.height - (Theme.paddingMedium * 2) : Theme.fontSizeLarge
font.bold: true
anchors.verticalCenter: bonusPanel.verticalCenter
anchors.horizontalCenter: bonusPanel.horizontalCenter
opacity: 0//functions.garunning ? 0 : 1//infoTimer.running ? 1 : 0
Behavior on opacity {
FadeAnimator {}
}
}
Grid {
id: futureGrid
y: (parent.height-rect.height-height)/2
anchors {
right: parent.right
rightMargin: Theme.paddingLarge
}
columns: 4
rows: 3
Repeater {
id: futureRepeater
model: 12
delegate: Dot {width: Theme.paddingLarge*5/3 ;color: Theme.highlightBackgroundColor; opacity: 0.1}
}
}
Timer { // EXPERIMENT
id: hssplachTimer
running: splash.visible || highscores.visible && Qt.application.active && !contextMenu.visible? true : false//!functions.garunning && !gameOverTimer.running ? true : false
interval: 8000
repeat: true
onTriggered: if (highscores.visible) {highscores.visible=false;splash.visible = true;} else {highscores.visible=true;splash.visible=false}//highscores.visible ? splash.visible = true : high.visible = false
}
Rectangle {
id: rect
width: grid.width
height: grid.height
anchors {
bottom: parent.bottom
}
border.color: "transparent"
color: "black"
opacity: 0.8//0.8
Grid {
id: grid
columns: 12
rows: 17
Repeater {
id: repeater
model: 204
delegate: Dot {width: page.width/12 ;color: Theme.highlightBackgroundColor; opacity: 0.1}
onItemAdded: { // Bordi "muro"... seriamente, si può fare di meglio, 576 caratteri per una linea è più brutto di questo commento, da aggiungere a newGame() magari
if (index < 12 || index > 191 || index === 0 || index === 12 ||
index === 24 || index === 36 || index === 48 || index === 60 ||
index === 72 || index === 84 || index === 96 || index === 108 ||
index === 120 || index === 132 || index === 144 || index === 156 ||
index === 168 || index === 180 || index === 23 || index === 35 ||
index === 47 || index === 59 || index === 71 || index === 83 ||
index === 95 || index === 107 || index === 119 || index === 131 ||
index === 143 || index === 155 || index === 167 || index === 179 || index === 191)
{
itemAt(index).active = 3
itemAt(index).color = Theme.highlightBackgroundColor
itemAt(index).opacity = 0.5
}
}
}
}
/* ParticleSystem {
id: sys
}
Emitter {
anchors.fill: parent
system: sys
ImageParticle {
anchors.fill: parent
system: sys
source: "../data/dot.png"
clip: true
id: redblip
}
velocity: PointDirection {yVariation: 16; xVariation: 5}
acceleration: PointDirection {y: -16}
lifeSpan: Emitter.InfiniteLife
maximumEmitted: 1000
} */
MultiPointTouchArea {
id: mouseArea
property int offsetX: grid.width/14
property int offsetY: (grid.height/19)
property int blahX: (parent.width - (grid.width-point1.x))/offsetX
property int blahY: (parent.height - (grid.height-point1.y))/offsetY
property int prevX
property int prevY
property int initX
property int initY
property int inter: 85
property int plus: 0
x: 0
y: 0
anchors.fill: grid
anchors.margins: offsetX
mouseEnabled: true
touchPoints: [
TouchPoint { id: point1 },
TouchPoint { id: point2 },
TouchPoint { id: point3 }
]
onTouchUpdated: {
if (functions.maenabled){
if (point1.pressed && point2.pressed && point3.pressed){
if (blahY > prevY+2){
prevY=blahY
functions.instantDown()
}
} else if (point1.pressed && point2.pressed && !point3.pressed){
if (blahY > prevY){
prevY=blahY
functions.down()
}
if (blahY < prevY-2){
prevY=blahY
functions.pause()
}
} else if (point1.pressed && !point2.pressed && !point3.pressed){
//if (blahY==prevY) var plus = 0; else plus = 1;
if (blahY > prevY){
plus = 1;
functions.down()
prevY=blahY
} else {
if (blahX < prevX-plus) {
functions.left()
prevX=blahX
plus = 0
}
if (blahX > prevX+plus){
functions.right()
prevX=blahX
plus = 0
}
}
/* if (blahY > prevY){
prevY=blahY
functions.down()
} */
}
// console.log(blahX + " - " + prevX)
}
}
onReleased: {
if (blahY == initY && blahX == initX && !point1.pressed && !point2.pressed || point1.pressed && !point2.pressed){
functions.rotate()
}
functions.maenabled = true
}
onPressed: {
prevX = blahX
initX = blahX
prevY = blahY
initY = blahY
}
}
Label {
id: gameOverLabel
anchors {
horizontalCenter: rect.horizontalCenter
verticalCenter: rect.verticalCenter
}
style: Text.Outline; styleColor: Theme.secondaryHighlightColor
text: "GAME OVER"
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeHuge
font.bold: true
visible: !functions.garunning && opacity > 0 ? true : false
opacity: gameOverTimer.running ? 1 : 0
Behavior on opacity {
FadeAnimator {}
}
}
Label {
id: pauseLabel
anchors {
horizontalCenter: rect.horizontalCenter
verticalCenter: rect.verticalCenter
}
style: Text.Outline; styleColor: Theme.secondaryHighlightColor
text: "PAUSED"
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeHuge
font.bold: true
visible: false
states: [
State { when: stateVisible;
PropertyChanges { target: myRect; opacity: 1.0 }
},
State { when: !stateVisible;
PropertyChanges { target: myRect; opacity: 0.0 }
}
]
transitions: Transition {
NumberAnimation { property: "opacity"; duration: 300}
}
}
Label {
id: infoLabel
anchors {
verticalCenter: rect.verticalCenter
horizontalCenter: rect.horizontalCenter
}
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
style: Text.Outline; styleColor: Theme.secondaryHighlightColor;
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeLarge*2
font.bold: true
opacity: infoTimer.running ? 1 : 0
Behavior on opacity {
FadeAnimator {}
}
}
Timer {
id: infoTimer
running: false
repeat: false
interval: 1100
onTriggered: {
infoLabel.opacity = 0
}
}
}
Rectangle {
id: highscores
anchors.verticalCenter: rect.verticalCenter
anchors.horizontalCenter: rect.horizontalCenter
width: rect.width - (rect.width/6)
height: rect.height - (rect.height/8)+Theme.paddingSmall
visible: splash.visible || functions.garunning || getName.visible && opacity === 0 ? false : true //!splash.visible && !getName.visible && !functions.garunning
color: "transparent"//Theme.highlightBackgroundColor
opacity: !visible ? 0 : 0.8
Behavior on opacity {
FadeAnimator {onStopped: console.log("highscores fade animation klar")}
}
Label {
id: hs1
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Theme.paddingLarge
text: "HIGHSCORES ["+highScoreModel.game+"]"
color: Theme.secondaryHighlightColor
font.pixelSize: Theme.fontSizeLarge
font.bold: true
MouseArea {
anchors.fill: hs1
onClicked: {
!onlineEnabled ? onlineEnabled=true : onlineEnabled=false
console.log("ONLINE: "+highScoreModel.game)
}
}
}
ListView {
width: parent.width; height: parent.height - hs1.height
enabled: true
anchors.top: hs1.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Theme.paddingLarge
model: highScoreModel
delegate: ListItem {
id: showDelegate
// menu: contextMenu
highlighted: index == highScoreModel.place-1 ? true : false
width: ListView.view.width
height: menuOpen ? contextMenu.height + pn.height + Theme.paddingSmall : pn.height + Theme.paddingSmall//contextMenu.height + contentItem.height + Theme.paddingMedium : contentItem.height + Theme.paddingMedium
contentHeight: pn.height + Theme.paddingSmall
Text {
id: pn
font.pixelSize: Theme.fontSizeLarge
anchors.left: parent.left
anchors.leftMargin: Theme.paddingMedium
color: Theme.highlightColor
text: index+(1)+"."
}
Text {
id: pnm
font.pixelSize: Theme.fontSizeLarge
anchors.left: pn.right
anchors.leftMargin: Theme.paddingMedium
anchors.top: pn.top
color: Theme.highlightColor
text: player
}
Text {
font.pixelSize: Theme.fontSizeLarge
anchors.right: parent.right
anchors.rightMargin: Theme.paddingMedium
anchors.top: pn.top
color: Theme.highlightColor
text: score
}
}
}
}
ContextMenu {
id: contextMenu
width: rect.width - (rect.width/6)
anchors.horizontalCenter: highscores.horizontalCenter
MenuItem {
id:onlineMenu
visible: true
text: qsTr("Share score online")
onClicked: {
var uuid = generateUUID()
console.log("UUID: "+uuid)
/* internal ? ps(source) : model.id == 0 ? ps(source) : cps(model.id)
radioStation = title
if (icon == "0") picon = "../allradio-data/images/allradio.png"
else if (icon.search(".png")>0) picon = icon.toLowerCase(); // The old save in database
else picon = "../allradio-data/images/"+icon+".png";
website = (Qt.resolvedUrl(site)) */
}
}
}
Rectangle {
id: splash
anchors.verticalCenter: rect.verticalCenter
anchors.horizontalCenter: rect.horizontalCenter
width: rect.width - (rect.width/6)
height: rect.height - (rect.height/8) + Theme.paddingSmall
visible: functions.garunning || getName.visible || highscores.visible && opacity === 0 ? false : true//true && !functions.garunning//!functions.garunning && !getName.visible && !highscores.visible//&& !gameOverTimer.done ? true : false //&& gameOverTimer.running ? done :
color: "transparent"//Theme.highlightBackgroundColor
opacity: !visible ? 0 : 0.8
Behavior on opacity {
FadeAnimator {}
}
Column {
spacing: Theme.paddingLarge
anchors {
horizontalCenter: splash.horizontalCenter
verticalCenter: splash.verticalCenter
}
Label {
id: name
anchors.horizontalCenter: parent.horizontalCenter
text: "TetraFish"
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeHuge
font.bold: true
}
Image {
anchors.horizontalCenter: parent.horizontalCenter
id: tetraFish
width: name.width
height: width
source: "../data/tetrafish.png"
}
Label {
id: info1
anchors.horizontalCenter: parent.horizontalCenter
text: "A TETRIS clone"
color: Theme.secondaryHighlightColor
font.pixelSize: Theme.fontSizeLarge
font.bold: true
}
Label {
id: info2
anchors.horizontalCenter: parent.horizontalCenter
text: "Presented by NESNOMIS"
color: Theme.secondaryHighlightColor
font.pixelSize: Theme.fontSizeSmall
font.bold: true
}
}
}
Rectangle {
id: getName
anchors.fill: rect
anchors.left: rect.left
anchors.top: rect.top
width: rect.width
height: rect.height
visible: false//!functions.garunning && !gameOverTimer.done ? true : false //&& gameOverTimer.running ? done :
color: "black"//Theme.highlightBackgroundColor
opacity: !visible ? 0 : 0.8
Behavior on opacity {
FadeAnimator {}
}
Label {
id: congrat
anchors.horizontalCenter: parent.horizontalCenter
text: "CONGRATULATIONS"//+highScoreModel.place
color: Theme.highlightColor
anchors.bottom: namn.top
font.pixelSize: Theme.fontSizeExtraLarge
font.bold: true
}
Label {
id: namn
anchors.horizontalCenter: parent.horizontalCenter
text: "You placed No. "+highScoreModel.place
color: Theme.primaryColor
anchors.bottom: textField.top
anchors.bottomMargin: Theme.paddingLarge * 3
font.pixelSize: Theme.fontSizeMedium
font.bold: true
}
TextField {
id: textField
enabled: parent.visible
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
width: parent.width
maximumLength: 8
focus: true
placeholderText: "ENTER YOUR NAME"
inputMethodHints: Qt.ImhUppercaseOnly | Qt.ImhPreferUppercase | Qt.ImhNoPredictiveText
horizontalAlignment: TextInput.AlignHCenter
font.bold: true
font.pixelSize: Theme.fontSizeHuge
font.capitalization: Font.AllUppercase
EnterKey.iconSource: "image://theme/icon-m-enter" //"image://theme/icon-m-enter-close"
EnterKey.onClicked: {
getName.visible = false
splash.visible = false
highscores.visible = true
pullDownMenu.enabled = true
root.interactive = true
functions.saveHighscore(text.toUpperCase(),scoreValue)
}
}
}
}
function generateUUID(){
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c === 'x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
}
Component.onCompleted: splash.visible = true
}