Last active
February 23, 2024 15:02
-
-
Save benigumocom/026f02f57734d6978e6f29a7e8e4a976 to your computer and use it in GitHub Desktop.
【 SwiftUI 】 Pong Wars を SwiftUI に移植してみた 👉 https://android.benigumo.com/20240201/pong-wars/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
sc.2024-02-07.at.14.29.11.mov
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fixed this problem now.
