Skip to content

Commit

Permalink
part 9 initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Francois committed Feb 9, 2021
1 parent c60ede6 commit 46dd3ab
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Connect4.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = name.heuchamps.Connect4Tests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Debug;
Expand All @@ -589,6 +590,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = name.heuchamps.Connect4Tests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
Expand Down
95 changes: 95 additions & 0 deletions Connect4/Board/Position.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,101 @@ public struct Position {
numberOfMoves == Dimension.area
}

// MARK: - Anticipating winning move

/**
* Return a bitmap of all the possible next moves the do not lose in one turn.
* A losing move is a move leaving the possibility for the opponent to win directly.
*
* Warning this function is intended to test position where you cannot win in one turn
* If you have a winning move, this function can miss it and prefer to prevent the opponent
* to make an alignment.
*/
internal var possibleNonLoosingMoves : UInt {
assert(!canWinNext)
var possibleMask = possible
let opponentWin = opponentWinningPosition
let forcedMoves = possibleMask & opponentWin

if forcedMoves != 0 {
if (forcedMoves & (forcedMoves - 1)) != 0 {
// the opponnent has two winning moves and you cannot stop him
return 0
}
else {
// enforce to play the single forced move
possibleMask = forcedMoves;
}
}

// avoid to play below an opponent winning spot
return possibleMask & ~(opponentWin >> 1)
}

/// true if current player can win next move
internal var canWinNext : Bool {
winningPosition & possible != 0;
}

/// a bitmask of the possible winning positions for the current player
private var winningPosition : UInt {
computeWinningPosition(position: currentPosition, mask: mask)
}

/// a bitmask of the possible winning positions for the opponent
private var opponentWinningPosition : UInt {
computeWinningPosition(position: currentPosition ^ mask, mask: mask)
}

/// a bitmask of all possibles moves
private var possible : UInt {
(mask + Self.bottomMask) & Self.boardMask;
}

/**
* Identify all winning positions of a given board. Meaning all open ended 3-aligments.
* - parameter position: position for the current player
* - parameter mask: position for all players
* - returns: bitmask containg all open ended 3-alignements
*/
private func computeWinningPosition(position: UInt, mask: UInt) -> UInt {
// vertical;
var r = (position << 1) & (position << 2) & (position << 3)

//horizontal
var p = (position << (Dimension.height + 1)) & (position << (2 * (Dimension.height + 1)))
r |= p & (position << (3 * (Dimension.height + 1)))
r |= p & (position >> (Dimension.height + 1))
p = (position >> (Dimension.height + 1)) & (position >> (2 * (Dimension.height + 1)))
r |= p & (position << (Dimension.height + 1))
r |= p & (position >> (3 * (Dimension.height + 1)))

//diagonal 1
p = (position << (Dimension.height)) & (position << (2 * Dimension.height))
r |= p & (position << (3 * Dimension.height))
r |= p & (position >> Dimension.height)
p = (position >> Dimension.height) & (position >> (2 * Dimension.height))
r |= p & (position << Dimension.height)
r |= p & (position >> (3 * Dimension.height))

//diagonal 2
p = (position << (Dimension.height + 2)) & (position << (2 * (Dimension.height + 2)))
r |= p & (position << (3 * (Dimension.height + 2)))
r |= p & (position >> (Dimension.height + 2))
p = (position >> (Dimension.height + 2)) & (position >> (2 * (Dimension.height + 2)))
r |= p & (position << (Dimension.height + 2))
r |= p & (position >> (3 * (Dimension.height + 2)))

return r & (Self.boardMask ^ mask)
}

static private var bottomMask = bottom(width: Dimension.width, height: Dimension.height);
static private var boardMask = bottomMask * ((1 << Dimension.height) - 1);

static private func bottom(width: Int, height: Int) -> UInt {
width == 0 ? 0 : bottom(width: width - 1, height: height) | UInt(1) << ((width - 1) * (height + 1));
}

// MARK: - Static bit functions

/**
Expand Down
27 changes: 20 additions & 7 deletions Connect4/Solver/Solver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,30 @@ public final class Solver {
var alpha = alpha
var beta = beta
assert(alpha < beta)
assert(!position.canWinNext)

// increment counter of explored nodes
nodeCount += 1

// check for possible non loosing moves
let next = position.possibleNonLoosingMoves;
if next == 0 {
// if no possible non losing move, opponent wins next move
return -(Position.Dimension.area - position.numberOfMoves) / 2
}

// check for draw game
guard !position.draw else { return 0 }
if position.draw { return 0 }

// lower bound of score as opponent cannot win next move
let min = -(Position.Dimension.area - 2 - position.numberOfMoves) / 2
if(alpha < min) {
// there is no need to keep beta above our max possible score.
alpha = min;

// check if current player can win next move
for column in 0..<Position.Dimension.width {
if position.canPlay(in: column) && position.isWinnngMove(in: column) {
return (Position.Dimension.area + 1 - position.numberOfMoves) / 2
if(alpha >= beta) {
// prune the exploration if the [alpha;beta] window is empty.
return alpha;
}
}

Expand All @@ -97,10 +110,10 @@ public final class Solver {
}

// compute the score of all possible next move and keep the best one
// optimization : don't use 'for column in columnOrder' which is 20% slower on test-set 1
// optimization : don't use 'for column in columnOrder' which is 20% slower on test-set 2
for index in 0..<Position.Dimension.width {
let column = columnOrder[index]
if position.canPlay(in: column) {
if next & Position.columnMask(for: column) != 0 {
var position2 = Position(position: position)

// It's opponent turn in position2 position after current player plays x column.
Expand Down

0 comments on commit 46dd3ab

Please sign in to comment.