Skip to content

Commit

Permalink
Add Pong example (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
finnvoor authored Aug 14, 2024
1 parent e39ae40 commit 082d8ab
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 6 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"
]),
]
)
]
)
221 changes: 221 additions & 0 deletions Examples/Pong/Sources/Pong/Game.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import PlaydateKit

// MARK: - Game

final class Game: PlaydateGame {
// MARK: Lifecycle

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

playerPaddle.position = Point(x: 10, y: (Float(Display.height) / 2) - (playerPaddle.bounds.height / 2))
computerPaddle.position = Point(
x: Float(Display.width - 10) - computerPaddle.bounds.width,
y: Float(Display.height / 2) - (computerPaddle.bounds.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 computerPaddle = ComputerPaddle()
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))
Graphics.drawLine(
Line(
start: Point(x: Display.width / 2, y: 0),
end: Point(x: Display.width / 2, y: Display.height)
),
lineWidth: 1,
color: .pattern((0x0, 0x0, 0xFF, 0xFF, 0x0, 0x0, 0xFF, 0xFF))
)

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 {
game.score.computer += 1
game.ball.reset()
if game.hasWinner { game.state = .gameOver }
} else if collision.other == game.rightWall {
game.score.player += 1
game.ball.reset()
if game.hasWinner { game.state = .gameOver }
} else {
synth.playNote(frequency: 220.0, volume: 0.7, length: 0.1)
if collision.normal.x != 0 {
velocity.x *= -1
}
if collision.normal.y != 0 {
velocity.y *= -1
}
}
}
}

/// Setting to `.slide` prevents the ball from getting stuck between the paddle and top/bottom.
override func collisionResponse(other _: Sprite.Sprite) -> Sprite.CollisionResponseType {
.slide
}

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

// MARK: Private

private let synth: Sound.Synth = {
let synth = Sound.Synth()
synth.setWaveform(.square)
synth.setAttackTime(0.001)
synth.setDecayTime(0.05)
synth.setSustainLevel(0.0)
synth.setReleaseTime(0.05)
return synth
}()
}

// MARK: - ComputerPaddle

class ComputerPaddle: Paddle {
override func update() {
let ball = game.ball
let paddleCenter = position.y + bounds.height / 2
let ballCenter = ball.position.y + ball.bounds.height / 2

if ballCenter < paddleCenter - 5 {
// Ball is above the paddle center
moveWithCollisions(
goal: position - Vector(x: 0, y: speed)
)
} else if ballCenter > paddleCenter + 5 {
// Ball is below the paddle center
moveWithCollisions(
goal: position + Vector(x: 0, y: speed)
)
}
// If the ball is within 5 pixels of the paddle center, don't move
}
}

// MARK: - PlayerPaddle

class PlayerPaddle: Paddle {
override func update() {
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: 8, height: 48)
collideRect = bounds
}

// MARK: Internal

let speed: Float = 4.5

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
}
Loading

0 comments on commit 082d8ab

Please sign in to comment.