Puzzle Blast
Open in bitrigDownload Code
```swift
import SwiftUI
@main
struct PuzzleBlastApp: App {
var body: some Scene {
WindowGroup {
PuzzleGameView()
}
}
}
struct PuzzleGameView: View {
@State private var pieces: [PuzzlePiece] = []
@State private var isExploded = false
@State private var timeRemaining: Double = 30
@State private var gameOver = false
@State private var timerActive = false
@State private var colorInverted = false
let gridSize = 4
let pieceSize: CGFloat = 80
let symbols = ["star.fill", "heart.fill", "circle.fill", "square.fill", "triangle.fill", "diamond.fill", "hexagon.fill", "pentagon.fill"]
var body: some View {
ZStack {
(colorInverted ? Color.white : Color.black).ignoresSafeArea()
if !gameOver {
VStack(spacing: 20) {
ZStack {
Text("Block Blast")
.font(.system(size: 44, weight: .heavy))
.foregroundStyle(colorInverted ? .black : .white)
HStack {
Spacer()
Button {
shareContent()
} label: {
Image(systemName: "square.and.arrow.up")
.font(.system(size: 22))
.foregroundStyle(colorInverted ? .black : .white)
}
.padding(.trailing, 20)
}
}
.padding(.top, 60)
Text("Reassemble the blocks in less than 30 seconds")
.font(.system(size: 16, weight: .medium))
.foregroundStyle((colorInverted ? Color.black : Color.white).opacity(0.8))
.multilineTextAlignment(.center)
.padding(.horizontal, 30)
Text("Time: \(Int(timeRemaining))s")
.font(.system(size: 32, weight: .bold))
.foregroundStyle(colorInverted ? .black : .white)
Spacer()
ZStack {
ForEach(pieces) { piece in
PieceView(piece: piece, pieceSize: pieceSize)
.offset(x: piece.currentX, y: piece.currentY)
.gesture(
isExploded ? DragGesture()
.onChanged { value in
if let index = pieces.firstIndex(where: { $0.id == piece.id }) {
pieces[index].currentX = piece.dragStartX + value.translation.width
pieces[index].currentY = piece.dragStartY + value.translation.height
}
}
.onEnded { _ in
if let index = pieces.firstIndex(where: { $0.id == piece.id }) {
pieces[index].dragStartX = pieces[index].currentX
pieces[index].dragStartY = pieces[index].currentY
checkWin()
}
} : nil
)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
Spacer()
if !isExploded {
Button {
explodePuzzle()
} label: {
Text("EXPLODE!")
.font(.system(size: 24, weight: .bold))
.foregroundStyle(.white)
.padding(.horizontal, 40)
.padding(.vertical, 16)
.background(Color.red)
.cornerRadius(12)
}
.padding(.bottom, 40)
}
}
.onAppear {
setupPuzzle()
}
.onChange(of: timerActive) { _, active in
if active {
startTimer()
}
}
} else {
VStack(spacing: 30) {
Text("GAME OVER")
.font(.system(size: 60, weight: .bold))
.foregroundStyle(colorInverted ? .black : .white)
.transition(.opacity)
Button {
resetGame()
} label: {
Text("PLAY AGAIN")
.font(.system(size: 24, weight: .bold))
.foregroundStyle(colorInverted ? .black : .white)
.padding(.horizontal, 40)
.padding(.vertical, 16)
.background(Color.green)
.cornerRadius(12)
}
}
.onAppear {
flashColors()
}
}
}
.animation(.easeInOut(duration: 1), value: gameOver)
}
func setupPuzzle() {
pieces = []
let colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple, .pink, .cyan]
let symbolColorMap = Dictionary(uniqueKeysWithValues: zip(symbols, colors))
var id = 0
for row in 0..<gridSize {
for col in 0..<gridSize {
let x = CGFloat(col) * pieceSize - (CGFloat(gridSize - 1) * pieceSize / 2)
let y = CGFloat(row) * pieceSize - (CGFloat(gridSize - 1) * pieceSize / 2)
let symbol = symbols[id % symbols.count]
let color = symbolColorMap[symbol] ?? .blue
pieces.append(PuzzlePiece(
id: id,
correctX: x,
correctY: y,
currentX: x,
currentY: y,
symbol: symbol,
color: color
))
id += 1
}
}
}
func resetGame() {
gameOver = false
isExploded = false
timeRemaining = 30
timerActive = false
colorInverted = false
setupPuzzle()
}
func flashColors() {
var count = 0
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { timer in
colorInverted.toggle()
count += 1
if count >= 10 {
timer.invalidate()
}
}
}
func explodePuzzle() {
isExploded = true
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
let maxX = (screenWidth / 2) - (pieceSize / 2) - 20
let maxY = (screenHeight / 2) - (pieceSize / 2) - 100
withAnimation(.spring(response: 0.6, dampingFraction: 0.6)) {
for index in pieces.indices {
let randomX = CGFloat.random(in: -maxX...maxX)
let randomY = CGFloat.random(in: -maxY...maxY)
pieces[index].currentX = randomX
pieces[index].currentY = randomY
pieces[index].dragStartX = randomX
pieces[index].dragStartY = randomY
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
timerActive = true
}
}
func startTimer() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
if timeRemaining > 0 {
timeRemaining -= 0.1
} else {
timer.invalidate()
gameOver = true
}
}
}
func checkWin() {
let threshold: CGFloat = 30
let allCorrect = pieces.allSatisfy { piece in
abs(piece.currentX - piece.correctX) < threshold && abs(piece.currentY - piece.correctY) < threshold
}
if allCorrect {
timerActive = false
}
}
func shareContent() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first(where: { $0.isKeyWindow }) else {
return
}
let renderer = UIGraphicsImageRenderer(size: window.bounds.size)
let screenshot = renderer.image { context in
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
}
let message = "I made this with the Bitrig App on my iPhone!\n\n"https://www.bitrig.app/share/wj06cdn1"
let activityVC = UIActivityViewController(activityItems: [screenshot, message], applicationActivities: nil)
if let rootVC = window.rootViewController {
var topVC = rootVC
while let presentedVC = topVC.presentedViewController {
topVC = presentedVC
}
activityVC.popoverPresentationController?.sourceView = window
activityVC.popoverPresentationController?.sourceRect = CGRect(x: window.bounds.midX, y: window.bounds.midY, width: 0, height: 0)
activityVC.popoverPresentationController?.permittedArrowDirections = []
topVC.present(activityVC, animated: true)
}
}
}
}
struct PuzzlePiece: Identifiable {
let id: Int
let correctX: CGFloat
let correctY: CGFloat
var currentX: CGFloat
var currentY: CGFloat
var dragStartX: CGFloat = 0
var dragStartY: CGFloat = 0
let symbol: String
let color: Color
}
struct PieceView: View {
let piece: PuzzlePiece
let pieceSize: CGFloat
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(piece.color.gradient)
.frame(width: pieceSize - 4, height: pieceSize - 4)
Image(systemName: piece.symbol)
.font(.system(size: 36))
.foregroundStyle(.white)
}
}
}
```