Skip to content

Commit

Permalink
Add maze ball game
Browse files Browse the repository at this point in the history
  • Loading branch information
Ines333 committed Apr 9, 2024
1 parent 3f9bdc6 commit 1334734
Show file tree
Hide file tree
Showing 9 changed files with 574 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Examples/SwiftIOPlayground/12MoreProjects/MazeGame/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
17 changes: 17 additions & 0 deletions Examples/SwiftIOPlayground/12MoreProjects/MazeGame/Package.mmp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This is a MadMachine project file in TOML format
# This file holds those parameters that could not be managed by SwiftPM
# Edit this file would change the behavior of the building/downloading procedure
# Those project files in the dependent libraries would be IGNORED

# Specify the board name below
# There are "SwiftIOBoard" and "SwiftIOMicro" now
board = "SwiftIOMicro"

# Specifiy the target triple below
# There are "thumbv7em-unknown-none-eabi" and "thumbv7em-unknown-none-eabihf" now
# If your code use significant floating-point calculation,
# plz set it to "thumbv7em-unknown-none-eabihf"
triple = "thumbv7em-unknown-none-eabi"

# Reserved for future use
version = 1
29 changes: 29 additions & 0 deletions Examples/SwiftIOPlayground/12MoreProjects/MazeGame/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "MazeGame",
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"),
.package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"),
.package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"),
.package(url: "https://github.com/madmachineio/MadGraphics.git", branch: "main")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "MazeGame",
dependencies: [
"SwiftIO",
"MadBoards",
// Use specific library name rather than "MadDrivers" would speed up the build procedure.
.product(name: "ST7789", package: "MadDrivers"),
.product(name: "LIS3DH", package: "MadDrivers"),
"MadGraphics"
]),
]
)
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import MadGraphics

struct Ball {
var x1: Int
var y1: Int
let size: Int

var x2: Int {
x1 + size
}
var y2: Int {
y1 + size
}

init(at point: Point, size: Int) {
x1 = point.x
y1 = point.y
self.size = size
}
}
266 changes: 266 additions & 0 deletions Examples/SwiftIOPlayground/12MoreProjects/MazeGame/Sources/Game.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import MadGraphics
import ST7789
import SwiftIO

// Place a ball at the upper left corner of the maze and move it based on acceleration.
// If the ball reaches the destination (bottom right), the game ends.
// Press the D1 button to restart the game.
struct Game {
var maze: Maze
var ball: Ball
let ballColor = Color(UInt32(0xEFE891))

let width = 20
var speed = 2

let screen: ST7789
let canvas: Canvas
var frameBuffer: [UInt16]

init(screen: ST7789, canvas: Canvas) {
ball = Ball(at: Point(x: 1, y: 1), size: 7)
maze = Maze(width: width, canvas: canvas)
frameBuffer = [UInt16](repeating: 0, count: canvas.width * canvas.height)
self.screen = screen
self.canvas = canvas

maze.generate()

canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, color: ballColor)
updateDisplay(canvas: canvas, frameBuffer: &frameBuffer, screen: screen)
}

// Create a new maze and place the ball at the starting point.
mutating func reset() {
maze.reset()
maze.generate()

canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, color: maze.bgColor)
ball = Ball(at: Point(x: 1, y: 1), size: 7)
canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, color: ballColor)

updateDisplay(canvas: canvas, frameBuffer: &frameBuffer, screen: screen)
}

// Update the display to show that the game has finished.
mutating func finishGame() {
canvas.fillRectangle(at: Point(0, 0), width: canvas.width, height: canvas.height, color: Color.red)

var fileLength = 0
if let fontDataBuffer = openFile(path: "/lfs/Resources/Fonts/Roboto-Regular.ttf", length: &fileLength) {
let largeFont = Font(from: fontDataBuffer, length: fileLength, pointSize: 10, dpi: 220)
let largeText = largeFont.getMask("Good job!")
canvas.blend(from: largeText, foreground: Color.white, to: Point(x: (canvas.width - largeText.width) / 2, y: 60))

let font = Font(from: fontDataBuffer, length: fileLength, pointSize: 6, dpi: 220)
let text = font.getMask("Press D1 to continue")
canvas.blend(from: text, foreground: Color.white, to: Point(x: (canvas.width - text.width) / 2, y: 140))
}

updateDisplay(canvas: canvas, frameBuffer: &frameBuffer, screen: screen)
}

// Verify if the ball has reached the bottom right corner of the maze.
func finished() -> Bool {
return ball.x1 / width == maze.column - 1 && ball.y1 / width == maze.row - 1
}

// Update the ball's position based on the acceleration.
mutating func update(_ acceleration: (x: Float, y: Float, z: Float)) {
guard !finished() else {
finishGame()
return
}

let lastBallPos = Point(ball.x1, ball.y1)

// Move to the left.
if acceleration.x > 0.25 {
ball.x1 -= speed

let gridXmin = max(ball.x1 / width, 0)
let gridXmax = min(ball.x2 / width, maze.column - 1)
let gridYmin = max(ball.y1 / width, 0)
let gridYmax = min(ball.y2 / width, maze.row - 1)

// Check if the ball collides with any walls.
// If it does, reposition it close to the wall.
for y in gridYmin...gridYmax {
for x in gridXmin...gridXmax {
let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y))

if result.top || result.bottom || result.right {
ball.x1 = (x + 1) * width + 1
}

if result.left {
ball.x1 = x * width + 1
}
}
}
}

// Move to the right
if acceleration.x < -0.25 {
ball.x1 += speed

let gridXmin = max(ball.x1 / width, 0)
let gridXmax = min(ball.x2 / width, maze.column - 1)
let gridYmin = max(ball.y1 / width, 0)
let gridYmax = min(ball.y2 / width, maze.row - 1)

for y in gridYmin...gridYmax {
for x in gridXmin...gridXmax {
let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y))

if result.top || result.bottom || result.left {
ball.x1 = x * width - ball.size - 1
}

if result.right {
ball.x1 = (x + 1) * width - ball.size - 1
}
}
}
}

// Move downwards.
if acceleration.y > 0.25 {
ball.y1 += speed

let gridXmin = max(ball.x1 / width, 0)
let gridXmax = min(ball.x2 / width, maze.column - 1)
let gridYmin = max(ball.y1 / width, 0)
let gridYmax = min(ball.y2 / width, maze.row - 1)

for y in gridYmin...gridYmax {
for x in gridXmin...gridXmax {
let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y))

if result.bottom {
ball.y1 = (y + 1) * width - 1 - ball.size
}

if result.top || result.right || result.left {
ball.y1 = y * width - 1 - ball.size
}
}
}
}

// Move upwards.
if acceleration.y < -0.25 {
ball.y1 -= speed

let gridXmin = max(ball.x1 / width, 0)
let gridXmax = min(ball.x2 / width, maze.column - 1)
let gridYmin = max(ball.y1 / width, 0)
let gridYmax = min(ball.y2 / width, maze.row - 1)

for y in gridYmin...gridYmax {
for x in gridXmin...gridXmax {
let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y))

if result.top {
ball.y1 = y * width + 1
}

if result.bottom || result.right || result.left {
ball.y1 = (y + 1) * width + 1
}
}
}
}

// If the ball's position has changed, update the display.
if lastBallPos.x != ball.x1 || lastBallPos.y != ball.y1 {
canvas.fillRectangle(at: lastBallPos, width: ball.size, height: ball.size, color: maze.bgColor)
canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, color: ballColor)
updateDisplay(canvas: canvas, frameBuffer: &frameBuffer, screen: screen)
}
}

// Check if the ball collides with a wall.
func checkCollision(ballPos: Point, wallP1: Point, wallP2: Point) -> Bool {
return ball.x1 <= wallP2.x && ball.x2 >= wallP1.x && ball.y1 <= wallP2.y && ball.y2 >= wallP1.y
}

// Check if the ball collides with any wall of a cell in the maze grid.
func checkGridWalls(ballPos: Point, gridPos: Point) -> Wall {
let walls = maze.grids[maze.getIndex(gridPos)].walls
var result = Wall(top: false, right: false, bottom: false, left: false)

if walls.top &&
checkCollision(ballPos: ballPos, wallP1: Point(gridPos.x * width, gridPos.y * width), wallP2: Point((gridPos.x + 1) * width, gridPos.y * width)) {
result.top = true
}

if walls.right &&
checkCollision(ballPos: ballPos, wallP1: Point((gridPos.x + 1) * width, gridPos.y * width), wallP2: Point((gridPos.x + 1) * width, (gridPos.y + 1) * width)) {
result.right = true

}

if walls.bottom &&
checkCollision(ballPos: ballPos, wallP1: Point(gridPos.x * width, (gridPos.y + 1) * width), wallP2: Point((gridPos.x + 1) * width, (gridPos.y + 1) * width)) {
result.bottom = true
}

if walls.left &&
checkCollision(ballPos: ballPos, wallP1: Point(gridPos.x * width, gridPos.y * width), wallP2: Point(gridPos.x * width, (gridPos.y + 1) * width)) {
result.left = true
}

return result
}

// Open a font file.
func openFile(path: String, length: inout Int) -> UnsafeMutableRawBufferPointer? {
var fontDataBuffer: UnsafeMutableRawBufferPointer? = nil

print("open file:")
do {
let file = try FileDescriptor.open(path, .readOnly)

try file.seek(offset: 0, from: FileDescriptor.SeekOrigin.end)
let bytes = try file.tell()
fontDataBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bytes, alignment: 8)
length = bytes

try file.seek(offset: 0, from: FileDescriptor.SeekOrigin.start)
try file.read(into: fontDataBuffer!, count: bytes)
try file.close()
} catch {
print("Error, file handle error")
if let buffer = fontDataBuffer {
buffer.deallocate()
}
return nil
}

print("open file success")
return fontDataBuffer
}

// Get the region that needs to be updated and send data to the screen.
func updateDisplay(canvas: Canvas, frameBuffer: inout [UInt16], screen: ST7789) {
guard let dirty = canvas.getDirtyRect() else {
return
}

var index = 0
let stride = canvas.width
let canvasBuffer = canvas.buffer
for y in dirty.y0..<dirty.y1 {
for x in dirty.x0..<dirty.x1 {
frameBuffer[index] = Color.getRGB565LE(canvasBuffer[y * stride + x])
index += 1
}
}
frameBuffer.withUnsafeBytes { ptr in
screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: ptr)
}

canvas.finishRefresh()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
struct Wall {
var top: Bool
var right: Bool
var bottom: Bool
var left: Bool
}

struct Grid {
let x: Int
let y: Int
var walls = Wall(top: true, right: true, bottom: true, left: true)
var visited = false
}
Loading

0 comments on commit 1334734

Please sign in to comment.