Adds kmm module and start moving HH table drawing inside shared

kmm_hh
Laurent 5 years ago
parent 4b1f3abc4e
commit 710dbc2530
  1. 1
      app/src/main/java/net/pokeranalytics/android/util/MathUtils.kt
  2. 2
      gradle.properties
  3. 29
      kmmshared/build.gradle.kts
  4. 2
      kmmshared/src/androidMain/AndroidManifest.xml
  5. 5
      kmmshared/src/androidMain/kotlin/com/staxriver/kmmshared/Platform.kt
  6. 31
      kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Geometry.kt
  7. 7
      kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Greeting.kt
  8. 9
      kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/HandHistoryInformer.kt
  9. 141
      kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/MathUtils.kt
  10. 3
      kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/PAException.kt
  11. 5
      kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Platform.kt
  12. 192
      kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/TableDimension.kt
  13. 8
      kmmshared/src/iosMain/kotlin/com/staxriver/kmmshared/Platform.kt
  14. 1
      settings.gradle

@ -56,7 +56,6 @@ class MathUtils {
return null
}
}
private class EllipseQuarter(val x1: Float, val y1: Float, val xRadius: Float, val yRadius: Float, val direction: Direction): Path {

@ -35,4 +35,6 @@ android.enableBuildCache=true
# Enable simple gradle caching
org.gradle.caching=true
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false

@ -0,0 +1,29 @@
plugins {
kotlin("multiplatform")
id("com.android.library")
}
kotlin {
android()
ios {
binaries {
framework {
baseName = "kmmshared"
}
}
}
sourceSets {
val commonMain by getting
val androidMain by getting
val iosMain by getting
}
}
android {
compileSdkVersion(29)
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
}
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
}

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.staxriver.kmmshared" />

@ -0,0 +1,5 @@
package com.staxriver.kmmshared
actual class Platform actual constructor() {
actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}

@ -0,0 +1,31 @@
package com.staxriver.kmmshared
import kotlin.math.pow
import kotlin.math.sqrt
data class Point(var x: Float, var y: Float) {
fun length(): Float {
return sqrt(x.pow(2) + y.pow(2))
}
}
data class Size(var width: Float, var height: Float)
data class Circle(var x: Float, var y: Float, var radius: Float)
data class TextPoint(var x: Float, var y: Float, var fontSize: Float)
data class Rect(var bottom: Float = 0.0f, var top: Float = 0.0f, var left: Float = 0.0f, var right: Float = 0.0f) {
val width: Float
get() { return right - left }
val height: Float
get() { return top - bottom }
val centerX: Float
get() { return (this.left + this.right) / 2 }
val centerY: Float
get() { return (this.top + this.bottom) / 2 }
}

@ -0,0 +1,7 @@
package com.staxriver.kmmshared
class Greeting {
fun greeting(): String {
return "Hello, ${Platform().platform}!"
}
}

@ -0,0 +1,9 @@
package com.staxriver.kmmshared
interface HandHistoryInformer {
val playerCount: Int
val maxCards: Int
fun cardsCountForPlayer(position: Int): Int?
}

@ -0,0 +1,141 @@
package com.staxriver.kmmshared
import kotlin.math.PI
import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.math.tan
class MathUtils {
private interface Path {
val length: Float
fun pointForDistance(distance: Float): Point
}
class Line(val x1: Float, val y1: Float, val x2: Float, val y2: Float): Path {
override val length: Float
get() {
return Point(x2 - x1, y2 - y1).length()
}
override fun pointForDistance(distance: Float): Point {
val ratio = distance / this.length
return Point(x1 + ratio * (x2 - x1), y1 + ratio * (y2 - y1))
}
val slope: Float
get() { return (x2 - x1) / (y2 - y1) }
private fun intersects(line: Line): Point? {
val s1_x = this.x2 - this.x1
val s1_y = this.y2 - this.y1
val s2_x = line.x2 - line.x1
val s2_y = line.y2 - line.y1
val s = (-s1_y * (this.x1 - line.x1) + s1_x * (this.y1 - line.y1)) / (-s2_x * s1_y + s1_x * s2_y);
val t = ( s2_x * (this.y1 - line.y1) - s2_y * (this.x1 - line.x1)) / (-s2_x * s1_y + s1_x * s2_y);
return if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
Point(this.x1 + (t * s1_x), this.y1 + (t * s1_y))
} else {
null
}
}
fun intersects(rect: Rect): Point? {
val p1 = this.intersects(Line(rect.left, rect.top, rect.right, rect.top))
if (p1 != null) return p1
val p2 = this.intersects(Line(rect.right, rect.top, rect.right, rect.bottom))
if (p2 != null) return p2
val p3 = this.intersects(Line(rect.left, rect.bottom, rect.right, rect.bottom))
if (p3 != null) return p3
val p4 = this.intersects(Line(rect.left, rect.top, rect.left, rect.bottom))
if (p4 != null) return p4
return null
}
}
private class EllipseQuarter(val x1: Float, val y1: Float, val xRadius: Float, val yRadius: Float, val direction: Direction): Path {
enum class Direction { SOUTH_EAST, SOUTH_WEST, NORTH_EAST, NORTH_WEST;
val east: Boolean
get() { return this == SOUTH_EAST || this == NORTH_EAST }
val angle: Float
get() {
return when (this) {
NORTH_EAST -> PI.toFloat() * 0.5f
NORTH_WEST -> PI.toFloat()
SOUTH_EAST -> 0f
SOUTH_WEST -> PI.toFloat() * 1.5f
}
}
}
override val length: Float
get() {
return ellipseCircumference(this.xRadius, this.yRadius) / 4f
}
override fun pointForDistance(distance: Float): Point {
val angle = PI.toFloat() / 2 * distance / length
return pointForAngle(angle)
}
private fun pointForAngle(angle: Float): Point {
val baseAngle = direction.angle - angle
val denominator = sqrt(yRadius.pow(2) + xRadius.pow(2) * tan(baseAngle).pow(2))
val x = xRadius * yRadius / denominator
val y = xRadius * yRadius * tan(baseAngle) / denominator
return if (this.direction.east) Point(x1 + x, y1 - y)
else Point(x1 - x, y1 + y)
}
}
companion object {
private fun ellipseCircumference(a: Float, b: Float): Float {
return PI.toFloat() * sqrt(2f * (a.pow(2) + b.pow(2)))
}
fun positionsAroundRoundedRectangle(numberOfPlayers: Int, rect: Rect, xRadius: Float, yRadius: Float): List<Pair<Point, Boolean>> {
val segments = listOf(
Line(rect.centerX, rect.bottom, rect.left + xRadius, rect.bottom),
EllipseQuarter(rect.left + xRadius, rect.bottom - yRadius, xRadius, yRadius, EllipseQuarter.Direction.SOUTH_WEST),
Line(rect.left, rect.bottom - yRadius, rect.left, rect.top + yRadius),
EllipseQuarter(rect.left + xRadius, rect.top + yRadius, xRadius, yRadius, EllipseQuarter.Direction.NORTH_WEST),
Line(rect.left + xRadius, rect.top, rect.right - xRadius, rect.top),
EllipseQuarter(rect.right - xRadius, rect.top + yRadius, xRadius, yRadius, EllipseQuarter.Direction.NORTH_EAST),
Line(rect.right, rect.top + yRadius, rect.right, rect.bottom - yRadius),
EllipseQuarter(rect.right - xRadius, rect.bottom - yRadius, xRadius, yRadius, EllipseQuarter.Direction.SOUTH_EAST),
Line(rect.right - xRadius, rect.bottom, rect.centerX, rect.bottom)
)
val perimeter = segments.sumByDouble { it.length.toDouble() }
val distancePerPlayer = perimeter / numberOfPlayers
val playerPoints = mutableListOf<Pair<Point, Boolean>>()
for (i in 0 until numberOfPlayers) {
var distanceFromOrigin = i * distancePerPlayer.toFloat()
var pathIndex = 0
while (distanceFromOrigin > segments[pathIndex].length) {
distanceFromOrigin -= segments[pathIndex].length
pathIndex++
}
val p = segments[pathIndex].pointForDistance(distanceFromOrigin)
// 2 first and 2 last are at the bottom
val bottom = pathIndex < 2 || pathIndex > 6
playerPoints.add(Pair(p, bottom))
}
return playerPoints
}
}
}

@ -0,0 +1,3 @@
package com.staxriver.kmmshared
class PAException(message: String) : Exception(message)

@ -0,0 +1,5 @@
package com.staxriver.kmmshared
expect class Platform() {
val platform: String
}

@ -0,0 +1,192 @@
package com.staxriver.kmmshared
class TableDimension(var width: Float, var height: Float, var handHistory: HandHistoryInformer) {
var tableRect = Rect()
var tableCornerXRadius = 0f
var tableCornerYRadius = 0f
var cardSpecs: Size = Size(0f, 0f)
var cardRadius = 0f
private var playerStackRects = mutableListOf<Rect>()
private var playerCircles = mutableListOf<Circle>()
private var playerNamePoints = mutableListOf<TextPoint>()
private var playerStackPoints = mutableListOf<TextPoint>()
private var playerActionPoints = mutableListOf<TextPoint>()
private var playerCardRects = mutableListOf<List<Rect>>()
private var chipCircles = mutableListOf<Circle>()
private var chipTextPoints = mutableListOf<TextPoint>()
var boardCardRects = mutableListOf<Rect>()
private var dealerBottomOriented = true
val dealerCircle: Circle
get() {
val rect = this.playerStackRects.last()
val radius = (rect.bottom - rect.top) / 4
val width = rect.right - rect.left
val x: Float; val y: Float
if (this.dealerBottomOriented) {
x = rect.left
y = (rect.top + rect.bottom) / 2f
} else {
x = rect.left + width / 6.0f
y = rect.bottom
}
return Circle(x, y, radius)
}
private var playerItemsHeight = 10f
private var playerItemsWidth = 10f
private var chipRadius = 10f
private var paddingPercentage = 0.8f
private var cardsPaddingPercentage = 0.9f
private var tableHPadding = 0f
private var tableVPadding = 0f
// var showVillainHands: Boolean = true
var potTextPoint = TextPoint(0f, 0f, 0f)
var totalPotTextPoint = TextPoint(0f, 0f, 0f)
var potChipCircle = Circle(0f, 0f, 0f)
var tableStrokeWidth = 30f
var playerStrokeWidth = 8f
var cardStrokeWidth = 8f
private val centerX: Float
get() { return this.width / 2f }
private val centerY: Float
get() { return this.height / 2f }
val maxCards: Int = this.handHistory.maxCards
val players: Int = this.handHistory.playerCount
private fun setDimension(width: Float, height: Float) {
// Timber.d("Setting dimensions...")
this.width = width
this.height = height
val maxPlayerCards = this.maxCards
val hwRatio = 1.3f
val portrait = height > width
val base = 3.3f + this.players / 20.0f
val playerPerColumn = if (portrait) base * hwRatio else base
val playerPerRow = if (portrait) base else base * hwRatio
// val grid = when (this.handHistory.numberOfPlayers) {
// 9, 10 -> Pair(4f, 5f)
// else -> Pair(3.7f, 4.7f)
// }
// Timber.d("playerPerRow = $playerPerRow, playerPerColumn = $playerPerColumn")
// val playerPerColumn = if (portrait) grid.second else grid.first
// val playerPerRow = if (portrait) grid.first else grid.second
val padding = if (portrait) 0.8f else 0.95f
this.tableHPadding = width / playerPerRow / 2 * padding
this.tableVPadding = height / playerPerColumn * 0.8f
this.tableRect = Rect(tableHPadding, tableVPadding, width - tableHPadding, height - tableVPadding)
this.tableCornerXRadius = this.tableRect.width / 3
this.tableCornerYRadius = this.tableRect.height / 3
this.tableStrokeWidth = tableHPadding / 5f
this.playerStrokeWidth = this.tableStrokeWidth / 4f
this.cardStrokeWidth = this.tableStrokeWidth / 4f
// pz for Player Zone
val pzHeight = height / playerPerColumn
this.playerItemsHeight = pzHeight / 3
this.playerItemsWidth = this.tableHPadding * 2 * this.paddingPercentage
this.chipRadius = this.playerItemsHeight / 4
val cardWPaddingWidth = this.playerItemsWidth * 1.4f / this.maxCards
val cardWidth = cardWPaddingWidth * this.cardsPaddingPercentage
this.cardSpecs = Size(cardWidth, cardWidth * 1.75f)
this.cardRadius = cardWidth / 4
// Board cards rectangles
val bcCenterY = centerY + cardSpecs.height / 2
val bcTop = bcCenterY - cardSpecs.height / 2
val bcBottom = bcCenterY + cardSpecs.height / 2
val boardCards = 5
for (i in 0 until boardCards) {
val bcLeft = centerX - (boardCards / 2f - i) * cardWPaddingWidth
val bcRight = centerX - (boardCards / 2f - i) * cardWPaddingWidth + cardWidth
this.boardCardRects.add(Rect(bcLeft, bcTop, bcRight, bcBottom))
}
val chipTextSize = this.chipRadius
this.potChipCircle = Circle(centerX, bcTop - 3.7f * chipTextSize, this.chipRadius)
this.potTextPoint = TextPoint(centerX, bcTop - 1.6f * chipTextSize, chipTextSize)
this.totalPotTextPoint = TextPoint(centerX, bcTop - 0.5f * chipTextSize, chipTextSize)
val positions = MathUtils.positionsAroundRoundedRectangle(this.players, this.tableRect, this.tableCornerXRadius, this.tableCornerYRadius)
for (i in (0 until this.players)) {
val point = positions[i].first
val bottomOriented = positions[i].second
if (i == positions.size - 1) { dealerBottomOriented = bottomOriented }
val rectCenterX = point.x
val rectCenterY = point.y
val left = rectCenterX - this.playerItemsWidth / 2
val top = rectCenterY - this.playerItemsHeight / 2
val right = rectCenterX + this.playerItemsWidth / 2
val bottom = rectCenterY + this.playerItemsHeight / 2
val pRect = Rect(left, top, right, bottom)
this.playerStackRects.add(pRect)
val line = MathUtils.Line(point.x, point.y, tableRect.centerX, tableRect.centerY)
val prp = line.intersects(pRect) ?: throw PAException("should not happen")
val boxToCenterLine = MathUtils.Line(prp.x, prp.y, tableRect.centerX, tableRect.centerY)
val boxToChipDistance = if (bottomOriented) 3f * chipRadius else 2f * chipRadius // because the chip text needs space
val chipPoint = boxToCenterLine.pointForDistance(boxToChipDistance)
this.chipCircles.add(Circle(chipPoint.x, chipPoint.y, chipRadius))
this.chipTextPoints.add(TextPoint(chipPoint.x, chipPoint.y + 2 * chipTextSize, chipTextSize))
// we give each text zone 1/3rd of the box height, leaving 1/3 for space
// the y given is the bottom of the text rect, giving 1/18th as the offset
// 1 / (3_total_space * 3_each_space * 2_center)
val bottomOffset = this.playerItemsHeight / (3 * 3 * 2)
val fontSize = this.playerItemsHeight / 3
this.playerNamePoints.add(TextPoint(rectCenterX, rectCenterY - bottomOffset, fontSize))
this.playerStackPoints.add(TextPoint(rectCenterX, rectCenterY + this.playerItemsHeight / 3, fontSize))
this.playerActionPoints.add(TextPoint(rectCenterX, rectCenterY + this.playerItemsHeight / 9, fontSize))
val orientation = if (bottomOriented) -1 else 1
val circleOffset = playerItemsHeight * 1.75f * orientation
this.playerCircles.add(Circle(rectCenterX, rectCenterY - circleOffset, this.playerItemsHeight / 2))
val cardsUsed = this.handHistory.cardsCountForPlayer(i) ?: maxPlayerCards
val cardsRectangles = mutableListOf<Rect>()
if (cardsUsed > 0) {
val offSet = (cardsUsed / 2 - 0.5f) * cardWPaddingWidth
val cardCenterY = rectCenterY - circleOffset / 2
for (c in 0 until cardsUsed) {
val cardCenterX = rectCenterX - offSet + c * cardWPaddingWidth
val cardRect = Rect(cardCenterX - cardSpecs.width / 2, cardCenterY - cardSpecs.height / 2, cardCenterX + cardSpecs.width / 2, cardCenterY + cardSpecs.height / 2)
cardsRectangles.add(cardRect)
}
}
this.playerCardRects.add(cardsRectangles)
}
}
}

@ -0,0 +1,8 @@
package com.staxriver.kmmshared
import platform.UIKit.UIDevice
actual class Platform actual constructor() {
actual val platform: String =
UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

@ -1 +1,2 @@
include ':kmmshared'
include ':app'

Loading…
Cancel
Save