From b853e8f98a84630e6973402740a5f9fc21e4b6d7 Mon Sep 17 00:00:00 2001 From: Diego Recalde Date: Thu, 16 Apr 2020 16:18:44 -0400 Subject: [PATCH] Fixed matching cards issues. Signed-off-by: Diego Recalde --- .../example/memorygame/BoardActivity.class | Bin 8793 -> 9815 bytes .../com/example/memorygame/BoardActivity.kt | 39 ++++--- .../com/example/memorygame/BoardAdapter.kt | 34 +++--- .../com/example/memorygame/BoardUseCase.kt | 10 +- .../com/example/memorygame/BoardViewModel.kt | 106 ++++++++---------- app/src/main/res/layout/activity_board.xml | 13 +-- 6 files changed, 104 insertions(+), 98 deletions(-) diff --git a/app/build/tmp/kotlin-classes/debug/com/example/memorygame/BoardActivity.class b/app/build/tmp/kotlin-classes/debug/com/example/memorygame/BoardActivity.class index c135887b4d22d38543bf0c666845c96d5badf3e2..1e971123d95812bb3b1c8073958c3f9a6e64b5ef 100644 GIT binary patch delta 2921 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

V`qCaVF3Ae% z$Rl2~@2g)$m7FnJO|UGQwkqnNR~a_Y8WUAx@4POoLWNeq|tELVyoY}B!-?>M2iiyl#k?MFsyzB5$-fQK~6J+7| zxfNGn87#0ek^SOr*LgZ1p33U2y-zkN%se`g02hjpg$B6!%SI#1!z>>{4xT|SUPeC7 zU?nC|KnI1>?XoL;&OqR9<%ty!qYuRJ9qy+Y{))T3RKn-O$YU{smoQ{0yzsz>GWdBf zfO0&B3WnR2U6H(>?VjW?h-Hg7ncdtn!81J_;E~JWHDEyDk0j~}=y_bt=v8Rudv=gl z=DCxMc$`;OSw)TR<(#aIAA2Toy6*yv)|aNy6T_9ep~t%o7+(Utj^D(dvA22w#nk38XM}?A`Ireo!*0V5qrts})7+8G2=wLbYGWJ=%aeB>zok;?9 zj8YFfHgcXD+0!PCL@pDvA{9U2NA@JsH%CX#ZsIMbXq6W}?%T&zmFX%N^LWnTl=Y`+ z-0rjfK8@D1m}Sbd8s$GFWx`@aiJ*f9%q*eX#5LT)#cf`a$%6Sz5-m6k#2prC{%oX^ z4sEoo_kXzb{nO<(q@xvuXj|g4Xx^oK>a=FMn3pq_$l$5Q7$q^IPL0tpGn%Ai_ME~x zeT=Mkt74SC$d|i?E70E0-(LPS-d1J;baKOcQG+hDWBZaoI^uy4KR06;vW?g{z_pQW z=Pa|4bDG@jb4Uy40S>1uD@HksE;+@!=bM%1FkpuUFY7CB+k91><>z&}4qI~I0r*V;g3gCw2cZJ(EZ>4Q)B^AV5q>gn`o9J;( zQPJWV85EzkRAiq@W`D!JpDrn(q4oTE+N@(ZwgTrq0)&MV4e)Y@Bqh65+T7+I*URB zZQ_l#g)|j($McjT51!|H$s$IbGU~a5dJw;e#WfW-RoqaCR9shaNySxZK>dFMNf)7Z diff --git a/app/src/main/java/com/example/memorygame/BoardActivity.kt b/app/src/main/java/com/example/memorygame/BoardActivity.kt index 496e78d..74da706 100644 --- a/app/src/main/java/com/example/memorygame/BoardActivity.kt +++ b/app/src/main/java/com/example/memorygame/BoardActivity.kt @@ -2,7 +2,6 @@ package com.example.memorygame import android.media.MediaPlayer import android.os.Bundle -import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import androidx.appcompat.app.AppCompatActivity @@ -13,7 +12,6 @@ import androidx.recyclerview.widget.GridLayoutManager import com.example.memorygame.LobbyActivity.Companion.EXTRA_DIFICULTY import com.example.memorygame.databinding.ActivityBoardBinding - class BoardActivity : AppCompatActivity(), BoardAdapter.OnCardItemListener { private lateinit var boardAdapter: BoardAdapter @@ -26,8 +24,12 @@ class BoardActivity : AppCompatActivity(), BoardAdapter.OnCardItemListener { super.onCreate(savedInstanceState) difficulty = intent.getSerializableExtra(EXTRA_DIFICULTY) as BoardDifficulty binding = DataBindingUtil.setContentView(this, R.layout.activity_board) - viewModel = ViewModelProvider(this, BoardViewModelFactory(difficulty, BoardUseCaseImp())) - .get(BoardViewModel::class.java) + + viewModel = ViewModelProvider( + this, + BoardViewModelFactory(difficulty, BoardUseCaseImp()) + ).get(BoardViewModel::class.java) + binding.viewModel = viewModel binding.lifecycleOwner = this @@ -38,7 +40,7 @@ class BoardActivity : AppCompatActivity(), BoardAdapter.OnCardItemListener { private fun initViews() { binding.boardTableRv.apply { layoutManager = GridLayoutManager(this@BoardActivity, difficulty.columns) - boardAdapter = BoardAdapter( this@BoardActivity) + boardAdapter = BoardAdapter(this@BoardActivity) adapter = boardAdapter } binding.boardBackBtn.setOnClickListener { onBackPressed() } @@ -46,23 +48,31 @@ class BoardActivity : AppCompatActivity(), BoardAdapter.OnCardItemListener { private fun initObservers() { viewModel.score.observe(this, Observer { onScoreChanged(it) }) - viewModel.cardList.observe(this, Observer { updateAdapter(it) }) + viewModel.cardList.observe(this, Observer { + boardAdapter.setCards(it) + }) + viewModel.firstCardState.observe(this, Observer { + updaterItemOnAdapter(it.first, it.second) + }) + viewModel.secondCardState.observe(this, Observer { + updaterItemOnAdapter(it.first, it.second) + }) } - private fun updateAdapter(cardList: MutableList) { - boardAdapter.submitCardList(cardList) - boardAdapter.notifyDataSetChanged() + private fun updaterItemOnAdapter(position: Int, state: State) { + boardAdapter.notifyItemChanged(position, state) } private fun onScoreChanged(score: Int) { val isWinner: Boolean = viewModel.areYouWinner(score) - if (isWinner) + if (isWinner) { playWinnerAnimation() - else - playMatch() + } else { + playMatchSuccess() + } } - private fun playMatch() { + private fun playMatchSuccess() { mediaPlayer = MediaPlayer.create(this, R.raw.game_match) mediaPlayer.start() } @@ -78,6 +88,5 @@ class BoardActivity : AppCompatActivity(), BoardAdapter.OnCardItemListener { binding.boardLottieAnimation.playAnimation() } - override fun onCardClicked(card: Card, pos: Int) = viewModel.onCardClicked(card, pos) + override fun onCardClicked(card: Card, position: Int) = viewModel.onCardClicked(card, position) } - diff --git a/app/src/main/java/com/example/memorygame/BoardAdapter.kt b/app/src/main/java/com/example/memorygame/BoardAdapter.kt index a3a3b26..8b4c209 100644 --- a/app/src/main/java/com/example/memorygame/BoardAdapter.kt +++ b/app/src/main/java/com/example/memorygame/BoardAdapter.kt @@ -1,6 +1,6 @@ package com.example.memorygame -import android.view.LayoutInflater +import android.view.LayoutInflater.from import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -9,11 +9,11 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.example.memorygame.State.CLOSE +class BoardAdapter( + private val listener: OnCardItemListener +) : RecyclerView.Adapter() { -class BoardAdapter(private val listener: OnCardItemListener) : - RecyclerView.Adapter() { - - private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + private val diffCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Card, newItem: Card): Boolean { return oldItem.character == newItem.character && oldItem.state == newItem.state } @@ -21,13 +21,11 @@ class BoardAdapter(private val listener: OnCardItemListener) : override fun areContentsTheSame(oldItem: Card, newItem: Card): Boolean { return oldItem.character == newItem.character && oldItem.state == newItem.state } - } - private val differ = AsyncListDiffer(this, DIFF_CALLBACK) + private val differ = AsyncListDiffer(this, diffCallback) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardHolder { - val view = - LayoutInflater.from(parent.context).inflate(R.layout.item_board_layout, parent, false) + val view = from(parent.context).inflate(R.layout.item_board_layout, parent, false) return CardHolder(view) } @@ -39,19 +37,27 @@ class BoardAdapter(private val listener: OnCardItemListener) : holder.bind(differ.currentList[position], listener) } - fun submitCardList(cards: List) { - differ.submitList(cards) + override fun onBindViewHolder(holder: CardHolder, position: Int, payloads: MutableList) { + if (payloads.isNotEmpty()) { + val item = differ.currentList[position].copy(state = payloads.first() as State) + holder.bind(item, listener) + } else { + super.onBindViewHolder(holder, position, payloads) + } } + fun setCards(cards: List) = differ.submitList(cards) + class CardHolder(private val view: View) : RecyclerView.ViewHolder(view) { + private val image: ImageView = view.findViewById(R.id.item_board_card) as ImageView fun bind(card: Card, listener: OnCardItemListener) { - - if (card.state == CLOSE) + if (card.state == CLOSE) { image.setBackgroundResource(R.drawable.ic_card_cover) - else + } else { image.setBackgroundResource(card.character.resource) + } view.setOnClickListener { listener.onCardClicked(card, adapterPosition) diff --git a/app/src/main/java/com/example/memorygame/BoardUseCase.kt b/app/src/main/java/com/example/memorygame/BoardUseCase.kt index 784955a..ef2e715 100644 --- a/app/src/main/java/com/example/memorygame/BoardUseCase.kt +++ b/app/src/main/java/com/example/memorygame/BoardUseCase.kt @@ -18,6 +18,7 @@ interface BoardUseCase { } class BoardUseCaseImp : BoardUseCase { + private val cardSet = setOf( Card(BAT), Card(COW), @@ -37,20 +38,21 @@ class BoardUseCaseImp : BoardUseCase { override fun getCardsToPlay(numberOfCharacters: Int): List { val characters = getCharacters(numberOfCharacters) - var cardList = mutableListOf() + val cardList = mutableListOf() cardList.addAll(characters.shuffled()) cardList.addAll(characters.shuffled()) return cardList } override fun verifyMatch(firstCard: Card?, secondCard: Card?): Boolean = - firstCard != null && secondCard != null && (firstCard?.character == secondCard?.character) + firstCard != null && secondCard != null && (firstCard.character == secondCard.character) override fun areYouWinner(score: Int, difficulty: BoardDifficulty): Boolean { val maxScore = getMaxScore(difficulty) return score == maxScore } - private fun getMaxScore(difficulty: BoardDifficulty) = - (difficulty.columns * difficulty.rows) / 2 + private fun getMaxScore( + difficulty: BoardDifficulty + ) = (difficulty.columns * difficulty.rows) / 2 } \ No newline at end of file diff --git a/app/src/main/java/com/example/memorygame/BoardViewModel.kt b/app/src/main/java/com/example/memorygame/BoardViewModel.kt index 6f09d10..9909250 100644 --- a/app/src/main/java/com/example/memorygame/BoardViewModel.kt +++ b/app/src/main/java/com/example/memorygame/BoardViewModel.kt @@ -1,101 +1,91 @@ package com.example.memorygame +import android.os.Handler import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.example.memorygame.State.CLOSE -import com.example.memorygame.State.MATCHED import com.example.memorygame.State.OPEN - class BoardViewModel( private val difficulty: BoardDifficulty, private val boardUseCase: BoardUseCase ) : ViewModel() { - private val INVALID_POSITION = -1 private val maxScore = (difficulty.columns * difficulty.rows) / 2 private var isWaitingForMatch = false - private var firstCardPos = INVALID_POSITION - private var secondCardPos = INVALID_POSITION - private var _isBoardActive = MutableLiveData(true) - val isBoardActive: LiveData get() = _isBoardActive + private var _isBoardActive = MutableLiveData(true) + val isBoardActive: LiveData + get() = _isBoardActive + + private var _firstCardState = MutableLiveData>() + val firstCardState: LiveData> + get() = _firstCardState - private var _cardList = MutableLiveData(boardUseCase.getCardsToPlay(maxScore).toMutableList()) - val cardList: LiveData> get() = _cardList + private var _secondCardState = MutableLiveData>() + val secondCardState: LiveData> + get() = _secondCardState - private val _score = MutableLiveData(0) + private var _cardList = MutableLiveData>() + val cardList: LiveData> + get() = _cardList + + private val _score = MutableLiveData(INITIAL_SCORE) val score: LiveData get() = _score + init { + getCards() + } + + private fun getCards() { + _cardList.value = boardUseCase.getCardsToPlay(maxScore).toMutableList() + } + fun getMaxScore() = maxScore fun onCardClicked(clickedCard: Card, position: Int) { if (clickedCard.state == CLOSE) { - openCard(clickedCard, position) - if (!isWaitingForMatch) { - firstCardPos = position - } else { - secondCardPos = position - lockScreenBy(10000) + if (isWaitingForMatch) { + _secondCardState.value = Pair(position, OPEN) verifyMatch() + } else { + _firstCardState.value = Pair(position, OPEN) } isWaitingForMatch = !isWaitingForMatch } } private fun verifyMatch() { - val firstCard = cardList.value?.get(firstCardPos) - val secondCard = cardList.value?.get(secondCardPos) - val matchResult = boardUseCase.verifyMatch(firstCard, secondCard) - if (matchResult) { + _isBoardActive.value = false + + val firstPosition = firstCardState.value?.first ?: INVALID_POSITION + val secondPosition = secondCardState.value?.first ?: INVALID_POSITION + + val firstCard = cardList.value?.get(firstPosition) + val secondCard = cardList.value?.get(secondPosition) + if (boardUseCase.verifyMatch(firstCard, secondCard)) { val currentScore = score.value ?: 0 - matchCards(firstCard, secondCard) _score.value = currentScore + 1 + _isBoardActive.value = true } else { - closeCards(firstCard, secondCard) + Handler().postDelayed({ + _firstCardState.value = Pair(firstPosition, CLOSE) + _secondCardState.value = Pair(secondPosition, CLOSE) + _isBoardActive.value = true + }, REVEAL_WAIT_TIME_MILLIS) } - firstCardPos = INVALID_POSITION - secondCardPos = INVALID_POSITION - } - - private fun lockScreenBy(timeMillis: Long) { - _isBoardActive.value = false - Thread.sleep(1000) - _isBoardActive.value = true - } - - private fun matchCards(firstCard: Card?, secondCard: Card?) { - firstCard?.let { matchCard(firstCard, firstCardPos) } - secondCard?.let { matchCard(secondCard, secondCardPos) } - } - - private fun closeCards(firstCard: Card?, secondCard: Card?) { - firstCard?.let { closeCard(firstCard, firstCardPos) } - secondCard?.let { closeCard(secondCard, secondCardPos) } - } - - private fun changeCardState(card: Card, position: Int, newState: State) { - val copyCards = cardList.value ?: mutableListOf() - copyCards[position] = card.copy(state = newState) - _cardList.value = copyCards - } - - private fun openCard(card: Card, position: Int) { - changeCardState(card, position, OPEN) - } - - private fun closeCard(card: Card, position: Int) { - changeCardState(card, position, CLOSE) - } - - private fun matchCard(card: Card, position: Int) { - changeCardState(card, position, MATCHED) } fun areYouWinner(score: Int): Boolean { return boardUseCase.areYouWinner(score, difficulty) } + + companion object { + private const val INITIAL_SCORE = 0 + private const val INVALID_POSITION = -1 + private const val REVEAL_WAIT_TIME_MILLIS = 500L + } } diff --git a/app/src/main/res/layout/activity_board.xml b/app/src/main/res/layout/activity_board.xml index 228d154..79b5314 100644 --- a/app/src/main/res/layout/activity_board.xml +++ b/app/src/main/res/layout/activity_board.xml @@ -4,7 +4,6 @@ xmlns:tools="http://schemas.android.com/tools"> - @@ -57,23 +56,23 @@ android:textColor="@color/colorPrimaryDark" android:textSize="16sp" android:textStyle="bold" + android:layout_marginEnd="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/board_progress" - tools:text="@string/board_score_label" - android:layout_marginEnd="16dp" /> + tools:text="@string/board_score_label" /> @@ -81,14 +80,14 @@ android:id="@+id/board_lottie_animation" android:layout_width="0dp" android:layout_height="0dp" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:lottie_loop="true" - app:lottie_autoPlay="false" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/board_score_tv" + app:lottie_autoPlay="false" app:lottie_fileName="winner_animation.json" - android:visibility="gone"/> + app:lottie_loop="true" /> \ No newline at end of file