A Quarto board game mimic that you can play against your friend or play against an advanced bot that will never lose! (Currently, there's a bug that occurs near the end of the game where the bot will not work properly)
The AI for the bot is implemented using a custom version of Negamax with Alpha-Beta pruning.
Implementing the Negamax just requires one extra parameter in the recursive method, NSInteger color. If color is positive, then it's the bot's turn. If the color is negative, then it's the opponent's turn.
How the bot decides where to move is very similar to TicTacToeAI in how it looks ahead a certain number of moves with different perspectives. It has just one change: each move has two parts to it.
The bot is given a piece to place (Quarto game rules) and the board indices. It iterates through all the empty spots and places the piece to determine where would be a good spot to place. At each iteration, it also has to iterate through all the pieces that are left to decide which piece to give to the opponent that will minimize the opponent's score. The stopping point is when there are no more pieces, or a winner is found, or when it searched through just enough depths.
The animation is done by implementing these three methods from UIResponder class:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
How they work is:
- touchesBegan: Called once when finger touches the screen.
- touchesMoved: Called many times as finger moves across the screen.
- touchesEnded: Called once when finger leaves the screen.
You can test which UIView was initially touched by doing this:
UITouch *touch = [touches anyObject];
if ([touch.view isKindOfClass:[QuartoPiece class]]) {
// The touched object is a Quarto Piece.
}
Important! In all three methods, this returns the first object that was touched, but not where the touch is released or where the finger is on top of while moving.
To test for which UIView the finger is currently on, you can use this:
CGPoint currentTouchLocation = [touch locationInView:self.view];
UIView *viewInCurrentTouchLocation = [self.yourView hitTest:currentTouchLocation withEvent:nil];
The position of your finger is relative to the top-left corner of the initial position of the view. So you can do this inside touchesMoved
:
// Get the location of the finger relative to the initial touch.
CGPoint fingerPoint = [touch locationInView:self.yourView];
// Make the view centered on the finger and shifted up.
touch.view.center = CGPointMake(fingerPoint.x, fingerPoint.y-touch.view.frame.size.height / 2.f);
While dragging the UIView, you might notice that it is behind some other UIViews. To fix this, you can add something like this in touchesBegan
:
self.quartoView.pickedPieceView.layer.zPosition = MAXFLOAT;
self.quartoView.piecesView.layer.zPosition = 0;
// Add a property
@property (nonatomic, assign) CGPoint firstTouchPointCenter; // Saves the location of the first touch.
// Inside touchesMoved
_firstTouchPointCenter = touch.view.center;
// Call this in touchesMoved or touchesEnded to make it go back to original spot.
touch.view.center = CGPointMake(self.firstTouchPointCenter.x, self.firstTouchPointCenter.y);
This is my settings that I like to use:
self.playerVsPlayerButton.layer.shadowOpacity = .5f;
self.playerVsPlayerButton.layer.shadowRadius = 1;
self.playerVsPlayerButton.layer.shadowOffset = CGSizeMake(0, 6);
self.playerVsPlayerButton.layer.cornerRadius = self.playerVsPlayerButton.bounds.size.height * 1/8.f;
Uses a custom alert view instead of the one provided by UIKit. It's so that I can add whatever I want in there for customization.
// Initialize CustomIOSAlertView.
_customIOSAlertView = [[CustomIOSAlertView alloc] init];
[self.customIOSAlertView setButtonTitles:nil];
[self.customIOSAlertView setCloseOnTouchUpOutside:YES];
[self.customIOSAlertView setContainerView:self.settingsView]; // This is your custom view you want to use inside the AlertView.
// Use these to open and close.
[self.customIOSAlertView show];
[self.customIOSAlertView close];
Thanks to Alaric, Janelle, Jessica, Justin, Monique, and Vivian (ordered alphabetically) for suggestions and inputs.
Special Thanks:
- Alaric: Button text color change on press in the main menu. Design ideas. Product testing.
- Janelle: Design ideas.
- Jessica: Design ideas. Product testing.
Board pieces taken from Typeicons by Stephen Hutchings. License: CC BY-SA 3.0. Changes:
- Removed paddings inside the icon.
- Different colors.
Settings icon taken from Icons8. License: CC BY-ND 3.0