diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.swift new file mode 100644 index 0000000..8d25e0c --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.swift @@ -0,0 +1,28 @@ +// 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: "Fireworks", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/CFreeType", from: "2.13.0"), + ], + 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: "Fireworks", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType" + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Fonts/Roboto-Regular.ttf b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Fonts/Roboto-Regular.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Sounds/boom.wav b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Sounds/boom.wav new file mode 100644 index 0000000..b67d344 Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Sounds/boom.wav differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Firework.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Firework.swift new file mode 100644 index 0000000..86102f4 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Firework.swift @@ -0,0 +1,89 @@ +import MadGraphics +import ST7789 +import SwiftIO + +struct Firework { + var particle: Particle + var sparks: [Spark] = [] + + var exploded = false + + let color: Color + let size = 2 + + // Create a firework at the bottom of the screen. + init(color: Color, maxWidth: Int) { + self.color = color + + let x = Array(0..<(maxWidth - 1)).shuffled().randomElement()! + particle = Particle(pos: Point(x: x, y: maxWidth - 1)) + } + + // Update firework's position. + // If it explodes, it will return true to indicate it's time to play sound. + mutating func update(_ layer: Layer) -> Bool { + if exploded { + for spark in sparks { + layer.draw() { canvas in + canvas.fillCircle(at: spark.pos, radius: size, data: Color.black.rawValue) + } + } + + updateSparks() + + for spark in sparks { + let color = Color.blend(foreground: color.rawValue, background: Color.black.rawValue, mask: spark.lifespan) + layer.draw() { canvas in + canvas.fillCircle(at: spark.pos, radius: size, data: color.rawValue) + } + } + } else { + layer.draw() { canvas in + canvas.fillCircle(at: particle.pos, radius: size, data: Color.black.rawValue) + } + + let playSound = updateParticle() + + layer.draw() { canvas in + canvas.fillCircle(at: particle.pos, radius: size, data: color.rawValue) + } + return playSound + } + + return false + } + + // Update sparks' position and speed over time. + mutating func updateSparks() { + for i in sparks.indices.reversed() { + sparks[i].update() + // If + if sparks[i].done() { + sparks.remove(at: i) + } + } + } + + // Update particle's position and speed over time. + mutating func updateParticle() -> Bool { + particle.update() + if particle.willExplode() { + exploded = true + explode() + return true + } + + return false + } + + // Generate firework sparks after explosion. + mutating func explode() { + for _ in 0..<100 { + sparks.append(Spark(pos: particle.pos)) + } + } + + func done() -> Bool { + return exploded && sparks.count == 0 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Fireworks.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Fireworks.swift new file mode 100644 index 0000000..2724192 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Fireworks.swift @@ -0,0 +1,94 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics + +// Use lock to protect data from simultaneous access by multiple threads. +let i2sLock = Mutex() +// Whether the speaker will play sound. +var playSound = false + +@main +public struct Fireworks { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + let bl = DigitalOut(Id.D2) + let rst = DigitalOut(Id.D12) + let dc = DigitalOut(Id.D13) + let cs = DigitalOut(Id.D5) + let spi = SPI(Id.SPI0, speed: 30_000_000) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.width) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.width) + + var colorIndex = 0 + let colors: [Color] = [ + .pink, .red, .lime, .blue, .cyan, + .purple, .magenta, .orange, .yellow + ] + + let layer = Layer(at: Point.zero, anchorPoint: UnitPoint.zero, width: screen.width, height: screen.height) + + + let font = Font(path: "/lfs/Resources/Fonts/Roboto-Regular.ttf", pointSize: 10, dpi: 220) + let text1 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 40), anchorPoint: UnitPoint.center, string: "Happy", font: font, foregroundColor: Color.red) + let text2 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 80), anchorPoint: UnitPoint.center, string: "Christmas", font: font, foregroundColor: Color.red) + + layer.append(text1) + layer.append(text2) + + var fireworks: [Firework] = [] + var exploded = false + + createThread( + name: "play_sound", + priority: 3, + stackSize: 1024 * 64, + soundThread + ) + + sleep(ms: 10) + + while true { + if Int.random(in: 0..<100) < 10 { + fireworks.append(Firework(color: colors[colorIndex], maxWidth: layer.bounds.width)) + // Update firwork's color. + colorIndex += 1 + if colorIndex == colors.count { + colorIndex = 0 + } + } + + var i = 0 + while i < fireworks.count { + if fireworks[i].update(layer) { + exploded = true + } + + // If a all sparks of a firework disppear, remove the firework. + if fireworks[i].done() { + fireworks.remove(at: i) + } else { + i += 1 + } + } + + // If any firework has exploded, update the global variable. + if exploded { + i2sLock.lock() + playSound = true + i2sLock.unlock() + + exploded = false + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + sleep(ms: 10) + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Particle.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Particle.swift new file mode 100644 index 0000000..6aa613c --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Particle.swift @@ -0,0 +1,29 @@ +import MadGraphics + +// The firework before explosion. +struct Particle { + var pos: Point + var velocity: (x: Float, y: Float) + var acceleration: (x: Float, y: Float) + + // Create a particle with a random velocity and acceleration. + init(pos: Point) { + self.pos = pos + velocity = (0, Float.random(in: (-14.0)...(-10.0))) + acceleration = (0, Float.random(in: 0.3..<0.4)) + } + + // Update the particle's position and velocity over time. + mutating func update() { + pos.x += Int(velocity.x) + pos.y += Int(velocity.y) + + velocity.x += acceleration.x + velocity.y += acceleration.y + } + + // Whether the particle reaches its maximum height. + func willExplode() -> Bool { + return velocity.y > 0 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/SoundThread.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/SoundThread.swift new file mode 100644 index 0000000..69ff853 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/SoundThread.swift @@ -0,0 +1,51 @@ +import SwiftIO +import MadBoard + +func soundThread(_ a: UnsafeMutableRawPointer?, _ b: UnsafeMutableRawPointer?, _ c: UnsafeMutableRawPointer?) -> () { + let speaker = I2S(Id.I2S0, rate: 44_100) + + // Read sound data from file. + let sound = readSoundData(from: "/lfs/Resources/Sounds/boom.wav") + var startPlay = false + + while true { + sleep(ms: 100) + + // Check the global variable to see if the speaker need to play sound. + i2sLock.lock() + if playSound { + startPlay = true + } + i2sLock.unlock() + + if startPlay { + speaker.write(sound) + startPlay = false + + // Update the global variable. + i2sLock.lock() + playSound = false + i2sLock.unlock() + } + } + + func readSoundData(from path: String) -> [UInt8] { + let headerSize = 0x2C + var buffer = [UInt8]() + + do { + let file = try FileDescriptor.open(path) + try file.seek(offset: 0, from: FileDescriptor.SeekOrigin.end) + let size = try file.tell() - headerSize + + buffer = [UInt8](repeating: 0, count: size) + try file.read(fromAbsoluteOffest: headerSize, into: &buffer, count: size) + try file.close() + } catch { + print("File \(path) handle error: \(error)") + return [] + } + + return buffer + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Spark.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Spark.swift new file mode 100644 index 0000000..9a76ea3 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Spark.swift @@ -0,0 +1,42 @@ +import MadGraphics + +// The firework after explosion. +struct Spark { + var pos: Point + var velocity: (x: Float, y: Float) + var acceleration: (x: Float, y: Float) + // How long the spark shows on the screen. + var lifespan: UInt8 = 255 + + // Create a particle with a random velocity and acceleration. + // Its initial position is the maximum height of the firework particle. + init(pos: Point) { + self.pos = pos + + // The speed of the spark at a random direction. + velocity.x = Float(Array(-100..<100).shuffled().randomElement()!) / 50 + velocity.y = Float(Array(-100..<100).shuffled().randomElement()!) / 50 + velocity.x *= Float.random(in: 1...4) + velocity.y *= Float.random(in: 1...4) + + acceleration = (0, Float.random(in: 0.3..<0.4)) + } + + // Update the particle's position and velocity over time. + mutating func update() { + velocity.x *= 0.8 + velocity.y *= 0.8 + lifespan -= 5 + + pos.x += Int(velocity.x) + pos.y += Int(velocity.y) + + velocity.x += acceleration.x + velocity.y += acceleration.y + } + + // Whether the spark will disappear. + func done() -> Bool { + return lifespan <= 0 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.swift new file mode 100644 index 0000000..6d7f734 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.swift @@ -0,0 +1,28 @@ +// 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: "HilbertCurve", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/CFreeType", from: "2.13.0"), + ], + 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: "HilbertCurve", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/Hilbert.swift b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/Hilbert.swift new file mode 100644 index 0000000..ba3d481 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/Hilbert.swift @@ -0,0 +1,71 @@ +import MadGraphics + +struct Hilbert { + // The order of the Hilbert curve. + let order: Int + // The points of the Hilbert curve. + var points = [Point]() + + let size: Int + let total: Int + + // The four corners of a unit square. + let unitSquarePoints = [Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)] + + // Generate a Hilbert curve with a specified order. + init(order: Int) { + self.order = order + + // Total number of points in the curve. + size = Int(Float.pow(2, Float(order))) + total = size * size + for i in 0.. Point { + var i = i + // One of the four corners of the unit square, which serves as the initial point. + var point = unitSquarePoints[i % 4] + + // Update the point iteratively. + for j in 1.. Float + +private extension Float { + @_transparent + static func pow(_ x: Float, _ y: Float) -> Float { + guard x >= 0 else { return .nan } + if x == 0 && y == 0 { return .nan } + return powf(x, y) + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/HilbertCurve.swift b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/HilbertCurve.swift new file mode 100644 index 0000000..803135b --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/HilbertCurve.swift @@ -0,0 +1,112 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics + +@main +public struct HilbertCurve { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + let bl = DigitalOut(Id.D2) + let rst = DigitalOut(Id.D12) + let dc = DigitalOut(Id.D13) + let cs = DigitalOut(Id.D5) + let spi = SPI(Id.SPI0, speed: 30_000_000) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + + let width = 240 + let height = 240 + + var screenBuffer = [UInt16](repeating: 0, count: width * height) + + // layer used to draw the Hilbert curve. + let layer = Layer(at: Point.zero, anchorPoint: UnitPoint.zero, width: width, height: height) + var frameBuffer = [UInt32](repeating: 0, count: width * height) + + var colorIndex = 0 + let colors: [Color] = [ + .red, .orange, .yellow, .lime, .blue, + Color(UInt32(0x4B0082)), Color(UInt32(0x9400D3)) + ] + + let minOrder = 1 + let maxOrder = 6 + var order = minOrder + var increaseOrder = true + + var hilbert = Hilbert(order: order) + // Scale and position each point to fit the layer. + var length: Int { width / hilbert.size } + + var pointIndex = 1 + + drawBorder() + + while true { + if pointIndex == hilbert.total { + // Generate a new Hilbert curve. + order = increaseOrder ? order + 1 : order - 1 + + if order == maxOrder { + increaseOrder = false + } else if order == minOrder { + increaseOrder = true + } + + hilbert = Hilbert(order: order) + // Clear the canvas. + layer.draw() { canvas in + canvas.fill(Color.black.rawValue) + } + drawBorder() + + colorIndex = 0 + pointIndex = 1 + + sleep(ms: 500) + } else { + // Draw a single line each time. + drawLine(pointIndex) + + colorIndex = (pointIndex / 4) % colors.count + pointIndex += 1 + + sleep(ms: 2) + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + + // Connect the given point with its preceding point on the curve. + func drawLine(_ index: Int) { + let x1 = hilbert.points[index - 1].x * length + length / 2 + let y1 = hilbert.points[index - 1].y * length + length / 2 + let x2 = hilbert.points[index].x * length + length / 2 + let y2 = hilbert.points[index].y * length + length / 2 + + layer.draw() { canvas in + canvas.drawLine(from: Point(x1, y1), to: Point(x2, y2), data: colors[colorIndex].rawValue) + } + } + + // Outline the canvas with a border. + func drawBorder() { + layer.draw() { canvas in + canvas.drawLine(from: Point(0, 0), to: Point(width - 1, 0), data: Color.silver.rawValue) + } + layer.draw() { canvas in + canvas.drawLine(from: Point(0, height - 1), to: Point(width - 1, height - 1), data: Color.silver.rawValue) + } + layer.draw() { canvas in + canvas.drawLine(from: Point(0, 0), to: Point(0, height - 1), data: Color.silver.rawValue) + } + layer.draw() { canvas in + canvas.drawLine(from: Point(width - 1, 0), to: Point(width - 1, height - 1), data: Color.silver.rawValue) + } + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.swift new file mode 100644 index 0000000..acf4109 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.swift @@ -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: "develop"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/CFreeType", from: "2.13.0"), + ], + 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"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Resources/Fonts/Roboto-Regular.ttf b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Resources/Fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Resources/Fonts/Roboto-Regular.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Ball.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Ball.swift new file mode 100644 index 0000000..8f31d63 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Ball.swift @@ -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 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Game.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Game.swift new file mode 100644 index 0000000..8a561e5 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Game.swift @@ -0,0 +1,241 @@ +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. +class Game { + var maze: Maze + var ball: Ball + let ballColor = Color(UInt32(0xEFE891)) + + let width = 20 + var speed = 2 + + let screen: ST7789 + let layer: Layer + var frameBuffer: [UInt32] + var screenBuffer: [UInt16] + + init(screen: ST7789, layer: Layer) { + ball = Ball(at: Point(x: 1, y: 1), size: 7) + maze = Maze(width: width, layer: layer) + frameBuffer = [UInt32](repeating: 0, count: layer.bounds.width * layer.bounds.height) + screenBuffer = [UInt16](repeating: 0, count: layer.bounds.width * layer.bounds.height) + self.screen = screen + self.layer = layer + + maze.generate(layer) + + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: ballColor.rawValue) + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + + // Create a new maze and place the ball at the starting point. + func reset() { + maze.reset(layer) + maze.generate(layer) + + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: maze.bgColor.rawValue) + } + ball = Ball(at: Point(x: 1, y: 1), size: 7) + + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: ballColor.rawValue) + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + + // Update the display to show that the game has finished. + func finishGame() { + layer.draw() { canvas in + canvas.fillRectangle(at: Point(0, 0), width: canvas.width, height: canvas.height, data: Color.red.rawValue) + } + + let font = Font(path: "/lfs/Resources/Fonts/Roboto-Regular.ttf", pointSize: 10, dpi: 220) + + let text1 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 60), anchorPoint: UnitPoint.center, string: "Good Job!", font: font, foregroundColor: Color.white) + + font.setSize(pointSize: 6) + let text2 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 140), anchorPoint: UnitPoint.center, string: "Press D1 to continue", font: font, foregroundColor: Color.white) + + layer.append(text1) + layer.append(text2) + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + layer.removeAll() + } + + // 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. + 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 { + layer.draw() { canvas in + canvas.fillRectangle(at: lastBallPos, width: ball.size, height: ball.size, data: maze.bgColor.rawValue) + } + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: ballColor.rawValue) + } + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + } + + // 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 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Grid.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Grid.swift new file mode 100644 index 0000000..9ccabe5 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Grid.swift @@ -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 +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Maze.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Maze.swift new file mode 100644 index 0000000..815134b --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Maze.swift @@ -0,0 +1,186 @@ +import MadGraphics +import ST7789 +import SwiftIO + +// Generate a random maze. +struct Maze { + let width: Int + let column: Int + let row: Int + + let bgColor = Color(UInt32(0x3F52E3)) + let wallColor = Color.white + let canvasSize: Size + + var current = Point(0, 0) + var grids: [Grid] = [] + var stack: [Point] = [] + + init(width: Int, layer: Layer) { + self.canvasSize = layer.bounds.size + self.width = width + column = canvasSize.width / width + row = canvasSize.height / width + reset(layer) + } + + // Reset all walls to their default state. + mutating func reset(_ layer: Layer) { + grids = [] + stack = [] + current = Point(0, 0) + + // Initially, all walls are present. + for y in 0.. Bool { + return grids.filter { !$0.visited }.count == 0 + } + + // Calculate the index of a cell in the array. + func getIndex(_ point: Point) -> Int { + return point.x + point.y * column + } + + // Remove the wall between two cells. + mutating func removeWall(_ current: Point, _ next: Point) { + let x = current.x - next.x + + if x == 1 { + grids[getIndex(current)].walls.left = false + grids[getIndex(next)].walls.right = false + } else if x == -1 { + grids[getIndex(current)].walls.right = false + grids[getIndex(next)].walls.left = false + } + + let y = current.y - next.y + + if y == 1 { + grids[getIndex(current)].walls.top = false + grids[getIndex(next)].walls.bottom = false + } else if y == -1 { + grids[getIndex(current)].walls.bottom = false + grids[getIndex(next)].walls.top = false + } + } + + // Find nearby cells that haven't been visited yet, and select one randomly. + func getNext() -> Point? { + var neighbors: [Point] = [] + + if current.y > 0 { + let top = Point(current.x, current.y - 1) + + if !grids[getIndex(top)].visited { + neighbors.append(top) + } + } + if current.x < column - 1 { + let right = Point(current.x + 1, current.y) + + if !grids[getIndex(right)].visited { + neighbors.append(right) + } + } + + if current.y < row - 1 { + let bottom = Point(current.x, current.y + 1) + if !grids[getIndex(bottom)].visited { + neighbors.append(bottom) + } + } + + if current.x > 0 { + let left = Point(current.x - 1, current.y) + if !grids[getIndex(left)].visited { + neighbors.append(left) + } + } + + return neighbors.randomElement() + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/MazeGame.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/MazeGame.swift new file mode 100644 index 0000000..2b2300d --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/MazeGame.swift @@ -0,0 +1,53 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics +import LIS3DH + +@main +public struct MazeGame { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + let bl = DigitalOut(Id.D2) + let rst = DigitalOut(Id.D12) + let dc = DigitalOut(Id.D13) + let cs = DigitalOut(Id.D5) + let spi = SPI(Id.SPI0, speed: 30_000_000) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + + let i2c = I2C(Id.I2C0) + let accelerometer = LIS3DH(i2c) + + let layer = Layer(at: Point.zero, anchorPoint: UnitPoint.zero, width: screen.width, height: screen.height) + let mazeGame = Game(screen: screen, layer: layer) + + let resetButton = DigitalIn(Id.D1) + + var reset = false + resetButton.setInterrupt(.falling) { + reset = true + } + + var sleepTime: Float = 0 + let maxTime: Float = 20 + let minTime: Float = 5 + + while true { + // If the reset button is pressed, restart the game. + if reset { + mazeGame.reset() + reset = false + } + + // Update ball's position based on the acceleration. + let acceleration = accelerometer.readXYZ() + mazeGame.update(acceleration) + + // Map the acceleration into a sleep time in order to control the speed of the ball. + sleepTime = min(max(abs(acceleration.x), abs(acceleration.y)), 1) * (minTime - maxTime) + maxTime + sleep(ms: Int(sleepTime)) + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.swift new file mode 100644 index 0000000..619d9c7 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.swift @@ -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: "SandSimulation", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/CFreeType", from: "2.13.0"), + ], + 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: "SandSimulation", + 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"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/Sand.swift b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/Sand.swift new file mode 100644 index 0000000..cc97551 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/Sand.swift @@ -0,0 +1,180 @@ +import MadGraphics + +struct Particle { + var x: Int + var y: Int + var xSpeed: Float + var ySpeed: Float + let color: Color +} + +struct Sand { + let count = 1000 + let size = 3 + var particles: [Particle] = [] + + let colors: [Color] = [.red, .orange, .yellow, .lime, .cyan, .blue, .purple, .magenta] + var grid: [Bool] + + let column: Int + let row: Int + + init(layer: Layer, _ acceleration: (x: Float, y: Float, z: Float)) { + column = layer.bounds.width / size + row = layer.bounds.height / size + grid = [Bool](repeating: false, count: column * row) + + // Draw the particles on the top of the layer. + var index = 0 + + while index < count { + let x = index % column + let y = index / column + + particles.append(Particle(x: x, y: y, xSpeed: 0, ySpeed: 0, color: colors.randomElement()!)) + layer.draw() { canvas in + canvas.fillRectangle(at: Point(x * size, y * size), width: size, height: size, data: particles[y * column + x].color.rawValue) + } + grid[y * column + x] = true + + index += 1 + } + } + + mutating func update(layer: Layer, _ acceleration: (x: Float, y: Float, z: Float)) { + updateSpeed(acceleration) + + for i in particles.indices { + var xSpeed = particles[i].xSpeed + var ySpeed = particles[i].ySpeed + + let lastX = particles[i].x + let lastY = particles[i].y + + var newX = particles[i].x + Int(xSpeed) + var newY = particles[i].y + Int(ySpeed) + + // If the particle collides with a wall, bounce it. + if newX > column - 1 { + newX = column - 1 + xSpeed /= -2 + } else if newX < 0 { + newX = 0 + xSpeed /= -2 + } + + if newY > row - 1 { + newY = row - 1 + ySpeed /= -2 + } else if newY < 0 { + newY = 0 + ySpeed /= -2 + } + + let lastIndex = lastY * column + lastX + let newIndex = newY * column + newX + + // The particle moves and collide with other particle. + if lastIndex != newIndex && grid[newIndex] { + if abs(lastIndex - newIndex) == 1 { + // The particle moves horizontally. + xSpeed /= -2 + newX = lastX + } else if abs(lastIndex - newIndex) == column { + // The particle moves vertically. + ySpeed /= -2 + newY = lastY + } else { + // If the particle moves diagonally, find an available position adjacent to the particle to place it. + if abs(xSpeed) >= abs(ySpeed) { + // The speed on the x-axis is greater than the speed on the y-axis. + if !grid[lastY * column + newX] { + // Suppress movement along the y-axis. + newY = lastY + ySpeed /= -2 + } else if !grid[newY * column + lastX] { + // Suppress movement along the x-axis. + newX = lastX + xSpeed /= -2 + } else { + // Remain still. + newX = lastX + newY = lastY + xSpeed /= -2 + ySpeed /= -2 + } + } else { + // The speed on the x-axis is less than the speed on the y-axis. + if !grid[newY * column + lastX] { + // Suppress movement along the x-axis. + newX = lastX + xSpeed /= -2 + } else if !grid[lastY * column + newX] { + // Suppress movement along the y-axis. + newY = lastY + ySpeed /= -2 + } else { + // Remain still. + newX = lastX + newY = lastY + xSpeed /= -2 + ySpeed /= -2 + } + } + } + } + + particles[i].x = newX + particles[i].y = newY + particles[i].xSpeed = xSpeed + particles[i].ySpeed = ySpeed + grid[lastY * column + lastX] = false + grid[newY * column + newX] = true + layer.draw() { canvas in + canvas.fillRectangle(at: Point(lastX * size, lastY * size), width: size, height: size, data: Color.black.rawValue) + } + layer.draw() { canvas in + canvas.fillRectangle(at: Point(newX * size, newY * size), width: size, height: size, data: particles[i].color.rawValue) + } + } + } + + mutating func updateSpeed(_ acceleration: (x: Float, y: Float, z: Float)) { + var xAccel = -acceleration.x + var yAccel = acceleration.y + var zAccel = min(abs(acceleration.z), 1) + + if abs(xAccel) <= 0.1 && abs(yAccel) <= 0.1 { + // Prevent the particles from moving when the board is lying on the table not completely level. + for i in particles.indices { + particles[i].xSpeed = 0 + particles[i].ySpeed = 0 + } + } else { + // Acceleration on z-axis simulates the effect of gravitational on the motion of the particles. + // When z-axis acceleration is close to 1, sensor is flat, and gravity barely affects xy movement. + // Lower z-axis acceleration means more gravity influence on xy motion. + zAccel = 0.5 - zAccel / 2 + xAccel -= zAccel + yAccel -= zAccel + + //A slight random motion is added to each particle according to the z-axis acceleration. + // Their speed stays below 1 to avoid overlap. + // However, the rapid iteration speed creates the illusion of smooth particle movement. + for i in particles.indices { + var xSpeed = particles[i].xSpeed + xAccel + Float.random(in: 0...zAccel) + if abs(xSpeed) > 1 { + xSpeed /= abs(xSpeed) + } + + var ySpeed = particles[i].ySpeed + yAccel + Float.random(in: 0...zAccel) + if abs(ySpeed) > 1 { + ySpeed /= abs(ySpeed) + } + + particles[i].xSpeed = xSpeed + particles[i].ySpeed = ySpeed + } + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/SandSimulation.swift b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/SandSimulation.swift new file mode 100644 index 0000000..42c2e61 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/SandSimulation.swift @@ -0,0 +1,43 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics +import LIS3DH + +@main +public struct SandSimulation { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + let bl = DigitalOut(Id.D2) + let rst = DigitalOut(Id.D12) + let dc = DigitalOut(Id.D13) + let cs = DigitalOut(Id.D5) + let spi = SPI(Id.SPI0, speed: 30_000_000) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.width) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.width) + + let layer = Layer(at: Point.zero, anchorPoint: UnitPoint.zero, width: screen.width, height: screen.height) + + // Initialize the accelerometer. + let i2c = I2C(Id.I2C0) + let accelerometer = LIS3DH(i2c) + + // Draw the sand particle. + var sand = Sand(layer: layer, accelerometer.readXYZ()) + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + // Update sand particle positions based on movement. + while true { + sand.update(layer: layer, accelerometer.readXYZ()) + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + sleep(ms: 1) + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.swift new file mode 100644 index 0000000..4be7625 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.swift @@ -0,0 +1,28 @@ +// 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: "SpinningCube", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/CFreeType", from: "2.13.0"), + ], + 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: "SpinningCube", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/SpinningCube.swift b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/SpinningCube.swift new file mode 100644 index 0000000..a594d0d --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/SpinningCube.swift @@ -0,0 +1,99 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics + + +@main +public struct SpinningCube { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + let bl = DigitalOut(Id.D2) + let rst = DigitalOut(Id.D12) + let dc = DigitalOut(Id.D13) + let cs = DigitalOut(Id.D5) + let spi = SPI(Id.SPI0, speed: 30_000_000) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.height) + + let layer = Layer(at: Point.zero, anchorPoint: UnitPoint.zero, width: screen.width, height: screen.height) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.height) + + let colors: [Color] = [.red, .orange, .yellow, .lime, .blue, Color(0x4B0082), .purple] + + // The vertices of the cube in 3D space. + let points: [[Float]] = [ + [-0.5, -0.5, -0.5], + [0.5, -0.5, -0.5], + [0.5, 0.5, -0.5], + [-0.5, 0.5, -0.5], + + [-0.5, -0.5, 0.5], + [0.5, -0.5, 0.5], + [0.5, 0.5, 0.5], + [-0.5, 0.5, 0.5] + ] + + // The coordinates on 2D plan of cube vertices. + var projectedPoints = [Point](repeating: Point.zero, count: points.count) + var lastProjectedPoints = projectedPoints + + var angle: Float = 0 + let width: Float = 200 + let offset = Point(x: 120, y: 120) + + while true { + // Rotate vertices of the cube and project them onto a 2D plane using perspective projection. + for i in points.indices { + let rotated = rotate([[points[i][0]], [points[i][1]], [points[i][2]]], angle: angle) + let projected = project(distance: 2, point: rotated) + projectedPoints[i] = Point(x: Int(projected[0][0] * width), y: Int(projected[1][0] * width)) + } + + // Draw the cube in its current position. + for i in 0..<4 { + layer.draw() { canvas in + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[(i + 1) % 4] + offset, + data: colors[(3 * i) % colors.count].rawValue) + + canvas.drawLine(from: lastProjectedPoints[i + 4] + offset, + to: lastProjectedPoints[(i + 1) % 4 + 4] + offset, + data: colors[(3 * i + 1) % colors.count].rawValue) + + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[i + 4] + offset, + data: colors[(3 * i + 2) % colors.count].rawValue) + } + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Color.getRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + sleep(ms: 10) + + // Clear the cube from its last position + for i in 0..<4 { + layer.draw() { canvas in + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[(i + 1) % 4] + offset, + data: Color.black.rawValue) + + canvas.drawLine(from: lastProjectedPoints[i + 4] + offset, + to: lastProjectedPoints[(i + 1) % 4 + 4] + offset, + data: Color.black.rawValue) + + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[i + 4] + offset, + data: Color.black.rawValue) + } + } + + lastProjectedPoints = projectedPoints + angle += 0.02 + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/Uitls.swift b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/Uitls.swift new file mode 100644 index 0000000..c0f4de1 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/Uitls.swift @@ -0,0 +1,68 @@ +// Calculate the projection of a point onto a 2D plane using perspective projection. +// `distance` refers to the distance from the viewer. +func project(distance: Float, point: [[Float]]) -> [[Float]] { + let z = 1 / (distance - point[2][0]) + let projectionMatrix: [[Float]] = [ + [z, 0, 0], + [0, z, 0] + ] + + return matrixMultiply(projectionMatrix, point) +} + +// Rotate a point around the x, y, and z axes by a given angle. +func rotate(_ point: [[Float]], angle: Float) -> [[Float]] { + var rotated = matrixMultiply(rotateX(angle), point) + rotated = matrixMultiply(rotateY(angle), rotated) + rotated = matrixMultiply(rotateZ(angle), rotated) + return rotated +} + +// Rotate around x-axis. +func rotateX(_ angle: Float) -> [[Float]] { + return [[1, 0, 0], + [0, cosf(angle), -sinf(angle)], + [0, sinf(angle), cosf(angle)]] +} + +// Rotate around y-axis. +func rotateY(_ angle: Float) -> [[Float]] { + return [[cosf(angle), 0, sinf(angle)], + [0, 1, 0], + [-sinf(angle), 0, cosf(angle)]] +} + +// Rotate around z-axis. +func rotateZ(_ angle: Float) -> [[Float]] { + return [[cosf(angle), -sinf(angle), 0], + [sinf(angle), cosf(angle), 0], + [0, 0, 1]] +} + +func matrixMultiply(_ matrix1: [[Float]], _ matrix2: [[Float]]) -> [[Float]] { + // Check if matrices are compatible for multiplication + guard matrix1[0].count == matrix2.count else { + return [[]] + } + + // Initialize result matrix with zeros + var result = Array(repeating: Array(repeating: Float(0), count: matrix2[0].count), count: matrix1.count) + + // Perform matrix multiplication + for i in 0.. Float + +@_extern(c, "sinf") +func sinf(_: Float) -> Float \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.swift new file mode 100644 index 0000000..f7d8e42 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.swift @@ -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: "WordClock", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "develop"), + .package(url: "https://github.com/madmachineio/CFreeType", from: "2.13.0"), + ], + 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: "WordClock", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + .product(name: "PCF8563", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Resources/Fonts/Graduate-Regular.ttf b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Resources/Fonts/Graduate-Regular.ttf new file mode 100644 index 0000000..29061d9 Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Resources/Fonts/Graduate-Regular.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Sources/WordClock/WordClock.swift b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Sources/WordClock/WordClock.swift new file mode 100644 index 0000000..574e78a --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Sources/WordClock/WordClock.swift @@ -0,0 +1,107 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics +import PCF8563 + +@main +public struct WordClock { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + let bl = DigitalOut(Id.D2) + let rst = DigitalOut(Id.D12) + let dc = DigitalOut(Id.D13) + let cs = DigitalOut(Id.D5) + let spi = SPI(Id.SPI0, speed: 30_000_000) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.width) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.width) + + // Initialize the rtc. + let i2c = I2C(Id.I2C0) + let rtc = PCF8563(i2c) + + // Update the RTC with your current time. + let currentTime = PCF8563.Time( + year: 2024, month: 12, day: 5, hour: 18, + minute: 29, second: 0, dayOfWeek: 4 + ) + + sleep(ms: 500) + rtc.setTime(currentTime) + + let layer = Layer(at: Point.zero, anchorPoint: UnitPoint.zero, width: screen.width, height: screen.height) + + // Calculate the point size for each character. + // Get masks from the font file for all characters of the word clock. + let dpi = 220 + let pointSize = min(screen.width / Words.column, screen.height / Words.row) * 72 / dpi * 4 / 5 + let characterMasks = getCharacterMasks(path: "/lfs/Resources/Fonts/Graduate-Regular.ttf", pointSize: pointSize, dpi: dpi) + + let clock = WordView(layer: layer, characterMasks: characterMasks) + + // Define the colors to be used for displaying the words. + let colors: [Color] = [.red, .orange, .yellow, .lime, .blue, .magenta, .cyan, Color(UInt32(0xFE679A))] + + // Highlight each column with different colors in turn. + var index = 0 + for x in 0.. [[Mask]] { + var characterMasks = [[Mask]](repeating: [Mask](), count: Words.row) + + let font = Font(path: path, pointSize: pointSize, dpi: dpi) + for y in 0.. (minute: [Minute], prep: Preposition?, hour: Hour) { + static func getWords(hourNumber: Int, minuteNumber: Int) -> Words { + var minuteWord: [MinuteWord] = [] + var hourWord = HourWord(rawValue: hourNumber < 12 ? hourNumber : hourNumber % 12)! + var prepWord: PrepositionWord? = nil + + switch minuteNumber { + case 5..<10: + minuteWord.append(.five) + prepWord = .past + case 10..<15: + minuteWord.append(.ten) + prepWord = .past + case 15..<20: + minuteWord.append(.quarter) + prepWord = .past + case 20..<25: + minuteWord.append(.twenty) + prepWord = .past + case 25..<30: + minuteWord.append(.twenty) + minuteWord.append(.five) + prepWord = .past + case 30..<35: + minuteWord.append(.half) + prepWord = .past + case 35..<40: + //minuteWord.append(contentsOf: [.twenty, .five]) + minuteWord.append(.twenty) + minuteWord.append(.five) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 40..<45: + minuteWord.append(.twenty) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 45..<50: + minuteWord.append(.quarter) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 50..<55: + minuteWord.append(.ten) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 55..<60: + minuteWord.append(.five) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + default: break + } + + return Words(hourWord: hourWord, prepWord: prepWord, minuteWord: minuteWord) + } +} \ No newline at end of file