Skip to content

Instantly share code, notes, and snippets.

@mintuz
Forked from dduan/SwiftChartPong.swift
Created July 11, 2022 15:10
Show Gist options
  • Save mintuz/08b03506b6482a27197cb664374b35c5 to your computer and use it in GitHub Desktop.
Save mintuz/08b03506b6482a27197cb664374b35c5 to your computer and use it in GitHub Desktop.
Pong Game implemented with Swift Charts.
import SwiftUI
import Charts
import Combine
import Foundation
final class GameState: ObservableObject {
struct Player {
var position: Int
var halfSize: Int = 150
var yStart: Int {
position - halfSize
}
var yEnd: Int {
position + halfSize
}
}
let boardSize: Int = 1000
var velocity: CGPoint = .zero
let speed: CGFloat = 5
var loser: String?
var direction: CGFloat = .zero {
didSet {
self.velocity = .init(
x: cos(self.direction) * self.speed,
y: sin(self.direction) * self.speed
)
}
}
var cancels = Set<AnyCancellable>()
init() {
self.paused = true
self.started = false
self.p0 = .init(position: Int(boardSize / 2))
self.p1 = .init(position: Int(boardSize / 2))
self.ball = .init(x: boardSize / 2, y: boardSize / 2)
Timer.publish(every: 0.013333333, on: .main, in: .common)
.autoconnect()
.sink { _ in
self.update()
}
.store(in: &cancels)
}
func update() {
guard !self.paused else {
return
}
self.ball = .init(
x: self.ball.x + self.velocity.x,
y: self.ball.y + self.velocity.y
)
if ball.x < 0 || ball.x >= CGFloat(boardSize) {
self.paused = true
self.loser = ball.x < 0 ? "0" : "1"
return
}
if ball.y >= CGFloat(boardSize) - 5 || ball.y <= 5 {
self.direction = 2 * .pi - self.direction
}
if CGRect(
x: 50,
y: self.p0.yStart,
width: 10,
height: self.p0.halfSize * 2
).contains(ball)
{
let newDirection = (CGFloat(p0.position) - ball.y) / CGFloat(p0.halfSize) * (.pi / 1.8)
self.direction = -newDirection
}
if CGRect(
x: 940,
y: self.p1.yStart,
width: 10,
height: self.p1.halfSize * 2
).contains(ball)
{
let newDirection = (CGFloat(p1.position) - ball.y) / CGFloat(p1.halfSize) * (.pi / 1.8)
self.direction = newDirection - .pi
}
}
@Published var started: Bool {
didSet {
self.paused = !started
self.loser = nil
if started {
self.direction = Double.random(in: 0 ... .pi * 2)
} else {
self.p0 = .init(position: Int(boardSize / 2))
self.p1 = .init(position: Int(boardSize / 2))
self.ball = .init(x: boardSize / 2, y: boardSize / 2)
}
}
}
@Published var paused: Bool
@Published var p0: Player
@Published var p1: Player
@Published var ball: CGPoint
func moveP0(up: Bool) {
self.p0.position = min(boardSize - self.p0.halfSize, max(self.p0.halfSize, self.p0.position + (up ? 50 : -50)))
}
func moveP1(up: Bool) {
self.p1.position = min(boardSize - self.p1.halfSize, max(self.p1.halfSize, self.p1.position + (up ? 50 : -50)))
}
}
struct ContentView: View {
@StateObject var state = GameState()
var body: some View {
VStack {
ZStack {
ChartView(state: state)
if let loser = state.loser {
Text("Player \(loser) is the loser!")
}
}
Grid(horizontalSpacing: 5, verticalSpacing: 5) {
GridRow {
Button(action: { state.moveP0(up: true) }) {
Text("⬆️").frame(width: 100, height: 100)
}
.buttonStyle(.bordered)
Button(action: { state.moveP1(up: true) }) {
Text("⬆️").frame(width: 100, height: 100)
}
.buttonStyle(.bordered)
}
GridRow {
Button(action: { state.moveP0(up: false) }) {
Text("⬇️").frame(width: 100, height: 100)
}
.buttonStyle(.bordered)
Button(action: { state.moveP1(up: false) }) {
Text("⬇️").frame(width: 100, height: 100)
}
.buttonStyle(.bordered)
}
GridRow {
Button(action: { state.started.toggle() }) {
Text(state.started ? "Reset" : "Start")
.frame(width: 100)
}
Button(action: { state.paused.toggle() }) {
Text(state.paused ? "Unpause" : "Pause")
.frame(width: 100)
}
.disabled(!state.started || state.loser != nil)
}
.buttonStyle(.borderedProminent)
}
}
}
}
struct ChartView: View {
@ObservedObject var state: GameState
var body: some View {
GeometryReader { geometry in
Chart {
PointMark(
x: .value("X", Int(state.ball.x)),
y: .value("Y", Int(state.ball.y))
)
.foregroundStyle(.red)
BarMark(
x: .value("P0", 50),
yStart: .value("Y0", state.p0.yStart),
yEnd: .value("Y1", state.p0.yEnd)
)
BarMark(
x: .value("P1", 950),
yStart: .value("Y0", state.p1.yStart),
yEnd: .value("Y1", state.p1.yEnd)
)
}
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.chartYScale(domain: 0 ... state.boardSize)
.chartXScale(domain: 0 ... state.boardSize)
.chartPlotStyle { area in
area.border(.black)
}
.padding()
.frame(height: min(geometry.size.width, geometry.size.height))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment