Skip to main content

Scramble app with Kannada words

 A simple scramble game - that was my original intention. But I needed that words to be in Kannada. 

 


 

  1. Get a word list - can be inside the code
  2. Scramble the list to get random words
  3. Select a random word
  4. Separate the letters of the word
  5. Now scramble the letters of the word
  6. Display the scrambled letters 
  7. Let the user click on each letter in order to unscramble it

Simple. Quite simple. Only difficulty in this case - our dear mother tongue which uses two unicode characters to display a compound letter like kaa, and uses more to display a ottakshara like kka kku etc. 

So if the letter is any of the vowel sounds or half letter, we need to add this the the existing a character in step 4. So basically the the list size will be lesser than the string length.

e.g.

ಶಾಲೆಯಲ್ಲಿ  must give us only four letters -  ಶಾ,   ಲೆ,  ಯ  and   ಲ್ಲಿ 

 

So here is the code in Kotlin

There are two kotlin files - one is viewmodel and the other is activity

package com.hegdeapps.scramble

import android.graphics.Color
import android.os.CountDownTimer
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.*
import kotlin.random.Random

class ScrViewModel: ViewModel() {
     var mTimeRem: MutableLiveData<Int>

    private var mOttakshara: Boolean = false
      var mCurrentWord: MutableLiveData<String>
    lateinit var timer:MutableLiveData<CountDownTimer>
    private val MyStringArray = arrayOf("ಅಡಗಿಸಿದೆನು","ಅಪಹರಿಸು","ಕಳುಹಿಸಬಹುದು","ಕಾಪಾಡಲಿ","ಕೂಗಿಕೊಂಡನು",
        "ಕೊರತೆಯಿಂದ","ಗಾಳಿಯಿಂದ","ಗೋಡೆಗಳಿಗೆ","ಚಿಂತಿಸಬೇಡ","ಜೊತೆಯಾಗಿ",
        "ಲಸಿಕೆ","ವಸ್ತುನಿಷ್ಠ","ಮಾವಿನತೋಪು","ಕಾರಭಾರಗಳು","ಕರ್ತವ್ಯನಿಷ್ಠ ","ಕಿರಾತಕ","ಕಿರುಕಾಣಿಕೆ","ಕುರುಹುಗಳನ್ನು",
        "ಕೂಗಾಟದಿಂದ","ಕಲ್ಮಶವಲ್ಲದ","ಕ್ರಮಬದ್ಧವಾಗಿರುವ","ಅಕ್ಕರೆಯಿಂದ","ಸಕ್ಕರೆಯಿಲ್ಲದ","ಕೊಕ್ಕರೆಯ","ಶುಕ್ರವಾರ",
        "ಚೊಕ್ಕವಾದ","ಬಾಗಿಲಿನೊಳಗೆ","ಬಗ್ಗುಬಡೆದನು","ಸಿಗ್ಗಿಲ್ಲದೆ","ಗಂಟಿಲನೊಳಗೆ","ಸುಂಟರಗಾಳಿ","ನೆಂಟರಿಗಾಗಿತ್ತು",
        "ಮಲ್ಲಿಗೆಯ","ಮಿಂಚಿನ","ಸಂಚಿನಿಂದ","ಕೊಂಚವಾದರೂ","ಲೇಖನಿಯ","ಪುಸ್ತಕದೊಳಗೆ","ಪವಿತ್ರವಾದ","ಅರ್ಪಿಸಿದರು",
        "ಸುಪ್ರತೀಕ","ಕರ್ಪೂರದ","ಬೆಳಗಿನ","ಮುಂಜಾವು","ಸಂಜೆಯಲ್ಲಿ","ರಸ್ತೆಯಲ್ಲಿ","ಬೆಳಕಿಲ್ಲದೆ","ಬೇಕಾಗಿಲ್ಲ","ಸಂತೆಯಲ್ಲಿ",
        "ಮಂದಿರದೊಳಗೆ","ಸುಂದರವಲ್ಲದ","ಕಂದರದೊಳಗಿಂದ",
        "ಮಂದಾರಕುಸುಮ","ಸರ್ಪಶಯನ","ವಿಪ್ರೋತ್ತಮ","ವಿಳಂಬವಾಯಿತು","ವೇಳೆಯಿಲ್ಲ","ಸಮಯವ್ಯರ್ಥ","ಅಸಮಾಧಾನ")
    init {
        mCurrentWord = MutableLiveData()
        mCurrentWord.value = ""
        mTimeRem = MutableLiveData()
        timer = MutableLiveData()
        startTimer()
    }
    private var mUnUsedWordList: ArrayList<String> = MyStringArray.toList() as ArrayList<String>
    /**
     * create a display a puzzle
     */
     fun createPuzzle() {

        val rnd: Int = Random(Calendar.getInstance().timeInMillis).nextInt(mUnUsedWordList.size)
        val word: String = mUnUsedWordList[rnd]
        mCurrentWord.value =  word
        mUnUsedWordList.remove(word)

    }
    /**
     * Split the word into characters
     * Note that vowel form like aakar etc will be another unicode. So ki infact will be two characters k+i.
     * That is taken care - codes for vowel parts are 0x0cbe to 0x0ccd
     * and that of am is 0x0c82 and aha is 0x0c83
     */
     fun getKannadaCharArray(word: String): List<String> {
        var chList = mutableListOf<String>()
        var prevString = ""
        var i = 0
        for(ch in word){
            var str:String
            if(mOttakshara){
                str = prevString.plus(ch)
                chList.set(i-1,str)
                mOttakshara =false;

            }else {
                if (ch.toInt() == 0x0ccd) {
                    mOttakshara = true
                    str = prevString.plus(ch)
                    chList.set(i-1, str)
                } else if (ch.toInt() == 0x0c82 ||
                    ch.toInt() == 0x0c83 ||
                    (ch.toInt() >= 0x0cbe && ch.toInt() <= 0x0ccc)
                ) {
                    str = prevString.plus(ch)
                    chList.set(i-1, str)

                } else {
                    str = ch.toString()
                    chList.add(i, str)
                    i++
                }
            }
            prevString = str
        }
        return chList as List<String>

    }

     fun startTimer() {
        val oneMinute:Long = 60*1000
        timer.value= object: CountDownTimer(oneMinute,1000){
            override fun onFinish() {
                mTimeRem?.value = -1

            }

            override fun onTick(millisUntilFinished: Long) {
                updateTime(millisUntilFinished)
            }

        }
        timer.value?.start()
    }

    private fun updateTime(m:Long) {
        val timeRem = (m )/1000.0
        mTimeRem?.value = timeRem.toInt()


    }

    override fun onCleared() {
        super.onCleared()
        timer.value?.cancel()
    }

    fun restartGame() {
        timer.value?.cancel()
        mUnUsedWordList.clear()
        mUnUsedWordList = MyStringArray.toList() as ArrayList<String>
        createPuzzle()
        startTimer()
     }
}
And mainactivity
package com.hegdeapps.scramble

import android.content.Context
import android.content.DialogInterface
import android.graphics.Color
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.CountDownTimer
import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.children
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.hegdeapps.scramble.databinding.ActivityMainBinding
import org.w3c.dom.Text

import kotlin.random.Random
//TODO how to pause timer when game not visible??
class MainActivity : AppCompatActivity(), View.OnClickListener {


    private lateinit var timeObserver: Observer<Int>
    //  private lateinit var timer:CountDownTimer
    private val MAX_CHILDREN: Int=7
    private lateinit var viewModel: ScrViewModel
    private var mScore: Int = 0
    private var mOttakshara: Boolean=false
    private lateinit var dataBinding: ActivityMainBinding
    private var mInputIndex: Int=0
   // private var mCurrentWord: String=""
    private var mLetterIndex: Int=0
    private lateinit  var mTimeRem: LiveData<Long>


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        dataBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)

       // dataBinding.setLifecycleOwner (this)
        viewModel = ViewModelProviders.of(this).get(ScrViewModel::class.java)
        val wordObserver = Observer<String> {
            newWord ->
                        displayPuzzle(viewModel.getKannadaCharArray(newWord))
        }
        viewModel.mCurrentWord.observe(this,wordObserver)
         timeObserver = Observer<Int>{
                newTime->if(newTime!=-1)
            dataBinding.timeLeft.setText(newTime.toString())
            else {
             viewModel.mTimeRem?.removeObserver(timeObserver)
             showQuizEndDialog()

         }
        }
        viewModel.mTimeRem?.observe(this,timeObserver)
        //viewModel.mTimeRem?.value = 100
      /*  viewModel.mTimeRem?.observe(this, Observer{

            time->if(time<=0)
                     showQuizEndDialog()
                  else
                    dataBinding.timeLeft.text = time.toString()
        })
*/

      //  viewModel.startTimer()
        viewModel.createPuzzle()
       // displayPuzzle()
        dataBinding.done.setOnClickListener(this)

    }

    /**
     * create a display a puzzle
     */
    private fun displayPuzzle( oldList:List<String>) {
       // startTimer()
       /* val rnd:Int= Random(Calendar.getInstance().timeInMillis).nextInt(mUnUsedWordList.size)
        val word: String = mUnUsedWordList[rnd]
        mCurrentWord = word
        mUnUsedWordList.remove(word)
        var chList:List<String> = getKannadaCharArray(word)
        chList =  chList.shuffled()*/
       // val chList = viewModel.createPuzzle()
        val chList = oldList.shuffled()
        var tvArray= Array<TextView?>(chList.size){ null}
        val letterLayout = dataBinding.inputletters
        for(i in 0..chList.size-1) {
            tvArray[i] = letterLayout.getChildAt(i) as TextView?
            tvArray[i]?.text = chList.get(i)
            tvArray[i]?.setOnClickListener {
                    view ->   val str = (view as TextView).text
                putStringInLetterBox(str,view )
            }


        }
        for(i in chList.size..MAX_CHILDREN) {
            letterLayout.getChildAt(i).visibility = View.GONE
        }


        letterLayout.refreshDrawableState()

        val tvArrayInput : Array<TextView?> = Array (chList.size){null}
        val letterBoxLayout = dataBinding.letterbox
        for(i in 0..chList.size-1) {
            tvArrayInput[i] = letterBoxLayout.getChildAt(i) as TextView?
            tvArrayInput[i]?.text = ""
            tvArrayInput[i]?.setOnClickListener {
              v ->  val str = (v as TextView).text
                putStringInInputBox(str,v)
            }
        }
        for(i in chList.size..MAX_CHILDREN) {
            letterBoxLayout.getChildAt(i)?.visibility = View.GONE
        }

        letterBoxLayout.refreshDrawableState()


    }

    private fun showQuizEndDialog() {
        var bldr : AlertDialog.Builder = AlertDialog.Builder(this)
        bldr.setMessage("ನಿಮ್ಮ ಅಂಕೆಗಳು  $mScore");
        bldr.setPositiveButton("ನಿಲ್ಲಿಸಿ",
            { dialogInterface: DialogInterface, i: Int ->
                this.finish()
        })
        bldr.setNegativeButton("ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ ", { dialogInterface: DialogInterface, i: Int ->
            this.restartGame()
        })
        bldr.show()


    }

    private fun restartGame() {
        viewModel?.mTimeRem.value = 1000
        mScore = 0

        clearPuzzle()
        viewModel.mTimeRem?.observe(this,timeObserver)
        viewModel.restartGame()

    }


    override fun onClick(v: View?) {
        if (v?.id == R.id.done) {
            checkAnswer()
            clearPuzzle()
            viewModel.createPuzzle()
           // displayPuzzle()
            return
        }
        //Button does not have a tag. Others have
        val tag: String = v?.getTag() as String
        if (tag.equals("input")) {
            val str = (v as TextView).text
            putStringInLetterBox(str, v)
        } else if (tag.equals("letterbox")) {
            val str = (v as TextView).text
            putStringInInputBox(str, v)
        }
    }


    private fun checkAnswer() {
         val layout:LinearLayout = dataBinding.letterbox
        val  c = layout.getChildCount()
        var word=""
        for(i in 0..c-1){
            val tv = layout.getChildAt(i)
            val str = (tv as TextView).text.toString()
            word = word+str;
        }
        if(word.equals(viewModel.mCurrentWord.value)){
            Toast.makeText(this,"Correct",Toast.LENGTH_SHORT).show()
          //  showMessage("Correct")
            mScore++
        }else{
            Toast.makeText(this,"wrong",Toast.LENGTH_SHORT).show()
            //showMessage("Wrong")
        }
       // createPuzzle()
    }

    /*private fun showMessage(s: String) {
       val b:AlertDialog.Builder = AlertDialog.Builder(this)
        b.setMessage(s)
        b.setOnDismissListener {
            this.createPuzzle()
        }
        b.show()
       *//* val t = Toast.makeText(this,s,Toast.LENGTH_SHORT)
        t.show()
        this.createPuzzle()*//*


    }*/

    private fun clearPuzzle() {
         val inputLayout = dataBinding.inputletters
        for(tv in   inputLayout.children) {
            (tv as TextView).text = ""
            tv.visibility = View.VISIBLE
        }
        val letterBoxLayout = dataBinding.letterbox
        for(tv in   letterBoxLayout.children) {
            (tv as TextView).text = ""
            tv.visibility = View.VISIBLE
        }
        mLetterIndex = 0
    }

    private fun putStringInLetterBox(str: CharSequence?,v:View) {
        val letterboxlayout = dataBinding.letterbox
        val emptyIndex = findEmptyBox(letterboxlayout)
        val childTv:TextView = letterboxlayout.getChildAt(emptyIndex) as TextView
        childTv.text = str
        childTv.visibility = View.VISIBLE
        v.visibility= View.INVISIBLE


    }
    private fun putStringInInputBox(str: CharSequence?,v:View) {
        val inputboxlayout = dataBinding.inputletters
        val emptyIndex = findEmptyBox(inputboxlayout)
        val childTv:TextView = inputboxlayout.getChildAt(emptyIndex) as TextView
        childTv.text = str
        childTv.visibility = View.VISIBLE
        v.visibility= View.INVISIBLE
        mLetterIndex--;


    }

    private fun findEmptyBox(layout: LinearLayout?): Int {
        val m:Int= if(layout!=null) layout.childCount else 0
        for(i in 0..m){
            val view = layout?.getChildAt(i)
            if((view as TextView?)?.text=="" || view?.visibility == View.INVISIBLE){
                return i
            }
        }
        return 0
    }

    /*override fun onStop() {
        super.onStop()
        if(timer!=null)
            timer.cancel()
    }*/


}
The layout xml file is as follows
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    >

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/timeLeft"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginTop="28dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="16dp"
        android:text=""
        android:textStyle="bold"
        android:fontFamily="sans-serif-medium"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textColor="@android:color/white"
        app:layout_constraintBottom_toTopOf="@+id/letterbox"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"
        android:text="ಶಬ್ದವನ್ನು ಸರಿಪಡಿಸಿ"
        android:textColor="@android:color/white"
        android:textStyle="bold"
        android:textAppearance="?android:attr/textAppearanceLarge"
        app:layout_constraintBottom_toTopOf="@id/timeLeft"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/letterbox"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="16dp"
        android:background="@android:color/white"
        android:elevation="5dp"
        android:gravity="center"

        android:orientation="horizontal"
        android:padding="10dp"
        app:layout_constraintBottom_toTopOf="@+id/inputletters"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/timeLeft">

        <TextView
            android:id="@+id/lettertext1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/lettertext2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/lettertext3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/lettertext4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/lettertext5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/lettertext6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/lettertext7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/lettertext8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#f5f5f5"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/inputletters"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="32dp"
        android:background="#83FFFFFF"
        android:elevation="5dp"
        android:gravity="center"

        android:orientation="horizontal"
        android:padding="10dp"
        app:layout_constraintBottom_toTopOf="@+id/done"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/letterbox">

        <TextView
            android:id="@+id/inputtext1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/inputtext2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/inputtext3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/inputtext4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/inputtext5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/inputtext6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/inputtext7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

        <TextView
            android:id="@+id/inputtext8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_margin="2dp"
            android:background="#33ffffff"
            android:gravity="center"
            android:minWidth="30dp"
            android:minHeight="48dp"
            android:text=""
            android:textSize="24sp" />

    </LinearLayout>

    <Button
        android:id="@+id/done"
        style="@style/Widget.AppCompat.Button.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:layout_marginStart="24dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="24dp"
        android:layout_marginBottom="32dp"
        android:text="Done"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inputletters" />

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

You can modify the code. 

I am using live date to store time and score and word. So roation of the screen  does not stop the app and recreate it 

But currently thetimer does not pause when the sceen is not visible. I still have to tweak that.  

The app uses viewmodel and livedata from jetpack library

Comments