Why is my minimax algorithm not undoing every move? - swift

I am creating an AI for an original 4-player board game that I made.
Details about the board game:
4 players take turns to move their coloured pieces simultaneously in one of the four cardinal directions. Pieces can be moved off the board. Players each have 5 lives at the start. For each piece moved off the board, the player lose 1 life. New pieces will spawn deterministically throughout the game.
I looked up how to do a minimax algorithm and found this. I read through that and thought I understood everything, so I tried to translate the Java code in Section 1.5 to Swift.
Here is my thought process:
Since my game has 4 players, I would treat everyone else as minimising players.
In the Java code, there is a line where the move is undone. Since my game's game state can change drastically in each move, I would just store all the game states in an array, and when something needs to be undone, I can just call dropLast on the array.
Since a move in my game is represented as a Direction enum, I will return a (Int, Direction) tuple instead if an int array like the Java code.
game is a computed property that just returns gameStates.last!
game.currentPlayer will change every time I call one of the moveUp/Down/Left/Right methods on game, so I don't need to write extra code to decide who's the next player.
In the last line, I need to return (bestScore, bestDirection), but I realised sometimes bestDirection is not assigned. Therefore, I made bestDirection an optional. If it is not assigned at the return statement, I'll just return an arbitrary direction.
And here is my attempt:
private func minimax(depth: Int, color: Color) -> (score: Int, direction: Direction) {
var bestScore = color == myColor ? Int.min : Int.max
var currentScore: Int
var bestDirection: Direction?
if game.players.filter({$0.lives > 0}).count < 2 || depth == 0 {
// This is a call to my heuristic evaluation function
bestScore = evaluateHeuristics()
} else {
// if the player has no pieces on the board, just move up since moving in any direction won't change anything
for move in (game.board.indicesOf(color: color).count == 0 ? [Direction.up] : [Direction.up, .down, .left, .right]) {
let gameCopy = game.createCopy()
switch move {
case .up: gameCopy.moveUp()
case .down: gameCopy.moveDown()
case .left: gameCopy.moveLeft()
case .right: gameCopy.moveRight()
}
gameStates.append(gameCopy)
// myColor is like mySeed in the original Java code
if color == myColor {
currentScore = minimax(depth: depth - 1, color: game.currentPlayer.color).score
if currentScore > bestScore {
bestScore = currentScore
bestDirection = move
}
} else {
currentScore = minimax(depth: depth - 1, color: game.currentPlayer.color).score
if currentScore < bestScore {
bestScore = currentScore
bestDirection = move
}
}
_ = gameStates.dropLast()
}
}
return (bestScore, bestDirection ?? .left)
}
When I test out this AI with a depth of 4, it seems to either do stupid moves, like moving his pieces off the board, or move his pieces in one direction only.
I also noticed that gameStates has a length of about 90 when the recursive call returns. Normally it should be 1 right? Because all the moves the AI tried should have been undone by the time the recursive call returns, and gameStates will only contain the initial state.
What did I do wrong?

dropLast() returns an array slice containing all but the last element of the array. It does not modify the original array. Use removeLast()
Edit
What you really want is a stack data structure. Here's one.
public struct Stack<Element>
{
fileprivate var elements: [Element] = []
public init() {}
/// Push an element onto the top of the stack
///
/// - parameter newElement: The element to push
public mutating func push(_ newElement: Element)
{
elements.append(newElement)
}
/// Pops the top element off the stack
///
/// - returns: The top element or nil if the stack is empty.
public mutating func pop() -> Element?
{
let ret = elements.last
if ret != nil
{
elements.removeLast()
}
return ret
}
/// The top element of the stack. Will be nil if the stack is empty
public var top: Element?
{
return elements.last
}
/// Number of items in the stack
public var count: Int
{
return elements.count
}
/// True if the stack is empty
public var isEmpty: Bool
{
return elements.isEmpty
}
}

Related

Swift for loop is creating new objects

I'm working on a game where I store my player objects in an array. I want to give these players a hand of cards and I do so in the following way:
var players = [Player]()
mutating func deal(count: Int) {
for var player in players {
for _ in 1...count {
if let card = deck.draw(){
player.addCard(card: card)
}
}
if (player.id==1){print(player)}
}
print(players[0])
}
struct Player{
var id : Int
private(set) var randomValue: Int = 0
private(set) var hand = [PlayingCard]()
init(id: Int) {
self.id = id
randomValue = 100.arc4random
}
mutating func addCard(card: PlayingCard){
hand.append(card)
}
}
The problem is the hand is not actually being updated. Printing the player at index 0 (player.id == 1) inside the for loop gives me exactly what I expect. However, the print outside the loop has an empty hand and the random number is different. So I know the loop is creating a new object since init is called. Why is this happening and how can I edit the actual object in players array?
player is struct, when you iterate players - you get a copy of the players in the array.
You update it, print it and see the correct result, but it's scope is for the for loop only.
Either make player a class or update your array with the new copy of the player.

Constant Used Before Being Initialized

I'm converting a project to Swift 3 and I'm getting this error below:
fileprivate class func moveAi() -> DataModel {
let player = pathForPlayer(true)
let i: Int
let scope = GameModel.shared.scopeForPlayer(GameModel.shared.topPlayer.id, rival: GameModel.shared.downPlayer.id)
//for (i = player.count-1; i > 0; i -= 1) { // Original for loop prior to Swift 3
for i in stride(from: player.count-1, to: 0, by: -1) {
if scope.contains(player[i]) {
break
}
}
// Error is on this line
return DataModel.idConvertToPlayer(player[i], player: true)
}
// Path for Player
class func pathForPlayer(_ play: Bool) -> [Int] {
let player = play ? GameModel.shared.topPlayer.id : GameModel.shared.downPlayer.id
let end = play ? topEnd : downEnd
return pathForPlayer(Node(data: player, parent: -1), end: end)
}
If I move the return within the for loop the error goes away, but I still need to return a value here.
Any suggestions?
Updated Code:
fileprivate class func moveAi() -> DataModel {
let player = pathForPlayer(true)
let _: Int // i
var final: Int?
let scope = GameModel.shared.scopeForPlayer(GameModel.shared.topPlayer.id, rival: GameModel.shared.downPlayer.id)
**//for (i = player.count-1; i > 0; i -= 1)** {
for i in stride(from:player.count-1, to:0, by:-1) {
final = i
if scope.contains(player[i]) {
break
}
}
return DataModel.idConvertToPlayer(player[final!], player: true)
}
The above code does not provide the crash I was previously getting, but I am getting a new crash.
The best way to describe is my AI will prevent itself from finishing a game.
I'm building off the board game Quoridor. The AI will build walls around it's pawn so it can't move any further. This does not happen every game though. It's very random when it happens.
Could it be based on how I've written the for loop? Is the Swift way correct or could I get insight to how to right the commented for loop in Swift syntax?
Your loop gives i a value inside the loop, but not outside. After the loop ends, i has no value. If you want some value of i to be available after the loop finishes, you need to assign it to something that exists outside the loop.
One way would be to add this before the loop:
var final : Int?
Then in the loop, set final = i somewhere. After the loop finishes, the value will be in final.
Your function moveAi has 2 different things called i: An Int constant defined at the outer scope of the function and an inner constant that exists inside the scope of your for i in... loop. They are separate.
Inside the for loop, i hides the outer constant, and refers to the value extracted from your stride call.
Outside the for...in loop, that inner definition of i goes out of scope and no longer exists. Now you've got a constant that has never been initialized with a value.
You could rewrite your function like this:
fileprivate class func moveAi() -> DataModel {
let player = pathForPlayer(true)
var i: Int
let scope = GameModel.shared.scopeForPlayer(GameModel.shared.topPlayer.id, rival: GameModel.shared.downPlayer.id)
for index in stride(from: player.count-1, to: 0, by: -1) {
i = index
if scope.contains(player[i]) {
break
}
}
// Error is on this line
return DataModel.idConvertToPlayer(player[i], player: true)
}

Should I be using Classes or Structs for elements stored in a Swift Array

I would like to mutate a property in a Swift struct, stored within an array.
I have done a reassignment dance, but that it doesn't feel right.
I'm encouraged to use Struct's where possible, however this relatively simple use case (below) is pushing me towards using Classes (Reference Types).
Should I be using Classes for Game and/or Player?
Please find below a code sample .. with accompanying UnitTest
Test Summary
• Create a Game
• Create two Players
• Add both Players to Game
• Send message to Game to decrementPlayer
• Game iterates over collection (players)
• Finds player and sends message decrementScore
• Test Failed - Players' scores were not as expected (60 & 70 respectively)
struct Game {
fileprivate(set) var players = [Player]()
}
extension Game {
mutating func addPlayer(_ player: Player) {
players.append(player)
}
mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
for var player in players {
if player == decrementPlayer {
player.decrementScore(by: byScore)
}
}
}
}
struct Player {
var name: String
var score: Int
init(name: String, score: Int) {
self.name = name
self.score = score
}
mutating func decrementScore(by byScore: Int) {
self.score -= byScore
}
}
extension Player: Equatable {
public static func ==(lhs: Player, rhs: Player) -> Bool {
return lhs.name == rhs.name
}
}
class GameTests: XCTestCase {
var sut: Game!
func testDecrementingPlayerScores_isReflectedCorrectlyInGamePlayers() {
sut = Game()
let player1 = Player(name: "Ross", score: 100)
let player2 = Player(name: "Mary", score: 100)
sut.addPlayer(player1)
sut.addPlayer(player2)
XCTAssertEqual(2, sut.players.count) // Passes
sut.decrementPlayer(player1, byScore: 40)
sut.decrementPlayer(player2, byScore: 30)
XCTAssertEqual(60, sut.players[0].score) // Fails - score is 100 .. expecting 60
XCTAssertEqual(70, sut.players[1].score) // Fails - score is 100 .. expecting 70
}
}
I'm encouraged to use Struct's where possible
Yes, that is problematic. You should be encouraged to use structs where appropriate. Generally speaking, I find that structs aren't always as appropriate as fashion dictates.
Your problem here is that the for var player ... statement actually makes a mutable copy of each player as it iterates and amends the copy. If you want to stick with structs, you'll probably need to adopt a more functional approach.
mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
players = players.map {
return $0 == decrementPlayer ? $0.scoreDecrementedBy(by: byScore) : $0
}
}
Or a more traditional (and almost certainly more efficient) way would be to find the index of the player you want
mutating func decrementPlayer(_ decrementPlayer: Player, byScore: Int) {
if let index = players.index(of: decrementPlayer)
{
players[index].decrementScore(by: byScore)
}
}
I would like to mutate a property in a Swift struct, stored within an array. I have done a reassignment dance, but that it doesn't feel right.
Well, that's what you would have to do, since structs are not mutable in place. If you want to be able to mutate the object in place (within the array), you do need a class.
The question that should be asked when deciding between value types (structs) and reference types (classes) is if makes sense to have duplicate instances.
For example take number 5, which is an Int. You can copy it as many times, it's cheap (as structs are), and doesn't pose problems if copied.
Now let's consider a FileHandle. Can it be a struct? Yes. Should it be a struct? No. Making it a struct means the handle will get copied whenever passed as argument or stored as property. Meaning everyone will have a different reference to that handle (intentionally ignoring copy-on-write). If one reference holder decides to close the handle, this indirectly invalidate all the other FileHandle references, which will likely trigger unexpected behaviour.
In your particular case, I'd say to make Player, a class. I assume the player will have some UI effects also, so it makes sense to be a reference type. However, if Player is used only for statistical purposes, then make it a struct.

Can't modify struct property since "Function call returns immutable value"

I have a struct, called "enemy" with two properties:
struct enemy {
var hp = 0
var dam = 0
}
I have a function that rolls a dice (I know not the neatest way to do it but I'm emulating the real-life process), and I call that function in another function to decide enemy values during an enemy encounter.
My diceRoll function (I know this works since I use it elsewhere)
func diceRoll(roll: Int) {
outcome = roll
}
And finally the bit in question that's not working:
func decideEnemyStats() {
diceRoll(roll: Int(arc4random_uniform(6) + 1));
enemy().hp = outcome
}
I get this error:
cannot assign to property: function call returns immutable value
why?

Wait until property is true before continuing loop

Trying to do something I feel like should be simple but nothing I've tried so far works. I'm trying to prevent this loop from continuing until a property on my enemy is set to true.
My enemy node figures out a path to the player during the walk state. I don't want to iterate to the next enemy until the path has been calculated. My enemy node has a pathComplete node I set to true during the walk state.
This is executed on touch.
for node:AnyObject in self.children {
if node is EnemyNode {
let enemy = node as! EnemyNode
enemy.destination = coordinate
enemy.stateMachine.enterState(WalkingState)
}
}
If I understand what you want to do, then you should use the recursion instead the loop.
First you need to create some enemyNodeArray that will be contains objects you need.
Then you can make two functions like this:
func actionForObjectWithIndex(index: Int, completion block: (nextIndex: Int) -> Void) {
guard index >= 0 && index < enemyNodeArray.count else {
return
}
// do what you need with object in array like enemyNodeArray[index]...
...
// Then call completion
block(nextIndex: index + 1)
}
func makeActionWithIndex(index: Int) {
actionForObjectWithIndex(index, completion: {(nextIndex: Int) -> Void in
self.makeActionWithIndex(nextIndex)
})
}
and start use it like this:
if !enemyNodeArray.isEmpty {
makeActionWithIndex(0)
}
This algorithm will be take every object in an array, and perform certain actions with them, and it will move to the next item only after it has finished with the previous.