Fixes #34 : visual glitches when replaying a hand and exporting at the same time due to the static nature of the drawer

bs
Laurent 5 years ago
parent 2d1589babb
commit 7dafd0b87b
  1. 103
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt
  2. 15
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerAnimator.kt
  3. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerView.kt
  4. 658
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/TableDrawer.kt

@ -18,13 +18,11 @@ import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.extensions.toByteArray
import net.pokeranalytics.android.util.FFMPEG_DESCRIPTOR_FILE
import net.pokeranalytics.android.util.TriggerNotification
import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.video.AnimatedGIFWriter
import net.pokeranalytics.android.util.video.MMediaMuxer
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
@ -79,7 +77,6 @@ class ReplayExportService : Service() {
val height = square
animator.setDimension(width.toFloat(), height.toFloat())
TableDrawer.configurePaints(context, animator)
val formattedDate = Date().dateTimeFileFormatted
val path = File(
@ -91,8 +88,11 @@ class ReplayExportService : Service() {
val os = FileOutputStream(path)
writer.prepareForWrite(os, width, height)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
var animationCount = 0
animator.frames(context) { bitmap, count ->
animator.frames(drawer, context) { bitmap, count ->
when {
count > 10 -> {
@ -137,11 +137,12 @@ class ReplayExportService : Service() {
val height = square
animator.setDimension(width.toFloat(), height.toFloat())
TableDrawer.configurePaints(context, animator)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
// generates all images and file descriptor
Timber.d("Generating images for video...")
val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val tmpDir = animator.generateVideoContent(drawer, this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
val directory = context.getExternalFilesDir(null) ?: throw PAIllegalStateException("File is invalid")
@ -170,51 +171,51 @@ class ReplayExportService : Service() {
}
private fun startVideoExport() {
GlobalScope.launch(coroutineContext) {
val c = GlobalScope.async {
val realm = Realm.getDefaultInstance()
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val context = this@ReplayExportService
val animator = ReplayerAnimator(handHistory, true)
val square = 1024
val width = square
val height = square
animator.setDimension(width.toFloat(), height.toFloat())
TableDrawer.configurePaints(context, animator)
val muxer = MMediaMuxer()
muxer.init(null, width, height, "hhVideo", "YES!")
animator.frames(context) { bitmap, count ->
try {
val byteArray = bitmap.toByteArray()
muxer.addFrame(byteArray, count, false)
} catch (e: Exception) {
Timber.e("error = ${e.message}")
}
}
realm.close()
muxer.createVideo { path ->
notifyUser(path)
}
}
c.await()
}
}
// private fun startVideoExport() {
//
// GlobalScope.launch(coroutineContext) {
// val c = GlobalScope.async {
//
// val realm = Realm.getDefaultInstance()
// val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
//
// val context = this@ReplayExportService
//
// val animator = ReplayerAnimator(handHistory, true)
//
// val square = 1024
//
// val width = square
// val height = square
//
// animator.setDimension(width.toFloat(), height.toFloat())
// TableDrawer.configurePaints(context, animator)
//
// val muxer = MMediaMuxer()
// muxer.init(null, width, height, "hhVideo", "YES!")
//
// animator.frames(context) { bitmap, count ->
//
// try {
// val byteArray = bitmap.toByteArray()
// muxer.addFrame(byteArray, count, false)
// } catch (e: Exception) {
// Timber.e("error = ${e.message}")
// }
//
// }
//
// realm.close()
//
// muxer.createVideo { path ->
// notifyUser(path)
// }
//
// }
// c.await()
// }
//
// }
private fun notifyUser(path: String) {

@ -4,7 +4,6 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.RectF
import kotlinx.coroutines.delay
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.handhistory.Street
import net.pokeranalytics.android.model.realm.handhistory.Action
@ -147,7 +146,6 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) {
init {
loadHandHistory(this.handHistory)
TableDrawer.clearColors()
}
private fun loadHandHistory(handHistory: HandHistory) {
@ -479,7 +477,7 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) {
* Generates images and image descriptor to build the video using ffmpeg
* Command line: https://trac.ffmpeg.org/wiki/Slideshow
*/
suspend fun generateVideoContent(context: Context): File {
fun generateVideoContent(tableDrawer: TableDrawer, context: Context): File {
var ffmpegImageDescriptor = ""
var count = 0
@ -500,21 +498,20 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) {
val canvas = Canvas(bitmap)
val vo = this.visualOccurences / 90.0 // this is needed before the call to drawTable which pass to the next frame
TableDrawer.drawTable(this, canvas, context)
tableDrawer.drawTable(canvas, context)
imagePath = File(directory, "img_$count.png").path
FileUtils.writeBitmapToPNG(bitmap, imagePath)
bitmap.recycle()
// bitmap.recycle()
ffmpegImageDescriptor = ffmpegImageDescriptor.plus("file '$imagePath'\n")
ffmpegImageDescriptor = ffmpegImageDescriptor.plus("duration $vo\n")
count++
Thread.sleep(100L)
// awaitFrame()
// delay(100L)
}
nextStep()
@ -537,7 +534,7 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) {
* As soon as a bitmap has been created, the [frameHandler] is called with the created
* bitmap, and a appropriate occurrences of the bitmap to be added is passed along
*/
fun frames(context: Context, frameHandler: (Bitmap, Int) -> Unit) {
fun frames(tableDrawer: TableDrawer, context: Context, frameHandler: (Bitmap, Int) -> Unit) {
Timber.d("Step count = ${this.steps.size}")
@ -553,7 +550,7 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) {
val canvas = Canvas(bitmap)
val vo = this.visualOccurences // this is needed before the call to drawTable which pass to the next frame
TableDrawer.drawTable(this, canvas, context)
tableDrawer.drawTable(canvas, context)
frameHandler(bitmap, vo)

@ -9,6 +9,8 @@ import android.view.View
class ReplayerView(context: Context, attrs: AttributeSet) : View(context, attrs) {
private var drawer: TableDrawer = TableDrawer()
lateinit var animator: ReplayerAnimator
private val animationHandler = Handler(Looper.getMainLooper())
@ -20,7 +22,7 @@ class ReplayerView(context: Context, attrs: AttributeSet) : View(context, attrs)
init {
this.viewTreeObserver.addOnGlobalLayoutListener {
this.animator.setDimension(width.toFloat(), height.toFloat())
TableDrawer.configurePaints(context, this.animator)
this.drawer.configurePaints(context, this.animator)
}
}
@ -37,7 +39,7 @@ class ReplayerView(context: Context, attrs: AttributeSet) : View(context, attrs)
super.onDraw(canvas)
canvas?.let {
TableDrawer.drawTable(animator, canvas, context)
this.drawer.drawTable(canvas, context)
}
}

@ -12,446 +12,424 @@ import net.pokeranalytics.android.ui.modules.handhistory.model.ComputedAction
import net.pokeranalytics.android.util.RANDOM_PLAYER
import net.pokeranalytics.android.util.extensions.formatted
class TableDrawer(bitmap: Bitmap) : Canvas(bitmap) {
class TableDrawer {
companion object {
lateinit var animator: ReplayerAnimator
private const val backgroundColor = R.color.green_darker
private val backgroundPaint = Paint()
private val strokePaint = Paint()
private val fillPaint = Paint()
private val backgroundColor = R.color.green_darker
private val backgroundPaint = Paint()
private val strokePaint = Paint()
private val fillPaint = Paint()
private val tablePaint = Paint()
private val feltPaint = Paint()
private val textPaint = Paint()
private val cardTextPaint = Paint()
private val cardStrokePaint = Paint()
private val chipBorderPaint = Paint()
private val tablePaint = Paint()
private val feltPaint = Paint()
private val textPaint = Paint()
private val cardTextPaint = Paint()
private val cardStrokePaint = Paint()
private val chipBorderPaint = Paint()
private val colorsByAmount = hashMapOf<Double, ChipColor>()
private val colorsByAmount = hashMapOf<Double, ChipColor>()
fun clearColors() {
this.colorsByAmount.clear()
}
// fun clearColors() {
// this.colorsByAmount.clear()
// }
fun configurePaints(context: Context, animator: ReplayerAnimator) {
fun configurePaints(context: Context, animator: ReplayerAnimator) {
backgroundPaint.color = context.getColor(backgroundColor)
this.animator = animator
tablePaint.isAntiAlias = true
tablePaint.strokeWidth = animator.tableStrokeWidth
tablePaint.color = context.getColor(R.color.green)
tablePaint.style = Paint.Style.STROKE
backgroundPaint.color = context.getColor(backgroundColor)
feltPaint.isAntiAlias = true
feltPaint.color = context.getColor(R.color.green_transparent_felt)
feltPaint.style = Paint.Style.STROKE
feltPaint.strokeWidth = animator.tableStrokeWidth
tablePaint.isAntiAlias = true
tablePaint.strokeWidth = animator.tableStrokeWidth
tablePaint.color = context.getColor(R.color.green)
tablePaint.style = Paint.Style.STROKE
strokePaint.isAntiAlias = true
strokePaint.style = Paint.Style.STROKE
strokePaint.strokeWidth = animator.playerStrokeWidth
feltPaint.isAntiAlias = true
feltPaint.color = context.getColor(R.color.green_transparent_felt)
feltPaint.style = Paint.Style.STROKE
feltPaint.strokeWidth = animator.tableStrokeWidth
cardStrokePaint.isAntiAlias = true
cardStrokePaint.style = Paint.Style.STROKE
cardStrokePaint.strokeWidth = animator.cardStrokeWidth
strokePaint.isAntiAlias = true
strokePaint.style = Paint.Style.STROKE
strokePaint.strokeWidth = animator.playerStrokeWidth
chipBorderPaint.isAntiAlias = true
chipBorderPaint.style = Paint.Style.STROKE
cardStrokePaint.isAntiAlias = true
cardStrokePaint.style = Paint.Style.STROKE
cardStrokePaint.strokeWidth = animator.cardStrokeWidth
fillPaint.isAntiAlias = true
chipBorderPaint.isAntiAlias = true
chipBorderPaint.style = Paint.Style.STROKE
textPaint.color = context.getColor(R.color.white)
textPaint.textAlign = Paint.Align.CENTER
textPaint.isAntiAlias = true
textPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold)
fillPaint.isAntiAlias = true
cardTextPaint.color = context.getColor(R.color.black)
cardTextPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold)
cardTextPaint.textAlign = Paint.Align.CENTER
cardTextPaint.isAntiAlias = true
}
textPaint.color = context.getColor(R.color.white)
textPaint.textAlign = Paint.Align.CENTER
textPaint.isAntiAlias = true
textPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold)
fun drawTable(animator: ReplayerAnimator, canvas: Canvas, context: Context) {
drawTableItems(animator, canvas, context)
animator.frameDrawn()
}
cardTextPaint.color = context.getColor(R.color.black)
cardTextPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold)
cardTextPaint.textAlign = Paint.Align.CENTER
cardTextPaint.isAntiAlias = true
}
private fun drawTableItems(animator: ReplayerAnimator, canvas: Canvas, context: Context) {
fun drawTable(canvas: Canvas, context: Context) {
drawTableItems(canvas, context)
animator.frameDrawn()
}
// base
initializeTable(animator, canvas, context)
private fun drawTableItems(canvas: Canvas, context: Context) {
val step = animator.currentStep
val street = step.street
// base
initializeTable(canvas, context)
val computedAction = step as? ComputedAction?
val step = animator.currentStep
val street = step.street
// draw board
drawBoardCards(street, animator, canvas, context)
val computedAction = step as? ComputedAction?
// draw player shapes and chips
val hh = animator.handHistory
for (i in 0 until hh.numberOfPlayers) {
drawPlayerShapes(i, computedAction, animator, canvas, context)
drawPlayerChips(i, animator, canvas, context)
}
// draw board
drawBoardCards(street, canvas, context)
// draw pot
drawPot(street, computedAction, animator, canvas, context)
// draw player shapes and chips
val hh = animator.handHistory
for (i in 0 until hh.numberOfPlayers) {
drawPlayerShapes(i, computedAction, canvas, context)
drawPlayerChips(i, canvas, context)
}
// draw player cards
animator.activePositions.forEach {
drawPlayerCards(it, animator, canvas, context)
}
// draw pot
drawPot(street, computedAction, canvas, context)
// draw player cards
animator.activePositions.forEach {
drawPlayerCards(it, canvas, context)
}
private fun drawPlayerShapes(i: Int, computedAction: ComputedAction?, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
}
private fun drawPlayerShapes(i: Int, computedAction: ComputedAction?, canvas: Canvas, context: Context) {
// draw player rectangles with action or name + stack
if (computedAction?.positionIndex == i) {
drawPlayerRectangle(i, true, animator, canvas, context)
drawAction(i, computedAction, animator, canvas, context)
// draw player rectangles with action or name + stack
if (computedAction?.positionIndex == i) {
drawPlayerRectangle(i, true, canvas, context)
drawAction(i, computedAction, canvas, context)
} else {
val info = if (animator.isPlayerAllin(i)) {
context.getString(R.string.allin)
} else {
val info = if (animator.isPlayerAllin(i)) {
context.getString(R.string.allin)
} else {
val remainingStack = animator.playerRemainingStack(i)
remainingStack?.formatted
}
drawPlayerRectangle(i, false, animator, canvas, context)
drawPositionAndInfo(i, info, animator, canvas)
val remainingStack = animator.playerRemainingStack(i)
remainingStack?.formatted
}
drawPlayerRectangle(i, false, canvas, context)
drawPositionAndInfo(i, info, canvas)
}
/***
* Draw chips
* A chip should appears when the player has put some money during the current street
*/
private fun drawPlayerChips(i: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
when (animator.currentStep) {
is Street -> {
when (animator.frameType) {
FrameType.STATE -> {
if (animator.currentStep == Street.SUMMARY) {
val winnerPots = animator.handHistory.winnerPots.firstOrNull { it.position == i }
winnerPots?.let { pot ->
val color = colorForAmount(pot.amount)
val circle = animator.animatedChipCircleFromPot(i)
drawChipCircle(circle, color, canvas, context)
drawChipText(pot.amount, animator.chipText(i), canvas, context)
}
}
}
}
FrameType.GATHER_ANIMATION -> {
lastCommittedAmount(i, animator)?.let { amount ->
val color = colorForAmount(amount)
val circle = animator.animatedChipCircleToPot(i)
drawChipCircle(circle, color, canvas, context)
}
}
FrameType.DISTRIBUTION_ANIMATION -> {
/***
* Draw chips
* A chip should appears when the player has put some money during the current street
*/
private fun drawPlayerChips(i: Int, canvas: Canvas, context: Context) {
when (animator.currentStep) {
is Street -> {
when (animator.frameType) {
FrameType.STATE -> {
if (animator.currentStep == Street.SUMMARY) {
val winnerPots = animator.handHistory.winnerPots.firstOrNull { it.position == i }
winnerPots?.let { pot ->
val color = colorForAmount(pot.amount)
val circle = animator.animatedChipCircleFromPot(i)
drawChipCircle(circle, color, canvas, context)
drawChipText(pot.amount, animator.chipText(i), canvas, context)
}
}
}
}
is ComputedAction -> {
drawChipForAction(i, animator, canvas, context)
}
}
}
FrameType.GATHER_ANIMATION -> {
lastCommittedAmount(i)?.let { amount ->
val color = colorForAmount(amount)
val circle = animator.animatedChipCircleToPot(i)
drawChipCircle(circle, color, canvas, context)
}
}
FrameType.DISTRIBUTION_ANIMATION -> {
private fun drawChipForAction(i: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
lastCommittedAmount(i, animator)?.let { amount ->
drawChip(amount, i, animator, canvas, context)
val winnerPots = animator.handHistory.winnerPots.firstOrNull { it.position == i }
winnerPots?.let { pot ->
val color = colorForAmount(pot.amount)
val circle = animator.animatedChipCircleFromPot(i)
drawChipCircle(circle, color, canvas, context)
}
}
}
}
}
private fun lastCommittedAmount(i: Int, animator: ReplayerAnimator): Double? {
var committingAction = animator.lastChipCommittingActionOfPlayer(i)
if (committingAction?.action?.type?.isCall == true) {
committingAction = committingAction.getStreetLastSignificantAction()
is ComputedAction -> {
drawChipForAction(i, canvas, context)
}
return committingAction?.action?.amount
}
/***
* WARNING: Avoid instancing objects here, as it's called from onDraw method
*/
private fun initializeTable(animator: ReplayerAnimator, canvas: Canvas, context: Context) {
canvas.drawColor(context.getColor(backgroundColor))
// Felt rosace
// val xr = animator.tableCornerXRadius
// val yr = animator.tableCornerYRadius
// val r = animator.tableRect
// val r1 = RectF(r.left, r.top, r.left + 2 * xr, r.top + 2 * yr)
// canvas.drawRoundRect(r1, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
// val r2 = RectF(r.right - 2 * xr, r.top, r.right, r.top + 2 * yr)
// canvas.drawRoundRect(r2, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
// val r3 = RectF(r.right - 2 * xr, r.bottom - 2 * yr, r.right, r.bottom)
// canvas.drawRoundRect(r3, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
// val r4 = RectF(r.left, r.bottom - 2 * yr, r.left + 2 * xr, r.bottom)
// canvas.drawRoundRect(r4, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
// val r5 = RectF(r.right - 2 * xr, r.centerY() - yr, r.right, r.centerY() + yr)
// canvas.drawRoundRect(r5, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
// val r6 = RectF(r.right - 2 * xr, r.top, r.right, r.top + 2 * yr)
// canvas.drawRoundRect(r6, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
// val r7 = RectF(r.left, r.centerY() - yr, r.left + 2 * xr, r.centerY() + yr)
// canvas.drawRoundRect(r7, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
// val r8 = RectF(r.left, r.bottom - 2 * yr, r.left + 2 * xr, r.bottom)
// canvas.drawRoundRect(r8, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint)
canvas.drawRoundRect(animator.tableRect, animator.tableCornerXRadius, animator.tableCornerYRadius, this.tablePaint)
this.cardTextPaint.textSize = animator.cardSpecs.height * .38f
val hh = animator.handHistory
for (i in 0 until hh.numberOfPlayers) {
drawPlayerRectangle(i,false, animator, canvas, context)
drawPlayerCircle(i, animator, canvas, context)
}
}
}
private fun drawChip(amount: Double, playerIndex: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
val circle = animator.chipCircle(playerIndex)
val text = animator.chipText(playerIndex)
drawChip(amount, text, circle, canvas, context)
private fun drawChipForAction(i: Int, canvas: Canvas, context: Context) {
lastCommittedAmount(i)?.let { amount ->
drawChip(amount, i, canvas, context)
}
}
private fun drawChip(amount: Double, chipText: ReplayerAnimator.TextPoint, chipCircle: ReplayerAnimator.Circle, canvas: Canvas, context: Context) {
val color = colorForAmount(amount)
drawChipCircle(chipCircle, color, canvas, context)
drawChipText(amount, chipText, canvas, context)
private fun lastCommittedAmount(i: Int): Double? {
var committingAction = animator.lastChipCommittingActionOfPlayer(i)
if (committingAction?.action?.type?.isCall == true) {
committingAction = committingAction.getStreetLastSignificantAction()
}
return committingAction?.action?.amount
}
private fun drawChipText(amount: Double, chipText: ReplayerAnimator.TextPoint, canvas: Canvas, context: Context) {
this.textPaint.textSize = chipText.fontSize
this.textPaint.color = context.getColor(R.color.white)
canvas.drawText(amount.formatted, chipText.x, chipText.y, this.textPaint)
}
/***
* WARNING: Avoid instancing objects here, as it's called from onDraw method
*/
private fun initializeTable(canvas: Canvas, context: Context) {
private fun colorForAmount(amount: Double): ChipColor {
canvas.drawColor(context.getColor(backgroundColor))
canvas.drawRoundRect(animator.tableRect, animator.tableCornerXRadius, animator.tableCornerYRadius, this.tablePaint)
this.colorsByAmount[amount]?.let { return it }
this.cardTextPaint.textSize = animator.cardSpecs.height * .38f
val index = this.colorsByAmount.size % ChipColor.all.size
val color = ChipColor.all[index]
this.colorsByAmount[amount] = color
return color
val hh = animator.handHistory
for (i in 0 until hh.numberOfPlayers) {
drawPlayerRectangle(i,false, canvas, context)
drawPlayerCircle(i, canvas, context)
}
private fun drawChipCircle(chipCircle: ReplayerAnimator.Circle, chipColor: ChipColor, canvas: Canvas, context: Context) {
this.fillPaint.color = context.getColor(chipColor.fillColor)
canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius, this.fillPaint)
this.chipBorderPaint.color = context.getColor(chipColor.borderColor)
val chipBorderStrokeWidth = chipCircle.radius / 3f
this.chipBorderPaint.strokeWidth = chipBorderStrokeWidth
}
chipBorderPaint.pathEffect = DashPathEffect(floatArrayOf(chipCircle.radius * 0.6f, chipCircle.radius * 0.44f), 0f)
canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius - chipBorderStrokeWidth / 2, this.chipBorderPaint)
private fun drawChip(amount: Double, playerIndex: Int, canvas: Canvas, context: Context) {
val circle = animator.chipCircle(playerIndex)
val text = animator.chipText(playerIndex)
drawChip(amount, text, circle, canvas, context)
}
}
private fun drawChip(amount: Double, chipText: ReplayerAnimator.TextPoint, chipCircle: ReplayerAnimator.Circle, canvas: Canvas, context: Context) {
val color = colorForAmount(amount)
drawChipCircle(chipCircle, color, canvas, context)
drawChipText(amount, chipText, canvas, context)
}
private fun drawPlayerCards(playerIndex: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
val playerSetup = animator.handHistory.playerSetupForPosition(playerIndex)
val cardRects = animator.cardRects(playerIndex)
val cards = playerSetup?.cards
val isHero = (animator.handHistory.heroIndex == playerIndex)
cardRects.forEachIndexed { j, cardRect ->
if (j < cards?.size ?: 0 && (animator.showVillainHands || isHero)) { // show card
val card = cards?.get(j)!! // tested line before
drawCard(card, cardRect, animator, canvas, context)
} else { // show hidden cards
fillPaint.color = context.getColor(R.color.card_fill)
canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint)
cardStrokePaint.color = context.getColor(R.color.card_border)
canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, cardStrokePaint)
}
private fun drawChipText(amount: Double, chipText: ReplayerAnimator.TextPoint, canvas: Canvas, context: Context) {
this.textPaint.textSize = chipText.fontSize
this.textPaint.color = context.getColor(R.color.white)
canvas.drawText(amount.formatted, chipText.x, chipText.y, this.textPaint)
}
}
private fun colorForAmount(amount: Double): ChipColor {
}
this.colorsByAmount[amount]?.let { return it }
private fun drawCard(card: Card, cardRect: RectF, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
val index = this.colorsByAmount.size % ChipColor.all.size
val color = ChipColor.all[index]
this.colorsByAmount[amount] = color
return color
}
fillPaint.color = context.getColor(R.color.white)
canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint)
private fun drawChipCircle(chipCircle: ReplayerAnimator.Circle, chipColor: ChipColor, canvas: Canvas, context: Context) {
cardTextPaint.color = context.getColor(R.color.black)
val valueY = cardRect.top + animator.cardSpecs.height * 0.44f
canvas.drawText(card.formattedValue, cardRect.centerX(), valueY, cardTextPaint)
this.fillPaint.color = context.getColor(chipColor.fillColor)
canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius, this.fillPaint)
this.chipBorderPaint.color = context.getColor(chipColor.borderColor)
val chipBorderStrokeWidth = chipCircle.radius / 3f
this.chipBorderPaint.strokeWidth = chipBorderStrokeWidth
val suit = card.suit ?: Card.Suit.UNDEFINED
cardTextPaint.color = context.getColor(suit.color)
val suitY = cardRect.top + animator.cardSpecs.height * 0.88f
canvas.drawText(suit.value, cardRect.centerX(), suitY, cardTextPaint)
chipBorderPaint.pathEffect = DashPathEffect(floatArrayOf(chipCircle.radius * 0.6f, chipCircle.radius * 0.44f), 0f)
canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius - chipBorderStrokeWidth / 2, this.chipBorderPaint)
}
}
private fun drawPlayerCircle(i: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
val playerSetup = animator.handHistory.playerSetupForPosition(i)
// Player portrait zone
val circle = animator.circleForPlayer(i)
val radius = circle.radius
this.fillPaint.color = context.getColor(R.color.green_darker)
canvas.drawCircle(circle.x, circle.y, radius, this.fillPaint)
this.strokePaint.color = context.getColor(R.color.green)
canvas.drawCircle(circle.x, circle.y, radius, this.strokePaint)
val playerInitials = playerSetup?.player?.initials ?: RANDOM_PLAYER
this.textPaint.textSize = radius
canvas.drawText(playerInitials, circle.x, circle.y + radius * 0.4f, this.textPaint)
// Player picture
playerSetup?.player?.picture?.let { picturePath ->
val pictureBitmap = RoundedBitmapDrawableFactory.create(context.resources, picturePath)
pictureBitmap.setAntiAlias(true)
pictureBitmap.cornerRadius = circle.radius
pictureBitmap.bitmap?.let { bitmap ->
val pictureRect = RectF(circle.x - radius, circle.y - radius, circle.x + radius, circle.y + radius)
canvas.drawBitmap(bitmap, null, pictureRect, null)
}
private fun drawPlayerCards(playerIndex: Int, canvas: Canvas, context: Context) {
val playerSetup = animator.handHistory.playerSetupForPosition(playerIndex)
val cardRects = animator.cardRects(playerIndex)
val cards = playerSetup?.cards
val isHero = (animator.handHistory.heroIndex == playerIndex)
cardRects.forEachIndexed { j, cardRect ->
if (j < cards?.size ?: 0 && (animator.showVillainHands || isHero)) { // show card
val card = cards?.get(j)!! // tested line before
drawCard(card, cardRect, canvas, context)
} else { // show hidden cards
fillPaint.color = context.getColor(R.color.card_fill)
canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint)
cardStrokePaint.color = context.getColor(R.color.card_border)
canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, cardStrokePaint)
}
}
/***
* [i] is the player position in the hand
*/
private fun drawPlayerRectangle(i: Int, highlighted: Boolean, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
val rect = animator.stackRectForPlayer(i)
val rectRadius = (rect.bottom - rect.top) / 4
val color = if (highlighted) R.color.kaki else R.color.green_darker
fillPaint.color = context.getColor(color)
canvas.drawRoundRect(
animator.stackRectForPlayer(i),
rectRadius,
rectRadius,
this.fillPaint
)
strokePaint.color = context.getColor(R.color.green)
canvas.drawRoundRect(
animator.stackRectForPlayer(i),
rectRadius,
rectRadius,
this.strokePaint
)
}
if (i == animator.handHistory.numberOfPlayers - 1) { // refresh dealer button
drawDealerButton(animator, canvas, context)
}
}
private fun drawCard(card: Card, cardRect: RectF, canvas: Canvas, context: Context) {
private fun drawDealerButton(animator: ReplayerAnimator, canvas: Canvas, context: Context) {
fillPaint.color = context.getColor(R.color.white)
canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint)
// Dealer button
val dealerCircle = animator.dealerCircle
this.fillPaint.color = context.getColor(R.color.red)
canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, fillPaint)
this.strokePaint.color = context.getColor(R.color.white)
canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, strokePaint)
this.textPaint.textSize = dealerCircle.radius
canvas.drawText("D", dealerCircle.x, dealerCircle.y + this.textPaint.textSize / 3, this.textPaint)
cardTextPaint.color = context.getColor(R.color.black)
val valueY = cardRect.top + animator.cardSpecs.height * 0.44f
canvas.drawText(card.formattedValue, cardRect.centerX(), valueY, cardTextPaint)
}
val suit = card.suit ?: Card.Suit.UNDEFINED
cardTextPaint.color = context.getColor(suit.color)
val suitY = cardRect.top + animator.cardSpecs.height * 0.88f
canvas.drawText(suit.value, cardRect.centerX(), suitY, cardTextPaint)
private fun drawPositionAndInfo(i: Int, secondLine: String?, animator: ReplayerAnimator, canvas: Canvas) {
val hh = animator.handHistory
// Player position
val positions = Position.positionsPerPlayers(hh.numberOfPlayers)
val name = positions.elementAt(i).value
val pnPoint = animator.pointForPlayerName(i)
this.textPaint.textSize = pnPoint.fontSize
canvas.drawText(name, pnPoint.x, pnPoint.y, this.textPaint)
// Player stack
secondLine?.let {
val psPoint = animator.pointForPlayerStack(i)
this.textPaint.textSize = psPoint.fontSize
canvas.drawText(it, psPoint.x, psPoint.y, this.textPaint)
}
private fun drawPlayerCircle(i: Int, canvas: Canvas, context: Context) {
val playerSetup = animator.handHistory.playerSetupForPosition(i)
// Player portrait zone
val circle = animator.circleForPlayer(i)
val radius = circle.radius
this.fillPaint.color = context.getColor(R.color.green_darker)
canvas.drawCircle(circle.x, circle.y, radius, this.fillPaint)
this.strokePaint.color = context.getColor(R.color.green)
canvas.drawCircle(circle.x, circle.y, radius, this.strokePaint)
val playerInitials = playerSetup?.player?.initials ?: RANDOM_PLAYER
this.textPaint.textSize = radius
canvas.drawText(playerInitials, circle.x, circle.y + radius * 0.4f, this.textPaint)
// Player picture
playerSetup?.player?.picture?.let { picturePath ->
val pictureBitmap = RoundedBitmapDrawableFactory.create(context.resources, picturePath)
pictureBitmap.setAntiAlias(true)
pictureBitmap.cornerRadius = circle.radius
pictureBitmap.bitmap?.let { bitmap ->
val pictureRect = RectF(circle.x - radius, circle.y - radius, circle.x + radius, circle.y + radius)
canvas.drawBitmap(bitmap, null, pictureRect, null)
}
}
}
/***
* [i] is the player position in the hand
*/
private fun drawPlayerRectangle(i: Int, highlighted: Boolean, canvas: Canvas, context: Context) {
val rect = animator.stackRectForPlayer(i)
val rectRadius = (rect.bottom - rect.top) / 4
val color = if (highlighted) R.color.kaki else R.color.green_darker
fillPaint.color = context.getColor(color)
canvas.drawRoundRect(
animator.stackRectForPlayer(i),
rectRadius,
rectRadius,
this.fillPaint
)
strokePaint.color = context.getColor(R.color.green)
canvas.drawRoundRect(
animator.stackRectForPlayer(i),
rectRadius,
rectRadius,
this.strokePaint
)
if (i == animator.handHistory.numberOfPlayers - 1) { // refresh dealer button
drawDealerButton(canvas, context)
}
}
private fun drawAction(i: Int, action: ComputedAction, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
action.action.type?.let { type ->
val actionName = context.getString(type.resId)
val actionPoint: ReplayerAnimator.TextPoint
if (!type.isPassive) { // show action + amount
actionPoint = animator.pointForPlayerName(i)
action.action.displayedAmount?.let { amount ->
val amountPoint = animator.pointForPlayerStack(i)
this.textPaint.textSize = amountPoint.fontSize
canvas.drawText(amount.formatted, amountPoint.x, amountPoint.y, this.textPaint)
}
} else { // show action name only
actionPoint = animator.pointForPlayerAction(i)
}
this.textPaint.textSize = actionPoint.fontSize
canvas.drawText(actionName, actionPoint.x, actionPoint.y, this.textPaint)
private fun drawDealerButton(canvas: Canvas, context: Context) {
}
// Dealer button
val dealerCircle = animator.dealerCircle
this.fillPaint.color = context.getColor(R.color.red)
canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, fillPaint)
this.strokePaint.color = context.getColor(R.color.white)
canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, strokePaint)
this.textPaint.textSize = dealerCircle.radius
canvas.drawText("D", dealerCircle.x, dealerCircle.y + this.textPaint.textSize / 3, this.textPaint)
}
private fun drawPositionAndInfo(i: Int, secondLine: String?, canvas: Canvas) {
val hh = animator.handHistory
// Player position
val positions = Position.positionsPerPlayers(hh.numberOfPlayers)
val name = positions.elementAt(i).value
val pnPoint = animator.pointForPlayerName(i)
this.textPaint.textSize = pnPoint.fontSize
canvas.drawText(name, pnPoint.x, pnPoint.y, this.textPaint)
// Player stack
secondLine?.let {
val psPoint = animator.pointForPlayerStack(i)
this.textPaint.textSize = psPoint.fontSize
canvas.drawText(it, psPoint.x, psPoint.y, this.textPaint)
}
private fun drawBoardCards(street: Street, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
}
val cards = animator.handHistory.cardsForStreet(street)
animator.boardCardRects.take(street.totalBoardCards).forEachIndexed { index, rectF ->
if (index < cards.size) {
drawCard(cards[index], rectF, animator, canvas, context)
private fun drawAction(i: Int, action: ComputedAction, canvas: Canvas, context: Context) {
action.action.type?.let { type ->
val actionName = context.getString(type.resId)
val actionPoint: ReplayerAnimator.TextPoint
if (!type.isPassive) { // show action + amount
actionPoint = animator.pointForPlayerName(i)
action.action.displayedAmount?.let { amount ->
val amountPoint = animator.pointForPlayerStack(i)
this.textPaint.textSize = amountPoint.fontSize
canvas.drawText(amount.formatted, amountPoint.x, amountPoint.y, this.textPaint)
}
} else { // show action name only
actionPoint = animator.pointForPlayerAction(i)
}
this.textPaint.textSize = actionPoint.fontSize
canvas.drawText(actionName, actionPoint.x, actionPoint.y, this.textPaint)
}
private fun drawPot(street: Street, computedAction: ComputedAction?, animator: ReplayerAnimator, canvas: Canvas, context: Context) {
}
private fun drawBoardCards(street: Street, canvas: Canvas, context: Context) {
if (street == Street.SUMMARY && animator.frameType != FrameType.GATHER_ANIMATION) {
return
val cards = animator.handHistory.cardsForStreet(street)
animator.boardCardRects.take(street.totalBoardCards).forEachIndexed { index, rectF ->
if (index < cards.size) {
drawCard(cards[index], rectF, canvas, context)
}
}
val pot = animator.actionList.potSizeForStreet(street)
}
val action = computedAction ?: animator.lastActionBeforeStreet(street)
val totalPot = action?.let {
animator.actionList.totalPotSize(it.action.index + 1)
} ?: run {
pot
}
private fun drawPot(street: Street, computedAction: ComputedAction?, canvas: Canvas, context: Context) {
drawChip(pot, animator.potTextPoint, animator.potChipCircle, canvas, context)
val tpTextPoint = animator.totalPotTextPoint
this.textPaint.textSize = tpTextPoint.fontSize
this.textPaint.color = context.getColor(R.color.white)
if (street == Street.SUMMARY && animator.frameType != FrameType.GATHER_ANIMATION) {
return
}
canvas.drawText(totalPot.formatted, animator.totalPotTextPoint.x, animator.totalPotTextPoint.y, this.textPaint)
val pot = animator.actionList.potSizeForStreet(street)
val action = computedAction ?: animator.lastActionBeforeStreet(street)
val totalPot = action?.let {
animator.actionList.totalPotSize(it.action.index + 1)
} ?: run {
pot
}
drawChip(pot, animator.potTextPoint, animator.potChipCircle, canvas, context)
val tpTextPoint = animator.totalPotTextPoint
this.textPaint.textSize = tpTextPoint.fontSize
this.textPaint.color = context.getColor(R.color.white)
canvas.drawText(totalPot.formatted, animator.totalPotTextPoint.x, animator.totalPotTextPoint.y, this.textPaint)
}
}

Loading…
Cancel
Save