├── .gitignore ├── ConwayMatrixContainerProject ├── ConwayMatrixContainerProject.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── ConwayMatrixContainerProject │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Particle Sprite Atlas.spriteatlas │ │ ├── Contents.json │ │ ├── bokeh.imageset │ │ ├── Contents.json │ │ └── bokeh.png │ │ └── spark.imageset │ │ ├── Contents.json │ │ └── spark.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── LICENSE ├── PentatonicGameOfLife.playground ├── Contents.swift ├── Resources │ ├── Actions.sks │ ├── GameScene.sks │ ├── Meow_1.wav │ ├── Meow_10.wav │ ├── Meow_11.wav │ ├── Meow_12.wav │ ├── Meow_2.wav │ ├── Meow_3.wav │ ├── Meow_4.wav │ ├── Meow_5.wav │ ├── Meow_6.wav │ ├── Meow_7.wav │ ├── Meow_8.wav │ ├── Meow_9.wav │ ├── background.png │ ├── button.png │ ├── buttonsmall.png │ ├── cat.png │ ├── circle.png │ ├── golrules.png │ ├── logotype.png │ ├── logotype_rainbow.png │ ├── pentatonic.png │ ├── pulse.png │ ├── rain.sks │ ├── tone.png │ ├── tonematrix_1.wav │ ├── tonematrix_10.wav │ ├── tonematrix_11.wav │ ├── tonematrix_12.wav │ ├── tonematrix_2.wav │ ├── tonematrix_3.wav │ ├── tonematrix_4.wav │ ├── tonematrix_5.wav │ ├── tonematrix_6.wav │ ├── tonematrix_7.wav │ ├── tonematrix_8.wav │ └── tonematrix_9.wav ├── Sources │ ├── ButtonNode.swift │ ├── GameScene.swift │ ├── Preset.swift │ ├── Rainbow.swift │ ├── SpotlightNode.swift │ └── ToneNode.swift └── contents.xcplayground ├── README.md └── Resources ├── howitworks.png ├── screenshots.png └── title.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D83102DB206B08E50098457E /* SpotlightNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83102DA206B08E50098457E /* SpotlightNode.swift */; }; 11 | D85125782069B8770078B156 /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85125732069B8770078B156 /* GameScene.swift */; }; 12 | D85125792069B8770078B156 /* ToneNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85125742069B8770078B156 /* ToneNode.swift */; }; 13 | D851257A2069B8770078B156 /* Rainbow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85125752069B8770078B156 /* Rainbow.swift */; }; 14 | D851257B2069B8770078B156 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85125762069B8770078B156 /* Preset.swift */; }; 15 | D851257C2069B8770078B156 /* ButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85125772069B8770078B156 /* ButtonNode.swift */; }; 16 | D87CC5352066B76A00C22911 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87CC5342066B76A00C22911 /* AppDelegate.swift */; }; 17 | D87CC5372066B76A00C22911 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87CC5362066B76A00C22911 /* ViewController.swift */; }; 18 | D87CC53A2066B76A00C22911 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D87CC5382066B76A00C22911 /* Main.storyboard */; }; 19 | D87CC53C2066B76B00C22911 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D87CC53B2066B76B00C22911 /* Assets.xcassets */; }; 20 | D87CC53F2066B76B00C22911 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D87CC53D2066B76B00C22911 /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | D83102DA206B08E50098457E /* SpotlightNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SpotlightNode.swift; path = ../PentatonicGameOfLife.playground/Sources/SpotlightNode.swift; sourceTree = ""; }; 25 | D85125722069B8460078B156 /* PentatonicGameOfLife.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = PentatonicGameOfLife.playground; path = ../PentatonicGameOfLife.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 26 | D85125732069B8770078B156 /* GameScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GameScene.swift; path = ../PentatonicGameOfLife.playground/Sources/GameScene.swift; sourceTree = ""; }; 27 | D85125742069B8770078B156 /* ToneNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ToneNode.swift; path = ../PentatonicGameOfLife.playground/Sources/ToneNode.swift; sourceTree = ""; }; 28 | D85125752069B8770078B156 /* Rainbow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Rainbow.swift; path = ../PentatonicGameOfLife.playground/Sources/Rainbow.swift; sourceTree = ""; }; 29 | D85125762069B8770078B156 /* Preset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Preset.swift; path = ../PentatonicGameOfLife.playground/Sources/Preset.swift; sourceTree = ""; }; 30 | D85125772069B8770078B156 /* ButtonNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ButtonNode.swift; path = ../PentatonicGameOfLife.playground/Sources/ButtonNode.swift; sourceTree = ""; }; 31 | D87CC5312066B76A00C22911 /* ConwayMatrixContainerProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ConwayMatrixContainerProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | D87CC5342066B76A00C22911 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | D87CC5362066B76A00C22911 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 34 | D87CC5392066B76A00C22911 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | D87CC53B2066B76B00C22911 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | D87CC53E2066B76B00C22911 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | D87CC5402066B76B00C22911 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | D87CC52E2066B76A00C22911 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | D87CC5282066B76A00C22911 = { 52 | isa = PBXGroup; 53 | children = ( 54 | D83102DA206B08E50098457E /* SpotlightNode.swift */, 55 | D85125772069B8770078B156 /* ButtonNode.swift */, 56 | D85125732069B8770078B156 /* GameScene.swift */, 57 | D85125762069B8770078B156 /* Preset.swift */, 58 | D85125752069B8770078B156 /* Rainbow.swift */, 59 | D85125742069B8770078B156 /* ToneNode.swift */, 60 | D85125722069B8460078B156 /* PentatonicGameOfLife.playground */, 61 | D87CC5332066B76A00C22911 /* ConwayMatrixContainerProject */, 62 | D87CC5322066B76A00C22911 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | D87CC5322066B76A00C22911 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | D87CC5312066B76A00C22911 /* ConwayMatrixContainerProject.app */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | D87CC5332066B76A00C22911 /* ConwayMatrixContainerProject */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | D87CC5342066B76A00C22911 /* AppDelegate.swift */, 78 | D87CC5362066B76A00C22911 /* ViewController.swift */, 79 | D87CC5382066B76A00C22911 /* Main.storyboard */, 80 | D87CC53B2066B76B00C22911 /* Assets.xcassets */, 81 | D87CC53D2066B76B00C22911 /* LaunchScreen.storyboard */, 82 | D87CC5402066B76B00C22911 /* Info.plist */, 83 | ); 84 | path = ConwayMatrixContainerProject; 85 | sourceTree = ""; 86 | }; 87 | /* End PBXGroup section */ 88 | 89 | /* Begin PBXNativeTarget section */ 90 | D87CC5302066B76A00C22911 /* ConwayMatrixContainerProject */ = { 91 | isa = PBXNativeTarget; 92 | buildConfigurationList = D87CC5432066B76B00C22911 /* Build configuration list for PBXNativeTarget "ConwayMatrixContainerProject" */; 93 | buildPhases = ( 94 | D87CC52D2066B76A00C22911 /* Sources */, 95 | D87CC52E2066B76A00C22911 /* Frameworks */, 96 | D87CC52F2066B76A00C22911 /* Resources */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = ConwayMatrixContainerProject; 103 | productName = ConwayMatrixContainerProject; 104 | productReference = D87CC5312066B76A00C22911 /* ConwayMatrixContainerProject.app */; 105 | productType = "com.apple.product-type.application"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | D87CC5292066B76A00C22911 /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastSwiftUpdateCheck = 0930; 114 | LastUpgradeCheck = 0930; 115 | ORGANIZATIONNAME = "Nathan Gitter"; 116 | TargetAttributes = { 117 | D87CC5302066B76A00C22911 = { 118 | CreatedOnToolsVersion = 9.3; 119 | }; 120 | }; 121 | }; 122 | buildConfigurationList = D87CC52C2066B76A00C22911 /* Build configuration list for PBXProject "ConwayMatrixContainerProject" */; 123 | compatibilityVersion = "Xcode 9.3"; 124 | developmentRegion = en; 125 | hasScannedForEncodings = 0; 126 | knownRegions = ( 127 | en, 128 | Base, 129 | ); 130 | mainGroup = D87CC5282066B76A00C22911; 131 | productRefGroup = D87CC5322066B76A00C22911 /* Products */; 132 | projectDirPath = ""; 133 | projectRoot = ""; 134 | targets = ( 135 | D87CC5302066B76A00C22911 /* ConwayMatrixContainerProject */, 136 | ); 137 | }; 138 | /* End PBXProject section */ 139 | 140 | /* Begin PBXResourcesBuildPhase section */ 141 | D87CC52F2066B76A00C22911 /* Resources */ = { 142 | isa = PBXResourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | D87CC53F2066B76B00C22911 /* LaunchScreen.storyboard in Resources */, 146 | D87CC53C2066B76B00C22911 /* Assets.xcassets in Resources */, 147 | D87CC53A2066B76A00C22911 /* Main.storyboard in Resources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXResourcesBuildPhase section */ 152 | 153 | /* Begin PBXSourcesBuildPhase section */ 154 | D87CC52D2066B76A00C22911 /* Sources */ = { 155 | isa = PBXSourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | D83102DB206B08E50098457E /* SpotlightNode.swift in Sources */, 159 | D85125782069B8770078B156 /* GameScene.swift in Sources */, 160 | D85125792069B8770078B156 /* ToneNode.swift in Sources */, 161 | D851257A2069B8770078B156 /* Rainbow.swift in Sources */, 162 | D851257B2069B8770078B156 /* Preset.swift in Sources */, 163 | D851257C2069B8770078B156 /* ButtonNode.swift in Sources */, 164 | D87CC5372066B76A00C22911 /* ViewController.swift in Sources */, 165 | D87CC5352066B76A00C22911 /* AppDelegate.swift in Sources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXSourcesBuildPhase section */ 170 | 171 | /* Begin PBXVariantGroup section */ 172 | D87CC5382066B76A00C22911 /* Main.storyboard */ = { 173 | isa = PBXVariantGroup; 174 | children = ( 175 | D87CC5392066B76A00C22911 /* Base */, 176 | ); 177 | name = Main.storyboard; 178 | sourceTree = ""; 179 | }; 180 | D87CC53D2066B76B00C22911 /* LaunchScreen.storyboard */ = { 181 | isa = PBXVariantGroup; 182 | children = ( 183 | D87CC53E2066B76B00C22911 /* Base */, 184 | ); 185 | name = LaunchScreen.storyboard; 186 | sourceTree = ""; 187 | }; 188 | /* End PBXVariantGroup section */ 189 | 190 | /* Begin XCBuildConfiguration section */ 191 | D87CC5412066B76B00C22911 /* Debug */ = { 192 | isa = XCBuildConfiguration; 193 | buildSettings = { 194 | ALWAYS_SEARCH_USER_PATHS = NO; 195 | CLANG_ANALYZER_NONNULL = YES; 196 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 197 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 198 | CLANG_CXX_LIBRARY = "libc++"; 199 | CLANG_ENABLE_MODULES = YES; 200 | CLANG_ENABLE_OBJC_ARC = YES; 201 | CLANG_ENABLE_OBJC_WEAK = YES; 202 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 203 | CLANG_WARN_BOOL_CONVERSION = YES; 204 | CLANG_WARN_COMMA = YES; 205 | CLANG_WARN_CONSTANT_CONVERSION = YES; 206 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 207 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 208 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 209 | CLANG_WARN_EMPTY_BODY = YES; 210 | CLANG_WARN_ENUM_CONVERSION = YES; 211 | CLANG_WARN_INFINITE_RECURSION = YES; 212 | CLANG_WARN_INT_CONVERSION = YES; 213 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 214 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 215 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 217 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 218 | CLANG_WARN_STRICT_PROTOTYPES = YES; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | CODE_SIGN_IDENTITY = "iPhone Developer"; 224 | COPY_PHASE_STRIP = NO; 225 | DEBUG_INFORMATION_FORMAT = dwarf; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | ENABLE_TESTABILITY = YES; 228 | GCC_C_LANGUAGE_STANDARD = gnu11; 229 | GCC_DYNAMIC_NO_PIC = NO; 230 | GCC_NO_COMMON_BLOCKS = YES; 231 | GCC_OPTIMIZATION_LEVEL = 0; 232 | GCC_PREPROCESSOR_DEFINITIONS = ( 233 | "DEBUG=1", 234 | "$(inherited)", 235 | ); 236 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 237 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 238 | GCC_WARN_UNDECLARED_SELECTOR = YES; 239 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 240 | GCC_WARN_UNUSED_FUNCTION = YES; 241 | GCC_WARN_UNUSED_VARIABLE = YES; 242 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 243 | MTL_ENABLE_DEBUG_INFO = YES; 244 | ONLY_ACTIVE_ARCH = YES; 245 | SDKROOT = iphoneos; 246 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 247 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 248 | }; 249 | name = Debug; 250 | }; 251 | D87CC5422066B76B00C22911 /* Release */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_ENABLE_OBJC_WEAK = YES; 262 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_COMMA = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 275 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | CODE_SIGN_IDENTITY = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu11; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | SWIFT_COMPILATION_MODE = wholemodule; 300 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Release; 304 | }; 305 | D87CC5442066B76B00C22911 /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | CODE_SIGN_STYLE = Automatic; 310 | DEVELOPMENT_TEAM = PW5ET5HNB2; 311 | INFOPLIST_FILE = ConwayMatrixContainerProject/Info.plist; 312 | LD_RUNPATH_SEARCH_PATHS = ( 313 | "$(inherited)", 314 | "@executable_path/Frameworks", 315 | ); 316 | PRODUCT_BUNDLE_IDENTIFIER = nathangitter.ConwayMatrixContainerProject; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | SWIFT_VERSION = 4.0; 319 | TARGETED_DEVICE_FAMILY = "1,2"; 320 | }; 321 | name = Debug; 322 | }; 323 | D87CC5452066B76B00C22911 /* Release */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 327 | CODE_SIGN_STYLE = Automatic; 328 | DEVELOPMENT_TEAM = PW5ET5HNB2; 329 | INFOPLIST_FILE = ConwayMatrixContainerProject/Info.plist; 330 | LD_RUNPATH_SEARCH_PATHS = ( 331 | "$(inherited)", 332 | "@executable_path/Frameworks", 333 | ); 334 | PRODUCT_BUNDLE_IDENTIFIER = nathangitter.ConwayMatrixContainerProject; 335 | PRODUCT_NAME = "$(TARGET_NAME)"; 336 | SWIFT_VERSION = 4.0; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | }; 339 | name = Release; 340 | }; 341 | /* End XCBuildConfiguration section */ 342 | 343 | /* Begin XCConfigurationList section */ 344 | D87CC52C2066B76A00C22911 /* Build configuration list for PBXProject "ConwayMatrixContainerProject" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | D87CC5412066B76B00C22911 /* Debug */, 348 | D87CC5422066B76B00C22911 /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | D87CC5432066B76B00C22911 /* Build configuration list for PBXNativeTarget "ConwayMatrixContainerProject" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | D87CC5442066B76B00C22911 /* Debug */, 357 | D87CC5452066B76B00C22911 /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | /* End XCConfigurationList section */ 363 | }; 364 | rootObject = D87CC5292066B76A00C22911 /* Project object */; 365 | } 366 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ConwayMatrixContainerProject 4 | // 5 | // Created by Nathan Gitter on 3/24/18. 6 | // Copyright © 2018 Nathan Gitter. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Particle Sprite Atlas.spriteatlas/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bokeh.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "spark.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/ConwayMatrixContainerProject/ConwayMatrixContainerProject/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ConwayMatrixContainerProject/ConwayMatrixContainerProject/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ConwayMatrixContainerProject 4 | // 5 | // Created by Nathan Gitter on 3/24/18. 6 | // Copyright © 2018 Nathan Gitter. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidAppear(_ animated: Bool) { 14 | 15 | // 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nathan Gitter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: # Pentatonic Game of Life 🔬🎵 2 | //: ## Interactive music generation based on Conway's game of life 3 | //: 4 | //: ### Instructions 5 | //: 6 | //: 1. Open the Assistant Editor (button in the top right). 7 | //: 2. Click the "Play" button in the bottom left. 8 | //: 9 | //: ### About the Playground 10 | //: 11 | //: To start, click and drag to activate cells or choose from one of the presets. 12 | //: 13 | //: Every few seconds, the cells will form a new generation. This will add and remove cells according to the rules of the game of life (listed below). 14 | //: 15 | //: Try to create interesting patterns and music! There's no way to "win" the game, as it's just an interactive simulation. Have fun! 16 | //: 17 | //: ### Rules of Conway's Game of Life 18 | //: 19 | //: ![Rules of Conway's Game of Life](golrules.png) 20 | //: 21 | //: Conway's game of life is a cellular automaton that follows simple rules to create life-like patterns. The idea is that each cell can be "alive" or "dead" based on the number of surrounding cells. 22 | //: 23 | //: Each "generation", cells change state. They either stay the same, die, or come alive. This can create interesting patterns, shapes, and more! 24 | //: 25 | //: While it's called a "game", it's more of a simulation since there's no way to win or lose. 26 | //: 27 | //: ### Pentatonic Scale 28 | //: 29 | //: ![Diagram showing the pentatonic scale](pentatonic.png) 30 | //: 31 | //: A pentatonic tone matrix uses the pentatonic scale to create tones that sound pleasing. 32 | //: 33 | //: A pentatonic scale has 5 fives notes per octave, which is different from the traditional septatonic scale which has 7. 34 | //: 35 | //: In this case, we are using the notes "C, D, F, G, A" instead of the traditional "C, D, E, F, G, B, A". 36 | //: 37 | //: ### Special Effects 38 | //: 39 | //: Make sure to try the special effects! Try the rainbow effect, meow effect, and party effect! Or combine them all! 🌈😺🎉 40 | //: 41 | import PlaygroundSupport 42 | import SpriteKit 43 | 44 | let sceneView = SKView(frame: CGRect(x: 0 , y: 0, width: 590, height: 840)) 45 | if let scene = GameScene(fileNamed: "GameScene") { 46 | scene.scaleMode = .aspectFill 47 | sceneView.presentScene(scene) 48 | } 49 | 50 | PlaygroundSupport.PlaygroundPage.current.liveView = sceneView 51 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Actions.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Actions.sks -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/GameScene.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/GameScene.sks -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_1.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_10.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_11.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_11.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_12.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_12.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_2.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_3.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_4.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_5.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_6.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_7.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_8.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/Meow_9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/Meow_9.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/background.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/button.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/buttonsmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/buttonsmall.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/cat.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/circle.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/golrules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/golrules.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/logotype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/logotype.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/logotype_rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/logotype_rainbow.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/pentatonic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/pentatonic.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/pulse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/pulse.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/rain.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/rain.sks -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tone.png -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_1.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_10.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_11.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_11.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_12.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_12.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_2.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_3.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_4.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_5.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_6.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_7.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_8.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Resources/tonematrix_9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/PentatonicGameOfLife.playground/Resources/tonematrix_9.wav -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Sources/ButtonNode.swift: -------------------------------------------------------------------------------- 1 | import SpriteKit 2 | 3 | class ButtonNode: SKSpriteNode { 4 | 5 | // MARK: - Public API 6 | 7 | public var text: String = "" { 8 | didSet { 9 | labelNode.text = text 10 | } 11 | } 12 | 13 | public var action: () -> () = {} 14 | 15 | public func touchEffect() { 16 | let fadeInAction = SKAction.fadeAlpha(to: 0.5, duration: 0.05) 17 | let fadeBackAction = SKAction.fadeAlpha(to: 0.2, duration: 0.2) 18 | spriteNode.run(SKAction.sequence([fadeInAction, fadeBackAction])) 19 | } 20 | 21 | public let isSmall: Bool 22 | 23 | public var isSelected = false { 24 | didSet { 25 | spriteNode.alpha = isSelected ? 0.5 : 0.2 26 | } 27 | } 28 | 29 | // MARK: - Nodes 30 | 31 | private var spriteNode = SKSpriteNode(texture: nil, color: .white, size: .zero) 32 | private var labelNode = SKLabelNode() 33 | 34 | // MARK: - Constants 35 | 36 | private let normalSize = CGSize(width: 152, height: 36) 37 | private let smallSize = CGSize(width: 120, height: 36) 38 | 39 | // MARK: - Initialization 40 | 41 | init(isSmall: Bool = false) { 42 | self.isSmall = isSmall 43 | super.init(texture: nil, color: .clear, size: isSmall ? smallSize : normalSize) 44 | spriteNode.size = isSmall ? smallSize : normalSize 45 | spriteNode.texture = isSmall ? SKTexture(imageNamed: "buttonsmall") : SKTexture(imageNamed: "button") 46 | spriteNode.colorBlendFactor = 1 47 | spriteNode.alpha = 0.2 48 | spriteNode.isUserInteractionEnabled = false 49 | labelNode.fontName = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.heavy).fontName 50 | labelNode.fontSize = 16 51 | labelNode.verticalAlignmentMode = .center 52 | addChild(spriteNode) 53 | addChild(labelNode) 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | self.isSmall = false 58 | super.init(coder: aDecoder) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Sources/GameScene.swift: -------------------------------------------------------------------------------- 1 | import SpriteKit 2 | 3 | public class GameScene: SKScene { 4 | 5 | // MARK: - Constants 6 | 7 | private let gridCount = 12 8 | private let cellSpacing: CGFloat = 38 9 | private let playDuration: TimeInterval = 2 10 | 11 | // MARK: - Nodes 12 | 13 | private let matrixNode = SKNode() 14 | private let buttonsNode = SKNode() 15 | private var toneNodes = [[ToneNode]]() 16 | private var titleNode = SKSpriteNode() 17 | private var descriptionLabel = SKLabelNode() 18 | private var leftSpotlightNode = SpotlightNode() 19 | private var rightSpotlightNode = SpotlightNode() 20 | private let rainEmitterNode = SKEmitterNode(fileNamed: "rain")! 21 | 22 | // MARK: - Steup 23 | 24 | public override func didMove(to view: SKView) { 25 | 26 | // set the background 27 | let backgroundNode = SKSpriteNode(texture: SKTexture(imageNamed: "background"), color: .clear, size: size) 28 | addChild(backgroundNode) 29 | 30 | // fancy title 31 | titleNode = SKSpriteNode(texture: SKTexture(imageNamed: "logotype"), color: .clear, size: CGSize(width: 450, height: 40)) 32 | titleNode.position = CGPoint(x: 0, y: 330) 33 | addChild(titleNode) 34 | 35 | // description label 36 | descriptionLabel.text = "Stable state — click cells or select a preset to continue the simulation." 37 | descriptionLabel.alpha = 0 38 | descriptionLabel.position = CGPoint(x: 0, y: -185) 39 | descriptionLabel.verticalAlignmentMode = .center 40 | descriptionLabel.fontSize = 12 41 | descriptionLabel.fontName = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.heavy).fontName 42 | addChild(descriptionLabel) 43 | 44 | // build the tone matrix 45 | for x in 0 ..< gridCount { 46 | var toneColumn = [ToneNode]() 47 | for y in 0 ..< gridCount { 48 | let toneNode = ToneNode() 49 | let offset = -CGFloat(gridCount) / 2 * cellSpacing + cellSpacing / 2 50 | toneNode.position = CGPoint(x: offset + CGFloat(x) * cellSpacing, y: offset + CGFloat(y) * cellSpacing) 51 | toneNode.row = y + 1 52 | matrixNode.addChild(toneNode) 53 | toneColumn.append(toneNode) 54 | } 55 | toneNodes.append(toneColumn) 56 | } 57 | 58 | // set up the matrix node 59 | matrixNode.position = CGPoint(x: 0, y: 54) 60 | addChild(matrixNode) 61 | 62 | // set up buttons 63 | buttonsNode.position = CGPoint(x: 0, y: -250) 64 | addChild(buttonsNode) 65 | 66 | let presetsLabelNode = SKLabelNode() 67 | presetsLabelNode.position = CGPoint(x: -184, y: 36) 68 | presetsLabelNode.verticalAlignmentMode = .center 69 | presetsLabelNode.text = "PRESETS" 70 | presetsLabelNode.fontSize = 16 71 | presetsLabelNode.fontName = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.heavy).fontName 72 | buttonsNode.addChild(presetsLabelNode) 73 | 74 | let emptyButton = ButtonNode() 75 | emptyButton.position = CGPoint(x: -148, y: 0) 76 | emptyButton.text = "EMPTY" 77 | emptyButton.action = { 78 | self.resetPlaying() 79 | self.applyPreset(preset: Preset.empty) 80 | self.hideDescription() 81 | } 82 | buttonsNode.addChild(emptyButton) 83 | 84 | let heartButton = ButtonNode() 85 | heartButton.position = CGPoint(x: -148, y: -44) 86 | heartButton.text = "HEART" 87 | heartButton.action = { 88 | self.resetPlaying() 89 | self.applyPreset(preset: Preset.heart) 90 | self.hideDescription() 91 | } 92 | buttonsNode.addChild(heartButton) 93 | 94 | let gliderButton = ButtonNode() 95 | gliderButton.position = CGPoint(x: -148, y: -88) 96 | gliderButton.text = "GLIDER" 97 | gliderButton.action = { 98 | self.resetPlaying() 99 | self.applyPreset(preset: Preset.glider) 100 | self.hideDescription() 101 | } 102 | buttonsNode.addChild(gliderButton) 103 | 104 | let swiftButton = ButtonNode() 105 | swiftButton.position = CGPoint(x: 16, y: 0) 106 | swiftButton.text = "SWIFT" 107 | swiftButton.action = { 108 | self.resetPlaying() 109 | self.applyPreset(preset: Preset.swift) 110 | self.hideDescription() 111 | } 112 | buttonsNode.addChild(swiftButton) 113 | 114 | let randomButton = ButtonNode() 115 | randomButton.position = CGPoint(x: 16, y: -44) 116 | randomButton.text = "RANDOM" 117 | randomButton.action = { 118 | self.resetPlaying() 119 | self.applyPreset(preset: Preset.random) 120 | self.hideDescription() 121 | } 122 | buttonsNode.addChild(randomButton) 123 | 124 | let oscillatorButton = ButtonNode() 125 | oscillatorButton.position = CGPoint(x: 16, y: -88) 126 | oscillatorButton.text = "OSCILLATOR" 127 | oscillatorButton.action = { 128 | self.resetPlaying() 129 | self.applyPreset(preset: Preset.oscillator) 130 | self.hideDescription() 131 | } 132 | buttonsNode.addChild(oscillatorButton) 133 | 134 | let funLabelNode = SKLabelNode() 135 | funLabelNode.position = CGPoint(x: 142, y: 36) 136 | funLabelNode.verticalAlignmentMode = .center 137 | funLabelNode.text = "EFFECTS" 138 | funLabelNode.fontSize = 16 139 | funLabelNode.fontName = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.heavy).fontName 140 | buttonsNode.addChild(funLabelNode) 141 | 142 | let colorButton = ButtonNode(isSmall: true) 143 | colorButton.position = CGPoint(x: 165, y: 0) 144 | colorButton.text = "🌈 COLORS" 145 | colorButton.action = { 146 | self.isRainbow = colorButton.isSelected 147 | self.updateEffects() 148 | } 149 | buttonsNode.addChild(colorButton) 150 | 151 | let meowButton = ButtonNode(isSmall: true) 152 | meowButton.position = CGPoint(x: 165, y: -44) 153 | meowButton.text = "😺 MEOWS" 154 | meowButton.action = { 155 | self.isMeow = meowButton.isSelected 156 | self.updateEffects() 157 | } 158 | buttonsNode.addChild(meowButton) 159 | 160 | let partyButton = ButtonNode(isSmall: true) 161 | partyButton.position = CGPoint(x: 165, y: -88) 162 | partyButton.text = "🎉 PARTY" 163 | partyButton.action = { 164 | self.isParty = partyButton.isSelected 165 | self.updateEffects() 166 | } 167 | buttonsNode.addChild(partyButton) 168 | 169 | // party effect nodes 170 | rightSpotlightNode = SpotlightNode() 171 | rightSpotlightNode.move(path: .right) 172 | rightSpotlightNode.isHidden = true 173 | addChild(rightSpotlightNode) 174 | 175 | leftSpotlightNode = SpotlightNode() 176 | leftSpotlightNode.move(path: .left) 177 | leftSpotlightNode.isHidden = true 178 | addChild(leftSpotlightNode) 179 | 180 | rainEmitterNode.position = CGPoint(x: 0, y: 400) 181 | rainEmitterNode.isHidden = true 182 | addChild(rainEmitterNode) 183 | 184 | // fancy scattered intro animation 185 | for toneColumn in toneNodes { 186 | for toneNode in toneColumn { 187 | let finalPosition = toneNode.position 188 | let randomOffset = CGFloat(20 + arc4random_uniform(200)) 189 | let offsetAction = SKAction.moveBy(x: 0, y: -randomOffset, duration: 0) 190 | toneNode.run(offsetAction) 191 | toneNode.alpha = 0 192 | let randomDelay = 0.5 + Double(arc4random_uniform(20)) / 20 193 | let delayAction = SKAction.wait(forDuration: randomDelay) 194 | let introDuration = Double(randomOffset) / 200 195 | let moveAction = SKAction.move(to: finalPosition, duration: introDuration) 196 | let fadeAction = SKAction.fadeIn(withDuration: introDuration) 197 | let introAction = SKAction.group([moveAction, fadeAction]) 198 | introAction.timingMode = .easeOut 199 | toneNode.run(SKAction.sequence([delayAction, introAction])) 200 | } 201 | } 202 | 203 | // load one of the presets 204 | applyPreset(preset: Preset.oscillator) 205 | 206 | // play the tones 207 | resetPlaying() 208 | 209 | } 210 | 211 | // MARK: - Logic 212 | 213 | private func resetPlaying() { 214 | let playActionKey = "playAction" 215 | removeAction(forKey: playActionKey) 216 | removeAction(forKey: "stepKey") 217 | toneNodes.forEach { $0.forEach { $0.reset() }} 218 | let delayAction = SKAction.wait(forDuration: playDuration + 0.2) 219 | let playTonesAction = SKAction.run { self.playTones() } 220 | let playAction = SKAction.repeatForever(SKAction.sequence([delayAction, playTonesAction])) 221 | run(playAction, withKey: playActionKey) 222 | } 223 | 224 | private func playTones() { 225 | 226 | // loop left -> right, playing tones 227 | for (index, toneColumn) in toneNodes.enumerated() { 228 | let delayAction = SKAction.wait(forDuration: Double(index) * playDuration / Double(gridCount)) 229 | for toneNode in toneColumn { 230 | let playAction = SKAction.run { 231 | if toneNode.isOn { 232 | toneNode.play() 233 | if toneNode.isCat { 234 | self.playMeow(number: toneNode.row) 235 | } else { 236 | self.playTone(number: toneNode.row) 237 | } 238 | } 239 | } 240 | toneNode.run(SKAction.sequence([delayAction, playAction])) 241 | } 242 | } 243 | 244 | // at the end, make one step in game of life 245 | let waitAction = SKAction.wait(forDuration: playDuration) 246 | let stepAction = SKAction.run { self.stepGameOfLife() } 247 | run(SKAction.sequence([waitAction, stepAction]), withKey: "stepKey") 248 | 249 | } 250 | 251 | private var previousNeighbors = [[Int]]() 252 | 253 | private func stepGameOfLife() { 254 | 255 | // create a parallel array to track neighbors 256 | var neighbors = Array(repeating: Array(repeating: 0, count: toneNodes[0].count), count: toneNodes.count) 257 | 258 | // count the number of neighbors for each cell 259 | for (x, toneColumn) in toneNodes.enumerated() { 260 | for (y, _) in toneColumn.enumerated() { 261 | neighbors[x][y] = { 262 | var count = 0 263 | let nearIndexes = [ 264 | (x - 1, y - 1), (x, y - 1), (x + 1, y - 1), 265 | (x - 1, y), (x + 1, y), 266 | (x - 1, y + 1), (x, y + 1), (x + 1, y + 1), 267 | ] 268 | for (xIndex, yIndex) in nearIndexes { 269 | if xIndex >= 0 && xIndex < toneNodes.count && yIndex >= 0 && yIndex < toneNodes[xIndex].count { 270 | if toneNodes[xIndex][yIndex].isOn { 271 | count += 1 272 | } 273 | } 274 | } 275 | return count 276 | }() 277 | } 278 | } 279 | 280 | // check for stable state 281 | if neighbors.reduce([], +) == previousNeighbors.reduce([], +) { showDescription() } 282 | previousNeighbors = neighbors 283 | 284 | // decide the fate of each cell 285 | for (x, toneColumn) in toneNodes.enumerated() { 286 | for (y, toneNode) in toneColumn.enumerated() { 287 | let numNeighbors = neighbors[x][y] 288 | if toneNode.isOn { 289 | if numNeighbors < 2 || numNeighbors > 3 { 290 | toneNode.die() 291 | } 292 | } else { 293 | if numNeighbors == 3 { 294 | toneNode.birth() 295 | } 296 | } 297 | } 298 | } 299 | 300 | } 301 | 302 | private func applyPreset(preset: [[Bool]]) { 303 | for (x, toneColumn) in toneNodes.enumerated() { 304 | for (y, toneNode) in toneColumn.enumerated() { 305 | toneNode.isOn = preset[x][y] 306 | } 307 | } 308 | } 309 | 310 | // MARK: - Effects :) 311 | 312 | private var isRainbow = false 313 | private var isMeow = false 314 | private var isParty = false 315 | 316 | private func updateEffects() { 317 | 318 | // rainbow effect 319 | titleNode.texture = isRainbow ? SKTexture(imageNamed: "logotype_rainbow") : SKTexture(imageNamed: "logotype") 320 | for toneColumn in toneNodes { 321 | for (index, toneNode) in toneColumn.enumerated() { 322 | toneNode.updateColor(to: isRainbow ? Rainbow.allColors.reversed()[index] : .white) 323 | } 324 | } 325 | leftSpotlightNode.isColorful = isRainbow 326 | rightSpotlightNode.isColorful = isRainbow 327 | if isRainbow { 328 | let times = (0 ..< Rainbow.allColors.count).map { Double($0) * 0.15 } 329 | rainEmitterNode.particleColorSequence = SKKeyframeSequence(keyframeValues: Rainbow.allColors, times: times as [NSNumber]) 330 | } else { 331 | rainEmitterNode.particleColorSequence = SKKeyframeSequence(keyframeValues: [UIColor.white], times: [0]) 332 | } 333 | 334 | // cat effect 335 | for toneColumn in toneNodes { 336 | for toneNode in toneColumn { 337 | toneNode.isCat = isMeow 338 | } 339 | } 340 | 341 | // party effect 342 | leftSpotlightNode.isHidden = !isParty 343 | rightSpotlightNode.isHidden = !isParty 344 | rainEmitterNode.isHidden = !isParty 345 | if isParty { 346 | let growAction = SKAction.scale(to: 1.1, duration: 0.4) 347 | let shrinkAction = SKAction.scale(to: 1, duration: 0.4) 348 | let pulseAction = SKAction.sequence([growAction, shrinkAction]) 349 | titleNode.run(SKAction.repeatForever(pulseAction)) 350 | } else { 351 | titleNode.removeAllActions() 352 | titleNode.run(SKAction.scale(to: 1, duration: 0)) 353 | } 354 | 355 | } 356 | 357 | // MARK: - Helper Animations 358 | 359 | private func showDescription() { 360 | let showAction = SKAction.fadeIn(withDuration: 0.5) 361 | descriptionLabel.run(showAction) 362 | } 363 | 364 | private func hideDescription() { 365 | let hideAction = SKAction.fadeOut(withDuration: 0.5) 366 | descriptionLabel.run(hideAction) 367 | } 368 | 369 | // MARK: - Touches 370 | 371 | /// Whether the first tone touched was on. 372 | /// This dictates the behavior for the remainder of the gesture. 373 | private var isStartingSelectionOn = false 374 | 375 | private func touchDown(atPoint point: CGPoint) { 376 | if let toneNode = nodes(at: point).filter({ $0 is ToneNode }).first as? ToneNode { 377 | isStartingSelectionOn = toneNode.isOn 378 | toneNode.isOn = !isStartingSelectionOn 379 | hideDescription() 380 | } 381 | } 382 | 383 | private func touchMoved(toPoint point: CGPoint) { 384 | if let toneNode = nodes(at: point).filter({ $0 is ToneNode }).first as? ToneNode { 385 | toneNode.isOn = !isStartingSelectionOn 386 | } 387 | } 388 | 389 | private func touchUp(atPoint point: CGPoint) { 390 | if let buttonNode = nodes(at: point).filter({ $0 is ButtonNode }).first as? ButtonNode { 391 | if buttonNode.isSmall { 392 | buttonNode.isSelected = !buttonNode.isSelected 393 | } else { 394 | buttonNode.touchEffect() 395 | } 396 | buttonNode.action() 397 | } 398 | } 399 | 400 | public override func touchesBegan(_ touches: Set, with event: UIEvent?) { 401 | for t in touches { touchDown(atPoint: t.location(in: self)) } 402 | } 403 | 404 | public override func touchesMoved(_ touches: Set, with event: UIEvent?) { 405 | for t in touches { touchMoved(toPoint: t.location(in: self)) } 406 | } 407 | 408 | public override func touchesEnded(_ touches: Set, with event: UIEvent?) { 409 | for t in touches { touchUp(atPoint: t.location(in: self)) } 410 | } 411 | 412 | public override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 413 | for t in touches { touchUp(atPoint: t.location(in: self)) } 414 | } 415 | 416 | // MARK: - Sounds 417 | 418 | // keeping all sound actions in memory for better performance 419 | private let tone1Action = SKAction.playSoundFileNamed("tonematrix_1.wav", waitForCompletion: false) 420 | private let tone2Action = SKAction.playSoundFileNamed("tonematrix_2.wav", waitForCompletion: false) 421 | private let tone3Action = SKAction.playSoundFileNamed("tonematrix_3.wav", waitForCompletion: false) 422 | private let tone4Action = SKAction.playSoundFileNamed("tonematrix_4.wav", waitForCompletion: false) 423 | private let tone5Action = SKAction.playSoundFileNamed("tonematrix_5.wav", waitForCompletion: false) 424 | private let tone6Action = SKAction.playSoundFileNamed("tonematrix_6.wav", waitForCompletion: false) 425 | private let tone7Action = SKAction.playSoundFileNamed("tonematrix_7.wav", waitForCompletion: false) 426 | private let tone8Action = SKAction.playSoundFileNamed("tonematrix_8.wav", waitForCompletion: false) 427 | private let tone9Action = SKAction.playSoundFileNamed("tonematrix_9.wav", waitForCompletion: false) 428 | private let tone10Action = SKAction.playSoundFileNamed("tonematrix_10.wav", waitForCompletion: false) 429 | private let tone11Action = SKAction.playSoundFileNamed("tonematrix_11.wav", waitForCompletion: false) 430 | private let tone12Action = SKAction.playSoundFileNamed("tonematrix_12.wav", waitForCompletion: false) 431 | 432 | private let meow1Action = SKAction.playSoundFileNamed("Meow_1.wav", waitForCompletion: false) 433 | private let meow2Action = SKAction.playSoundFileNamed("Meow_2.wav", waitForCompletion: false) 434 | private let meow3Action = SKAction.playSoundFileNamed("Meow_3.wav", waitForCompletion: false) 435 | private let meow4Action = SKAction.playSoundFileNamed("Meow_4.wav", waitForCompletion: false) 436 | private let meow5Action = SKAction.playSoundFileNamed("Meow_5.wav", waitForCompletion: false) 437 | private let meow6Action = SKAction.playSoundFileNamed("Meow_6.wav", waitForCompletion: false) 438 | private let meow7Action = SKAction.playSoundFileNamed("Meow_7.wav", waitForCompletion: false) 439 | private let meow8Action = SKAction.playSoundFileNamed("Meow_8.wav", waitForCompletion: false) 440 | private let meow9Action = SKAction.playSoundFileNamed("Meow_9.wav", waitForCompletion: false) 441 | private let meow10Action = SKAction.playSoundFileNamed("Meow_10.wav", waitForCompletion: false) 442 | private let meow11Action = SKAction.playSoundFileNamed("Meow_11.wav", waitForCompletion: false) 443 | private let meow12Action = SKAction.playSoundFileNamed("Meow_12.wav", waitForCompletion: false) 444 | 445 | private var allToneActions: [SKAction] { 446 | return [tone1Action, tone2Action, tone3Action, tone4Action, tone5Action, tone6Action, tone7Action, tone8Action, tone9Action, tone10Action, tone11Action, tone12Action] 447 | } 448 | 449 | private var allMeowActions: [SKAction] { 450 | return [meow1Action, meow2Action, meow3Action, meow4Action, meow5Action, meow6Action, meow7Action, meow8Action, meow9Action, meow10Action, meow11Action, meow12Action] 451 | } 452 | 453 | private func playTone(number: Int) { 454 | run(allToneActions[number - 1]) 455 | } 456 | 457 | private func playMeow(number: Int) { 458 | run(allMeowActions[number - 1]) 459 | } 460 | 461 | } 462 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Sources/Preset.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Preset { 4 | 5 | static var empty: [[Bool]] { 6 | let array = [ 7 | [0,0,0,0,0,0,0,0,0,0,0,0], 8 | [0,0,0,0,0,0,0,0,0,0,0,0], 9 | [0,0,0,0,0,0,0,0,0,0,0,0], 10 | [0,0,0,0,0,0,0,0,0,0,0,0], 11 | [0,0,0,0,0,0,0,0,0,0,0,0], 12 | [0,0,0,0,0,0,0,0,0,0,0,0], 13 | [0,0,0,0,0,0,0,0,0,0,0,0], 14 | [0,0,0,0,0,0,0,0,0,0,0,0], 15 | [0,0,0,0,0,0,0,0,0,0,0,0], 16 | [0,0,0,0,0,0,0,0,0,0,0,0], 17 | [0,0,0,0,0,0,0,0,0,0,0,0], 18 | [0,0,0,0,0,0,0,0,0,0,0,0], 19 | ] 20 | return convert(array: array) 21 | } 22 | 23 | static var swift: [[Bool]] { 24 | let array = [ 25 | [0,0,0,0,0,0,0,0,0,0,0,0], 26 | [0,0,0,0,0,0,0,0,0,0,0,0], 27 | [0,0,0,0,0,0,1,0,0,0,0,0], 28 | [0,0,0,0,1,0,0,1,0,0,0,0], 29 | [0,0,0,1,0,1,0,1,0,0,0,0], 30 | [0,0,0,0,1,1,1,1,1,0,0,0], 31 | [0,0,1,0,0,0,1,1,1,0,0,0], 32 | [0,0,0,1,1,1,1,1,0,0,0,0], 33 | [0,0,0,0,0,1,1,0,1,0,0,0], 34 | [0,0,0,0,0,0,0,0,0,0,0,0], 35 | [0,0,0,0,0,0,0,0,0,0,0,0], 36 | [0,0,0,0,0,0,0,0,0,0,0,0], 37 | ] 38 | return convert(array: array) 39 | } 40 | 41 | static var heart: [[Bool]] { 42 | let array = [ 43 | [0,0,0,0,0,0,0,0,0,0,0,0], 44 | [0,0,0,1,1,0,0,1,1,0,0,0], 45 | [0,0,1,0,0,1,1,0,0,1,0,0], 46 | [0,1,0,0,0,0,0,0,0,0,1,0], 47 | [0,1,0,0,0,0,0,0,0,0,1,0], 48 | [0,1,0,0,0,0,0,0,0,0,1,0], 49 | [0,1,0,0,0,0,0,0,0,0,1,0], 50 | [0,0,1,0,0,0,0,0,0,1,0,0], 51 | [0,0,0,1,0,0,0,0,1,0,0,0], 52 | [0,0,0,0,1,0,0,1,0,0,0,0], 53 | [0,0,0,0,0,1,1,0,0,0,0,0], 54 | [0,0,0,0,0,0,0,0,0,0,0,0], 55 | ] 56 | return convert(array: array) 57 | } 58 | 59 | static var glider: [[Bool]] { 60 | let array = [ 61 | [0,0,0,0,0,0,0,0,0,0,0,0], 62 | [0,0,1,0,0,0,0,0,0,0,0,0], 63 | [0,0,0,1,0,0,0,0,0,0,0,0], 64 | [0,1,1,1,0,0,0,0,0,0,0,0], 65 | [0,0,0,0,0,0,0,0,0,0,0,0], 66 | [0,0,0,0,0,0,0,0,0,0,0,0], 67 | [0,0,0,0,0,0,0,0,0,0,0,0], 68 | [0,0,0,0,0,0,0,0,0,0,0,0], 69 | [0,0,0,0,0,0,0,0,0,0,0,0], 70 | [0,0,0,0,0,0,0,0,0,0,0,0], 71 | [0,0,0,0,0,0,0,0,0,0,0,0], 72 | [0,0,0,0,0,0,0,0,0,0,0,0], 73 | ] 74 | return convert(array: array) 75 | } 76 | 77 | static var oscillator: [[Bool]] { 78 | let array = [ 79 | [0,0,0,0,0,0,0,0,0,0,0,0], 80 | [0,0,0,0,0,0,0,0,0,0,0,0], 81 | [0,0,0,0,0,0,0,0,0,0,0,0], 82 | [0,0,0,1,1,1,0,0,0,0,0,0], 83 | [0,0,0,1,1,1,0,0,0,0,0,0], 84 | [0,0,0,1,1,1,0,0,0,0,0,0], 85 | [0,0,0,0,0,0,1,1,1,0,0,0], 86 | [0,0,0,0,0,0,1,1,1,0,0,0], 87 | [0,0,0,0,0,0,1,1,1,0,0,0], 88 | [0,0,0,0,0,0,0,0,0,0,0,0], 89 | [0,0,0,0,0,0,0,0,0,0,0,0], 90 | [0,0,0,0,0,0,0,0,0,0,0,0], 91 | ] 92 | return convert(array: array) 93 | } 94 | 95 | static var random: [[Bool]] { 96 | var array = [[Int]]() 97 | for _ in 0 ..< 12 { 98 | var row = [Int]() 99 | for _ in 0 ..< 12 { 100 | let value = Double(arc4random_uniform(100)) / 100 < 0.1 ? 1 : 0 101 | row.append(value) 102 | } 103 | array.append(row) 104 | } 105 | return convert(array: array) 106 | } 107 | 108 | /// Converts between the easy to design format and easy to use format. 109 | private static func convert(array: [[Int]]) -> [[Bool]] { 110 | var newArray = Array(repeating: [Bool](), count: 12) 111 | for row in array { 112 | for (j, int) in row.enumerated() { 113 | newArray[j].insert(int == 1, at: 0) 114 | } 115 | } 116 | return newArray 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Sources/Rainbow.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | struct Rainbow { 4 | 5 | private static let red = UIColor(hex: 0xFF3B3B) 6 | private static let redOrange = UIColor(hex: 0xFF723B) 7 | private static let orange = UIColor(hex: 0xFFA73B) 8 | private static let yellow = UIColor(hex: 0xFFDD3B) 9 | private static let lime = UIColor(hex: 0xB6FF3B) 10 | private static let mint = UIColor(hex: 0x3BFFA9) 11 | private static let sky = UIColor(hex: 0x3BFFF5) 12 | private static let blue = UIColor(hex: 0x3BBDFF) 13 | private static let darkBlue = UIColor(hex: 0x3B75FF) 14 | private static let purple = UIColor(hex: 0x803BFF) 15 | private static let velvet = UIColor(hex: 0xBC3BFF) 16 | private static let pink = UIColor(hex: 0xFF3BD0) 17 | 18 | static var allColors: [UIColor] { 19 | return [red, redOrange, orange, yellow, lime, mint, sky, blue, darkBlue, purple, velvet, pink] 20 | } 21 | 22 | } 23 | 24 | extension UIColor { 25 | convenience init(hex: Int, alpha: CGFloat = 1) { 26 | let r = CGFloat((hex & 0xFF0000) >> 16) / 255 27 | let g = CGFloat((hex & 0xFF00) >> 8) / 255 28 | let b = CGFloat((hex & 0xFF)) / 255 29 | self.init(red: r, green: g, blue: b, alpha: alpha) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Sources/SpotlightNode.swift: -------------------------------------------------------------------------------- 1 | import SpriteKit 2 | 3 | enum SpotlightPath { 4 | case right 5 | case left 6 | } 7 | 8 | class SpotlightNode: SKSpriteNode { 9 | 10 | public func move(path: SpotlightPath) { 11 | var bezierPath: UIBezierPath 12 | switch path { 13 | case .left: 14 | bezierPath = UIBezierPath(ovalIn: CGRect(x: -100, y: 80, width: 500, height: 200)) 15 | case .right: 16 | bezierPath = UIBezierPath(ovalIn: CGRect(x: -600, y: -80, width: 500, height: 200)) 17 | bezierPath = bezierPath.reversing() 18 | } 19 | let moveAction = SKAction.follow(bezierPath.cgPath, asOffset: false, orientToPath: false, duration: 6) 20 | moveAction.timingMode = path == .left ? .easeInEaseOut : .linear 21 | run(SKAction.repeatForever(moveAction)) 22 | } 23 | 24 | public var isColorful = false { 25 | didSet { 26 | guard oldValue != isColorful else { return } 27 | if isColorful { 28 | let colorizeAction = SKAction.sequence(Rainbow.allColors.map { SKAction.colorize(with: $0, colorBlendFactor: 1, duration: 0.5) }) 29 | run(SKAction.repeatForever(colorizeAction), withKey: colorActionKey) 30 | } else { 31 | removeAction(forKey: colorActionKey) 32 | color = .white 33 | } 34 | } 35 | } 36 | 37 | private let colorActionKey = "colorActionKey" 38 | 39 | init() { 40 | super.init(texture: SKTexture(imageNamed: "circle"), color: .white, size: CGSize(width: 200, height: 200)) 41 | colorBlendFactor = 1 42 | alpha = 0.4 43 | pulse() 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | super.init(coder: aDecoder) 48 | } 49 | 50 | private func pulse() { 51 | let fadeInAction = SKAction.fadeAlpha(to: 0.6, duration: 0.5) 52 | let fadeOutAction = SKAction.fadeAlpha(to: 0.4, duration: 0.5) 53 | let fadeAction = SKAction.sequence([fadeInAction, fadeOutAction]) 54 | run(SKAction.repeatForever(fadeAction)) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/Sources/ToneNode.swift: -------------------------------------------------------------------------------- 1 | import SpriteKit 2 | 3 | class ToneNode: SKSpriteNode { 4 | 5 | // MARK: - Pubilc API 6 | 7 | /// Whether the cell is currently alive. 8 | /// Updates the visual appearance of the node. 9 | public var isOn = false { 10 | didSet { 11 | spriteNode.alpha = isOn ? onAlpha : offAlpha 12 | } 13 | } 14 | 15 | /// The row of the cell. This is used to know which note to play. 16 | public var row: Int = 0 17 | 18 | /// Displays an animation alongside the tone playing. 19 | /// The actual sound effect is not handled in the `ToneNode` for performance reasons. 20 | public func play() { 21 | bounce() 22 | pulse() 23 | } 24 | 25 | /// Kills the cell with an animation. 26 | public func die() { 27 | isOn = false 28 | let extraNode = SKSpriteNode(texture: spriteNode.texture, color: spriteNode.color, size: spriteNode.size) 29 | extraNode.name = "extraNode" 30 | extraNode.colorBlendFactor = 1 31 | extraNode.alpha = onAlpha 32 | addChild(extraNode) 33 | let shrinkAction = SKAction.scale(to: 0, duration: 0.5) 34 | shrinkAction.timingMode = .easeInEaseOut 35 | let fadeAction = SKAction.fadeAlpha(to: 0, duration: 0.5) 36 | fadeAction.timingMode = .easeInEaseOut 37 | extraNode.run(SKAction.group([shrinkAction, fadeAction])) { 38 | extraNode.removeFromParent() 39 | } 40 | } 41 | 42 | /// Revives the cell with an animation. 43 | public func birth() { 44 | isOn = true 45 | spriteNode.alpha = offAlpha 46 | let extraNode = SKSpriteNode(texture: spriteNode.texture, color: spriteNode.color, size: spriteNode.size) 47 | extraNode.name = "extraNode" 48 | extraNode.colorBlendFactor = 1 49 | addChild(extraNode) 50 | extraNode.run(SKAction.scale(to: 0, duration: 0)) 51 | let growAction = SKAction.scale(to: 1, duration: 0.5) 52 | growAction.timingMode = .easeInEaseOut 53 | let fadeAction = SKAction.fadeAlpha(to: onAlpha, duration: 0.5) 54 | fadeAction.timingMode = .easeInEaseOut 55 | extraNode.run(SKAction.group([growAction, fadeAction])) { 56 | extraNode.removeFromParent() 57 | self.spriteNode.alpha = self.onAlpha 58 | } 59 | } 60 | 61 | /// Resets the node. Useful to cancel any in-progress animations. 62 | public func reset() { 63 | childNode(withName: "extraNode")?.removeFromParent() 64 | } 65 | 66 | /// Updates the color of the node. 67 | public func updateColor(to color: UIColor) { 68 | spriteNode.color = color 69 | } 70 | 71 | /// `true` is the tone node is actually a cat! 😺 72 | public var isCat = false { 73 | didSet { 74 | spriteNode.texture = isCat ? SKTexture(imageNamed: "cat") : SKTexture(imageNamed: "tone") 75 | } 76 | } 77 | 78 | // MARK: - Constants 79 | 80 | private let onAlpha: CGFloat = 0.9 81 | private let offAlpha: CGFloat = 0.2 82 | 83 | // MARK: - Initialization 84 | 85 | private var spriteNode = SKSpriteNode(texture: SKTexture(imageNamed: "tone"), color: .white, size: CGSize(width: 32, height: 32)) 86 | 87 | init() { 88 | super.init(texture: nil, color: .clear, size: CGSize(width: 32, height: 32)) 89 | spriteNode.colorBlendFactor = 1 90 | spriteNode.alpha = offAlpha 91 | addChild(spriteNode) 92 | addChild(pulseNode) 93 | pulseNode.alpha = 0 94 | } 95 | 96 | required init?(coder aDecoder: NSCoder) { 97 | super.init(coder: aDecoder) 98 | } 99 | 100 | // MARK: - Animation 101 | 102 | private func bounce() { 103 | let scaleUpAction = SKAction.scale(to: 1.2, duration: 0.05) 104 | let scaleDownAction = SKAction.scale(to: 1, duration: 0.25) 105 | scaleDownAction.timingMode = SKActionTimingMode.easeOut 106 | let bounceAction = SKAction.sequence([scaleUpAction, scaleDownAction]) 107 | spriteNode.run(bounceAction) 108 | } 109 | 110 | private var pulseNode = SKSpriteNode(texture: SKTexture(imageNamed: "pulse"), color: .white, size: CGSize(width: 32, height: 32)) 111 | 112 | private func pulse() { 113 | pulseNode.run(SKAction.scale(to: 1, duration: 0)) 114 | pulseNode.run(SKAction.fadeAlpha(to: 1, duration: 0)) 115 | let growAction = SKAction.scale(to: 2, duration: 0.5) 116 | growAction.timingMode = .easeInEaseOut 117 | let fadeAction = SKAction.fadeAlpha(to: 0, duration: 0.5) 118 | fadeAction.timingMode = .easeInEaseOut 119 | pulseNode.run(SKAction.group([growAction, fadeAction])) 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /PentatonicGameOfLife.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pentatonic game of life. Interactive music generation based on Conway's game of life. 2 | 3 | Screenshots of the playground 4 | 5 | ## How it Works 6 | The playground is a cross between a pentatonic tone matrix and Conway’s game of life. It’s an interactive music generation simulation. 7 | 8 | Conway’s game of life is a cellular automaton that follows a few simple rules. Based on the number of cells surrounding a given cell, it comes to life, dies, or stays the same. A pentatonic tone matrix is tool to create melodies. Each row corresponds to a note on the pentatonic scale. 9 | 10 | How it works: Images showing the rules of conway's game of life and the notes in a pentatonic scale. 11 | 12 | The notes in the tone matrix are played left-to-rght. After each pass, the notes change according to the rules above. This creates musical patterns that change and evolve over time. 13 | 14 | ## Addtional Features 15 | The user can click any cell to toggle its state between alive and dead. They can also select from five presets, which populate the cells with patterns. 16 | 17 | For fun, there are three special effects: "colors", "meows", and "party", which add a rainbow effect, replace images with cats and sounds with meows, and add party animations, respectively. 18 | 19 | ## Context 20 | This playground was created for the WWDC18 scholarship program, which offers tickets to Apple's annual developer conference in San Jose, CA. Applicants submit an interactive Swift playground and written essays which are reviewed by Apple. I was fortunate to be selected as a winner! 21 | 22 | More information is available here: https://developer.apple.com/wwdc/scholarships/ 23 | 24 | ## Installation Instructions 25 | 1. Download/clone this repo. 26 | 2. Open the `PentatonicGameOfLife.playground` file with Xcode 9.3. 27 | 3. Follow the instructions in the playground to run. 28 | 29 | To modify the playground, I reccomend opening the `ConwayMatrixContainerProject.xcodeproj` file. This project contains a reference to the playground, and allows auto-complete and syntax highlighting for Swift files in the playground's `Sources` folder. 30 | 31 | ## Liscence 32 | Code is under an MIT liscence. Images and sounds are © 2018 Nathan Gitter and may not be reused in any way. 33 | -------------------------------------------------------------------------------- /Resources/howitworks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/Resources/howitworks.png -------------------------------------------------------------------------------- /Resources/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/Resources/screenshots.png -------------------------------------------------------------------------------- /Resources/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathangitter/PentatonicGameOfLife/fded143773751a5f1f9186bf744e48cde796d5c6/Resources/title.png --------------------------------------------------------------------------------