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 c135887..1e97112 100644 Binary files a/app/build/tmp/kotlin-classes/debug/com/example/memorygame/BoardActivity.class and b/app/build/tmp/kotlin-classes/debug/com/example/memorygame/BoardActivity.class differ 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