├── .gitignore ├── .gitmodules ├── .swiftlint.yml ├── CardsAgainst.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── CardsAgainst.xcscmblueprint ├── CardsAgainst ├── AppDelegate.swift ├── Assets │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-76.png │ │ │ ├── Icon-76@2x.png │ │ │ ├── Icon-83.5@2x.png │ │ │ ├── Icon-Small-40.png │ │ │ ├── Icon-Small-40@2x.png │ │ │ ├── Icon-Small-40@3x.png │ │ │ ├── Icon-Small.png │ │ │ ├── Icon-Small@2x.png │ │ │ └── Icon-Small@3x.png │ │ └── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── LaunchImage-4Inch.png │ │ │ ├── LaunchImage-iPadRetina.png │ │ │ ├── LaunchImage-iPhone6.png │ │ │ ├── LaunchImage-iPhone6Plus.png │ │ │ └── LaunchImage.png │ ├── cards.json │ └── cards_pg13.json ├── Controllers │ ├── CardManager.swift │ ├── ConnectionManager.swift │ └── WhiteCardFlowLayout.swift ├── Extensions │ ├── UIFont+CardsAgainst.swift │ └── UIImage+LaunchImage.swift ├── Helpers │ └── Colors.swift ├── Models │ ├── Answer.swift │ ├── Card.swift │ ├── GameState.swift │ ├── MPCAttributedString.swift │ ├── Player.swift │ └── Vote.swift ├── Supporting Files │ ├── CardsAgainst-Bridging-Header.h │ └── Info.plist ├── View Controllers │ ├── GameViewController.swift │ └── MenuViewController.swift └── Views │ ├── PlayerCell.swift │ ├── TouchableLabel.swift │ └── WhiteCardCell.swift ├── CardsAgainstTests ├── CardsAgainstTests.swift └── Supporting Files │ └── Info.plist ├── Design ├── CardsAgainst-Icon.sketch └── CardsAgainst-Splash.sketch ├── LICENSE └── README.md /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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.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 | -------------------------------------------------------------------------------- /CardsAgainst.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/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/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/Assets/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/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/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/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/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/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/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/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/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /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/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-4Inch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-4Inch.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPadRetina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPadRetina.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6Plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage-iPhone6Plus.png -------------------------------------------------------------------------------- /CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/CardsAgainst/Assets/Images.xcassets/LaunchImage.launchimage/LaunchImage.png -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Design/CardsAgainst-Icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/Design/CardsAgainst-Icon.sketch -------------------------------------------------------------------------------- /Design/CardsAgainst-Splash.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpsim/CardsAgainst/e28334f2f2a53b59997579a7a52cd1166e29eb6e/Design/CardsAgainst-Splash.sketch -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ![](https://www.objc.io/images/issue-18/dedicated-9942fc7b.gif) 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 | --------------------------------------------------------------------------------