From d4963ea852d47ee7db6ba6f3cb9e81c209a4052e Mon Sep 17 00:00:00 2001 From: Sumer Shinde Date: Wed, 30 Oct 2024 13:16:28 -0400 Subject: [PATCH 1/7] #111: Completed snake logic without testing --- NERODevelopment/content/CMakeLists.txt | 2 + NERODevelopment/content/Snake.qml | 174 +++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 NERODevelopment/content/Snake.qml diff --git a/NERODevelopment/content/CMakeLists.txt b/NERODevelopment/content/CMakeLists.txt index c18a9d9..4821f50 100644 --- a/NERODevelopment/content/CMakeLists.txt +++ b/NERODevelopment/content/CMakeLists.txt @@ -39,6 +39,7 @@ qt6_add_qml_module(content TorqueValueComponent.qml TorqueAdj.qml SpeedMode.qml + Snake.qml TimerDisplay.qml MaxSpeedComparator.qml MaxDrawGraph.qml @@ -69,4 +70,5 @@ qt6_add_qml_module(content images/yellowbird-upflap.png images/yellowbird-midflap.png images/yellowbird-downflap.png + QML_FILES Snake.qml ) diff --git a/NERODevelopment/content/Snake.qml b/NERODevelopment/content/Snake.qml new file mode 100644 index 0000000..7275832 --- /dev/null +++ b/NERODevelopment/content/Snake.qml @@ -0,0 +1,174 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + id: gameWindow + width: 400 + height: 400 + visible: true + title: "Snake Game" + color: "black" + + property int tileSize: 20 + property int gridWidth: width / tileSize + property int gridHeight: height / tileSize + + property var snake: [] + property var food: { 'x': 0, 'y': 0 } + property string direction: "Right" + property bool gameOver: false + property int score: 0 + + Component.onCompleted: startGame() + + function startGame() { + snake = [{ x: Math.floor(gridWidth / 2), y: Math.floor(gridHeight / 2) }] + direction = "Right" + gameOver = false + score = 0 + placeFood() + gameTimer.start() + updateSnakeModel() + } + + function placeFood() { + do { + food.x = Math.floor(Math.random() * gridWidth) + food.y = Math.floor(Math.random() * gridHeight) + } while (isSnakePosition(food.x, food.y)) + } + + function isSnakePosition(x, y) { + for (let i = 0; i < snake.length; i++) { + if (snake[i].x === x && snake[i].y === y) { + return true + } + } + return false + } + + function checkCollision(x, y) { + if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) { + return true + } + for (let i = 1; i < snake.length; i++) { + if (snake[i].x === x && snake[i].y === y) { + return true + } + } + return false + } + + function updateGame() { + if (gameOver) { + gameTimer.stop() + return + } + + let head = { x: snake[0].x, y: snake[0].y } + + switch (direction) { + case "Left": + head.x -= 1 + break + case "Right": + head.x += 1 + break + case "Up": + head.y -= 1 + break + case "Down": + head.y += 1 + break + } + + if (checkCollision(head.x, head.y)) { + gameOver = true + return + } + + snake.unshift(head) + + if (head.x === food.x && head.y === food.y) { + score += 1 + placeFood() + } else { + snake.pop() + } + + updateSnakeModel() + } + + function updateSnakeModel() { + snakeModel.clear() + for (let segment of snake) { + snakeModel.append({ "x": segment.x, "y": segment.y }) + } + } + + Timer { + id: gameTimer + interval: 100 + repeat: true + running: false + onTriggered: updateGame() + } + + Keys.onPressed: { + if ((event.key === Qt.Key_Left || event.key === Qt.Key_A) && direction !== "Right") { + direction = "Left" + } else if ((event.key === Qt.Key_Right || event.key === Qt.Key_D) && direction !== "Left") { + direction = "Right" + } else if ((event.key === Qt.Key_Up || event.key === Qt.Key_W) && direction !== "Down") { + direction = "Up" + } else if ((event.key === Qt.Key_Down || event.key === Qt.Key_S) && direction !== "Up") { + direction = "Down" + } else if (event.key === Qt.Key_Space && gameOver) { + startGame() + } + } + + ListModel { + id: snakeModel + } + + Repeater { + model: snakeModel + Rectangle { + x: model.x * tileSize + y: model.y * tileSize + width: tileSize + height: tileSize + color: "lime" + } + } + + Rectangle { + x: food.x * tileSize + y: food.y * tileSize + width: tileSize + height: tileSize + color: "red" + } + + Text { + id: gameOverText + anchors.centerIn: parent + text: gameOver ? "Game Over\nPress Space to Restart" : "" + color: "white" + font.pixelSize: 24 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: gameOver + } + + Text { + id: scoreText + text: "Score: " + score + color: "white" + font.pixelSize: 16 + anchors.top: parent.top + anchors.topMargin: 10 + anchors.horizontalCenter: parent.horizontalCenter + } +} From 96a77d3fb95340053f21f6163478868007bb81b7 Mon Sep 17 00:00:00 2001 From: Sumer Shinde Date: Wed, 30 Oct 2024 13:18:02 -0400 Subject: [PATCH 2/7] cmake lists --- NERODevelopment/content/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/NERODevelopment/content/CMakeLists.txt b/NERODevelopment/content/CMakeLists.txt index 4821f50..1387557 100644 --- a/NERODevelopment/content/CMakeLists.txt +++ b/NERODevelopment/content/CMakeLists.txt @@ -70,5 +70,4 @@ qt6_add_qml_module(content images/yellowbird-upflap.png images/yellowbird-midflap.png images/yellowbird-downflap.png - QML_FILES Snake.qml ) From d635c6e64c3ce9204c568d1364fc426911936105 Mon Sep 17 00:00:00 2001 From: Sumer Shinde Date: Wed, 6 Nov 2024 01:02:13 -0500 Subject: [PATCH 3/7] #111:Modified Snake.qml --- NERODevelopment/content/Snake.qml | 109 ++++++++++++++++++------------ 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/NERODevelopment/content/Snake.qml b/NERODevelopment/content/Snake.qml index 7275832..60a2b50 100644 --- a/NERODevelopment/content/Snake.qml +++ b/NERODevelopment/content/Snake.qml @@ -1,29 +1,36 @@ import QtQuick 2.15 -import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 -Window { - id: gameWindow - width: 400 - height: 400 +Rectangle { + id: snakeGame + + anchors.fill: parent + focus: snakeGame.isFocused visible: true - title: "Snake Game" + + property bool isFocused: false + + width: 800 + height: 480 color: "black" property int tileSize: 20 property int gridWidth: width / tileSize property int gridHeight: height / tileSize - property var snake: [] + property var snakeBody: [] property var food: { 'x': 0, 'y': 0 } - property string direction: "Right" + property int direction: 1 // 0: Up, 1: Right, 2: Down, 3: Left property bool gameOver: false property int score: 0 + // Initialize the game when the component is ready Component.onCompleted: startGame() + // Function to start or restart the game function startGame() { - snake = [{ x: Math.floor(gridWidth / 2), y: Math.floor(gridHeight / 2) }] - direction = "Right" + snakeBody = [{ x: Math.floor(gridWidth / 2), y: Math.floor(gridHeight / 2) }] + direction = 1 // Start moving right gameOver = false score = 0 placeFood() @@ -31,6 +38,7 @@ Window { updateSnakeModel() } + // Place food at a random position not occupied by the snake function placeFood() { do { food.x = Math.floor(Math.random() * gridWidth) @@ -38,48 +46,54 @@ Window { } while (isSnakePosition(food.x, food.y)) } + // Check if a given position is occupied by the snake function isSnakePosition(x, y) { - for (let i = 0; i < snake.length; i++) { - if (snake[i].x === x && snake[i].y === y) { + for (let i = 0; i < snakeBody.length; i++) { + if (snakeBody[i].x === x && snakeBody[i].y === y) { return true } } return false } + // Check for collisions with walls or self function checkCollision(x, y) { + // Check walls if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) { return true } - for (let i = 1; i < snake.length; i++) { - if (snake[i].x === x && snake[i].y === y) { + // Check self-collision + for (let i = 1; i < snakeBody.length; i++) { + if (snakeBody[i].x === x && snakeBody[i].y === y) { return true } } return false } + // Update the game state on each timer tick function updateGame() { if (gameOver) { gameTimer.stop() + snakeController.saveScore(score) return } - let head = { x: snake[0].x, y: snake[0].y } + let head = { x: snakeBody[0].x, y: snakeBody[0].y } switch (direction) { - case "Left": - head.x -= 1 + case 0: // Up + head.y -= 1 break - case "Right": + case 1: // Right head.x += 1 break - case "Up": - head.y -= 1 - break - case "Down": + case 2: // Down head.y += 1 break + case 3: // Left + head.x -= 1 + break } if (checkCollision(head.x, head.y)) { @@ -87,25 +101,27 @@ Window { return } - snake.unshift(head) + snakeBody.unshift(head) if (head.x === food.x && head.y === food.y) { score += 1 placeFood() } else { - snake.pop() + snakeBody.pop() } updateSnakeModel() } + // Update the snake model for the Repeater function updateSnakeModel() { snakeModel.clear() - for (let segment of snake) { + for (let segment of snakeBody) { snakeModel.append({ "x": segment.x, "y": segment.y }) } } + // Timer to drive the game loop Timer { id: gameTimer interval: 100 @@ -114,47 +130,41 @@ Window { onTriggered: updateGame() } + // Handle key presses via snakeController Keys.onPressed: { - if ((event.key === Qt.Key_Left || event.key === Qt.Key_A) && direction !== "Right") { - direction = "Left" - } else if ((event.key === Qt.Key_Right || event.key === Qt.Key_D) && direction !== "Left") { - direction = "Right" - } else if ((event.key === Qt.Key_Up || event.key === Qt.Key_W) && direction !== "Down") { - direction = "Up" - } else if ((event.key === Qt.Key_Down || event.key === Qt.Key_S) && direction !== "Up") { - direction = "Down" - } else if (event.key === Qt.Key_Space && gameOver) { + snakeController.handleKeyPress(event.key) + if (event.key === Qt.Key_Return && gameOver) { startGame() } } + // ListModel to store snake segments ListModel { id: snakeModel } + // Display the snake using the SnakeBody component Repeater { model: snakeModel - Rectangle { + delegate: SnakeBody { x: model.x * tileSize y: model.y * tileSize - width: tileSize - height: tileSize - color: "lime" + dimension: tileSize } } - Rectangle { + // Display the food using the SnakeFood component + SnakeFood { x: food.x * tileSize y: food.y * tileSize - width: tileSize - height: tileSize - color: "red" + dimension: tileSize } + // Display game over text when the game ends Text { id: gameOverText anchors.centerIn: parent - text: gameOver ? "Game Over\nPress Space to Restart" : "" + text: gameOver ? "Game Over\nPress Enter to Restart" : "" color: "white" font.pixelSize: 24 horizontalAlignment: Text.AlignHCenter @@ -162,13 +172,22 @@ Window { visible: gameOver } + // Display the current score Text { id: scoreText text: "Score: " + score + font.pixelSize: 24 color: "white" - font.pixelSize: 16 anchors.top: parent.top - anchors.topMargin: 10 anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + } + + // Integrate with the snakeController + Connections { + target: snakeController + onDirectionChanged: { + direction = newDirection + } } } From 4be6a1715c7c0347cf10f2d9bf32542b652493c3 Mon Sep 17 00:00:00 2001 From: Sumer Shinde Date: Wed, 6 Nov 2024 17:05:39 -0500 Subject: [PATCH 4/7] #111: Mostly finished snake logic --- NERODevelopment/content/Snake.qml | 175 +++++++++++++++++++++++++++--- NERODevelopment/src/main.cpp | 3 + 2 files changed, 162 insertions(+), 16 deletions(-) diff --git a/NERODevelopment/content/Snake.qml b/NERODevelopment/content/Snake.qml index 49be328..ddfb600 100644 --- a/NERODevelopment/content/Snake.qml +++ b/NERODevelopment/content/Snake.qml @@ -2,10 +2,10 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 Rectangle { - id: snake + id: snakeGame anchors.fill: parent - focus: snake.isFocused + focus: snakeGame.isFocused visible: true property bool isFocused: false @@ -14,33 +14,176 @@ Rectangle { height: 480 color: "black" - property int minX: 200 - property int maxX: 600 - property int minY: 40 - property int maxY: 440 + property int tileSize: 20 + property int gridWidth: Math.floor(width / tileSize) + property int gridHeight: Math.floor(height / tileSize) - property var snakeBody: [{"x": 300, "y": 300}, {"x": 280, "y": 300}, {"x": 260, "y": 300}] - property int segmentSize: 20 - property string direction: "right" + property var snakeBody: [] + QtObject { + id: food + property int x: 0 + property int y: 0 + } + + property int direction: 1 // 0: Up, 1: Right, 2: Down, 3: Left + property bool gameOver: false property int score: 0 - Rectangle { - width: 400 - height: 400 + Component.onCompleted: startGame() + + function startGame() { + snakeBody = [{ x: Math.floor(gridWidth / 2), y: Math.floor(gridHeight / 2) }] + direction = 1 + gameOver = false + score = 0 + food.x = 0 + food.y = 0 + placeFood() + gameTimer.start() + updateSnakeModel() + } + + function placeFood() { + var x, y + do { + x = Math.floor(Math.random() * gridWidth) + y = Math.floor(Math.random() * gridHeight) + } while (isSnakePosition(x, y)) + food.x = x + food.y = y + } + + function isSnakePosition(x, y) { + for (let i = 0; i < snakeBody.length; i++) { + if (snakeBody[i].x === x && snakeBody[i].y === y) { + return true + } + } + return false + } + + function checkCollision(x, y) { + if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) { + return true + } + for (let i = 1; i < snakeBody.length; i++) { + if (snakeBody[i].x === x && snakeBody[i].y === y) { + return true + } + } + return false + } + + function updateGame() { + if (gameOver) { + gameTimer.stop() + snakeController.saveScore(score) + return + } + + let head = { x: snakeBody[0].x, y: snakeBody[0].y } + + switch (direction) { + case 0: // Up + head.y -= 1 + break + case 1: // Right + head.x += 1 + break + case 2: // Down + head.y += 1 + break + case 3: // Left + head.x -= 1 + break + } + + if (checkCollision(head.x, head.y)) { + gameOver = true + return + } + + snakeBody.unshift(head) + + if (head.x === food.x && head.y === food.y) { + score += 1 + placeFood() + } else { + snakeBody.pop() + } + + updateSnakeModel() + } + + function updateSnakeModel() { + snakeModel.clear() + for (let segment of snakeBody) { + snakeModel.append({ "x": segment.x, "y": segment.y }) + } + } + + Timer { + id: gameTimer + interval: 100 + repeat: true + running: false + onTriggered: updateGame() + } + + Keys.onPressed: { + console.log("Key pressed:", event.key) + snakeController.handleKeyPress(event.key) + if (event.key === Qt.Key_Return && gameOver) { + startGame() + } + } + + ListModel { + id: snakeModel + } + + Repeater { + model: snakeModel + delegate: SnakeBody { + x: model.x * tileSize + y: model.y * tileSize + dimension: tileSize + } + } + + SnakeFood { + x: food.x * tileSize + y: food.y * tileSize + dimension: tileSize + } + + Text { + id: gameOverText anchors.centerIn: parent - border.color: "white" - border.width: 2 - color: "transparent" + text: gameOver ? "Game Over\nPress Enter to Restart" : "" + color: "white" + font.pixelSize: 24 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: gameOver } Text { id: scoreText - text: "Score: " + snake.score + text: "Score: " + score font.pixelSize: 24 color: "white" anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 10 } + + Connections { + target: snakeController + onDirectionChanged: { + direction = newDirection + console.log("Direction updated to:", direction) + } + } } diff --git a/NERODevelopment/src/main.cpp b/NERODevelopment/src/main.cpp index 4472d76..b13b012 100644 --- a/NERODevelopment/src/main.cpp +++ b/NERODevelopment/src/main.cpp @@ -12,6 +12,7 @@ #include "controllers/keyboardcontroller.h" #include "controllers/navigationcontroller.h" #include "controllers/offviewcontroller.h" +#include "controllers/snakecontroller.h" #include "controllers/speedcontroller.h" #include "import_qml_components_plugins.h" #include "import_qml_plugins.h" @@ -50,6 +51,7 @@ int main(int argc, char *argv[]) { NavigationController navigationController(model); DebugTableController tableController(model); FlappyBirdController flappyBirdController(model); + SnakeController snakeController(model); ConfigurationController configurationController(model); KeyboardController keyboardController(model); DebugGraphController graphController(model); @@ -76,6 +78,7 @@ int main(int argc, char *argv[]) { &navigationController); engine.rootContext()->setContextProperty("flappyBirdController", &flappyBirdController); + engine.rootContext()->setContextProperty("snakeController", &snakeController); engine.rootContext()->setContextProperty("configurationController", &configurationController); engine.rootContext()->setContextProperty("keyboardViewController", From eb2f338cfae34c02ce1ec1997d5f61fb8e50eef7 Mon Sep 17 00:00:00 2001 From: Sumer Shinde Date: Tue, 12 Nov 2024 17:01:15 -0500 Subject: [PATCH 5/7] Changed length of inital snake to 3 --- NERODevelopment/content/Snake.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NERODevelopment/content/Snake.qml b/NERODevelopment/content/Snake.qml index ddfb600..911dce2 100644 --- a/NERODevelopment/content/Snake.qml +++ b/NERODevelopment/content/Snake.qml @@ -33,7 +33,13 @@ Rectangle { Component.onCompleted: startGame() function startGame() { - snakeBody = [{ x: Math.floor(gridWidth / 2), y: Math.floor(gridHeight / 2) }] + let centerX = Math.floor(gridWidth / 2) + let centerY = Math.floor(gridHeight / 2) + snakeBody = [ + { x: centerX, y: centerY }, + { x: centerX - 1, y: centerY }, + { x: centerX - 2, y: centerY } + ] direction = 1 gameOver = false score = 0 From ef3640c26b2147704b7d24ac5c9f0fc545c94098 Mon Sep 17 00:00:00 2001 From: Sumer Shinde Date: Thu, 14 Nov 2024 19:31:52 -0500 Subject: [PATCH 6/7] cmake --- NERODevelopment/content/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/NERODevelopment/content/CMakeLists.txt b/NERODevelopment/content/CMakeLists.txt index 838aa7e..74c529c 100644 --- a/NERODevelopment/content/CMakeLists.txt +++ b/NERODevelopment/content/CMakeLists.txt @@ -48,7 +48,6 @@ qt6_add_qml_module(content Arrow.qml OffScreen2.qml StatusDisplay.qml - Snake.qml SnakeBody.qml SnakeFood.qml From 4b0177625a41cb47c9058a4f81354d2d31375d6d Mon Sep 17 00:00:00 2001 From: mattrwang Date: Thu, 5 Dec 2024 19:55:12 -0500 Subject: [PATCH 7/7] fixed crashing --- NERODevelopment/content/Snake.qml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/NERODevelopment/content/Snake.qml b/NERODevelopment/content/Snake.qml index 911dce2..7befdf3 100644 --- a/NERODevelopment/content/Snake.qml +++ b/NERODevelopment/content/Snake.qml @@ -22,8 +22,8 @@ Rectangle { QtObject { id: food - property int x: 0 - property int y: 0 + property int x: Math.floor(gridWidth / 3 * 2) + property int y: Math.floor(gridHeight / 2) } property int direction: 1 // 0: Up, 1: Right, 2: Down, 3: Left @@ -43,9 +43,8 @@ Rectangle { direction = 1 gameOver = false score = 0 - food.x = 0 - food.y = 0 - placeFood() + food.x = Math.floor(gridWidth / 3 * 2) + food.y = centerY gameTimer.start() updateSnakeModel() }