crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

PuzzleCatalog.swift (2086B)


      1 import Foundation
      2 
      3 /// Knows about packaged `.xd` puzzle resources. Used by the new-game picker
      4 /// to list available puzzles.
      5 struct PuzzleCatalog {
      6     struct Entry: Identifiable {
      7         let id: String        // resource name (e.g. "sample")
      8         let title: String     // parsed from the XD metadata
      9         let source: String    // raw XD text
     10         let cmVersion: Int    // Crossmate version parser version
     11     }
     12 
     13     static func bundledPuzzles() -> [Entry] {
     14         puzzles(in: "Puzzles/Bundled")
     15     }
     16 
     17     static func debugPuzzles() -> [Entry] {
     18         puzzles(in: "Puzzles/Debug")
     19     }
     20 
     21     static func source(
     22         matchingResourceID resourceID: String?,
     23         title: String?
     24     ) -> Entry? {
     25         let entries = bundledPuzzles() + debugPuzzles()
     26 
     27         if let resourceID {
     28             return entries.first { $0.id == resourceID }
     29         } else if let title {
     30             return entries.first { $0.title == title }
     31         } else {
     32             return nil
     33         }
     34     }
     35 
     36     static func resourceID(matching source: String) -> String? {
     37         (bundledPuzzles() + debugPuzzles()).first { $0.source == source }?.id
     38     }
     39 
     40     private static func puzzles(in subdirectory: String) -> [Entry] {
     41         guard let directoryURL = Bundle.main.resourceURL?
     42             .appendingPathComponent(subdirectory, isDirectory: true) else {
     43             return []
     44         }
     45         let urls = (try? FileManager.default.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: nil)) ?? []
     46 
     47         return urls.compactMap { url in
     48             guard url.pathExtension == "xd" else {
     49                 return nil
     50             }
     51             let name = url.deletingPathExtension().lastPathComponent
     52             guard let source = try? String(contentsOf: url, encoding: .utf8),
     53                   let xd = try? XD.parse(source) else {
     54                 return nil
     55             }
     56             return Entry(id: name, title: xd.title ?? name, source: source, cmVersion: xd.cmVersion)
     57         }
     58         .sorted { $0.id.localizedStandardCompare($1.id) == .orderedAscending }
     59     }
     60 }