Add Speedy Circles game#50
Conversation
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Limrun Preview
Reviewer must be a member of the organization on Limrun. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8b81ba0. Configure here.
| private func hitCircle(in size: CGSize) { | ||
| guard phase == .playing else { return } | ||
|
|
||
| score += 1 |
There was a problem hiding this comment.
Score increment outside animation prevents numeric text transition
Low Severity
score += 1 in hitCircle is placed outside the withAnimation block, so the .contentTransition(.numericText()) modifier applied in statCard never triggers its animated digit-rolling effect for the score display. The modifier requires the driving state change to occur within an animation context to produce any visible transition.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8b81ba0. Configure here.
Greptile SummaryThis PR replaces the placeholder SwiftUI screen with a complete "Speedy Circles" tap game, adding interval selection, a shrinking/randomised target circle, a score+time HUD with an animated progress bar, a game-over screen, and a retry flow — all within a single
Confidence Score: 4/5The game is functionally correct and safe to merge; both findings are polish-level concerns that do not affect core gameplay. The game logic is self-contained and well-guarded throughout. The wall-clock deadline approach is sound, circle placement math handles edge cases, and phase guards prevent double-finish or stale taps. The two findings — a silent no-op animation on the game-over score and potential timer subscription churn from the 50 Hz publisher being re-created on every render — are cosmetic or performance-adjacent rather than game-breaking. sample-native-app/ContentView.swift — specifically the timer publisher declaration and the game-over score transition. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A([App Launch]) --> S[Start Screen - interval picker]
S -- startGame --> P[Playing - HUD + circle]
P -- hitCircle - score++ resetTimer --> P
P -- timer tick updatedTime==0 finishGame --> F[Game Over - finalScore display]
F -- retry - phase=.start --> S
subgraph Timer [50 Hz Timer onReceive]
T1[compute deadline.timeIntervalSince now]
T1 --> T2{updatedTime == 0?}
T2 -- No --> T3[remainingTime = updatedTime]
T2 -- Yes --> T4[finishGame]
end
P -.-> T1
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A([App Launch]) --> S[Start Screen - interval picker]
S -- startGame --> P[Playing - HUD + circle]
P -- hitCircle - score++ resetTimer --> P
P -- timer tick updatedTime==0 finishGame --> F[Game Over - finalScore display]
F -- retry - phase=.start --> S
subgraph Timer [50 Hz Timer onReceive]
T1[compute deadline.timeIntervalSince now]
T1 --> T2{updatedTime == 0?}
T2 -- No --> T3[remainingTime = updatedTime]
T2 -- Yes --> T4[finishGame]
end
P -.-> T1
Reviews (1): Last reviewed commit: "Add Speedy Circles game" | Re-trigger Greptile |
| Text("\(finalScore)") | ||
| .font(.system(size: 74, weight: .black, design: .rounded)) | ||
| .foregroundStyle(.white) | ||
| .contentTransition(.numericText()) |
There was a problem hiding this comment.
contentTransition(.numericText()) will not animate on the game-over screen
finalScore is set synchronously before phase = .finished is assigned in finishGame(). When gameOverScreen is first inserted into the view hierarchy, the text already holds the final value, so there is no numeric change for the transition to interpolate — the score pops in at full value rather than counting up. To get the intended count-up effect, initialize finalScore to 0 and drive it to the real score inside an onAppear animation.
| @State private var circleSize = Constants.startCircleSize | ||
| @State private var circleColor = Color.pink | ||
|
|
||
| private let timer = Timer.publish(every: 0.02, on: .main, in: .common).autoconnect() |
There was a problem hiding this comment.
Timer publisher recreated on every render pass
private let timer = Timer.publish(every: 0.02, ...) is a stored property of a value-type View struct. SwiftUI recreates the struct on every render, and because the 50 Hz timer drives a state change on each tick, the publisher object gets a new identity each render cycle. onReceive compares publisher identity; a new Timer.TimerPublisher instance on every render causes continuous subscribe/unsubscribe cycling, which can introduce missed ticks and drains battery even when phase != .playing. The idiomatic fix is to lift the publisher to module scope (a let constant outside the struct) or wrap it in a @StateObject, so a single publisher instance is reused for the view's lifetime.


Summary
Demo
speedy-circles-demo.mp4
Preview
8b81ba08b81ba0Validation
lim xcode build ..