FourInARow/AAPLMinmaxStrategy.m
/* |
Copyright (C) 2015 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Additions to the game model classes adding GameplayKit protocols for use with the minmax strategist. |
*/ |
#import "AAPLMinmaxStrategy.h" |
@implementation AAPLMove |
- (instancetype)initWithColumn:(NSInteger)column { |
self = [super init]; |
if (self) { |
_column = column; |
} |
return self; |
} |
+ (AAPLMove *)moveInColumn:(NSInteger)column { |
return [[self alloc] initWithColumn:column]; |
} |
@end |
@implementation AAPLPlayer (MinmaxStrategy) |
- (NSInteger)playerId { |
return self.chip; |
} |
@end |
@implementation AAPLBoard (MinmaxStrategy) |
#pragma mark - Managing players |
- (NSArray<AAPLPlayer *> *)players { |
return [AAPLPlayer allPlayers]; |
} |
- (AAPLPlayer *)activePlayer { |
return self.currentPlayer; |
} |
#pragma mark - Copying board state |
- (__nonnull id)copyWithZone:(nullable NSZone *)zone { |
AAPLBoard *copy = [[[self class] allocWithZone:zone] init]; |
[copy setGameModel:self]; |
return copy; |
} |
- (void)setGameModel:(AAPLBoard *)gameModel { |
[self updateChipsFromBoard:gameModel]; |
self.currentPlayer = gameModel.currentPlayer; |
} |
#pragma mark - Finding & applying moves |
- (NSArray<AAPLMove *> *)gameModelUpdatesForPlayer:(AAPLPlayer *)player { |
NSMutableArray<AAPLMove *> *moves = [NSMutableArray arrayWithCapacity:AAPLBoard.width]; |
for (NSInteger column = 0; column < AAPLBoard.width; column++) { |
if ([self canMoveInColumn:column]) { |
[moves addObject:[AAPLMove moveInColumn:column]]; |
} |
} |
// Will be empty if isFull. |
return moves; |
} |
- (void)applyGameModelUpdate:(AAPLMove *)gameModelUpdate { |
[self addChip:self.currentPlayer.chip inColumn:gameModelUpdate.column]; |
self.currentPlayer = self.currentPlayer.opponent; |
} |
#pragma mark - Evaluating board state |
- (BOOL)isWinForPlayer:(AAPLPlayer *)player { |
// Use AAPLBoard's utility method to find all N-in-a-row runs of the player's chip. |
NSArray<NSNumber *> *runCounts = [self runCountsForPlayer:player]; |
// The player wins if there are any runs of 4 (or more, but that shouldn't happen in a regular game). |
NSNumber *longestRun = [runCounts valueForKeyPath:@"@max.self"]; |
return longestRun.integerValue >= AAPLCountToWin; |
} |
- (BOOL)isLossForPlayer:(AAPLPlayer *)player { |
// This is a two-player game, so a win for the opponent is a loss for the player. |
return [self isWinForPlayer:player.opponent]; |
} |
- (NSInteger)scoreForPlayer:(AAPLPlayer *)player { |
/* |
Heuristic: the chance of winning soon is related to the number and length |
of N-in-a-row runs of chips. For example, a player with two runs of two chips each |
is more likely to win soon than a player with no runs. |
Scoring should weigh the player's chance of success against that of failure, |
which in a two-player game means success for the opponent. Sum the player's number |
and size of runs, and subtract from it the same score for the opponent. |
This is not the best possible heuristic for Four-In-A-Row, but it produces |
moderately strong gameplay. Try these improvements: |
- Account for "broken runs"; e.g. a row of two chips, then a space, then a third chip. |
- Weight the run lengths; e.g. two runs of three is better than three runs of two. |
*/ |
// Use AAPLBoard's utility method to find all runs of the player's chip and sum their length. |
NSArray<NSNumber *> *playerRunCounts = [self runCountsForPlayer:player]; |
NSNumber *playerTotal = [playerRunCounts valueForKeyPath:@"@sum.self"]; |
// Repeat for the opponent's chip. |
NSArray<NSNumber *> *opponentRunCounts = [self runCountsForPlayer:player.opponent]; |
NSNumber *opponentTotal = [opponentRunCounts valueForKeyPath:@"@sum.self"]; |
// Return the sum of player runs minus the sum of opponent runs. |
return playerTotal.integerValue - opponentTotal.integerValue; |
} |
@end |
Copyright © 2015 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2015-09-16