Skip to content

Commit

Permalink
part 4 initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Francois committed Feb 8, 2021
1 parent d217b8a commit 453b48d
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@
argument = "position 2252576253462244111563365343671351441"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "test-set 0 --weak"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "position 427566236745127177115664464254 --weak"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
Expand Down
65 changes: 44 additions & 21 deletions Connect4/Solver/Solver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,25 @@ public final class Solver {
/// counter of explored nodes.
public var nodeCount : Int = 0

/// public initilizert
/// public initializer
public init() {
}

/**
Recursively solve a connect 4 position using negamax variant of min-max algorithm.
Recursively solve a connect 4 position using negamax variant of alpha-beta algorithm.
- parameter position: actual position to be solved
- returns: the score of a position:
- 0 for a draw game
- positive score if you can win whatever your opponent is playing. Your score is
the number of moves before the end you can win (the faster you win, the higher your score)
- negative score if your opponent can force you to lose. Your score is the oposite of
the number of moves before the end you will lose (the faster you lose, the lower your score).
- parameter alpha: lower bound score
- parameter beta: upper bound score
- returns: the exact score, an upper or lower bound score depending of the case:
- if true score of position <= alpha then true score <= return value <= alpha
- if true score of position >= beta then beta <= return value <= true score
- if alpha <= true score <= beta then return value = true score
*/
private func negamax(position: Position) -> Int {
private func negamax(position: Position, alpha : Int, beta: Int) -> Int {
var alpha = alpha
var beta = beta
assert(alpha < beta)

// increment counter of explored nodes
nodeCount += 1

Expand All @@ -54,8 +58,17 @@ public final class Solver {
}
}

// init the best possible score with a lower bound of score.
var bestScore = -Position.Dimension.area
// upper bound of our score as we cannot win immediately
let max = (Position.Dimension.area - 1 - position.numberOfMoves) / 2
if (beta > max) {
// there is no need to keep beta above our max possible score.
beta = max

// prune the exploration if the [alpha;beta] window is empty.
if alpha >= beta {
return beta
}
}

// compute the score of all possible next move and keep the best one
for column in 0..<Position.Dimension.width {
Expand All @@ -66,26 +79,36 @@ public final class Solver {
position2.play(in: column)

// If current player plays col x, his score will be the opposite of opponent's score after playing col x
let score = -negamax(position: position2)
let score = -negamax(position: position2, alpha: -beta,beta: -alpha)

// keep track of best possible score so far.
if(score > bestScore) {
bestScore = score
// prune the exploration if we find a possible move better than what we were
if score >= beta {
return score
}

// reduce the [alpha;beta] window for next exploration, as we only
// need to search for a position that is better than the best so far.
if score > alpha {
alpha = score
}
}
}

return bestScore;
return alpha;
}

/**
Entry point to solve a connect 4 position
- parameter position: actual position to be solved
- parameter weak : if true, only tells you the win/draw/loss outcome of the position, otherwise, it will tell you
the score taking into account the number of moves before the end of the game
*/
public func solve(position: Position) -> Int {
public func solve(position: Position, weak: Bool = false) -> Int {
nodeCount = 0;
return negamax(position: position);
return weak ?
// Use a [-1;1] score window to look only for win/draw/loss
negamax(position: position, alpha: -1, beta: 1)
:
negamax(position: position, alpha: -Position.Dimension.area / 2, beta: Position.Dimension.area / 2);
}
}


14 changes: 10 additions & 4 deletions Connect4CLI/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ extension Connect4Solver {
@Argument(help: "A sequence of the played columns.")
var moves: String

@Flag(help: "Wether to use weak solver or not.")
var weak = false

func run() {

guard let position = Connect4.Position(moves: moves) else {
Expand All @@ -70,7 +73,7 @@ extension Connect4Solver {
os_signpost(.begin, log: pointsOfInterest, name: "Solver", "position %@", position.debugDescription)

let begin = Date()
let score = solver.solve(position: position)
let score = solver.solve(position: position, weak: weak)
let duration = -begin.timeIntervalSinceNow

os_signpost(.end, log: pointsOfInterest, name: "Solver", "score %d, nodes %d", score, solver.nodeCount)
Expand All @@ -97,6 +100,9 @@ extension Connect4Solver {
@Argument(help: "Choose a test set index between 0 and 5")
var index: Int

@Flag(help: "Wether to use weak solver or not.")
var weak = false

func validate() throws {
guard (0..<6).contains(index) else {
throw ValidationError("Test set must be between 0 and 5.")
Expand All @@ -123,10 +129,10 @@ extension Connect4Solver {

let begin = Date()

let score = solver.solve(position: position.position)
let score = solver.solve(position: position.position, weak: weak)
let duration = -begin.timeIntervalSinceNow
if score != position.score {
os_signpost(.event, log: pointsOfInterest, name: "Solver", "Score is %d, expected is %d", score, position.score)
if (!weak && score != position.score ) || (weak && score != position.score.signum()){
os_signpost(.event, log: pointsOfInterest, name: "Solver", "Score is %d, expected is %d", score, weak ? position.score : position.score.signum())
}
totalDuration += duration
totalNode += solver.nodeCount
Expand Down
9 changes: 7 additions & 2 deletions Connect4Tests/SolverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Connect4

class SolverTests: XCTestCase {

let moves = "2252576253462244111563365343671351441"
let moves = "427566236745127177115664464254"
var position : Position? = nil

override func setUpWithError() throws {
Expand All @@ -31,6 +31,11 @@ class SolverTests: XCTestCase {

func testSolve() throws {
guard let position = position else { return }
XCTAssertEqual(Solver().solve(position: position), -1)
XCTAssertEqual(Solver().solve(position: position), 2)
}

func testWeakSolve() throws {
guard let position = position else { return }
XCTAssertEqual(Solver().solve(position: position, weak: true), 1)
}
}

0 comments on commit 453b48d

Please sign in to comment.