Home > OS >  Read and store game state as CSV
Read and store game state as CSV

Time:06-15

Thanks to the great help from Tenfour04, I've got wonderful code for handling CSV files.

However, I am in trouble like followings.

  1. How to call these functions?
  2. How to initialize 2-dimensional array variables?

Below is the code that finally worked.

MainActivity.kt

package com.surlofia.csv_tenfour04_1

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import java.io.File
import java.io.IOException
import com.surlofia.csv_tenfour04_1.databinding.ActivityMainBinding

var chk_Q_Num: MutableList<Int> = mutableListOf  (
    0,
    1, 2, 3, 4, 5,
    6, 7, 8, 9, 10,
    11, 12, 13, 14, 15,
    16, 17, 18, 19, 20,
)

var chk_Q_State: MutableList<String> = mutableListOf  (
    "z",
    "a", "b", "c", "d", "e",
    "f", "g", "h", "i", "j"
)


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // setContentView(R.layout.activity_main)

        binding = ActivityMainBinding.inflate(layoutInflater)

        val view = binding.root

        setContentView(view)

        // Load saved data at game startup. It will be invalid if performed by other activities.

        val filePath = filesDir.path   "/chk_Q.csv"
        val file = File(filePath)

        binding.fileExists.text = isFileExists(file).toString()

        if (isFileExists(file)) {
            val csvIN = file.readAsCSV()

            for (i in 0 .. 10) {
                chk_Q_Num[i] = csvIN[i][0].toInt()
                chk_Q_State[i] = csvIN[i][1]
            }

        }

        // Game Program Run


        val csvOUT = mutableListOf(
            mutableListOf("0","OK"),
            mutableListOf("1","OK"),
            mutableListOf("2","OK"),
            mutableListOf("3","Not yet"),
            mutableListOf("4","Not yet"),
            mutableListOf("5","Not yet"),
            mutableListOf("6","Not yet"),
            mutableListOf("7","Not yet"),
            mutableListOf("8","Not yet"),
            mutableListOf("9","Not yet"),
            mutableListOf("10","Not yet")
        )

        var tempString = ""

        for (i in 0 .. 10) {
            csvOUT[i][0] = chk_Q_Num[i].toString()
            csvOUT[i][1] = "OK"

            tempString = tempString   csvOUT[i][0]   "-->"   csvOUT[i][1]   "\n"
        }

        binding.readFile.text = tempString

        // and save Data
        file.writeAsCSV(csvOUT)


    }


    // https://www.techiedelight.com/ja/check-if-a-file-exists-in-kotlin/

    private fun isFileExists(file: File): Boolean {
        return file.exists() && !file.isDirectory
    }

    @Throws(IOException::class)
    fun File.readAsCSV(): List<List<String>> {
        val splitLines = mutableListOf<List<String>>()
        forEachLine {
            splitLines  = it.split(", ")
        }
        return splitLines
    }

    @Throws(IOException::class)
    fun File.writeAsCSV(values: List<List<String>>) {
        val csv = values.joinToString("\n") { line -> line.joinToString(", ") }
        writeText(csv)
    }
}

chk_Q.csv

0,0
1,OK
2,OK
3,Not yet
4,Not yet
5,Not yet
6,Not yet
7,Not yet
8,Not yet
9,Not yet
10,Not yet

1. How to call these functions?

The code below seems work well. Did I call these funtions in right way? Or are there better ways to achieve this?

read

if (isFileExists(file)) {
    val csvIN = file.readAsCSV()

    for (i in 0 .. 10) {
        chk_Q_Num[i] = csvIN[i][0].toInt()
        chk_Q_State[i] = csvIN[i][1]
    }
}

write

file.writeAsCSV(csvOUT)

2. How to initialize 2-dimensional array variables?

val csvOUT = mutableListOf(
    mutableListOf("0","OK"),
    mutableListOf("1","OK"),
    mutableListOf("2","OK"),
    mutableListOf("3","Not yet"),
    mutableListOf("4","Not yet"),
    mutableListOf("5","Not yet"),
    mutableListOf("6","Not yet"),
    mutableListOf("7","Not yet"),
    mutableListOf("8","Not yet"),
    mutableListOf("9","Not yet"),
    mutableListOf("10","Not yet")
)

I would like to know the clever way to use a for loop instead of writing specific values one by one.

For example, something like bellow.

val csvOUT = mutableListOf(mutableListOf())
for (i in 0 .. 10) {
    csvOUT[i][0] = i
    csvOUT[i][1] = "OK"
}

But this gave me the following error message:

Not enough information to infer type variable T

It would be great if you could provide an example of how to execute this for beginners.


----- Added on June 15, 2022. ----- [Question 1] Regarding initialization, I got an error "keep stopping" when I executed the following code. The application is forced to terminate. Why is this?

val csvOUT: MutableList<MutableList<String>> = mutableListOf(mutableListOf())
    for (i in 0 .. 10) {
        csvOUT[i][0] = "$i"
        csvOUT[i][1] = "OK"
    }

----- Added on June 15, 2022. ----- [Question 2] Regarding instant(= 2022-06-14T17:03:09.936190Z) and (= levelState[i].timeBeaten), I got an error "keep stopping" when I executed the following code. The application is forced to terminate. Why is this?

    file.writeAsCSV(levelState)


    var tempString = ""

    for (i in 0 .. 10) {
        csvOUT[i][0] = chk_Q_Num[i].toString()
        levelState[i].state = "Not Yet"
        csvOUT[i][1] = levelState[i].state

        // When the next line is deleted it works fine.
        // There is no error message and it stops when executed.
        csvOUT[i][2] = levelState[i].timeBeaten.toString()

        tempString = tempString  
                csvOUT[i][0]   "-->"  
                csvOUT[i][1]   "-->"  
                csvOUT[i][2]   "\n"
    }

    binding.readFile.text = tempString

CodePudding user response:

In my opinion there are basically two parts to your question. First you need an understanding of the Kotlin type system including generics. Secondly you want some knowledge about approaches to the problem at hand.

type-system and generics

The function mutableListOf you're using is generic and thus needs a single type parameter T, as can be seen by definition its taken from the documentation:

fun <T> mutableListOf(): MutableList<T>

Most of the time the Kotlin compiler is quite good at type-inference, that is guessing the type used based on the context. For example, I do not need to provide a type explicitly in the following example, because the Kotlin compiler can infer the type from the usage context.

val listWithInts = mutableListOf(3, 7)

The infered type is MutableList<Int>. However, sometimes this might not be what one desires. For example, I might want to allow null values in my list above. To achieve this, I have to tell the compiler that it should not only allow Int values to the list but also null values, widening the type from Int to Int?. I can achieve this in at least two ways.

  1. providing a generic type parameter
val listWithNullableInts = mutableListOf<Int?>(3, 7)
  1. defining the expected return type explicitly
val listWithNullableInts: MutableList<Int?> = mutableListOf(3, 7)

In your case the compiler does NOT have enough information to infer the type from the usage context. Thus you either have to provide it that context, e.g. by passing values of a specific type to the function or using one of the two options named above.

initialization of multidimensional arrays

There are questions and answers on creating multi-dimensional arrays in Kotlin on StackOverflow already.

One solution to your problem at hand might be the following.

val csvOUT: MutableList<MutableList<String>> = mutableListOf(mutableListOf())

for (i in 0 .. 10) {
    csvOUT[i][0] = "$i"
    csvOUT[i][1] = "OK"
}

You help the Kotlin compiler by defining the expected return type explicitly and then add the values as Strings to your 2D list.

If the dimensions are fixed, you might want to use fixed-size Arrays instead.

val csvArray = Array(11) { index -> arrayOf("$index", "OK") }

In both solutions you convert the Int index to a String however. If the only information you want to store for each level is a String, you might as well use a simple List<String and use the index of each entry as the level number, e.g.:

val csvOut = List(11) { "OK" }
val levelThree = csvOut[2] // first index of List is 0 

This would also work with more complicated data structures instead of Strings. You simply would have to adjust your fun File.writeAsCSV(values: List<List<String>>) to accept a different type as the values parameter. Assume a simple data class you might end up with something along the lines of:

data class LevelState(val state: String, val timeBeaten: Instant?)

val levelState = List(11) { LevelState("OK", Instant.now()) }

fun File.writeAsCSV(values: List<LevelState>) {
    val csvString = values
        .mapIndexed { index, levelState -> "$index, ${levelState.state}, ${levelState.timeBeaten}" }
        .joinToString("\n")
    
    writeText(csvString)
}
  • Related