Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
710dbc2530 | 5 years ago |
@ -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…
Reference in new issue