├── SixFeetBetween_WWDC20SwiftChallenge
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Sound Effect
│ ├── jump.wav
│ ├── clock1.wav
│ ├── coin1.mp3
│ ├── coin2.wav
│ ├── coin3.wav
│ ├── coin4.wav
│ ├── coin5.wav
│ ├── coin6.wav
│ ├── coin7.wav
│ ├── error1.wav
│ ├── error2.wav
│ ├── failure1.wav
│ ├── failure2.wav
│ ├── failure3.wav
│ ├── success1.wav
│ ├── success2.wav
│ ├── success3.wav
│ └── success4.wav
├── Visual Assets
│ ├── factory1.png
│ ├── factory2.png
│ ├── research.png
│ ├── production.png
│ ├── wash_hand.png
│ └── humanw_mask.png
├── WWDC20PlaygroundTest.xcdatamodeld
│ ├── .xccurrentversion
│ └── WWDC20PlaygroundTest.xcdatamodel
│ │ └── contents
├── Views
│ ├── IconView.swift
│ ├── BackgroundView.swift
│ ├── StoryView.swift
│ ├── BriefLogisticExplanationView.swift
│ ├── TutorialView.swift
│ ├── ButtonStackView.swift
│ ├── NPCUI.swift
│ ├── GameFailureView.swift
│ ├── StoryTextIntroView.swift
│ ├── ContentView.swift
│ ├── GameSuccessView.swift
│ ├── InternalTutorialView.swift
│ ├── InternalTutorialPartialView.swift
│ └── PlayerUI.swift
├── Support
│ ├── AnimationPresets.swift
│ ├── MediaSupport.swift
│ └── GameLogistics.swift
├── Data
│ ├── DataModel.swift
│ └── Calculation.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
├── SceneDelegate.swift
└── AppDelegate.swift
├── 6 Feet Between.playgroundbook
└── Contents
│ ├── UserModules
│ └── GameFoundationModule.playgroundmodule
│ │ └── Sources
│ │ ├── SharedCode.swift
│ │ ├── IconView.swift
│ │ ├── AnimationPresets.swift
│ │ ├── DataModel.swift
│ │ ├── BackgroundView.swift
│ │ ├── StoryView.swift
│ │ ├── MediaSupport.swift
│ │ ├── BriefLogisticExplanationView.swift
│ │ ├── GameLogistics.swift
│ │ ├── TutorialView.swift
│ │ ├── ButtonStackView.swift
│ │ ├── NPCUI.swift
│ │ ├── Calculation.swift
│ │ ├── GameFailureView.swift
│ │ ├── StoryTextIntroView.swift
│ │ ├── ContentView.swift
│ │ ├── GameSuccessView.swift
│ │ ├── InternalTutorialView.swift
│ │ ├── InternalTutorialPartialView.swift
│ │ └── PlayerUI.swift
│ ├── PrivateResources
│ ├── jump.wav
│ ├── clock1.wav
│ ├── clock2.wav
│ ├── coin7.wav
│ ├── error1.wav
│ ├── error2.wav
│ ├── factory1.png
│ ├── factory2.png
│ ├── research.png
│ ├── success2.wav
│ ├── wash_hand.png
│ ├── 6FeetBetween.png
│ ├── Ninja_Circle.png
│ ├── humanw:mask.png
│ └── production.png
│ ├── Chapters
│ └── 6FeetBetween.playgroundchapter
│ │ ├── Pages
│ │ ├── Go Game.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ ├── Template.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ ├── Get Some Training.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ └── The Story Behind.playgroundpage
│ │ │ ├── Manifest.plist
│ │ │ └── main.swift
│ │ └── Manifest.plist
│ └── Manifest.plist
├── SixFeetBetween_WWDC20SwiftChallenge.xcodeproj
├── xcuserdata
│ └── tony.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── LICENSE
└── README.md
/SixFeetBetween_WWDC20SwiftChallenge/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/SharedCode.swift:
--------------------------------------------------------------------------------
1 | // Code inside modules can be shared between pages and other source files.
2 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/jump.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/jump.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/clock1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/clock1.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin1.mp3
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin2.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin3.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin3.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin4.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin4.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin5.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin5.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin6.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin6.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin7.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin7.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error1.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error2.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure1.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure2.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure3.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure3.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success1.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success2.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success3.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success3.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success4.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success4.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory1.png
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory2.png
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/research.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/research.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/jump.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/jump.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/production.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/production.png
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/wash_hand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/wash_hand.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/clock1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/clock1.wav
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/clock2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/clock2.wav
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/coin7.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/coin7.wav
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/error1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/error1.wav
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/error2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/error2.wav
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/humanw_mask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/humanw_mask.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/factory1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/factory1.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/factory2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/factory2.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/research.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/research.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/success2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/success2.wav
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/wash_hand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/wash_hand.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/6FeetBetween.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/6FeetBetween.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/Ninja_Circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/Ninja_Circle.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/humanw:mask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/humanw:mask.png
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/PrivateResources/production.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/production.png
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/xcuserdata/tony.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/WWDC20PlaygroundTest.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | WWDC20PlaygroundTest.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/WWDC20PlaygroundTest.xcdatamodeld/WWDC20PlaygroundTest.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Go Game.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Go Game
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | HiddenByDefault
11 |
12 |
13 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Template.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Template Page
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | HiddenByDefault
11 |
12 |
13 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Get Some Training.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | Get Some Training
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | HiddenByDefault
11 |
12 |
13 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/The Story Behind.playgroundpage/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | The Story Behind
7 | LiveViewEdgeToEdge
8 |
9 | LiveViewMode
10 | HiddenByDefault
11 |
12 |
13 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/xcuserdata/tony.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | WWDC20PlaygroundTest.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Name
6 | 6FeetBetween
7 | TemplatePageFilename
8 | Template.playgroundpage
9 | InitialUserPages
10 |
11 | The Story Behind.playgroundpage
12 | Get Some Training.playgroundpage
13 | Go Game.playgroundpage
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/IconView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IconView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct FactoryView: View {
12 | var body: some View {
13 | Image(uiImage: UIImage(named: "factory1")!)
14 | .resizable()
15 | .renderingMode(.template)
16 | .foregroundColor(Color(UIColor.label))
17 | }
18 | }
19 |
20 | struct LabView: View {
21 | var body: some View {
22 | Image(uiImage: UIImage(named: "research")!)
23 | .resizable()
24 | .renderingMode(.template)
25 | .foregroundColor(Color(UIColor.label))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/IconView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IconView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct FactoryView: View {
12 | var body: some View {
13 | Image(uiImage: UIImage(named: "factory1")!)
14 | .resizable()
15 | .renderingMode(.template)
16 | .foregroundColor(Color(UIColor.label))
17 | }
18 | }
19 |
20 | struct LabView: View {
21 | var body: some View {
22 | Image(uiImage: UIImage(named: "research")!)
23 | .resizable()
24 | .renderingMode(.template)
25 | .foregroundColor(Color(UIColor.label))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Support/AnimationPresets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimationPresets.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | public struct Shake: GeometryEffect {
13 | var amount: CGFloat = 8
14 | var shakesPerUnit: CGFloat = 5
15 |
16 | public var animatableData: CGFloat
17 |
18 | public func effectValue(size: CGSize) -> ProjectionTransform {
19 | ProjectionTransform(
20 | CGAffineTransform(translationX: amount * sin(animatableData * .pi * shakesPerUnit), y: 0)
21 | )
22 | }
23 | }
24 |
25 | public extension Animation {
26 |
27 | static let playerMove = Animation.spring(response: 0.3, dampingFraction: 0.75, blendDuration: 0)
28 |
29 | static let npcTransition = Animation.easeInOut.speed(0.6)
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/The Story Behind.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # 6 Feet Between - The Story Behind
3 |
4 | ## Welcome to 6 Feet Between!
5 | This is a 🎮 of future. On this page, you will get to knwo about the story behind the 🎮 and your duty as part of it.
6 |
7 | Click the **Run** button located on the lower right of your screen to read the animated story.
8 |
9 | - - -
10 |
11 | - Important:
12 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button.
13 |
14 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time.
15 | */
16 |
17 | //#-hidden-code
18 | // The Story Behind Page
19 | import PlaygroundSupport
20 | PlaygroundPage.current.setLiveView(StoryView())
21 | //#-end-hidden-code
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/AnimationPresets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimationPresets.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | public struct Shake: GeometryEffect {
13 | var amount: CGFloat = 8
14 | var shakesPerUnit: CGFloat = 5
15 |
16 | public var animatableData: CGFloat
17 |
18 | public func effectValue(size: CGSize) -> ProjectionTransform {
19 | ProjectionTransform(
20 | CGAffineTransform(translationX: amount * sin(animatableData * .pi * shakesPerUnit), y: 0)
21 | )
22 | }
23 | }
24 |
25 | public extension Animation {
26 |
27 | static let playerMove = Animation.spring(response: 0.3, dampingFraction: 0.75, blendDuration: 0)
28 |
29 | static let npcTransition = Animation.easeInOut.speed(0.6)
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Manifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Description
6 | A game to educate people about keeping social distance.
7 | ImageReference
8 | 6FeetBetween.png
9 | Chapters
10 |
11 | 6FeetBetween.playgroundchapter
12 |
13 | ContentIdentifier
14 | com.tonytangzixuan.playgroundbook.sixFeetBetween
15 | ContentVersion
16 | 1.0
17 | DeploymentTarget
18 | ios-current
19 | DevelopmentRegion
20 | en
21 | SwiftVersion
22 | 5.1
23 | Version
24 | 7.0
25 | UserAutoImportedAuxiliaryModules
26 |
27 | UserModuleMode
28 | Full
29 |
30 |
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 TonyTang
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 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Get Some Training.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # 6 Feet Between - Get Some Training
3 |
4 | Now you know the story, please get some training. You will need to know some basic actions to nagivate through this game. This won't take long.
5 |
6 | - - -
7 |
8 | - callout(Anonymous Man):
9 | Hi there, be sure to practice some basic actions before you start your 🎮!
10 | You know... Practice makes perfect!
11 |
12 |
13 | Here are a few **tips** for you:
14 | * Protect Yourself, be sure to stay 6 feet away from others! Even during your blink! Otherwise you may get infected and lose the game.
15 | * Mind your setps, try **not** to go out of screen. The system won't let you do so.
16 | * If you are unsure about where to blink to, drag and hold to freeze the time for some thinking.
17 |
18 | - Important:
19 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button.
20 |
21 | - Remember to switch on the ringtone and turn volumn to 50% 🔊 for the sound effects!
22 |
23 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time.
24 | */
25 |
26 | //#-hidden-code
27 | // et Some Training
28 | import PlaygroundSupport
29 | PlaygroundPage.current.setLiveView(TutorialView())
30 | //#-end-hidden-code
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Data/DataModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataModel.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/8/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | public struct ScreenCoordinate: Hashable {
13 | public var x: CGFloat
14 | public var y: CGFloat
15 |
16 | public init(x: CGFloat, y: CGFloat) {
17 | self.x = x
18 | self.y = y
19 | }
20 | }
21 |
22 | // MARK: - View Information
23 | public var viewWidth: CGFloat = 0
24 | public var viewHeight: CGFloat = 0
25 |
26 | // MARK: - Game Setting
27 | public let mapIconSize: CGFloat = 50
28 | public let npcCount: Int = 15
29 | public let npcSize: CGFloat = 30
30 | public let playerSize: CGFloat = 35
31 | public let npcWarningRangeSize: CGFloat = npcSize * 1.5
32 | public let safetyDistance: CGFloat = (npcSize*2 + playerSize)/2
33 |
34 | // MARK: - Player Info
35 | public var previousPosition = ScreenCoordinate(x: 0, y: 0)
36 | public var currentPosition: CGSize = .zero
37 | public var newPosition: CGSize = .zero
38 | public var playerColor: Color = Color(UIColor.systemBlue)
39 | public var playerPathColor: Color = Color(UIColor.systemBlue)
40 |
41 | // MARK: - NPC Coordination
42 | public var npcCoords: [[ScreenCoordinate]] = [[]]
43 |
44 | // MARK: - Game Status
45 | public var playerWon: Bool = false
46 | public var started: Bool = false
47 | public var endOnHold: Bool = false
48 | public var startTime: Date = Date()
49 | public var endTime: Date = Date()
50 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/DataModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataModel.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/8/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | public struct ScreenCoordinate: Hashable {
13 | public var x: CGFloat
14 | public var y: CGFloat
15 |
16 | public init(x: CGFloat, y: CGFloat) {
17 | self.x = x
18 | self.y = y
19 | }
20 | }
21 |
22 | // MARK: - View Information
23 | public var viewWidth: CGFloat = 0
24 | public var viewHeight: CGFloat = 0
25 |
26 | // MARK: - Game Setting
27 | public let mapIconSize: CGFloat = 50
28 | public let npcCount: Int = 15
29 | public let npcSize: CGFloat = 30
30 | public let playerSize: CGFloat = 35
31 | public let npcWarningRangeSize: CGFloat = npcSize * 1.5
32 | public let safetyDistance: CGFloat = (npcSize*2 + playerSize)/2
33 |
34 | // MARK: - Player Info
35 | public var previousPosition = ScreenCoordinate(x: 0, y: 0)
36 | public var currentPosition: CGSize = .zero
37 | public var newPosition: CGSize = .zero
38 | public var playerColor: Color = Color(UIColor.systemBlue)
39 | public var playerPathColor: Color = Color(UIColor.systemBlue)
40 |
41 | // MARK: - NPC Coordination
42 | public var npcCoords: [[ScreenCoordinate]] = [[]]
43 |
44 | // MARK: - Game Status
45 | public var playerWon: Bool = false
46 | public var started: Bool = false
47 | public var endOnHold: Bool = false
48 | public var startTime: Date = Date()
49 | public var endTime: Date = Date()
50 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Go Game.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # 6 Feet Between - Go Game!
3 |
4 | After your previous training, I am sure you know what to do by now. You are doing great, and I am fully confident in you! 😃
5 |
6 | - - -
7 |
8 | But before you start your challenge, here are a few **last tips** for you:
9 | * Protect Yourself, be sure to stay 6 feet away from others! Even during your blink!
10 | * Mind your setps, try **not** to go out of screen.
11 | * If you are unsure about where to blink to, drag and hold to freeze the time.
12 | * Restart the game any time using the Swift Playground **Run/Stop** Button.
13 |
14 | - Important:
15 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button.
16 |
17 | - Remember to switch on the ringtone and turn volumn to 50% 🔊 for the sound effects!
18 |
19 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time.
20 |
21 | - Ready, Set, Go! The clock is ticking.
22 | */
23 |
24 | //#-hidden-code
25 | // Go Game Page
26 | import PlaygroundSupport
27 | PlaygroundPage.current.setLiveView(ContentView())
28 | //#-end-hidden-code
29 |
30 | /*:
31 | - Have you noticed the ending words are different each time you replay the game? 😉
32 |
33 | - Do you know you can drag the floating ending(success/failure) card? 🤩
34 |
35 | - This playground support Dark Mode! Now you are a ninja in the dark! 😎
36 |
37 | - Go find some more easter eggs!
38 | */
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/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 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Template.playgroundpage/main.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | # Customization
3 |
4 | Enjoying this game? The current version feels too easy?
5 |
6 | **Try make your own customized settings!**
7 |
8 | - Important:
9 | This modification is only recommended for the ones who have prior coding experience. Carefully follow the instructions below to customize **6 Feet Between** to your favor!
10 |
11 | - - -
12 | # Customizing Instruction
13 | Everyone has their preffered 🎮 modes, may be you want this game to be simpler or harder, or may be you just want to change the color theme of the game to match your preference. You are covered! Just follow these simple steps to build your own version of **6 Feet Between**!
14 |
15 | - All the game setting variables are located in the **DataModel.swift** file inside the **GameFoundationModule**
16 |
17 | - To avoid causing errors, it is recommended to keep a copy of the previous version that works.
18 |
19 | - Should you messed up the codes by chance, just **quite and reset** this playground. The original version work will be recovered.
20 |
21 | - Important:
22 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button.
23 |
24 | - Remember to switch on the ringtone and turn volumn to 50% 🔊 for the sound effects!
25 |
26 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time.
27 | */
28 |
29 | import PlaygroundSupport
30 |
31 | // Do some changes here to your favor!
32 |
33 |
34 | //#-hidden-code
35 | PlaygroundPage.current.setLiveView(ContentView())
36 | //#-end-hidden-code
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/BackgroundView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BackgroundView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct BackgroundView: View {
12 |
13 | @State var animateStart: Bool = false
14 |
15 | @Binding var canvasWidth: CGFloat
16 | @Binding var canvasHeight: CGFloat
17 |
18 | public init(canvasWidth: Binding, canvasHeight: Binding) {
19 | self._canvasWidth = canvasWidth
20 | self._canvasHeight = canvasHeight
21 | }
22 |
23 | var totalShapeCount = 21
24 | let colors = [UIColor.systemRed, UIColor.systemYellow, UIColor.systemBlue, UIColor.systemPink, UIColor.systemTeal, UIColor.systemGray2, UIColor.systemIndigo, UIColor.systemOrange, UIColor.systemGreen, UIColor.systemPink]
25 |
26 |
27 |
28 | public var body: some View {
29 | ZStack {
30 | ForEach(0.., canvasHeight: Binding) {
19 | self._canvasWidth = canvasWidth
20 | self._canvasHeight = canvasHeight
21 | }
22 |
23 | var totalShapeCount = 21
24 | let colors = [UIColor.systemRed, UIColor.systemYellow, UIColor.systemBlue, UIColor.systemPink, UIColor.systemTeal, UIColor.systemGray2, UIColor.systemIndigo, UIColor.systemOrange, UIColor.systemGreen, UIColor.systemPink]
25 |
26 |
27 |
28 | public var body: some View {
29 | ZStack {
30 | ForEach(0..
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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 |
37 |
38 |
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/StoryView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoryView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/16/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct StoryView: View {
12 |
13 | var endingString = "Go to the next page for some training!"
14 |
15 | @State var animate1Start = false
16 | @State var animate1End = false
17 | @State var animate2Start = false
18 | @State var animate2End = false
19 | @State var animate3Start = false
20 | @State var animate3End = false
21 | @State var animate4Start = false
22 | @State var animate4End = false
23 | @State var animate5Start = false
24 |
25 | public init() {}
26 |
27 | public var body: some View {
28 | ZStack {
29 |
30 | StoryTextIntroView(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End)
31 | .opacity(animate3Start ? 0 : 1)
32 | .onAppear {
33 | self.animate1Start = true
34 | DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
35 | self.animate1End = true
36 | }
37 | }
38 |
39 | BriefLogisticExplanationView(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start)
40 | .opacity(animate5Start ? 0 : 1)
41 |
42 | VStack {
43 | Text(endingString)
44 | .fontWeight(.semibold)
45 | .font(.system(.title, design: .rounded))
46 | .multilineTextAlignment(.center)
47 | .opacity(self.animate5Start ? 1 : 0)
48 | .offset(y: self.animate5Start ? 0 : 16)
49 | .animation(Animation.easeInOut(duration: 1))
50 | }
51 | .opacity(animate5Start ? 1 : 0)
52 |
53 | }
54 |
55 | }
56 | }
57 |
58 | struct StoryView_Previews: PreviewProvider {
59 | static var previews: some View {
60 | StoryView()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/StoryView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoryView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/16/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct StoryView: View {
12 |
13 | var endingString = "Go to the next page for some training!"
14 |
15 | @State var animate1Start = false
16 | @State var animate1End = false
17 | @State var animate2Start = false
18 | @State var animate2End = false
19 | @State var animate3Start = false
20 | @State var animate3End = false
21 | @State var animate4Start = false
22 | @State var animate4End = false
23 | @State var animate5Start = false
24 |
25 | public init() {}
26 |
27 | public var body: some View {
28 | ZStack {
29 |
30 | StoryTextIntroView(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End)
31 | .opacity(animate3Start ? 0 : 1)
32 | .onAppear {
33 | self.animate1Start = true
34 | DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
35 | self.animate1End = true
36 | }
37 | }
38 |
39 | BriefLogisticExplanationView(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start)
40 | .opacity(animate5Start ? 0 : 1)
41 |
42 | VStack {
43 | Text(endingString)
44 | .fontWeight(.semibold)
45 | .font(.system(.title, design: .rounded))
46 | .multilineTextAlignment(.center)
47 | .opacity(self.animate5Start ? 1 : 0)
48 | .offset(y: self.animate5Start ? 0 : 16)
49 | .animation(Animation.easeInOut(duration: 1))
50 | }
51 | .opacity(animate5Start ? 1 : 0)
52 |
53 | }
54 |
55 | }
56 | }
57 |
58 | struct StoryView_Previews: PreviewProvider {
59 | static var previews: some View {
60 | StoryView()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Support/MediaSupport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaModule.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/16/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AVFoundation
11 |
12 | public var audioPlayer: AVAudioPlayer?
13 | public var audioPlayer2: AVAudioPlayer?
14 | public var audioPlayerBG: AVAudioPlayer?
15 |
16 | extension AVAudioPlayer {
17 |
18 | static func playSound(sound: String, type: String) {
19 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else {
20 | fatalError("Could not find path for audio file named: \(sound)")
21 | }
22 | do {
23 | // var audioPlayer: AVAudioPlayer?
24 | audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
25 | audioPlayer?.volume = 0.5
26 | audioPlayer?.play()
27 | } catch {
28 | print("Error: Could not play sound file")
29 | }
30 |
31 | }
32 |
33 | static func playSound2(sound: String, type: String) {
34 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else {
35 | fatalError("Could not find path for audio file named: \(sound)")
36 | }
37 | do {
38 | audioPlayer2 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
39 | audioPlayer2?.volume = 0.1
40 | audioPlayer2?.play()
41 | } catch {
42 | print("Error: Could not play sound file")
43 | }
44 |
45 | }
46 |
47 | static func startPlaySoundBG() {
48 | guard let path = Bundle.main.path(forResource: "clock1", ofType: "wav") else {
49 | fatalError("Could not find background audio file")
50 | }
51 | do {
52 | audioPlayerBG = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
53 | audioPlayerBG?.rate = 4
54 | audioPlayerBG?.numberOfLoops = -1
55 | audioPlayerBG?.volume = 0.08
56 | audioPlayerBG?.play()
57 | } catch {
58 | print("Error: Could not play sound file")
59 | }
60 |
61 | }
62 |
63 | static func stopPlaySoundBG() {
64 | audioPlayerBG?.stop()
65 | }
66 |
67 |
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/MediaSupport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaModule.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/16/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AVFoundation
11 |
12 | public var audioPlayer: AVAudioPlayer?
13 | public var audioPlayer2: AVAudioPlayer?
14 | public var audioPlayerBG: AVAudioPlayer?
15 |
16 | extension AVAudioPlayer {
17 |
18 | static func playSound(sound: String, type: String) {
19 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else {
20 | fatalError("Could not find path for audio file named: \(sound)")
21 | }
22 | do {
23 | // var audioPlayer: AVAudioPlayer?
24 | audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
25 | audioPlayer?.volume = 0.5
26 | audioPlayer?.play()
27 | } catch {
28 | print("Error: Could not play sound file")
29 | }
30 |
31 | }
32 |
33 | static func playSound2(sound: String, type: String) {
34 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else {
35 | fatalError("Could not find path for audio file named: \(sound)")
36 | }
37 | do {
38 | audioPlayer2 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
39 | audioPlayer2?.volume = 0.1
40 | audioPlayer2?.play()
41 | } catch {
42 | print("Error: Could not play sound file")
43 | }
44 |
45 | }
46 |
47 | static func startPlaySoundBG() {
48 | guard let path = Bundle.main.path(forResource: "clock1", ofType: "wav") else {
49 | fatalError("Could not find background audio file")
50 | }
51 | do {
52 | audioPlayerBG = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
53 | audioPlayerBG?.rate = 4
54 | audioPlayerBG?.numberOfLoops = -1
55 | audioPlayerBG?.volume = 0.08
56 | audioPlayerBG?.play()
57 | } catch {
58 | print("Error: Could not play sound file")
59 | }
60 |
61 | }
62 |
63 | static func stopPlaySoundBG() {
64 | audioPlayerBG?.stop()
65 | }
66 |
67 |
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/BriefLogisticExplanationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BriefLogisticExplanationView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct BriefLogisticExplanationView: View {
12 |
13 | @Binding var animate3Start: Bool
14 | @Binding var animate3End : Bool
15 | @Binding var animate4Start: Bool
16 | @Binding var animate4End : Bool
17 | @Binding var animate5Start: Bool
18 |
19 | public init(
20 | animate3Start: Binding,
21 | animate3End: Binding,
22 | animate4Start: Binding,
23 | animate4End: Binding,
24 | animate5Start: Binding) {
25 |
26 | self._animate3Start = animate3Start
27 | self._animate3End = animate3End
28 | self._animate4Start = animate4Start
29 | self._animate4End = animate4End
30 | self._animate5Start = animate5Start
31 |
32 | }
33 |
34 | public var body: some View {
35 | VStack {
36 | Spacer()
37 |
38 | FactoryView()
39 | .frame(width: 100, height: 100)
40 | .opacity(animate3Start ? 1 : 0)
41 | .animation(Animation.easeInOut)
42 | .offset(y: self.animate5Start ? -1000 : 0)
43 |
44 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
45 | .resizable()
46 | .frame(width: playerSize, height: playerSize)
47 | .opacity(animate4Start ? 1 : 0)
48 | .animation(Animation.easeInOut)
49 | .offset(y: animate4Start ? 0 : 20)
50 |
51 | Spacer()
52 |
53 | Image(systemName: "chevron.left.2")
54 | .resizable()
55 | .foregroundColor(playerPathColor)
56 | .rotationEffect(Angle(degrees: 90))
57 | .frame(width: playerSize, height: playerSize)
58 | .opacity((animate3Start && !animate4Start) ? 1 : 0)
59 | .animation(Animation.easeInOut)
60 | .offset(y: animate4Start ? -40 : 0)
61 |
62 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
63 | .resizable()
64 | .frame(width: playerSize, height: playerSize)
65 | .opacity((animate3Start && !animate4Start) ? 1 : 0)
66 | .animation(Animation.easeInOut)
67 | .offset(y: animate4Start ? -60 : 0)
68 |
69 | LabView()
70 | .frame(width: 100, height: 100)
71 | .opacity(animate3Start ? 1 : 0)
72 | .animation(Animation.easeInOut)
73 | .offset(y: self.animate5Start ? 1000 : 0)
74 |
75 | Spacer()
76 |
77 | ButtonStackView2(animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start)
78 |
79 | Spacer()
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/BriefLogisticExplanationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BriefLogisticExplanationView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct BriefLogisticExplanationView: View {
12 |
13 | @Binding var animate3Start: Bool
14 | @Binding var animate3End : Bool
15 | @Binding var animate4Start: Bool
16 | @Binding var animate4End : Bool
17 | @Binding var animate5Start: Bool
18 |
19 | public init(
20 | animate3Start: Binding,
21 | animate3End: Binding,
22 | animate4Start: Binding,
23 | animate4End: Binding,
24 | animate5Start: Binding) {
25 |
26 | self._animate3Start = animate3Start
27 | self._animate3End = animate3End
28 | self._animate4Start = animate4Start
29 | self._animate4End = animate4End
30 | self._animate5Start = animate5Start
31 |
32 | }
33 |
34 | public var body: some View {
35 | VStack {
36 | Spacer()
37 |
38 | FactoryView()
39 | .frame(width: 100, height: 100)
40 | .opacity(animate3Start ? 1 : 0)
41 | .animation(Animation.easeInOut)
42 | .offset(y: self.animate5Start ? -1000 : 0)
43 |
44 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
45 | .resizable()
46 | .frame(width: playerSize, height: playerSize)
47 | .opacity(animate4Start ? 1 : 0)
48 | .animation(Animation.easeInOut)
49 | .offset(y: animate4Start ? 0 : 20)
50 |
51 | Spacer()
52 |
53 | Image(systemName: "chevron.left.2")
54 | .resizable()
55 | .foregroundColor(playerPathColor)
56 | .rotationEffect(Angle(degrees: 90))
57 | .frame(width: playerSize, height: playerSize)
58 | .opacity((animate3Start && !animate4Start) ? 1 : 0)
59 | .animation(Animation.easeInOut)
60 | .offset(y: animate4Start ? -40 : 0)
61 |
62 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
63 | .resizable()
64 | .frame(width: playerSize, height: playerSize)
65 | .opacity((animate3Start && !animate4Start) ? 1 : 0)
66 | .animation(Animation.easeInOut)
67 | .offset(y: animate4Start ? -60 : 0)
68 |
69 | LabView()
70 | .frame(width: 100, height: 100)
71 | .opacity(animate3Start ? 1 : 0)
72 | .animation(Animation.easeInOut)
73 | .offset(y: self.animate5Start ? 1000 : 0)
74 |
75 | Spacer()
76 |
77 | ButtonStackView2(animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start)
78 |
79 | Spacer()
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Support/GameLogistics.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameLogistics.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | // generate NPC coordinate when game starts
13 | public func generateNPCCoords(viewWidth: CGFloat, viewHeight: CGFloat) -> [[ScreenCoordinate]] {
14 | var resultCoords: [[ScreenCoordinate]] = [[]]
15 |
16 | for _ in 0.. Bool {
49 | let playerCoord = getPlayerCoord()
50 | if playerCoord.x <= viewWidth/2 + 2*mapIconSize &&
51 | playerCoord.x >= viewWidth/2 - 2*mapIconSize &&
52 | playerCoord.y <= 2*mapIconSize {
53 | return true
54 | }
55 | return false
56 | }
57 |
58 | // check if game ends
59 | public func gameStateCheck() -> (ended: Bool, succeeded: Bool) {
60 | var isEnded = false
61 | var isSucceeded = false
62 | let playerPosition = getPlayerCoord()
63 |
64 | if playerArriveDest() {
65 | isEnded = true
66 | isSucceeded = true
67 | return (isEnded, isSucceeded)
68 | }
69 |
70 | npcCoords.forEach { npcCoordArray in
71 | var npcCoordNow: ScreenCoordinate
72 |
73 | if npcCoordArray.endIndex < 2 {
74 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1]
75 | } else {
76 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1]
77 | }
78 |
79 | let distance = distanceFromPoint(p: npcCoordNow, toLineSegment: previousPosition, and: playerPosition)
80 |
81 | if distance < safetyDistance {
82 | isEnded = true
83 | isSucceeded = false
84 | }
85 | }
86 |
87 | return (isEnded, isSucceeded)
88 | }
89 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/GameLogistics.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameLogistics.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | // generate NPC coordinate when game starts
13 | public func generateNPCCoords(viewWidth: CGFloat, viewHeight: CGFloat) -> [[ScreenCoordinate]] {
14 | var resultCoords: [[ScreenCoordinate]] = [[]]
15 |
16 | for _ in 0.. Bool {
49 | let playerCoord = getPlayerCoord()
50 | if playerCoord.x <= viewWidth/2 + 2*mapIconSize &&
51 | playerCoord.x >= viewWidth/2 - 2*mapIconSize &&
52 | playerCoord.y <= 2*mapIconSize {
53 | return true
54 | }
55 | return false
56 | }
57 |
58 | // check if game ends
59 | public func gameStateCheck() -> (ended: Bool, succeeded: Bool) {
60 | var isEnded = false
61 | var isSucceeded = false
62 | let playerPosition = getPlayerCoord()
63 |
64 | if playerArriveDest() {
65 | isEnded = true
66 | isSucceeded = true
67 | return (isEnded, isSucceeded)
68 | }
69 |
70 | npcCoords.forEach { npcCoordArray in
71 | var npcCoordNow: ScreenCoordinate
72 |
73 | if npcCoordArray.endIndex < 2 {
74 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1]
75 | } else {
76 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1]
77 | }
78 |
79 | let distance = distanceFromPoint(p: npcCoordNow, toLineSegment: previousPosition, and: playerPosition)
80 |
81 | if distance < safetyDistance {
82 | isEnded = true
83 | isSucceeded = false
84 | }
85 | }
86 |
87 | return (isEnded, isSucceeded)
88 | }
89 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/TutorialView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TutorialView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/16/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct TutorialView: View {
12 |
13 | var tutorial1String = ["Welcome to training session,",
14 | "You will be taught several skills",
15 | "to better navigate through difficulties."]
16 |
17 | public init() {}
18 |
19 | @State var animate1Start = false
20 | @State var animate1End = false
21 | @State var animate2Start = false
22 | @State var animate2End = false
23 | @State var animate3Start = false
24 | @State var animate3End = false
25 |
26 | public var body: some View {
27 | ZStack {
28 | VStack {
29 | Spacer()
30 |
31 | VStack {
32 | ForEach(tutorial1String, id: \.self) { string in
33 | Text(string)
34 | .fontWeight(.semibold)
35 | .font(.system(.title, design: .rounded))
36 | .multilineTextAlignment(.center)
37 | .opacity(self.animate1Start ? 1 : 0)
38 | .offset(y: self.animate1Start ? 0 : 16)
39 | .offset(y: 10 * CGFloat(self.tutorial1String.firstIndex(of: string)!))
40 | .animation(Animation.easeInOut(duration: 1).delay(
41 | Double(self.tutorial1String.firstIndex(of: string)!)
42 | )
43 | )
44 | }
45 | .opacity(animate2Start ? 0 : 1)
46 | }
47 |
48 | Spacer()
49 |
50 | ZStack {
51 | Button(action: {
52 | self.animate2Start = true
53 |
54 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
55 | self.animate2End = true
56 | }
57 | }) {
58 | Text("Next")
59 | .fontWeight(.semibold)
60 | .font(.system(.title, design: .rounded))
61 | }
62 | .opacity((animate1End && !animate2Start) ? 1 : 0)
63 |
64 | }
65 | Spacer()
66 | }
67 | .opacity(animate3Start ? 0 : 1)
68 | .onAppear {
69 | self.animate1Start = true
70 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
71 | self.animate1End = true
72 | }
73 | }
74 |
75 | VStack {
76 | Spacer()
77 | if animate2Start {
78 | InTutorialView()
79 | }
80 | Spacer()
81 | }
82 |
83 | }
84 |
85 | }
86 | }
87 |
88 |
89 | struct TutorialView_Previews: PreviewProvider {
90 | static var previews: some View {
91 | TutorialView()
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/TutorialView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TutorialView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/16/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct TutorialView: View {
12 |
13 | var tutorial1String = ["Welcome to training session,",
14 | "You will be taught several skills",
15 | "to better navigate through difficulties."]
16 |
17 | public init() {}
18 |
19 | @State var animate1Start = false
20 | @State var animate1End = false
21 | @State var animate2Start = false
22 | @State var animate2End = false
23 | @State var animate3Start = false
24 | @State var animate3End = false
25 |
26 | public var body: some View {
27 | ZStack {
28 | VStack {
29 | Spacer()
30 |
31 | VStack {
32 | ForEach(tutorial1String, id: \.self) { string in
33 | Text(string)
34 | .fontWeight(.semibold)
35 | .font(.system(.title, design: .rounded))
36 | .multilineTextAlignment(.center)
37 | .opacity(self.animate1Start ? 1 : 0)
38 | .offset(y: self.animate1Start ? 0 : 16)
39 | .offset(y: 10 * CGFloat(self.tutorial1String.firstIndex(of: string)!))
40 | .animation(Animation.easeInOut(duration: 1).delay(
41 | Double(self.tutorial1String.firstIndex(of: string)!)
42 | )
43 | )
44 | }
45 | .opacity(animate2Start ? 0 : 1)
46 | }
47 |
48 | Spacer()
49 |
50 | ZStack {
51 | Button(action: {
52 | self.animate2Start = true
53 |
54 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
55 | self.animate2End = true
56 | }
57 | }) {
58 | Text("Next")
59 | .fontWeight(.semibold)
60 | .font(.system(.title, design: .rounded))
61 | }
62 | .opacity((animate1End && !animate2Start) ? 1 : 0)
63 |
64 | }
65 | Spacer()
66 | }
67 | .opacity(animate3Start ? 0 : 1)
68 | .onAppear {
69 | self.animate1Start = true
70 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
71 | self.animate1End = true
72 | }
73 | }
74 |
75 | VStack {
76 | Spacer()
77 | if animate2Start {
78 | InTutorialView()
79 | }
80 | Spacer()
81 | }
82 |
83 | }
84 |
85 | }
86 | }
87 |
88 |
89 | struct TutorialView_Previews: PreviewProvider {
90 | static var previews: some View {
91 | TutorialView()
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/6/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
21 |
22 | // Get the managed object context from the shared persistent container.
23 | let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
24 |
25 | // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
26 | // Add `@Environment(\.managedObjectContext)` in the views that will need the context.
27 | let contentView = ContentView().environment(\.managedObjectContext, context)
28 |
29 | // Use a UIHostingController as window root view controller.
30 | if let windowScene = scene as? UIWindowScene {
31 | let window = UIWindow(windowScene: windowScene)
32 | window.rootViewController = UIHostingController(rootView: contentView)
33 | self.window = window
34 | window.makeKeyAndVisible()
35 | }
36 | }
37 |
38 | func sceneDidDisconnect(_ scene: UIScene) {
39 | // Called as the scene is being released by the system.
40 | // This occurs shortly after the scene enters the background, or when its session is discarded.
41 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
42 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
43 | }
44 |
45 | func sceneDidBecomeActive(_ scene: UIScene) {
46 | // Called when the scene has moved from an inactive state to an active state.
47 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
48 | }
49 |
50 | func sceneWillResignActive(_ scene: UIScene) {
51 | // Called when the scene will move from an active state to an inactive state.
52 | // This may occur due to temporary interruptions (ex. an incoming phone call).
53 | }
54 |
55 | func sceneWillEnterForeground(_ scene: UIScene) {
56 | // Called as the scene transitions from the background to the foreground.
57 | // Use this method to undo the changes made on entering the background.
58 | }
59 |
60 | func sceneDidEnterBackground(_ scene: UIScene) {
61 | // Called as the scene transitions from the foreground to the background.
62 | // Use this method to save data, release shared resources, and store enough scene-specific state information
63 | // to restore the scene back to its current state.
64 |
65 | // Save changes in the application's managed object context when the application transitions to the background.
66 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
67 | }
68 |
69 |
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/ButtonStackView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ButtonStackView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct ButtonStackView1: View {
12 |
13 | @Binding var animate1Start: Bool
14 | @Binding var animate1End : Bool
15 | @Binding var animate2Start: Bool
16 | @Binding var animate2End : Bool
17 | @Binding var animate3Start: Bool
18 | @Binding var animate3End : Bool
19 |
20 | public init(
21 | animate1Start: Binding,
22 | animate1End: Binding,
23 | animate2Start: Binding,
24 | animate2End: Binding,
25 | animate3Start: Binding,
26 | animate3End: Binding) {
27 | self._animate1Start = animate1Start
28 | self._animate1End = animate1End
29 | self._animate2Start = animate2Start
30 | self._animate2End = animate2End
31 | self._animate3Start = animate3Start
32 | self._animate3End = animate3End
33 |
34 | }
35 |
36 | public var body: some View {
37 | ZStack {
38 | Button(action: {
39 | self.animate2Start = true
40 |
41 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
42 | self.animate2End = true
43 | }
44 | }) {
45 | Text("Next")
46 | .fontWeight(.semibold)
47 | .font(.system(.title, design: .rounded))
48 | }
49 | .opacity((animate1End && !animate2Start) ? 1 : 0)
50 |
51 | Button(action: {
52 | self.animate3Start = true
53 |
54 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
55 | self.animate3End = true
56 | }
57 | }) {
58 | Text("Next")
59 | .fontWeight(.semibold)
60 | .font(.system(.title, design: .rounded))
61 | }
62 | .opacity((animate2End && !animate3Start) ? 1 : 0)
63 | }
64 | }
65 | }
66 |
67 | public struct ButtonStackView2: View {
68 |
69 | @Binding var animate3End : Bool
70 | @Binding var animate4Start: Bool
71 | @Binding var animate4End : Bool
72 | @Binding var animate5Start: Bool
73 |
74 | public init(
75 | animate3End: Binding,
76 | animate4Start: Binding,
77 | animate4End: Binding,
78 | animate5Start: Binding) {
79 |
80 | self._animate3End = animate3End
81 | self._animate4Start = animate4Start
82 | self._animate4End = animate4End
83 | self._animate5Start = animate5Start
84 |
85 | }
86 |
87 | public var body: some View {
88 | ZStack {
89 | Button(action: {
90 | self.animate4Start = true
91 |
92 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
93 | self.animate4End = true
94 | }
95 | }) {
96 | Text("Next")
97 | .fontWeight(.semibold)
98 | .font(.system(.title, design: .rounded))
99 | }
100 | .opacity((animate3End && !animate4Start) ? 1 : 0)
101 |
102 | Button(action: {
103 | self.animate5Start = true
104 | }) {
105 | Text("Next")
106 | .fontWeight(.semibold)
107 | .font(.system(.title, design: .rounded))
108 | }
109 | .opacity((animate4End && !animate5Start) ? 1 : 0)
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SixFeetBetween_WWDC20SwiftChallenge
2 | Swift Playground Project "6 Feet Between" written by Zixuan(Tony) Tang is a game written completely with SwiftUI and without any game Kit or engine. The project has been accepted by Apple, being one of the 350 winning projects around the globe.
3 |
4 | You may watch the demo screenrecording of this project on [YouTube](https://youtu.be/sj_laBHKu6I) at your convenience.
5 |
6 | ## Context
7 | In year 2020, Apple holds the [Swift Student Challenge](https://developer.apple.com/wwdc20/swift-student-challenge/) for the first time. Candidates are required to showcase their love of coding by creating an incredible Swift playground on the topic of your choice. This challenge is open to students around the world.
8 |
9 | ## Words from the Author
10 | My iPad Playground Book project named “6 Feet Between” is an interactive game that harnesses the power of the latest Apple software technologies to provide an immersive experience. This game is intended to educate people about keeping social distance as a hygiene practice. In the game story, the human is facing a crisis caused by a virus. The player will become a ninja who tries to save people’s lives by transporting a research sample from the lab to the factory. The ninja needs to keep at least 6 feet away from other pedestrians on the way to the destination.
11 |
12 |
13 | Providing the player with an interactive and entertaining experience, the game comes in three parts, the background story, the instruction, and the game scene. The background story page greets the player when they first open this project. The whole story will be told concisely, along with visual patterns and animations. Simple diagrams combined with animated symbols help the player to understand the game context. The instruction page introduces basic playing actions and rules to the user, providing an experimental ground for the player’s moves. After the user is fully loaded, they will enter the game page to put all they have just learned into practice. After the game ends, the player will receive a card showcasing their game performance. The game also lively adapts when user switch to dark mode.
14 |
15 |
16 | The latest Apple software technologies, including “SwiftUI,” its internal Metal off-screen rendering, along with “AVFoundation” and “PlaygroundSupport,” are implemented to build this interactive game. In creating “6 Feet Between”, I took a fundamentally different technical approach from other games. More specifically, it is a game that is built up without a game framework. While other 2D games are mostly based on “SpriteKit” or “SceneKit,” I built “6 Feet Between” entirely on the “SwiftUI,” which just came out barely a year ago. Although being comparatively new, SwiftUI provides surprisingly high performance and rich animations that allowed me to build a game from the ground up. Although it was not easy to start off with, I managed to dive deep into the documentation for the powerful technology of “SwiftUI,” including its off-screen view rendering using “Metal.” I also used multithreading technology throughout the project for smooth animation rendering for every UI element in this project. However, interaction is more than just visual appearance. By using “AVFoundation,” I have integrated user actions with sound effects. Therefore, every time the player clicks a button, moves on the screen, or ends the game, subtle sound effects provide clear feedback.
17 |
18 |
19 | ## More Info
20 | At the time of this commit, I just finished my first year of college as an international undergraduate at UCSD(University of California San Diego). Growing up as a techie, and now as a developer, I am aware of the importance of being supported and the feeling of belonging. Feel free to ask me any question you have encountered, and I am more than happy to help out if I am able to!
21 |
22 | Follow me at:
23 | - GitHub: https://github.com/TonyTang2001
24 | - Twitter: @TonyTang_here
25 | - Weibo: @TonyTang_Dev and @TonyTang2001
26 | - LinkedIn: Zixuan Tang
27 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/ButtonStackView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ButtonStackView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct ButtonStackView1: View {
12 |
13 | @Binding var animate1Start: Bool
14 | @Binding var animate1End : Bool
15 | @Binding var animate2Start: Bool
16 | @Binding var animate2End : Bool
17 | @Binding var animate3Start: Bool
18 | @Binding var animate3End : Bool
19 |
20 | public init(
21 | animate1Start: Binding,
22 | animate1End: Binding,
23 | animate2Start: Binding,
24 | animate2End: Binding,
25 | animate3Start: Binding,
26 | animate3End: Binding) {
27 | self._animate1Start = animate1Start
28 | self._animate1End = animate1End
29 | self._animate2Start = animate2Start
30 | self._animate2End = animate2End
31 | self._animate3Start = animate3Start
32 | self._animate3End = animate3End
33 |
34 | }
35 |
36 | public var body: some View {
37 | ZStack {
38 | Button(action: {
39 | self.animate2Start = true
40 |
41 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
42 | self.animate2End = true
43 | }
44 | }) {
45 | Text("Next")
46 | .fontWeight(.semibold)
47 | .font(.system(.title, design: .rounded))
48 | }
49 | .opacity((animate1End && !animate2Start) ? 1 : 0)
50 |
51 | Button(action: {
52 | self.animate3Start = true
53 |
54 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
55 | self.animate3End = true
56 | }
57 | }) {
58 | Text("Next")
59 | .fontWeight(.semibold)
60 | .font(.system(.title, design: .rounded))
61 | }
62 | .opacity((animate2End && !animate3Start) ? 1 : 0)
63 | }
64 | }
65 | }
66 |
67 | public struct ButtonStackView2: View {
68 |
69 | @Binding var animate3End : Bool
70 | @Binding var animate4Start: Bool
71 | @Binding var animate4End : Bool
72 | @Binding var animate5Start: Bool
73 |
74 | public init(
75 | animate3End: Binding,
76 | animate4Start: Binding,
77 | animate4End: Binding,
78 | animate5Start: Binding) {
79 |
80 | self._animate3End = animate3End
81 | self._animate4Start = animate4Start
82 | self._animate4End = animate4End
83 | self._animate5Start = animate5Start
84 |
85 | }
86 |
87 | public var body: some View {
88 | ZStack {
89 | Button(action: {
90 | self.animate4Start = true
91 |
92 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
93 | self.animate4End = true
94 | }
95 | }) {
96 | Text("Next")
97 | .fontWeight(.semibold)
98 | .font(.system(.title, design: .rounded))
99 | }
100 | .opacity((animate3End && !animate4Start) ? 1 : 0)
101 |
102 | Button(action: {
103 | self.animate5Start = true
104 | }) {
105 | Text("Next")
106 | .fontWeight(.semibold)
107 | .font(.system(.title, design: .rounded))
108 | }
109 | .opacity((animate4End && !animate5Start) ? 1 : 0)
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/6/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | // MARK: UISceneSession Lifecycle
23 |
24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
25 | // Called when a new scene session is being created.
26 | // Use this method to select a configuration to create the new scene with.
27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
28 | }
29 |
30 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
31 | // Called when the user discards a scene session.
32 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
33 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
34 | }
35 |
36 | // MARK: - Core Data stack
37 |
38 | lazy var persistentContainer: NSPersistentContainer = {
39 | /*
40 | The persistent container for the application. This implementation
41 | creates and returns a container, having loaded the store for the
42 | application to it. This property is optional since there are legitimate
43 | error conditions that could cause the creation of the store to fail.
44 | */
45 | let container = NSPersistentContainer(name: "WWDC20PlaygroundTest")
46 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in
47 | if let error = error as NSError? {
48 | // Replace this implementation with code to handle the error appropriately.
49 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
50 |
51 | /*
52 | Typical reasons for an error here include:
53 | * The parent directory does not exist, cannot be created, or disallows writing.
54 | * The persistent store is not accessible, due to permissions or data protection when the device is locked.
55 | * The device is out of space.
56 | * The store could not be migrated to the current model version.
57 | Check the error message to determine what the actual problem was.
58 | */
59 | fatalError("Unresolved error \(error), \(error.userInfo)")
60 | }
61 | })
62 | return container
63 | }()
64 |
65 | // MARK: - Core Data Saving support
66 |
67 | func saveContext () {
68 | let context = persistentContainer.viewContext
69 | if context.hasChanges {
70 | do {
71 | try context.save()
72 | } catch {
73 | // Replace this implementation with code to handle the error appropriately.
74 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
75 | let nserror = error as NSError
76 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
77 | }
78 | }
79 | }
80 |
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/NPCUI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NPCUI.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | public struct NPCInternalView: View {
13 |
14 | @Binding var isDragging: Bool
15 |
16 | public init(isDragging: Binding) {
17 | self._isDragging = isDragging
18 | }
19 |
20 | public var body: some View {
21 | ZStack {
22 | Image(systemName: "person.circle.fill")
23 | .resizable()
24 | .frame(width: npcSize, height: npcSize)
25 | .opacity(self.isDragging ? 0.6 : 1.0)
26 | .animation(.npcTransition)
27 | .clipShape(Circle())
28 |
29 | // warning range indicator
30 | Circle()
31 | .frame(width: npcWarningRangeSize, height: npcWarningRangeSize)
32 | .opacity(self.isDragging ? 0 : 0)
33 | .overlay(
34 | Circle()
35 | .stroke(Color.red.opacity(self.isDragging ? 0.9 : 0), lineWidth: self.isDragging ? npcSize/2 : 0)
36 | .animation(.npcTransition)
37 | )
38 | }
39 | .frame(width: npcSize * 2, height: npcSize * 2)
40 | .drawingGroup() // enable off-screen Metal rendering
41 | }
42 | }
43 |
44 | struct NPCView: View {
45 |
46 | @Binding var isDragging: Bool
47 |
48 | @State var index: Int = 0
49 | @State var npcCurrentCoords: [ScreenCoordinate] = []
50 |
51 | var body: some View {
52 |
53 | NPCInternalView(isDragging: $isDragging)
54 | .position(x: 0, y: 0)
55 | .offset(x: npcCurrentCoords[npcCurrentCoords.endIndex-1].x, y: npcCurrentCoords[npcCurrentCoords.endIndex-1].y)
56 | .animation(Animation.linear(duration: 1))
57 | .onAppear {
58 | self.toNewPosition()
59 | }
60 |
61 | }
62 |
63 | func toNewPosition() {
64 | if endOnHold {
65 | return
66 | }
67 |
68 | if !isDragging {
69 | let const: CGFloat = 8
70 |
71 | let previousCoord = npcCurrentCoords[npcCurrentCoords.endIndex-1]
72 |
73 | var xMove = CGFloat.random(in: -const...const)
74 | var yMove = CGFloat.random(in: -const...const)
75 |
76 | // let destCoord = ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)
77 | let playerPosition = getPlayerCoord()
78 |
79 | while outOfView(coord: ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)) || approachPlayer(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove), playerPosition) {
80 | xMove = CGFloat.random(in: -const...const)
81 | yMove = CGFloat.random(in: -const...const)
82 | }
83 |
84 | npcCurrentCoords.append(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove))
85 |
86 | npcCoords[self.index] = self.npcCurrentCoords
87 |
88 | }
89 |
90 | DispatchQueue.main.asyncAfter(deadline: .now() + .random(in: 0.8...1.2)) {
91 |
92 | npcCoords[self.index] = self.npcCurrentCoords
93 | self.toNewPosition()
94 | // npcCoords[self.index] = self.npcCurrentCoords
95 | }
96 | }
97 |
98 | }
99 |
100 | public struct NPCMap: View {
101 |
102 | @Binding var isDragging: Bool
103 |
104 | public init(isDragging: Binding) {
105 | self._isDragging = isDragging
106 | }
107 |
108 | public var body: some View {
109 | ZStack {
110 | ForEach(npcCoords, id: \.self) { coord in
111 | NPCView(isDragging: self.$isDragging, index: npcCoords.firstIndex(of: coord)!, npcCurrentCoords: coord)
112 | }
113 | }
114 | .drawingGroup() // enable off-screen Metal rendering
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/NPCUI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NPCUI.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | public struct NPCInternalView: View {
13 |
14 | @Binding var isDragging: Bool
15 |
16 | public init(isDragging: Binding) {
17 | self._isDragging = isDragging
18 | }
19 |
20 | public var body: some View {
21 | ZStack {
22 | Image(systemName: "person.circle.fill")
23 | .resizable()
24 | .frame(width: npcSize, height: npcSize)
25 | .opacity(self.isDragging ? 0.6 : 1.0)
26 | .animation(.npcTransition)
27 | .clipShape(Circle())
28 |
29 | // warning range indicator
30 | Circle()
31 | .frame(width: npcWarningRangeSize, height: npcWarningRangeSize)
32 | .opacity(self.isDragging ? 0 : 0)
33 | .overlay(
34 | Circle()
35 | .stroke(Color.red.opacity(self.isDragging ? 0.9 : 0), lineWidth: self.isDragging ? npcSize/2 : 0)
36 | .animation(.npcTransition)
37 | )
38 | }
39 | .frame(width: npcSize * 2, height: npcSize * 2)
40 | .drawingGroup() // enable off-screen Metal rendering
41 | }
42 | }
43 |
44 | struct NPCView: View {
45 |
46 | @Binding var isDragging: Bool
47 |
48 | @State var index: Int = 0
49 | @State var npcCurrentCoords: [ScreenCoordinate] = []
50 |
51 | var body: some View {
52 |
53 | NPCInternalView(isDragging: $isDragging)
54 | .position(x: 0, y: 0)
55 | .offset(x: npcCurrentCoords[npcCurrentCoords.endIndex-1].x, y: npcCurrentCoords[npcCurrentCoords.endIndex-1].y)
56 | .animation(Animation.linear(duration: 1))
57 | .onAppear {
58 | self.toNewPosition()
59 | }
60 |
61 | }
62 |
63 | func toNewPosition() {
64 | if endOnHold {
65 | return
66 | }
67 |
68 | if !isDragging {
69 | let const: CGFloat = 8
70 |
71 | let previousCoord = npcCurrentCoords[npcCurrentCoords.endIndex-1]
72 |
73 | var xMove = CGFloat.random(in: -const...const)
74 | var yMove = CGFloat.random(in: -const...const)
75 |
76 | // let destCoord = ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)
77 | let playerPosition = getPlayerCoord()
78 |
79 | while outOfView(coord: ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)) || approachPlayer(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove), playerPosition) {
80 | xMove = CGFloat.random(in: -const...const)
81 | yMove = CGFloat.random(in: -const...const)
82 | }
83 |
84 | npcCurrentCoords.append(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove))
85 |
86 | npcCoords[self.index] = self.npcCurrentCoords
87 |
88 | }
89 |
90 | DispatchQueue.main.asyncAfter(deadline: .now() + .random(in: 0.8...1.2)) {
91 |
92 | npcCoords[self.index] = self.npcCurrentCoords
93 | self.toNewPosition()
94 | // npcCoords[self.index] = self.npcCurrentCoords
95 | }
96 | }
97 |
98 | }
99 |
100 | public struct NPCMap: View {
101 |
102 | @Binding var isDragging: Bool
103 |
104 | public init(isDragging: Binding) {
105 | self._isDragging = isDragging
106 | }
107 |
108 | public var body: some View {
109 | ZStack {
110 | ForEach(npcCoords, id: \.self) { coord in
111 | NPCView(isDragging: self.$isDragging, index: npcCoords.firstIndex(of: coord)!, npcCurrentCoords: coord)
112 | }
113 | }
114 | .drawingGroup() // enable off-screen Metal rendering
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Data/Calculation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Calculation.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | // get length using sqrt of x and y squares
13 | public func getLength(x: CGFloat, y: CGFloat) -> CGFloat {
14 | return (x * x + y * y).squareRoot()
15 | }
16 |
17 | // get length between two screen coordinates
18 | public func getDistance(_ point1: ScreenCoordinate, _ point2: ScreenCoordinate) -> CGFloat {
19 | let p1X = point1.x
20 | let p1Y = point1.y
21 | let p2X = point2.x
22 | let p2Y = point2.y
23 |
24 | let xDiff = p1X - p2X
25 | let yDiff = p1Y - p2Y
26 |
27 | let ptDistance = (xDiff * xDiff + yDiff * yDiff).squareRoot()
28 |
29 | return ptDistance
30 | }
31 |
32 | // check if npc approches player
33 | public func approachPlayer(_ npc: ScreenCoordinate, _ player: ScreenCoordinate) -> Bool {
34 | let distance = getDistance(npc, player)
35 | if distance <= safetyDistance {
36 | return true
37 | } else {
38 | return false
39 | }
40 | }
41 |
42 | // calculate player exact coordinate
43 | public func getPlayerCoord() -> ScreenCoordinate {
44 | let playerPosition = ScreenCoordinate(x: currentPosition.width + viewWidth/2 , y: currentPosition.height + viewHeight - playerSize/2)
45 | return playerPosition
46 | }
47 |
48 | // check if the coordinate is out of view
49 | public func outOfView(coord: ScreenCoordinate) -> Bool {
50 | let coordLeftX = coord.x - 20
51 | let coordRightX = coord.x + 20
52 | let coordTopY = coord.y - 20
53 | let coordBtmY = coord.y + 20
54 | if coordLeftX < 0 || coordRightX > viewWidth || coordTopY < 0 || coordBtmY > viewHeight {
55 | return true
56 | } else {
57 | return false
58 | }
59 | }
60 |
61 | // calculate shortest length from a point to a line segment
62 | public func distanceFromPoint(p: ScreenCoordinate, toLineSegment v: ScreenCoordinate, and w: ScreenCoordinate) -> CGFloat {
63 | let pv_dx = p.x - v.x
64 | let pv_dy = p.y - v.y
65 | let wv_dx = w.x - v.x
66 | let wv_dy = w.y - v.y
67 |
68 | let dot = pv_dx * wv_dx + pv_dy * wv_dy
69 | let len_sq = wv_dx * wv_dx + wv_dy * wv_dy
70 | let param = dot / len_sq
71 |
72 | var int_x, int_y: CGFloat /* intersection of normal to vw that goes through p */
73 |
74 | if param < 0 || (v.x == w.x && v.y == w.y) {
75 | int_x = v.x
76 | int_y = v.y
77 | } else if param > 1 {
78 | int_x = w.x
79 | int_y = w.y
80 | } else {
81 | int_x = v.x + param * wv_dx
82 | int_y = v.y + param * wv_dy
83 | }
84 |
85 | /* Components of normal */
86 | let dx = p.x - int_x
87 | let dy = p.y - int_y
88 |
89 | return sqrt(dx * dx + dy * dy)
90 | }
91 |
92 | // get game duration from start to end
93 | public func getGameDuration(from start: Date, to end: Date) -> Int {
94 | let calendar = Calendar.current
95 | let dateComponents = calendar.dateComponents([Calendar.Component.second], from: start, to: end)
96 |
97 | let seconds = dateComponents.second
98 | return Int(seconds!)
99 | }
100 |
101 | // get game rating
102 | public func getGameRating(duration: Int) -> Int {
103 | if duration < npcCount/2 {
104 | return 3
105 | } else if duration < npcCount {
106 | return 2
107 | } else {
108 | return 1
109 | }
110 | }
111 |
112 | // check if player is out of screen view
113 | public func playerOutOfView() -> Bool {
114 | let playerDest = getPlayerCoord()
115 | let playerLeftX = playerDest.x - playerSize/2
116 | let playerRightX = playerDest.x + playerSize/2
117 | let playerTopY = playerDest.y - playerSize/2
118 | let playerBottomY = playerDest.y + playerSize/2
119 |
120 | if playerLeftX < 0 || playerRightX > viewWidth ||
121 | playerTopY < 0 || playerBottomY > viewHeight {
122 | return true
123 | }
124 |
125 | return false
126 | }
127 |
128 | // calculate player destination using user dragging one-axis distance
129 | public func calculateMoveEstimate(input: CGFloat) -> CGFloat {
130 | let square = input * input
131 | let ans = 30 * square/(9999 + (square-input))
132 | return ans
133 | }
134 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/Calculation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Calculation.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | // get length using sqrt of x and y squares
13 | public func getLength(x: CGFloat, y: CGFloat) -> CGFloat {
14 | return (x * x + y * y).squareRoot()
15 | }
16 |
17 | // get length between two screen coordinates
18 | public func getDistance(_ point1: ScreenCoordinate, _ point2: ScreenCoordinate) -> CGFloat {
19 | let p1X = point1.x
20 | let p1Y = point1.y
21 | let p2X = point2.x
22 | let p2Y = point2.y
23 |
24 | let xDiff = p1X - p2X
25 | let yDiff = p1Y - p2Y
26 |
27 | let ptDistance = (xDiff * xDiff + yDiff * yDiff).squareRoot()
28 |
29 | return ptDistance
30 | }
31 |
32 | // check if npc approches player
33 | public func approachPlayer(_ npc: ScreenCoordinate, _ player: ScreenCoordinate) -> Bool {
34 | let distance = getDistance(npc, player)
35 | if distance <= safetyDistance {
36 | return true
37 | } else {
38 | return false
39 | }
40 | }
41 |
42 | // calculate player exact coordinate
43 | public func getPlayerCoord() -> ScreenCoordinate {
44 | let playerPosition = ScreenCoordinate(x: currentPosition.width + viewWidth/2 , y: currentPosition.height + viewHeight - playerSize/2)
45 | return playerPosition
46 | }
47 |
48 | // check if the coordinate is out of view
49 | public func outOfView(coord: ScreenCoordinate) -> Bool {
50 | let coordLeftX = coord.x - 20
51 | let coordRightX = coord.x + 20
52 | let coordTopY = coord.y - 20
53 | let coordBtmY = coord.y + 20
54 | if coordLeftX < 0 || coordRightX > viewWidth || coordTopY < 0 || coordBtmY > viewHeight {
55 | return true
56 | } else {
57 | return false
58 | }
59 | }
60 |
61 | // calculate shortest length from a point to a line segment
62 | public func distanceFromPoint(p: ScreenCoordinate, toLineSegment v: ScreenCoordinate, and w: ScreenCoordinate) -> CGFloat {
63 | let pv_dx = p.x - v.x
64 | let pv_dy = p.y - v.y
65 | let wv_dx = w.x - v.x
66 | let wv_dy = w.y - v.y
67 |
68 | let dot = pv_dx * wv_dx + pv_dy * wv_dy
69 | let len_sq = wv_dx * wv_dx + wv_dy * wv_dy
70 | let param = dot / len_sq
71 |
72 | var int_x, int_y: CGFloat /* intersection of normal to vw that goes through p */
73 |
74 | if param < 0 || (v.x == w.x && v.y == w.y) {
75 | int_x = v.x
76 | int_y = v.y
77 | } else if param > 1 {
78 | int_x = w.x
79 | int_y = w.y
80 | } else {
81 | int_x = v.x + param * wv_dx
82 | int_y = v.y + param * wv_dy
83 | }
84 |
85 | /* Components of normal */
86 | let dx = p.x - int_x
87 | let dy = p.y - int_y
88 |
89 | return sqrt(dx * dx + dy * dy)
90 | }
91 |
92 | // get game duration from start to end
93 | public func getGameDuration(from start: Date, to end: Date) -> Int {
94 | let calendar = Calendar.current
95 | let dateComponents = calendar.dateComponents([Calendar.Component.second], from: start, to: end)
96 |
97 | let seconds = dateComponents.second
98 | return Int(seconds!)
99 | }
100 |
101 | // get game rating
102 | public func getGameRating(duration: Int) -> Int {
103 | if duration < npcCount/2 {
104 | return 3
105 | } else if duration < npcCount {
106 | return 2
107 | } else {
108 | return 1
109 | }
110 | }
111 |
112 | // check if player is out of screen view
113 | public func playerOutOfView() -> Bool {
114 | let playerDest = getPlayerCoord()
115 | let playerLeftX = playerDest.x - playerSize/2
116 | let playerRightX = playerDest.x + playerSize/2
117 | let playerTopY = playerDest.y - playerSize/2
118 | let playerBottomY = playerDest.y + playerSize/2
119 |
120 | if playerLeftX < 0 || playerRightX > viewWidth ||
121 | playerTopY < 0 || playerBottomY > viewHeight {
122 | return true
123 | }
124 |
125 | return false
126 | }
127 |
128 | // calculate player destination using user dragging one-axis distance
129 | public func calculateMoveEstimate(input: CGFloat) -> CGFloat {
130 | let square = input * input
131 | let ans = 30 * square/(9999 + (square-input))
132 | return ans
133 | }
134 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/GameFailureView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameFailureView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/13/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import AVFoundation
11 |
12 | struct FailureSignView: View {
13 | var body: some View {
14 | HStack {
15 | Image(systemName: "xmark.seal.fill")
16 | .resizable()
17 | .frame(width: 30, height: 30)
18 |
19 | Text("You Lost")
20 | .fontWeight(.semibold)
21 | .font(.system(.title, design: .rounded))
22 | }
23 | }
24 | }
25 |
26 | struct FaliureThumbNailView: View {
27 | @State var animate: Bool = false
28 |
29 | var body: some View {
30 | ZStack {
31 | RoundedRectangle(cornerRadius: 14, style: .continuous)
32 | .frame(width: 80, height: 14)
33 | .foregroundColor(Color.red)
34 | .rotationEffect(self.animate ? Angle(degrees: 45) : Angle(degrees: 0))
35 |
36 |
37 | RoundedRectangle(cornerRadius: 14, style: .continuous)
38 | .frame(width: 80, height: 14)
39 | .foregroundColor(Color.red)
40 | .rotationEffect(self.animate ? Angle(degrees: -45) : Angle(degrees: 0))
41 | }
42 | .frame(width: 60, height: 60)
43 | .animation(Animation.spring().delay(0.1))
44 | .onAppear {
45 | withAnimation {
46 | self.animate = true
47 | }
48 | }
49 |
50 | }
51 | }
52 |
53 | public struct GameFailureView: View {
54 |
55 | @State var appear = false
56 | @State var userDrag = CGSize.zero
57 |
58 | public init() {}
59 |
60 | let textOption = Int.random(in: 0..<6)
61 | let failureSentences: [String] = ["Good Work, \nbut Not Enough.",
62 | "Still a Way to Go",
63 | "Keep up with It, \nYou Got This!",
64 | "Just... a bit More.",
65 | "Better Luck Next Time!",
66 | "Oops, didn't see that coming..."]
67 |
68 | public var body: some View {
69 | VStack {
70 | Spacer()
71 | VStack {
72 | Spacer()
73 | FailureSignView()
74 |
75 | Spacer()
76 | FaliureThumbNailView()
77 |
78 | Spacer()
79 | Text(failureSentences[textOption])
80 | .fontWeight(.bold)
81 | .font(.system(.largeTitle, design: .rounded))
82 | .multilineTextAlignment(.center)
83 |
84 | Spacer()
85 | }
86 | .frame(width: 420, height: 330)
87 | .background(Color(UIColor.systemBackground))
88 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
89 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16)
90 | .offset(x: 0, y: self.appear ? 0 : 16)
91 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0))
92 | .animation(.easeInOut)
93 |
94 | Spacer()
95 |
96 | Text("To restart the game, \ntap on Playground Start/Stop Button.")
97 | .fontWeight(.semibold)
98 | .font(.system(.footnote, design: .rounded))
99 | .multilineTextAlignment(.center)
100 |
101 | Spacer()
102 | }
103 | .opacity(self.appear ? 1 : 0)
104 | .onAppear {
105 | self.appear = true
106 | AVAudioPlayer.stopPlaySoundBG()
107 | AVAudioPlayer.playSound(sound: "error2", type: "wav")
108 | }
109 | .gesture(
110 | DragGesture().onChanged { value in
111 | self.userDrag = value.translation
112 | }
113 | .onEnded { value in
114 | self.userDrag = .zero
115 | }
116 | )
117 |
118 |
119 |
120 |
121 | }
122 | }
123 |
124 | struct GameFailureView_Previews: PreviewProvider {
125 | static var previews: some View {
126 | GameFailureView()
127 | }
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/GameFailureView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameFailureView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/13/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import AVFoundation
11 |
12 | struct FailureSignView: View {
13 | var body: some View {
14 | HStack {
15 | Image(systemName: "xmark.seal.fill")
16 | .resizable()
17 | .frame(width: 30, height: 30)
18 |
19 | Text("You Lost")
20 | .fontWeight(.semibold)
21 | .font(.system(.title, design: .rounded))
22 | }
23 | }
24 | }
25 |
26 | struct FaliureThumbNailView: View {
27 | @State var animate: Bool = false
28 |
29 | var body: some View {
30 | ZStack {
31 | RoundedRectangle(cornerRadius: 14, style: .continuous)
32 | .frame(width: 80, height: 14)
33 | .foregroundColor(Color.red)
34 | .rotationEffect(self.animate ? Angle(degrees: 45) : Angle(degrees: 0))
35 |
36 |
37 | RoundedRectangle(cornerRadius: 14, style: .continuous)
38 | .frame(width: 80, height: 14)
39 | .foregroundColor(Color.red)
40 | .rotationEffect(self.animate ? Angle(degrees: -45) : Angle(degrees: 0))
41 | }
42 | .frame(width: 60, height: 60)
43 | .animation(Animation.spring().delay(0.1))
44 | .onAppear {
45 | withAnimation {
46 | self.animate = true
47 | }
48 | }
49 |
50 | }
51 | }
52 |
53 | public struct GameFailureView: View {
54 |
55 | @State var appear = false
56 | @State var userDrag = CGSize.zero
57 |
58 | public init() {}
59 |
60 | let textOption = Int.random(in: 0..<6)
61 | let failureSentences: [String] = ["Good Work, \nbut Not Enough.",
62 | "Still a Way to Go",
63 | "Keep up with It, \nYou Got This!",
64 | "Just... a bit More.",
65 | "Better Luck Next Time!",
66 | "Oops, didn't see that coming..."]
67 |
68 | public var body: some View {
69 | VStack {
70 | Spacer()
71 | VStack {
72 | Spacer()
73 | FailureSignView()
74 |
75 | Spacer()
76 | FaliureThumbNailView()
77 |
78 | Spacer()
79 | Text(failureSentences[textOption])
80 | .fontWeight(.bold)
81 | .font(.system(.largeTitle, design: .rounded))
82 | .multilineTextAlignment(.center)
83 |
84 | Spacer()
85 | }
86 | .frame(width: 420, height: 330)
87 | .background(Color(UIColor.systemBackground))
88 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
89 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16)
90 | .offset(x: 0, y: self.appear ? 0 : 16)
91 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0))
92 | .animation(.easeInOut)
93 |
94 | Spacer()
95 |
96 | Text("To restart the game, \ntap on Playground Start/Stop Button.")
97 | .fontWeight(.semibold)
98 | .font(.system(.footnote, design: .rounded))
99 | .multilineTextAlignment(.center)
100 |
101 | Spacer()
102 | }
103 | .opacity(self.appear ? 1 : 0)
104 | .onAppear {
105 | self.appear = true
106 | AVAudioPlayer.stopPlaySoundBG()
107 | AVAudioPlayer.playSound(sound: "error2", type: "wav")
108 | }
109 | .gesture(
110 | DragGesture().onChanged { value in
111 | self.userDrag = value.translation
112 | }
113 | .onEnded { value in
114 | self.userDrag = .zero
115 | }
116 | )
117 |
118 |
119 |
120 |
121 | }
122 | }
123 |
124 | struct GameFailureView_Previews: PreviewProvider {
125 | static var previews: some View {
126 | GameFailureView()
127 | }
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/StoryTextIntroView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoryTextIntroView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct StoryTextIntroView: View {
12 |
13 | var context1String = ["Sometime in the future,",
14 | "an virus named C9 is emerging worldwide.",
15 | "It has infected a huge amount of people,",
16 | "and everyone is waiting for help.",
17 | "Fortunately, the cure has been developed.",
18 | "",
19 | "However, the research sample has not been delivered",
20 | "to the factory for mass production."]
21 | var context2String = ["You are a Ninja.",
22 | "The people need your help!",
23 | "",
24 | "You need to transport the sample",
25 | "from the lab to the factory."]
26 |
27 | @Binding var animate1Start: Bool
28 | @Binding var animate1End : Bool
29 | @Binding var animate2Start: Bool
30 | @Binding var animate2End : Bool
31 | @Binding var animate3Start: Bool
32 | @Binding var animate3End : Bool
33 |
34 | public init(
35 | animate1Start: Binding,
36 | animate1End: Binding,
37 | animate2Start: Binding,
38 | animate2End: Binding,
39 | animate3Start: Binding,
40 | animate3End: Binding) {
41 | self._animate1Start = animate1Start
42 | self._animate1End = animate1End
43 | self._animate2Start = animate2Start
44 | self._animate2End = animate2End
45 | self._animate3Start = animate3Start
46 | self._animate3End = animate3End
47 |
48 | }
49 |
50 | var body: some View {
51 | VStack {
52 | Spacer()
53 | ZStack {
54 | VStack {
55 | ForEach(context1String, id: \.self) { string in
56 | Text(string)
57 | .fontWeight(.semibold)
58 | .font(.system(.title, design: .rounded))
59 | .multilineTextAlignment(.center)
60 | .opacity(self.animate1Start ? 1 : 0)
61 | .offset(y: self.animate1Start ? 0 : 16)
62 | .offset(y: 10 * CGFloat(self.context1String.firstIndex(of: string)!))
63 | .animation(Animation.easeInOut(duration: 1).delay(
64 | Double(self.context1String.firstIndex(of: string)!)
65 | )
66 | )
67 | }
68 | .opacity(animate2Start ? 0 : 1)
69 | }
70 |
71 | VStack {
72 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
73 | .resizable()
74 | .frame(width: playerSize, height: playerSize)
75 | .opacity(self.animate2Start ? 1 : 0)
76 |
77 | ForEach(context2String, id: \.self) { string in
78 | Text(string)
79 | .fontWeight(.semibold)
80 | .font(.system(.title, design: .rounded))
81 | .multilineTextAlignment(.center)
82 | .opacity(self.animate2Start ? 1 : 0)
83 | .offset(y: self.animate2Start ? 0 : 16)
84 | .offset(y: 10 * CGFloat(self.context2String.firstIndex(of: string)!))
85 | .animation(Animation.easeInOut(duration: 1).delay(
86 | Double(self.context2String.firstIndex(of: string)!)
87 | )
88 | )
89 | }
90 | .opacity(animate3Start ? 0 : 1)
91 | }
92 | }
93 |
94 |
95 | Spacer()
96 |
97 | ButtonStackView1(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End)
98 |
99 | Spacer()
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/StoryTextIntroView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoryTextIntroView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct StoryTextIntroView: View {
12 |
13 | var context1String = ["Sometime in the future,",
14 | "an virus named C9 is emerging worldwide.",
15 | "It has infected a huge amount of people,",
16 | "and everyone is waiting for help.",
17 | "Fortunately, the cure has been developed.",
18 | "",
19 | "However, the research sample has not been delivered",
20 | "to the factory for mass production."]
21 | var context2String = ["You are a Ninja.",
22 | "The people need your help!",
23 | "",
24 | "You need to transport the sample",
25 | "from the lab to the factory."]
26 |
27 | @Binding var animate1Start: Bool
28 | @Binding var animate1End : Bool
29 | @Binding var animate2Start: Bool
30 | @Binding var animate2End : Bool
31 | @Binding var animate3Start: Bool
32 | @Binding var animate3End : Bool
33 |
34 | public init(
35 | animate1Start: Binding,
36 | animate1End: Binding,
37 | animate2Start: Binding,
38 | animate2End: Binding,
39 | animate3Start: Binding,
40 | animate3End: Binding) {
41 | self._animate1Start = animate1Start
42 | self._animate1End = animate1End
43 | self._animate2Start = animate2Start
44 | self._animate2End = animate2End
45 | self._animate3Start = animate3Start
46 | self._animate3End = animate3End
47 |
48 | }
49 |
50 | var body: some View {
51 | VStack {
52 | Spacer()
53 | ZStack {
54 | VStack {
55 | ForEach(context1String, id: \.self) { string in
56 | Text(string)
57 | .fontWeight(.semibold)
58 | .font(.system(.title, design: .rounded))
59 | .multilineTextAlignment(.center)
60 | .opacity(self.animate1Start ? 1 : 0)
61 | .offset(y: self.animate1Start ? 0 : 16)
62 | .offset(y: 10 * CGFloat(self.context1String.firstIndex(of: string)!))
63 | .animation(Animation.easeInOut(duration: 1).delay(
64 | Double(self.context1String.firstIndex(of: string)!)
65 | )
66 | )
67 | }
68 | .opacity(animate2Start ? 0 : 1)
69 | }
70 |
71 | VStack {
72 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
73 | .resizable()
74 | .frame(width: playerSize, height: playerSize)
75 | .opacity(self.animate2Start ? 1 : 0)
76 |
77 | ForEach(context2String, id: \.self) { string in
78 | Text(string)
79 | .fontWeight(.semibold)
80 | .font(.system(.title, design: .rounded))
81 | .multilineTextAlignment(.center)
82 | .opacity(self.animate2Start ? 1 : 0)
83 | .offset(y: self.animate2Start ? 0 : 16)
84 | .offset(y: 10 * CGFloat(self.context2String.firstIndex(of: string)!))
85 | .animation(Animation.easeInOut(duration: 1).delay(
86 | Double(self.context2String.firstIndex(of: string)!)
87 | )
88 | )
89 | }
90 | .opacity(animate3Start ? 0 : 1)
91 | }
92 | }
93 |
94 |
95 | Spacer()
96 |
97 | ButtonStackView1(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End)
98 |
99 | Spacer()
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/6/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import AVFoundation
11 |
12 | // MARK: - MapSetting
13 | struct GroundMapView: View {
14 | var iconSize: CGFloat = mapIconSize
15 |
16 | var body: some View {
17 | VStack {
18 | FactoryView()
19 | .frame(width: iconSize, height: iconSize)
20 | .foregroundColor(.white)
21 |
22 | Spacer()
23 |
24 | LabView()
25 | .frame(width: iconSize, height: iconSize)
26 | }
27 | }
28 | }
29 |
30 | // MARK: - Interface
31 | struct InterfaceView: View {
32 |
33 | @Binding var distance: CGFloat
34 | @Binding var xDistance: CGFloat
35 | @Binding var yDistance: CGFloat
36 | @Binding var isDragging: Bool
37 |
38 | @Binding var gameEnded: Bool
39 |
40 | var body: some View {
41 |
42 | VStack {
43 |
44 | Spacer()
45 |
46 | NPCMap(isDragging: $isDragging)
47 |
48 | PlayerView(inputX: self.$xDistance, inputY: self.$yDistance, showPathPreview: self.$isDragging, gameEnded: $gameEnded, tutorialMode: false)
49 |
50 | }
51 | .background(Color.white.opacity(0.00001))
52 |
53 | }
54 | }
55 |
56 | // MARK: - ContentView
57 | public struct ContentView: View {
58 |
59 | @State private var xDistance: CGFloat = 0
60 | @State private var yDistance: CGFloat = 0
61 | @State private var distance: CGFloat = 0
62 | @State private var isDragging: Bool = false
63 |
64 | @State private var viewW: CGFloat = 400
65 | @State private var viewH: CGFloat = 600
66 |
67 | // @State private var npcCount = npcCount
68 |
69 | @State private var initialized = false
70 |
71 | @State private var gameEnded = false
72 |
73 | public init() {}
74 |
75 | public var body: some View {
76 | ZStack {
77 |
78 | BackgroundView(canvasWidth: $viewW, canvasHeight: $viewH)
79 |
80 | VStack {
81 |
82 | if !initialized {
83 | GeometryReader { geometry in
84 | Button(action: {
85 | // get and setup canvas size
86 | viewWidth = geometry.size.width
87 | viewHeight = geometry.size.height
88 | self.viewW = viewWidth
89 | self.viewH = viewHeight
90 |
91 | // start game timer
92 | startTime = Date()
93 |
94 | // start playing background sound effect
95 | AVAudioPlayer.startPlaySoundBG()
96 |
97 | npcCoords = generateNPCCoords(viewWidth: viewWidth, viewHeight: viewHeight)
98 | self.initialized = true
99 | }) {
100 | Text("Start Game")
101 | .foregroundColor(Color(UIColor.systemBackground))
102 | .fontWeight(.semibold)
103 | .font(.system(.title, design: .rounded))
104 | .multilineTextAlignment(.center)
105 | }
106 | .frame(width: 200, height: 68)
107 | .background(Color(UIColor.systemYellow))
108 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
109 | }
110 |
111 | } else {
112 | InterfaceView(distance: $distance, xDistance: $xDistance, yDistance: $yDistance, isDragging: $isDragging, gameEnded: $gameEnded)
113 | .gesture(DragGesture()
114 | .onChanged({ value in
115 | self.isDragging = true
116 | self.xDistance = value.translation.width
117 | self.yDistance = value.translation.height
118 | self.distance = getLength(x: self.xDistance, y: self.yDistance)
119 |
120 | })
121 | .onEnded({ (value) in
122 | self.isDragging = false
123 | })
124 | )
125 | }
126 |
127 | }
128 | .edgesIgnoringSafeArea(.all)
129 | .blur(radius: gameEnded ? 26 : 0)
130 |
131 | GroundMapView()
132 |
133 | if gameEnded && playerWon {
134 | GameSuccessView()
135 | } else if gameEnded && !playerWon {
136 | GameFailureView()
137 | }
138 |
139 | }
140 |
141 | }
142 |
143 |
144 | }
145 |
146 |
147 | // MARK: - Preview
148 | struct ContentView_Previews: PreviewProvider {
149 | static var previews: some View {
150 | ContentView()
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/6/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import AVFoundation
11 |
12 | // MARK: - MapSetting
13 | struct GroundMapView: View {
14 | var iconSize: CGFloat = mapIconSize
15 |
16 | var body: some View {
17 | VStack {
18 | FactoryView()
19 | .frame(width: iconSize, height: iconSize)
20 | .foregroundColor(.white)
21 |
22 | Spacer()
23 |
24 | LabView()
25 | .frame(width: iconSize, height: iconSize)
26 | }
27 | }
28 | }
29 |
30 | // MARK: - Interface
31 | struct InterfaceView: View {
32 |
33 | @Binding var distance: CGFloat
34 | @Binding var xDistance: CGFloat
35 | @Binding var yDistance: CGFloat
36 | @Binding var isDragging: Bool
37 |
38 | @Binding var gameEnded: Bool
39 |
40 | var body: some View {
41 |
42 | VStack {
43 |
44 | Spacer()
45 |
46 | NPCMap(isDragging: $isDragging)
47 |
48 | PlayerView(inputX: self.$xDistance, inputY: self.$yDistance, showPathPreview: self.$isDragging, gameEnded: $gameEnded, tutorialMode: false)
49 |
50 | }
51 | .background(Color.white.opacity(0.00001))
52 |
53 | }
54 | }
55 |
56 | // MARK: - ContentView
57 | public struct ContentView: View {
58 |
59 | @State private var xDistance: CGFloat = 0
60 | @State private var yDistance: CGFloat = 0
61 | @State private var distance: CGFloat = 0
62 | @State private var isDragging: Bool = false
63 |
64 | @State private var viewW: CGFloat = 400
65 | @State private var viewH: CGFloat = 600
66 |
67 | // @State private var npcCount = npcCount
68 |
69 | @State private var initialized = false
70 |
71 | @State private var gameEnded = false
72 |
73 | public init() {}
74 |
75 | public var body: some View {
76 | ZStack {
77 |
78 | BackgroundView(canvasWidth: $viewW, canvasHeight: $viewH)
79 |
80 | VStack {
81 |
82 | if !initialized {
83 | GeometryReader { geometry in
84 | Button(action: {
85 | // get and setup canvas size
86 | viewWidth = geometry.size.width
87 | viewHeight = geometry.size.height
88 | self.viewW = viewWidth
89 | self.viewH = viewHeight
90 |
91 | // start game timer
92 | startTime = Date()
93 |
94 | // start playing background sound effect
95 | AVAudioPlayer.startPlaySoundBG()
96 |
97 | npcCoords = generateNPCCoords(viewWidth: viewWidth, viewHeight: viewHeight)
98 | self.initialized = true
99 | }) {
100 | Text("Start Game")
101 | .foregroundColor(Color(UIColor.systemBackground))
102 | .fontWeight(.semibold)
103 | .font(.system(.title, design: .rounded))
104 | .multilineTextAlignment(.center)
105 | }
106 | .frame(width: 200, height: 68)
107 | .background(Color(UIColor.systemYellow))
108 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
109 | }
110 |
111 | } else {
112 | InterfaceView(distance: $distance, xDistance: $xDistance, yDistance: $yDistance, isDragging: $isDragging, gameEnded: $gameEnded)
113 | .gesture(DragGesture()
114 | .onChanged({ value in
115 | self.isDragging = true
116 | self.xDistance = value.translation.width
117 | self.yDistance = value.translation.height
118 | self.distance = getLength(x: self.xDistance, y: self.yDistance)
119 |
120 | })
121 | .onEnded({ (value) in
122 | self.isDragging = false
123 | })
124 | )
125 | }
126 |
127 | }
128 | .edgesIgnoringSafeArea(.all)
129 | .blur(radius: gameEnded ? 26 : 0)
130 |
131 | GroundMapView()
132 | .blur(radius: gameEnded ? 26 : 0)
133 |
134 | if gameEnded && playerWon {
135 | GameSuccessView()
136 | } else if gameEnded && !playerWon {
137 | GameFailureView()
138 | }
139 |
140 | }
141 |
142 | }
143 |
144 |
145 | }
146 |
147 |
148 | // MARK: - Preview
149 | struct ContentView_Previews: PreviewProvider {
150 | static var previews: some View {
151 | ContentView()
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/GameSuccessView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameSuccessView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/13/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import AVFoundation
11 |
12 | struct SuccessSignView: View {
13 | var body: some View {
14 | HStack {
15 | Image(systemName: "checkmark.seal.fill")
16 | .resizable()
17 | .frame(width: 30, height: 30)
18 |
19 | Text("You Won")
20 | .fontWeight(.semibold)
21 | .font(.system(.title, design: .rounded))
22 | }
23 | }
24 | }
25 |
26 | struct SuccessRatingView: View {
27 | let starCount: Int
28 | @State var start: Bool = false
29 |
30 | func determineColor(index: Int) -> Color {
31 | if index <= starCount {
32 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 * Double(index)) {
33 | AVAudioPlayer.playSound2(sound: "coin7", type: "wav")
34 | }
35 | return Color.yellow
36 | } else {
37 | return Color.gray
38 | }
39 | }
40 |
41 | func determineOpacity(index: Int) -> Double {
42 | if index <= starCount {
43 | return 1
44 | } else {
45 | return 0
46 | }
47 | }
48 |
49 | func determineAnimation(index: Int) -> Animation {
50 | return Animation.easeOut.delay(Double(index) * 0.3)
51 | }
52 |
53 | var body: some View {
54 | HStack {
55 | ForEach(1..<4, id: \.self) { i in
56 | ZStack {
57 | Image(systemName: "star.circle.fill")
58 | .resizable()
59 | .frame(width: 60, height: 60)
60 | .foregroundColor(self.determineColor(index: i))
61 |
62 | Image(systemName: "star.circle.fill")
63 | .resizable()
64 | .frame(width: 60, height: 60)
65 | .scaleEffect(self.start ? 1.8 : 1)
66 | .opacity(self.start ? 0 : self.determineOpacity(index: i))
67 | .foregroundColor(Color.yellow)
68 | .animation(self.determineAnimation(index: i))
69 | }
70 |
71 |
72 | }
73 |
74 | }
75 | .onAppear {
76 | withAnimation {
77 | self.start = true
78 | }
79 | }
80 | }
81 | }
82 |
83 | public struct GameSuccessView: View {
84 |
85 | @State var appear = false
86 | @State var userDrag = CGSize.zero
87 |
88 | let gameDuration = getGameDuration(from: startTime, to: endTime)
89 |
90 | public init() {}
91 |
92 | let textOption = Int.random(in: 0..<5)
93 | let successSentences: [String] = ["You are a Hero!",
94 | "Lengendary!",
95 | "Congratulations!",
96 | "Good Job!",
97 | "Play of the Game!"]
98 |
99 | public var body: some View {
100 | VStack {
101 | Spacer()
102 | VStack {
103 | Spacer()
104 | SuccessSignView()
105 |
106 | Spacer()
107 | SuccessRatingView(starCount: getGameRating(duration: gameDuration))
108 |
109 | Spacer()
110 | Text(successSentences[textOption])
111 | .fontWeight(.bold)
112 | .font(.system(.largeTitle, design: .rounded))
113 | .multilineTextAlignment(.center)
114 |
115 | Spacer()
116 | }
117 | .frame(width: 420, height: 330)
118 | .background(Color(UIColor.systemBackground))
119 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
120 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16)
121 | .offset(x: 0, y: self.appear ? 0 : 16)
122 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0))
123 | .animation(.easeInOut)
124 |
125 | Spacer()
126 |
127 | Text("To restart the game, \ntap on Playground Start/Stop Button.")
128 | .fontWeight(.semibold)
129 | .font(.system(.footnote, design: .rounded))
130 | .multilineTextAlignment(.center)
131 |
132 | Spacer()
133 | }
134 | .opacity(self.appear ? 1 : 0)
135 | .onAppear {
136 | withAnimation {
137 | self.appear = true
138 | }
139 | AVAudioPlayer.stopPlaySoundBG()
140 | AVAudioPlayer.playSound(sound: "success2", type: "wav")
141 | }
142 | .gesture(
143 | DragGesture().onChanged { value in
144 | self.userDrag = value.translation
145 | }
146 | .onEnded { value in
147 | self.userDrag = .zero
148 | }
149 | )
150 |
151 | }
152 | }
153 |
154 | struct GameSuccessView_Previews: PreviewProvider {
155 | static var previews: some View {
156 | GameSuccessView()
157 | }
158 | }
159 |
160 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/InternalTutorialView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct InTutorialView: View {
4 |
5 | @State private var xDistance: CGFloat = 0
6 | @State private var yDistance: CGFloat = 0
7 | @State private var isDragging: Bool = false
8 |
9 | @State var animate1Start = false
10 | @State var animate1End = false
11 | @State var animate2Start = false
12 | @State var animate2End = false
13 | @State var animate3Start = false
14 | @State var animate3End = false
15 | @State var animate4Start = false
16 | @State var animate4End = false
17 | @State var animate5Start = false
18 | @State var animate5End = false
19 | @State var animate6Start = false
20 |
21 | public init() {}
22 | public var body: some View {
23 | ZStack {
24 | VStack {
25 | FactoryView()
26 | .frame(width: mapIconSize, height: mapIconSize)
27 | .opacity(self.animate2Start ? 1 : 0)
28 |
29 | Spacer()
30 |
31 | LabView()
32 | .frame(width: mapIconSize, height: mapIconSize)
33 | .opacity(self.animate1Start ? 1 : 0)
34 | }
35 | .animation(Animation.easeInOut(duration: 0.8))
36 |
37 | InternalTutorialPart1View(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start)
38 | .opacity((self.animate1Start && !self.animate2End) ? 1 : 0)
39 | .animation(Animation.easeInOut(duration: 0.8))
40 |
41 | InternalTutorialPart2View(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start)
42 | .opacity((self.animate3Start && !self.animate3End) ? 1 : 0)
43 | .animation(Animation.easeInOut(duration: 0.8))
44 |
45 | VStack {
46 | Spacer()
47 |
48 | Text("To reach your destination, \nyou will need to use \"Bink\".\n\nTo move, just drag anywhere on screen, \nninja will blink once you release your finger.\n\nJust TRY IT OUT!")
49 | .fontWeight(.semibold)
50 | .font(.system(.title, design: .rounded))
51 | .multilineTextAlignment(.center)
52 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0)
53 | .animation(Animation.easeInOut(duration: 0.8))
54 |
55 | Spacer()
56 |
57 | PlayerView(inputX: $xDistance, inputY: $yDistance, showPathPreview: $isDragging, gameEnded: .constant(false), tutorialMode: true)
58 |
59 | Spacer()
60 |
61 | Button(action: {
62 | self.animate4End = true
63 | self.animate5Start = true
64 | }) {
65 | Text("Next")
66 | .fontWeight(.semibold)
67 | .font(.system(.title, design: .rounded))
68 | }
69 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0)
70 | .animation(Animation.easeInOut(duration: 0.8))
71 |
72 | Spacer()
73 | }
74 | .background(Color.white.opacity(0.00001))
75 | .gesture(DragGesture()
76 | .onChanged({ value in
77 | self.isDragging = true
78 | self.xDistance = value.translation.width
79 | self.yDistance = value.translation.height
80 | })
81 | .onEnded({ (value) in
82 | self.isDragging = false
83 | })
84 | )
85 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0)
86 |
87 | InternalTutorialPart3View(animate5End: $animate5End, animate6Start: $animate6Start)
88 | .opacity((self.animate5Start && !self.animate5End) ? 1 : 0)
89 | .animation(Animation.easeInOut(duration: 0.8))
90 |
91 | VStack {
92 | Spacer()
93 | Spacer()
94 | Text("That's all.\nIt's your time to save the world.")
95 | .fontWeight(.semibold)
96 | .font(.system(.title, design: .rounded))
97 | .multilineTextAlignment(.center)
98 | .opacity(self.animate6Start ? 1 : 0)
99 | Spacer()
100 | Text("Go to the Next page to start.")
101 | .fontWeight(.semibold)
102 | .font(.system(.callout, design: .rounded))
103 | .multilineTextAlignment(.center)
104 | .opacity(self.animate6Start ? 1 : 0)
105 | Spacer()
106 | }
107 | .opacity(self.animate6Start ? 1 : 0)
108 | .animation(Animation.easeInOut(duration: 0.8))
109 |
110 | }
111 | .onAppear {
112 | self.animate1Start = true
113 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
114 | self.animate2Start = true
115 | }
116 | }
117 |
118 |
119 | }
120 |
121 | }
122 |
123 | struct InTutorialView_Previews: PreviewProvider {
124 | static var previews: some View {
125 | InTutorialView()
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/GameSuccessView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameSuccessView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/13/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import AVFoundation
11 |
12 | struct SuccessSignView: View {
13 | var body: some View {
14 | HStack {
15 | Image(systemName: "checkmark.seal.fill")
16 | .resizable()
17 | .frame(width: 30, height: 30)
18 |
19 | Text("You Won")
20 | .fontWeight(.semibold)
21 | .font(.system(.title, design: .rounded))
22 | }
23 | }
24 | }
25 |
26 | struct SuccessRatingView: View {
27 | let starCount: Int
28 | @State var start: Bool = false
29 |
30 | func determineColor(index: Int) -> Color {
31 | if index <= starCount {
32 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 * Double(index)) {
33 | AVAudioPlayer.playSound2(sound: "coin7", type: "wav")
34 | }
35 | return Color.yellow
36 | } else {
37 | return Color.gray
38 | }
39 | }
40 |
41 | func determineOpacity(index: Int) -> Double {
42 | if index <= starCount {
43 | return 1
44 | } else {
45 | return 0
46 | }
47 | }
48 |
49 | func determineAnimation(index: Int) -> Animation {
50 | return Animation.easeOut.delay(Double(index) * 0.3)
51 | }
52 |
53 | var body: some View {
54 | HStack {
55 | ForEach(1..<4, id: \.self) { i in
56 | ZStack {
57 | Image(systemName: "star.circle.fill")
58 | .resizable()
59 | .frame(width: 60, height: 60)
60 | .foregroundColor(self.determineColor(index: i))
61 |
62 | Image(systemName: "star.circle.fill")
63 | .resizable()
64 | .frame(width: 60, height: 60)
65 | .scaleEffect(self.start ? 1.8 : 1)
66 | .opacity(self.start ? 0 : self.determineOpacity(index: i))
67 | .foregroundColor(Color.yellow)
68 | .animation(self.determineAnimation(index: i))
69 | }
70 |
71 |
72 | }
73 |
74 | }
75 | .onAppear {
76 | withAnimation {
77 | self.start = true
78 | }
79 | }
80 | }
81 | }
82 |
83 | public struct GameSuccessView: View {
84 |
85 | @State var appear = false
86 | @State var userDrag = CGSize.zero
87 |
88 | let gameDuration = getGameDuration(from: startTime, to: endTime)
89 |
90 | public init() {}
91 |
92 | let textOption = Int.random(in: 0..<5)
93 | let successSentences: [String] = ["You are a Hero!",
94 | "Lengendary!",
95 | "Congratulations!",
96 | "Good Job!",
97 | "Play of the Game!"]
98 |
99 | public var body: some View {
100 | VStack {
101 | Spacer()
102 | VStack {
103 | Spacer()
104 | SuccessSignView()
105 |
106 | Spacer()
107 | SuccessRatingView(starCount: getGameRating(duration: gameDuration))
108 |
109 | Spacer()
110 | Text(successSentences[textOption])
111 | .fontWeight(.bold)
112 | .font(.system(.largeTitle, design: .rounded))
113 | .multilineTextAlignment(.center)
114 |
115 | Spacer()
116 | }
117 | .frame(width: 420, height: 330)
118 | .background(Color(UIColor.systemBackground))
119 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
120 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16)
121 | .offset(x: 0, y: self.appear ? 0 : 16)
122 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0))
123 | .animation(.easeInOut)
124 |
125 | Spacer()
126 |
127 | Text("To restart the game, \ntap on Playground Start/Stop Button.")
128 | .fontWeight(.semibold)
129 | .font(.system(.footnote, design: .rounded))
130 | .multilineTextAlignment(.center)
131 |
132 | Spacer()
133 | }
134 | .opacity(self.appear ? 1 : 0)
135 | .onAppear {
136 | withAnimation {
137 | self.appear = true
138 | }
139 | AVAudioPlayer.stopPlaySoundBG()
140 | AVAudioPlayer.playSound(sound: "success2", type: "wav")
141 | }
142 | .gesture(
143 | DragGesture().onChanged { value in
144 | self.userDrag = value.translation
145 | }
146 | .onEnded { value in
147 | self.userDrag = .zero
148 | }
149 | )
150 |
151 | }
152 | }
153 |
154 | struct GameSuccessView_Previews: PreviewProvider {
155 | static var previews: some View {
156 | GameSuccessView()
157 | }
158 | }
159 |
160 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/InternalTutorialView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct InTutorialView: View {
4 |
5 | @State private var xDistance: CGFloat = 0
6 | @State private var yDistance: CGFloat = 0
7 | @State private var isDragging: Bool = false
8 |
9 | @State var animate1Start = false
10 | @State var animate1End = false
11 | @State var animate2Start = false
12 | @State var animate2End = false
13 | @State var animate3Start = false
14 | @State var animate3End = false
15 | @State var animate4Start = false
16 | @State var animate4End = false
17 | @State var animate5Start = false
18 | @State var animate5End = false
19 | @State var animate6Start = false
20 |
21 | public init() {}
22 | public var body: some View {
23 | ZStack {
24 | VStack {
25 | FactoryView()
26 | .frame(width: mapIconSize, height: mapIconSize)
27 | .opacity(self.animate2Start ? 1 : 0)
28 |
29 | Spacer()
30 |
31 | LabView()
32 | .frame(width: mapIconSize, height: mapIconSize)
33 | .opacity(self.animate1Start ? 1 : 0)
34 | }
35 | .animation(Animation.easeInOut(duration: 0.8))
36 |
37 | InternalTutorialPart1View(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start)
38 | .opacity((self.animate1Start && !self.animate2End) ? 1 : 0)
39 | .animation(Animation.easeInOut(duration: 0.8))
40 |
41 | InternalTutorialPart2View(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start)
42 | .opacity((self.animate3Start && !self.animate3End) ? 1 : 0)
43 | .animation(Animation.easeInOut(duration: 0.8))
44 |
45 | VStack {
46 | Spacer()
47 |
48 | Text("To reach your destination, \nyou will need to use \"Bink\".\n\nTo move, just drag anywhere on screen, \nninja will blink once you release your finger.\n\nJust TRY IT OUT!")
49 | .fontWeight(.semibold)
50 | .font(.system(.title, design: .rounded))
51 | .multilineTextAlignment(.center)
52 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0)
53 | .animation(Animation.easeInOut(duration: 0.8))
54 |
55 | Spacer()
56 |
57 | PlayerView(inputX: $xDistance, inputY: $yDistance, showPathPreview: $isDragging, gameEnded: .constant(false), tutorialMode: true)
58 |
59 | Spacer()
60 |
61 | Button(action: {
62 | self.animate4End = true
63 | self.animate5Start = true
64 | }) {
65 | Text("Next")
66 | .fontWeight(.semibold)
67 | .font(.system(.title, design: .rounded))
68 | }
69 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0)
70 | .animation(Animation.easeInOut(duration: 0.8))
71 |
72 | Spacer()
73 | }
74 | .background(Color.white.opacity(0.00001))
75 | .gesture(DragGesture()
76 | .onChanged({ value in
77 | self.isDragging = true
78 | self.xDistance = value.translation.width
79 | self.yDistance = value.translation.height
80 | })
81 | .onEnded({ (value) in
82 | self.isDragging = false
83 | })
84 | )
85 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0)
86 |
87 | InternalTutorialPart3View(animate5End: $animate5End, animate6Start: $animate6Start)
88 | .opacity((self.animate5Start && !self.animate5End) ? 1 : 0)
89 | .animation(Animation.easeInOut(duration: 0.8))
90 |
91 | VStack {
92 | Spacer()
93 | Spacer()
94 | Text("That's all.\nIt's your time to save the world.")
95 | .fontWeight(.semibold)
96 | .font(.system(.title, design: .rounded))
97 | .multilineTextAlignment(.center)
98 | .opacity(self.animate6Start ? 1 : 0)
99 | Spacer()
100 | Text("Go to the Next page to start.")
101 | .fontWeight(.semibold)
102 | .font(.system(.callout, design: .rounded))
103 | .multilineTextAlignment(.center)
104 | .opacity(self.animate6Start ? 1 : 0)
105 | Spacer()
106 | }
107 | .opacity(self.animate6Start ? 1 : 0)
108 | .animation(Animation.easeInOut(duration: 0.8))
109 |
110 | }
111 | .onAppear {
112 | self.animate1Start = true
113 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
114 | self.animate2Start = true
115 | }
116 | }
117 |
118 |
119 | }
120 |
121 | }
122 |
123 | struct InTutorialView_Previews: PreviewProvider {
124 | static var previews: some View {
125 | InTutorialView()
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/InternalTutorialPartialView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InternalTutorialPartialView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct InternalTutorialPart1View: View {
12 |
13 | @Binding var animate1Start: Bool
14 | @Binding var animate1End : Bool
15 | @Binding var animate2Start: Bool
16 | @Binding var animate2End : Bool
17 | @Binding var animate3Start: Bool
18 |
19 | public init(
20 | animate1Start: Binding,
21 | animate1End: Binding,
22 | animate2Start: Binding,
23 | animate2End: Binding,
24 | animate3Start: Binding) {
25 | self._animate1Start = animate1Start
26 | self._animate1End = animate1End
27 | self._animate2Start = animate2Start
28 | self._animate2End = animate2End
29 | self._animate3Start = animate3Start
30 |
31 | }
32 |
33 | public var body: some View {
34 | VStack {
35 | Spacer()
36 | Text("Your destination is the factory, \non the top of your screen.")
37 | .fontWeight(.semibold)
38 | .font(.system(.title, design: .rounded))
39 | .multilineTextAlignment(.center)
40 | .opacity((self.animate2Start && !self.animate2End) ? 1 : 0)
41 |
42 | Spacer()
43 |
44 | Button(action: {
45 | self.animate1End = true
46 | self.animate2End = true
47 | self.animate3Start = true
48 | }) {
49 | Text("Next")
50 | .fontWeight(.semibold)
51 | .font(.system(.title, design: .rounded))
52 | }
53 | .opacity((animate2Start && !animate1End) ? 1 : 0)
54 | .animation(Animation.easeInOut)
55 |
56 | Spacer()
57 |
58 | Text("You will start off at the Lab, \non the bottom of your screen.")
59 | .fontWeight(.semibold)
60 | .font(.system(.title, design: .rounded))
61 | .multilineTextAlignment(.center)
62 | .opacity((self.animate1Start && !self.animate1End) ? 1 : 0)
63 | Spacer()
64 | }
65 | }
66 | }
67 |
68 | public struct InternalTutorialPart2View: View {
69 |
70 | @Binding var animate3Start: Bool
71 | @Binding var animate3End : Bool
72 | @Binding var animate4Start: Bool
73 |
74 | public init(
75 | animate3Start: Binding,
76 | animate3End: Binding,
77 | animate4Start: Binding) {
78 |
79 | self._animate3Start = animate3Start
80 | self._animate3End = animate3End
81 | self._animate4Start = animate4Start
82 |
83 | }
84 |
85 | public var body: some View {
86 | VStack {
87 | Spacer()
88 | Text("There will be people walking around.\nBeware that you don't know about \ntheir health conditions, \nand most of them are infected.")
89 | .fontWeight(.semibold)
90 | .font(.system(.title, design: .rounded))
91 | .multilineTextAlignment(.center)
92 | .animation(Animation.easeInOut(duration: 0.8))
93 |
94 | HStack {
95 | NPCInternalView(isDragging: .constant(false))
96 | NPCInternalView(isDragging: .constant(false))
97 | NPCInternalView(isDragging: .constant(false))
98 | }
99 | .animation(Animation.easeInOut(duration: 0.8))
100 |
101 | Text("To protect yourself, \nkeep at lease 6 feet away from others!")
102 | .fontWeight(.semibold)
103 | .font(.system(.title, design: .rounded))
104 | .multilineTextAlignment(.center)
105 | .animation(Animation.easeInOut(duration: 0.8))
106 |
107 | Spacer()
108 |
109 | Button(action: {
110 | self.animate3End = true
111 | self.animate4Start = true
112 | }) {
113 | Text("Next")
114 | .fontWeight(.semibold)
115 | .font(.system(.title, design: .rounded))
116 | }
117 | .animation(Animation.easeInOut)
118 |
119 | Spacer()
120 | }
121 | }
122 | }
123 |
124 | public struct InternalTutorialPart3View: View {
125 |
126 | @Binding var animate5End : Bool
127 | @Binding var animate6Start: Bool
128 |
129 | public init(
130 | animate5End: Binding,
131 | animate6Start: Binding) {
132 |
133 | self._animate5End = animate5End
134 | self._animate6Start = animate6Start
135 |
136 | }
137 |
138 | public var body: some View {
139 | VStack {
140 | Spacer()
141 | Text("When you are trying to blink, \nyou will freeze the time and see \nthe 6-feet safety distance around other people. \nPedestrians will freeze until you complete your \"Blink\"")
142 | .fontWeight(.semibold)
143 | .font(.system(.title, design: .rounded))
144 | .multilineTextAlignment(.center)
145 | .animation(Animation.easeInOut(duration: 0.8))
146 |
147 | Spacer()
148 |
149 | HStack {
150 | Spacer()
151 | NPCInternalView(isDragging: .constant(true))
152 | Spacer()
153 | NPCInternalView(isDragging: .constant(true))
154 | Spacer()
155 | NPCInternalView(isDragging: .constant(true))
156 | Spacer()
157 | }
158 | .animation(Animation.easeInOut(duration: 0.8))
159 |
160 | Spacer()
161 |
162 | Text("Remember, \nkeep at lease 6 feet away from others!")
163 | .fontWeight(.semibold)
164 | .font(.system(.title, design: .rounded))
165 | .multilineTextAlignment(.center)
166 | .animation(Animation.easeInOut(duration: 0.8))
167 |
168 | Spacer()
169 |
170 | Button(action: {
171 | self.animate5End = true
172 | self.animate6Start = true
173 | }) {
174 | Text("Next")
175 | .fontWeight(.semibold)
176 | .font(.system(.title, design: .rounded))
177 | }
178 | .animation(Animation.easeInOut)
179 |
180 | Spacer()
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/InternalTutorialPartialView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InternalTutorialPartialView.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/17/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct InternalTutorialPart1View: View {
12 |
13 | @Binding var animate1Start: Bool
14 | @Binding var animate1End : Bool
15 | @Binding var animate2Start: Bool
16 | @Binding var animate2End : Bool
17 | @Binding var animate3Start: Bool
18 |
19 | public init(
20 | animate1Start: Binding,
21 | animate1End: Binding,
22 | animate2Start: Binding,
23 | animate2End: Binding,
24 | animate3Start: Binding) {
25 | self._animate1Start = animate1Start
26 | self._animate1End = animate1End
27 | self._animate2Start = animate2Start
28 | self._animate2End = animate2End
29 | self._animate3Start = animate3Start
30 |
31 | }
32 |
33 | public var body: some View {
34 | VStack {
35 | Spacer()
36 | Text("Your destination is the factory, \non the top of your screen.")
37 | .fontWeight(.semibold)
38 | .font(.system(.title, design: .rounded))
39 | .multilineTextAlignment(.center)
40 | .opacity((self.animate2Start && !self.animate2End) ? 1 : 0)
41 |
42 | Spacer()
43 |
44 | Button(action: {
45 | self.animate1End = true
46 | self.animate2End = true
47 | self.animate3Start = true
48 | }) {
49 | Text("Next")
50 | .fontWeight(.semibold)
51 | .font(.system(.title, design: .rounded))
52 | }
53 | .opacity((animate2Start && !animate1End) ? 1 : 0)
54 | .animation(Animation.easeInOut)
55 |
56 | Spacer()
57 |
58 | Text("You will start off at the Lab, \non the bottom of your screen.")
59 | .fontWeight(.semibold)
60 | .font(.system(.title, design: .rounded))
61 | .multilineTextAlignment(.center)
62 | .opacity((self.animate1Start && !self.animate1End) ? 1 : 0)
63 | Spacer()
64 | }
65 | }
66 | }
67 |
68 | public struct InternalTutorialPart2View: View {
69 |
70 | @Binding var animate3Start: Bool
71 | @Binding var animate3End : Bool
72 | @Binding var animate4Start: Bool
73 |
74 | public init(
75 | animate3Start: Binding,
76 | animate3End: Binding,
77 | animate4Start: Binding) {
78 |
79 | self._animate3Start = animate3Start
80 | self._animate3End = animate3End
81 | self._animate4Start = animate4Start
82 |
83 | }
84 |
85 | public var body: some View {
86 | VStack {
87 | Spacer()
88 | Text("There will be people walking around.\nBeware that you don't know about \ntheir health conditions, \nand most of them are infected.")
89 | .fontWeight(.semibold)
90 | .font(.system(.title, design: .rounded))
91 | .multilineTextAlignment(.center)
92 | .animation(Animation.easeInOut(duration: 0.8))
93 |
94 | HStack {
95 | NPCInternalView(isDragging: .constant(false))
96 | NPCInternalView(isDragging: .constant(false))
97 | NPCInternalView(isDragging: .constant(false))
98 | }
99 | .animation(Animation.easeInOut(duration: 0.8))
100 |
101 | Text("To protect yourself, \nkeep at lease 6 feet away from others!")
102 | .fontWeight(.semibold)
103 | .font(.system(.title, design: .rounded))
104 | .multilineTextAlignment(.center)
105 | .animation(Animation.easeInOut(duration: 0.8))
106 |
107 | Spacer()
108 |
109 | Button(action: {
110 | self.animate3End = true
111 | self.animate4Start = true
112 | }) {
113 | Text("Next")
114 | .fontWeight(.semibold)
115 | .font(.system(.title, design: .rounded))
116 | }
117 | .animation(Animation.easeInOut)
118 |
119 | Spacer()
120 | }
121 | }
122 | }
123 |
124 | public struct InternalTutorialPart3View: View {
125 |
126 | @Binding var animate5End : Bool
127 | @Binding var animate6Start: Bool
128 |
129 | public init(
130 | animate5End: Binding,
131 | animate6Start: Binding) {
132 |
133 | self._animate5End = animate5End
134 | self._animate6Start = animate6Start
135 |
136 | }
137 |
138 | public var body: some View {
139 | VStack {
140 | Spacer()
141 | Text("When you are trying to blink, \nyou will freeze the time and see \nthe 6-feet safety distance around other people. \nPedestrians will freeze until you complete your \"Blink\"")
142 | .fontWeight(.semibold)
143 | .font(.system(.title, design: .rounded))
144 | .multilineTextAlignment(.center)
145 | .animation(Animation.easeInOut(duration: 0.8))
146 |
147 | Spacer()
148 |
149 | HStack {
150 | Spacer()
151 | NPCInternalView(isDragging: .constant(true))
152 | Spacer()
153 | NPCInternalView(isDragging: .constant(true))
154 | Spacer()
155 | NPCInternalView(isDragging: .constant(true))
156 | Spacer()
157 | }
158 | .animation(Animation.easeInOut(duration: 0.8))
159 |
160 | Spacer()
161 |
162 | Text("Remember, \nkeep at lease 6 feet away from others!")
163 | .fontWeight(.semibold)
164 | .font(.system(.title, design: .rounded))
165 | .multilineTextAlignment(.center)
166 | .animation(Animation.easeInOut(duration: 0.8))
167 |
168 | Spacer()
169 |
170 | Button(action: {
171 | self.animate5End = true
172 | self.animate6Start = true
173 | }) {
174 | Text("Next")
175 | .fontWeight(.semibold)
176 | .font(.system(.title, design: .rounded))
177 | }
178 | .animation(Animation.easeInOut)
179 |
180 | Spacer()
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge/Views/PlayerUI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlayerUI.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 | import AVFoundation
12 |
13 | // MARK: - Player
14 | struct MovePathIndicatorView: View {
15 | @Binding var showPathPreview: Bool
16 | @State var destCoord: CGSize
17 |
18 | var body: some View {
19 | Image(systemName: "xmark.circle")
20 | .resizable()
21 | .frame(width: playerSize, height: playerSize)
22 | .offset(destCoord)
23 | .foregroundColor(Color.blue.opacity(showPathPreview ? 1 : 0))
24 | .font(.system(size: 25, weight: .bold))
25 | }
26 | }
27 |
28 | public struct PlayerView: View {
29 |
30 | @Binding var inputX: CGFloat
31 | @Binding var inputY: CGFloat
32 | @Binding var showPathPreview: Bool
33 | @Binding var gameEnded: Bool
34 |
35 | @State var playerFailed = false
36 | @State var invalidMoveCount: Int = 0
37 |
38 | var tutorialMode: Bool
39 |
40 | public init(inputX: Binding, inputY: Binding, showPathPreview: Binding, gameEnded: Binding, tutorialMode: Bool) {
41 | self._inputX = inputX
42 | self._inputY = inputY
43 | self._showPathPreview = showPathPreview
44 | self._gameEnded = gameEnded
45 | self.tutorialMode = tutorialMode
46 | }
47 |
48 |
49 | public var body: some View {
50 |
51 | ZStack {
52 |
53 | // path preview
54 | Image(systemName: "xmark.circle")
55 | .resizable()
56 | .frame(width: playerSize, height: playerSize)
57 | .offset(destinationPreviewCalculation(inputX: inputX, inputY: inputY))
58 | .foregroundColor(playerPathColor.opacity(showPathPreview ? 1 : 0))
59 | .font(.system(size: 25, weight: .bold))
60 |
61 | // player icon
62 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
63 | .resizable()
64 | .frame(width: playerSize, height: playerSize)
65 | // .foregroundColor(playerColor)
66 | // invalid move warning indication
67 | .modifier(Shake(animatableData: CGFloat(invalidMoveCount)))
68 | // movement animation
69 | .animation(.playerMove)
70 | .overlay(
71 | Circle()
72 | .foregroundColor(Color(UIColor.systemRed))
73 | .frame(width: playerSize, height: playerSize)
74 | .opacity((!self.tutorialMode && self.playerFailed) ? 0.9 : 0)
75 | .animation(.easeInOut)
76 | )
77 |
78 | }
79 | .offset(showPathPreview ? newPosition : playerMove())
80 |
81 | }
82 |
83 | private func playerMove() -> CGSize {
84 |
85 | if tutorialMode {
86 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY)
87 |
88 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height)
89 |
90 | AVAudioPlayer.playSound(sound: "jump", type: "wav")
91 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2)
92 | newPosition = currentPosition
93 |
94 | return newPosition
95 | }
96 |
97 | if endOnHold {
98 | return newPosition
99 | }
100 |
101 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY)
102 |
103 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height)
104 |
105 | if playerOutOfView() {
106 |
107 | // use async to prevent updating state variable during UI rerendering
108 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
109 | // trigger shaking animation to indicate invalid move warning
110 | withAnimation(.default) {
111 | AVAudioPlayer.playSound(sound: "error1", type: "wav")
112 | self.invalidMoveCount += 1
113 | self.showPathPreview = true
114 | }
115 | }
116 | return newPosition
117 | } else {
118 | // valid move
119 | AVAudioPlayer.playSound(sound: "jump", type: "wav")
120 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2)
121 | newPosition = currentPosition
122 | }
123 |
124 | if started {
125 | let gameStatus = gameStateCheck()
126 | if gameStatus.ended {
127 | // game ended
128 | endOnHold = true
129 | endTime = Date()
130 |
131 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
132 | self.gameEnded = true
133 | }
134 |
135 | if gameStatus.succeeded {
136 | // player won
137 | playerWon = true
138 | } else {
139 | // player failed
140 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
141 | self.playerFailed = true
142 | }
143 |
144 | playerWon = false
145 |
146 | }
147 | } else {
148 | // game still going on
149 | }
150 | }
151 |
152 | started = true
153 |
154 | return newPosition
155 | }
156 |
157 | private func destinationPreviewCalculation(inputX: CGFloat, inputY: CGFloat) -> CGSize {
158 | let vectorDirectoin = CGSize(width: -inputX, height: -inputY)
159 |
160 | var vX = vectorDirectoin.width
161 | var vY = vectorDirectoin.height
162 |
163 | let const: CGFloat = 10
164 |
165 | if vX <= 0 {
166 | vX = -calculateMoveEstimate(input: -vX) * const
167 | } else {
168 | vX = calculateMoveEstimate(input: vX) * const
169 | }
170 |
171 | if vY <= 0 {
172 | vY = -calculateMoveEstimate(input: -vY) * const
173 | } else {
174 | vY = calculateMoveEstimate(input: vY) * const
175 | }
176 |
177 | let result = CGSize(width: vX, height: vY)
178 |
179 | return result
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/PlayerUI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlayerUI.swift
3 | // WWDC20PlaygroundTest
4 | //
5 | // Created by Tony Tang on 5/15/20.
6 | // Copyright © 2020 TonyTang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 | import AVFoundation
12 |
13 | // MARK: - Player
14 | struct MovePathIndicatorView: View {
15 | @Binding var showPathPreview: Bool
16 | @State var destCoord: CGSize
17 |
18 | var body: some View {
19 | Image(systemName: "xmark.circle")
20 | .resizable()
21 | .frame(width: playerSize, height: playerSize)
22 | .offset(destCoord)
23 | .foregroundColor(Color.blue.opacity(showPathPreview ? 1 : 0))
24 | .font(.system(size: 25, weight: .bold))
25 | }
26 | }
27 |
28 | public struct PlayerView: View {
29 |
30 | @Binding var inputX: CGFloat
31 | @Binding var inputY: CGFloat
32 | @Binding var showPathPreview: Bool
33 | @Binding var gameEnded: Bool
34 |
35 | @State var playerFailed = false
36 | @State var invalidMoveCount: Int = 0
37 |
38 | var tutorialMode: Bool
39 |
40 | public init(inputX: Binding, inputY: Binding, showPathPreview: Binding, gameEnded: Binding, tutorialMode: Bool) {
41 | self._inputX = inputX
42 | self._inputY = inputY
43 | self._showPathPreview = showPathPreview
44 | self._gameEnded = gameEnded
45 | self.tutorialMode = tutorialMode
46 | }
47 |
48 |
49 | public var body: some View {
50 |
51 | ZStack {
52 |
53 | // path preview
54 | Image(systemName: "xmark.circle")
55 | .resizable()
56 | .frame(width: playerSize, height: playerSize)
57 | .offset(destinationPreviewCalculation(inputX: inputX, inputY: inputY))
58 | .foregroundColor(playerPathColor.opacity(showPathPreview ? 1 : 0))
59 | .font(.system(size: 25, weight: .bold))
60 |
61 | // player icon
62 | Image(uiImage: UIImage(named: "Ninja_Circle")!)
63 | .resizable()
64 | .frame(width: playerSize, height: playerSize)
65 | // .foregroundColor(playerColor)
66 | // invalid move warning indication
67 | .modifier(Shake(animatableData: CGFloat(invalidMoveCount)))
68 | // movement animation
69 | .animation(.playerMove)
70 | .overlay(
71 | Circle()
72 | .foregroundColor(Color(UIColor.systemRed))
73 | .frame(width: playerSize, height: playerSize)
74 | .opacity((!self.tutorialMode && self.playerFailed) ? 0.9 : 0)
75 | .animation(.easeInOut)
76 | )
77 |
78 | }
79 | .offset(showPathPreview ? newPosition : playerMove())
80 |
81 | }
82 |
83 | private func playerMove() -> CGSize {
84 |
85 | if tutorialMode {
86 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY)
87 |
88 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height)
89 |
90 | AVAudioPlayer.playSound(sound: "jump", type: "wav")
91 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2)
92 | newPosition = currentPosition
93 |
94 | return newPosition
95 | }
96 |
97 | if endOnHold {
98 | return newPosition
99 | }
100 |
101 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY)
102 |
103 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height)
104 |
105 | if playerOutOfView() {
106 | return newPosition
107 | // use async to prevent updating state variable during UI rerendering
108 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
109 | // trigger shaking animation to indicate invalid move warning
110 | withAnimation(.default) {
111 | AVAudioPlayer.playSound(sound: "error1", type: "wav")
112 | self.invalidMoveCount += 1
113 | self.showPathPreview = true
114 | }
115 | }
116 |
117 | } else {
118 | // valid move
119 | AVAudioPlayer.playSound(sound: "jump", type: "wav")
120 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2)
121 | newPosition = currentPosition
122 | }
123 |
124 | if started {
125 | let gameStatus = gameStateCheck()
126 | if gameStatus.ended {
127 | // game ended
128 | endOnHold = true
129 | endTime = Date()
130 |
131 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
132 | self.gameEnded = true
133 | }
134 |
135 | if gameStatus.succeeded {
136 | // player won
137 | playerWon = true
138 | } else {
139 | // player failed
140 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
141 | self.playerFailed = true
142 | }
143 |
144 | playerWon = false
145 |
146 | }
147 | } else {
148 | // game still going on
149 | }
150 | }
151 |
152 | started = true
153 |
154 | return newPosition
155 | }
156 |
157 | private func destinationPreviewCalculation(inputX: CGFloat, inputY: CGFloat) -> CGSize {
158 | let vectorDirectoin = CGSize(width: -inputX, height: -inputY)
159 |
160 | var vX = vectorDirectoin.width
161 | var vY = vectorDirectoin.height
162 |
163 | let const: CGFloat = 10
164 |
165 | if vX <= 0 {
166 | vX = -calculateMoveEstimate(input: -vX) * const
167 | } else {
168 | vX = calculateMoveEstimate(input: vX) * const
169 | }
170 |
171 | if vY <= 0 {
172 | vY = -calculateMoveEstimate(input: -vY) * const
173 | } else {
174 | vY = calculateMoveEstimate(input: vY) * const
175 | }
176 |
177 | let result = CGSize(width: vX, height: vY)
178 |
179 | return result
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | EA078F902471365700B31133 /* InternalTutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA078F8F2471365700B31133 /* InternalTutorialView.swift */; };
11 | EA2618602462A7E300ADFE5C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA26185F2462A7E300ADFE5C /* AppDelegate.swift */; };
12 | EA2618622462A7E300ADFE5C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2618612462A7E300ADFE5C /* SceneDelegate.swift */; };
13 | EA2618652462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = EA2618632462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld */; };
14 | EA2618672462A7E300ADFE5C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2618662462A7E300ADFE5C /* ContentView.swift */; };
15 | EA2618692462A7EA00ADFE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA2618682462A7EA00ADFE5C /* Assets.xcassets */; };
16 | EA26186C2462A7EA00ADFE5C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA26186B2462A7EA00ADFE5C /* Preview Assets.xcassets */; };
17 | EA26186F2462A7EA00ADFE5C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EA26186D2462A7EA00ADFE5C /* LaunchScreen.storyboard */; };
18 | EA29397D246FC55A00B8A898 /* BackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA29397C246FC55A00B8A898 /* BackgroundView.swift */; };
19 | EA293981246FCBDA00B8A898 /* jump.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29397E246FCBDA00B8A898 /* jump.wav */; };
20 | EA293982246FCBDA00B8A898 /* error2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29397F246FCBDA00B8A898 /* error2.wav */; };
21 | EA293983246FCBDA00B8A898 /* error1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293980246FCBDA00B8A898 /* error1.wav */; };
22 | EA293985246FCC4900B8A898 /* MediaSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA293984246FCC4900B8A898 /* MediaSupport.swift */; };
23 | EA29398E246FE60000B8A898 /* success2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293986246FE60000B8A898 /* success2.wav */; };
24 | EA29398F246FE60000B8A898 /* success4.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293987246FE60000B8A898 /* success4.wav */; };
25 | EA293990246FE60000B8A898 /* coin3.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293988246FE60000B8A898 /* coin3.wav */; };
26 | EA293991246FE60000B8A898 /* coin1.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EA293989246FE60000B8A898 /* coin1.mp3 */; };
27 | EA293992246FE60000B8A898 /* success3.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398A246FE60000B8A898 /* success3.wav */; };
28 | EA293993246FE60000B8A898 /* success1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398B246FE60000B8A898 /* success1.wav */; };
29 | EA293994246FE60000B8A898 /* coin4.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398C246FE60000B8A898 /* coin4.wav */; };
30 | EA293995246FE60000B8A898 /* coin2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398D246FE60000B8A898 /* coin2.wav */; };
31 | EA293999246FE92800B8A898 /* failure3.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293996246FE92800B8A898 /* failure3.wav */; };
32 | EA29399A246FE92800B8A898 /* failure1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293997246FE92800B8A898 /* failure1.wav */; };
33 | EA29399B246FE92800B8A898 /* failure2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293998246FE92800B8A898 /* failure2.wav */; };
34 | EA29399D2470B2A300B8A898 /* coin5.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29399C2470B2A300B8A898 /* coin5.wav */; };
35 | EA29399F2470B30A00B8A898 /* coin6.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29399E2470B30A00B8A898 /* coin6.wav */; };
36 | EA2939A12470B33D00B8A898 /* coin7.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA2939A02470B33D00B8A898 /* coin7.wav */; };
37 | EA2939A32470B76500B8A898 /* clock1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA2939A22470B76500B8A898 /* clock1.wav */; };
38 | EA2CE767246CB775004B8A34 /* GameSuccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2CE766246CB775004B8A34 /* GameSuccessView.swift */; };
39 | EA2CE769246CB77F004B8A34 /* GameFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2CE768246CB77F004B8A34 /* GameFailureView.swift */; };
40 | EA49601324713B0E00EBDA55 /* ButtonStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601224713B0E00EBDA55 /* ButtonStackView.swift */; };
41 | EA49601524713BD900EBDA55 /* BriefLogisticExplanationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601424713BD900EBDA55 /* BriefLogisticExplanationView.swift */; };
42 | EA49601724713D1100EBDA55 /* StoryTextIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601624713D1100EBDA55 /* StoryTextIntroView.swift */; };
43 | EA49601924713E9A00EBDA55 /* InternalTutorialPartialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601824713E9A00EBDA55 /* InternalTutorialPartialView.swift */; };
44 | EA49601B2471468500EBDA55 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601A2471468500EBDA55 /* IconView.swift */; };
45 | EA7AF2C6246F9F360004BDC4 /* Calculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2C5246F9F360004BDC4 /* Calculation.swift */; };
46 | EA7AF2C8246F9FFD0004BDC4 /* GameLogistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2C7246F9FFD0004BDC4 /* GameLogistics.swift */; };
47 | EA7AF2CA246FA0180004BDC4 /* AnimationPresets.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2C9246FA0180004BDC4 /* AnimationPresets.swift */; };
48 | EA7AF2CC246FA1530004BDC4 /* NPCUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2CB246FA1530004BDC4 /* NPCUI.swift */; };
49 | EA7AF2CE246FA1A90004BDC4 /* PlayerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2CD246FA1A90004BDC4 /* PlayerUI.swift */; };
50 | EA8B674A2470E7C400240747 /* factory2.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67462470E7C300240747 /* factory2.png */; };
51 | EA8B674B2470E7C400240747 /* wash_hand.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67472470E7C400240747 /* wash_hand.png */; };
52 | EA8B674C2470E7C400240747 /* humanw_mask.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67482470E7C400240747 /* humanw_mask.png */; };
53 | EA8B674D2470E7C400240747 /* factory1.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67492470E7C400240747 /* factory1.png */; };
54 | EA8B674F2470E7DF00240747 /* StoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8B674E2470E7DF00240747 /* StoryView.swift */; };
55 | EA8B67512470E7F400240747 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8B67502470E7F400240747 /* TutorialView.swift */; };
56 | EA8B67542470E95000240747 /* production.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67522470E95000240747 /* production.png */; };
57 | EA8B67552470E95000240747 /* research.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67532470E95000240747 /* research.png */; };
58 | EAC000BE24727ED3009A19B5 /* Ninja_Circle.png in Resources */ = {isa = PBXBuildFile; fileRef = EAC000BD24727ED3009A19B5 /* Ninja_Circle.png */; };
59 | EAFAA58124653BB00006CAFF /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAFAA58024653BB00006CAFF /* DataModel.swift */; };
60 | /* End PBXBuildFile section */
61 |
62 | /* Begin PBXFileReference section */
63 | EA078F8F2471365700B31133 /* InternalTutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalTutorialView.swift; sourceTree = ""; };
64 | EA26185C2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SixFeetBetween_WWDC20SwiftChallenge.app; sourceTree = BUILT_PRODUCTS_DIR; };
65 | EA26185F2462A7E300ADFE5C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
66 | EA2618612462A7E300ADFE5C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
67 | EA2618642462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = WWDC20PlaygroundTest.xcdatamodel; sourceTree = ""; };
68 | EA2618662462A7E300ADFE5C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
69 | EA2618682462A7EA00ADFE5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
70 | EA26186B2462A7EA00ADFE5C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
71 | EA26186E2462A7EA00ADFE5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
72 | EA2618702462A7EA00ADFE5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
73 | EA29397C246FC55A00B8A898 /* BackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundView.swift; sourceTree = ""; };
74 | EA29397E246FCBDA00B8A898 /* jump.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = jump.wav; sourceTree = ""; };
75 | EA29397F246FCBDA00B8A898 /* error2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = error2.wav; sourceTree = ""; };
76 | EA293980246FCBDA00B8A898 /* error1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = error1.wav; sourceTree = ""; };
77 | EA293984246FCC4900B8A898 /* MediaSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSupport.swift; sourceTree = ""; };
78 | EA293986246FE60000B8A898 /* success2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success2.wav; sourceTree = ""; };
79 | EA293987246FE60000B8A898 /* success4.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success4.wav; sourceTree = ""; };
80 | EA293988246FE60000B8A898 /* coin3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin3.wav; sourceTree = ""; };
81 | EA293989246FE60000B8A898 /* coin1.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = coin1.mp3; sourceTree = ""; };
82 | EA29398A246FE60000B8A898 /* success3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success3.wav; sourceTree = ""; };
83 | EA29398B246FE60000B8A898 /* success1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success1.wav; sourceTree = ""; };
84 | EA29398C246FE60000B8A898 /* coin4.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin4.wav; sourceTree = ""; };
85 | EA29398D246FE60000B8A898 /* coin2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin2.wav; sourceTree = ""; };
86 | EA293996246FE92800B8A898 /* failure3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = failure3.wav; sourceTree = ""; };
87 | EA293997246FE92800B8A898 /* failure1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = failure1.wav; sourceTree = ""; };
88 | EA293998246FE92800B8A898 /* failure2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = failure2.wav; sourceTree = ""; };
89 | EA29399C2470B2A300B8A898 /* coin5.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin5.wav; sourceTree = ""; };
90 | EA29399E2470B30A00B8A898 /* coin6.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin6.wav; sourceTree = ""; };
91 | EA2939A02470B33D00B8A898 /* coin7.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin7.wav; sourceTree = ""; };
92 | EA2939A22470B76500B8A898 /* clock1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = clock1.wav; sourceTree = ""; };
93 | EA2CE766246CB775004B8A34 /* GameSuccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSuccessView.swift; sourceTree = ""; };
94 | EA2CE768246CB77F004B8A34 /* GameFailureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameFailureView.swift; sourceTree = ""; };
95 | EA49601224713B0E00EBDA55 /* ButtonStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStackView.swift; sourceTree = ""; };
96 | EA49601424713BD900EBDA55 /* BriefLogisticExplanationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BriefLogisticExplanationView.swift; sourceTree = ""; };
97 | EA49601624713D1100EBDA55 /* StoryTextIntroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryTextIntroView.swift; sourceTree = ""; };
98 | EA49601824713E9A00EBDA55 /* InternalTutorialPartialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalTutorialPartialView.swift; sourceTree = ""; };
99 | EA49601A2471468500EBDA55 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; };
100 | EA7AF2C5246F9F360004BDC4 /* Calculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calculation.swift; sourceTree = ""; };
101 | EA7AF2C7246F9FFD0004BDC4 /* GameLogistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameLogistics.swift; sourceTree = ""; };
102 | EA7AF2C9246FA0180004BDC4 /* AnimationPresets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationPresets.swift; sourceTree = ""; };
103 | EA7AF2CB246FA1530004BDC4 /* NPCUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NPCUI.swift; sourceTree = ""; };
104 | EA7AF2CD246FA1A90004BDC4 /* PlayerUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerUI.swift; sourceTree = ""; };
105 | EA8B67462470E7C300240747 /* factory2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = factory2.png; sourceTree = ""; };
106 | EA8B67472470E7C400240747 /* wash_hand.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wash_hand.png; sourceTree = ""; };
107 | EA8B67482470E7C400240747 /* humanw_mask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = humanw_mask.png; sourceTree = ""; };
108 | EA8B67492470E7C400240747 /* factory1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = factory1.png; sourceTree = ""; };
109 | EA8B674E2470E7DF00240747 /* StoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryView.swift; sourceTree = ""; };
110 | EA8B67502470E7F400240747 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = ""; };
111 | EA8B67522470E95000240747 /* production.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = production.png; sourceTree = ""; };
112 | EA8B67532470E95000240747 /* research.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = research.png; sourceTree = ""; };
113 | EAC000BD24727ED3009A19B5 /* Ninja_Circle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Ninja_Circle.png; sourceTree = ""; };
114 | EAFAA58024653BB00006CAFF /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = ""; };
115 | /* End PBXFileReference section */
116 |
117 | /* Begin PBXFrameworksBuildPhase section */
118 | EA2618592462A7E300ADFE5C /* Frameworks */ = {
119 | isa = PBXFrameworksBuildPhase;
120 | buildActionMask = 2147483647;
121 | files = (
122 | );
123 | runOnlyForDeploymentPostprocessing = 0;
124 | };
125 | /* End PBXFrameworksBuildPhase section */
126 |
127 | /* Begin PBXGroup section */
128 | EA2618532462A7E300ADFE5C = {
129 | isa = PBXGroup;
130 | children = (
131 | EA26185E2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */,
132 | EA26185D2462A7E300ADFE5C /* Products */,
133 | );
134 | sourceTree = "";
135 | };
136 | EA26185D2462A7E300ADFE5C /* Products */ = {
137 | isa = PBXGroup;
138 | children = (
139 | EA26185C2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge.app */,
140 | );
141 | name = Products;
142 | sourceTree = "";
143 | };
144 | EA26185E2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */ = {
145 | isa = PBXGroup;
146 | children = (
147 | EA26185F2462A7E300ADFE5C /* AppDelegate.swift */,
148 | EA2618612462A7E300ADFE5C /* SceneDelegate.swift */,
149 | EA8B67432470DE2700240747 /* Views */,
150 | EA8B67452470DE7D00240747 /* Support */,
151 | EA8B67442470DE3100240747 /* Data */,
152 | EA2939A52470CC4A00B8A898 /* Visual Assets */,
153 | EA2939A42470BC1000B8A898 /* Sound Effect */,
154 | EA2618682462A7EA00ADFE5C /* Assets.xcassets */,
155 | EA26186D2462A7EA00ADFE5C /* LaunchScreen.storyboard */,
156 | EA2618702462A7EA00ADFE5C /* Info.plist */,
157 | EA2618632462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld */,
158 | EA26186A2462A7EA00ADFE5C /* Preview Content */,
159 | );
160 | path = SixFeetBetween_WWDC20SwiftChallenge;
161 | sourceTree = "";
162 | };
163 | EA26186A2462A7EA00ADFE5C /* Preview Content */ = {
164 | isa = PBXGroup;
165 | children = (
166 | EA26186B2462A7EA00ADFE5C /* Preview Assets.xcassets */,
167 | );
168 | path = "Preview Content";
169 | sourceTree = "";
170 | };
171 | EA2939A42470BC1000B8A898 /* Sound Effect */ = {
172 | isa = PBXGroup;
173 | children = (
174 | EA293980246FCBDA00B8A898 /* error1.wav */,
175 | EA29397F246FCBDA00B8A898 /* error2.wav */,
176 | EA29397E246FCBDA00B8A898 /* jump.wav */,
177 | EA293989246FE60000B8A898 /* coin1.mp3 */,
178 | EA29398D246FE60000B8A898 /* coin2.wav */,
179 | EA293988246FE60000B8A898 /* coin3.wav */,
180 | EA29398C246FE60000B8A898 /* coin4.wav */,
181 | EA29399C2470B2A300B8A898 /* coin5.wav */,
182 | EA29399E2470B30A00B8A898 /* coin6.wav */,
183 | EA2939A02470B33D00B8A898 /* coin7.wav */,
184 | EA293997246FE92800B8A898 /* failure1.wav */,
185 | EA293998246FE92800B8A898 /* failure2.wav */,
186 | EA293996246FE92800B8A898 /* failure3.wav */,
187 | EA29398B246FE60000B8A898 /* success1.wav */,
188 | EA293986246FE60000B8A898 /* success2.wav */,
189 | EA29398A246FE60000B8A898 /* success3.wav */,
190 | EA293987246FE60000B8A898 /* success4.wav */,
191 | EA2939A22470B76500B8A898 /* clock1.wav */,
192 | );
193 | path = "Sound Effect";
194 | sourceTree = "";
195 | };
196 | EA2939A52470CC4A00B8A898 /* Visual Assets */ = {
197 | isa = PBXGroup;
198 | children = (
199 | EAC000BD24727ED3009A19B5 /* Ninja_Circle.png */,
200 | EA8B67492470E7C400240747 /* factory1.png */,
201 | EA8B67462470E7C300240747 /* factory2.png */,
202 | EA8B67482470E7C400240747 /* humanw_mask.png */,
203 | EA8B67472470E7C400240747 /* wash_hand.png */,
204 | EA8B67522470E95000240747 /* production.png */,
205 | EA8B67532470E95000240747 /* research.png */,
206 | );
207 | path = "Visual Assets";
208 | sourceTree = "";
209 | };
210 | EA8B67432470DE2700240747 /* Views */ = {
211 | isa = PBXGroup;
212 | children = (
213 | EA8B674E2470E7DF00240747 /* StoryView.swift */,
214 | EA49601224713B0E00EBDA55 /* ButtonStackView.swift */,
215 | EA8B67502470E7F400240747 /* TutorialView.swift */,
216 | EA49601824713E9A00EBDA55 /* InternalTutorialPartialView.swift */,
217 | EA078F8F2471365700B31133 /* InternalTutorialView.swift */,
218 | EA49601A2471468500EBDA55 /* IconView.swift */,
219 | EA49601424713BD900EBDA55 /* BriefLogisticExplanationView.swift */,
220 | EA49601624713D1100EBDA55 /* StoryTextIntroView.swift */,
221 | EA2618662462A7E300ADFE5C /* ContentView.swift */,
222 | EA29397C246FC55A00B8A898 /* BackgroundView.swift */,
223 | EA7AF2CB246FA1530004BDC4 /* NPCUI.swift */,
224 | EA7AF2CD246FA1A90004BDC4 /* PlayerUI.swift */,
225 | EA2CE766246CB775004B8A34 /* GameSuccessView.swift */,
226 | EA2CE768246CB77F004B8A34 /* GameFailureView.swift */,
227 | );
228 | path = Views;
229 | sourceTree = "";
230 | };
231 | EA8B67442470DE3100240747 /* Data */ = {
232 | isa = PBXGroup;
233 | children = (
234 | EAFAA58024653BB00006CAFF /* DataModel.swift */,
235 | EA7AF2C5246F9F360004BDC4 /* Calculation.swift */,
236 | );
237 | path = Data;
238 | sourceTree = "";
239 | };
240 | EA8B67452470DE7D00240747 /* Support */ = {
241 | isa = PBXGroup;
242 | children = (
243 | EA7AF2C7246F9FFD0004BDC4 /* GameLogistics.swift */,
244 | EA293984246FCC4900B8A898 /* MediaSupport.swift */,
245 | EA7AF2C9246FA0180004BDC4 /* AnimationPresets.swift */,
246 | );
247 | path = Support;
248 | sourceTree = "";
249 | };
250 | /* End PBXGroup section */
251 |
252 | /* Begin PBXNativeTarget section */
253 | EA26185B2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */ = {
254 | isa = PBXNativeTarget;
255 | buildConfigurationList = EA2618732462A7EA00ADFE5C /* Build configuration list for PBXNativeTarget "SixFeetBetween_WWDC20SwiftChallenge" */;
256 | buildPhases = (
257 | EA2618582462A7E300ADFE5C /* Sources */,
258 | EA2618592462A7E300ADFE5C /* Frameworks */,
259 | EA26185A2462A7E300ADFE5C /* Resources */,
260 | );
261 | buildRules = (
262 | );
263 | dependencies = (
264 | );
265 | name = SixFeetBetween_WWDC20SwiftChallenge;
266 | productName = WWDC20PlaygroundTest;
267 | productReference = EA26185C2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge.app */;
268 | productType = "com.apple.product-type.application";
269 | };
270 | /* End PBXNativeTarget section */
271 |
272 | /* Begin PBXProject section */
273 | EA2618542462A7E300ADFE5C /* Project object */ = {
274 | isa = PBXProject;
275 | attributes = {
276 | LastSwiftUpdateCheck = 1110;
277 | LastUpgradeCheck = 1110;
278 | ORGANIZATIONNAME = TonyTang;
279 | TargetAttributes = {
280 | EA26185B2462A7E300ADFE5C = {
281 | CreatedOnToolsVersion = 11.1;
282 | };
283 | };
284 | };
285 | buildConfigurationList = EA2618572462A7E300ADFE5C /* Build configuration list for PBXProject "SixFeetBetween_WWDC20SwiftChallenge" */;
286 | compatibilityVersion = "Xcode 9.3";
287 | developmentRegion = en;
288 | hasScannedForEncodings = 0;
289 | knownRegions = (
290 | en,
291 | Base,
292 | );
293 | mainGroup = EA2618532462A7E300ADFE5C;
294 | productRefGroup = EA26185D2462A7E300ADFE5C /* Products */;
295 | projectDirPath = "";
296 | projectRoot = "";
297 | targets = (
298 | EA26185B2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */,
299 | );
300 | };
301 | /* End PBXProject section */
302 |
303 | /* Begin PBXResourcesBuildPhase section */
304 | EA26185A2462A7E300ADFE5C /* Resources */ = {
305 | isa = PBXResourcesBuildPhase;
306 | buildActionMask = 2147483647;
307 | files = (
308 | EA8B674D2470E7C400240747 /* factory1.png in Resources */,
309 | EA293994246FE60000B8A898 /* coin4.wav in Resources */,
310 | EA2939A12470B33D00B8A898 /* coin7.wav in Resources */,
311 | EA29399A246FE92800B8A898 /* failure1.wav in Resources */,
312 | EA8B67552470E95000240747 /* research.png in Resources */,
313 | EA293990246FE60000B8A898 /* coin3.wav in Resources */,
314 | EA293992246FE60000B8A898 /* success3.wav in Resources */,
315 | EA293982246FCBDA00B8A898 /* error2.wav in Resources */,
316 | EA293983246FCBDA00B8A898 /* error1.wav in Resources */,
317 | EA29398E246FE60000B8A898 /* success2.wav in Resources */,
318 | EA293995246FE60000B8A898 /* coin2.wav in Resources */,
319 | EA293981246FCBDA00B8A898 /* jump.wav in Resources */,
320 | EAC000BE24727ED3009A19B5 /* Ninja_Circle.png in Resources */,
321 | EA29399D2470B2A300B8A898 /* coin5.wav in Resources */,
322 | EA29399F2470B30A00B8A898 /* coin6.wav in Resources */,
323 | EA8B674B2470E7C400240747 /* wash_hand.png in Resources */,
324 | EA8B674C2470E7C400240747 /* humanw_mask.png in Resources */,
325 | EA293993246FE60000B8A898 /* success1.wav in Resources */,
326 | EA26186F2462A7EA00ADFE5C /* LaunchScreen.storyboard in Resources */,
327 | EA8B67542470E95000240747 /* production.png in Resources */,
328 | EA8B674A2470E7C400240747 /* factory2.png in Resources */,
329 | EA29398F246FE60000B8A898 /* success4.wav in Resources */,
330 | EA293991246FE60000B8A898 /* coin1.mp3 in Resources */,
331 | EA26186C2462A7EA00ADFE5C /* Preview Assets.xcassets in Resources */,
332 | EA29399B246FE92800B8A898 /* failure2.wav in Resources */,
333 | EA2939A32470B76500B8A898 /* clock1.wav in Resources */,
334 | EA2618692462A7EA00ADFE5C /* Assets.xcassets in Resources */,
335 | EA293999246FE92800B8A898 /* failure3.wav in Resources */,
336 | );
337 | runOnlyForDeploymentPostprocessing = 0;
338 | };
339 | /* End PBXResourcesBuildPhase section */
340 |
341 | /* Begin PBXSourcesBuildPhase section */
342 | EA2618582462A7E300ADFE5C /* Sources */ = {
343 | isa = PBXSourcesBuildPhase;
344 | buildActionMask = 2147483647;
345 | files = (
346 | EA293985246FCC4900B8A898 /* MediaSupport.swift in Sources */,
347 | EA7AF2CA246FA0180004BDC4 /* AnimationPresets.swift in Sources */,
348 | EAFAA58124653BB00006CAFF /* DataModel.swift in Sources */,
349 | EA7AF2C8246F9FFD0004BDC4 /* GameLogistics.swift in Sources */,
350 | EA7AF2C6246F9F360004BDC4 /* Calculation.swift in Sources */,
351 | EA49601B2471468500EBDA55 /* IconView.swift in Sources */,
352 | EA2618602462A7E300ADFE5C /* AppDelegate.swift in Sources */,
353 | EA49601324713B0E00EBDA55 /* ButtonStackView.swift in Sources */,
354 | EA7AF2CC246FA1530004BDC4 /* NPCUI.swift in Sources */,
355 | EA49601724713D1100EBDA55 /* StoryTextIntroView.swift in Sources */,
356 | EA2CE767246CB775004B8A34 /* GameSuccessView.swift in Sources */,
357 | EA8B674F2470E7DF00240747 /* StoryView.swift in Sources */,
358 | EA2618652462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld in Sources */,
359 | EA7AF2CE246FA1A90004BDC4 /* PlayerUI.swift in Sources */,
360 | EA49601524713BD900EBDA55 /* BriefLogisticExplanationView.swift in Sources */,
361 | EA8B67512470E7F400240747 /* TutorialView.swift in Sources */,
362 | EA078F902471365700B31133 /* InternalTutorialView.swift in Sources */,
363 | EA29397D246FC55A00B8A898 /* BackgroundView.swift in Sources */,
364 | EA2CE769246CB77F004B8A34 /* GameFailureView.swift in Sources */,
365 | EA2618672462A7E300ADFE5C /* ContentView.swift in Sources */,
366 | EA49601924713E9A00EBDA55 /* InternalTutorialPartialView.swift in Sources */,
367 | EA2618622462A7E300ADFE5C /* SceneDelegate.swift in Sources */,
368 | );
369 | runOnlyForDeploymentPostprocessing = 0;
370 | };
371 | /* End PBXSourcesBuildPhase section */
372 |
373 | /* Begin PBXVariantGroup section */
374 | EA26186D2462A7EA00ADFE5C /* LaunchScreen.storyboard */ = {
375 | isa = PBXVariantGroup;
376 | children = (
377 | EA26186E2462A7EA00ADFE5C /* Base */,
378 | );
379 | name = LaunchScreen.storyboard;
380 | sourceTree = "";
381 | };
382 | /* End PBXVariantGroup section */
383 |
384 | /* Begin XCBuildConfiguration section */
385 | EA2618712462A7EA00ADFE5C /* Debug */ = {
386 | isa = XCBuildConfiguration;
387 | buildSettings = {
388 | ALWAYS_SEARCH_USER_PATHS = NO;
389 | CLANG_ANALYZER_NONNULL = YES;
390 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
391 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
392 | CLANG_CXX_LIBRARY = "libc++";
393 | CLANG_ENABLE_MODULES = YES;
394 | CLANG_ENABLE_OBJC_ARC = YES;
395 | CLANG_ENABLE_OBJC_WEAK = YES;
396 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
397 | CLANG_WARN_BOOL_CONVERSION = YES;
398 | CLANG_WARN_COMMA = YES;
399 | CLANG_WARN_CONSTANT_CONVERSION = YES;
400 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
401 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
402 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
403 | CLANG_WARN_EMPTY_BODY = YES;
404 | CLANG_WARN_ENUM_CONVERSION = YES;
405 | CLANG_WARN_INFINITE_RECURSION = YES;
406 | CLANG_WARN_INT_CONVERSION = YES;
407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
408 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
409 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
411 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
412 | CLANG_WARN_STRICT_PROTOTYPES = YES;
413 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
414 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
415 | CLANG_WARN_UNREACHABLE_CODE = YES;
416 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
417 | COPY_PHASE_STRIP = NO;
418 | DEBUG_INFORMATION_FORMAT = dwarf;
419 | ENABLE_STRICT_OBJC_MSGSEND = YES;
420 | ENABLE_TESTABILITY = YES;
421 | GCC_C_LANGUAGE_STANDARD = gnu11;
422 | GCC_DYNAMIC_NO_PIC = NO;
423 | GCC_NO_COMMON_BLOCKS = YES;
424 | GCC_OPTIMIZATION_LEVEL = 0;
425 | GCC_PREPROCESSOR_DEFINITIONS = (
426 | "DEBUG=1",
427 | "$(inherited)",
428 | );
429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
430 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
431 | GCC_WARN_UNDECLARED_SELECTOR = YES;
432 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
433 | GCC_WARN_UNUSED_FUNCTION = YES;
434 | GCC_WARN_UNUSED_VARIABLE = YES;
435 | IPHONEOS_DEPLOYMENT_TARGET = 13.1;
436 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
437 | MTL_FAST_MATH = YES;
438 | ONLY_ACTIVE_ARCH = YES;
439 | SDKROOT = iphoneos;
440 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
441 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
442 | };
443 | name = Debug;
444 | };
445 | EA2618722462A7EA00ADFE5C /* Release */ = {
446 | isa = XCBuildConfiguration;
447 | buildSettings = {
448 | ALWAYS_SEARCH_USER_PATHS = NO;
449 | CLANG_ANALYZER_NONNULL = YES;
450 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
451 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
452 | CLANG_CXX_LIBRARY = "libc++";
453 | CLANG_ENABLE_MODULES = YES;
454 | CLANG_ENABLE_OBJC_ARC = YES;
455 | CLANG_ENABLE_OBJC_WEAK = YES;
456 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
457 | CLANG_WARN_BOOL_CONVERSION = YES;
458 | CLANG_WARN_COMMA = YES;
459 | CLANG_WARN_CONSTANT_CONVERSION = YES;
460 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
461 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
462 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
463 | CLANG_WARN_EMPTY_BODY = YES;
464 | CLANG_WARN_ENUM_CONVERSION = YES;
465 | CLANG_WARN_INFINITE_RECURSION = YES;
466 | CLANG_WARN_INT_CONVERSION = YES;
467 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
468 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
469 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
470 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
471 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
472 | CLANG_WARN_STRICT_PROTOTYPES = YES;
473 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
474 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
475 | CLANG_WARN_UNREACHABLE_CODE = YES;
476 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
477 | COPY_PHASE_STRIP = NO;
478 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
479 | ENABLE_NS_ASSERTIONS = NO;
480 | ENABLE_STRICT_OBJC_MSGSEND = YES;
481 | GCC_C_LANGUAGE_STANDARD = gnu11;
482 | GCC_NO_COMMON_BLOCKS = YES;
483 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
484 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
485 | GCC_WARN_UNDECLARED_SELECTOR = YES;
486 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
487 | GCC_WARN_UNUSED_FUNCTION = YES;
488 | GCC_WARN_UNUSED_VARIABLE = YES;
489 | IPHONEOS_DEPLOYMENT_TARGET = 13.1;
490 | MTL_ENABLE_DEBUG_INFO = NO;
491 | MTL_FAST_MATH = YES;
492 | SDKROOT = iphoneos;
493 | SWIFT_COMPILATION_MODE = wholemodule;
494 | SWIFT_OPTIMIZATION_LEVEL = "-O";
495 | VALIDATE_PRODUCT = YES;
496 | };
497 | name = Release;
498 | };
499 | EA2618742462A7EA00ADFE5C /* Debug */ = {
500 | isa = XCBuildConfiguration;
501 | buildSettings = {
502 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
503 | CODE_SIGN_STYLE = Automatic;
504 | DEVELOPMENT_ASSET_PATHS = "\"SixFeetBetween_WWDC20SwiftChallenge/Preview Content\"";
505 | DEVELOPMENT_TEAM = 3K4PT9RJ98;
506 | ENABLE_PREVIEWS = YES;
507 | INFOPLIST_FILE = SixFeetBetween_WWDC20SwiftChallenge/Info.plist;
508 | LD_RUNPATH_SEARCH_PATHS = (
509 | "$(inherited)",
510 | "@executable_path/Frameworks",
511 | );
512 | PRODUCT_BUNDLE_IDENTIFIER = "com.tonytangzixuan.SixFeetBetween-WWDC20SwiftChallenge";
513 | PRODUCT_NAME = SixFeetBetween_WWDC20SwiftChallenge;
514 | SWIFT_VERSION = 5.0;
515 | TARGETED_DEVICE_FAMILY = "1,2";
516 | };
517 | name = Debug;
518 | };
519 | EA2618752462A7EA00ADFE5C /* Release */ = {
520 | isa = XCBuildConfiguration;
521 | buildSettings = {
522 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
523 | CODE_SIGN_STYLE = Automatic;
524 | DEVELOPMENT_ASSET_PATHS = "\"SixFeetBetween_WWDC20SwiftChallenge/Preview Content\"";
525 | DEVELOPMENT_TEAM = 3K4PT9RJ98;
526 | ENABLE_PREVIEWS = YES;
527 | INFOPLIST_FILE = SixFeetBetween_WWDC20SwiftChallenge/Info.plist;
528 | LD_RUNPATH_SEARCH_PATHS = (
529 | "$(inherited)",
530 | "@executable_path/Frameworks",
531 | );
532 | PRODUCT_BUNDLE_IDENTIFIER = "com.tonytangzixuan.SixFeetBetween-WWDC20SwiftChallenge";
533 | PRODUCT_NAME = SixFeetBetween_WWDC20SwiftChallenge;
534 | SWIFT_VERSION = 5.0;
535 | TARGETED_DEVICE_FAMILY = "1,2";
536 | };
537 | name = Release;
538 | };
539 | /* End XCBuildConfiguration section */
540 |
541 | /* Begin XCConfigurationList section */
542 | EA2618572462A7E300ADFE5C /* Build configuration list for PBXProject "SixFeetBetween_WWDC20SwiftChallenge" */ = {
543 | isa = XCConfigurationList;
544 | buildConfigurations = (
545 | EA2618712462A7EA00ADFE5C /* Debug */,
546 | EA2618722462A7EA00ADFE5C /* Release */,
547 | );
548 | defaultConfigurationIsVisible = 0;
549 | defaultConfigurationName = Release;
550 | };
551 | EA2618732462A7EA00ADFE5C /* Build configuration list for PBXNativeTarget "SixFeetBetween_WWDC20SwiftChallenge" */ = {
552 | isa = XCConfigurationList;
553 | buildConfigurations = (
554 | EA2618742462A7EA00ADFE5C /* Debug */,
555 | EA2618752462A7EA00ADFE5C /* Release */,
556 | );
557 | defaultConfigurationIsVisible = 0;
558 | defaultConfigurationName = Release;
559 | };
560 | /* End XCConfigurationList section */
561 |
562 | /* Begin XCVersionGroup section */
563 | EA2618632462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld */ = {
564 | isa = XCVersionGroup;
565 | children = (
566 | EA2618642462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodel */,
567 | );
568 | currentVersion = EA2618642462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodel */;
569 | path = WWDC20PlaygroundTest.xcdatamodeld;
570 | sourceTree = "";
571 | versionGroupType = wrapper.xcdatamodel;
572 | };
573 | /* End XCVersionGroup section */
574 | };
575 | rootObject = EA2618542462A7E300ADFE5C /* Project object */;
576 | }
577 |
--------------------------------------------------------------------------------