Home > other >  Why add method overwrites all items in array list
Why add method overwrites all items in array list

Time:09-12

I have board for game tic-tac-toe. When I'm tryna add new board to list of boards it's always overwrites

fun String.replaceUnusualSymbols() =
    replace(", ", "").replace("[", "").replace("]", "")

fun Array<Array<Char>>.printBoard(board: Array<Array<Char>>) = indices.forEach {
    println("|${board[it].contentToString().replaceUnusualSymbols()}|")
}

fun main() {
    var board = Array(3) { Array(3) { ' ' } }
    val game: ArrayList<Array<Array<Char>>> = arrayListOf()
    board[0][0] = 'X'
    game.add(board) // add first board
    board[1][1] = 'X'
    game.add(board) // add second board
    println(game.size)
    game.forEach {
        it.printBoard(board) // but outputs always second board
    }
}

How to fix this?

CodePudding user response:

There are two problems.

  1. Array is a mutable collection. Since you only created one var board, there's only once instance - which is mutated every time you do board[0][0] or board[0][1].
  2. printBoard() isn't printing each board, it's only printing a single board.

Copying the mutable Array

You can try copying the board every time you want to add the current board state to game.

Note that because board is a nested array, just doing board.copyOf() isn't enough - this won't copy recursively. So we have to specifically copy each row of board as well.

Because map {} returns a List and board is an Array, we have to convert the copied result using toTypedArray().

  board[0][0] = 'X'
  game.add(board.map { row -> row.copyOf() }.toTypedArray()) // add first board

  board[1][1] = '0'
  game.add(board.map { row -> row.copyOf() }.toTypedArray()) // add second board
}

What we end up with is something that's pretty clunky and verbose. We can try making an extension function for the copy, but then we'll still have the risk involved with mutable collections. What happens if at some point you forget to copy the board?

To keep this answer short and focused, I'll leave refactoring to use immutable collections as an exercise for the reader, with some hints:

  • How can you use a class to encapsulate the game logic?
  • Instead of using a List<List<Char>>, can you create a data class?
  • Can you create a function that both 1. adds a new symbol to the board, and 2. saves the current game state, in one go?

printBoard()

printBoard() is an extension function has both a receiver and an argument of type Array<Array<Char>>, which is unusual.

//      receiver
//         ↓
fun Array<Array<Char>>.printBoard(board: Array<Array<Char>>) 
//                                          ↑     
//                                       argument

This is causing one bug. When you call it.printBoard(board), it will use the indices of it, but it will print the values of board.

//                                        the indices come from the receiver
//                                                                ↓
fun Array<Array<Char>>.printBoard(board: Array<Array<Char>>) = indices.forEach {
    println("|${board[it].contentToString().replaceUnusualSymbols()}|")
} //              ↑     
//   the values come from the argument

So when you use it in a loop like this

    game.forEach {
        it.printBoard(board)
    }

it won't print each board.

Here's a fixed version that will only print the receiver.

fun Array<Array<Char>>.printBoard() = indices.forEach {
  println("|${this[it].contentToString().replaceUnusualSymbols()}|")
}

Replace replaceUnusualSymbols() with joinToString()

And I'll go one step further - we can get rid of contentToString() and replaceUnusualSymbols() and use the Kotlin stdlib function joinToString():

fun Array<Array<Char>>.printBoard() {
  val boardString = joinToString(
    separator = "\n",
    prefix = "\n",
  ) { row ->
    row.joinToString(" ", prefix = "|", postfix = "|")
  }

  println(boardString)
}
  • Related