Last active
February 4, 2024 07:05
-
-
Save gold24park/aac348be0ba43b0ac459fdf77510596b to your computer and use it in GitHub Desktop.
StateMachine with Kotlin (Legend of Zelda)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kotlinx.coroutines.delay | |
import kotlinx.coroutines.runBlocking | |
import kotlin.random.Random | |
enum class GameEvent(val message: String): Event { | |
OnPlayerEnterArea("- 플레이어가 보코블린의 시야에 들어옵니다. -"), | |
OnPlayerLeaveArea("- 플레이어가 보코블린의 시야를 벗어납니다. -"), | |
CloseToAttack("- 보코블린의 타격 범위안에 플레이어가 있습니다. -"), | |
OnPlayerAttack("- 플레이어가 공격합니다 -"), | |
OnHealthZero("- 보코블린의 체력이 0이 되었습니다. -") | |
} | |
sealed interface MonsterState : State { | |
data object Rest : MonsterState { | |
override fun onEnter() { | |
println("보코블린이 앉아서 쉬고 있다.") | |
} | |
override fun onPreLeave() { | |
super.onPreLeave() | |
println("보코블린이 일어선다.") | |
} | |
} | |
data object Alert : MonsterState { | |
override fun onPreEnter() { | |
println("보코블린 머리위에 ! 를 띄운다.") | |
} | |
override fun onEnter() { | |
println("보코블린이 플레이어에게 다가온다.") | |
} | |
override fun onPreLeave() { | |
println("보코블린 머리위에 ! 를 지운다.") | |
} | |
} | |
data object Attack : MonsterState { | |
override fun onPreEnter() { | |
println("보코블린이 등에 있던 곤봉을 꺼낸다.") | |
} | |
override fun onEnter() { | |
println("보코블린가 플레이어를 공격한다.") | |
} | |
} | |
data object Death : MonsterState { | |
override fun onPreEnter() { | |
println("보코블린이 비명을 지릅니다. 크아아아아악!") | |
} | |
override fun onEnter() { | |
println("보코블린이 쓰러졌습니다.") | |
} | |
} | |
} | |
interface State { | |
fun onPreEnter() {} | |
fun onPreLeave() {} | |
fun onEnter() {} | |
fun onLeave() {} | |
} | |
interface Event | |
abstract class BaseStateMachine<T : State>( | |
private var currentState: T | |
) { | |
init { | |
currentState.onPreEnter() | |
currentState.onEnter() | |
} | |
fun get() = currentState | |
fun set(next: T) { | |
if (currentState == next) { | |
return | |
} | |
currentState.onPreLeave() | |
next.onPreEnter() | |
currentState.onLeave() | |
currentState = next | |
next.onEnter() | |
} | |
} | |
abstract class FiniteStateMachine<T : State>( | |
initialState: T, | |
private val transitions: Map<Transition, T> | |
) : BaseStateMachine<T>(initialState) { | |
data class Transition( | |
val state: State, | |
val event: Event | |
) | |
fun onEvent(event: Event) { | |
val next = transitions[Transition(state = get(), event)] ?: return | |
set(next) | |
} | |
} | |
class MonsterStateMachine(initialState: MonsterState) : FiniteStateMachine<MonsterState>( | |
initialState = initialState, | |
transitions = mapOf( | |
Transition(state = MonsterState.Rest, event = GameEvent.OnPlayerEnterArea) to MonsterState.Alert, | |
Transition(state = MonsterState.Rest, event = GameEvent.OnPlayerAttack) to MonsterState.Attack, | |
Transition(state = MonsterState.Alert, event = GameEvent.CloseToAttack) to MonsterState.Attack, | |
Transition(state = MonsterState.Alert, event = GameEvent.OnPlayerAttack) to MonsterState.Attack, | |
Transition(state = MonsterState.Alert, event = GameEvent.OnPlayerLeaveArea) to MonsterState.Rest, | |
Transition(state = MonsterState.Attack, event = GameEvent.OnPlayerLeaveArea) to MonsterState.Rest, | |
Transition(state = MonsterState.Attack, event = GameEvent.OnHealthZero) to MonsterState.Death, | |
Transition(state = MonsterState.Rest, event = GameEvent.OnHealthZero) to MonsterState.Death, | |
Transition(state = MonsterState.Alert, event = GameEvent.OnHealthZero) to MonsterState.Death, | |
) | |
) | |
fun main(args: Array<String>) { | |
runBlocking { | |
var cnt = 0 | |
val monster = MonsterStateMachine(MonsterState.Rest) | |
while (cnt < 10) { | |
delay(Random.nextInt(5) * 1000L) | |
val event = GameEvent.values().random() | |
println(event.message) | |
monster.onEvent(event) | |
cnt++ | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment