Skip to content

Instantly share code, notes, and snippets.

@benigumocom
Last active February 23, 2024 15:02
Show Gist options
  • Save benigumocom/026f02f57734d6978e6f29a7e8e4a976 to your computer and use it in GitHub Desktop.
Save benigumocom/026f02f57734d6978e6f29a7e8e4a976 to your computer and use it in GitHub Desktop.
【 SwiftUI 】 Pong Wars を SwiftUI に移植してみた 👉 https://android.benigumo.com/20240201/pong-wars/
import Foundation
@Observable final class Pong {
var squares: [Square] = []
var balls: [Ball] = []
var dayCount = 0
var nightCount = 0
var generation = 0
private var cols = 0
private var rows = 0
private var task: Task<Void, Never>?
func create(cols: Int, rows: Int) {
self.cols = cols
self.rows = rows
createSquares()
createBalls()
}
func start() {
task = Task { @MainActor in // *
do {
while true {
balls.forEach { ball in
updateSquares(ball)
ball.checkBoundary(cols, rows)
ball.avoidSpin() // *
ball.next()
}
dayCount = count(day: true)
nightCount = count(day: false)
generation += 1
try await Task.sleep(for: .seconds(.duration))
}
} catch {
if Task.isCancelled {
print("cancelled.")
}
}
}
}
func stop() {
task?.cancel()
}
private func createSquares() {
(0 ..< rows).forEach { _ in
(0 ..< cols).forEach { i in
squares.append(
Square(day: i < cols / 2)
)
}
}
}
private func createBalls() {
balls.append(
Ball(
x: 0,
y: 0,
dx: .size / 2,
dy: .size / 2,
day: false)
)
balls.append(
Ball(
x: Double(cols - 1) * .size,
y: Double(rows - 1) * .size,
dx: -.size / 2,
dy: -.size / 2,
day: true
)
)
}
private func updateSquares(_ ball: Ball) {
for angle in stride(from: 0, to: Double.pi * 2, by: Double.pi / 4) {
let cos = cos(angle)
let sin = sin(angle)
let checkX = ball.x + cos * .size / 2
let checkY = ball.y + sin * .size / 2
let i = Int(floor(checkX / .size))
let j = Int(floor(checkY / .size))
if (0 ..< cols).contains(i) && (0 ..< rows).contains(j) {
let index = cols * j + i
if squares[index].day == ball.day {
squares[index].day.toggle()
if abs(cos) > abs(sin) {
ball.dx *= -1
}
if abs(cos) < abs(sin) {
ball.dy *= -1
}
}
}
}
}
func count(day: Bool) -> Int {
return squares.compactMap { $0.day }.filter { $0 == day }.count
}
}
@Observable final class Square {
var day: Bool
init(day: Bool) {
self.day = day
}
}
@Observable final class Ball: Identifiable {
var x: Double
var y: Double
var dx: Double
var dy: Double
var day: Bool
var queue: [Double] = []
init(x: Double, y: Double, dx: Double, dy: Double, day: Bool) {
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.day = day
}
}
extension Ball {
func next() {
x += dx
y += dy
}
func checkBoundary(_ cols: Int, _ rows: Int) {
if !(0...Double(cols - 1) * .size).contains(x + dx) {
dx *= -1
}
if !(0...Double(rows - 1) * .size).contains(y + dy) {
dy *= -1
}
}
func avoidSpin() {
queue.append(x)
if queue.count > 25 { // recent 25 values
queue.removeFirst()
if (queue.max()! - queue.min()! <= .size) {
print(day, x, y, dx, dy, queue)
if day {
dx *= -1
} else {
dy *= -1
}
queue.removeAll()
}
}
}
}
import Foundation
import SwiftUI
struct PongWars: View {
private var pong = Pong()
var body: some View {
VStack(spacing: 0) {
PongLayout(pong: pong)
VStack {
Text("day \(pong.dayCount) | night \(pong.nightCount)").font(.headline)
Text("generation \(pong.generation)").font(.headline)
}
.padding()
}
}
}
struct PongLayout: View {
var pong: Pong
@State private var on = false
var body: some View {
GeometryReader { gp in
ZStack {
ForEach(pong.squares.indices, id: \.self) { index in
SquareView(index: index, cols: gp.cols, day: pong.squares[index].day)
}
ForEach(pong.balls) { ball in
BallView(ball: ball)
}
}
.frame(width: Double(gp.cols) * .size, height: Double(gp.rows) * .size)
.frame(width: gp.width, height: gp.height, alignment: .center)
.onAppear {
pong.create(cols: gp.cols, rows: gp.rows)
}
.onTapGesture {
on.toggle()
if on {
pong.start()
} else {
pong.stop()
}
}
}
}
}
struct SquareView: View {
private var i: Int
private var j: Int
private var color: Color
init(index: Int, cols: Int, day: Bool) {
self.i = index % cols
self.j = index / cols
self.color = day ? .day : .night
}
var body: some View {
color
.frame(width: CGFloat(.size), height: CGFloat(.size))
.position(x: Double(i) * .size + .size / 2, y: Double(j) * .size + .size / 2)
}
}
struct BallView: View {
var ball: Ball
var body: some View {
Circle()
.fill(ball.day ? Color.day : Color.night)
.frame(width: CGFloat(.size))
.position(x: ball.x + .size / 2, y: ball.y + .size / 2)
}
}
extension GeometryProxy {
var width: CGFloat { size.width }
var height: CGFloat { size.height }
var cols: Int { Int(width / .size) }
var rows: Int { Int(height / .size) }
}
extension Double {
static let size = 25.0
static let duration = 0.001
}
extension Color {
static let day = Color(red: 217 / 256, green: 231 / 256, blue: 226 / 256)
static let night = Color(red: 15 / 256, green: 75 / 256, blue: 89 / 256)
}
#Preview {
PongWars()
}
@benigumocom
Copy link
Author

benigumocom commented Feb 5, 2024

Fixed this problem now.
sc 2024-02-04 at 23 10 29

@benigumocom
Copy link
Author

sc.2024-02-07.at.14.29.11.mov

@benigumocom
Copy link
Author

sc 2024-02-09 at 18 12 38

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment