Skip to content

Commit

Permalink
Added Match Game Content Notification Extension (#28)
Browse files Browse the repository at this point in the history
* renamed to push story content extension

* added match game

* removed matchgameview, series of matchcardviews

* game over

* added play again button

* cleaned up

* bigger font

* added score apparatus

* match game

* cleaned up

* supporting portrait/landscape mode

* folders

* updated ui

* 374

* passing data from app group to app

* using number of cards

* using xib, not storyboard

* removed files

* cleaned up

* privatized

* added comments

* userDefaults

* cleaned up

* changed filename

* no more spaces

* set constraint to 999 to resolve layout conflicts on collapse
  • Loading branch information
Justin Malandruccolo authored Feb 23, 2021
1 parent 08f4f66 commit 7a9ed52
Show file tree
Hide file tree
Showing 138 changed files with 1,055 additions and 78 deletions.
328 changes: 265 additions & 63 deletions Braze Demo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7F8CB2E525C888000001F270"
BuildableName = "Braze Demo Content Extension.appex"
BlueprintName = "Braze Demo Content Extension"
BuildableName = "Braze Demo Push Story Content Extension.appex"
BlueprintName = "Braze Demo Push Story Content Extension"
ReferencedContainer = "container:Braze Demo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
Expand Down

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions Braze-Demo-Match-Game-Content-Extension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Braze Demo Match Game Content Extension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<string>match_game</string>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>0.65000000000000002</real>
<key>UNNotificationExtensionUserInteractionEnabled</key>
<true/>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.content-extension</string>
</dict>
</dict>
</plist>
17 changes: 17 additions & 0 deletions Braze-Demo-Match-Game-Content-Extension/Model/AttemptCounter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
struct AttemptCounter {
private(set) var attemptCount = 0
private var bestScore = Int.max

mutating func increment() {
attemptCount += 1
}

mutating func getHighScore() -> Int {
bestScore = min(bestScore, attemptCount)
return bestScore
}

mutating func reset() {
attemptCount = 0
}
}
24 changes: 24 additions & 0 deletions Braze-Demo-Match-Game-Content-Extension/Model/MatchCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import UIKit

enum CardType: String, CaseIterable {
case angry
case heartEyes = "heart-eyes"
case tearsOfJoy = "tears-of-joy"
case rainbow
case pensive
case stunned
}

struct MatchCard {
private(set) var type: CardType

var selectedImage: UIImage? {
return UIImage(named: type.rawValue)
}
}

extension MatchCard: Equatable {
static func ==(lhs: MatchCard, rhs: MatchCard) -> Bool {
return lhs.type == rhs.type
}
}
99 changes: 99 additions & 0 deletions Braze-Demo-Match-Game-Content-Extension/Model/MatchGame.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
protocol MatchGameDelegate: class {
func cardsDidLoad(_ cards: [MatchCard])
func cardsDidMatch(_ indicies: [Int], currentScore: Int)
func cardsDidNotMatch(_ indicies: [Int], currentScore: Int)
}

struct MatchGame {

// MARK: - Variables
private var cards = [MatchCard]()
private var numberOfCards = 0
private var matchedCardsCount = 0
private var flippedIndicies = [Int]() // represents the 2 flipped cards
private var attemptCounter = AttemptCounter()
private weak var delegate: MatchGameDelegate?

var noMatchesLeft: Bool {
return matchedCardsCount == cards.count
}
}

// MARK: - Public Methods
extension MatchGame {
mutating func configureGame(numberOfCards: Int, delegate: MatchGameDelegate? = nil) {
self.numberOfCards = numberOfCards
self.delegate = delegate

loadCards(numberOfCards: numberOfCards)
}

/// The top-left card on the board has an index of 0 and increments from left to right. The bottom-right card has an index of n-1, n representing the number of cards.
mutating func cardFlipped(at index: Int) {
flippedIndicies.append(index)

if flippedIndicies.count == 2 {
checkForMatch(with: flippedIndicies)
flippedIndicies.removeAll()
}
}

mutating func getHighScore() -> Int {
return attemptCounter.getHighScore()
}

mutating func playAgain() {
matchedCardsCount = 0
attemptCounter.reset()

loadCards(numberOfCards: numberOfCards)
}
}

// MARK: - Private Methods
private extension MatchGame {
/// Creates the deck of cards to be synced up with the card views. The loop creates a pair of `MatchCard` objects with the same `CardType` value.
/// - parameter numberOfCards: The number of cards the game needs to create. Number of pairs is `numberOfCards / 2`.
mutating func loadCards(numberOfCards: Int) {
cards = []

for index in 0..<numberOfCards / 2 {
let type = CardType.allCases[index % CardType.allCases.count]
let card = MatchCard(type: type)
cards.append(card)
cards.append(card)
}
randomizeCards()

delegate?.cardsDidLoad(cards)
}

// SOURCE: - https://www.dartmouth.edu/~chance/teaching_aids/Mann.pdf
mutating func randomizeCards() {
for _ in 0..<7 {
cards.shuffle()
}
}

mutating func checkForMatch(with flippedIndicies: [Int]) {
attemptCounter.increment()

if isMatched(flippedIndicies: flippedIndicies) {
matchedCardsCount += 2
delegate?.cardsDidMatch(flippedIndicies, currentScore: attemptCounter.attemptCount)
} else {
delegate?.cardsDidNotMatch(flippedIndicies, currentScore: attemptCounter.attemptCount)
}
}

/// The indicies are used to fetch the `MatchCard` from the `cards` array and adds the card to the `cardsToMatch` array. If all cards in the array have an equal `CardType`, the return value will be `true`.
/// - parameter flippedIndicies: Rrepresent an array of each index flipped cards on the board.
func isMatched(flippedIndicies: [Int]) -> Bool {
var cardsToMatch = [MatchCard]()
for index in flippedIndicies {
cardsToMatch.append(cards[index])
}

return cardsToMatch.allSatisfy { $0.type == cardsToMatch.first?.type}
}
}
42 changes: 42 additions & 0 deletions Braze-Demo-Match-Game-Content-Extension/View/MatchCardView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import UIKit

protocol MatchCardViewDelegate: class {
func cardTapped(at index: Int)
}

class MatchCardView: UIView {

// MARK: - Outlets
@IBOutlet private weak var button: UIButton!

// MARK: - Actions
@IBAction func cardTapped(_ sender: UIButton) {
delegate?.cardTapped(at: sender.tag)

flipCard()
}

// MARK: - Variables
private weak var delegate: MatchCardViewDelegate?

override func awakeFromNib() {
super.awakeFromNib()

layer.cornerRadius = 5
layer.masksToBounds = true
}

func configureView(selectedImage: UIImage?, tag: Int, delegate: MatchCardViewDelegate? = nil) {
self.delegate = delegate

button.setImage(selectedImage, for: .selected)
button.tag = tag
}

func flipCard() {
button.isSelected.toggle()
isUserInteractionEnabled = !button.isSelected

UIView.transition(with: self, duration: 0.5, options: .transitionFlipFromRight, animations: nil, completion: nil)
}
}
49 changes: 49 additions & 0 deletions Braze-Demo-Match-Game-Content-Extension/View/MatchCardView.xib
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="PLR-VX-SOo" customClass="MatchCardView" customModule="Braze_Demo_Match_Game_Content_Extension" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="158" height="221"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NaN-Ty-6DP">
<rect key="frame" x="0.0" y="0.0" width="158" height="221"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" image="card-background"/>
<connections>
<action selector="cardTapped:" destination="PLR-VX-SOo" eventType="touchUpInside" id="eCt-qw-jk2"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="0hT-2F-PfG"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="NaN-Ty-6DP" firstAttribute="top" secondItem="PLR-VX-SOo" secondAttribute="top" id="4BD-of-5UY"/>
<constraint firstItem="NaN-Ty-6DP" firstAttribute="leading" secondItem="PLR-VX-SOo" secondAttribute="leading" id="ZXR-BU-FVO"/>
<constraint firstAttribute="trailing" secondItem="NaN-Ty-6DP" secondAttribute="trailing" id="hkL-Ep-rCY"/>
<constraint firstAttribute="bottom" secondItem="NaN-Ty-6DP" secondAttribute="bottom" id="kgv-7X-7CM"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="button" destination="NaN-Ty-6DP" id="fqN-OT-E7G"/>
</connections>
<point key="canvasLocation" x="139" y="50"/>
</view>
</objects>
<resources>
<image name="card-background" width="737" height="1041"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
Loading

0 comments on commit 7a9ed52

Please sign in to comment.