Skip to content

Commit

Permalink
Room database implementation
Browse files Browse the repository at this point in the history
implemented #27 (ui will be next committed)
ippatsu now works properly
riichi is not combined anymore with double riichi
fix #33
fix #34

notes:
mahjong balance changes not by pure win/lose points, but depending on already having balance - the more you have already, the less you win. This will be properly documented later.
mahjong streak changes if you are not tempai. If you win, it's +1, otherwise it's =0. But if you are tempai, it won't be affected.
now riichi bet possibility also depends on a player's balance, it's impossible if player has less than 1000 points to required bet
saving games for future #36 statistics implementation, profile system (database only)

not tested as far.
  • Loading branch information
N1ckn1ght committed Jun 24, 2022
1 parent 90c1016 commit a34a7fe
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 17 deletions.
1 change: 1 addition & 0 deletions .idea/dictionaries/Nick.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}

android {
Expand Down Expand Up @@ -34,8 +35,17 @@ android {
}

dependencies {
implementation 'androidx.room:room-common:2.4.2'
implementation 'androidx.room:room-runtime:2.4.2'
implementation 'androidx.room:room-ktx:2.4.2'
kapt 'androidx.room:room-compiler:2.4.2'

implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference-ktx:1.2.0'

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1-native-mt'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1-native-mt'

implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.0"
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
Expand Down
15 changes: 14 additions & 1 deletion app/src/main/java/com/example/riichit/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@ import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.example.riichit.AppDatabase.Companion.instance
import com.example.riichit.Drawables.flag
import com.example.riichit.LocaleHelper.changeLocale
import com.example.riichit.LocaleHelper.getLocale
import com.example.riichit.LocaleHelper.setLocale
import com.example.riichit.Utility.toInt
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
private val context = this
private lateinit var db: AppDatabase

@OptIn(DelicateCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
setLocale(context)
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -45,13 +52,19 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
}

// language icon shall be changed upon a preferenced language
// language icon shall be changed upon a preferred language
val imageViewLang = findViewById<ImageView>(R.id.ivLang)
flag[getLocale(context)]?.let { imageViewLang.setImageResource(it) }
imageViewLang.setOnClickListener {
changeLocale(context)
recreate()
}

// create profile #0 if not exists
db = instance(this)
GlobalScope.launch(Dispatchers.IO) {
Operations.createProfile(db, 0)
}
}

private fun startGame(mode: Int) {
Expand Down
139 changes: 139 additions & 0 deletions app/src/main/java/com/example/riichit/Operations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.example.riichit

import android.util.Log
import com.example.riichit.Ruleset.newBalance
import com.example.riichit.Utility.operationsLimit
import com.example.riichit.Utility.toInt

object Operations {
fun addGame(
db: AppDatabase,
profile: Int,
hand: MutableList<Int>,
tsumo: Int,
kanTiles: MutableList<Int>,
discard: MutableList<Int>,
yakuConditional: Map<String, Boolean>? = null
) {
val yakuConditionalArray = arrayOf(0, 0, 0, 0, 0, 0)
yakuConditional?.let {
var i = 0
for ((_, v) in it) {
yakuConditionalArray[i++] = v.toInt()
}
}

val handString = hand.toIntArray().contentToString()
val kanTilesString = kanTiles.toIntArray().contentToString()
val discardString = discard.toIntArray().contentToString()
val yakuConditionalString = yakuConditionalArray.contentToString()

db.gamesDao().addGame(
profile,
handString,
tsumo,
kanTilesString,
discardString,
yakuConditionalString
)
}

fun updateProfile(
db: AppDatabase,
profile: Int,
mode: Int,
balanceChange: Int,
streakChange: Int
) {
val data = getProfile(db, profile)

if (data.isEmpty()) {
Log.d("d/operations", "Profile $profile not found in database $db!")
return
}

var mahjongBalance = data[0].mahjongBalance
var mahjongStreak = data[0].mahjongStreak
var manBalance = data[0].manBalance
var manStreak = data[0].manStreak
when (mode) {
0 -> {
mahjongBalance = newBalance(mahjongBalance, balanceChange)
when (streakChange) {
-1 -> {
mahjongStreak = 0
}
1 -> {
mahjongStreak += 1
}
}
}
1 -> {
manBalance = newBalance(manBalance, balanceChange)
when (streakChange) {
-1 -> {
manStreak = 0
}
1 -> {
manStreak += 1
}
}
}
}
updateProfile(db, profile, mahjongBalance, mahjongStreak, manBalance, manStreak)
}

fun createProfile(
db: AppDatabase,
profile: Int
) {
var recursionLevel = 0

var data = getProfile(db, profile)
while (data.isEmpty() && recursionLevel++ < operationsLimit) {
db.profilesDao().createProfile()
data = getProfile(db, profile)
}
}

fun getBalance(
db: AppDatabase,
profile: Int,
mode: Int
): Int {
val data = getProfile(db, profile)

if (data.isEmpty()) {
Log.d("d/operations", "Profile $profile not found in database $db!")
return 0
}

when (mode) {
0 -> {
return data[0].mahjongBalance
}
1 -> {
return data[0].manBalance
}
}
return 0
}

private fun getProfile(
db: AppDatabase,
profile: Int
): List<ProfileEntity> {
return db.profilesDao().getProfile(profile)
}

private fun updateProfile(
db: AppDatabase,
profile: Int,
mahjongBalance: Int,
mahjongStreak: Int,
manBalance: Int,
manStreak: Int
) {
db.profilesDao().updateProfile(profile, mahjongBalance, mahjongStreak, manBalance, manStreak)
}
}
62 changes: 62 additions & 0 deletions app/src/main/java/com/example/riichit/Room.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.example.riichit

import android.content.Context
import androidx.lifecycle.LiveData
import androidx.room.*

@Entity(tableName = "games")
data class GameEntity(
@PrimaryKey(autoGenerate = true) var id: Int,
@ColumnInfo(name = "profile", defaultValue = "0") var profile: Int,
@ColumnInfo(name = "hand", defaultValue = "[]") var hand: String,
@ColumnInfo(name = "tsumo", defaultValue = "136") var tsumo: Int,
@ColumnInfo(name = "kan_tiles", defaultValue = "[]") var kanTiles: String,
@ColumnInfo(name = "discard", defaultValue = "[]") var discard: String,
@ColumnInfo(name = "yaku_conditional", defaultValue = "[0, 0, 0, 0, 0, 0]") var yakuConditional: String,
@ColumnInfo(name = "datetime", defaultValue = "CURRENT_TIMESTAMP") var datetime: String,
)

@Entity(tableName = "profiles")
data class ProfileEntity(
@PrimaryKey(autoGenerate = true) var id: Int,
@ColumnInfo(name = "mahjong_balance", defaultValue = "0") var mahjongBalance: Int,
@ColumnInfo(name = "man_balance", defaultValue = "0") var manBalance: Int,
@ColumnInfo(name = "mahjong_streak", defaultValue = "0") var mahjongStreak: Int,
@ColumnInfo(name = "man_streak", defaultValue = "0") var manStreak: Int
)

@Dao
interface GamesDao {
@Query("INSERT INTO games (profile, hand, tsumo, kan_tiles, discard, yaku_conditional) VALUES (:profile, :hand, :tsumo, :kanTiles, :discard, :yakuConditional)")
fun addGame(profile: Int, hand: String, tsumo: Int, kanTiles: String, discard: String, yakuConditional: String)
}

@Dao
interface ProfilesDao {
@Query("INSERT INTO profiles (mahjong_balance, man_balance) VALUES (:mahjongBalance, :manBalance)")
fun createProfile(mahjongBalance: Int = 25000, manBalance: Int = 25000)

@Query("SELECT * FROM profiles WHERE id = (:profile)")
fun getProfile(profile: Int): List<ProfileEntity>

@Query("UPDATE profiles SET mahjong_balance = (:mahjongBalance), mahjong_streak = (:mahjongStreak), man_balance = (:manBalance), man_streak = (:manStreak) WHERE id = (:profile)")
fun updateProfile(profile: Int, mahjongBalance: Int, manBalance: Int, mahjongStreak: Int, manStreak: Int)
}

@Database(entities = [GameEntity::class, ProfileEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun gamesDao(): GamesDao
abstract fun profilesDao(): ProfilesDao

companion object {
private var INSTANCE: AppDatabase? = null

internal fun instance(context: Context): AppDatabase {
if (INSTANCE == null) {
INSTANCE =
Room.databaseBuilder(context, AppDatabase::class.java, "quotations.db").build()
}
return INSTANCE!!
}
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/com/example/riichit/Ruleset.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.example.riichit

import java.lang.Integer.min

object Ruleset {
val yakuCountedCost = mapOf(
"dora" to 1,
Expand Down Expand Up @@ -48,4 +50,15 @@ object Ruleset {
"tenhou" to 13,
"tsuuiisou" to 13
)

fun newBalance(currentBalance: Int, change: Int): Int {
var balance = currentBalance
if (change < 0) {
balance = min(0, balance - change)
} else {
val modif = (100000.0 / (min(25000, balance) + 75000).toDouble())
balance += (change * modif).toInt()
}
return balance
}
}
Loading

0 comments on commit a34a7fe

Please sign in to comment.