Skip to content

Commit

Permalink
part 7 initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Francois committed Feb 9, 2021
1 parent e4460e1 commit 9704dc9
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 4 deletions.
8 changes: 8 additions & 0 deletions Connect4.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
0F640D8025D2993400E9B0B9 /* TranspositionTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F640D7F25D2993400E9B0B9 /* TranspositionTable.swift */; };
0F640D8825D29CFA00E9B0B9 /* TranspositionTableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F640D8725D29CFA00E9B0B9 /* TranspositionTableTests.swift */; };
0F7FDE7625B43DB8008D5C4C /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 0F7FDE7525B43DB8008D5C4C /* ArgumentParser */; };
0FB794B825D1400C005AE438 /* Chip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB794B025D1400C005AE438 /* Chip.swift */; };
0FB794B925D1400C005AE438 /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB794B125D1400C005AE438 /* Position.swift */; };
Expand Down Expand Up @@ -63,6 +65,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
0F640D7F25D2993400E9B0B9 /* TranspositionTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranspositionTable.swift; sourceTree = "<group>"; };
0F640D8725D29CFA00E9B0B9 /* TranspositionTableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranspositionTableTests.swift; sourceTree = "<group>"; };
0FB7948125D13E22005AE438 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
0FB794B025D1400C005AE438 /* Chip.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Chip.swift; sourceTree = "<group>"; };
0FB794B125D1400C005AE438 /* Position.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -134,6 +138,7 @@
isa = PBXGroup;
children = (
0FB794B325D1400C005AE438 /* Solver.swift */,
0F640D7F25D2993400E9B0B9 /* TranspositionTable.swift */,
);
path = Solver;
sourceTree = "<group>";
Expand All @@ -160,6 +165,7 @@
0FB794C825D1402E005AE438 /* BenchmarkDataSetTests.swift */,
0FB794C925D1402E005AE438 /* SolverTests.swift */,
0FB794CA25D1402E005AE438 /* PositionTests.swift */,
0F640D8725D29CFA00E9B0B9 /* TranspositionTableTests.swift */,
0FB794CB25D1402E005AE438 /* Info.plist */,
);
path = Connect4Tests;
Expand Down Expand Up @@ -333,6 +339,7 @@
buildActionMask = 2147483647;
files = (
0FB794B925D1400C005AE438 /* Position.swift in Sources */,
0F640D8025D2993400E9B0B9 /* TranspositionTable.swift in Sources */,
0FB794B825D1400C005AE438 /* Chip.swift in Sources */,
0FB794BC25D1400C005AE438 /* BenchmarkDataSet.swift in Sources */,
0FB794BA25D1400C005AE438 /* Solver.swift in Sources */,
Expand All @@ -343,6 +350,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0F640D8825D29CFA00E9B0B9 /* TranspositionTableTests.swift in Sources */,
0FB794CE25D1402E005AE438 /* PositionTests.swift in Sources */,
0FB794CD25D1402E005AE438 /* SolverTests.swift in Sources */,
0FB794CC25D1402E005AE438 /* BenchmarkDataSetTests.swift in Sources */,
Expand Down
10 changes: 9 additions & 1 deletion Connect4.xcodeproj/xcshareddata/xcschemes/Connect4CLI.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,16 @@
<CommandLineArguments>
<CommandLineArgument
argument = "test-set 0"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "test-set 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "test-set 2"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "position 2252576253462244111563365343671351441"
isEnabled = "NO">
Expand All @@ -71,6 +75,10 @@
argument = "test-set 1 --weak"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "test-set 2 --weak"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "position 427566236745127177115664464254 --weak"
isEnabled = "NO">
Expand Down
2 changes: 1 addition & 1 deletion Connect4/Board/Position.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public struct Position {
public var numberOfMoves : Int

/// compact representation of a position on width*(height+1) bits.
private var key: UInt {
internal var key: UInt {
currentPosition + mask
}

Expand Down
31 changes: 29 additions & 2 deletions Connect4/Solver/Solver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,24 @@ public final class Solver {
/// column exploration order : center first, edge last
internal let columnOrder : [Int]

/// transposition table
private var transpositionTable : TranspositionTable

/// define min and max score
struct Score {
static let minScore = -(Position.Dimension.area) / 2 + 3;
static let maxScore = (Position.Dimension.area + 1) / 2 - 3;
}

/// public initializer
public init() {
// initialize the columnOrder array : center columns first and edge columns at the end
columnOrder = (0..<Position.Dimension.width).map { index in
Position.Dimension.width / 2 + (1 - 2 * (index % 2)) * (index + 1) / 2
}

//8388593 prime = 64MB of transposition table
transpositionTable = TranspositionTable(size: 8388593)
}

/**
Expand Down Expand Up @@ -66,7 +78,14 @@ public final class Solver {
}

// upper bound of our score as we cannot win immediately
let max = (Position.Dimension.area - 1 - position.numberOfMoves) / 2
var max = (Position.Dimension.area - 1 - position.numberOfMoves) / 2

// check into transposition table
let value = transpositionTable.get(key: position.key)
if value != 0 {
max = value + Score.minScore - 1
}

if (beta > max) {
// there is no need to keep beta above our max possible score.
beta = max
Expand Down Expand Up @@ -103,17 +122,25 @@ public final class Solver {
}
}

// save the upper bound of the position
transpositionTable.put(key: position.key, value: alpha - Score.minScore + 1)

return alpha;
}

/// reset solver to solve another position
public func reset() {
nodeCount = 0
transpositionTable.reset()
}

/**
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, weak: Bool = false) -> Int {
nodeCount = 0;
return weak ?
// Use a [-1;1] score window to look only for win/draw/loss
negamax(position: position, alpha: -1, beta: 1)
Expand Down
94 changes: 94 additions & 0 deletions Connect4/Solver/TranspositionTable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* This file is part of my swift adapation of Connect4 Game Solver
* <http://connect4.gamesolver.org> by Pascal Pons <[email protected]>
* Copyright (C) 2021 Francois Heuchamps
*
* Connect4 Game Solver is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Connect4 Game Solver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Connect4 Game Solver. If not, see <http://www.gnu.org/licenses/>.
*/

import Foundation

/**
* Transposition Table is a simple hash map with fixed storage size.
* In case of collision we keep the last entry and overide the previous one.
*
* We use 56-bit keys and 8-bit non-null values
*/
final internal class TranspositionTable {

private var table : UnsafeMutablePointer<UInt>
private var size : Int

/**
* Allocate and initialize to zero a mutable pointer.
* We don't use UnsafeMutableBufferPointer because we don't need collection protocol
* We'll store a 56 bit key in low bytes and an 8 bit value in high byte
* - parameter size: number of entries
*/
internal init(size: Int) {
self.size = size
table = UnsafeMutablePointer<UInt>.allocate(capacity: size)
table.initialize(repeating: .zero, count: size)
}

deinit {
table.deallocate()
}

/**
* Compute the index in the transition table for the given key.
*/
private func index(for key: UInt) -> Int {
Int(bitPattern: key) % size
}

/**
* Store a value for a given key
* - parameter key: 56-bit key
* - parameter value: non-null 8-bit value. null (0) value are used to encode missing data.
*/
internal func put(key: UInt, value: Int) {
assert(key < (UInt(1) << 56))
assert(value < (1 << 8))
assert(value > -(1 << 8))

let position = index(for: key)
let entryPointer = table.advanced(by: position)

entryPointer.pointee = (key + (UInt(bitPattern: value) << 56))
}

/**
* Get the value of a key
* - parameter key: 56-bit key
* - parameter value: 8-bit value associated with the key if present, 0 otherwise.
*/
internal func get(key: UInt) -> Int {
assert(key < (UInt(1) << 56))

let position = index(for: key)
let entry = table.advanced(by: position).pointee

guard entry & ((1 << 56) - 1) == key else { return 0 }

return Int(bitPattern: (entry >> 56))
}

/**
* Empty the Transition Table.
*/
internal func reset() {
table.assign(repeating: .zero, count: size)
}
}
2 changes: 2 additions & 0 deletions Connect4CLI/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ extension Connect4Solver {
totalNode += solver.nodeCount

os_signpost(.end, log: pointsOfInterest, name: "Solver", "nodes %d", solver.nodeCount)

solver.reset()
}

print ("duration : \(totalDuration)s, explored \(totalNode) nodes, average nodes \(Double(totalNode) / Double(testSet.count))")
Expand Down
39 changes: 39 additions & 0 deletions Connect4Tests/TranspositionTableTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// TranspositionTableTests.swift
// Connect4Tests
//
// Created by Francois on 09/02/2021.
//

import XCTest
@testable import Connect4

class TranspositionTableTests: XCTestCase {

func testInit() throws {
let table = TranspositionTable(size: 97)
XCTAssertEqual(table.get(key: 42), 0)
}

func testPutAndGet() throws {
let table = TranspositionTable(size: 97)

table.put(key:42, value: 1)
table.put(key:96, value: 2)

XCTAssertEqual(table.get(key: 42), 1)
XCTAssertEqual(table.get(key: 96), 2)

table.put(key: 42 + 97, value: 3)
XCTAssertEqual(table.get(key: 42), 0)
XCTAssertEqual(table.get(key: 42 + 97), 3)
}

func testReset() throws {
let table = TranspositionTable(size: 97)
table.put(key:42, value: 1)
table.reset()

XCTAssertEqual(table.get(key: 42), 0)
}
}

0 comments on commit 9704dc9

Please sign in to comment.