diff --git a/app/src/main/java/net/pokeranalytics/android/util/MathUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/MathUtils.kt
index 0ad2022d..134a7382 100644
--- a/app/src/main/java/net/pokeranalytics/android/util/MathUtils.kt
+++ b/app/src/main/java/net/pokeranalytics/android/util/MathUtils.kt
@@ -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 {
@@ -96,7 +95,7 @@ class MathUtils {
}
- companion object{
+ companion object {
private fun ellipseCircumference(a: Float, b: Float): Float {
return Math.PI.toFloat() * sqrt(2f * (a.pow(2) + b.pow(2)))
diff --git a/gradle.properties b/gradle.properties
index 84ada6d0..8ba4fc71 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -35,4 +35,6 @@ android.enableBuildCache=true
# Enable simple gradle caching
org.gradle.caching=true
+kotlin.mpp.enableGranularSourceSetsMetadata=true
+kotlin.native.enableDependencyPropagation=false
diff --git a/kmmshared/build.gradle.kts b/kmmshared/build.gradle.kts
new file mode 100644
index 00000000..3abc34b4
--- /dev/null
+++ b/kmmshared/build.gradle.kts
@@ -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")
+}
\ No newline at end of file
diff --git a/kmmshared/src/androidMain/AndroidManifest.xml b/kmmshared/src/androidMain/AndroidManifest.xml
new file mode 100644
index 00000000..fa804145
--- /dev/null
+++ b/kmmshared/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/kmmshared/src/androidMain/kotlin/com/staxriver/kmmshared/Platform.kt b/kmmshared/src/androidMain/kotlin/com/staxriver/kmmshared/Platform.kt
new file mode 100644
index 00000000..efb723d8
--- /dev/null
+++ b/kmmshared/src/androidMain/kotlin/com/staxriver/kmmshared/Platform.kt
@@ -0,0 +1,5 @@
+package com.staxriver.kmmshared
+
+actual class Platform actual constructor() {
+ actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
+}
\ No newline at end of file
diff --git a/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Geometry.kt b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Geometry.kt
new file mode 100644
index 00000000..8912209d
--- /dev/null
+++ b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Geometry.kt
@@ -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 }
+}
diff --git a/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Greeting.kt b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Greeting.kt
new file mode 100644
index 00000000..aa47851c
--- /dev/null
+++ b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Greeting.kt
@@ -0,0 +1,7 @@
+package com.staxriver.kmmshared
+
+class Greeting {
+ fun greeting(): String {
+ return "Hello, ${Platform().platform}!"
+ }
+}
\ No newline at end of file
diff --git a/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/HandHistoryInformer.kt b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/HandHistoryInformer.kt
new file mode 100644
index 00000000..e84f97f4
--- /dev/null
+++ b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/HandHistoryInformer.kt
@@ -0,0 +1,9 @@
+package com.staxriver.kmmshared
+
+interface HandHistoryInformer {
+
+ val playerCount: Int
+ val maxCards: Int
+ fun cardsCountForPlayer(position: Int): Int?
+
+}
\ No newline at end of file
diff --git a/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/MathUtils.kt b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/MathUtils.kt
new file mode 100644
index 00000000..81c607f4
--- /dev/null
+++ b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/MathUtils.kt
@@ -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> {
+
+ 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>()
+ 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
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/PAException.kt b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/PAException.kt
new file mode 100644
index 00000000..1281272d
--- /dev/null
+++ b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/PAException.kt
@@ -0,0 +1,3 @@
+package com.staxriver.kmmshared
+
+class PAException(message: String) : Exception(message)
diff --git a/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Platform.kt b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Platform.kt
new file mode 100644
index 00000000..b3bb76e2
--- /dev/null
+++ b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/Platform.kt
@@ -0,0 +1,5 @@
+package com.staxriver.kmmshared
+
+expect class Platform() {
+ val platform: String
+}
\ No newline at end of file
diff --git a/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/TableDimension.kt b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/TableDimension.kt
new file mode 100644
index 00000000..441f530f
--- /dev/null
+++ b/kmmshared/src/commonMain/kotlin/com/staxriver/kmmshared/TableDimension.kt
@@ -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()
+ private var playerCircles = mutableListOf()
+ private var playerNamePoints = mutableListOf()
+ private var playerStackPoints = mutableListOf()
+ private var playerActionPoints = mutableListOf()
+ private var playerCardRects = mutableListOf>()
+
+ private var chipCircles = mutableListOf()
+ private var chipTextPoints = mutableListOf()
+
+ var boardCardRects = mutableListOf()
+
+ 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()
+ 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)
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/kmmshared/src/iosMain/kotlin/com/staxriver/kmmshared/Platform.kt b/kmmshared/src/iosMain/kotlin/com/staxriver/kmmshared/Platform.kt
new file mode 100644
index 00000000..ca37e247
--- /dev/null
+++ b/kmmshared/src/iosMain/kotlin/com/staxriver/kmmshared/Platform.kt
@@ -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
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index e7b4def4..fefa5156 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
+include ':kmmshared'
include ':app'