March 2022: A (Not So) Quick Foray into Kotlin 2: Electric Boogaloo

Week 4 (Part 1)

Yep, it's one of those months again. I could cry. I still might. Let's see how this goes. This time around, I haven't just shamelessly plagiarised code from someone smarter than me, so I wanna go through what I did using actual code snippets (Hooray!). But before this, I want to recount a few experiences I've had with javascript a couple of years back.

So using javascript feels very similar to what I'm trying to do now, just based off the fact that I'm fighting to get an interface to render in a language I don't know well. Javascript projects have some sort of HTML file attached that are fed into the browser, mainly so the routing doesn't go wonky, but that also means that technically one could create GUI instances in the HTML file and reference them in the JS file later down the line. I never did that, because I thought that was very confusing and absolute cancer to read. A lot of that comes down to this quirk, where instead of handing the HTML object an id, you can just reuse later, you'll first have to call a getElementById function, and feed that into a variable.

Kotlin similarly uses something called a "View", which basically functions like a grid to stick the individual GUI elements onto, and then pile stuff as high on those elements as time goes on. I think it's very easy to see how uncomfortable I still am with the language just judging from the fact that I'm trying to avoid string operations like the plague. Below is the onCreate() function as of now, meaning this is what the app does whenever it starts up. I had trouble fetching the idea off the XML, so I had to go back to my old tricks and add them in programmatically.

It's technically pretty simple, just a for loop iterating over a numbered fields, that I'll give the number as an id and slot into the rows, so they build a three by three square, and it took me forever. Initially I had tried writing these in the XML, but I had trouble getting back variable handles for my Buttons, so this will have to do. I don't think I'll try too much new stuff while still trying to come to grips with this, the XML, styles and all, and the gradle.

class MainActivity : AppCompatActivity() {

private var empty_fields = mutableListOf<Button>()

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

    val top: LinearLayout = findViewById(R.id.Top)
    val mid: LinearLayout = findViewById(R.id.Mid)
    val bot: LinearLayout = findViewById(R.id.Bot)

    for (i in 1..9) {
        val b = Button(this)
        b.setText("")
        b.setId(i)
        b.setOnClickListener {
            onFieldClicked(b, "x")
        }

        if (i in intArrayOf(1,2,3)) {
            top.addView(b)
        }
        else if (i in intArrayOf(4,563)) {
            mid.addView(b)
        }
        if (i in intArrayOf(7,8,9)) {
            bot.addView(b)
        }
        empty_fields.add(b)
    }
}

As it's laid out, the player will always play as the cross, and the computer will always play as the circle. For the purpose of simplicity, it's also "X" and "O", because I'm not about to download graphics for this. The player also goes first. Every time a player has made a move, the computer will randomly pick an element from the empty_fields array and make its move there. After that, the player will presumably have ample time to make the next move. I think at least. I might set a win condition, I might not. That's just set dressing at that point and I might add that stuff down the line, but I'm not expecting to get to that this month.

fun pc_turn() {
    val pc_pick = empty_fields.random()
    onFieldClicked(findViewById(pc_pick, "O"))
}

private fun onFieldClicked(b_id: Button, mark: String) {
    if (b_id.text == "") {
        b_id.text = mark
        empty_fields.remove(b_id)
    }
}

Either way, I decided to test out what I had so far.

Okay, I'm not entirely disappointed, I guess. I haven't written button styles yet and I have a very forgiving opponent (he seems to be sleeping or otherwise indisposed), but it tells me it's a Tic Tac Toe game, and it takes my input at least. Thinking about my logic, I might have forgotten to tell the listener function for the buttons to let the computer have a turn after the player had a go. Either way, this isn't the worst thing I've made, and I imagine this could've been quite painless, had I known some of the kotlin quirks and conventions beforehand. It's almost like I could've maybe gone a few rounds on hackerrank before I decided to build something from scratch. I'm pleasantly surprised.

Week 4 (Part II)

So the first thing I wanted to do, was have the computer fight back a little. Tic Tac Toe is already an easy game, no need to deprive the opponent of any more means. This was as easy a function call at the bottom of the onFieldClicked function. Then, I thought the interface should ideally be less off-center and look a little less like a form for phone numbers. Cal used a the xml stylesheet for this, but I couldn't find a way to do that quickly, so the API method it is.

var width = metrics.widthPixels

val b = Button(this)
b.setText("")
b.setId(i)
b.setOnClickListener {
    onFieldClicked(b, "x")
    pc_turn()
}
b.width = width / 3
b.height = width / 3

I had to take a walk for this one, so clearly I haven't done this enough, not even in python. I somebody asked me, how to describe a tic tac toe board, I'd tell them that it's nine squares, keyword here being square. I was thinking about how I was going to get the button height correct. The width seemed very immediately apparent: Take up a third of the screen, more or less. So if it's a square, Maybe give it the same height, instead of trying to get dimensions of the parent layout. Either way, this is how it looks now.

Still basic, but at least it looks like it wasn't an accident. So next is win states. I wasn't sure I wanted to include this, mainly because this part specifically is just a string of if/else blocks that aren't very helpful for my understanding the language. Turns out, I needed it for any other feature such as clearing the board. I could have added a button for that, but I'm fairly sure that squarely qualifies as bad design. So win states it is. Here's how I went over it.

I checked the diagonal for the symbols and checked every win condition from there. This way the code aborts very quickly, if it not all necessary squares have been ticked and it samples for the winning symbol. You'll still have to check for wins 9 times.

private fun checkWins() {
    var res: TextView = findViewById<TextView>(R.id.resultsTV)
    val topmark = all_fields[0].text
    val midmark = all_fields[4].text
    val botmark = all_fields[8].text

    if (topmark != "") {
        if ((all_fields[1].text == topmark && all_fields[2].text == topmark) ||
            (all_fields[3].text == topmark && all_fields[6].text == topmark) ||
            (midmark == topmark && topmark == botmark)) {
            if (topmark == "x") {
                res.text = "You Won!"
            }
            else {
                res.text = "You Lost!"
            }
            running = false
        }
    }

    if (midmark != "") {
        if ((all_fields[3].text == midmark && all_fields[5].text == midmark) ||
            (all_fields[1].text == midmark && all_fields[7].text == midmark) ||
            (all_fields[2].text == midmark && all_fields[6].text == midmark)) {
            if (midmark == "x") {
                res.text = "You Won!"
            }
            else {
                res.text = "You Lost!"
            }
            running = false
        }
    }

    if (botmark != "") {
        if ((all_fields[2].text == botmark && all_fields[5].text == botmark) ||
            (all_fields[7].text == botmark && all_fields[6].text == botmark)) {
            if (botmark == "x") {
                res.text = "You Won!"
            }
            else {
                res.text = "You Lost!"
            }
            running = false
        }
    }
}

The running variable tells us whether the game is running or not, so when the game ends, the game restarts itself. With an extra day or two, this didn't turn out too bad. Honestly, now that I've gotten somewhere, I can imagine keeping this one in my arsenal for the future. I might even migrate one of my WIP python projects to kotlin. I still have to look around for the API, but it's decidedly less fussy than something like PyQt. Either way, starting next week, I wanna take a break from posting code, and maybe do something more creativity-heavy again.

If you wanna try out the app, or in desperate need of a round of Tic Tac Toe, I hope the filesharing link works: APK File

Previous
Previous

April 2022: Learning Shogi

Next
Next

February 2022: A (Not So) Quick Foray into Kotlin (Part 1)