Skip to content

Commit

Permalink
Basic support for extra time and penalties
Browse files Browse the repository at this point in the history
  • Loading branch information
dreymonde committed Jul 20, 2017
1 parent 47a3a80 commit 8ee0b10
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 86 deletions.
38 changes: 38 additions & 0 deletions Kit Tests/EventTests.swift
@@ -0,0 +1,38 @@
//
// UploadTests.swift
// TheGreatGame
//
// Created by Oleg Dreyman on 13.06.17.
// Copyright 漏 2017 The Great Game. All rights reserved.
//

import XCTest
import Shallows
import Alba
@testable import TheGreatKit

class EventTests: XCTestCase {

override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}

func testAdditionalEventFE() {
let event = Match.Event(kind: .halftime_start, text: "FEtest", realMinute: 115, matchMinute: 90)
XCTAssertEqual(event.kind, .end_and_extra)
XCTAssertEqual(event.text, "test")
}

func testAdditionalEventET() {
let event = Match.Event(kind: .halftime_end, text: "ETtest", realMinute: 115, matchMinute: 90)
XCTAssertEqual(event.kind, .extra_start)
XCTAssertEqual(event.text, "test")
}

}
2 changes: 1 addition & 1 deletion Kit/Complication/ComplicationTemplate.swift
Expand Up @@ -71,7 +71,7 @@ public final class ComplicationTemplate {

private func makeMatch(teams: (Match.Team, Match.Team), score: (Int, Int)?) -> Match.Full {
let scorescore = score.map({ Match.Score.init(home: $0.0, away: $0.1) })
return Match.Full(id: Match.ID.init(rawValue: -1)!, home: teams.0, away: teams.1, date: Date(), endDate: Date().addingTimeInterval(60 * 120), location: "Netherlands", stageTitle: "Group Stage", score: scorescore, events: [])
return Match.Full(id: Match.ID.init(rawValue: -1)!, home: teams.0, away: teams.1, date: Date(), endDate: Date().addingTimeInterval(60 * 120), location: "Netherlands", stageTitle: "Group Stage", score: scorescore, penalties: nil, events: [])
}

}
148 changes: 132 additions & 16 deletions Kit/Models/Match/Match.swift
Expand Up @@ -16,6 +16,7 @@ public protocol MatchProtocol {
var date: Date { get }
var endDate: Date { get }
var score: Match.Score? { get }
var penalties: Match.Score? { get }

}

Expand Down Expand Up @@ -58,15 +59,19 @@ extension MatchProtocol {
return progressInterval / completeInterval
}

public func scoreString() -> String {
return score?.demo_string ?? "-:-"
public func scoreOrPenaltyString() -> String {
return penalties?.demo_string ?? score?.demo_string ?? "-:-"
// if let score = score {
// return score.demo_string
// } else {
// return formatter.string(from: date)
// }
}

public func onlyMainTimeScoreString() -> String {
return score?.demo_string ?? "-:-"
}

public func scoreOrTimeString() -> String {
if let score = score {
return score.demo_string
Expand Down Expand Up @@ -125,6 +130,7 @@ public enum Match {
public let home: Int
public let away: Int

@available(*, deprecated)
public var demo_string: String {
return "\(home):\(away)"
}
Expand All @@ -135,6 +141,8 @@ public enum Match {

public enum Kind : String {
case start, goal_home, goal_away, end, info, halftime_start, halftime_end
case end_and_extra, extra_start, penalties
case pen_goal_home, pen_goal_away, pen_miss_home, pen_miss_away
}

public let kind: Kind
Expand All @@ -143,12 +151,63 @@ public enum Match {
public let matchMinute: Int

public init(kind: Kind, text: String, realMinute: Int, matchMinute: Int) {
self.kind = kind
self.text = text
if let extra = Event.analyze(text, kind: kind) {
self.kind = extra.0
self.text = extra.1
} else {
self.kind = kind
self.text = text
}
self.realMinute = realMinute
self.matchMinute = matchMinute
}

internal enum Additional {
static let endAndExtra = "FE"
static let extraTimeStart = "ET"
static let penalties = "PS"
static let penGoalHome = "PH"
static let penGoalAway = "PA"
static let penHomeMiss = "MH"
static let penAwayMiss = "MA"
}

internal static func analyze(_ text: String, kind: Kind) -> (Kind, String)? {
if text.hasPrefix(Additional.endAndExtra), kind == .halftime_start {
let cut = cutAdditional(from: text)
return (.end_and_extra, cut)
}
if text.hasPrefix(Additional.extraTimeStart), kind == .halftime_end {
let cut = cutAdditional(from: text)
return (.extra_start, cut)
}
if text.hasPrefix(Additional.penalties), kind == .halftime_start {
let cut = cutAdditional(from: text)
return (.penalties, cut)
}
if text.hasPrefix(Additional.penGoalHome), kind == .info {
let cut = cutAdditional(from: text)
return (.pen_goal_home, cut)
}
if text.hasPrefix(Additional.penGoalAway), kind == .info {
let cut = cutAdditional(from: text)
return (.pen_goal_away, cut)
}
if text.hasPrefix(Additional.penHomeMiss), kind == .info {
let cut = cutAdditional(from: text)
return (.pen_miss_home, cut)
}
if text.hasPrefix(Additional.penAwayMiss), kind == .info {
let cut = cutAdditional(from: text)
return (.pen_miss_away, cut)
}
return nil
}

internal static func cutAdditional(from text: String) -> String {
return text.substring(from: text.index(text.startIndex, offsetBy: 2))
}

}

public struct Team {
Expand All @@ -167,6 +226,7 @@ public enum Match {
public let endDate: Date
public let location: String
public let score: Score?
public let penalties: Match.Score?

}

Expand All @@ -180,6 +240,7 @@ public enum Match {
public let location: String
public let stageTitle: String
public var score: Score?
public var penalties: Match.Score?
public var events: [Event]

public static func reevaluateScore(from events: [Event]) -> Score? {
Expand All @@ -191,6 +252,15 @@ public enum Match {
return Score(home: goalsHome, away: goalsAway)
}

public static func reevaluatePenalties(from events: [Event]) -> Score? {
guard events.contains(eventOfKind: .penalties) else {
return nil
}
let pensHome = events.filter({ $0.kind == .pen_goal_home }).count
let pensAway = events.filter({ $0.kind == .pen_goal_away }).count
return Score(home: pensHome, away: pensAway)
}

public func withUnpredictableScore() -> Full {
var copy = self
if copy.score != nil {
Expand All @@ -213,6 +283,7 @@ public enum Match {
location: location,
stageTitle: stageTitle,
score: Full.reevaluateScore(from: eventsBeforeMinute),
penalties: Full.reevaluatePenalties(from: eventsBeforeMinute),
events: eventsBeforeMinute)
}

Expand All @@ -226,28 +297,52 @@ public enum Match {
return snapshot(beforeRealMinute: -1)
}

public var isFullTime: Bool {
return isEnded || events.contains(eventOfKind: .end_and_extra)
}

public var isEnded: Bool {
return events.contains(where: { $0.kind == .end })
return events.contains(eventOfKind: .end)
}

public var isStarted: Bool {
return events.contains(where: { $0.kind == .start })
return events.contains(eventOfKind: .start)
}

public var isInHalfTime: Bool {
return isFirstHalfEnded && !isSecondHalf
return (isFirstHalfEnded && !isMainHalfTimeEnded) || (isExtraTimeAppointed && !isExtraTime && !isEnded && !isPenaltiesAppointed)
}

public var isSecondHalf: Bool {
return events.contains(where: { $0.kind == .halftime_end })
return isMainHalfTimeEnded && !isFullTime
}

public var isMainHalfTimeEnded: Bool {
return events.contains(eventOfKind: .halftime_end)
}

public var isFirstHalf: Bool {
return !isFirstHalfEnded
}

public var isFirstHalfEnded: Bool {
return events.contains(where: { $0.kind == .halftime_start })
return events.contains(eventOfKind: .halftime_start)
}

public var isExtraTimeAppointed: Bool {
return events.contains(eventOfKind: .end_and_extra)
}

public var isExtraTime: Bool {
return events.contains(eventOfKind: .extra_start) && !isEnded && !isPenaltiesAppointed
}

public var isExtraTimeEnded: Bool {
return isPenaltiesAppointed || isEnded
}

public var isPenaltiesAppointed: Bool {
return events.contains(eventOfKind: .penalties)
}

public func minuteOrStateString() -> String {
Expand All @@ -263,7 +358,11 @@ public enum Match {
if let lastEvent = events.last {
let dateOfLastEvent = self.date(afterRealMinutesFromStart: lastEvent.realMinute)
let intervalAfter = Int(Date().timeIntervalSince(dateOfLastEvent) / 60)
return "\(lastEvent.matchMinute + intervalAfter)'"
var string = "\(lastEvent.matchMinute + intervalAfter)'"
if isExtraTimeAppointed {
string.append(" ET")
}
return string
} else {
return " "
}
Expand All @@ -273,6 +372,14 @@ public enum Match {

}

extension Sequence where Iterator.Element == Match.Event {

func contains(eventOfKind kind: Match.Event.Kind) -> Bool {
return contains(where: { $0.kind == kind })
}

}

extension Match.Team : Mappable {

public enum MappingKeys : String, IndexPathElement {
Expand Down Expand Up @@ -320,10 +427,11 @@ extension Match.Event : Mappable {
}

public init<Source : InMap>(mapper: InMapper<Source, MappingKeys>) throws {
self.kind = try mapper.map(from: .type)
self.text = try mapper.map(from: .text)
self.realMinute = try mapper.map(from: .real_minute)
self.matchMinute = try mapper.map(from: .match_minute)
let kind = try mapper.map(from: .type) as Kind
let text = try mapper.map(from: .text) as String
let realMinute = try mapper.map(from: .real_minute) as Int
let matchMinute = try mapper.map(from: .match_minute) as Int
self.init(kind: kind, text: text, realMinute: realMinute, matchMinute: matchMinute)
}

public func outMap<Destination : OutMap>(mapper: inout OutMapper<Destination, MappingKeys>) throws {
Expand All @@ -338,7 +446,7 @@ extension Match.Event : Mappable {
extension Match.Compact : Mappable {

public enum MappingKeys : String, IndexPathElement {
case id, home, away, date, endDate, location, score
case id, home, away, date, endDate, location, score, penalties
}

public init<Source : InMap>(mapper: InMapper<Source, MappingKeys>) throws {
Expand All @@ -349,6 +457,7 @@ extension Match.Compact : Mappable {
self.endDate = try mapper.map(from: .endDate)
self.location = try mapper.map(from: .location)
self.score = try? mapper.map(from: .score)
self.penalties = try? mapper.map(from: .penalties)
}

public func outMap<Destination : OutMap>(mapper: inout OutMapper<Destination, MappingKeys>) throws {
Expand All @@ -361,14 +470,17 @@ extension Match.Compact : Mappable {
if let score = self.score {
try mapper.map(score, to: .score)
}
if let pens = self.penalties {
try mapper.map(pens, to: .penalties)
}
}

}

extension Match.Full : Mappable {

public enum MappingKeys : String, IndexPathElement {
case id, home, away, date, endDate, location, score, events, stage_title
case id, home, away, date, endDate, location, score, events, stage_title, penalties
}

public init<Source : InMap>(mapper: InMapper<Source, MappingKeys>) throws {
Expand All @@ -381,6 +493,7 @@ extension Match.Full : Mappable {
self.stageTitle = try mapper.map(from: .stage_title)
self.events = try mapper.map(from: .events)
self.score = try? mapper.map(from: .score)
self.penalties = try? mapper.map(from: .penalties)
}

public func outMap<Destination : OutMap>(mapper: inout OutMapper<Destination, MappingKeys>) throws {
Expand All @@ -395,6 +508,9 @@ extension Match.Full : Mappable {
if let score = self.score {
try mapper.map(score, to: .score)
}
if let pens = self.penalties {
try mapper.map(pens, to: .penalties)
}
}

}
Expand Down
2 changes: 1 addition & 1 deletion Match Notification/NotificationViewController.swift
Expand Up @@ -51,7 +51,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
}
avenue.prepareItem(at: match.home.badges.large)
avenue.prepareItem(at: match.away.badges.large)
scoreLabel.text = match.scoreString()
scoreLabel.text = match.scoreOrPenaltyString()
homeLabel.text = match.home.shortName
awayLabel.text = match.away.shortName
// if let lastEvent = match.events.last {
Expand Down
4 changes: 4 additions & 0 deletions TheGreatGame.xcodeproj/project.pbxproj
Expand Up @@ -189,6 +189,7 @@
7CAAEFB91EC36B3900427D7E /* CombinedRefreshing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAAEFB81EC36B3900427D7E /* CombinedRefreshing.swift */; };
7CAAEFBB1EC36E1A00427D7E /* Sourceful.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAAEFBA1EC36E1A00427D7E /* Sourceful.swift */; };
7CAB303D1F20F14C00E65429 /* gallery.ckcomplication in Resources */ = {isa = PBXBuildFile; fileRef = 7CAB303C1F20F14C00E65429 /* gallery.ckcomplication */; };
7CAB303F1F210F4D00E65429 /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAB303E1F210F4D00E65429 /* EventTests.swift */; };
7CCD25C61EDD7AE300F3C5CB /* SingleElementMemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C357ED71ED442D300CFCF3B /* SingleElementMemoryCache.swift */; };
7CCD25C91EDDCF3F00F3C5CB /* ComplicationReloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD25C81EDDCF3F00F3C5CB /* ComplicationReloader.swift */; };
7CCD25CB1EDEA23900F3C5CB /* ComplicationPusher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCD25CA1EDEA23900F3C5CB /* ComplicationPusher.swift */; };
Expand Down Expand Up @@ -498,6 +499,7 @@
7CAAEFB81EC36B3900427D7E /* CombinedRefreshing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinedRefreshing.swift; sourceTree = "<group>"; };
7CAAEFBA1EC36E1A00427D7E /* Sourceful.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sourceful.swift; sourceTree = "<group>"; };
7CAB303C1F20F14C00E65429 /* gallery.ckcomplication */ = {isa = PBXFileReference; lastKnownFileType = folder; path = gallery.ckcomplication; sourceTree = "<group>"; };
7CAB303E1F210F4D00E65429 /* EventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventTests.swift; sourceTree = "<group>"; };
7CCD25C81EDDCF3F00F3C5CB /* ComplicationReloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationReloader.swift; sourceTree = "<group>"; };
7CCD25CA1EDEA23900F3C5CB /* ComplicationPusher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationPusher.swift; sourceTree = "<group>"; };
7CCD25CC1EDEB1BA00F3C5CB /* Watch Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Watch Extension.entitlements"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -737,6 +739,7 @@
7C357ED51ED4408C00CFCF3B /* APICacheTests.swift */,
7CA542101EDB157B00B4D1BD /* ComplicationDataSourceTests.swift */,
BE1713C61EEFED1100CFB448 /* UploadTests.swift */,
7CAB303E1F210F4D00E65429 /* EventTests.swift */,
);
path = "Kit Tests";
sourceTree = "<group>";
Expand Down Expand Up @@ -1730,6 +1733,7 @@
BE1713C71EEFED1100CFB448 /* UploadTests.swift in Sources */,
7C0EA6171EBA1C9000F50EB2 /* TheGreatKitTests.swift in Sources */,
7CA542111EDB157B00B4D1BD /* ComplicationDataSourceTests.swift in Sources */,
7CAB303F1F210F4D00E65429 /* EventTests.swift in Sources */,
7C7916DF1EBB12250070E6C5 /* NetworkActivityIndicatorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down

0 comments on commit 8ee0b10

Please sign in to comment.