working ios game
This commit is contained in:
154
ios/Shikaku/Core/ShikakuGame.swift
Normal file
154
ios/Shikaku/Core/ShikakuGame.swift
Normal file
@@ -0,0 +1,154 @@
|
||||
import Foundation
|
||||
|
||||
enum GameStatus: Equatable {
|
||||
case playing
|
||||
case solved
|
||||
case error
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class ShikakuGame: ObservableObject {
|
||||
static let supportedSizes = [5, 7, 10, 15, 20, 25]
|
||||
|
||||
@Published private(set) var puzzle: ShikakuPuzzle
|
||||
@Published private(set) var playerRects: [PlayerRect] = []
|
||||
@Published private(set) var errorRectIDs: Set<PlayerRect.ID> = []
|
||||
@Published private(set) var message = "Ready"
|
||||
@Published private(set) var status: GameStatus = .playing
|
||||
|
||||
private var startDate = Date()
|
||||
private var solvedElapsed: TimeInterval?
|
||||
|
||||
var size: Int {
|
||||
puzzle.size
|
||||
}
|
||||
|
||||
var currentSeed: UInt64 {
|
||||
puzzle.seed
|
||||
}
|
||||
|
||||
init(size: Int = 5) {
|
||||
self.puzzle = Self.makePuzzle(size: size)
|
||||
self.startDate = Date()
|
||||
}
|
||||
|
||||
func newPuzzle(size requestedSize: Int? = nil, seed requestedSeed: UInt64? = nil) {
|
||||
let nextSize = requestedSize ?? size
|
||||
puzzle = Self.makePuzzle(size: nextSize, seed: requestedSeed)
|
||||
playerRects = []
|
||||
errorRectIDs = []
|
||||
message = "Ready"
|
||||
status = .playing
|
||||
startDate = Date()
|
||||
solvedElapsed = nil
|
||||
}
|
||||
|
||||
func clear() {
|
||||
playerRects = []
|
||||
errorRectIDs = []
|
||||
message = "Cleared"
|
||||
status = .playing
|
||||
|
||||
if let solvedElapsed {
|
||||
startDate = Date().addingTimeInterval(-solvedElapsed)
|
||||
}
|
||||
|
||||
solvedElapsed = nil
|
||||
}
|
||||
|
||||
func placeRect(_ rect: PlayerRect) {
|
||||
guard rect.area > 1 else {
|
||||
errorRectIDs = []
|
||||
status = .playing
|
||||
message = "Drag over at least 2 cells"
|
||||
return
|
||||
}
|
||||
|
||||
let newCells = Set(rect.cells)
|
||||
playerRects.removeAll { !Set($0.cells).isDisjoint(with: newCells) }
|
||||
playerRects.append(rect)
|
||||
errorRectIDs = []
|
||||
status = .playing
|
||||
runAutoCheck()
|
||||
}
|
||||
|
||||
func removeRect(at cell: GridPoint) {
|
||||
let oldCount = playerRects.count
|
||||
playerRects.removeAll { $0.contains(cell) }
|
||||
|
||||
if playerRects.count != oldCount {
|
||||
errorRectIDs = []
|
||||
status = .playing
|
||||
message = "Ready"
|
||||
solvedElapsed = nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkSolution() {
|
||||
let result = ShikakuValidation.verify(size: size, playerRects: playerRects, clues: puzzle.clues)
|
||||
|
||||
if result.isSolved {
|
||||
markSolved()
|
||||
errorRectIDs = []
|
||||
message = "Solved in \(formattedElapsed())"
|
||||
} else {
|
||||
status = .error
|
||||
errorRectIDs = invalidRectIDs()
|
||||
message = result.message
|
||||
}
|
||||
}
|
||||
|
||||
func formattedElapsed(at date: Date = Date()) -> String {
|
||||
let elapsed = solvedElapsed ?? max(0, date.timeIntervalSince(startDate))
|
||||
let minutes = Int(elapsed) / 60
|
||||
let seconds = Int(elapsed) % 60
|
||||
return String(format: "%d:%02d", minutes, seconds)
|
||||
}
|
||||
|
||||
private func runAutoCheck() {
|
||||
guard status != .solved else {
|
||||
return
|
||||
}
|
||||
|
||||
let result = ShikakuValidation.verify(size: size, playerRects: playerRects, clues: puzzle.clues)
|
||||
if result.isSolved {
|
||||
markSolved()
|
||||
message = "Solved in \(formattedElapsed())"
|
||||
} else {
|
||||
status = .playing
|
||||
message = "Ready"
|
||||
}
|
||||
}
|
||||
|
||||
private func markSolved() {
|
||||
if solvedElapsed == nil {
|
||||
solvedElapsed = Date().timeIntervalSince(startDate)
|
||||
}
|
||||
status = .solved
|
||||
}
|
||||
|
||||
private func invalidRectIDs() -> Set<PlayerRect.ID> {
|
||||
Set(playerRects.compactMap { rect in
|
||||
let clueValues = rect.cells.compactMap { puzzle.clues[$0] }
|
||||
if clueValues.count != 1 || clueValues.first != rect.area {
|
||||
return rect.id
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
private static func makePuzzle(size: Int, seed requestedSeed: UInt64? = nil) -> ShikakuPuzzle {
|
||||
do {
|
||||
return try ShikakuGenerator.generate(size: size, seed: requestedSeed)
|
||||
} catch {
|
||||
assertionFailure("Falling back after puzzle generation error: \(error)")
|
||||
return fallbackPuzzle(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
private static func fallbackPuzzle(size: Int) -> ShikakuPuzzle {
|
||||
let rect = SolutionRect(row: 0, col: 0, height: size, width: size)
|
||||
let clue = GridPoint(row: size / 2, col: size / 2)
|
||||
return ShikakuPuzzle(size: size, clues: [clue: rect.area], solution: [rect], seed: 0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user