├── Design
├── CardsAgainst-Icon.sketch
└── CardsAgainst-Splash.sketch
├── CardsAgainst
├── Assets
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Icon-76.png
│ │ │ ├── Icon-60@2x.png
│ │ │ ├── Icon-60@3x.png
│ │ │ ├── Icon-76@2x.png
│ │ │ ├── Icon-Small.png
│ │ │ ├── Icon-83.5@2x.png
│ │ │ ├── Icon-Small-40.png
│ │ │ ├── Icon-Small@2x.png
│ │ │ ├── Icon-Small@3x.png
│ │ │ ├── Icon-Small-40@2x.png
│ │ │ ├── Icon-Small-40@3x.png
│ │ │ └── Contents.json
│ │ └── LaunchImage.launchimage
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage-4Inch.png
│ │ │ ├── LaunchImage-iPhone6.png
│ │ │ ├── LaunchImage-iPadRetina.png
│ │ │ ├── LaunchImage-iPhone6Plus.png
│ │ │ └── Contents.json
│ └── cards_pg13.json
├── Supporting Files
│ ├── CardsAgainst-Bridging-Header.h
│ └── Info.plist
├── Models
│ ├── Answer.swift
│ ├── GameState.swift
│ ├── Vote.swift
│ ├── MPCAttributedString.swift
│ ├── Card.swift
│ └── Player.swift
├── Helpers
│ └── Colors.swift
├── Extensions
│ ├── UIFont+CardsAgainst.swift
│ └── UIImage+LaunchImage.swift
├── Views
│ ├── PlayerCell.swift
│ ├── TouchableLabel.swift
│ └── WhiteCardCell.swift
├── AppDelegate.swift
├── Controllers
│ ├── WhiteCardFlowLayout.swift
│ ├── CardManager.swift
│ └── ConnectionManager.swift
└── View Controllers
│ ├── MenuViewController.swift
│ └── GameViewController.swift
├── .swiftlint.yml
├── CardsAgainst.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── CardsAgainst.xcscmblueprint
└── project.pbxproj
├── .gitmodules
├── CardsAgainstTests
├── Supporting Files
│ └── Info.plist
└── CardsAgainstTests.swift
├── LICENSE
├── README.md
└── .gitignore
/Design/CardsAgainst-Icon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/Design/CardsAgainst-Icon.sketch
--------------------------------------------------------------------------------
/Design/CardsAgainst-Splash.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/Design/CardsAgainst-Splash.sketch
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-4Inch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-4Inch.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPadRetina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPadRetina.png
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6Plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpsim/CardsAgainst/HEAD/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6Plus.png
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | included:
2 | - CardsAgainst
3 | disabled_rules:
4 | - file_length
5 | - force_cast
6 | - force_try
7 | - type_body_length
8 |
9 | identifier_name:
10 | excluded:
11 | - me
12 | - to
13 |
--------------------------------------------------------------------------------
/CardsAgainst.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CardsAgainst/Supporting Files/CardsAgainst-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // CardsAgainst-Bridging-Header.h.h
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/3/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | #import "SVProgressHUD.h"
10 |
--------------------------------------------------------------------------------
/CardsAgainst/Models/Answer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Answer.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/6/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Answer {
12 | let sender: Player
13 | let answer: NSAttributedString
14 | }
15 |
--------------------------------------------------------------------------------
/CardsAgainst/Models/GameState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameState.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/2/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum GameState {
12 | case pickingCard
13 | case waitingForOthers
14 | case pickingWinner
15 | }
16 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Vendor/Cartography"]
2 | path = Vendor/Cartography
3 | url = git@github.com:robb/Cartography.git
4 | [submodule "Vendor/SVProgressHUD"]
5 | path = Vendor/SVProgressHUD
6 | url = git@github.com:TransitApp/SVProgressHUD.git
7 | [submodule "Vendor/PeerKit"]
8 | path = Vendor/PeerKit
9 | url = git@github.com:jpsim/PeerKit.git
10 |
--------------------------------------------------------------------------------
/CardsAgainst/Helpers/Colors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Colors.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 10/25/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | let navBarColor = UIColor(white: 0.33, alpha: 1)
12 | let darkColor = UIColor(white: 0.1, alpha: 1)
13 | let lightColor = UIColor.white
14 | let appTintColor = UIColor(red: 102/255, green: 176/255, blue: 1, alpha: 1)
15 | let appBackgroundColor = darkColor
16 |
--------------------------------------------------------------------------------
/CardsAgainst/Models/Vote.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Vote.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/6/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Vote {
12 | let votee: Player
13 | let voter: Player
14 |
15 | static func stringFromVoteCount(_ voteCount: Int) -> String {
16 | switch voteCount {
17 | case 0:
18 | return "no votes"
19 | case 1:
20 | return "1 vote"
21 | default:
22 | return "\(voteCount) votes"
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/CardsAgainst/Models/MPCAttributedString.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MPCAttributedString.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/3/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct MPCAttributedString: MPCSerializable {
12 | let attributedString: NSAttributedString
13 |
14 | var mpcSerialized: Data {
15 | return NSKeyedArchiver.archivedData(withRootObject: attributedString)
16 | }
17 |
18 | init(attributedString: NSAttributedString) {
19 | self.attributedString = attributedString
20 | }
21 |
22 | init(mpcSerialized: Data) {
23 | let attributedString = NSKeyedUnarchiver.unarchiveObject(with: mpcSerialized) as! NSAttributedString
24 | self.init(attributedString: attributedString)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/CardsAgainstTests/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/CardsAgainst/Extensions/UIFont+CardsAgainst.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+CardsAgainst.swift
3 | // CardsAgainst
4 | //
5 | // Created by Cap'n Slipp on 12/1/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// The screen width the base font sizes are designed for.
12 | private let baseScreenWidth: CGFloat = 375
13 |
14 | private func screenScaledFontSize(_ baseFontSize: CGFloat) -> CGFloat {
15 | let screenPortraitWidth = UIScreen.main.nativeBounds.size.width / UIScreen.main.nativeScale
16 | return baseFontSize / baseScreenWidth * screenPortraitWidth
17 | }
18 |
19 | extension UIFont {
20 | class var blackCardFont: UIFont { return UIFont.boldSystemFont(ofSize: screenScaledFontSize(35)) }
21 | class var whiteCardFont: UIFont { return UIFont.boldSystemFont(ofSize: screenScaledFontSize(20)) }
22 | class var voteButtonFont: UIFont { return UIFont.systemFont(ofSize: screenScaledFontSize(17)) }
23 | }
24 |
--------------------------------------------------------------------------------
/CardsAgainst/Views/PlayerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlayerCell.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/4/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cartography
11 |
12 | final class PlayerCell: UICollectionViewCell {
13 |
14 | class var reuseID: String { return "PlayerCell" }
15 | let label = UILabel()
16 |
17 | override init(frame: CGRect) {
18 | super.init(frame: frame)
19 | setupLabel()
20 | }
21 |
22 | required init?(coder aDecoder: NSCoder) {
23 | fatalError("init(coder:) has not been implemented")
24 | }
25 |
26 | fileprivate func setupLabel() {
27 | // Label
28 | contentView.addSubview(label)
29 | label.translatesAutoresizingMaskIntoConstraints = false
30 | label.textColor = lightColor
31 | label.font = .boldSystemFont(ofSize: 22)
32 |
33 | // Layout
34 | constrain(label) { label in
35 | label.edges == inset(label.superview!.edges, 15, 10)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/CardsAgainstTests/CardsAgainstTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardsAgainstTests.swift
3 | // CardsAgainstTests
4 | //
5 | // Created by JP Simard on 10/25/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 |
12 | class CardsAgainstTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | XCTAssert(true, "Pass")
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure() {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 JP Simard.
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 |
--------------------------------------------------------------------------------
/CardsAgainst/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 10/25/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | final class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
15 |
16 | func application(_ application: UIApplication,
17 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 |
19 | // Window
20 | window?.rootViewController = UINavigationController(rootViewController: MenuViewController())
21 | window?.makeKeyAndVisible()
22 |
23 | // Appearance
24 | application.statusBarStyle = .lightContent
25 | UINavigationBar.appearance().barTintColor = navBarColor
26 | UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: lightColor]
27 | window?.tintColor = appTintColor
28 |
29 | // Simultaneously advertise and browse for other players
30 | ConnectionManager.start()
31 | return true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/CardsAgainst/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 0.0.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 0.0.1
23 | LSRequiresIPhoneOS
24 |
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 |
33 | UIViewControllerBasedStatusBarAppearance
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/CardsAgainst/Views/TouchableLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TouchableLabel.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/3/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class TouchableLabel: UILabel {
12 | var placeholderRanges = [NSRange]()
13 |
14 | override init(frame: CGRect) {
15 | super.init(frame: frame)
16 | isUserInteractionEnabled = true
17 | }
18 |
19 | required init?(coder aDecoder: NSCoder) {
20 | fatalError("init(coder:) has not been implemented")
21 | }
22 |
23 | fileprivate func setLastTokenAlpha(_ alpha: CGFloat) {
24 | if let lastRange = placeholderRanges.last,
25 | let mAttributedText = attributedText?.mutableCopy() as? NSMutableAttributedString {
26 | mAttributedText.addAttribute(.foregroundColor, value: tintColor.withAlphaComponent(alpha), range: lastRange)
27 | attributedText = mAttributedText
28 | }
29 | }
30 |
31 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
32 | setLastTokenAlpha(0.5)
33 | }
34 |
35 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
36 | setLastTokenAlpha(1)
37 | }
38 |
39 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
40 | setLastTokenAlpha(1)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/CardsAgainst/Extensions/UIImage+LaunchImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage+LaunchImage.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/5/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIImage {
12 | class func launchImage() -> UIImage {
13 | // We only care about iOS 8+ portrait iPhones
14 | // LaunchImage names found here: http://stackoverflow.com/a/25843887/373262
15 | var launchImageName: String
16 | let screenHeight = UIScreen.main.bounds.size.height
17 | switch screenHeight {
18 | case 0..<568:
19 | // 3.5 inch screen
20 | launchImageName = "LaunchImage-700"
21 | case 568:
22 | // 4 inch screen
23 | launchImageName = "LaunchImage-700-568h"
24 | case 667:
25 | // 4.7 inch screen
26 | launchImageName = "LaunchImage-800-667h"
27 | case 736:
28 | // 5.5 inch screen
29 | launchImageName = "LaunchImage-800-Portrait-736h"
30 | case 1024:
31 | // iPads, ev'ry last one of 'em
32 | launchImageName = "LaunchImage-700-Portrait@2x~ipad"
33 | default:
34 | fatalError(
35 | """
36 | Unable to find a LaunchImage for this device's screen size (height of \(screenHeight)pt).
37 | UIImage+LaunchImage likely needs to be updated.
38 | """
39 | )
40 | }
41 | return UIImage(named: launchImageName)!
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CardsAgainst App
2 |
3 | ## An iOS game for horrible people
4 |
5 | A peer-to-peer [Cards Against Humanity][cah] game for iOS, written with Multipeer Connectivity in Swift 4.
6 |
7 | 
8 |
9 | ## Libraries
10 |
11 | This project uses the following libraries:
12 |
13 | * [PeerKit](https://github.com/jpsim/PeerKit) for event-driven, zero-config Multipeer Connectivity
14 | * [Cartography](https://github.com/robb/Cartography) for layout
15 | * [SVProgressHUD](https://github.com/TransitApp/SVProgressHUD) for HUDs
16 |
17 | ## Offensive Content
18 |
19 | Running this game from source will use a small, very mild, impossible to offend subset of the Cards Against Humanity cards.
20 |
21 | However, simply set `let pg13 = false` in [CardManager.swift](https://github.com/jpsim/CardsAgainst/blob/master/CardsAgainst/Controllers/CardManager.swift#L11) to gain access to the entirety of the card collection.
22 |
23 | ## License
24 |
25 | This project is under the MIT license.
26 |
27 | Thanks to [Cards Against Humanity][cah] for this great CC-BY-NC-SA 2.0 game! This project is unaffiliated with the good people behind Cards Against Humanity. You should buy their game!
28 |
29 | Thanks to [Hangouts Against Humanity](https://github.com/samurailink3/hangouts-against-humanity) for the cards!
30 |
31 | While it is not strictly forbidden by the license, I would greatly appreciate it if you didn't redistribute this app exactly the way it is in the App Store. There's nothing stopping you, but please don't be a jerk.
32 |
33 | [cah]: http://cardsagainsthumanity.com
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 |
28 | ## Playgrounds
29 | timeline.xctimeline
30 | playground.xcworkspace
31 |
32 | # Swift Package Manager
33 | #
34 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
35 | # Packages/
36 | .build/
37 |
38 | # CocoaPods
39 | #
40 | # We recommend against adding the Pods directory to your .gitignore. However
41 | # you should judge for yourself, the pros and cons are mentioned at:
42 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
43 | #
44 | # Pods/
45 |
46 | # Carthage
47 | #
48 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
49 | # Carthage/Checkouts
50 |
51 | Carthage/Build
52 |
53 | # fastlane
54 | #
55 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
56 | # screenshots whenever they are needed.
57 | # For more information about the recommended setup visit:
58 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
59 |
60 | fastlane/report.xml
61 | fastlane/Preview.html
62 | fastlane/screenshots
63 | fastlane/test_output
64 |
--------------------------------------------------------------------------------
/CardsAgainst/Models/Card.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Card.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/2/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | let blackCardPlaceholder = "________"
12 |
13 | enum CardType: String {
14 | case white = "A", black = "Q"
15 | }
16 |
17 | struct Card: MPCSerializable {
18 | let content: String
19 | let type: CardType
20 | let expansion: String
21 |
22 | var mpcSerialized: Data {
23 | let dictionary = ["content": content, "type": type.rawValue, "expansion": expansion]
24 | return NSKeyedArchiver.archivedData(withRootObject: dictionary)
25 | }
26 |
27 | init(content: String, type: CardType, expansion: String) {
28 | self.content = content
29 | self.type = type
30 | self.expansion = expansion
31 | }
32 |
33 | init(mpcSerialized: Data) {
34 | let dict = NSKeyedUnarchiver.unarchiveObject(with: mpcSerialized) as! [String: String]
35 | content = dict["content"]!
36 | type = CardType(rawValue: dict["type"]!)!
37 | expansion = dict["expansion"]!
38 | }
39 | }
40 |
41 | struct CardArray: MPCSerializable {
42 | let array: [Card]
43 |
44 | var mpcSerialized: Data {
45 | return NSKeyedArchiver.archivedData(withRootObject: array.map { $0.mpcSerialized })
46 | }
47 |
48 | init(array: [Card]) {
49 | self.array = array
50 | }
51 |
52 | init(mpcSerialized: Data) {
53 | let dataArray = NSKeyedUnarchiver.unarchiveObject(with: mpcSerialized) as! [Data]
54 | array = dataArray.map { return Card(mpcSerialized: $0) }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "extent" : "full-screen",
5 | "idiom" : "iphone",
6 | "subtype" : "736h",
7 | "filename" : "LaunchImage-iPhone6Plus.png",
8 | "minimum-system-version" : "8.0",
9 | "orientation" : "portrait",
10 | "scale" : "3x"
11 | },
12 | {
13 | "extent" : "full-screen",
14 | "idiom" : "iphone",
15 | "subtype" : "667h",
16 | "filename" : "LaunchImage-iPhone6.png",
17 | "minimum-system-version" : "8.0",
18 | "orientation" : "portrait",
19 | "scale" : "2x"
20 | },
21 | {
22 | "orientation" : "portrait",
23 | "idiom" : "iphone",
24 | "extent" : "full-screen",
25 | "minimum-system-version" : "7.0",
26 | "filename" : "LaunchImage.png",
27 | "scale" : "2x"
28 | },
29 | {
30 | "extent" : "full-screen",
31 | "idiom" : "iphone",
32 | "subtype" : "retina4",
33 | "filename" : "LaunchImage-4Inch.png",
34 | "minimum-system-version" : "7.0",
35 | "orientation" : "portrait",
36 | "scale" : "2x"
37 | },
38 | {
39 | "orientation" : "portrait",
40 | "idiom" : "ipad",
41 | "extent" : "full-screen",
42 | "minimum-system-version" : "7.0",
43 | "scale" : "1x"
44 | },
45 | {
46 | "orientation" : "portrait",
47 | "idiom" : "ipad",
48 | "extent" : "full-screen",
49 | "minimum-system-version" : "7.0",
50 | "filename" : "LaunchImage-iPadRetina.png",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/CardsAgainst/Controllers/WhiteCardFlowLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WhiteCardFlowLayout.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/3/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // swiftlint:disable:next identifier_name
12 | private func easeInOut( _ t: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat) -> CGFloat {
13 | var t = t
14 | t /= d/2
15 | if t < 1 {
16 | return c/2*t*t*t + b
17 | }
18 | t -= 2
19 | return c/2*(t*t*t + 2) + b
20 | }
21 |
22 | final class WhiteCardFlowLayout: UICollectionViewFlowLayout {
23 |
24 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
25 | let layoutAttributes = super.layoutAttributesForElements(in: rect)
26 | let topContentInset = collectionView!.contentInset.top + 20
27 | let transitionRegion = CGFloat(120)
28 | for attributes in layoutAttributes! as [UICollectionViewLayoutAttributes] {
29 | let yOriginInSuperview = collectionView!.convert(attributes.frame.origin, to: collectionView!.superview).y
30 | if topContentInset > yOriginInSuperview {
31 | let difference = topContentInset - yOriginInSuperview
32 | let progress = difference/transitionRegion
33 | attributes.alpha = easeInOut(min(progress, 1), b: 1, c: -0.95, d: 1)
34 | } else {
35 | attributes.alpha = 1
36 | }
37 | }
38 | return layoutAttributes
39 | }
40 |
41 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
42 | return true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/CardsAgainst/Models/Player.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/2/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MultipeerConnectivity
11 |
12 | private let myName = UIDevice.current.name
13 |
14 | struct Player: Hashable, Equatable, MPCSerializable {
15 |
16 | // MARK: Properties
17 |
18 | let name: String
19 |
20 | // MARK: Computed Properties
21 |
22 | var me: Bool { return name == myName }
23 | var displayName: String { return me ? "You" : name }
24 | var hashValue: Int { return name.hash }
25 | var mpcSerialized: Data { return name.data(using: String.Encoding.utf8)! }
26 |
27 | // MARK: Initializers
28 |
29 | init(name: String) {
30 | self.name = name
31 | }
32 |
33 | init(mpcSerialized: Data) {
34 | name = NSString(data: mpcSerialized, encoding: String.Encoding.utf8.rawValue)! as String
35 | }
36 |
37 | init(peer: MCPeerID) {
38 | name = peer.displayName
39 | }
40 |
41 | static func getMe() -> Player {
42 | return Player(name: myName)
43 | }
44 |
45 | // MARK: Methods
46 |
47 | func winningString() -> String {
48 | if me {
49 | return "You win this round!"
50 | }
51 | return "\(name) wins this round!"
52 | }
53 |
54 | func cardString(_ voted: Bool) -> String {
55 | if voted {
56 | return me ? "My card" : "\(name)'s card"
57 | }
58 | return me ? "Vote for my card" : "Vote for this card"
59 | }
60 | }
61 |
62 | func == (lhs: Player, rhs: Player) -> Bool {
63 | return lhs.name == rhs.name
64 | }
65 |
--------------------------------------------------------------------------------
/CardsAgainst/Views/WhiteCardCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WhiteCardCell.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/3/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cartography
11 |
12 | final class WhiteCardCell: UICollectionViewCell {
13 |
14 | class var reuseID: String { return "WhiteCardCell" }
15 |
16 | let label = UILabel()
17 | override var isHighlighted: Bool {
18 | get {
19 | return super.isHighlighted
20 | }
21 | set {
22 | contentView.backgroundColor = newValue ? .gray : lightColor
23 | super.isHighlighted = newValue
24 | }
25 | }
26 |
27 | override init(frame: CGRect) {
28 | super.init(frame: frame)
29 |
30 | // Background
31 | contentView.backgroundColor = lightColor
32 | contentView.layer.cornerRadius = 8
33 |
34 | // Label
35 | setupLabel()
36 | }
37 |
38 | required init?(coder aDecoder: NSCoder) {
39 | fatalError("init(coder:) has not been implemented")
40 | }
41 |
42 | fileprivate func setupLabel() {
43 | // Label
44 | contentView.addSubview(label)
45 | label.translatesAutoresizingMaskIntoConstraints = false
46 | label.numberOfLines = 0
47 | label.lineBreakMode = .byWordWrapping
48 | label.font = .whiteCardFont
49 | label.textColor = darkColor
50 |
51 | // Layout
52 | constrain(label) { label in
53 | label.edges == inset(label.superview!.edges, 15, 10)
54 | }
55 | }
56 |
57 | override func layoutSubviews() {
58 | super.layoutSubviews()
59 | label.preferredMaxLayoutWidth = label.frame.size.width
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/CardsAgainst/Controllers/CardManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardManager.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/2/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | private let pg13 = true
12 |
13 | private func loadCards() -> ([Card], [Card]) {
14 | let resourceName = pg13 ? "cards_pg13" : "cards"
15 | let jsonPath = Bundle.main.path(forResource: resourceName, ofType: "json")
16 | let cards = try! JSONSerialization.jsonObject(with: Data(contentsOf: URL(fileURLWithPath: jsonPath!)),
17 | options: []) as! [[String: String]]
18 |
19 | var whiteCards = [Card]()
20 | var blackCards = [Card]()
21 |
22 | for card in cards {
23 | let card = Card(content: card["text"]!,
24 | type: CardType(rawValue: card["cardType"]!)!,
25 | expansion: card["expansion"]!)
26 | if card.type == .white {
27 | whiteCards.append(card)
28 | } else {
29 | blackCards.append(card)
30 | }
31 | }
32 |
33 | return (blackCards, whiteCards)
34 | }
35 |
36 | private let (blackCards, whiteCards) = loadCards()
37 |
38 | private var (mWhiteCards, mBlackCards) = ([Card](), [Card]())
39 |
40 | struct CardManager {
41 | static func nextCardsWithType(_ type: CardType, count: UInt = 1) -> [Card] {
42 | let generator = Array(repeating: 0, count: Int(count))
43 | if type == .black {
44 | return generator.map { _ in return takeRandom(&mBlackCards, original: blackCards) }
45 | } else {
46 | return generator.map { _ in return takeRandom(&mWhiteCards, original: whiteCards) }
47 | }
48 | }
49 |
50 | fileprivate static func takeRandom(_ mutable: inout [U], original: [U]) -> U {
51 | if mutable.count == 0 {
52 | // reshuffle
53 | mutable = original.sorted { _, _ in arc4random() % 2 == 0 }
54 | }
55 | return mutable.removeLast()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/CardsAgainst/Controllers/ConnectionManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectionManager.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/2/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import PeerKit
11 | import MultipeerConnectivity
12 |
13 | protocol MPCSerializable {
14 | var mpcSerialized: Data { get }
15 | init(mpcSerialized: Data)
16 | }
17 |
18 | enum Event: String {
19 | case startGame, answer, cancelAnswer, vote, nextCard, endGame
20 | }
21 |
22 | struct ConnectionManager {
23 |
24 | // MARK: Properties
25 |
26 | fileprivate static var peers: [MCPeerID] {
27 | return PeerKit.session?.connectedPeers as [MCPeerID]? ?? []
28 | }
29 |
30 | static var otherPlayers: [Player] {
31 | return peers.map { Player(peer: $0) }
32 | }
33 |
34 | static var allPlayers: [Player] { return [Player.getMe()] + otherPlayers }
35 |
36 | // MARK: Start
37 |
38 | static func start() {
39 | PeerKit.transceive(serviceType: "cards-against")
40 | }
41 |
42 | // MARK: Event Handling
43 |
44 | static func onConnect(_ run: PeerBlock?) {
45 | PeerKit.onConnect = run
46 | }
47 |
48 | static func onDisconnect(_ run: PeerBlock?) {
49 | PeerKit.onDisconnect = run
50 | }
51 |
52 | static func onEvent(_ event: Event, run: ObjectBlock?) {
53 | if let run = run {
54 | PeerKit.eventBlocks[event.rawValue] = run
55 | } else {
56 | PeerKit.eventBlocks.removeValue(forKey: event.rawValue)
57 | }
58 | }
59 |
60 | // MARK: Sending
61 |
62 | static func sendEvent(_ event: Event, object: [String: MPCSerializable]? = nil,
63 | toPeers peers: [MCPeerID]? = PeerKit.session?.connectedPeers) {
64 | var anyObject: [String: Data]?
65 | if let object = object {
66 | anyObject = [String: Data]()
67 | for (key, value) in object {
68 | anyObject![key] = value.mpcSerialized
69 | }
70 | }
71 | PeerKit.sendEvent(event.rawValue, object: anyObject as AnyObject, toPeers: peers)
72 | }
73 |
74 | static func sendEventForEach(_ event: Event, objectBlock: () -> ([String: MPCSerializable])) {
75 | for peer in ConnectionManager.peers {
76 | ConnectionManager.sendEvent(event, object: objectBlock(), toPeers: [peer])
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/CardsAgainst/Assets/cards_pg13.json:
--------------------------------------------------------------------------------
1 | [{"cardType":"A","text":"Michelle Obama's arms","expansion":"Base"},{"cardType":"A","text":"A disappointing birthday party","expansion":"Base"},{"cardType":"A","text":"Puppies!","expansion":"Base"},{"cardType":"A","text":"Being on fire","expansion":"Base"},{"cardType":"A","text":"A lifetime of sadness","expansion":"Base"},{"cardType":"A","text":"Pterodactyl eggs","expansion":"Base"},{"cardType":"A","text":"Exchanging pleasantries","expansion":"Base"},{"cardType":"A","text":"The forbidden fruit","expansion":"Base"},{"cardType":"A","text":"Republicans","expansion":"Base"},{"cardType":"A","text":"The Big Bang","expansion":"Base"},{"cardType":"A","text":"Agriculture","expansion":"Base"},{"cardType":"A","text":"Making a pouty face","expansion":"Base"},{"cardType":"A","text":"Charisma","expansion":"Base"},{"cardType":"A","text":"YOU MUST CONSTRUCT ADDITIONAL PYLONS","expansion":"Base"},{"cardType":"A","text":"Taking off your shirt","expansion":"Base"},{"cardType":"A","text":"Ronald Reagan","expansion":"Base"},{"cardType":"A","text":"Morgan Freeman's voice","expansion":"Base"},{"cardType":"A","text":"Breaking out into song and dance","expansion":"Base"},{"cardType":"A","text":"All-you-can-eat shrimp for $4.99","expansion":"Base"},{"cardType":"A","text":"Soup that is too hot","expansion":"Base"},{"cardType":"A","text":"Tom Cruise","expansion":"Base"},{"cardType":"A","text":"Stifling a giggle at the mention of Hutus and Tutsis","expansion":"Base"},{"cardType":"A","text":"Edible underpants","expansion":"Base"},{"cardType":"A","text":"Object permanence","expansion":"Base"},{"cardType":"A","text":"Consultants","expansion":"Base"},{"cardType":"A","text":"Intelligent design","expansion":"Base"},{"cardType":"A","text":"Nocturnal emissions","expansion":"Base"},{"cardType":"A","text":"Uppercuts","expansion":"Base"},{"cardType":"Q","text":"________? There's an app for that","expansion":"Base"},{"cardType":"Q","text":"Why can't I sleep at night?","expansion":"Base"},{"cardType":"Q","text":"What's that smell?","expansion":"Base"},{"cardType":"Q","text":"I got 99 problems but ________ ain't one","expansion":"Base"},{"cardType":"Q","text":"Maybe she's born with it. Maybe it's ________","expansion":"Base"},{"cardType":"Q","text":"What's the next Happy Meal® toy?","expansion":"Base"},{"cardType":"Q","text":"Anthropologists have recently discovered a primitive tribe that worships ________","expansion":"Base"},{"cardType":"Q","text":"It's a pity that kids these days are all getting involved with ________","expansion":"Base"}]
--------------------------------------------------------------------------------
/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "size" : "29x29",
15 | "idiom" : "iphone",
16 | "filename" : "Icon-Small@2x.png",
17 | "scale" : "2x"
18 | },
19 | {
20 | "size" : "29x29",
21 | "idiom" : "iphone",
22 | "filename" : "Icon-Small@3x.png",
23 | "scale" : "3x"
24 | },
25 | {
26 | "size" : "40x40",
27 | "idiom" : "iphone",
28 | "filename" : "Icon-Small-40@2x.png",
29 | "scale" : "2x"
30 | },
31 | {
32 | "size" : "40x40",
33 | "idiom" : "iphone",
34 | "filename" : "Icon-Small-40@3x.png",
35 | "scale" : "3x"
36 | },
37 | {
38 | "size" : "60x60",
39 | "idiom" : "iphone",
40 | "filename" : "Icon-60@2x.png",
41 | "scale" : "2x"
42 | },
43 | {
44 | "size" : "60x60",
45 | "idiom" : "iphone",
46 | "filename" : "Icon-60@3x.png",
47 | "scale" : "3x"
48 | },
49 | {
50 | "idiom" : "ipad",
51 | "size" : "20x20",
52 | "scale" : "1x"
53 | },
54 | {
55 | "idiom" : "ipad",
56 | "size" : "20x20",
57 | "scale" : "2x"
58 | },
59 | {
60 | "size" : "29x29",
61 | "idiom" : "ipad",
62 | "filename" : "Icon-Small.png",
63 | "scale" : "1x"
64 | },
65 | {
66 | "size" : "29x29",
67 | "idiom" : "ipad",
68 | "filename" : "Icon-Small@2x.png",
69 | "scale" : "2x"
70 | },
71 | {
72 | "size" : "40x40",
73 | "idiom" : "ipad",
74 | "filename" : "Icon-Small-40.png",
75 | "scale" : "1x"
76 | },
77 | {
78 | "size" : "40x40",
79 | "idiom" : "ipad",
80 | "filename" : "Icon-Small-40@2x.png",
81 | "scale" : "2x"
82 | },
83 | {
84 | "size" : "76x76",
85 | "idiom" : "ipad",
86 | "filename" : "Icon-76.png",
87 | "scale" : "1x"
88 | },
89 | {
90 | "size" : "76x76",
91 | "idiom" : "ipad",
92 | "filename" : "Icon-76@2x.png",
93 | "scale" : "2x"
94 | },
95 | {
96 | "size" : "83.5x83.5",
97 | "idiom" : "ipad",
98 | "filename" : "Icon-83.5@2x.png",
99 | "scale" : "2x"
100 | },
101 | {
102 | "idiom" : "ios-marketing",
103 | "size" : "1024x1024",
104 | "scale" : "1x"
105 | }
106 | ],
107 | "info" : {
108 | "version" : 1,
109 | "author" : "xcode"
110 | }
111 | }
--------------------------------------------------------------------------------
/CardsAgainst.xcodeproj/project.xcworkspace/xcshareddata/CardsAgainst.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "D429D03651EABCB3A00B4BB7FCB2986A61F36791",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "9A64317953518213A59F0416BCEF6EF2D02BD22B" : 0,
8 | "D429D03651EABCB3A00B4BB7FCB2986A61F36791" : 0,
9 | "364D2E5F611143E7C4D0A99C691B1988FBD5381F" : 0,
10 | "9E1ECC80773D1F4F22D03CB4DEA298B3CB57FAF5" : 0
11 | },
12 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "105BF1D1-0D6F-4BAB-8720-EFD5A6A4EF69",
13 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
14 | "9A64317953518213A59F0416BCEF6EF2D02BD22B" : "CardsAgainst\/Vendor\/PeerKit\/",
15 | "D429D03651EABCB3A00B4BB7FCB2986A61F36791" : "CardsAgainst\/",
16 | "364D2E5F611143E7C4D0A99C691B1988FBD5381F" : "CardsAgainst\/Vendor\/Cartography\/",
17 | "9E1ECC80773D1F4F22D03CB4DEA298B3CB57FAF5" : "CardsAgainst\/Vendor\/SVProgressHUD\/"
18 | },
19 | "DVTSourceControlWorkspaceBlueprintNameKey" : "CardsAgainst",
20 | "DVTSourceControlWorkspaceBlueprintVersion" : 204,
21 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CardsAgainst.xcodeproj",
22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
23 | {
24 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:robb\/Cartography.git",
25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "364D2E5F611143E7C4D0A99C691B1988FBD5381F"
27 | },
28 | {
29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:jpsim\/PeerKit.git",
30 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
31 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9A64317953518213A59F0416BCEF6EF2D02BD22B"
32 | },
33 | {
34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:TransitApp\/SVProgressHUD.git",
35 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
36 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9E1ECC80773D1F4F22D03CB4DEA298B3CB57FAF5"
37 | },
38 | {
39 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/jpsim\/CardsAgainst",
40 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
41 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D429D03651EABCB3A00B4BB7FCB2986A61F36791"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/CardsAgainst/View Controllers/MenuViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuViewController.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 10/25/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cartography
11 |
12 | final class MenuViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
13 |
14 | // MARK: Properties
15 |
16 | fileprivate let startGameButton = UIButton(type: .system)
17 | fileprivate let separator = UIView()
18 | fileprivate let collectionView = UICollectionView(frame: .zero,
19 | collectionViewLayout: UICollectionViewFlowLayout())
20 |
21 | // MARK: Lifecycle
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | // UI
27 | setupNavigationBar()
28 | setupLaunchImage()
29 | setupStartGameButton()
30 | setupSeparator()
31 | setupCollectionView()
32 | }
33 |
34 | override func viewWillAppear(_ animated: Bool) {
35 | super.viewWillAppear(animated)
36 |
37 | ConnectionManager.onConnect { _, _ in
38 | self.updatePlayers()
39 | }
40 | ConnectionManager.onDisconnect { _, _ in
41 | self.updatePlayers()
42 | }
43 | ConnectionManager.onEvent(.startGame) { [unowned self] _, object in
44 | let dict = object as! [String: NSData]
45 | let blackCard = Card(mpcSerialized: dict["blackCard"]! as Data)
46 | let whiteCards = CardArray(mpcSerialized: dict["whiteCards"]! as Data).array
47 | self.startGame(blackCard: blackCard, whiteCards: whiteCards)
48 | }
49 | }
50 |
51 | override func viewWillDisappear(_ animated: Bool) {
52 | ConnectionManager.onConnect(nil)
53 | ConnectionManager.onDisconnect(nil)
54 | ConnectionManager.onEvent(.startGame, run: nil)
55 |
56 | super.viewWillDisappear(animated)
57 | }
58 |
59 | // MARK: UI
60 |
61 | fileprivate func setupNavigationBar() {
62 | navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
63 | navigationController!.navigationBar.shadowImage = UIImage()
64 | navigationController!.navigationBar.isTranslucent = true
65 | }
66 |
67 | fileprivate func setupLaunchImage() {
68 | view.addSubview(UIImageView(image: .launchImage()))
69 |
70 | let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
71 | blurView.frame = view.bounds
72 | view.addSubview(blurView)
73 | }
74 |
75 | fileprivate func setupStartGameButton() {
76 | // Button
77 | startGameButton.translatesAutoresizingMaskIntoConstraints = false
78 | startGameButton.titleLabel!.font = startGameButton.titleLabel!.font.withSize(25)
79 | startGameButton.setTitle("Waiting For Players", for: .disabled)
80 | startGameButton.setTitle("Start Game", for: UIControlState())
81 | startGameButton.addTarget(
82 | self,
83 | action: #selector(MenuViewController.startGame as (MenuViewController) -> () -> Void),
84 | for: .touchUpInside
85 | )
86 | startGameButton.isEnabled = false
87 | view.addSubview(startGameButton)
88 |
89 | // Layout
90 | constrain(startGameButton) { button in
91 | button.top == button.superview!.top + 60
92 | button.centerX == button.superview!.centerX
93 | }
94 | }
95 |
96 | fileprivate func setupSeparator() {
97 | // Separator
98 | separator.translatesAutoresizingMaskIntoConstraints = false
99 | separator.backgroundColor = lightColor
100 | view.addSubview(separator)
101 |
102 | // Layout
103 | constrain(separator, startGameButton) { separator, startGameButton in
104 | separator.top == startGameButton.bottom + 10
105 | separator.centerX == separator.superview!.centerX
106 | separator.width == separator.superview!.width - 40
107 | separator.height == 1 / UIScreen.main.scale
108 | }
109 | }
110 |
111 | fileprivate func setupCollectionView() {
112 | // Collection View
113 | collectionView.dataSource = self
114 | collectionView.delegate = self
115 | collectionView.backgroundColor = .clear
116 | collectionView.translatesAutoresizingMaskIntoConstraints = false
117 | collectionView.register(PlayerCell.self, forCellWithReuseIdentifier: PlayerCell.reuseID)
118 | collectionView.alwaysBounceVertical = true
119 | view.addSubview(collectionView)
120 |
121 | // Layout
122 | constrain(collectionView, separator) { collectionView, separator in
123 | collectionView.top == separator.bottom
124 | collectionView.left == separator.left
125 | collectionView.right == separator.right
126 | collectionView.bottom == collectionView.superview!.bottom
127 | }
128 | }
129 |
130 | // MARK: Actions
131 |
132 | @objc func startGame() {
133 | let blackCard = CardManager.nextCardsWithType(.black).first!
134 | let whiteCards = CardManager.nextCardsWithType(.white, count: 10)
135 | sendBlackCard(blackCard)
136 | startGame(blackCard: blackCard, whiteCards: whiteCards)
137 | }
138 |
139 | fileprivate func startGame(blackCard: Card, whiteCards: [Card]) {
140 | let gameVC = GameViewController(blackCard: blackCard, whiteCards: whiteCards)
141 | navigationController!.pushViewController(gameVC, animated: true)
142 | }
143 |
144 | // MARK: Multipeer
145 |
146 | fileprivate func sendBlackCard(_ blackCard: Card) {
147 | ConnectionManager.sendEventForEach(.startGame) {
148 | let whiteCards = CardManager.nextCardsWithType(.white, count: 10)
149 | let whiteCardsArray = CardArray(array: whiteCards)
150 | return ["blackCard": blackCard, "whiteCards": whiteCardsArray]
151 | }
152 | }
153 |
154 | fileprivate func updatePlayers() {
155 | startGameButton.isEnabled = (ConnectionManager.otherPlayers.count > 0)
156 | collectionView.reloadData()
157 | }
158 |
159 | // MARK: UICollectionViewDataSource
160 |
161 | @objc func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
162 | return ConnectionManager.otherPlayers.count
163 | }
164 |
165 | @objc func collectionView(_ collectionView: UICollectionView,
166 | cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
167 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PlayerCell.reuseID,
168 | for: indexPath) as! PlayerCell
169 | cell.label.text = ConnectionManager.otherPlayers[indexPath.row].name
170 | return cell
171 | }
172 |
173 | @objc func collectionView(_ collectionView: UICollectionView,
174 | layout collectionViewLayout: UICollectionViewLayout,
175 | sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
176 | return CGSize(width: collectionView.frame.size.width - 32, height: 50)
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/CardsAgainst/View Controllers/GameViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GameViewController.swift
3 | // CardsAgainst
4 | //
5 | // Created by JP Simard on 11/2/14.
6 | // Copyright (c) 2014 JP Simard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Cartography
11 |
12 | final class GameViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
13 |
14 | // MARK: Properties
15 |
16 | // Data
17 | fileprivate var gameState = GameState.pickingCard
18 | fileprivate var blackCard: Card
19 | fileprivate var whiteCards: [Card]
20 | fileprivate var answers = [Answer]()
21 | fileprivate var votes = [Vote]()
22 | fileprivate var numberOfCardsPlayed = 0
23 | fileprivate var scores = [Player: Int]()
24 | fileprivate var hasVoted: Bool = false {
25 | didSet {
26 | voteButton.tintColor = hasVoted ? lightColor : appTintColor
27 | voteButton.isUserInteractionEnabled = !hasVoted
28 | scrollViewDidEndDecelerating(scrollView)
29 | }
30 | }
31 |
32 | // UI
33 | fileprivate let blackCardLabel = TouchableLabel()
34 | fileprivate let whiteCardCollectionView = UICollectionView(frame: .zero,
35 | collectionViewLayout: WhiteCardFlowLayout())
36 | fileprivate let pageControl = UIPageControl()
37 | fileprivate let scrollView = UIScrollView()
38 | fileprivate let scrollViewContentView = UIView()
39 | fileprivate let voteButton = UIButton(type: .system)
40 |
41 | // UI Helper
42 | fileprivate var blackCardLabelBottomConstraint = NSLayoutConstraint()
43 | fileprivate var otherBlackCardViews = [UIView]()
44 | fileprivate let cellHeights = NSCache()
45 | private var kvoObserver: NSKeyValueObservation?
46 |
47 | // Computed Properties
48 | fileprivate var voteeForCurrentPage: Player {
49 | return voteeForPage(pageControl.currentPage)
50 | }
51 | fileprivate var hasEveryPeerAnswered: Bool {
52 | return answers.count == ConnectionManager.otherPlayers.count
53 | }
54 | fileprivate var hasEveryPeerVoted: Bool {
55 | return votes.count == ConnectionManager.allPlayers.count
56 | }
57 | fileprivate var winner: Player? {
58 | if votes.count < 2 {
59 | return nil
60 | }
61 | var votesForPlayers = [Player: Int]()
62 | for votee in votes.map({ $0.votee }) {
63 | if let freq = votesForPlayers[votee] {
64 | votesForPlayers[votee] = freq + 1
65 | } else {
66 | votesForPlayers[votee] = 1
67 | }
68 | }
69 | if votesForPlayers.count == 1 {
70 | return votesForPlayers.keys.first!
71 | }
72 | let sortedVotes = votesForPlayers.values.sorted { $0 > $1 }
73 | let maxVotes = sortedVotes[0]
74 | if maxVotes == sortedVotes[1] {
75 | return nil // Tie
76 | }
77 | return votesForPlayers.keys.filter({votesForPlayers[$0] == maxVotes}).first!
78 | }
79 | fileprivate var stats: String {
80 | return scores.keys.map({ "\($0.displayName): \(scores[$0] ?? 0)" }).joined(separator: "\n")
81 | }
82 | fileprivate var unansweredPlayers: [Player] {
83 | let answeredPlayers = answers.map { $0.sender }
84 | return ConnectionManager.otherPlayers.filter { !answeredPlayers.contains($0) }
85 | }
86 | fileprivate var waitingForPeersMessage: String {
87 | return "Waiting for " + unansweredPlayers.map({ $0.name }).joined(separator: ", ")
88 | }
89 |
90 | // MARK: View Lifecycle
91 |
92 | init(blackCard: Card, whiteCards: [Card]) {
93 | self.blackCard = blackCard
94 | self.whiteCards = whiteCards
95 |
96 | super.init(nibName: nil, bundle: nil)
97 | }
98 |
99 | required init?(coder aDecoder: NSCoder) {
100 | fatalError("init(coder:) has not been implemented")
101 | }
102 |
103 | override func viewDidLoad() {
104 | super.viewDidLoad()
105 | navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
106 | navigationController!.navigationBar.shadowImage = UIImage()
107 | cellHeights.countLimit = 20
108 | view.backgroundColor = appBackgroundColor
109 |
110 | updateTitle()
111 |
112 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Stats", style: .plain, target: self,
113 | action: #selector(showStats))
114 |
115 | // UI
116 | setupVoteButton()
117 | setupPageControl()
118 | setupScrollView()
119 | setupWhiteCardCollectionView()
120 | setupBlackCard()
121 |
122 | // Other setup
123 | blackCardLabel.text = blackCard.content
124 | blackCardLabel.font = .blackCardFont
125 | whiteCardCollectionView.reloadData()
126 |
127 | for player in ConnectionManager.allPlayers {
128 | scores[player] = 0
129 | }
130 | }
131 |
132 | override func viewWillAppear(_ animated: Bool) {
133 | super.viewWillAppear(animated)
134 |
135 | // KVO
136 | kvoObserver = blackCardLabel.observe(\.bounds, options: .new) { label, _ in
137 | self.whiteCardCollectionView.contentInset = UIEdgeInsets(top: label.frame.size.height + 20 + 64,
138 | left: 0, bottom: 20, right: 0)
139 | self.whiteCardCollectionView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1),
140 | animated: true)
141 | }
142 |
143 | setupMultipeerEventHandlers()
144 | }
145 |
146 | override func viewWillDisappear(_ animated: Bool) {
147 | kvoObserver?.invalidate()
148 | let observedEvents: [Event] = [.answer, .cancelAnswer, .vote, .nextCard, .endGame]
149 | for event in observedEvents {
150 | ConnectionManager.onEvent(event, run: nil)
151 | }
152 |
153 | super.viewWillDisappear(animated)
154 | }
155 |
156 | // MARK: UI Setup
157 |
158 | fileprivate func setupVoteButton() {
159 | // Button
160 | voteButton.translatesAutoresizingMaskIntoConstraints = false
161 | view.addSubview(voteButton)
162 | voteButton.isEnabled = false
163 | voteButton.titleLabel?.numberOfLines = 0
164 | voteButton.titleLabel?.textAlignment = .center
165 | voteButton.titleLabel?.font = UIFont.voteButtonFont
166 | voteButton.addTarget(self, action: #selector(vote), for: .touchUpInside)
167 |
168 | // Layout
169 | constrain(voteButton) { voteButton in
170 | voteButton.bottom == voteButton.superview!.bottom - 16
171 | voteButton.centerX == voteButton.superview!.centerX
172 | voteButton.width == voteButton.superview!.width - 32
173 | }
174 | }
175 |
176 | fileprivate func setupPageControl() {
177 | // Page Control
178 | pageControl.translatesAutoresizingMaskIntoConstraints = false
179 | view.addSubview(pageControl)
180 | pageControl.numberOfPages = ConnectionManager.otherPlayers.count + 1
181 |
182 | // Layout
183 | constrain(pageControl, voteButton) { pageControl, voteButton in
184 | pageControl.bottom == voteButton.top
185 | pageControl.centerX == pageControl.superview!.centerX
186 | }
187 | }
188 |
189 | fileprivate func setupScrollView() {
190 | // Scroll View
191 | scrollView.translatesAutoresizingMaskIntoConstraints = false
192 | view.addSubview(scrollView)
193 | scrollView.delegate = self
194 | scrollView.isScrollEnabled = false
195 | scrollView.alwaysBounceHorizontal = true
196 | scrollView.isPagingEnabled = true
197 | scrollView.showsHorizontalScrollIndicator = false
198 |
199 | // Layout
200 | constrain(scrollView) { scrollView in
201 | scrollView.edges == scrollView.superview!.edges
202 | }
203 |
204 | // Scroll View Content View
205 | scrollViewContentView.frame = view.bounds
206 | scrollView.addSubview(scrollViewContentView)
207 | }
208 |
209 | fileprivate func setupBlackCard() {
210 | // Label
211 | blackCardLabel.translatesAutoresizingMaskIntoConstraints = false
212 | scrollViewContentView.addSubview(blackCardLabel)
213 | blackCardLabel.contentMode = .top
214 | blackCardLabel.textColor = lightColor
215 | blackCardLabel.numberOfLines = 0
216 | blackCardLabel.minimumScaleFactor = 0.5
217 | blackCardLabel.adjustsFontSizeToFitWidth = true
218 |
219 | // Layout
220 | constrain(blackCardLabel, scrollViewContentView) { blackCardLabel, scrollViewContentView in
221 | blackCardLabel.top == scrollViewContentView.top + 64
222 | blackCardLabel.width == scrollViewContentView.width - 32
223 | blackCardLabel.leading == scrollViewContentView.leading + 16
224 | blackCardLabelBottomConstraint = (blackCardLabel.bottom <= scrollViewContentView.bottom - 200)
225 | }
226 |
227 | // Gesture
228 | blackCardLabel.addGestureRecognizer(UITapGestureRecognizer(target: self,
229 | action: #selector(removeLastWhiteCard)))
230 | }
231 |
232 | fileprivate func setupWhiteCardCollectionView() {
233 | // Collection View
234 | whiteCardCollectionView.translatesAutoresizingMaskIntoConstraints = false
235 | scrollViewContentView.addSubview(whiteCardCollectionView)
236 | whiteCardCollectionView.register(WhiteCardCell.self,
237 | forCellWithReuseIdentifier: WhiteCardCell.reuseID)
238 | whiteCardCollectionView.showsVerticalScrollIndicator = false
239 | whiteCardCollectionView.alwaysBounceVertical = true
240 | whiteCardCollectionView.dataSource = self
241 | whiteCardCollectionView.delegate = self
242 | whiteCardCollectionView.backgroundColor = appBackgroundColor
243 |
244 | // Layout
245 | constrain(whiteCardCollectionView) { whiteCardCollectionView in
246 | whiteCardCollectionView.edges == whiteCardCollectionView.superview!.edges
247 | }
248 | }
249 |
250 | // MARK: UI Derived
251 |
252 | override func didMove(toParentViewController parent: UIViewController?) {
253 | // User initiated pop
254 | if parent == nil {
255 | ConnectionManager.sendEvent(.endGame)
256 | }
257 | }
258 |
259 | fileprivate func updateTitle() {
260 | numberOfCardsPlayed += 1
261 | title = "Card \(numberOfCardsPlayed)"
262 | }
263 |
264 | fileprivate func prepareForBlackCards() {
265 | scrollView.contentSize = CGSize(width: view.frame.size.width, height: 0)
266 | voteButton.isEnabled = false
267 | pageControl.alpha = 0
268 |
269 | for view in otherBlackCardViews {
270 | view.removeFromSuperview()
271 | }
272 | otherBlackCardViews.removeAll(keepingCapacity: true)
273 |
274 | if hasEveryPeerAnswered {
275 | pickWinner()
276 | } else {
277 | updateWaitingForPeers()
278 | }
279 | }
280 |
281 | fileprivate func updateWaitingForPeers() {
282 | if unansweredPlayers.count > 0 {
283 | voteButton.setTitle(waitingForPeersMessage, for: .disabled)
284 | } else {
285 | updateVoteButton()
286 | }
287 | }
288 |
289 | fileprivate func updateVoteButton() {
290 | let cardString = voteeForCurrentPage.cardString(hasVoted)
291 | let votesString = Vote.stringFromVoteCount(voteCountForPage(pageControl.currentPage))
292 | voteButton.setTitle("\(cardString) (\(votesString))", for: UIControlState())
293 | }
294 |
295 | fileprivate func generateBlackCards() {
296 | pageControl.numberOfPages = answers.count + 1
297 | scrollView.contentSize = CGSize(width: view.frame.size.width * CGFloat(pageControl.numberOfPages), height: 0)
298 | for (index, answer) in answers.enumerated() {
299 | // Content View
300 | let contentFrame = scrollViewContentView.frame.offsetBy(
301 | dx: scrollViewContentView.frame.size.width * CGFloat(index + 1),
302 | dy: 0
303 | )
304 | let contentView = UIView(frame: contentFrame)
305 | scrollView.addSubview(contentView)
306 | otherBlackCardViews.append(contentView)
307 |
308 | // Black Card Label
309 | let blackCardLabel = TouchableLabel()
310 | blackCardLabel.translatesAutoresizingMaskIntoConstraints = false
311 | contentView.addSubview(blackCardLabel)
312 | blackCardLabel.contentMode = .top
313 | blackCardLabel.textColor = lightColor
314 | blackCardLabel.numberOfLines = 0
315 | blackCardLabel.minimumScaleFactor = 0.5
316 | blackCardLabel.adjustsFontSizeToFitWidth = true
317 |
318 | blackCardLabel.attributedText = answer.answer
319 | // override remote font size with our own screen-specific size
320 | blackCardLabel.font = self.blackCardLabel.font
321 |
322 | // Layout
323 | constrain(blackCardLabel, contentView) { blackCardLabel, contentView in
324 | blackCardLabel.top == contentView.top + 64
325 | blackCardLabel.width == contentView.width - 32
326 | blackCardLabel.leading == contentView.leading + 16
327 | blackCardLabel.bottom <= contentView.bottom - 80
328 | }
329 | }
330 | }
331 |
332 | // MARK: Multipeer
333 |
334 | fileprivate func setupMultipeerEventHandlers() {
335 | // Answer
336 | ConnectionManager.onEvent(.answer) { [unowned self] peer, object in
337 | let dict = object as! [String: NSData]
338 | let attr = MPCAttributedString(mpcSerialized: dict["answer"]! as Data).attributedString
339 | self.answers.append(Answer(sender: Player(peer: peer), answer: attr))
340 | self.updateWaitingForPeers()
341 | if self.gameState != .pickingCard && self.hasEveryPeerAnswered {
342 | self.pickWinner()
343 | }
344 | }
345 |
346 | // Cancel Answer
347 | ConnectionManager.onEvent(.cancelAnswer) { [unowned self] peer, _ in
348 | let sender = Player(peer: peer)
349 | self.answers = self.answers.filter { $0.sender != sender }
350 | self.updateWaitingForPeers()
351 | }
352 |
353 | // Vote
354 | ConnectionManager.onEvent(.vote) { [unowned self] peer, object in
355 | let voter = Player(peer: peer)
356 | let votee = Player(mpcSerialized: (object as! [String: NSData])["votee"]! as Data)
357 | self.addVote(voter, to: votee)
358 | }
359 |
360 | // Next Card
361 | ConnectionManager.onEvent(.nextCard) { [unowned self] _, object in
362 | let dict = object as! [String: NSData]
363 | let winner = Player(mpcSerialized: dict["winner"]! as Data)
364 | let blackCard = Card(mpcSerialized: dict["blackCard"]! as Data)
365 | let whiteCards = CardArray(mpcSerialized: dict["whiteCards"]! as Data).array
366 | self.scores[winner]! += 1
367 | self.nextBlackCard(blackCard, newWhiteCards: whiteCards, winner: winner)
368 | }
369 |
370 | // End Game
371 | ConnectionManager.onEvent(.endGame) { [unowned self] _, _ in
372 | self.dismiss()
373 | }
374 | }
375 |
376 | // MARK: Actions
377 |
378 | fileprivate func dismiss() {
379 | navigationController?.popViewController(animated: true)
380 | }
381 |
382 | fileprivate func nextCardWithWinner(_ winner: Player) {
383 | let blackCard = CardManager.nextCardsWithType(.black).first!
384 | scores[winner]! += 1
385 | ConnectionManager.sendEventForEach(.nextCard) {
386 | let nextWhiteCards = CardManager.nextCardsWithType(.white, count: UInt(10 - self.whiteCards.count))
387 | let payload: [String: MPCSerializable] = [
388 | "blackCard": blackCard,
389 | "whiteCards": CardArray(array: nextWhiteCards),
390 | "winner": winner
391 | ]
392 | return payload
393 | }
394 | let newWhiteCards = CardManager.nextCardsWithType(.white, count: UInt(10 - whiteCards.count))
395 | nextBlackCard(blackCard, newWhiteCards: newWhiteCards, winner: winner)
396 | }
397 |
398 | fileprivate func nextBlackCard(_ blackCard: Card, newWhiteCards: [Card], winner: Player) {
399 | showWinner(winner)
400 | answers = [Answer]()
401 | pageControl.currentPage = 0
402 | blackCardLabel.isUserInteractionEnabled = true
403 | gameState = .pickingCard
404 | blackCardLabel.placeholderRanges = [NSRange]()
405 | scrollView.contentOffset = CGPoint.zero
406 | blackCardLabelBottomConstraint.constant = -200
407 | scrollView.isScrollEnabled = false
408 | view.sendSubview(toBack: voteButton)
409 | view.sendSubview(toBack: pageControl)
410 |
411 | blackCardLabel.text = blackCard.content
412 | blackCardLabel.font = UIFont.blackCardFont
413 | whiteCards += newWhiteCards
414 | whiteCardCollectionView.reloadData()
415 | whiteCardCollectionView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: false)
416 | UIView.animate(withDuration: 0.33, animations: {
417 | self.whiteCardCollectionView.alpha = 1
418 | self.scrollView.isScrollEnabled = false
419 | self.scrollViewContentView.layoutSubviews()
420 | self.viewDidLayoutSubviews()
421 | })
422 | for view in otherBlackCardViews {
423 | view.removeFromSuperview()
424 | }
425 | otherBlackCardViews.removeAll(keepingCapacity: true)
426 | votes = [Vote]()
427 | hasVoted = false
428 | updateTitle()
429 | }
430 |
431 | // MARK: HUD
432 |
433 | fileprivate func showWinner(_ winner: Player) {
434 | showHUD("\(winner.winningString())\n\n\(stats)", duration: 2)
435 | }
436 |
437 | @objc func showStats() {
438 | showHUD(stats)
439 | }
440 |
441 | fileprivate func showHUD(_ status: String, duration: Double = 1) {
442 | SVProgressHUD.setDefaultMaskType(.black)
443 | SVProgressHUD.show(withStatus: status)
444 | let delay = DispatchTime.now() + Double(Int64(duration * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
445 | DispatchQueue.main.asyncAfter(deadline: delay) {
446 | SVProgressHUD.dismiss()
447 | }
448 | }
449 |
450 | // MARK: Voting
451 |
452 | @objc func vote() {
453 | if hasVoted {
454 | return
455 | }
456 | let votee = voteeForCurrentPage
457 | addVote(.getMe(), to: votee)
458 | hasVoted = true
459 | ConnectionManager.sendEvent(.vote, object: ["votee": votee])
460 |
461 | if hasEveryPeerVoted {
462 | if let winner = winner {
463 | nextCardWithWinner(winner)
464 | } else {
465 | handleTie()
466 | }
467 | }
468 | }
469 |
470 | fileprivate func addVote(_ from: Player, to: Player) {
471 | votes.append(Vote(votee: to, voter: from))
472 | if gameState != .pickingCard {
473 | scrollViewDidEndDecelerating(scrollView)
474 | }
475 | }
476 |
477 | fileprivate func handleTie() {
478 | let alert = UIAlertController(title: "Tie Breaker!",
479 | message: "There was a tie! You picked last, so you decide who wins",
480 | preferredStyle: .alert)
481 | for player in ConnectionManager.allPlayers {
482 | alert.addAction(UIAlertAction(title: player.name,
483 | style: .default) { _ in
484 | self.nextCardWithWinner(player)
485 | })
486 | }
487 | present(alert, animated: true) {}
488 | }
489 |
490 | fileprivate func pickWinner() {
491 | if gameState != .waitingForOthers {
492 | return
493 | }
494 | gameState = .pickingWinner
495 | scrollViewDidEndDecelerating(scrollView)
496 | voteButton.isEnabled = true
497 | scrollView.contentOffset = .zero
498 | generateBlackCards()
499 | blackCardLabel.isUserInteractionEnabled = false
500 | UIView.animate(withDuration: 2) {
501 | self.pageControl.alpha = 1
502 | }
503 | }
504 |
505 | // MARK: Adding/Removing Cards
506 |
507 | fileprivate func addSelectedCardToBlackCard(_ selectedCard: Card) {
508 | if let range = blackCardLabel.text?.range(of: blackCardPlaceholder) {
509 | blackCardLabel.text = blackCardLabel.text?.replacingCharacters(in: range, with: selectedCard.content)
510 | let start = blackCardLabel.text!.characters.distance(from: blackCardLabel.text!.startIndex,
511 | to: range.lowerBound)
512 | let length = selectedCard.content.characters.count
513 | blackCardLabel.placeholderRanges.append(NSRange(location: start, length: length))
514 | } else {
515 | let range = NSRange(location: blackCardLabel.text!.characters.count + 1,
516 | length: selectedCard.content.characters.count)
517 | blackCardLabel.placeholderRanges.append(range)
518 | blackCardLabel.text! += "\n\(selectedCard.content)"
519 | }
520 | let blackCardStyled = NSMutableAttributedString(string: blackCardLabel.text!)
521 | for range in blackCardLabel.placeholderRanges {
522 | blackCardStyled.addAttribute(.foregroundColor, value: appTintColor, range: range)
523 | }
524 | blackCardLabel.attributedText = blackCardStyled
525 | if blackCardLabel.text?.range(of: blackCardPlaceholder) == nil {
526 | gameState = .waitingForOthers
527 | blackCardLabel.font = UIFont.blackCardFont
528 | blackCardLabelBottomConstraint.constant = -80
529 | UIView.animate(withDuration: 0.33, animations: {
530 | self.whiteCardCollectionView.alpha = 0
531 | self.scrollView.isScrollEnabled = true
532 | self.scrollViewContentView.layoutSubviews()
533 | self.viewDidLayoutSubviews()
534 | }, completion: { _ in
535 | self.view.bringSubview(toFront: self.voteButton)
536 | })
537 |
538 | let attr = MPCAttributedString(attributedString: blackCardLabel.attributedText!)
539 | ConnectionManager.sendEvent(.answer, object: ["answer": attr])
540 | prepareForBlackCards()
541 | }
542 | }
543 |
544 | @objc func removeLastWhiteCard() {
545 | if let lastRange = blackCardLabel.placeholderRanges.last {
546 | let blackCardLabelNSString = blackCardLabel.text! as NSString
547 | whiteCardCollectionView.performBatchUpdates({
548 | let content = blackCardLabelNSString.substring(with: lastRange)
549 | let lastWhiteCard = Card(content: content, type: .white, expansion: "")
550 | self.whiteCards.append(lastWhiteCard)
551 | let indexPath = IndexPath(item: self.whiteCards.count - 1, section: 0)
552 | self.whiteCardCollectionView.insertItems(at: [indexPath])
553 | }, completion: nil)
554 | blackCardLabel.text = blackCardLabelNSString.replacingCharacters(in: lastRange, with: blackCardPlaceholder)
555 | let placeholderlessLength = blackCardPlaceholder.characters.count + 1
556 |
557 | let blackCardLabelSubstring = blackCardLabelNSString
558 | .substring(from: blackCardLabelNSString.length - placeholderlessLength)
559 | if blackCardLabelSubstring == "\n\(blackCardPlaceholder)" {
560 | blackCardLabel.text = blackCardLabelNSString
561 | .substring(to: blackCardLabelNSString.length - placeholderlessLength)
562 | }
563 | blackCardLabel.placeholderRanges.removeLast()
564 | let blackCardStyled = NSMutableAttributedString(string: blackCardLabel.text!)
565 | for range in blackCardLabel.placeholderRanges {
566 | blackCardStyled.addAttribute(.foregroundColor, value: appTintColor, range: range)
567 | }
568 | blackCardLabel.attributedText = blackCardStyled
569 | gameState = .pickingCard
570 | blackCardLabel.font = UIFont.blackCardFont
571 | view.sendSubview(toBack: voteButton)
572 | view.sendSubview(toBack: pageControl)
573 | blackCardLabelBottomConstraint.constant = -200
574 | UIView.animate(withDuration: 0.33) {
575 | self.whiteCardCollectionView.alpha = 1
576 | self.scrollView.isScrollEnabled = false
577 | self.scrollViewContentView.layoutSubviews()
578 | self.viewDidLayoutSubviews()
579 | }
580 | ConnectionManager.sendEvent(.cancelAnswer)
581 | }
582 | }
583 |
584 | fileprivate func removeCardAtIndexPath(_ indexPath: IndexPath) {
585 | if gameState == .pickingWinner {
586 | return
587 | }
588 | whiteCardCollectionView.performBatchUpdates({
589 | self.whiteCardCollectionView.deleteItems(at: [indexPath])
590 | self.whiteCards.remove(at: indexPath.row)
591 | }, completion: nil)
592 | }
593 |
594 | // MARK: Logic
595 |
596 | fileprivate func voteCountForPage(_ page: Int) -> Int {
597 | let votee = voteeForPage(page)
598 | return votes.filter({ $0.votee.name == votee.name }).count
599 | }
600 |
601 | fileprivate func voteeForPage(_ page: Int) -> Player {
602 | if page > 0 {
603 | return answers[page - 1].sender
604 | }
605 | return .getMe()
606 | }
607 |
608 | // MARK: UICollectionViewDataSource
609 |
610 | @objc func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
611 | return whiteCards.count
612 | }
613 |
614 | @objc func collectionView(_ collectionView: UICollectionView,
615 | cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
616 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WhiteCardCell.reuseID,
617 | for: indexPath) as! WhiteCardCell
618 | cell.label.text = whiteCards[indexPath.row].content
619 | cell.setNeedsUpdateConstraints()
620 | cell.updateConstraintsIfNeeded()
621 | return cell
622 | }
623 |
624 | @objc func collectionView(_ collectionView: UICollectionView,
625 | layout collectionViewLayout: UICollectionViewLayout,
626 | sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
627 | let hash = whiteCards[indexPath.row].content.hash
628 | var size = CGSize(width: collectionView.frame.size.width - 32, height: 50)
629 | if let heightNumber = cellHeights.object(forKey: hash as AnyObject) as? NSNumber {
630 | size.height = CGFloat(heightNumber.floatValue)
631 | return size
632 | }
633 | let cell = WhiteCardCell(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height))
634 | cell.label.text = whiteCards[indexPath.row].content
635 | cell.setNeedsUpdateConstraints()
636 | cell.updateConstraintsIfNeeded()
637 | cell.setNeedsLayout()
638 | cell.layoutIfNeeded()
639 | let cellSize = cell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
640 | size.height = cellSize.height + 1
641 | cellHeights.setObject(size.height as AnyObject, forKey: hash as AnyObject)
642 |
643 | return size
644 | }
645 |
646 | // MARK: - UICollectionViewDelegate
647 |
648 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
649 | addSelectedCardToBlackCard(whiteCards[indexPath.row])
650 | removeCardAtIndexPath(indexPath)
651 | }
652 |
653 | // MARK: Paging
654 |
655 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
656 | if scrollView == self.scrollView {
657 | let page = round(scrollView.contentOffset.x / scrollView.frame.size.width)
658 | pageControl.currentPage = Int(page)
659 | updateVoteButton()
660 | }
661 | }
662 | }
663 |
--------------------------------------------------------------------------------
/CardsAgainst.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D4CD0B9E1A2D3AD7003B83B4 /* UIFont+CardsAgainst.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CD0B9D1A2D3AD7003B83B4 /* UIFont+CardsAgainst.swift */; };
11 | E82FC1901A0B57B600516EE5 /* UIImage+LaunchImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82FC18F1A0B57B600516EE5 /* UIImage+LaunchImage.swift */; };
12 | E852ACA51A0C3B5800AFA58D /* cards_pg13.json in Resources */ = {isa = PBXBuildFile; fileRef = E852ACA41A0C3B5800AFA58D /* cards_pg13.json */; };
13 | E8652A891A099AF50029BC21 /* PlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8652A881A099AF50029BC21 /* PlayerCell.swift */; };
14 | E865F8761A07FF41001C5E11 /* WhiteCardFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E865F8751A07FF41001C5E11 /* WhiteCardFlowLayout.swift */; };
15 | E865F87B1A08016B001C5E11 /* WhiteCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E865F87A1A08016B001C5E11 /* WhiteCardCell.swift */; };
16 | E86DCD141A075E16009BEC5A /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86DCD131A075E16009BEC5A /* Player.swift */; };
17 | E86DCD191A0766C6009BEC5A /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86DCD181A0766C6009BEC5A /* ConnectionManager.swift */; };
18 | E86DCD1B1A0768F8009BEC5A /* CardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86DCD1A1A0768F8009BEC5A /* CardManager.swift */; };
19 | E86DCD1D1A07692E009BEC5A /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86DCD1C1A07692E009BEC5A /* Card.swift */; };
20 | E86DCD1F1A077833009BEC5A /* TouchableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86DCD1E1A077833009BEC5A /* TouchableLabel.swift */; };
21 | E86DCD211A078E72009BEC5A /* MPCAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86DCD201A078E72009BEC5A /* MPCAttributedString.swift */; };
22 | E8A6C6B11CFF8ACB0088DB27 /* SVProgressAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = E8A6C6B01CFF8ACB0088DB27 /* SVProgressAnimatedView.m */; };
23 | E8AF292F1A074F52004F4E73 /* Cartography.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AF29261A074F2D004F4E73 /* Cartography.framework */; };
24 | E8AF29311A074F61004F4E73 /* Cartography.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = E8AF29261A074F2D004F4E73 /* Cartography.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
25 | E8AF29331A0759B3004F4E73 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF29321A0759B3004F4E73 /* GameViewController.swift */; };
26 | E8AF29351A0759BC004F4E73 /* GameState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF29341A0759BC004F4E73 /* GameState.swift */; };
27 | E8C9BBA61A0807D80084813D /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = E8C9BB941A0807D80084813D /* SVProgressHUD.bundle */; };
28 | E8C9BBA71A0807D80084813D /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = E8C9BB961A0807D80084813D /* SVProgressHUD.m */; };
29 | E8C9BBB41A0832AA0084813D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E8C9BBB31A0832AA0084813D /* Images.xcassets */; };
30 | E8C9BBC11A08569B0084813D /* cards.json in Resources */ = {isa = PBXBuildFile; fileRef = E8C9BBC01A08569B0084813D /* cards.json */; };
31 | E8D6D1E41A0B665D00CDF953 /* Vote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D6D1E31A0B665D00CDF953 /* Vote.swift */; };
32 | E8D6D1E91A0B667000CDF953 /* Answer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D6D1E81A0B667000CDF953 /* Answer.swift */; };
33 | E8DB8BFA1CA069F10002D4DD /* SVIndefiniteAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = E8DB8BF71CA069F10002D4DD /* SVIndefiniteAnimatedView.m */; };
34 | E8DB8BFB1CA069F20002D4DD /* SVRadialGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = E8DB8BF91CA069F10002D4DD /* SVRadialGradientLayer.m */; };
35 | E8F188FB19FCA3EA001C5080 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F188FA19FCA3EA001C5080 /* AppDelegate.swift */; };
36 | E8F1891119FCA3EA001C5080 /* CardsAgainstTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1891019FCA3EA001C5080 /* CardsAgainstTests.swift */; };
37 | E8F1891B19FCA524001C5080 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1891A19FCA524001C5080 /* Colors.swift */; };
38 | E8F805E71A0C3EA300D95CC3 /* PeerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8F805E21A0C3E8700D95CC3 /* PeerKit.framework */; };
39 | E8F805E81A0C3EA700D95CC3 /* PeerKit.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = E8F805E21A0C3E8700D95CC3 /* PeerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
40 | E8FE921419FCA66700C4977B /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FE921319FCA66700C4977B /* MenuViewController.swift */; };
41 | /* End PBXBuildFile section */
42 |
43 | /* Begin PBXContainerItemProxy section */
44 | E805BCB11CA06295006EAA76 /* PBXContainerItemProxy */ = {
45 | isa = PBXContainerItemProxy;
46 | containerPortal = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
47 | proxyType = 2;
48 | remoteGlobalIDString = 632F090A1BF1E7AA002431A3;
49 | remoteInfo = "Cartography-tvOS";
50 | };
51 | E805BCB31CA06295006EAA76 /* PBXContainerItemProxy */ = {
52 | isa = PBXContainerItemProxy;
53 | containerPortal = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
54 | proxyType = 2;
55 | remoteGlobalIDString = 632F09221BF1E7FC002431A3;
56 | remoteInfo = "Cartography-tvOS-tests";
57 | };
58 | E805BCB81CA06295006EAA76 /* PBXContainerItemProxy */ = {
59 | isa = PBXContainerItemProxy;
60 | containerPortal = E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */;
61 | proxyType = 2;
62 | remoteGlobalIDString = E86EC4421AB734F5001A7734;
63 | remoteInfo = "PeerKit-iOS-Tests";
64 | };
65 | E805BCBA1CA06295006EAA76 /* PBXContainerItemProxy */ = {
66 | isa = PBXContainerItemProxy;
67 | containerPortal = E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */;
68 | proxyType = 2;
69 | remoteGlobalIDString = E8DC82821AB7306C000BB585;
70 | remoteInfo = "PeerKit-OSX";
71 | };
72 | E805BCBC1CA06295006EAA76 /* PBXContainerItemProxy */ = {
73 | isa = PBXContainerItemProxy;
74 | containerPortal = E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */;
75 | proxyType = 2;
76 | remoteGlobalIDString = E86EC4521AB73597001A7734;
77 | remoteInfo = "PeerKit-OSX-Tests";
78 | };
79 | E8AF29251A074F2D004F4E73 /* PBXContainerItemProxy */ = {
80 | isa = PBXContainerItemProxy;
81 | containerPortal = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
82 | proxyType = 2;
83 | remoteGlobalIDString = 54C96A11195063CD000CDD27;
84 | remoteInfo = "Cartography-iOS";
85 | };
86 | E8AF29271A074F2D004F4E73 /* PBXContainerItemProxy */ = {
87 | isa = PBXContainerItemProxy;
88 | containerPortal = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
89 | proxyType = 2;
90 | remoteGlobalIDString = 54C96A1C195063CD000CDD27;
91 | remoteInfo = "Cartography-iOS-Tests";
92 | };
93 | E8AF29291A074F2D004F4E73 /* PBXContainerItemProxy */ = {
94 | isa = PBXContainerItemProxy;
95 | containerPortal = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
96 | proxyType = 2;
97 | remoteGlobalIDString = 54F6A838195C20C100313D24;
98 | remoteInfo = "Cartography-Mac";
99 | };
100 | E8AF292B1A074F2D004F4E73 /* PBXContainerItemProxy */ = {
101 | isa = PBXContainerItemProxy;
102 | containerPortal = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
103 | proxyType = 2;
104 | remoteGlobalIDString = 54F6A842195C20C200313D24;
105 | remoteInfo = "Cartography-Mac-Tests";
106 | };
107 | E8AF292D1A074F4C004F4E73 /* PBXContainerItemProxy */ = {
108 | isa = PBXContainerItemProxy;
109 | containerPortal = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
110 | proxyType = 1;
111 | remoteGlobalIDString = 54C96A10195063CD000CDD27;
112 | remoteInfo = "Cartography-iOS";
113 | };
114 | E8F1890B19FCA3EA001C5080 /* PBXContainerItemProxy */ = {
115 | isa = PBXContainerItemProxy;
116 | containerPortal = E8F188ED19FCA3EA001C5080 /* Project object */;
117 | proxyType = 1;
118 | remoteGlobalIDString = E8F188F419FCA3EA001C5080;
119 | remoteInfo = CardsAgainst;
120 | };
121 | E8F805E11A0C3E8700D95CC3 /* PBXContainerItemProxy */ = {
122 | isa = PBXContainerItemProxy;
123 | containerPortal = E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */;
124 | proxyType = 2;
125 | remoteGlobalIDString = E89FD6C01A0C3CDC00C2FEF7;
126 | remoteInfo = PeerKit;
127 | };
128 | E8F805E51A0C3E9E00D95CC3 /* PBXContainerItemProxy */ = {
129 | isa = PBXContainerItemProxy;
130 | containerPortal = E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */;
131 | proxyType = 1;
132 | remoteGlobalIDString = E89FD6BF1A0C3CDC00C2FEF7;
133 | remoteInfo = PeerKit;
134 | };
135 | /* End PBXContainerItemProxy section */
136 |
137 | /* Begin PBXCopyFilesBuildPhase section */
138 | E8AF29301A074F56004F4E73 /* Copy Frameworks */ = {
139 | isa = PBXCopyFilesBuildPhase;
140 | buildActionMask = 2147483647;
141 | dstPath = "";
142 | dstSubfolderSpec = 10;
143 | files = (
144 | E8F805E81A0C3EA700D95CC3 /* PeerKit.framework in Copy Frameworks */,
145 | E8AF29311A074F61004F4E73 /* Cartography.framework in Copy Frameworks */,
146 | );
147 | name = "Copy Frameworks";
148 | runOnlyForDeploymentPostprocessing = 0;
149 | };
150 | /* End PBXCopyFilesBuildPhase section */
151 |
152 | /* Begin PBXFileReference section */
153 | D4CD0B9D1A2D3AD7003B83B4 /* UIFont+CardsAgainst.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIFont+CardsAgainst.swift"; sourceTree = ""; };
154 | E82FC18F1A0B57B600516EE5 /* UIImage+LaunchImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+LaunchImage.swift"; sourceTree = ""; };
155 | E852ACA41A0C3B5800AFA58D /* cards_pg13.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = cards_pg13.json; sourceTree = ""; };
156 | E8652A881A099AF50029BC21 /* PlayerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerCell.swift; sourceTree = ""; };
157 | E865F8751A07FF41001C5E11 /* WhiteCardFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhiteCardFlowLayout.swift; sourceTree = ""; };
158 | E865F87A1A08016B001C5E11 /* WhiteCardCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhiteCardCell.swift; sourceTree = ""; };
159 | E86DCD131A075E16009BEC5A /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; };
160 | E86DCD181A0766C6009BEC5A /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; };
161 | E86DCD1A1A0768F8009BEC5A /* CardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardManager.swift; sourceTree = ""; };
162 | E86DCD1C1A07692E009BEC5A /* Card.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = ""; };
163 | E86DCD1E1A077833009BEC5A /* TouchableLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchableLabel.swift; sourceTree = ""; };
164 | E86DCD201A078E72009BEC5A /* MPCAttributedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MPCAttributedString.swift; sourceTree = ""; };
165 | E8A6C6AF1CFF8ACB0088DB27 /* SVProgressAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressAnimatedView.h; sourceTree = ""; };
166 | E8A6C6B01CFF8ACB0088DB27 /* SVProgressAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressAnimatedView.m; sourceTree = ""; };
167 | E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Cartography.xcodeproj; sourceTree = ""; };
168 | E8AF29321A0759B3004F4E73 /* GameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = ""; };
169 | E8AF29341A0759BC004F4E73 /* GameState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameState.swift; sourceTree = ""; };
170 | E8C9BB931A0807D80084813D /* SVProgressHUD-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-Prefix.pch"; sourceTree = ""; };
171 | E8C9BB941A0807D80084813D /* SVProgressHUD.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SVProgressHUD.bundle; sourceTree = ""; };
172 | E8C9BB951A0807D80084813D /* SVProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressHUD.h; sourceTree = ""; };
173 | E8C9BB961A0807D80084813D /* SVProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressHUD.m; sourceTree = ""; };
174 | E8C9BBB21A08082F0084813D /* CardsAgainst-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CardsAgainst-Bridging-Header.h"; sourceTree = ""; };
175 | E8C9BBB31A0832AA0084813D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
176 | E8C9BBC01A08569B0084813D /* cards.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = cards.json; sourceTree = ""; };
177 | E8D6D1E31A0B665D00CDF953 /* Vote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vote.swift; sourceTree = ""; };
178 | E8D6D1E81A0B667000CDF953 /* Answer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Answer.swift; sourceTree = ""; };
179 | E8DB8BF61CA069F10002D4DD /* SVIndefiniteAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVIndefiniteAnimatedView.h; sourceTree = ""; };
180 | E8DB8BF71CA069F10002D4DD /* SVIndefiniteAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVIndefiniteAnimatedView.m; sourceTree = ""; };
181 | E8DB8BF81CA069F10002D4DD /* SVRadialGradientLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVRadialGradientLayer.h; sourceTree = ""; };
182 | E8DB8BF91CA069F10002D4DD /* SVRadialGradientLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVRadialGradientLayer.m; sourceTree = ""; };
183 | E8F188F519FCA3EA001C5080 /* CardsAgainst.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CardsAgainst.app; sourceTree = BUILT_PRODUCTS_DIR; };
184 | E8F188F919FCA3EA001C5080 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
185 | E8F188FA19FCA3EA001C5080 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
186 | E8F1890A19FCA3EA001C5080 /* CardsAgainstTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CardsAgainstTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
187 | E8F1890F19FCA3EA001C5080 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
188 | E8F1891019FCA3EA001C5080 /* CardsAgainstTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardsAgainstTests.swift; sourceTree = ""; };
189 | E8F1891A19FCA524001C5080 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; };
190 | E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = PeerKit.xcodeproj; sourceTree = ""; };
191 | E8FE921319FCA66700C4977B /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; };
192 | /* End PBXFileReference section */
193 |
194 | /* Begin PBXFrameworksBuildPhase section */
195 | E8F188F219FCA3EA001C5080 /* Frameworks */ = {
196 | isa = PBXFrameworksBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | E8F805E71A0C3EA300D95CC3 /* PeerKit.framework in Frameworks */,
200 | E8AF292F1A074F52004F4E73 /* Cartography.framework in Frameworks */,
201 | );
202 | runOnlyForDeploymentPostprocessing = 0;
203 | };
204 | E8F1890719FCA3EA001C5080 /* Frameworks */ = {
205 | isa = PBXFrameworksBuildPhase;
206 | buildActionMask = 2147483647;
207 | files = (
208 | );
209 | runOnlyForDeploymentPostprocessing = 0;
210 | };
211 | /* End PBXFrameworksBuildPhase section */
212 |
213 | /* Begin PBXGroup section */
214 | E85797001A0B5E520055EFBD /* View Controllers */ = {
215 | isa = PBXGroup;
216 | children = (
217 | E8FE921319FCA66700C4977B /* MenuViewController.swift */,
218 | E8AF29321A0759B3004F4E73 /* GameViewController.swift */,
219 | );
220 | path = "View Controllers";
221 | sourceTree = "";
222 | };
223 | E85797011A0B5E5E0055EFBD /* Assets */ = {
224 | isa = PBXGroup;
225 | children = (
226 | E8C9BBB31A0832AA0084813D /* Images.xcassets */,
227 | E8C9BBC01A08569B0084813D /* cards.json */,
228 | E852ACA41A0C3B5800AFA58D /* cards_pg13.json */,
229 | );
230 | path = Assets;
231 | sourceTree = "";
232 | };
233 | E85797021A0B5E780055EFBD /* Views */ = {
234 | isa = PBXGroup;
235 | children = (
236 | E86DCD1E1A077833009BEC5A /* TouchableLabel.swift */,
237 | E865F87A1A08016B001C5E11 /* WhiteCardCell.swift */,
238 | E8652A881A099AF50029BC21 /* PlayerCell.swift */,
239 | );
240 | path = Views;
241 | sourceTree = "";
242 | };
243 | E85797031A0B5E850055EFBD /* Models */ = {
244 | isa = PBXGroup;
245 | children = (
246 | E86DCD201A078E72009BEC5A /* MPCAttributedString.swift */,
247 | E8AF29341A0759BC004F4E73 /* GameState.swift */,
248 | E86DCD131A075E16009BEC5A /* Player.swift */,
249 | E86DCD1C1A07692E009BEC5A /* Card.swift */,
250 | E8D6D1E31A0B665D00CDF953 /* Vote.swift */,
251 | E8D6D1E81A0B667000CDF953 /* Answer.swift */,
252 | );
253 | path = Models;
254 | sourceTree = "";
255 | };
256 | E85797041A0B5E930055EFBD /* Controllers */ = {
257 | isa = PBXGroup;
258 | children = (
259 | E865F8751A07FF41001C5E11 /* WhiteCardFlowLayout.swift */,
260 | E86DCD181A0766C6009BEC5A /* ConnectionManager.swift */,
261 | E86DCD1A1A0768F8009BEC5A /* CardManager.swift */,
262 | );
263 | path = Controllers;
264 | sourceTree = "";
265 | };
266 | E85797051A0B5EA40055EFBD /* Extensions */ = {
267 | isa = PBXGroup;
268 | children = (
269 | E82FC18F1A0B57B600516EE5 /* UIImage+LaunchImage.swift */,
270 | D4CD0B9D1A2D3AD7003B83B4 /* UIFont+CardsAgainst.swift */,
271 | );
272 | path = Extensions;
273 | sourceTree = "";
274 | };
275 | E85797061A0B5EBB0055EFBD /* Helpers */ = {
276 | isa = PBXGroup;
277 | children = (
278 | E8F1891A19FCA524001C5080 /* Colors.swift */,
279 | );
280 | path = Helpers;
281 | sourceTree = "";
282 | };
283 | E8AF28E71A074F2D004F4E73 /* Vendor */ = {
284 | isa = PBXGroup;
285 | children = (
286 | E8AF28E81A074F2D004F4E73 /* Cartography */,
287 | E8F805C11A0C3E8700D95CC3 /* PeerKit */,
288 | E8C9BB7D1A0807D80084813D /* SVProgressHUD */,
289 | );
290 | path = Vendor;
291 | sourceTree = "";
292 | };
293 | E8AF28E81A074F2D004F4E73 /* Cartography */ = {
294 | isa = PBXGroup;
295 | children = (
296 | E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */,
297 | );
298 | path = Cartography;
299 | sourceTree = "";
300 | };
301 | E8AF28FB1A074F2D004F4E73 /* Products */ = {
302 | isa = PBXGroup;
303 | children = (
304 | E8AF29261A074F2D004F4E73 /* Cartography.framework */,
305 | E8AF29281A074F2D004F4E73 /* Cartography-iOS-Tests.xctest */,
306 | E8AF292A1A074F2D004F4E73 /* Cartography.framework */,
307 | E8AF292C1A074F2D004F4E73 /* Cartography-Mac-Tests.xctest */,
308 | E805BCB21CA06295006EAA76 /* Cartography.framework */,
309 | E805BCB41CA06295006EAA76 /* Cartography-tvOS-tests.xctest */,
310 | );
311 | name = Products;
312 | sourceTree = "";
313 | };
314 | E8C9BB7D1A0807D80084813D /* SVProgressHUD */ = {
315 | isa = PBXGroup;
316 | children = (
317 | E8C9BB921A0807D80084813D /* SVProgressHUD */,
318 | );
319 | path = SVProgressHUD;
320 | sourceTree = "";
321 | };
322 | E8C9BB921A0807D80084813D /* SVProgressHUD */ = {
323 | isa = PBXGroup;
324 | children = (
325 | E8DB8BF61CA069F10002D4DD /* SVIndefiniteAnimatedView.h */,
326 | E8DB8BF71CA069F10002D4DD /* SVIndefiniteAnimatedView.m */,
327 | E8A6C6AF1CFF8ACB0088DB27 /* SVProgressAnimatedView.h */,
328 | E8A6C6B01CFF8ACB0088DB27 /* SVProgressAnimatedView.m */,
329 | E8C9BB931A0807D80084813D /* SVProgressHUD-Prefix.pch */,
330 | E8C9BB941A0807D80084813D /* SVProgressHUD.bundle */,
331 | E8C9BB951A0807D80084813D /* SVProgressHUD.h */,
332 | E8C9BB961A0807D80084813D /* SVProgressHUD.m */,
333 | E8DB8BF81CA069F10002D4DD /* SVRadialGradientLayer.h */,
334 | E8DB8BF91CA069F10002D4DD /* SVRadialGradientLayer.m */,
335 | );
336 | path = SVProgressHUD;
337 | sourceTree = "";
338 | };
339 | E8F188EC19FCA3EA001C5080 = {
340 | isa = PBXGroup;
341 | children = (
342 | E8AF28E71A074F2D004F4E73 /* Vendor */,
343 | E8F188F719FCA3EA001C5080 /* CardsAgainst */,
344 | E8F1890D19FCA3EA001C5080 /* CardsAgainstTests */,
345 | E8F188F619FCA3EA001C5080 /* Products */,
346 | );
347 | sourceTree = "";
348 | };
349 | E8F188F619FCA3EA001C5080 /* Products */ = {
350 | isa = PBXGroup;
351 | children = (
352 | E8F188F519FCA3EA001C5080 /* CardsAgainst.app */,
353 | E8F1890A19FCA3EA001C5080 /* CardsAgainstTests.xctest */,
354 | );
355 | name = Products;
356 | sourceTree = "";
357 | };
358 | E8F188F719FCA3EA001C5080 /* CardsAgainst */ = {
359 | isa = PBXGroup;
360 | children = (
361 | E8F188FA19FCA3EA001C5080 /* AppDelegate.swift */,
362 | E85797011A0B5E5E0055EFBD /* Assets */,
363 | E85797041A0B5E930055EFBD /* Controllers */,
364 | E85797051A0B5EA40055EFBD /* Extensions */,
365 | E85797061A0B5EBB0055EFBD /* Helpers */,
366 | E85797031A0B5E850055EFBD /* Models */,
367 | E85797001A0B5E520055EFBD /* View Controllers */,
368 | E85797021A0B5E780055EFBD /* Views */,
369 | E8F188F819FCA3EA001C5080 /* Supporting Files */,
370 | );
371 | path = CardsAgainst;
372 | sourceTree = "";
373 | };
374 | E8F188F819FCA3EA001C5080 /* Supporting Files */ = {
375 | isa = PBXGroup;
376 | children = (
377 | E8C9BBB21A08082F0084813D /* CardsAgainst-Bridging-Header.h */,
378 | E8F188F919FCA3EA001C5080 /* Info.plist */,
379 | );
380 | path = "Supporting Files";
381 | sourceTree = "";
382 | };
383 | E8F1890D19FCA3EA001C5080 /* CardsAgainstTests */ = {
384 | isa = PBXGroup;
385 | children = (
386 | E8F1891019FCA3EA001C5080 /* CardsAgainstTests.swift */,
387 | E8F1890E19FCA3EA001C5080 /* Supporting Files */,
388 | );
389 | path = CardsAgainstTests;
390 | sourceTree = "";
391 | };
392 | E8F1890E19FCA3EA001C5080 /* Supporting Files */ = {
393 | isa = PBXGroup;
394 | children = (
395 | E8F1890F19FCA3EA001C5080 /* Info.plist */,
396 | );
397 | path = "Supporting Files";
398 | sourceTree = "";
399 | };
400 | E8F805C11A0C3E8700D95CC3 /* PeerKit */ = {
401 | isa = PBXGroup;
402 | children = (
403 | E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */,
404 | );
405 | path = PeerKit;
406 | sourceTree = "";
407 | };
408 | E8F805CC1A0C3E8700D95CC3 /* Products */ = {
409 | isa = PBXGroup;
410 | children = (
411 | E8F805E21A0C3E8700D95CC3 /* PeerKit.framework */,
412 | E805BCB91CA06295006EAA76 /* PeerKit-iOS-Tests.xctest */,
413 | E805BCBB1CA06295006EAA76 /* PeerKit.framework */,
414 | E805BCBD1CA06295006EAA76 /* PeerKit-OSX-Tests.xctest */,
415 | );
416 | name = Products;
417 | sourceTree = "";
418 | };
419 | /* End PBXGroup section */
420 |
421 | /* Begin PBXNativeTarget section */
422 | E8F188F419FCA3EA001C5080 /* CardsAgainst */ = {
423 | isa = PBXNativeTarget;
424 | buildConfigurationList = E8F1891419FCA3EA001C5080 /* Build configuration list for PBXNativeTarget "CardsAgainst" */;
425 | buildPhases = (
426 | E8F188F119FCA3EA001C5080 /* Sources */,
427 | E8F188F219FCA3EA001C5080 /* Frameworks */,
428 | E8F188F319FCA3EA001C5080 /* Resources */,
429 | E8AF29301A074F56004F4E73 /* Copy Frameworks */,
430 | 8FF8E0BB1FA10E6800A30DD3 /* Lint */,
431 | );
432 | buildRules = (
433 | );
434 | dependencies = (
435 | E8F805E61A0C3E9E00D95CC3 /* PBXTargetDependency */,
436 | E8AF292E1A074F4C004F4E73 /* PBXTargetDependency */,
437 | );
438 | name = CardsAgainst;
439 | productName = CardsAgainst;
440 | productReference = E8F188F519FCA3EA001C5080 /* CardsAgainst.app */;
441 | productType = "com.apple.product-type.application";
442 | };
443 | E8F1890919FCA3EA001C5080 /* CardsAgainstTests */ = {
444 | isa = PBXNativeTarget;
445 | buildConfigurationList = E8F1891719FCA3EA001C5080 /* Build configuration list for PBXNativeTarget "CardsAgainstTests" */;
446 | buildPhases = (
447 | E8F1890619FCA3EA001C5080 /* Sources */,
448 | E8F1890719FCA3EA001C5080 /* Frameworks */,
449 | E8F1890819FCA3EA001C5080 /* Resources */,
450 | );
451 | buildRules = (
452 | );
453 | dependencies = (
454 | E8F1890C19FCA3EA001C5080 /* PBXTargetDependency */,
455 | );
456 | name = CardsAgainstTests;
457 | productName = CardsAgainstTests;
458 | productReference = E8F1890A19FCA3EA001C5080 /* CardsAgainstTests.xctest */;
459 | productType = "com.apple.product-type.bundle.unit-test";
460 | };
461 | /* End PBXNativeTarget section */
462 |
463 | /* Begin PBXProject section */
464 | E8F188ED19FCA3EA001C5080 /* Project object */ = {
465 | isa = PBXProject;
466 | attributes = {
467 | LastSwiftMigration = 0720;
468 | LastSwiftUpdateCheck = 0720;
469 | LastUpgradeCheck = 0900;
470 | ORGANIZATIONNAME = "JP Simard";
471 | TargetAttributes = {
472 | E8F188F419FCA3EA001C5080 = {
473 | CreatedOnToolsVersion = 6.1;
474 | DevelopmentTeam = 865LEQR26C;
475 | LastSwiftMigration = 0830;
476 | ProvisioningStyle = Automatic;
477 | };
478 | E8F1890919FCA3EA001C5080 = {
479 | CreatedOnToolsVersion = 6.1;
480 | LastSwiftMigration = 0830;
481 | TestTargetID = E8F188F419FCA3EA001C5080;
482 | };
483 | };
484 | };
485 | buildConfigurationList = E8F188F019FCA3EA001C5080 /* Build configuration list for PBXProject "CardsAgainst" */;
486 | compatibilityVersion = "Xcode 3.2";
487 | developmentRegion = English;
488 | hasScannedForEncodings = 0;
489 | knownRegions = (
490 | en,
491 | Base,
492 | );
493 | mainGroup = E8F188EC19FCA3EA001C5080;
494 | productRefGroup = E8F188F619FCA3EA001C5080 /* Products */;
495 | projectDirPath = "";
496 | projectReferences = (
497 | {
498 | ProductGroup = E8AF28FB1A074F2D004F4E73 /* Products */;
499 | ProjectRef = E8AF28FA1A074F2D004F4E73 /* Cartography.xcodeproj */;
500 | },
501 | {
502 | ProductGroup = E8F805CC1A0C3E8700D95CC3 /* Products */;
503 | ProjectRef = E8F805CB1A0C3E8700D95CC3 /* PeerKit.xcodeproj */;
504 | },
505 | );
506 | projectRoot = "";
507 | targets = (
508 | E8F188F419FCA3EA001C5080 /* CardsAgainst */,
509 | E8F1890919FCA3EA001C5080 /* CardsAgainstTests */,
510 | );
511 | };
512 | /* End PBXProject section */
513 |
514 | /* Begin PBXReferenceProxy section */
515 | E805BCB21CA06295006EAA76 /* Cartography.framework */ = {
516 | isa = PBXReferenceProxy;
517 | fileType = wrapper.framework;
518 | path = Cartography.framework;
519 | remoteRef = E805BCB11CA06295006EAA76 /* PBXContainerItemProxy */;
520 | sourceTree = BUILT_PRODUCTS_DIR;
521 | };
522 | E805BCB41CA06295006EAA76 /* Cartography-tvOS-tests.xctest */ = {
523 | isa = PBXReferenceProxy;
524 | fileType = wrapper.cfbundle;
525 | path = "Cartography-tvOS-tests.xctest";
526 | remoteRef = E805BCB31CA06295006EAA76 /* PBXContainerItemProxy */;
527 | sourceTree = BUILT_PRODUCTS_DIR;
528 | };
529 | E805BCB91CA06295006EAA76 /* PeerKit-iOS-Tests.xctest */ = {
530 | isa = PBXReferenceProxy;
531 | fileType = wrapper.cfbundle;
532 | path = "PeerKit-iOS-Tests.xctest";
533 | remoteRef = E805BCB81CA06295006EAA76 /* PBXContainerItemProxy */;
534 | sourceTree = BUILT_PRODUCTS_DIR;
535 | };
536 | E805BCBB1CA06295006EAA76 /* PeerKit.framework */ = {
537 | isa = PBXReferenceProxy;
538 | fileType = wrapper.framework;
539 | path = PeerKit.framework;
540 | remoteRef = E805BCBA1CA06295006EAA76 /* PBXContainerItemProxy */;
541 | sourceTree = BUILT_PRODUCTS_DIR;
542 | };
543 | E805BCBD1CA06295006EAA76 /* PeerKit-OSX-Tests.xctest */ = {
544 | isa = PBXReferenceProxy;
545 | fileType = wrapper.cfbundle;
546 | path = "PeerKit-OSX-Tests.xctest";
547 | remoteRef = E805BCBC1CA06295006EAA76 /* PBXContainerItemProxy */;
548 | sourceTree = BUILT_PRODUCTS_DIR;
549 | };
550 | E8AF29261A074F2D004F4E73 /* Cartography.framework */ = {
551 | isa = PBXReferenceProxy;
552 | fileType = wrapper.framework;
553 | path = Cartography.framework;
554 | remoteRef = E8AF29251A074F2D004F4E73 /* PBXContainerItemProxy */;
555 | sourceTree = BUILT_PRODUCTS_DIR;
556 | };
557 | E8AF29281A074F2D004F4E73 /* Cartography-iOS-Tests.xctest */ = {
558 | isa = PBXReferenceProxy;
559 | fileType = wrapper.cfbundle;
560 | path = "Cartography-iOS-Tests.xctest";
561 | remoteRef = E8AF29271A074F2D004F4E73 /* PBXContainerItemProxy */;
562 | sourceTree = BUILT_PRODUCTS_DIR;
563 | };
564 | E8AF292A1A074F2D004F4E73 /* Cartography.framework */ = {
565 | isa = PBXReferenceProxy;
566 | fileType = wrapper.framework;
567 | path = Cartography.framework;
568 | remoteRef = E8AF29291A074F2D004F4E73 /* PBXContainerItemProxy */;
569 | sourceTree = BUILT_PRODUCTS_DIR;
570 | };
571 | E8AF292C1A074F2D004F4E73 /* Cartography-Mac-Tests.xctest */ = {
572 | isa = PBXReferenceProxy;
573 | fileType = wrapper.cfbundle;
574 | path = "Cartography-Mac-Tests.xctest";
575 | remoteRef = E8AF292B1A074F2D004F4E73 /* PBXContainerItemProxy */;
576 | sourceTree = BUILT_PRODUCTS_DIR;
577 | };
578 | E8F805E21A0C3E8700D95CC3 /* PeerKit.framework */ = {
579 | isa = PBXReferenceProxy;
580 | fileType = wrapper.framework;
581 | path = PeerKit.framework;
582 | remoteRef = E8F805E11A0C3E8700D95CC3 /* PBXContainerItemProxy */;
583 | sourceTree = BUILT_PRODUCTS_DIR;
584 | };
585 | /* End PBXReferenceProxy section */
586 |
587 | /* Begin PBXResourcesBuildPhase section */
588 | E8F188F319FCA3EA001C5080 /* Resources */ = {
589 | isa = PBXResourcesBuildPhase;
590 | buildActionMask = 2147483647;
591 | files = (
592 | E8C9BBA61A0807D80084813D /* SVProgressHUD.bundle in Resources */,
593 | E8C9BBC11A08569B0084813D /* cards.json in Resources */,
594 | E852ACA51A0C3B5800AFA58D /* cards_pg13.json in Resources */,
595 | E8C9BBB41A0832AA0084813D /* Images.xcassets in Resources */,
596 | );
597 | runOnlyForDeploymentPostprocessing = 0;
598 | };
599 | E8F1890819FCA3EA001C5080 /* Resources */ = {
600 | isa = PBXResourcesBuildPhase;
601 | buildActionMask = 2147483647;
602 | files = (
603 | );
604 | runOnlyForDeploymentPostprocessing = 0;
605 | };
606 | /* End PBXResourcesBuildPhase section */
607 |
608 | /* Begin PBXShellScriptBuildPhase section */
609 | 8FF8E0BB1FA10E6800A30DD3 /* Lint */ = {
610 | isa = PBXShellScriptBuildPhase;
611 | buildActionMask = 2147483647;
612 | files = (
613 | );
614 | inputPaths = (
615 | );
616 | name = Lint;
617 | outputPaths = (
618 | );
619 | runOnlyForDeploymentPostprocessing = 0;
620 | shellPath = /bin/sh;
621 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
622 | };
623 | /* End PBXShellScriptBuildPhase section */
624 |
625 | /* Begin PBXSourcesBuildPhase section */
626 | E8F188F119FCA3EA001C5080 /* Sources */ = {
627 | isa = PBXSourcesBuildPhase;
628 | buildActionMask = 2147483647;
629 | files = (
630 | E86DCD1D1A07692E009BEC5A /* Card.swift in Sources */,
631 | E8AF29331A0759B3004F4E73 /* GameViewController.swift in Sources */,
632 | E86DCD1F1A077833009BEC5A /* TouchableLabel.swift in Sources */,
633 | E8D6D1E91A0B667000CDF953 /* Answer.swift in Sources */,
634 | E86DCD1B1A0768F8009BEC5A /* CardManager.swift in Sources */,
635 | E8F188FB19FCA3EA001C5080 /* AppDelegate.swift in Sources */,
636 | E8C9BBA71A0807D80084813D /* SVProgressHUD.m in Sources */,
637 | E8DB8BFB1CA069F20002D4DD /* SVRadialGradientLayer.m in Sources */,
638 | D4CD0B9E1A2D3AD7003B83B4 /* UIFont+CardsAgainst.swift in Sources */,
639 | E865F8761A07FF41001C5E11 /* WhiteCardFlowLayout.swift in Sources */,
640 | E8652A891A099AF50029BC21 /* PlayerCell.swift in Sources */,
641 | E8AF29351A0759BC004F4E73 /* GameState.swift in Sources */,
642 | E86DCD191A0766C6009BEC5A /* ConnectionManager.swift in Sources */,
643 | E8A6C6B11CFF8ACB0088DB27 /* SVProgressAnimatedView.m in Sources */,
644 | E86DCD211A078E72009BEC5A /* MPCAttributedString.swift in Sources */,
645 | E8D6D1E41A0B665D00CDF953 /* Vote.swift in Sources */,
646 | E8F1891B19FCA524001C5080 /* Colors.swift in Sources */,
647 | E8DB8BFA1CA069F10002D4DD /* SVIndefiniteAnimatedView.m in Sources */,
648 | E865F87B1A08016B001C5E11 /* WhiteCardCell.swift in Sources */,
649 | E86DCD141A075E16009BEC5A /* Player.swift in Sources */,
650 | E82FC1901A0B57B600516EE5 /* UIImage+LaunchImage.swift in Sources */,
651 | E8FE921419FCA66700C4977B /* MenuViewController.swift in Sources */,
652 | );
653 | runOnlyForDeploymentPostprocessing = 0;
654 | };
655 | E8F1890619FCA3EA001C5080 /* Sources */ = {
656 | isa = PBXSourcesBuildPhase;
657 | buildActionMask = 2147483647;
658 | files = (
659 | E8F1891119FCA3EA001C5080 /* CardsAgainstTests.swift in Sources */,
660 | );
661 | runOnlyForDeploymentPostprocessing = 0;
662 | };
663 | /* End PBXSourcesBuildPhase section */
664 |
665 | /* Begin PBXTargetDependency section */
666 | E8AF292E1A074F4C004F4E73 /* PBXTargetDependency */ = {
667 | isa = PBXTargetDependency;
668 | name = "Cartography-iOS";
669 | targetProxy = E8AF292D1A074F4C004F4E73 /* PBXContainerItemProxy */;
670 | };
671 | E8F1890C19FCA3EA001C5080 /* PBXTargetDependency */ = {
672 | isa = PBXTargetDependency;
673 | target = E8F188F419FCA3EA001C5080 /* CardsAgainst */;
674 | targetProxy = E8F1890B19FCA3EA001C5080 /* PBXContainerItemProxy */;
675 | };
676 | E8F805E61A0C3E9E00D95CC3 /* PBXTargetDependency */ = {
677 | isa = PBXTargetDependency;
678 | name = PeerKit;
679 | targetProxy = E8F805E51A0C3E9E00D95CC3 /* PBXContainerItemProxy */;
680 | };
681 | /* End PBXTargetDependency section */
682 |
683 | /* Begin XCBuildConfiguration section */
684 | E8F1891219FCA3EA001C5080 /* Debug */ = {
685 | isa = XCBuildConfiguration;
686 | buildSettings = {
687 | ALWAYS_SEARCH_USER_PATHS = NO;
688 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
689 | CLANG_CXX_LIBRARY = "libc++";
690 | CLANG_ENABLE_MODULES = YES;
691 | CLANG_ENABLE_OBJC_ARC = YES;
692 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
693 | CLANG_WARN_BOOL_CONVERSION = YES;
694 | CLANG_WARN_COMMA = YES;
695 | CLANG_WARN_CONSTANT_CONVERSION = YES;
696 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
697 | CLANG_WARN_EMPTY_BODY = YES;
698 | CLANG_WARN_ENUM_CONVERSION = YES;
699 | CLANG_WARN_INFINITE_RECURSION = YES;
700 | CLANG_WARN_INT_CONVERSION = YES;
701 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
702 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
703 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
704 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
705 | CLANG_WARN_STRICT_PROTOTYPES = YES;
706 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
707 | CLANG_WARN_UNREACHABLE_CODE = YES;
708 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
709 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
710 | COPY_PHASE_STRIP = NO;
711 | ENABLE_STRICT_OBJC_MSGSEND = YES;
712 | ENABLE_TESTABILITY = YES;
713 | GCC_C_LANGUAGE_STANDARD = gnu99;
714 | GCC_DYNAMIC_NO_PIC = NO;
715 | GCC_NO_COMMON_BLOCKS = YES;
716 | GCC_OPTIMIZATION_LEVEL = 0;
717 | GCC_PREPROCESSOR_DEFINITIONS = (
718 | "DEBUG=1",
719 | "$(inherited)",
720 | );
721 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
722 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
723 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
724 | GCC_WARN_UNDECLARED_SELECTOR = YES;
725 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
726 | GCC_WARN_UNUSED_FUNCTION = YES;
727 | GCC_WARN_UNUSED_VARIABLE = YES;
728 | IPHONEOS_DEPLOYMENT_TARGET = 8.1;
729 | MTL_ENABLE_DEBUG_INFO = YES;
730 | ONLY_ACTIVE_ARCH = YES;
731 | SDKROOT = iphoneos;
732 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
733 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
734 | SWIFT_VERSION = 4.0;
735 | };
736 | name = Debug;
737 | };
738 | E8F1891319FCA3EA001C5080 /* Release */ = {
739 | isa = XCBuildConfiguration;
740 | buildSettings = {
741 | ALWAYS_SEARCH_USER_PATHS = NO;
742 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
743 | CLANG_CXX_LIBRARY = "libc++";
744 | CLANG_ENABLE_MODULES = YES;
745 | CLANG_ENABLE_OBJC_ARC = YES;
746 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
747 | CLANG_WARN_BOOL_CONVERSION = YES;
748 | CLANG_WARN_COMMA = YES;
749 | CLANG_WARN_CONSTANT_CONVERSION = YES;
750 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
751 | CLANG_WARN_EMPTY_BODY = YES;
752 | CLANG_WARN_ENUM_CONVERSION = YES;
753 | CLANG_WARN_INFINITE_RECURSION = YES;
754 | CLANG_WARN_INT_CONVERSION = YES;
755 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
756 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
757 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
758 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
759 | CLANG_WARN_STRICT_PROTOTYPES = YES;
760 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
761 | CLANG_WARN_UNREACHABLE_CODE = YES;
762 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
763 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
764 | COPY_PHASE_STRIP = YES;
765 | ENABLE_NS_ASSERTIONS = NO;
766 | ENABLE_STRICT_OBJC_MSGSEND = YES;
767 | GCC_C_LANGUAGE_STANDARD = gnu99;
768 | GCC_NO_COMMON_BLOCKS = YES;
769 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
770 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
771 | GCC_WARN_UNDECLARED_SELECTOR = YES;
772 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
773 | GCC_WARN_UNUSED_FUNCTION = YES;
774 | GCC_WARN_UNUSED_VARIABLE = YES;
775 | IPHONEOS_DEPLOYMENT_TARGET = 8.1;
776 | MTL_ENABLE_DEBUG_INFO = NO;
777 | SDKROOT = iphoneos;
778 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
779 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
780 | SWIFT_VERSION = 4.0;
781 | VALIDATE_PRODUCT = YES;
782 | };
783 | name = Release;
784 | };
785 | E8F1891519FCA3EA001C5080 /* Debug */ = {
786 | isa = XCBuildConfiguration;
787 | buildSettings = {
788 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
789 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
790 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
791 | DEVELOPMENT_TEAM = 865LEQR26C;
792 | INFOPLIST_FILE = "CardsAgainst/Supporting Files/Info.plist";
793 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
794 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
795 | PRODUCT_BUNDLE_IDENTIFIER = com.jpsim.CardsAgainst2;
796 | PRODUCT_NAME = "$(TARGET_NAME)";
797 | PROVISIONING_PROFILE_SPECIFIER = "";
798 | SWIFT_OBJC_BRIDGING_HEADER = "CardsAgainst/Supporting Files/CardsAgainst-Bridging-Header.h";
799 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
800 | SWIFT_VERSION = 4.0;
801 | TARGETED_DEVICE_FAMILY = "1,2";
802 | };
803 | name = Debug;
804 | };
805 | E8F1891619FCA3EA001C5080 /* Release */ = {
806 | isa = XCBuildConfiguration;
807 | buildSettings = {
808 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
809 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
810 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
811 | DEVELOPMENT_TEAM = 865LEQR26C;
812 | INFOPLIST_FILE = "CardsAgainst/Supporting Files/Info.plist";
813 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
814 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
815 | PRODUCT_BUNDLE_IDENTIFIER = com.jpsim.CardsAgainst2;
816 | PRODUCT_NAME = "$(TARGET_NAME)";
817 | PROVISIONING_PROFILE_SPECIFIER = "";
818 | SWIFT_OBJC_BRIDGING_HEADER = "CardsAgainst/Supporting Files/CardsAgainst-Bridging-Header.h";
819 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
820 | SWIFT_VERSION = 4.0;
821 | TARGETED_DEVICE_FAMILY = "1,2";
822 | };
823 | name = Release;
824 | };
825 | E8F1891819FCA3EA001C5080 /* Debug */ = {
826 | isa = XCBuildConfiguration;
827 | buildSettings = {
828 | BUNDLE_LOADER = "$(TEST_HOST)";
829 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
830 | GCC_PREPROCESSOR_DEFINITIONS = (
831 | "DEBUG=1",
832 | "$(inherited)",
833 | );
834 | INFOPLIST_FILE = "CardsAgainstTests/Supporting Files/Info.plist";
835 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
836 | PRODUCT_BUNDLE_IDENTIFIER = "com.jpsim.$(PRODUCT_NAME:rfc1034identifier)";
837 | PRODUCT_NAME = "$(TARGET_NAME)";
838 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
839 | SWIFT_VERSION = 4.0;
840 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CardsAgainst.app/CardsAgainst";
841 | };
842 | name = Debug;
843 | };
844 | E8F1891919FCA3EA001C5080 /* Release */ = {
845 | isa = XCBuildConfiguration;
846 | buildSettings = {
847 | BUNDLE_LOADER = "$(TEST_HOST)";
848 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
849 | INFOPLIST_FILE = "CardsAgainstTests/Supporting Files/Info.plist";
850 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
851 | PRODUCT_BUNDLE_IDENTIFIER = "com.jpsim.$(PRODUCT_NAME:rfc1034identifier)";
852 | PRODUCT_NAME = "$(TARGET_NAME)";
853 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
854 | SWIFT_VERSION = 4.0;
855 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CardsAgainst.app/CardsAgainst";
856 | };
857 | name = Release;
858 | };
859 | /* End XCBuildConfiguration section */
860 |
861 | /* Begin XCConfigurationList section */
862 | E8F188F019FCA3EA001C5080 /* Build configuration list for PBXProject "CardsAgainst" */ = {
863 | isa = XCConfigurationList;
864 | buildConfigurations = (
865 | E8F1891219FCA3EA001C5080 /* Debug */,
866 | E8F1891319FCA3EA001C5080 /* Release */,
867 | );
868 | defaultConfigurationIsVisible = 0;
869 | defaultConfigurationName = Release;
870 | };
871 | E8F1891419FCA3EA001C5080 /* Build configuration list for PBXNativeTarget "CardsAgainst" */ = {
872 | isa = XCConfigurationList;
873 | buildConfigurations = (
874 | E8F1891519FCA3EA001C5080 /* Debug */,
875 | E8F1891619FCA3EA001C5080 /* Release */,
876 | );
877 | defaultConfigurationIsVisible = 0;
878 | defaultConfigurationName = Release;
879 | };
880 | E8F1891719FCA3EA001C5080 /* Build configuration list for PBXNativeTarget "CardsAgainstTests" */ = {
881 | isa = XCConfigurationList;
882 | buildConfigurations = (
883 | E8F1891819FCA3EA001C5080 /* Debug */,
884 | E8F1891919FCA3EA001C5080 /* Release */,
885 | );
886 | defaultConfigurationIsVisible = 0;
887 | defaultConfigurationName = Release;
888 | };
889 | /* End XCConfigurationList section */
890 | };
891 | rootObject = E8F188ED19FCA3EA001C5080 /* Project object */;
892 | }
893 |
--------------------------------------------------------------------------------