Skip to content

Commit

Permalink
Add Pong example
Browse files Browse the repository at this point in the history
  • Loading branch information
finnvoor committed Aug 12, 2024
1 parent 807a2f8 commit 330118e
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 5 deletions.
6 changes: 6 additions & 0 deletions Examples/Pong/.swiftpm/build-and-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#! /bin/sh
set -e
(killall 'Playdate Simulator' || true) 2>/dev/null
cd ..
swift package pdc
~/Developer/PlaydateSDK/bin/Playdate\ Simulator.app/Contents/MacOS/Playdate\ Simulator .build/plugins/PDCPlugin/outputs/$PRODUCT_NAME.pdx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
79 changes: 79 additions & 0 deletions Examples/Pong/.swiftpm/xcode/xcshareddata/xcschemes/Pong.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
version = "2.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Pong"
BuildableName = "Pong"
BlueprintName = "Pong"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "NO"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<PathRunnable
runnableDebuggingMode = "0"
Location = "workspace"
FilePath = ".swiftpm/build-and-run.sh">
</PathRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Pong"
BuildableName = "Pong"
BlueprintName = "Pong"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "PRODUCT_NAME"
value = "$(PRODUCT_NAME)"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
42 changes: 42 additions & 0 deletions Examples/Pong/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// swift-tools-version: 5.10

import Foundation
import PackageDescription

let gccIncludePrefix =
"/usr/local/playdate/gcc-arm-none-eabi-9-2019-q4-major/lib/gcc/arm-none-eabi/9.2.1"

let playdateSDKPath: String = if let path = Context.environment["PLAYDATE_SDK_PATH"] {
path
} else {
"\(Context.environment["HOME"]!)/Developer/PlaydateSDK/"
}

let package = Package(
name: "Pong",
platforms: [.macOS(.v14)],
products: [.library(name: "Pong", targets: ["Pong"])],
dependencies: [
.package(path: "../.."),
],
targets: [
.target(
name: "Pong",
dependencies: [.product(name: "PlaydateKit", package: "PlaydateKit")],
swiftSettings: [
.enableExperimentalFeature("Embedded"),
.unsafeFlags([
"-Xfrontend", "-disable-objc-interop",
"-Xfrontend", "-disable-stack-protector",
"-Xfrontend", "-function-sections",
"-Xfrontend", "-gline-tables-only",
"-Xcc", "-DTARGET_EXTENSION",
"-Xcc", "-I", "-Xcc", "\(gccIncludePrefix)/include",
"-Xcc", "-I", "-Xcc", "\(gccIncludePrefix)/include-fixed",
"-Xcc", "-I", "-Xcc", "\(gccIncludePrefix)/../../../../arm-none-eabi/include",
"-I", "\(playdateSDKPath)/C_API"
]),
]
)
]
)
173 changes: 173 additions & 0 deletions Examples/Pong/Sources/Pong/Game.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import PlaydateKit

// MARK: - Game

final class Game: PlaydateGame {
// MARK: Lifecycle

init() {
[
playerPaddle,
ball,
topWall, bottomWall, leftWall, rightWall
].forEach { $0.addToDisplayList() }

playerPaddle.position = Point(x: 10, y: Display.height / 2)
ball.position = Point(x: Display.width / 2, y: 10)
}

// MARK: Internal

enum State {
case playing
case gameOver
}

var state: State = .playing
var score: (player: Int, computer: Int) = (0, 0)
let winningScore = 11
let playerPaddle = PlayerPaddle()
let ball = Ball()

let topWall = Wall(bounds: Rect(x: 0, y: -1, width: Display.width, height: 1))
let bottomWall = Wall(bounds: Rect(x: 0, y: Display.height, width: Display.width, height: 1))
let leftWall = Wall(bounds: Rect(x: -1, y: 0, width: 1, height: Display.height))
let rightWall = Wall(bounds: Rect(x: Display.width, y: 0, width: 1, height: Display.height))

var hasWinner: Bool { score.player >= winningScore || score.computer >= winningScore }

func update() -> Bool {
switch state {
case .playing:
Sprite.updateAndDrawDisplayListSprites()
case .gameOver:
if System.buttonState.current.contains(.a) {
score = (0, 0)
state = .playing
}

// TODO: - Center properly
Graphics.drawText(
"Game Over",
at: Point(x: (Display.width / 2) - 40, y: (Display.height / 2) - 20)
)
Graphics.drawText(
"Press Ⓐ to play again",
at: Point(x: (Display.width / 2) - 80, y: Display.height / 2)
)
}

Graphics.drawText("\(score.player)", at: Point(x: (Display.width / 2) - 80, y: 10))
Graphics.drawText("\(score.computer)", at: Point(x: (Display.width / 2) + 80, y: 10))

return true
}
}

// MARK: - Wall

class Wall: Sprite.Sprite {
init(bounds: Rect) {
super.init()
self.bounds = bounds
collideRect = Rect(origin: .zero, width: bounds.width, height: bounds.height)
}
}

// MARK: - Ball

typealias Vector = Point

// MARK: - Ball

class Ball: Sprite.Sprite {
// MARK: Lifecycle

override init() {
super.init()
bounds = .init(x: 0, y: 0, width: 8, height: 8)
collideRect = bounds
}

// MARK: Internal

var velocity = Vector(x: 4, y: 5)

func reset() {
position = Point(x: Display.width / 2, y: 10)
velocity.x *= Bool.random() ? 1 : -1
velocity.y = abs(velocity.y)
}

override func update() {
let collisionInfo = moveWithCollisions(
goal: position + velocity
)
for collision in collisionInfo.collisions {
if collision.other == game.leftWall.pointer {
game.score.computer += 1
game.ball.reset()
if game.hasWinner { game.state = .gameOver }
} else if collision.other == game.rightWall.pointer {
game.score.player += 1
game.ball.reset()
if game.hasWinner { game.state = .gameOver }
} else {
if collision.normal.x != 0 {
velocity.x *= -1
}
if collision.normal.y != 0 {
velocity.y *= -1
}
}
}
}

override func draw(bounds: Rect, drawRect _: Rect) {
Graphics.fillEllipse(in: bounds)
}
}

// MARK: - PlayerPaddle

class PlayerPaddle: Paddle {
override func updatePosition() {
if System.buttonState.current.contains(.down) {
moveWithCollisions(
goal: position + Vector(x: 0, y: speed)
)
}

if System.buttonState.current.contains(.up) {
moveWithCollisions(
goal: position - Vector(x: 0, y: speed)
)
}
}
}

// MARK: - Paddle

class Paddle: Sprite.Sprite {
// MARK: Lifecycle

override init() {
super.init()
bounds = .init(x: 0, y: 0, width: 10, height: 40)
collideRect = bounds
}

// MARK: Internal

let speed: Float = 4

func updatePosition() {}

override func update() {
updatePosition()
}

override func draw(bounds: Rect, drawRect _: Rect) {
Graphics.fillRect(bounds)
}
}
5 changes: 5 additions & 0 deletions Examples/Pong/Sources/Pong/Resources/pdxinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name=Pong
author=Finn Voorhees
description=Pong built using PlaydateKit
bundleID=com.finnvoorhees.Pong
imagePath=
18 changes: 18 additions & 0 deletions Examples/Pong/Sources/Pong/entry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import PlaydateKit

/// Boilerplate entry code
nonisolated(unsafe) var game: Game!
@_cdecl("eventHandler") func eventHandler(
pointer: UnsafeMutableRawPointer!,
event: System.Event,
_: CUnsignedInt
) -> CInt {
switch event {
case .initialize:
Playdate.initialize(with: pointer)
game = Game()
System.updateCallback = game.update
default: game.handle(event)
}
return 0
}
15 changes: 10 additions & 5 deletions Sources/PlaydateKit/Core/Sprite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public enum Sprite {

// MARK: Public

public let pointer: OpaquePointer

/// The sprite's stencil bitmap, if set.
public var stencil: Graphics.Bitmap? { _stencil }

Expand All @@ -101,9 +103,14 @@ public enum Sprite {

/// Gets the current position of sprite.
public var position: Point {
var x: Float = 0, y: Float = 0
sprite.getPosition.unsafelyUnwrapped(pointer, &x, &y)
return Point(x: x, y: y)
get {
var x: Float = 0, y: Float = 0
sprite.getPosition.unsafelyUnwrapped(pointer, &x, &y)
return Point(x: x, y: y)
} set {
bounds.x = newValue.x
bounds.y = newValue.y
}
}

/// Gets/sets the bounds of the sprite.
Expand Down Expand Up @@ -312,8 +319,6 @@ public enum Sprite {

// MARK: Internal

let pointer: OpaquePointer

/// Gets/sets the sprite’s userdata, an arbitrary pointer used for associating the sprite with other data.
var userdata: UnsafeMutableRawPointer? {
get { sprite.getUserdata.unsafelyUnwrapped(pointer) }
Expand Down

0 comments on commit 330118e

Please sign in to comment.