Swift for loop is creating new objects - swift

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.

Related

Verify and swap elements swift

Sorry for the beginner question and my bad english.
I need to make a function in my class Team that will check my array to see if the player exists, if doesn't, it will return an error, if it exists, it will make an swap between the two array.
Example: I have an array with starters and reserve players, players 1,2 and 3 are starters, and players 4,5 and 6 are substitutes.
so when I want to do a player swap, I can use something like: Team.swapfunc(player1, player4) or something similar
My example code:
class SoccerPlayers {
let name: String
let number: Int
init(name: String, number: Int) {
self.name = name
self.number = number
}
}
class Team {
var nameTeam: String
// Array of players
var startingPlayers:[SoccerPlayers] = []
var reservePlayers:[SoccerPlayers] = []
init(nameTeam:String, startingPlayers: [SoccerPlayers], reservePlayers: [SoccerPlayers]) {
self.nameTeam = nameTeam
self.startingPlayers = startingPlayers
self.reservePlayers = reservePlayers
}
}
var player1: SoccerPlayers = SoccerPlayers(
name: "Andre",
number: 1
)
var player2: SoccerPlayers = SoccerPlayers(
name: "Joao",
number: 2
)
var player3: SoccerPlayers = SoccerPlayers(
name: "Matheus",
number: 3
)
var player4: SoccerPlayers = SoccerPlayers(
name: "Junior",
number: 4
)
var player5: SoccerPlayers = SoccerPlayers(
name: "Fabio",
number: 5
)
var player6: SoccerPlayers = SoccerPlayers(
name: "Paulo",
number: 6
)
let team1 = Team(nameTeam:"YourTeam", startingPlayers:[player1, player2, player3], reservePlayers:[player4, player5, player6])
}
obs: this code is only for study, not a real working project
Let's break this down, what we need to do is to
Check if players exists in the corresponding array
Remove them from the array
Insert them into the other array
To check if a player exist we want to be able to compare two players, we can either do that manually or make the player type correspond to Equatable and also decide what makes two player equal, here I decided that it is the number that uniquely defines a player so we are using that.
struct SoccerPlayer: Equatable { // Conform to Equatable
let name: String
let number: Int
// This function will automatically be used whenever some other function want's to check if two players are equal
static func == (lhs: SoccerPlayer, rhs: SoccerPlayer) {
lhs.number == rhs.number
}
}
Note that I have changed from class to struct because that is what is recommended for this kind of data/functionality. I also change the name to SoccerPlayer since each instance holds a single player
In the swap function we need to check for a player and then remove it from the array, looking at the Array type we see it has a remove function that takes an index (position in the array) as an argument so we want to get the index of each player first.
The function firstIndex(of:) is perfect here since it returns an index or nil if nothing was found so we can also use it for our validation.
let playerIndex = startingPlayers.firstIndex(of: player)
here playerIndex will be nil if the array doesn't contain the player, also not that pass the player as argument to the function and this can be done since it conforms to Equatable
To perform the check and get the result in one go we use a guard statement. And once we have the index we can remove the players from one array and add them to the other. Here is the full swap function that belongs to Team
mutating func swap(player: SoccerPlayer, substitute: SoccerPlayer) -> Bool {
guard let playerIndex = startingPlayers.firstIndex(of: player),
let substituteIndex = reservePlayers.firstIndex(of: substitute) else {
return false
}
// Use the indices to remove players
startingPlayers.remove(at: playerIndex)
reservePlayers.remove(at: substituteIndex)
// Add them to the other array
startingPlayers.append(substitute)
reservePlayers.append(player)
return true
}
Note that I have also changed Team to a struct and that the mutating keyword is used to tell the compiler we are updating the struct.
I didn't really understand what you meant by returning an error so I simply return a boolean instead.
As an extra check you could test that each player doesn't exists in the array we are going to add it to

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)
}

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?

Swift SpriteKit use struct instead of class to render sprites

I have been updating my game recently to use more value types. I am still not 100% confident with weak and unowned in some cases so I went the struct way to avoid strong reference cycles. As per apples newer keynotes it seems value types are they way to go for the most part anyway.
I have never seen an example where structs are used to render sprites in a spriteKit game so I wonder what the drawbacks are.
I understand that they are copied and not referenced but for my usage it seems to work.
So basically is there something I need to watch out for when doing this
struct Flag {
let post: SKSpriteNode
let flag: SKSpriteNode
init(postImage: String, flagImage: String) {
post = SKSpriteNode(imageNamed: postImage)
// other set ups for post sprite
flag = SKSpriteNode(imageNamed: flagImage)
// other set ups for flag sprite
post.addChild(flag)
}
func animate() {
// code to animate flag
}
}
Than in my SKScenes I simply add them as usual
let flag = Flag(postImage: "FlagPostImage", flagImage: "FlagImage")
flag.post.position = ...
addChild(flag.post)
flag.animate()
Now even if I create multiple flags in the same scene I seem to have no problems with this way.
I am just curious because I have never really seen an example like this so I wonder if I am missing something, like performance drawbacks etc.
Thanks for any help.
Personally I avoid creating Structs that contain Classes. Because Structs copy, each and every copy that get's passed around your app will increase the reference count of the Classes. This makes it harder to manage them instead of easier.
It is also useful to take a look at how UIKit uses Structs. A UIView is an object but has many defining properties that are Structs. For example it's frame.
Drop the code below in a playground to see some effects of this behaviour.
The protocol is just to get some meaningful feedback form the playground.
protocol IDLookable : CustomPlaygroundQuickLookable {
var id : Int { get set }
}
extension IDLookable {
func customPlaygroundQuickLook() -> PlaygroundQuickLook {
return PlaygroundQuickLook.AttributedString(NSAttributedString(string: "\(self.dynamicType) with id : \(self.id)"))
}
}
class MyClass : IDLookable {
var id : Int = 0
init(id : Int) {
self.id = id
}
}
struct MyContainerStruct : IDLookable {
var id : Int = 0
var object : MyClass
init(id : Int, object:MyClass) {
self.id = id
self.object = object
}
}
class Scope {
// ref count = 1
var object = MyClass(id: 11)
var structContainer : MyContainerStruct
init() {
// ref count = 2
structContainer = MyContainerStruct(id: 222, object: object)
messWithAClassInAStruct()
}
func messWithAClassInAStruct() {
// ref count = 3
var structContainerTwo = structContainer
structContainerTwo.id = 333
structContainerTwo.object // 11
// altering the object in one struct will obvously update all references
structContainerTwo.object.id = 1
structContainer.object // 1
structContainerTwo.object // 1
}
}
let test = Scope()
One pattern that does make it easy to work with Reference Types in Value Types is to store them as weak optionals in the Value Types. This means that something will need to have a strong reference but chances are that some Class will be responsible for creating the Structs this is a good place to keep that strong reference.
struct MyContainerStruct : IDLookable {
var id : Int = 0
weak var object : MyClass?
init(id : Int, object:MyClass) {
self.id = id
self.object = object
}
}
class Scope {
// ref count = 1
var object = MyClass(id: 11)
var structContainer : MyContainerStruct
init() {
// ref count = 1
structContainer = MyContainerStruct(id: 222, object: object)
messWithAClassInAStruct()
}
func messWithAClassInAStruct() {
// ref count = 1
var structContainerTwo = structContainer
structContainerTwo.id = 333
structContainerTwo.object // 11
}
}
let test = Scope()

Swift variable value back to original after loop

I have this simple code:
for index in 0..<people.count {
var person = people[index]
var home = homes[index]
person.home = home
println("\(person.home)")
}
for index in 0..<people.count {
var person = people[index]
println("\(person.home)")
}
Person:
struct Person: Deserializable {
var home: Home?
init() { }
init(data: [String : AnyObject]) {
home <-- data["home"]
}
}
In the println statement in first loop, it assigns the home to it's respective person and prints the proper home.
In the second loop, it prints nil (back to normal). It's almost as if the first loop has no effect.
I have no idea how to debug this. Please help
You do not give any information about what person is. But suppose it is a struct. Then I would not expect there to be any effect on the people array's persons, because the person in var person = people[index] is a copy.
So in that case you would need an extra line in your first loop, writing the altered person back into the array:
people[index] = person