Slotbaer / 7 Tage im Oktober / Tag 3
 

 

23.10.2013 7 Tage im Oktober Tag 3.

//
//  CBTSettings.h
//  CBT
//
//  Created by Brumbaer on 23.10.13.
//  Copyright (c) 2013 Brumbaer. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef enum { endByLapLimit, endByTimeLimit } CBTEndLimit;
typedef enum { sortByLaps, aortByBesLap } CBTSortParticipants;
typedef enum { immediateleyAfterFirst, immediatelyAfterThird, finishLapAfterFirst, finishLapAfterThird, endAfterLast } CBTEndSession;
typedef enum { startIndividually, startWithFirst, startImmediately } CBTStartSession;
typedef enum { fuelOff, fuelOn  } CBTFuelMode;

@interface CBTSettings : NSObject

@property (assign, nonatomic) CBTEndLimit endLimit;
@property (assign, nonatomic) NSUInteger lapLimit;
@property (assign, nonatomic) NSTimeInterval timeLimit;

@property (assign, nonatomic) CBTStartSession startSession;
@property (assign, nonatomic) CBTEndSession endSession;

@property (assign, nonatomic) CBTFuelMode fuel;

@property (assign, nonatomic) CBTSortParticipants sort;

@end
#import <Foundation/Foundation.h>

@class CBTSession;

@interface CBTParticipant : NSObject

@property (readonly, copy, nonatomic) NSString* key;
@property (readonly, copy, nonatomic) UIColor* color;
@property (readonly, assign, nonatomic) NSUInteger laps;
@property (readwrite, assign, nonatomic) NSUInteger position;
@property (readonly, assign, nonatomic) NSTimeInterval startTime;
@property (readonly, assign, nonatomic) NSTimeInterval endTime;
@property (readonly, assign, nonatomic) NSTimeInterval lastCrossing;

@property (readonly, assign, nonatomic) NSTimeInterval lapTime;
@property (readonly, assign, nonatomic) NSTimeInterval bestTime;

- (instancetype) initWithKey: (NSString*) key color: (UIColor*) color;

- (void) prepareForSession: (CBTSession*) session;

- (BOOL) addLapTime: (NSTimeInterval) time session: (CBTSession*) session; // YES if Besttime

- (BOOL) running;
- (BOOL) finished;
- (BOOL) started;
- (NSTimeInterval) totalTime;

- (void) finishSession: (CBTSession*) session;
@end
//
//  CBTSession.h
//  CBT
//
//  Created by Brumbaer on 23.10.13.
//  Copyright (c) 2013 Brumbaer. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef enum { finishLine} CBTSensorType;
typedef enum { startLightOff, startLightRace, startLight1, startLight2, startLight3, startLight4, startLight5, startLightYellow} CBTStartLight;
typedef enum { stateUnknown, sessionPrepared, sessionRunning, sessionFinished, sessionStopped, sessionAborted } CBTState;

@class CBTSession;
@class CBTSettings;
@class CBTParticipant;

@protocol CBTSessionDelegate 

@optional

- (void) sessionDidUpdateStartLight: (CBTSession*) session;
- (void) sessionDidPrepareSession: (CBTSession*) session;
- (void) sessionDidStartSession: (CBTSession*) session;
- (void) sessionDidStopSession: (CBTSession*) session;
- (void) sessionDidAbortSession: (CBTSession*) session;
- (void) sessionDidEndSession: (CBTSession*) session;
- (void) sessionDidJumpStart: (CBTSession*) session;
- (void) sessionDidUpdate: (CBTSession*) session;
- (void) session: (CBTSession*) session particpantsDidChange: (NSSet*) participants;
- (void) session: (CBTSession*) session particpantNewLapTime: (CBTParticipant*) participant;
- (void) session: (CBTSession*) session particpantNewBestTime: (CBTParticipant*) participant;
- (void) session: (CBTSession*) session particpantNewBestOverallTime: (CBTParticipant*) participant;
- (void) session: (CBTSession*) session particpantFinishes: (CBTParticipant*) participant;

@end

@interface CBTSession : NSObject

@property (assign, readonly) CBTState status;
@property (assign, readonly) NSTimeInterval startTime;
@property (assign, readonly) NSTimeInterval endTime;
@property (assign, readonly) NSTimeInterval timeOffset;
@property (assign, readonly) NSTimeInterval currentTime;
@property (assign, readonly) BOOL timeOffsetSet;

@property (assign, readonly) CBTStartLight startLight;

- (void) prepareWithParticipants: (NSArray*) ps settings: (CBTSettings*) settings;
- (void) updateStartLight: (CBTStartLight) light;
- (void) startSession;
- (void) stopSession;
- (void) abortSession;
- (void) endSession;
- (void) jumpStart;
- (void) sensor: (CBTSensorType) sensor key: (NSString*) participantsKey time: (NSTimeInterval) time;

- (void) addDelegate: (id) delegate;
- (void) removeDelegate: (id) delegate;
- (void) removeAllDelegates;

- (NSArray*) participants;

- (BOOL) acceptsCrossings;
@end
- (void) sensor: (CBTSensorType) sensor key: (NSString*) participantsKey time: (NSTimeInterval) time {
	NSMutableSet* changedParticipants = [NSMutableSet new];
	CBTParticipant* participant = [self participantForKey: participantsKey];
	
	if (sensor == finishLine && self.acceptsCrossings && !participant.finished) {
		if (!_timeOffsetSet) {
			NSTimeInterval t = [NSDate timeIntervalSinceReferenceDate];
			
			_timeOffsetSet = YES;
			_timeOffset = t - time;
		}
		
		_currentTime = time + _timeOffsetSet;
		
		if (_startTime == -1) {
			_startTime = _currentTime;
		}
		
		if (participant) {
			if ([participant addLapTime: time session: self]) { // Neue Bestzeit
				if ([self isOverallBestTime: participant.lapTime])
					[self delegatesPerformSelector: @selector(session:particpantNewBestOverallTime:) withObject: participant];
				else
					[self delegatesPerformSelector: @selector(session:particpantNewBestTime:) withObject: participant];
			}
			else
				[self delegatesPerformSelector: @selector(session:particpantNewLapTime:) withObject: participant];
			
			BOOL endFlag = (_settings.endLimit == endByLapLimit) ? (participant.laps >= _settings.lapLimit) : (participant.lastCrossing - _startTime > _settings.timeLimit);
			
			if (endFlag) {
				[participant finishSession: self];
			}
			else if ((_settings.endSession == finishLapAfterFirst) || (_settings.endSession == finishLapAfterThird && [self participantsPassing: ^BOOL(CBTParticipant* p, NSUInteger idx, BOOL *stop) { return p.finished; }].count == 3)) {
				endFlag = YES;
				[participant finishSession: self];
			}
			
			if (endFlag) {
				[self delegatesPerformSelector: @selector(session:particpantFinishes:) withObject: participant];
				
				if (_settings.endSession == immediateleyAfterFirst || (_settings.endSession == immediatelyAfterThird && [self participantsPassing: ^BOOL(CBTParticipant* p, NSUInteger idx, BOOL *stop) { return p.finished; }].count == 3)) {
					[_participants enumerateObjectsUsingBlock:^(CBTParticipant* p, NSUInteger idx, BOOL *stop) {

						if (p.running) {
							[p finishSession: self];
							[self delegatesPerformSelector: @selector(session:particpantFinishes:) withObject: p];
						}
					}];
				}
				
				if ([self participantsPassing: ^BOOL(CBTParticipant* p, NSUInteger idx, BOOL *stop) { return p.running; }].count == 0) {
					[self endSession];
				}
			}
			
			[_participants sortUsingComparator:^NSComparisonResult(CBTParticipant* p1, CBTParticipant* p2) {
				if (!p1.started && !p2.started)
					return p1.position - p2.position;
				else if (!p1.started && p2.started)
					return NSOrderedDescending;
				else if (p1.started && !p2.started)
					return NSOrderedAscending;
				else if (_settings.sort == sortByLaps) {
					if (p1.laps != p2.laps)
						return p2.laps - p1.laps;
					else
						return p1.totalTime - p2.totalTime;
				}
				else {
					NSTimeInterval t1 = (p1.bestTime != -1) ? p1.bestTime : 10000000;
					NSTimeInterval t2 = (p2.bestTime != -1) ? p2.bestTime : 10000000;
					return t1 - t2;
				}
			}];
			
			[_participants enumerateObjectsUsingBlock:^(CBTParticipant* p, NSUInteger idx, BOOL *stop) {
				if (p.started && p.position != idx) {
					p.position = idx;
					[changedParticipants addObject: p];
				}
			}];
		}
	}
		
	if (changedParticipants.count)
		[self delegatesPerformSelector: @selector(session:particpantsDidChange:) withObject: changedParticipants];
	[self delegatesPerformSelector: @selector(sessionDidUpdate:)];
}

Heute geht's ins Eingemachte. Die Einweckgläser müssen aber nicht entstaubt werden. Es geht mehr um Innereien, nicht Leber, Nieren und so - man denkt ihr nur ans Essen - es geht um die Zentrale Schaltstelle in einem Rundenzählerprogramm. 

Alles verpackt in - falsch, nicht in Frischhaltefolie - einer Klasse. CBTSession beschreibt eine Rennsession. Das kann ein Rennen oder ein Trainingslauf sein.

Eine Session bekommt zwei Parameter gestellt die Teilnehmer vom Typ CBTParticipant und die Einstellungen in Form der Klasse CBTSettings.

CBTSettings beschreibt wie die Sitzung ablaufen soll. D.h. soll die Session nach fester Rundenzahl oder nach einer bestimmten Zeit beendet werden. Soll sie sofort enden, wenn der erste Teilnehmer fertig ist, oder erst bei der nächsten Zieldurchfahrt oder etwa erst, wenn alle Teilnehmer die vorgeschriebenen Runden bzw. Zeit gefahren sind. Ebenso kann die Session mit dem Startsignal beginnen, mit der ersten Zieldurchfahrt eines beliebigen Teilnehmers oder für jeden Teilnehmer getrennt bei dessen erster Zieldurchfahrt. Zwischen der Sortierung nach Runden und Gesamtzeit oder nach Bestzeit kann man ebenfalls wählen.

CBTSettings ist im Moment eine reine Datenklasse zum Speichern der Werte

CBTParticipant beschreibt einen Teilnehmer. Der Key, identifiziert den Teilnehmer gegenüber dem System. Und die color gibt an, in welcher Farbe er dargestellt werden soll. Man kann später weitere beschreibende Parameter, wie Fahrername oder Fahrzeugtyp hinzufügen.

Ferner enthält CBTParticipant alle Variablen, die benötigt werden um die Rundenzeiten zu bestimmen, Bestzeiten zu verwalten und Runden zu zählen.

addLapTime:session: bekommt den Zeitpunkt einer Zieldurchfahrt übergeben und bestimmt daraus die Rundenzeiten. Handelt es sich um eine Bestzeit, so gibt die Routine YES zurück.

Die CU liefert Zeiten nur mit einer Sensordurchfahrt. Will man eine laufende Uhr darstellen, muss man also auf die Computeruhr zurückgreifen. Um beide Zeiten zu synchronisieren wird der Abstand zwischen beiden bestimmt und alle von der CU eingehenden werden mit dessen Hilfe an die Computerzeit angepasst.

Die Startzeit einer Session oder eines Teilnehmers ist der Beginn dieser Session in so weit das Objekt betroffen ist. Je nach start Modus, können die Werte für Session und Teilnehmer gleich oder unterschiedlich sein.

Solange die Startzeit -1 ist, ist das Rennen nicht gestartet bzw, nimmt der Teilnehmer noch nicht an der Session teil.

Wird die Session beendet bzw. beendet ein Teilnehmer die Session, so wird die Endzeit von -1 auf die Zeit zu der das Ereignis geschah gesetzt. 

Somit kann man anhand von Start- und Endzeit bestimmen ob ein Teilnehmer, schon gestartet ist, teilnimmt oder das Rennen beendet hat. Zur Bequemlichkeit gibt es 3 Methoden, die  den Status des Teilnehmers an Hand von Start- und Endzeit bestimmen.

finishSession: wird aufgerufen, damit der Teilnehmer auch merkt, dass er fertig ist.

 CBTSession ist deutlich aufwändiger, hat schließlich auch mehr zu tun.

 Will man eine Session starten ruf man prepareWithParticipants: settings: auf.

updateStartLight:, startSession,
stopSession, abortSession,
endSession, jumpStart teilen der Session mit, dass sich was geändert hat und dass sie gefälligst was unternehmen soll. EndSession fällt ein wenig aus der Rolle, weil es auch aus Session heraus aufgerufen wird. 

Einige der Routinen machen im Moment noch nicht wirklich viel, aber das kann sich noch ändern.

sensor:key:time: wird aufgerufen, wenn ein Sensor ein Signal meldet. Es wird ein Sensortyp übergeben, aber im Moment werden nur Zieldurchfahrten erkannt.

In dieser Routine passiert fast Alles. Die Runde wird gezählt, Runden- und Bestzeiten ermittelt, es wird überprüft ob der Teilnehmer seine Runden gefahren ist oder ob er aus anderem Grund die Session beenden soll, ob die Session beendet werden soll.

Wie bei Menschen führt die Erkenntnis oft nicht zu Taten sondern nur zu Gerede und so reicht die Session die Erkenntnis an ihre Delegates weiter.

Man kann Erwarten, dass sich z.B. ein Delegate um die Bildschirmanzeige kümmert, ein weiterer Delegate um die Sprachausgabe und ein dritter z.B. um die Kommunikation mit anderen Anzeigegeräten.

Die Klassen sind noch nicht getestet. Es können also noch Fehler vorhanden sein. 

Die Header und die unkommentierte sensor Routine stehen rechts.

Das Schreiben hat etwa dreieinhalb Stunden gedauert. Diesen Text zu schreiben und die anderen Artikel zu verschieben weitere 2.5 Stunden.

Es wirkt Alles etwas ungeordnet. ich hätte erst das Projekt machen sollen und danach in Ruhe darüber schreiben sollen - außerdem hätte ich dann mehr Zeit zum Programmieren :)