commit f844d5466fb4a0b082d72f12b4837e8045b885e5 Author: Shadowfacts Date: Tue Oct 15 12:24:58 2019 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc6431b --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +.DS_Store +MyPlayground.playground/ + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +.build/ + +# CocoaPods - Refactored to standalone file + +# Carthage - Refactored to standalone file + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output + +### Xcode ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated + +## Various settings + +## Other + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno diff --git a/Tetris WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tetris WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..6c0f2b4 --- /dev/null +++ b/Tetris WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,81 @@ +{ + "images" : [ + { + "size" : "24x24", + "idiom" : "watch", + "scale" : "2x", + "role" : "notificationCenter", + "subtype" : "38mm" + }, + { + "size" : "27.5x27.5", + "idiom" : "watch", + "scale" : "2x", + "role" : "notificationCenter", + "subtype" : "42mm" + }, + { + "size" : "29x29", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "watch", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "38mm" + }, + { + "size" : "44x44", + "idiom" : "watch", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "40mm" + }, + { + "size" : "50x50", + "idiom" : "watch", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "44mm" + }, + { + "size" : "86x86", + "idiom" : "watch", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "38mm" + }, + { + "size" : "98x98", + "idiom" : "watch", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "42mm" + }, + { + "size" : "108x108", + "idiom" : "watch", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "44mm" + }, + { + "idiom" : "watch-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit App/Assets.xcassets/Contents.json b/Tetris WatchKit App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Tetris WatchKit App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit App/Base.lproj/Interface.storyboard b/Tetris WatchKit App/Base.lproj/Interface.storyboard new file mode 100644 index 0000000..1fa6c00 --- /dev/null +++ b/Tetris WatchKit App/Base.lproj/Interface.storyboard @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/Tetris WatchKit App/Info.plist b/Tetris WatchKit App/Info.plist new file mode 100644 index 0000000..31701e0 --- /dev/null +++ b/Tetris WatchKit App/Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Tetris WatchKit App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + WKCompanionAppBundleIdentifier + net.shadowfacts.Tetris + WKWatchKitApp + + + diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json new file mode 100644 index 0000000..1571c7e --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json @@ -0,0 +1,48 @@ +{ + "assets" : [ + { + "idiom" : "watch", + "filename" : "Circular.imageset", + "role" : "circular" + }, + { + "idiom" : "watch", + "filename" : "Extra Large.imageset", + "role" : "extra-large" + }, + { + "idiom" : "watch", + "filename" : "Graphic Bezel.imageset", + "role" : "graphic-bezel" + }, + { + "idiom" : "watch", + "filename" : "Graphic Circular.imageset", + "role" : "graphic-circular" + }, + { + "idiom" : "watch", + "filename" : "Graphic Corner.imageset", + "role" : "graphic-corner" + }, + { + "idiom" : "watch", + "filename" : "Graphic Large Rectangular.imageset", + "role" : "graphic-large-rectangular" + }, + { + "idiom" : "watch", + "filename" : "Modular.imageset", + "role" : "modular" + }, + { + "idiom" : "watch", + "filename" : "Utilitarian.imageset", + "role" : "utilitarian" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json new file mode 100644 index 0000000..aefef29 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : "<=145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/Assets.xcassets/Contents.json b/Tetris WatchKit Extension/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Tetris WatchKit Extension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris WatchKit Extension/ContentView.swift b/Tetris WatchKit Extension/ContentView.swift new file mode 100644 index 0000000..4e7cc8f --- /dev/null +++ b/Tetris WatchKit Extension/ContentView.swift @@ -0,0 +1,21 @@ +// +// ContentView.swift +// Tetris WatchKit Extension +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + Text("Hello, World!") + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Tetris WatchKit Extension/ExtensionDelegate.swift b/Tetris WatchKit Extension/ExtensionDelegate.swift new file mode 100644 index 0000000..ef8664b --- /dev/null +++ b/Tetris WatchKit Extension/ExtensionDelegate.swift @@ -0,0 +1,56 @@ +// +// ExtensionDelegate.swift +// Tetris WatchKit Extension +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import WatchKit + +class ExtensionDelegate: NSObject, WKExtensionDelegate { + + func applicationDidFinishLaunching() { + // Perform any final initialization of your application. + } + + func applicationDidBecomeActive() { + // 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. + } + + func applicationWillResignActive() { + // 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. + // Use this method to pause ongoing tasks, disable timers, etc. + } + + func handle(_ backgroundTasks: Set) { + // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one. + for task in backgroundTasks { + // Use a switch statement to check the task type + switch task { + case let backgroundTask as WKApplicationRefreshBackgroundTask: + // Be sure to complete the background task once you’re done. + backgroundTask.setTaskCompletedWithSnapshot(false) + case let snapshotTask as WKSnapshotRefreshBackgroundTask: + // Snapshot tasks have a unique completion call, make sure to set your expiration date + snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil) + case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask: + // Be sure to complete the connectivity task once you’re done. + connectivityTask.setTaskCompletedWithSnapshot(false) + case let urlSessionTask as WKURLSessionRefreshBackgroundTask: + // Be sure to complete the URL session task once you’re done. + urlSessionTask.setTaskCompletedWithSnapshot(false) + case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask: + // Be sure to complete the relevant-shortcut task once you're done. + relevantShortcutTask.setTaskCompletedWithSnapshot(false) + case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask: + // Be sure to complete the intent-did-run task once you're done. + intentDidRunTask.setTaskCompletedWithSnapshot(false) + default: + // make sure to complete unhandled task types + task.setTaskCompletedWithSnapshot(false) + } + } + } + +} diff --git a/Tetris WatchKit Extension/HostingController.swift b/Tetris WatchKit Extension/HostingController.swift new file mode 100644 index 0000000..7af3a0f --- /dev/null +++ b/Tetris WatchKit Extension/HostingController.swift @@ -0,0 +1,17 @@ +// +// HostingController.swift +// Tetris WatchKit Extension +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import WatchKit +import Foundation +import SwiftUI + +class HostingController: WKHostingController { + override var body: ContentView { + return ContentView() + } +} diff --git a/Tetris WatchKit Extension/Info.plist b/Tetris WatchKit Extension/Info.plist new file mode 100644 index 0000000..3adcb42 --- /dev/null +++ b/Tetris WatchKit Extension/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Tetris WatchKit Extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + WKAppBundleIdentifier + net.shadowfacts.Tetris.test.watchkitapp + + NSExtensionPointIdentifier + com.apple.watchkit + + WKExtensionDelegateClassName + $(PRODUCT_MODULE_NAME).ExtensionDelegate + WKRunsIndependentlyOfCompanionApp + + + diff --git a/Tetris WatchKit Extension/Preview Content/Preview Assets.xcassets/Contents.json b/Tetris WatchKit Extension/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Tetris WatchKit Extension/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris/AppDelegate.swift b/Tetris/AppDelegate.swift new file mode 100644 index 0000000..0e50d2f --- /dev/null +++ b/Tetris/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// Tetris +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Tetris/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tetris/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/Tetris/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris/Assets.xcassets/Contents.json b/Tetris/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Tetris/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris/Base.lproj/LaunchScreen.storyboard b/Tetris/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Tetris/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tetris/ContentView.swift b/Tetris/ContentView.swift new file mode 100644 index 0000000..386101e --- /dev/null +++ b/Tetris/ContentView.swift @@ -0,0 +1,129 @@ +// +// ContentView.swift +// Tetris +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import SwiftUI +import TetrisKit +import TetrisUI + +struct ContentView: View { + @ObservedObject var controller: GameController = { + let c = GameController() + c.start() + return c + }() + @State var timer: Timer? + @State var prevHorizTranslation: CGFloat? + + var body: some View { + GeometryReader { (geometry) in + VStack { + BoardView(board: self.$controller.board, currentPiece: self.$controller.currentPiece, droppedPiece: self.$controller.currentPieceAtDropPoint) + .aspectRatio(CGSize(width: 10, height: 16), contentMode: .fit) + .onAppear(perform: self.startTimer) + .onDisappear(perform: self.stopTimer) + .onTapGesture(perform: self.onTap) + .gesture(self.horizDragGesture(geometry: geometry)) + // .gesture(ExclusiveGesture(horizDragGesture, verticalDragGesture)) + // .gesture(horizDragGesture.simultaneously(with: verticalDragGesture)) + + HStack { + Button(action: self.onTap) { + Image(systemName: "goforward").resizable().frame(width: 50, height: 50) + } + } + HStack { + Button(action: self.controller.left) { + Image(systemName: "arrow.left.square.fill").resizable().frame(width: 50, height: 50) + } + Spacer() + Button(action: self.controller.drop) { + Image(systemName: "arrow.down.square.fill").resizable().frame(width: 50, height: 50) + } + Spacer() + Button(action: self.controller.right) { + Image(systemName: "arrow.right.square.fill").resizable().frame(width: 50, height: 50) + } + } + } + } + } + + func horizDragGesture(geometry: GeometryProxy) -> some Gesture { + DragGesture(coordinateSpace: .global) + .onChanged { (state) in + guard let currentPiece = self.controller.currentPiece else { return } +// let position = (state.location.x) / geometry.size.width * 10 +// let moved = currentPiece.moved(by: (Int(position.rounded()) - currentPiece.topLeft.0, 0)) +// if !self.controller.overlapsAny(moved) { +// self.controller.currentPiece = moved +// } + if self.prevHorizTranslation == nil { + self.prevHorizTranslation = state.translation.width + } + let delta = state.translation.width - self.prevHorizTranslation! + let amount = Int((delta / 20).rounded()) + let moved = currentPiece.moved(by: (amount, 0)) + if !self.controller.overlapsAny(moved) { + self.controller.currentPiece = moved + self.prevHorizTranslation = state.translation.width + } + }.onEnded { (state) in + self.prevHorizTranslation = nil + } + } + + var verticalDragGesture: some Gesture { + DragGesture() + .onEnded { (state) in + let deltaY = state.location.y - state.startLocation.y + if abs(deltaY) > 40 { + if deltaY > 0 { + self.onSwipeDown() + } else { + self.onSwipeUp() + } + } + } + } + + func startTimer() { + self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { (_) in + self.controller.step() + } + } + + func stopTimer() { + timer?.invalidate() + } + + func onTap() { + self.controller.rotate(direction: .clockwise) + } + + func onSwipeLeft() { + self.controller.left() + } + + func onSwipeRight() { + self.controller.right() + } + + func onSwipeUp() { + // hold + } + + func onSwipeDown() { + self.controller.drop() + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Tetris/Info.plist b/Tetris/Info.plist new file mode 100644 index 0000000..9742bf0 --- /dev/null +++ b/Tetris/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Tetris/Preview Content/Preview Assets.xcassets/Contents.json b/Tetris/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Tetris/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tetris/SceneDelegate.swift b/Tetris/SceneDelegate.swift new file mode 100644 index 0000000..c18804e --- /dev/null +++ b/Tetris/SceneDelegate.swift @@ -0,0 +1,64 @@ +// +// SceneDelegate.swift +// Tetris +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit +import SwiftUI + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + + // Create the SwiftUI view that provides the window contents. + let contentView = ContentView() + + // Use a UIHostingController as window root view controller. + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/TetrisKit/Array+Rotation.swift b/TetrisKit/Array+Rotation.swift new file mode 100644 index 0000000..8da4d3b --- /dev/null +++ b/TetrisKit/Array+Rotation.swift @@ -0,0 +1,61 @@ +// +// Array+Rotation.swift +// TetrisKit +// +// Created by Shadowfacts on 10/13/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation + +extension Array where Element: RandomAccessCollection, Element: MutableCollection, Element.Index == Int { + mutating func rotateClockwise() { + guard allSatisfy({ $0.count == self.count }) else { return } + + let layerCount = self.count / 2 + + for layer in 0.. Bool { + return self.tiles[y][x] + } + + mutating func set(piece: GamePiece) { + let (left, top) = piece.topLeft + + for y in 0..= 0 { + if board.tiles[row] == fullRow { + board.tiles.remove(at: row) + } + row -= 1 + } + for _ in 0.. Bool { + let (left, top) = piece.topLeft + for y in 0..= height || left + x < 0 || left + x >= width || board[left + x, top + y] { + return true + } + } + } + return false + } + +} diff --git a/TetrisKit/GamePiece.swift b/TetrisKit/GamePiece.swift new file mode 100644 index 0000000..45f287d --- /dev/null +++ b/TetrisKit/GamePiece.swift @@ -0,0 +1,47 @@ +// +// GamePiece.swift +// TetrisKit +// +// Created by Shadowfacts on 10/13/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation + +public struct GamePiece { + public let tetromino: Tetromino + public internal(set) var topLeft: (Int, Int) + public internal(set) var tiles: [[Bool]] + + public init(tetromino: Tetromino) { + self.tetromino = tetromino + self.tiles = tetromino.shape + self.topLeft = (0, 0) + } + + mutating func rotate(direction: RotationDirection) { + switch direction { + case .clockwise: + self.tiles.rotateClockwise() + case .counterClockwise: + self.tiles.rotateCounterclockwise() + } + } +} + +public enum RotationDirection { + case clockwise, counterClockwise +} + +extension GamePiece { + public func moved(by: (Int, Int)) -> GamePiece { + var modified = self + modified.topLeft = (topLeft.0 + by.0, topLeft.1 + by.1) + return modified + } + public func rotated(_ direction: RotationDirection) -> GamePiece { + var modified = self + modified.rotate(direction: direction) + return modified + } +} diff --git a/TetrisKit/Info.plist b/TetrisKit/Info.plist new file mode 100644 index 0000000..9bcb244 --- /dev/null +++ b/TetrisKit/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/TetrisKit/TetrisKit.h b/TetrisKit/TetrisKit.h new file mode 100644 index 0000000..11f7f09 --- /dev/null +++ b/TetrisKit/TetrisKit.h @@ -0,0 +1,19 @@ +// +// TetrisKit.h +// TetrisKit iOS +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +#import + +//! Project version number for TetrisKit. +FOUNDATION_EXPORT double TetrisKitVersionNumber; + +//! Project version string for TetrisKit. +FOUNDATION_EXPORT const unsigned char TetrisKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/TetrisKit/Tetromino.swift b/TetrisKit/Tetromino.swift new file mode 100644 index 0000000..d2e3810 --- /dev/null +++ b/TetrisKit/Tetromino.swift @@ -0,0 +1,68 @@ +// +// Tetromino.swift +// TetrisKit +// +// Created by Shadowfacts on 10/13/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation + +public enum Tetromino: CaseIterable { + case i, o, t, j, l, s, z + + public var shape: [[Bool]] { + return Tetromino.shapes[self]! + } +} + +extension Tetromino { + static var shapes: [Tetromino: [[Bool]]] = [ + .i: parseShape(""" +XXXX +---- +---- +---- +"""), + .o: parseShape(""" +XX +XX +"""), + .t: parseShape(""" +XXX +-X- +--- +"""), + .j: parseShape(""" +XXX +X-- +--- +"""), + .l: parseShape(""" +XXX +--X +--- +"""), + .s: parseShape(""" +XX- +-XX +--- +"""), + .z: parseShape(""" +-XX +XX- +--- +""") + ] + + static func parseShape(_ str: String) -> [[Bool]] { + let lines = str.split(separator: "\n") + return lines.map { line in line.map { char in char == "X" } } + } +} + +extension Tetromino { + static func random() -> Tetromino { + return allCases[Int.random(in: 0.., currentPiece: Binding, droppedPiece: Binding) { + self._board = board + self._currentPiece = currentPiece + self._droppedPiece = droppedPiece + } + + public var body: some View { + ZStack { + TilesView(board: $board) + + CurrentPieceView(boardWidth: board.width, boardHeight: board.height, currentPiece: $currentPiece, droppedPiece: $droppedPiece) + } + } +} + +struct BoardView_Previews: PreviewProvider { + @ObservedObject static var controller: GameController = { + var c = GameController() + c.currentPiece = GamePiece(tetromino: .t) + return c + }() + + static var previews: some View { + BoardView(board: $controller.board, currentPiece: $controller.currentPiece, droppedPiece: $controller.currentPieceAtDropPoint) + } +} diff --git a/TetrisUI/CurrentPieceView.swift b/TetrisUI/CurrentPieceView.swift new file mode 100644 index 0000000..24d8571 --- /dev/null +++ b/TetrisUI/CurrentPieceView.swift @@ -0,0 +1,53 @@ +// +// CurrentPieceView.swift +// Tetris +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import SwiftUI +import TetrisKit + +struct CurrentPieceView: View { + let boardWidth: Int + let boardHeight: Int + @Binding var currentPiece: GamePiece? + @Binding var droppedPiece: GamePiece? + + var body: some View { + GridView(rows: self.boardHeight, columns: self.boardWidth) { (col, row, size) in + Rectangle() + .foregroundColor(self.currentPieceAt(col, row) ? Color.blue : self.droppedPieceAt(col, row) ? Color.gray : Color.clear) + .frame(width: size, height: size) + } + } + + func currentPieceAt(_ col: Int, _ row: Int) -> Bool { + guard let currentPiece = self.currentPiece else { return false } + let (left, top) = currentPiece.topLeft + let pieceHeight = currentPiece.tiles.count + let pieceWidth = currentPiece.tiles.first!.count + return col - left >= 0 && col - left < pieceWidth && row - top >= 0 && row - top < pieceHeight && currentPiece.tiles[row - top][col - left] + } + + func droppedPieceAt(_ col: Int, _ row: Int) -> Bool { + guard let droppedPiece = self.droppedPiece else { return false } + let (left, top) = droppedPiece.topLeft + let pieceHeight = droppedPiece.tiles.count + let pieceWidth = droppedPiece.tiles.first!.count + return col - left >= 0 && col - left < pieceWidth && row - top >= 0 && row - top < pieceHeight && droppedPiece.tiles[row - top][col - left] + } +} + +struct CurrentPieceView_Previews: PreviewProvider { + @State static var currentPiece: GamePiece? = GamePiece(tetromino: .t) + @State static var droppedPiece: GamePiece? = { + var piece = GamePiece(tetromino: .t) + return piece.moved(by: (0, 16 - piece.tiles.count)) + }() + + static var previews: some View { + CurrentPieceView(boardWidth: 10, boardHeight: 16, currentPiece: $currentPiece, droppedPiece: $droppedPiece) + } +} diff --git a/TetrisUI/GridView.swift b/TetrisUI/GridView.swift new file mode 100644 index 0000000..8642c64 --- /dev/null +++ b/TetrisUI/GridView.swift @@ -0,0 +1,48 @@ +// +// GridView.swift +// Tetris +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import SwiftUI + +struct GridView: View where Cell: View { + + let rows: Int + let columns: Int + let cellProvider: (Int, Int, CGFloat) -> Cell + + init(rows: Int, columns: Int, @ViewBuilder cellProvider: @escaping (Int, Int, CGFloat) -> Cell) { + self.rows = rows + self.columns = columns + self.cellProvider = cellProvider + } + + var body: some View { + GeometryReader { (geometry) in + VStack(spacing: 0) { + ForEach(0.. CGFloat { + min(geometry.size.width / CGFloat(columns), geometry.size.height / CGFloat(rows)) + } +} + +struct GridView_Previews: PreviewProvider { + static var previews: some View { + GridView(rows: 3, columns: 3) { (col, row, size) in + Rectangle().frame(width: size, height: size).foregroundColor(Color.red) + } + } +} diff --git a/TetrisUI/Info.plist b/TetrisUI/Info.plist new file mode 100644 index 0000000..9bcb244 --- /dev/null +++ b/TetrisUI/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/TetrisUI/TetrisUI_iOS.h b/TetrisUI/TetrisUI_iOS.h new file mode 100644 index 0000000..bdbebb2 --- /dev/null +++ b/TetrisUI/TetrisUI_iOS.h @@ -0,0 +1,19 @@ +// +// TetrisUI_iOS.h +// TetrisUI iOS +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +#import + +//! Project version number for TetrisUI_iOS. +FOUNDATION_EXPORT double TetrisUI_iOSVersionNumber; + +//! Project version string for TetrisUI_iOS. +FOUNDATION_EXPORT const unsigned char TetrisUI_iOSVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/TetrisUI/TilesView.swift b/TetrisUI/TilesView.swift new file mode 100644 index 0000000..b9ec1aa --- /dev/null +++ b/TetrisUI/TilesView.swift @@ -0,0 +1,34 @@ +// +// TilesView.swift +// Tetris +// +// Created by Shadowfacts on 10/14/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import SwiftUI +import TetrisKit + +struct TilesView: View { + @Binding var board: GameBoard + + var body: some View { + GridView(rows: board.height, columns: board.width) { (col, row, size) in + Rectangle() + .frame(width: size, height: size) + .foregroundColor(self.board[col, row] ? Color.red : Color.black) + } + } +} + +struct TilesView_Previews: PreviewProvider { + @State static var board: GameBoard = { + var b = GameBoard(width: 10, height: 16) + b.set(tiles: [(0, 15), (1, 15), (1, 14), (2, 14)]) + return b + }() + + static var previews: some View { + TilesView(board: $board) + } +} diff --git a/tetriscli/main.swift b/tetriscli/main.swift new file mode 100644 index 0000000..25370d8 --- /dev/null +++ b/tetriscli/main.swift @@ -0,0 +1,131 @@ +// +// main.swift +// tetriscli +// +// Created by Shadowfacts on 10/13/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation + +class TwoDimString: CustomStringConvertible { + var width: Int + var height: Int + var rows: [String] + + var description: String { + return rows.joined(separator: "\n") + } + + init(width: Int, height: Int) { + self.width = width + self.height = height + self.rows = [] + + let empty = String(repeating: " ", count: width) + for _ in 1...height { + rows.append(empty) + } + } + + subscript(column: Int, row: Int) -> Character { + get { + guard column >= 0 && column < width, + row >= 0 && row < height else { fatalError("Out of bounds position \(column), \(row) for TwoDimString(width: \(width), height: \(height))") } + let rowStr = rows[row] + return rowStr[rowStr.index(rowStr.startIndex, offsetBy: column)] + } + set { + guard column >= 0 && column < width, + row >= 0 && row < height else { fatalError("Out of bounds position \(column), \(row) for TwoDimString(width: \(width), height: \(height))") } + let rowStr = rows[row] + + rows[row] = String(rowStr.prefix(column)) + String(newValue) + String(rowStr.dropFirst(column + 1)) + } + } + + func setRow(_ row: Int, to str: String) { + guard row >= 0 && row < height else { fatalError("Can't set row \(row) for TwoDimString(width: \(width), height: \(height))") } + guard str.count == width else { fatalError("Can't set row with count \(str.count) for TwoDimString(width: \(width), height: \(height))") } + + rows[row] = str + } + + func setColumn(_ col: Int, to str: String) { + guard col >= 0 && col < width else { fatalError("Can't set column \(col) for TwoDimString(width: \(width), height: \(height))") } + guard str.count == height else { fatalError("Can't set row with count \(str.count) for TwoDimString(width: \(width), height: \(height))") } + + for row in 0..= 0 && column + string.width <= width, + row >= 0 && row + string.height <= height else { fatalError("Can't set TwoDimString(width: \(string.width), height: \(string.height)) at (\(column), \(row))") } + + for subCol in 0..= 0 && y + top + 1 < height { + for x in 0..= 0 && x + left + 1 < width { + self[x + left + 1, y + top + 1] = currentPiece.tiles[y][x] ? "X" : "-" + } + } + } + } +} + +let controller = GameController() +controller.currentPiece = GamePiece(tetromino: .l) + +func readMove() { + print(TwoDimString(controller: controller)) + print("Move: ", terminator: "") + let input = readLine()!.trimmingCharacters(in: .whitespacesAndNewlines) + + switch input { + case "", "s", "step": + controller.step() + case "cw": + controller.rotate(direction: .clockwise) + case "ccw": + controller.rotate(direction: .counterClockwise) + case "l", "left": + controller.left() + case "r", "right": + controller.right() + case "drop": + controller.drop() + case "hold": + break + default: + break + } +} + +while true { + readMove() +}