Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
39 changes: 24 additions & 15 deletions app/src/main/java/com/example/memorygame/BoardActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -38,31 +40,39 @@ 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() }
}

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<Card>) {
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()
}
Expand All @@ -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)
}

34 changes: 20 additions & 14 deletions app/src/main/java/com/example/memorygame/BoardAdapter.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -9,25 +9,23 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.example.memorygame.State.CLOSE

class BoardAdapter(
private val listener: OnCardItemListener
) : RecyclerView.Adapter<BoardAdapter.CardHolder>() {

class BoardAdapter(private val listener: OnCardItemListener) :
RecyclerView.Adapter<BoardAdapter.CardHolder>() {

private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Card>() {
private val diffCallback = object : DiffUtil.ItemCallback<Card>() {
override fun areItemsTheSame(oldItem: Card, newItem: Card): Boolean {
return oldItem.character == newItem.character && oldItem.state == newItem.state
}

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)
}

Expand All @@ -39,19 +37,27 @@ class BoardAdapter(private val listener: OnCardItemListener) :
holder.bind(differ.currentList[position], listener)
}

fun submitCardList(cards: List<Card>) {
differ.submitList(cards)
override fun onBindViewHolder(holder: CardHolder, position: Int, payloads: MutableList<Any>) {
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<Card>) = 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)
Expand Down
10 changes: 6 additions & 4 deletions app/src/main/java/com/example/memorygame/BoardUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface BoardUseCase {
}

class BoardUseCaseImp : BoardUseCase {

private val cardSet = setOf(
Card(BAT),
Card(COW),
Expand All @@ -37,20 +38,21 @@ class BoardUseCaseImp : BoardUseCase {

override fun getCardsToPlay(numberOfCharacters: Int): List<Card> {
val characters = getCharacters(numberOfCharacters)
var cardList = mutableListOf<Card>()
val cardList = mutableListOf<Card>()
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
}
106 changes: 48 additions & 58 deletions app/src/main/java/com/example/memorygame/BoardViewModel.kt
Original file line number Diff line number Diff line change
@@ -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<Boolean>(true)
val isBoardActive: LiveData<Boolean> get() = _isBoardActive
private var _isBoardActive = MutableLiveData(true)
val isBoardActive: LiveData<Boolean>
get() = _isBoardActive

private var _firstCardState = MutableLiveData<Pair<Int, State>>()
val firstCardState: LiveData<Pair<Int, State>>
get() = _firstCardState

private var _cardList = MutableLiveData(boardUseCase.getCardsToPlay(maxScore).toMutableList())
val cardList: LiveData<MutableList<Card>> get() = _cardList
private var _secondCardState = MutableLiveData<Pair<Int, State>>()
val secondCardState: LiveData<Pair<Int, State>>
get() = _secondCardState

private val _score = MutableLiveData(0)
private var _cardList = MutableLiveData<List<Card>>()
val cardList: LiveData<List<Card>>
get() = _cardList

private val _score = MutableLiveData(INITIAL_SCORE)
val score: LiveData<Int>
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
}
}
13 changes: 6 additions & 7 deletions app/src/main/res/layout/activity_board.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
xmlns:tools="http://schemas.android.com/tools">

<data>

<variable
name="viewModel"
type="com.example.memorygame.BoardViewModel" />
Expand Down Expand Up @@ -57,38 +56,38 @@
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" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/board_table_rv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="156dp"
android:clickable="@{viewModel.isBoardActive}"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/board_score_tv"
tools:itemCount="12"
android:clickable="@{viewModel.isBoardActive}"
tools:listitem="@layout/item_board_layout"
tools:spanCount="3" />

<com.airbnb.lottie.LottieAnimationView
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" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>