Giter Club home page Giter Club logo

fsmgasm's Introduction

FSMgasm logo FSMgasm

FSMgasm is a Kotlin state machine library. It is useful to model complex systems, simplify code and facilitate code reuse. The library is available under MIT License.

FSMgasm was initially developed for Minecraft minigames. Here's a tutorial on how to use it with Spigot.

Install

FSMgasm is available on Maven through Jitpack.

Maven:

<repositories>
  <repository>
    <id>jitpack.io</id>
    <url>https://jitpack.io</url>
  </repository>
</repositories>
<dependency>
  <groupId>com.github.Minikloon</groupId>
  <artifactId>FSMgasm</artifactId>
  <version>-SNAPSHOT</version>
</dependency>

Gradle:

repositories {
  ...
  maven { url 'https://jitpack.io' }
}
dependencies {
  compile 'com.github.Minikloon:FSMgasm:-SNAPSHOT'
}

Usage

Using FSMgasm is about creating states and composing them together. A state is simple: it's something with a start, a duration and an end. Sometimes a state will also do stuff in-between.

Creating states

To create a state, override State:

class PrintState(val toPrint: String) : State() {
    override fun onStart() {
        println("Start: $toPrint")
    }

    override fun onUpdate() {
    }
    
    override fun onEnd() {
        println("End: $toPrint")
    }

    override val duration: Duration = Duration.ofSeconds(1)
}

Keep in mind FSMgasm doesn't handle state execution for you. This means there is no black magic behind using your newly-created state.

Using your state:

fun main(args: Array<String>) {
    val state = PrintState("Hello world!")
    state.start()
    state.end()
}

State does guarantee that onStart() and onEnd() will only be called once and that only a single onUpdate will be executed at a time. It also checks that start() has been called before continuing execution of update() and end(). These guarantees are retained in a multithreaded environment.

Composing states

There are two classes to help you compose states together.

StateSeries

StateSeries lets you compose your states sequentially. It is typical to use a state series as the "main state" of a system.

fun main(args: Array<String>) {
    val series = StateSeries(
        PrintState("State 1"),
        PrintState("State 2")
    )
    
    series.start()
    while(true) {
        series.update()
    }
}

StateSeries will take care of checking whether the current state is over and switch to the next state in its update method. Typically a state is over when it lasted for more than its duration. Duration is included in State because of how common it is. If your state doesn't need duration, you can override State::isReadyToEnd to setup your own ending condition.

You can setup a StateSeries either using the vararg constructor, a list of states, or adding them manually after construction using StateSeries::add. add will add a state to the end of the series and can be used after initialization. addNext can be used to add a state right after the current state.

What makes state composition with FSMgasm is that StateSeries extends State. This means you can do something like:

fun main(args: Array<String>) {
    val series = StateSeries(
            StateSeries(
                    PrintState("Sub-Series 1, State 1"),
                    PrintState("Sub-Series 1, State 2")
            ),
            StateSeries(
                    PrintState("Sub-Series 2, State 1"),
                    PrintState("Sub-Series 2, State 2"),
                    PrintState("Sub-Series 2, State 3")
            )
    )
    
    series.start()
    while(true) {
        series.update()
        Thread.sleep(10)
        if(series.ended)
            break
    }
}

Another feature of State (and thus StateSeries) is the frozen property.

series.frozen = true

This prevents State from ending and in the case of StateSeries, stops it from moving to the next state. Freezing a state series can be useful when testing and debugging.

StateGroup

StateGroup lets you compose your states concurrently. This doesn't mean they'll be executed on different threads. All the states within a StateGroup will be started on StateGroup::start, similarly with end.

fun main(args: Array<String>) {
    val group = StateGroup(
            PrintState("Hello"),
            PrintState("World!")
    )
    
    group.start()
    group.end()
}

StateGroup also extends State.

StateProxy

In some cases, you can't know all the states which are going to be needed at initialization ahead of time in a StateSeries.

For example, when modeling Build Battle, the game starts with 12 players all building at the same time for 5 minutes. After the build time, players are teleported to each build for 30 seconds one at a time for judging. Builds of players who left aren't available for judging. This situation can modeled like so:

StateSeries:
    1. StateGroup(12 x BuildState)
    2. PlayerCheckStateProxy => Creates 1 VoteState for each player still in the game
    3. AnnounceWinnerState

A StateProxy may be implemented like this:

class TwelveYearsAState(series: StateSeries) : StateProxy(series) {
    override fun createStates(): List<State> {
        return (1..12).map { 
            PrintState("Proxied State $it")
        }.toList()
    }
}

Or with the shortcut method:

val series = StateSeries()
series.add(stateProxy(series) {
    (1..12).map {
        PrintState("Proxied State $it")
    }.toList()
})

StateSwitch

Not all situations can be easily modeled using a StateSeries, for example a game's menus. The player's navigation through the menus could go as such:

MainMenuState => OptionMenuState => MainMenuState => StartGameState.

This is where StateSwitch comes into play. It's a simple class which can be used as such:

val switch = StateSwitch()
    switch.changeState(PrintState("First!"))
    switch.changeState(PrintState("Second!"))

StateSwitch::update is provided as a convenience method to update the underlying state.

fsmgasm's People

Contributors

minikloon avatar

Stargazers

 avatar Owen Shaule avatar Cco0lL avatar  avatar Ethan avatar SoraxDubbing avatar Tomáš Plánský avatar oHate avatar  avatar  avatar  avatar Art Shendrik avatar 0x31 avatar Semisol avatar Hamza Coşkun avatar Jerrick avatar dexit141 avatar HopefulForever avatar Duc Nguyen avatar Andrey avatar kirraObj avatar  avatar Vadim Hagedorn avatar  avatar  avatar LeeGod avatar tomotomo avatar Michel Arts avatar Ethan Smith avatar Subham K. avatar vytskalt avatar Mark Vainomaa avatar David Marsh avatar Cesar Marinho avatar nullicorn avatar Saylon avatar Joshua avatar Gabriel Souza avatar Andrew avatar George avatar Andrew avatar jojoe77777 avatar Yuku Kotani avatar Brandon Roberge avatar  avatar

Watchers

James Cloos avatar  avatar vytskalt avatar

fsmgasm's Issues

Allow startInstant to be modified after the state started

Hey,

I've been looking for a way to change the duration of a state while it already started. Think of for example cubecraft's pregame timer going down to 10 seconds when a certain amount of people hava joined. Could this be implemented in anyway? I made a not so elegant solution for this problem in a fork simply by just making startInstant to be modified (which then allows you to change startInstant to Instant.now() and then set the Duration to whatever you want for a new countdown). I assume there are way better ways to implement this.

TL;DR allow resetting and modifying the duration of a state after it already started

thanks.

Hope to contact you through the method

Hello Minikloon?,em first of all, I don't know if you will respond to me. My child was born in Hong Kong. He lives with me in Choi Choi Village. He is now 17 years old. He has been there since primary school. Now yesterday I was playing minecraft, he won't stop no matter how loud he screams, unfortunately he got cancer when Form 3, he told me at that time that he really liked watching Refraction videos. You once DMed him, and he went to watch your video. At that time, he had to stay in the hospital and took his laptop over there to use the laptop because the doctor told him about his cancer. There's a chance of relapse, and he just told me like he was brainwashing me every day that he wanted to experience the feeling of playing bedward with the admin. It's his birthday in exactly six days, and if you're really a little skeptical, you Can add my discord and I can send you my child's cancer report.... yeah. If you don't mind, anyways...I hope to get a reply from you here, his discord , hotdogs0001 , his minecraft game identity: 4KClip , Message from;; Ho Po Gei

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.