Skip to content

Instantly share code, notes, and snippets.

@gold24park
Last active February 4, 2024 07:05
Show Gist options
  • Save gold24park/aac348be0ba43b0ac459fdf77510596b to your computer and use it in GitHub Desktop.
Save gold24park/aac348be0ba43b0ac459fdf77510596b to your computer and use it in GitHub Desktop.
StateMachine with Kotlin (Legend of Zelda)
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