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.
Array
is a mutable collection. Since you only created onevar board
, there's only once instance - which is mutated every time you doboard[0][0]
orboard[0][1]
.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 adata 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)
}