├── .gitattributes ├── .github └── workflows │ └── bump-version.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── MonoGameStateMachine ├── MonoGameStateMachine.csproj ├── MonoGameStateMachine.nuspec ├── Properties │ └── AssemblyInfo.cs ├── TimeUnit.cs ├── Timer.cs ├── app.config ├── icon.png └── packages.config ├── NUnitTests ├── Examples.cs ├── FluentAfterTests.cs ├── FluentSyntaxTest1.cs ├── FluentTests.cs ├── GameProgrammingPatterns1.cs ├── NUnitTests.csproj ├── Properties │ └── AssemblyInfo.cs ├── SimpleTests.cs ├── TestTools.cs ├── app.config └── packages.config ├── README.md ├── StateMachine.sln ├── StateMachine.sln.DotSettings ├── StateMachine ├── Events │ ├── IfArgs.cs │ ├── StateChangeArgs.cs │ └── UpdateArgs.cs ├── Fluent │ └── Api │ │ ├── BuilderFluent.cs │ │ ├── FluentImplementation.cs │ │ ├── GlobalTransitionBuilderFluent.cs │ │ ├── GlobalTransitionFluent.cs │ │ ├── StateFluent.cs │ │ ├── TransitionFluent.cs │ │ └── TransitionStateFluent.cs ├── Fsm.cs ├── FsmBuilderException.cs ├── FsmModel.cs ├── Properties │ └── AssemblyInfo.cs ├── State.cs ├── StateMachine.csproj ├── StateMachine.nuspec ├── StateModel.cs ├── TimeUnit.cs ├── Timer.cs ├── Transition.cs ├── TransitionModel.cs └── icon.png ├── TestGame ├── Content │ ├── Content.mgcb │ └── spaceman.png ├── Game1.cs ├── Icon.ico ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TestGame.csproj ├── app.config ├── app.manifest └── packages.config └── docs ├── button.png ├── jumping_diving.png ├── standing_ducking.png └── walking_running.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Bump version 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | with: 14 | fetch-depth: '0' 15 | - name: Bump version and push tag 16 | uses: anothrNick/github-tag-action@master 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | RELEASE_BRANCHES: master 20 | DEFAULT_BUMP: patch 21 | WITH_V: false 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ######################## 2 | ### VARIABLES NEEDED ### 3 | ######################## 4 | # SOLUTION_NAME The name without file-ending 5 | # DEPLOY_BUILD Which build you want to pack and push to nuget('Debug' or 'Release') 6 | # MONOGAME Set it if you like to build a MonoGame project 7 | # ('latest' or a specific version number of a release on their GitHub 8 | # page like 'v3.7.1'), else unset it (or set it to 'false') 9 | # NUGET Whether or not to deploy the result as a nuget-package (true if set) 10 | # If you don't want to deploy, unset it (or set it to 'false') 11 | # 12 | ### Deploy 13 | # NUGET_API_KEY Your key from nuget.org 14 | # NUGET_PROJECT_FILENAME The name without file-ending 15 | # NUGET_PROJECT_PATH The path to your project (starting at git-repo-root; which is /) 16 | # 17 | # GITHUB_API_KEY Your key from your GitHub repository (to push the release-zip). 18 | # Has to be a personal API-key (Your GitHub-user-image -> Settings -> 19 | # Developer settings -> Personal access tokens). Has to have the 'repo' 20 | # permission (top level). 21 | # BUILD_TARGET The directory your build is saved in (to get it zipped for a 22 | # GitHub release) 23 | # TEST_NUNIT_FILE Set to path and filename of artifact containing the tests if you want 24 | # to run NUNIT tests. Unset or set to 'false' to not run tests 25 | # TEST_XUNIT_FILE Set to path and filename of artifact containing the tests if you want 26 | # to run XUNIT tests. Unset or set to 'false' to not run tests 27 | # 28 | #################### 29 | ### GENERAL INFO ### 30 | #################### 31 | # - git/depth: false is needed for minver 32 | # - The 'deploy' step in Travis isn't the same process as the rest. So you have to re-source 33 | # the functions into that process. More than that it isn't a full sh process like with the 34 | # script-phase. So you cannot even source from there directly. That's why the source 35 | # command is IN the deploy.sh script. 36 | 37 | language: csharp 38 | solution: $SOLUTION_NAME.sln 39 | mono: latest 40 | dotnet: none 41 | git: 42 | depth: false 43 | install: 44 | - git clone https://github.com/UnterrainerInformatik/Travis-Scripts.git travis 45 | - source travis/functions.sh 46 | - tr_setProjectSubdir .NET 47 | - source $TRAVIS/install.sh 48 | before_script: 49 | - source $TRAVIS/before_script.sh 50 | script: 51 | - source $TRAVIS/script.sh 52 | 53 | before_deploy: ./$TRAVIS/before_deploy.sh 54 | deploy: 55 | - provider: script 56 | skip_cleanup: true 57 | script: ./$TRAVIS/deploy.sh 58 | on: 59 | branch: master 60 | - provider: releases 61 | skip_cleanup: true 62 | overwrite: true 63 | api_key: $GITHUB_API_KEY 64 | file: $DEPLOY_BUILD.$VERSION.zip 65 | on: 66 | tags: true 67 | branch: master 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /MonoGameStateMachine/MonoGameStateMachine.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 10.0 6 | Debug 7 | AnyCPU 8 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3} 9 | Library 10 | Properties 11 | MonoGameStateMachine 12 | MonoGameStateMachine 13 | en-US 14 | 512 15 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | Profile111 17 | v4.5 18 | 19 | 20 | AnyCPU 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll 45 | True 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {baeab06b-25d7-4d2a-b21d-4435c0db794c} 59 | StateMachine 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /MonoGameStateMachine/MonoGameStateMachine.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Unterrainer Informatik OG Team 8 | Public Domain 9 | http://unlicense.org 10 | https://github.com/UnterrainerInformatik/FiniteStateMachine/raw/master/MonoGameStateMachine/icon.png 11 | https://github.com/UnterrainerInformatik/FiniteStateMachine 12 | false 13 | 14 | [DEPRECATED] This library is deprecated. Please use the normal StateMachine instead which is fully tested and being actively maintained. https://www.nuget.org/packages/StateMachine/ 15 | This project implements a Finite-State-Machine (FSM) designed to be used in games. 16 | Furthermore it implements even a Stack-Based-FSM (SBFSM). So you may tell it to 'continue with the last state before the active one'. 17 | You describe your FSM using a nice and well documented DSL (Domain Specific Language). 18 | 19 | This machine also adds the function '.After(time)' to transitions so you can set timed transitions that don't necessarily have to have a trigger at all. 20 | 21 | This replaces the code we usually had for keyboard-input (run-left-right-duck-jump), clicked buttons on the GUI (idle-over-down-refreshing), tower-states (idle-aiming-firing-reloading) or for the connection procedure when setting up peer2peer connections in our games.... 22 | This is a PCL (portable code library) so you should be able to use it in any of your MG projects. 23 | 24 | 25 | [DEPRECATED] This library is deprecated. Please use the normal StateMachine instead which is fully tested and being actively maintained. https://www.nuget.org/packages/StateMachine/ 26 | This project implements a Finite-State-Machine (FSM) designed to be used in games. 27 | Furthermore it implements even a Stack-Based-FSM (SBFSM). So you may tell it to 'continue with the last state before the active one'. 28 | 29 | 30 | [DEPRECATED] This library is deprecated. Please use the normal StateMachine instead which is fully tested and being actively maintained. https://www.nuget.org/packages/StateMachine/ nuget.org -> search for 'Unterrainer' or StateMachine. 31 | The new version there has been updated to host the same functionality as this version here did. You just have to feed it a TimeSpan on Update() like so: Update(TimeSpan.FromMilliseconds(gameTime.ElapsedGameTime.TotalMilliseconds)); 32 | 33 | Copyright 2017 34 | en-US 35 | state finite machine gamestate transition fluent gametime monogame mg after timer 36 | 37 | -------------------------------------------------------------------------------- /MonoGameStateMachine/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("MonoGameStateMachine")] 8 | [assembly: AssemblyProduct("MonoGameStateMachine")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyDescription("[DEPRECATED] This library is deprecated. Please use the normal StateMachine instead which is fully tested and being actively maintained. https://www.nuget.org/packages/StateMachine/")] 11 | [assembly: AssemblyCompany("Unterrainer Informatik OG")] 12 | [assembly: AssemblyCopyright("Copyright Unterrainer Informatik © 2017")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("65227cb6-80c3-4f33-b82e-b2f84b8d7bec")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.1.3.2")] 35 | [assembly: AssemblyFileVersion("1.1.3.2")] 36 | -------------------------------------------------------------------------------- /MonoGameStateMachine/TimeUnit.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace MonoGameStateMachine 29 | { 30 | public enum TimeUnit : long 31 | { 32 | MILLISECONDS = 1, 33 | SECONDS = 1000, 34 | MINUTES = SECONDS * 60, 35 | HOURS = MINUTES * 60, 36 | DAYS = HOURS * 24, 37 | WEEKS = DAYS * 7 38 | } 39 | } -------------------------------------------------------------------------------- /MonoGameStateMachine/Timer.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace MonoGameStateMachine 29 | { 30 | public struct Timer 31 | { 32 | public double Value { get; set; } 33 | public TimeUnit Unit { get; set; } 34 | public double Time { get; set; } 35 | public TS Target { get; set; } 36 | 37 | public Timer(TS target, double value, TimeUnit unit) 38 | { 39 | Target = target; 40 | Value = value; 41 | Unit = unit; 42 | Time = 0; 43 | Reset(); 44 | } 45 | 46 | public void Reset() 47 | { 48 | Time = Value * (long) Unit; 49 | } 50 | 51 | /// 52 | /// Lets the specified time tick away and subtracts it from the Timer.
53 | /// If the timer triggered the time that was left after the Timer triggered is returned. 54 | ///
55 | /// The time to tick away. 56 | /// Null if the timer didn't trigger, a positive value otherwise. 57 | public double? Tick(double timeInMillis) 58 | { 59 | Time -= timeInMillis; 60 | if (Time <= 0D) 61 | { 62 | var d = Time * -1D; 63 | Reset(); 64 | return d; 65 | } 66 | return null; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /MonoGameStateMachine/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MonoGameStateMachine/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/MonoGameStateMachine/icon.png -------------------------------------------------------------------------------- /MonoGameStateMachine/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /NUnitTests/Examples.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using NUnit.Framework; 29 | using StateMachine; 30 | 31 | namespace NUnitTests 32 | { 33 | public class Examples 34 | { 35 | private enum VState 36 | { 37 | DUCKING, 38 | STANDING, 39 | JUMPING, 40 | DESCENDING, 41 | DIVING 42 | }; 43 | 44 | private enum VTrigger 45 | { 46 | DOWN_RELEASED, 47 | DOWN_PRESSED, 48 | UP_PRESSED, 49 | SPACE_PRESSED 50 | }; 51 | 52 | private enum HState 53 | { 54 | STANDING, 55 | RUNNING_LEFT, 56 | RUNNING_RIGHT, 57 | WALKING_LEFT, 58 | WALKING_RIGHT, 59 | WALKING_DELAY_LEFT, 60 | WALKING_DELAY_RIGHT 61 | }; 62 | 63 | private enum HTrigger 64 | { 65 | LEFT_PRESSED, 66 | LEFT_RELEASED, 67 | RIGHT_PRESSED, 68 | RIGHT_RELEASED, 69 | SPACE_PRESSED 70 | }; 71 | 72 | private Fsm verticalMachine; 73 | private Fsm horizontalMachine; 74 | 75 | private Hero hero; 76 | 77 | [Test] 78 | [Ignore("Is output to PlantUML only.")] 79 | public void HorizontalMachine() 80 | { 81 | horizontalMachine = Fsm.Builder(HState.STANDING) 82 | .State(HState.STANDING) 83 | .TransitionTo(HState.WALKING_LEFT) 84 | .On(HTrigger.LEFT_PRESSED) 85 | .TransitionTo(HState.WALKING_RIGHT) 86 | .On(HTrigger.RIGHT_PRESSED) 87 | .State(HState.WALKING_LEFT) 88 | .TransitionTo(HState.WALKING_DELAY_LEFT) 89 | .On(HTrigger.LEFT_RELEASED) 90 | .State(HState.WALKING_RIGHT) 91 | .TransitionTo(HState.WALKING_DELAY_RIGHT) 92 | .On(HTrigger.RIGHT_RELEASED) 93 | .State(HState.WALKING_DELAY_LEFT) 94 | .TransitionTo(HState.WALKING_RIGHT) 95 | .On(HTrigger.RIGHT_PRESSED) 96 | .TransitionTo(HState.RUNNING_LEFT) 97 | .On(HTrigger.LEFT_PRESSED) 98 | .State(HState.WALKING_DELAY_RIGHT) 99 | .TransitionTo(HState.WALKING_LEFT) 100 | .On(HTrigger.LEFT_PRESSED) 101 | .TransitionTo(HState.RUNNING_RIGHT) 102 | .On(HTrigger.RIGHT_PRESSED) 103 | .State(HState.RUNNING_LEFT) 104 | .TransitionTo(HState.STANDING) 105 | .On(HTrigger.LEFT_RELEASED) 106 | .State(HState.RUNNING_RIGHT) 107 | .TransitionTo(HState.STANDING) 108 | .On(HTrigger.RIGHT_RELEASED) 109 | .GlobalTransitionTo(HState.STANDING) 110 | .OnGlobal(HTrigger.SPACE_PRESSED) 111 | .Build(); 112 | 113 | string s = horizontalMachine.GetPlantUml(); 114 | } 115 | 116 | [Test] 117 | [Ignore("Is output to PlantUML only.")] 118 | public void VerticalMachine() 119 | { 120 | verticalMachine = Fsm.Builder(VState.STANDING) 121 | .State(VState.STANDING) 122 | .TransitionTo(VState.DUCKING) 123 | .On(VTrigger.DOWN_PRESSED) 124 | .TransitionTo(VState.JUMPING) 125 | .On(VTrigger.UP_PRESSED) 126 | .State(VState.DUCKING) 127 | .TransitionTo(VState.STANDING) 128 | .On(VTrigger.DOWN_RELEASED) 129 | .State(VState.JUMPING) 130 | .TransitionTo(VState.DIVING) 131 | .On(VTrigger.DOWN_PRESSED) 132 | .State(VState.DESCENDING) 133 | .TransitionTo(VState.DIVING) 134 | .On(VTrigger.DOWN_PRESSED) 135 | .State(VState.DIVING) 136 | .TransitionTo(VState.DESCENDING) 137 | .On(VTrigger.DOWN_RELEASED) 138 | .GlobalTransitionTo(VState.STANDING) 139 | .OnGlobal(VTrigger.SPACE_PRESSED) 140 | .Build(); 141 | 142 | string s = verticalMachine.GetPlantUml(); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /NUnitTests/FluentAfterTests.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using NUnit.Framework; 30 | using StateMachine; 31 | 32 | namespace NUnitTests 33 | { 34 | [Category("FluentAfterTests")] 35 | public class FluentAfterTests 36 | { 37 | private enum State 38 | { 39 | IDLE, 40 | OVER, 41 | PRESSED, 42 | REFRESHING 43 | } 44 | 45 | private enum Trigger 46 | { 47 | MOUSE_CLICKED, 48 | MOUSE_RELEASED, 49 | MOUSE_OVER, 50 | MOUSE_LEAVE 51 | } 52 | 53 | private class Button 54 | { 55 | public bool IsActivated { get; set; } 56 | public State BtnState { get; set; } 57 | public State OldState { get; set; } = State.IDLE; 58 | 59 | public int UpdateCounter { get; set; } 60 | } 61 | 62 | [Test] 63 | public void WhenAfterConditionFiresOnEnterAndOnExitHooksShouldTrigger() 64 | { 65 | var button = new Button(); 66 | 67 | var m = Fsm.Builder(State.IDLE) 68 | .State(State.IDLE) 69 | .TransitionTo(State.OVER).On(Trigger.MOUSE_OVER) 70 | .OnEnter(t => button.BtnState = State.IDLE) 71 | .OnExit(t => button.OldState = button.BtnState) 72 | .State(State.OVER) 73 | .TransitionTo(State.IDLE).On(Trigger.MOUSE_LEAVE) 74 | .TransitionTo(State.PRESSED).On(Trigger.MOUSE_CLICKED) 75 | .OnEnter(t => button.BtnState = State.OVER) 76 | .OnExit(t => button.OldState = button.BtnState) 77 | .Update(a => button.UpdateCounter = button.UpdateCounter + 1) 78 | .State(State.PRESSED) 79 | .TransitionTo(State.IDLE).On(Trigger.MOUSE_LEAVE) 80 | .TransitionTo(State.REFRESHING).On(Trigger.MOUSE_RELEASED).If(a => button.IsActivated) 81 | .OnEnter(t => button.BtnState = State.PRESSED) 82 | .OnExit(t => button.OldState = button.BtnState) 83 | .State(State.REFRESHING) 84 | .OnEnter(t => button.BtnState = State.REFRESHING) 85 | .OnExit(t => button.OldState = button.BtnState) 86 | .TransitionTo(State.OVER).After(TimeSpan.FromSeconds(1)) 87 | .GlobalTransitionTo(State.IDLE).AfterGlobal(TimeSpan.FromSeconds(10)) 88 | .Build(); 89 | 90 | m.Update(TimeSpan.FromMilliseconds(16)); // Should do nothing. 91 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 92 | m.Trigger(Trigger.MOUSE_CLICKED); 93 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 94 | m.Trigger(Trigger.MOUSE_LEAVE); 95 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 96 | m.Trigger(Trigger.MOUSE_RELEASED); 97 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 98 | m.Trigger(Trigger.MOUSE_OVER); 99 | m.Update(TimeSpan.FromMilliseconds(16)); 100 | m.Update(TimeSpan.FromMilliseconds(16)); 101 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OVER)); 102 | Assert.That(button.BtnState, Is.EqualTo(State.OVER)); 103 | Assert.That(button.OldState, Is.EqualTo(State.IDLE)); 104 | m.Trigger(Trigger.MOUSE_CLICKED); 105 | Assert.That(m.Current.Identifier, Is.EqualTo(State.PRESSED)); 106 | Assert.That(button.BtnState, Is.EqualTo(State.PRESSED)); 107 | Assert.That(button.OldState, Is.EqualTo(State.OVER)); 108 | m.Trigger(Trigger.MOUSE_RELEASED); // Button is deactivated. 109 | Assert.That(m.Current.Identifier, Is.EqualTo(State.PRESSED)); 110 | Assert.That(button.BtnState, Is.EqualTo(State.PRESSED)); 111 | Assert.That(button.OldState, Is.EqualTo(State.OVER)); 112 | button.IsActivated = true; 113 | m.Trigger(Trigger.MOUSE_RELEASED); // Now it's activated. 114 | Assert.That(m.Current.Identifier, Is.EqualTo(State.REFRESHING)); 115 | Assert.That(button.BtnState, Is.EqualTo(State.REFRESHING)); 116 | Assert.That(button.OldState, Is.EqualTo(State.PRESSED)); 117 | m.Update(TimeSpan.FromMilliseconds(500)); // No transition yet... 118 | Assert.That(m.Current.Identifier, Is.EqualTo(State.REFRESHING)); 119 | Assert.That(button.BtnState, Is.EqualTo(State.REFRESHING)); 120 | Assert.That(button.OldState, Is.EqualTo(State.PRESSED)); 121 | m.Update(TimeSpan.FromMilliseconds(600)); // But now. 122 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OVER)); 123 | Assert.That(button.BtnState, Is.EqualTo(State.OVER)); 124 | Assert.That(button.OldState, Is.EqualTo(State.REFRESHING)); 125 | m.Update(TimeSpan.FromMilliseconds(10000)); 126 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 127 | Assert.That(button.BtnState, Is.EqualTo(State.IDLE)); 128 | Assert.That(button.OldState, Is.EqualTo(State.OVER)); 129 | 130 | // Update was triggered twice over all states. 131 | Assert.That(button.UpdateCounter, Is.EqualTo(3F)); 132 | } 133 | 134 | [Test] 135 | public void WhenMultipleAfterConditionsFireOnASingleUpdateAdvanceStateCorrectly() 136 | { 137 | var m = Fsm.Builder(State.IDLE) 138 | .State(State.IDLE) 139 | .TransitionTo(State.OVER).After(TimeSpan.FromMilliseconds(10)) 140 | .State(State.OVER) 141 | .TransitionTo(State.PRESSED).After(TimeSpan.FromMilliseconds(10)) 142 | .State(State.PRESSED) 143 | .TransitionTo(State.REFRESHING).After(TimeSpan.FromMilliseconds(10)) 144 | .State(State.REFRESHING) 145 | .TransitionTo(State.IDLE).After(TimeSpan.FromMilliseconds(10)) 146 | .Build(); 147 | 148 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 149 | m.Update(TimeSpan.FromMilliseconds(40)); 150 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 151 | } 152 | 153 | [Test] 154 | public void WhenMultipleAfterConditionsFireOnASingleUpdateOnEnterAndOnExitShoudFire() 155 | { 156 | var enterCount = 0; 157 | var exitCount = 0; 158 | var m = Fsm.Builder(State.IDLE) 159 | .State(State.IDLE) 160 | .OnEnter(t => enterCount++) 161 | .OnExit(t => exitCount++) 162 | .TransitionTo(State.OVER).After(TimeSpan.FromMilliseconds(10)) 163 | .State(State.OVER) 164 | .OnEnter(t => enterCount++) 165 | .OnExit(t => exitCount++) 166 | .TransitionTo(State.PRESSED).After(TimeSpan.FromMilliseconds(10)) 167 | .State(State.PRESSED) 168 | .OnEnter(t => enterCount++) 169 | .OnExit(t => exitCount++) 170 | .TransitionTo(State.REFRESHING).After(TimeSpan.FromMilliseconds(10)) 171 | .State(State.REFRESHING) 172 | .OnEnter(t => enterCount++) 173 | .OnExit(t => exitCount++) 174 | .TransitionTo(State.IDLE).After(TimeSpan.FromMilliseconds(10)) 175 | .Build(); 176 | 177 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 178 | m.Update(TimeSpan.FromMilliseconds(40)); 179 | Assert.That(enterCount, Is.EqualTo(4)); 180 | Assert.That(exitCount, Is.EqualTo(4)); 181 | } 182 | 183 | [Test] 184 | public void WhenAnAfterConditionDoesNotFiresAndATransitionHappensTheTimerIsResetOnReentry() 185 | { 186 | var m = Fsm.Builder(State.IDLE) 187 | .State(State.IDLE) 188 | .TransitionTo(State.OVER).After(TimeSpan.FromMilliseconds(10)) 189 | .TransitionTo(State.OVER).On(Trigger.MOUSE_CLICKED) 190 | .State(State.OVER) 191 | .TransitionTo(State.IDLE).On(Trigger.MOUSE_CLICKED) 192 | .Build(); 193 | 194 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 195 | m.Update(TimeSpan.FromMilliseconds(5)); 196 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 197 | m.Trigger(Trigger.MOUSE_CLICKED); 198 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OVER)); 199 | m.Trigger(Trigger.MOUSE_CLICKED); 200 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 201 | m.Update(TimeSpan.FromMilliseconds(5)); 202 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /NUnitTests/FluentSyntaxTest1.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System.Collections.Generic; 29 | using NUnit.Framework; 30 | using StateMachine; 31 | using StateMachine.Fluent.Api; 32 | 33 | namespace NUnitTests 34 | { 35 | class Spell 36 | { 37 | } 38 | 39 | class Timer 40 | { 41 | public void Start() 42 | { 43 | } 44 | 45 | public float Value { get; } 46 | 47 | public void StopAndReset() 48 | { 49 | } 50 | } 51 | 52 | class Hero 53 | { 54 | public void DoSpell(Spell spell) 55 | { 56 | } 57 | } 58 | 59 | class Button 60 | { 61 | public enum ButtonKind 62 | { 63 | FLIPBACK 64 | } 65 | 66 | public enum ButtonState 67 | { 68 | IDLE, 69 | OVER, 70 | DOWN, 71 | REFRESHING 72 | } 73 | 74 | public ButtonState State { get; set; } 75 | public ButtonKind Kind { get; set; } 76 | public Timer RefreshTimer { get; } 77 | 78 | public Spell DoAssociatedSpell() 79 | { 80 | //Do something useful. 81 | return new Spell(); 82 | } 83 | } 84 | 85 | class FluentSyntaxTest1 86 | { 87 | private enum State 88 | { 89 | IDLE, 90 | OVER, 91 | PRESSED, 92 | REFRESHING 93 | } 94 | 95 | private enum Trigger 96 | { 97 | MOUSE_CLICKED, 98 | MOUSE_RELEASED, 99 | MOUSE_OVER, 100 | MOUSE_LEAVE 101 | } 102 | 103 | private readonly Dictionary> buttonMachines = 104 | new Dictionary>(); 105 | 106 | [Test] 107 | [Ignore("Is output to PlantUML only.")] 108 | public void Main() 109 | { 110 | var hero = new Hero(); 111 | CreateMachineFor(Fsm.Builder(State.IDLE), new Button(), hero); 112 | CreateMachineFor(Fsm.Builder(State.IDLE), new Button(), hero); 113 | } 114 | 115 | private void CreateMachineFor(BuilderFluent builder, Button button, Hero hero) 116 | { 117 | buttonMachines.Add(button, builder 118 | .State(State.IDLE) 119 | .TransitionTo(State.OVER).On(Trigger.MOUSE_OVER) 120 | .OnEnter(t => button.State = Button.ButtonState.IDLE) 121 | .State(State.OVER) 122 | .TransitionTo(State.IDLE).On(Trigger.MOUSE_LEAVE) 123 | .TransitionTo(State.PRESSED).On(Trigger.MOUSE_CLICKED) 124 | .OnEnter(t => button.State = Button.ButtonState.OVER) 125 | .State(State.PRESSED) 126 | .TransitionTo(State.IDLE) 127 | .On(Trigger.MOUSE_LEAVE) 128 | .If(a => button.Kind == Button.ButtonKind.FLIPBACK) 129 | .TransitionTo(State.REFRESHING).On(Trigger.MOUSE_RELEASED) 130 | .OnEnter(t => button.State = Button.ButtonState.DOWN) 131 | .State(State.REFRESHING) 132 | .OnEnter(t => 133 | { 134 | hero.DoSpell(button.DoAssociatedSpell()); 135 | button.RefreshTimer.Start(); 136 | button.State = Button.ButtonState.REFRESHING; 137 | }) 138 | .Update(args => 139 | { 140 | if (button.RefreshTimer.Value <= 0F) 141 | { 142 | button.RefreshTimer.StopAndReset(); 143 | args.Machine.JumpTo(State.IDLE); 144 | } 145 | }) 146 | .Build()); 147 | 148 | string s = buttonMachines[button].GetPlantUml(); 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /NUnitTests/FluentTests.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using NUnit.Framework; 30 | using StateMachine; 31 | 32 | namespace NUnitTests 33 | { 34 | public class FluentTests 35 | { 36 | private enum State 37 | { 38 | IDLE, 39 | OVER, 40 | PRESSED, 41 | REFRESHING 42 | } 43 | 44 | private enum Trigger 45 | { 46 | MOUSE_CLICKED, 47 | MOUSE_RELEASED, 48 | MOUSE_OVER, 49 | MOUSE_LEAVE 50 | } 51 | 52 | private class Button 53 | { 54 | public bool IsActivated { get; set; } 55 | public State BtnState { get; set; } 56 | public State OldState { get; set; } = State.IDLE; 57 | public float RefreshTimer { get; set; } = 2F; 58 | 59 | public int UpdateCounter { get; set; } 60 | } 61 | 62 | [Test] 63 | [Category("FluentTests.OnMethods")] 64 | public void WhenStateChangesOnEnterAndOnExitHooksShouldTrigger() 65 | { 66 | var button = new Button(); 67 | 68 | var m = Fsm.Builder(State.IDLE) 69 | .State(State.IDLE) 70 | .TransitionTo(State.OVER).On(Trigger.MOUSE_OVER) 71 | .OnEnter(t => button.BtnState = State.IDLE) 72 | .OnExit(t => button.OldState = button.BtnState) 73 | .State(State.OVER) 74 | .TransitionTo(State.IDLE).On(Trigger.MOUSE_LEAVE) 75 | .TransitionTo(State.PRESSED).On(Trigger.MOUSE_CLICKED) 76 | .OnEnter(t => button.BtnState = State.OVER) 77 | .OnExit(t => button.OldState = button.BtnState) 78 | .Update(a => button.UpdateCounter += 1) 79 | .State(State.PRESSED) 80 | .TransitionTo(State.IDLE).On(Trigger.MOUSE_LEAVE) 81 | .TransitionTo(State.REFRESHING).On(Trigger.MOUSE_RELEASED).If(a => button.IsActivated) 82 | .OnEnter(t => button.BtnState = State.PRESSED) 83 | .OnExit(t => button.OldState = button.BtnState) 84 | .State(State.REFRESHING) 85 | .OnEnter(t => button.BtnState = State.REFRESHING) 86 | .OnExit(t => button.OldState = button.BtnState) 87 | .Update(a => 88 | { 89 | button.RefreshTimer -= (float)a.ElapsedTimeSpan.TotalMilliseconds; 90 | if (button.RefreshTimer <= 0F) 91 | { 92 | button.RefreshTimer = 0F; 93 | a.Machine.JumpTo(State.OVER); // or m.JumpTo(State.IDLE); 94 | } 95 | button.UpdateCounter = button.UpdateCounter + 1; 96 | }) 97 | .Build(); 98 | 99 | m.Update(TimeSpan.FromMilliseconds(2)); // Should do nothing. 100 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 101 | m.Trigger(Trigger.MOUSE_CLICKED); 102 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 103 | m.Trigger(Trigger.MOUSE_LEAVE); 104 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 105 | m.Trigger(Trigger.MOUSE_RELEASED); 106 | Assert.That(m.Current.Identifier, Is.EqualTo(State.IDLE)); 107 | m.Trigger(Trigger.MOUSE_OVER); 108 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OVER)); 109 | Assert.That(button.BtnState, Is.EqualTo(State.OVER)); 110 | Assert.That(button.OldState, Is.EqualTo(State.IDLE)); 111 | m.Trigger(Trigger.MOUSE_CLICKED); 112 | Assert.That(m.Current.Identifier, Is.EqualTo(State.PRESSED)); 113 | Assert.That(button.BtnState, Is.EqualTo(State.PRESSED)); 114 | Assert.That(button.OldState, Is.EqualTo(State.OVER)); 115 | m.Trigger(Trigger.MOUSE_RELEASED); // Button is deactivated. 116 | Assert.That(m.Current.Identifier, Is.EqualTo(State.PRESSED)); 117 | Assert.That(button.BtnState, Is.EqualTo(State.PRESSED)); 118 | Assert.That(button.OldState, Is.EqualTo(State.OVER)); 119 | button.IsActivated = true; 120 | m.Trigger(Trigger.MOUSE_RELEASED); // Now it's activated. 121 | Assert.That(m.Current.Identifier, Is.EqualTo(State.REFRESHING)); 122 | Assert.That(button.BtnState, Is.EqualTo(State.REFRESHING)); 123 | Assert.That(button.OldState, Is.EqualTo(State.PRESSED)); 124 | m.Update(TimeSpan.FromMilliseconds(1)); // No transition yet... 125 | Assert.That(m.Current.Identifier, Is.EqualTo(State.REFRESHING)); 126 | Assert.That(button.BtnState, Is.EqualTo(State.REFRESHING)); 127 | Assert.That(button.OldState, Is.EqualTo(State.PRESSED)); 128 | m.Update(TimeSpan.FromMilliseconds(1)); // But now. 129 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OVER)); 130 | Assert.That(button.BtnState, Is.EqualTo(State.OVER)); 131 | Assert.That(button.OldState, Is.EqualTo(State.REFRESHING)); 132 | 133 | // Update was triggered twice over all states. 134 | Assert.That(button.UpdateCounter, Is.EqualTo(2)); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /NUnitTests/GameProgrammingPatterns1.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using NUnit.Framework; 29 | using StateMachine; 30 | 31 | namespace NUnitTests 32 | { 33 | [TestFixture] 34 | [Category("GamingProgrammingPatterns")] 35 | public class GameProgrammingPatterns1 36 | { 37 | private enum State 38 | { 39 | DUCKING, 40 | STANDING, 41 | JUMPING, 42 | DIVING 43 | } 44 | 45 | private enum Trigger 46 | { 47 | DOWN, 48 | UP 49 | } 50 | 51 | [Test] 52 | [Category("GamingProgrammingPatterns.1")] 53 | public void StateChangesStandingDuckingJumpingDiving() 54 | { 55 | var m = Fsm.Builder(State.STANDING) 56 | .State(State.DUCKING) 57 | .TransitionTo(State.STANDING).On(Trigger.UP) 58 | .State(State.STANDING) 59 | .TransitionTo(State.DUCKING).On(Trigger.DOWN) 60 | .TransitionTo(State.JUMPING).On(Trigger.UP) 61 | .State(State.JUMPING) 62 | .TransitionTo(State.DIVING).On(Trigger.DOWN) 63 | .State(State.DIVING) 64 | .Build(); 65 | 66 | Assert.That(m.Current.Identifier, Is.EqualTo(State.STANDING)); 67 | m.Trigger(Trigger.DOWN); 68 | Assert.That(m.Current.Identifier, Is.EqualTo(State.DUCKING)); 69 | m.Trigger(Trigger.DOWN); 70 | Assert.That(m.Current.Identifier, Is.EqualTo(State.DUCKING)); 71 | m.Trigger(Trigger.UP); 72 | Assert.That(m.Current.Identifier, Is.EqualTo(State.STANDING)); 73 | m.Trigger(Trigger.UP); 74 | Assert.That(m.Current.Identifier, Is.EqualTo(State.JUMPING)); 75 | m.Trigger(Trigger.UP); 76 | Assert.That(m.Current.Identifier, Is.EqualTo(State.JUMPING)); 77 | m.Trigger(Trigger.DOWN); 78 | Assert.That(m.Current.Identifier, Is.EqualTo(State.DIVING)); 79 | } 80 | 81 | private enum WState 82 | { 83 | EMPTY_HANDED, 84 | GUN, 85 | SHOTGUN, 86 | LASER_RIFLE 87 | } 88 | 89 | private enum WTrigger 90 | { 91 | TAB, 92 | SHIFT_TAB 93 | } 94 | 95 | [Test] 96 | [Category("GamingProgrammingPatterns.2")] 97 | public void StateChangesGunRotation() 98 | { 99 | // Now for the weapons-machine with basic forward- and backward-rotation. 100 | var m = Fsm.Builder(WState.EMPTY_HANDED) 101 | .State(WState.EMPTY_HANDED) 102 | .TransitionTo(WState.GUN).On(WTrigger.TAB) 103 | .TransitionTo(WState.LASER_RIFLE).On(WTrigger.SHIFT_TAB) 104 | .State(WState.GUN) 105 | .TransitionTo(WState.SHOTGUN).On(WTrigger.TAB) 106 | .TransitionTo(WState.EMPTY_HANDED).On(WTrigger.SHIFT_TAB) 107 | .State(WState.SHOTGUN) 108 | .TransitionTo(WState.LASER_RIFLE).On(WTrigger.TAB) 109 | .TransitionTo(WState.GUN).On(WTrigger.SHIFT_TAB) 110 | .State(WState.LASER_RIFLE) 111 | .TransitionTo(WState.EMPTY_HANDED).On(WTrigger.TAB) 112 | .TransitionTo(WState.SHOTGUN).On(WTrigger.SHIFT_TAB) 113 | .Build(); 114 | 115 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.EMPTY_HANDED)); 116 | m.Trigger(WTrigger.TAB); 117 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.GUN)); 118 | m.Trigger(WTrigger.TAB); 119 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.SHOTGUN)); 120 | m.Trigger(WTrigger.TAB); 121 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.LASER_RIFLE)); 122 | m.Trigger(WTrigger.TAB); 123 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.EMPTY_HANDED)); 124 | m.Trigger(WTrigger.SHIFT_TAB); 125 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.LASER_RIFLE)); 126 | m.Trigger(WTrigger.SHIFT_TAB); 127 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.SHOTGUN)); 128 | m.Trigger(WTrigger.SHIFT_TAB); 129 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.GUN)); 130 | m.Trigger(WTrigger.SHIFT_TAB); 131 | Assert.That(m.Current.Identifier, Is.EqualTo(WState.EMPTY_HANDED)); 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /NUnitTests/NUnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7} 8 | Library 9 | Properties 10 | NUnitTests 11 | NUnitTests 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {baeab06b-25d7-4d2a-b21d-4435c0db794c} 62 | StateMachine 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /NUnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("NUnitTests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("NUnitTests")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("61de316c-7e56-4cf1-8f1a-8d0b58fcfba7")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /NUnitTests/SimpleTests.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System.Linq; 29 | using NUnit.Framework; 30 | using StateMachine; 31 | 32 | namespace NUnitTests 33 | { 34 | [TestFixture] 35 | [Category("Simple")] 36 | public class SimpleTests 37 | { 38 | private enum State 39 | { 40 | OPENED, 41 | CLOSED, 42 | POP 43 | } 44 | 45 | private enum Trigger 46 | { 47 | CLOSE, 48 | OPEN, 49 | PUSH_POP 50 | } 51 | 52 | [Test] 53 | public void WhenBuiltManuallyStackMachineShouldWork() 54 | { 55 | var opened = new State(State.OPENED); 56 | var closed = new State(State.CLOSED) 57 | { 58 | ClearStack = true 59 | }; 60 | opened.AddTransisionOn(Trigger.CLOSE, State.CLOSED).AddTransisionOn(Trigger.OPEN, State.OPENED); 61 | closed.AddTransisionOn(Trigger.OPEN, State.OPENED).AddTransisionOn(Trigger.CLOSE, State.CLOSED); 62 | 63 | var m = 64 | new Fsm(opened, true) 65 | .AddStateChangeHandler(TestTools.ConsoleOut) 66 | .Add(opened) 67 | .Add(closed); 68 | 69 | AssertSimpleTest(m); 70 | } 71 | 72 | [Test] 73 | public void WhenBuiltWithBuilderStackMachineShouldWork() 74 | { 75 | var m = Fsm.Builder(State.OPENED) 76 | .EnableStack() 77 | .State(State.OPENED) 78 | .TransitionTo(State.OPENED).On(Trigger.OPEN) 79 | .TransitionTo(State.CLOSED).On(Trigger.CLOSE) 80 | .State(State.CLOSED).ClearsStack() 81 | .TransitionTo(State.CLOSED).On(Trigger.CLOSE) 82 | .TransitionTo(State.OPENED).On(Trigger.OPEN) 83 | .Build(); 84 | 85 | AssertSimpleTest(m); 86 | } 87 | 88 | private void AssertSimpleTest(Fsm m) 89 | { 90 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OPENED)); 91 | m.Trigger(Trigger.OPEN); 92 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OPENED)); 93 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] {State.OPENED, State.OPENED})); 94 | 95 | m.Trigger(Trigger.CLOSE); 96 | Assert.That(m.Current.Identifier, Is.EqualTo(State.CLOSED)); 97 | Assert.That(m.Stack.ToArray(), Is.EquivalentTo(new State[] {})); 98 | 99 | m.Trigger(Trigger.CLOSE); 100 | Assert.That(m.Current.Identifier, Is.EqualTo(State.CLOSED)); 101 | Assert.That(m.Stack.ToArray(), Is.EquivalentTo(new State[] {})); 102 | 103 | m.Trigger(Trigger.OPEN); 104 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OPENED)); 105 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] {State.OPENED})); 106 | } 107 | 108 | [Test] 109 | public void WhenBuiltWithBuilderAndStringsStackMachineShouldWork() 110 | { 111 | var m = Fsm.Builder("OPENED") 112 | .EnableStack() 113 | .State("OPENED") 114 | .TransitionTo("OPENED").On("OPEN") 115 | .TransitionTo("CLOSED").On("CLOSE") 116 | .State("CLOSED").ClearsStack() 117 | .TransitionTo("CLOSED").On("CLOSE") 118 | .TransitionTo("OPENED").On("OPEN") 119 | .Build(); 120 | 121 | m.Trigger("OPEN"); 122 | Assert.That(m.Current.Identifier, Is.EqualTo("OPENED")); 123 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] { "OPENED", "OPENED" })); 124 | 125 | m.Trigger("CLOSE"); 126 | Assert.That(m.Current.Identifier, Is.EqualTo("CLOSED")); 127 | Assert.That(m.Stack.ToArray(), Is.EquivalentTo(new State[] { })); 128 | 129 | m.Trigger("CLOSE"); 130 | Assert.That(m.Current.Identifier, Is.EqualTo("CLOSED")); 131 | Assert.That(m.Stack.ToArray(), Is.EquivalentTo(new State[] { })); 132 | 133 | m.Trigger("OPEN"); 134 | Assert.That(m.Current.Identifier, Is.EqualTo("OPENED")); 135 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] { "OPENED" })); 136 | } 137 | 138 | [Test] 139 | public void WhenCallingPopOnStackMachineBuiltManuallyShouldBehaveCorrectly() 140 | { 141 | var opened = new State(State.OPENED); 142 | var closed = new State(State.CLOSED) 143 | { 144 | ClearStack = true 145 | }; 146 | var pop = new State(State.POP); 147 | opened.AddTransisionOn(Trigger.CLOSE, State.CLOSED) 148 | .AddTransisionOn(Trigger.OPEN, State.OPENED) 149 | .AddTransisionOn(Trigger.PUSH_POP, State.POP); 150 | pop.AddPopTransisionOn(Trigger.PUSH_POP); 151 | closed.AddTransisionOn(Trigger.OPEN, State.OPENED) 152 | .AddTransisionOn(Trigger.CLOSE, State.CLOSED); 153 | 154 | var m = 155 | new Fsm(opened, true) 156 | .AddStateChangeHandler(TestTools.ConsoleOut) 157 | .Add(opened) 158 | .Add(closed) 159 | .Add(pop); 160 | 161 | AssertTestWithPop(m); 162 | } 163 | 164 | [Test] 165 | public void WhenCallingPopOnStackMachineBuiltWithBuilderShouldBehaveCorrectly() 166 | { 167 | var m = Fsm.Builder(State.OPENED) 168 | .EnableStack() 169 | .State(State.OPENED) 170 | .TransitionTo(State.OPENED).On(Trigger.OPEN) 171 | .TransitionTo(State.CLOSED).On(Trigger.CLOSE) 172 | .TransitionTo(State.POP).On(Trigger.PUSH_POP) 173 | .State(State.CLOSED).ClearsStack() 174 | .TransitionTo(State.CLOSED).On(Trigger.CLOSE) 175 | .TransitionTo(State.OPENED).On(Trigger.OPEN) 176 | .State(State.POP) 177 | .PopTransition().On(Trigger.PUSH_POP) 178 | .Build(); 179 | 180 | AssertTestWithPop(m); 181 | } 182 | 183 | private void AssertTestWithPop(Fsm m) 184 | { 185 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OPENED)); 186 | m.Trigger(Trigger.OPEN); 187 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OPENED)); 188 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] { State.OPENED, State.OPENED })); 189 | 190 | m.Trigger(Trigger.PUSH_POP); 191 | Assert.That(m.Current.Identifier, Is.EqualTo(State.POP)); 192 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] { State.OPENED, State.OPENED, State.POP })); 193 | 194 | m.Trigger(Trigger.PUSH_POP); 195 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OPENED)); 196 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] { State.OPENED, State.OPENED })); 197 | 198 | m.Trigger(Trigger.CLOSE); 199 | Assert.That(m.Current.Identifier, Is.EqualTo(State.CLOSED)); 200 | Assert.That(m.Stack.ToArray(), Is.EquivalentTo(new State[] { })); 201 | 202 | m.Trigger(Trigger.CLOSE); 203 | Assert.That(m.Current.Identifier, Is.EqualTo(State.CLOSED)); 204 | Assert.That(m.Stack.ToArray(), Is.EquivalentTo(new State[] { })); 205 | 206 | m.Trigger(Trigger.OPEN); 207 | Assert.That(m.Current.Identifier, Is.EqualTo(State.OPENED)); 208 | Assert.That(m.Stack.Select(x => x.Identifier).ToArray(), Is.EquivalentTo(new[] { State.OPENED })); 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /NUnitTests/TestTools.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using StateMachine.Events; 30 | 31 | namespace NUnitTests 32 | { 33 | public static class TestTools 34 | { 35 | public static void ConsoleOut(object sender, StateChangeArgs e) 36 | { 37 | Console.Out.WriteLine($"From [{e.From}] with [{e.Input}] to [{e.To}]"); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /NUnitTests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /NUnitTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![license](https://img.shields.io/github/license/unterrainerinformatik/FiniteStateMachine.svg?maxAge=2592000)](http://unlicense.org) [![Twitter Follow](https://img.shields.io/twitter/follow/throbax.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/throbax) 2 | 3 | | Project | Package | 4 | | ------------------------------------------------------------ | :----------------------------------------------------------: | 5 | | StateMachine | [![NuGet](https://img.shields.io/nuget/v/StateMachine.svg?maxAge=2592000)](https://www.nuget.org/packages/StateMachine/) [![NuGet](https://img.shields.io/nuget/dt/StateMachine.svg?maxAge=2592000)](https://www.nuget.org/packages/StateMachine/) | 6 | | MonoGameStateMachine **(deprecated)**
**Please use the normal StateMachine instead** | [![NuGet](https://img.shields.io/nuget/v/MonoGameStateMachine.svg)](https://www.nuget.org/packages/MonoGameStateMachine/) [![NuGet](https://img.shields.io/nuget/dt/MonoGameStateMachine.svg)](https://www.nuget.org/packages/MonoGameStateMachine/)
Use the normal StateMachine and pass a TimeSpan on Update. | 7 | | Java-Version: JavaStateMachine | [Olards Java Version on GitHub](https://github.com/Olard/JavaStateMachine) | 8 | 9 | # Deprecation of MonoGameStateMachine 10 | 11 | We simply didn't want to maintain two libraries of this size and the non-MG users wanted the functionality of the MonoGame version as well. So we compromised and ported the MG version back to the core-version replacing the `GameTime` reference, which really was the only thing we used from MG, which felt like a waste, with a `TimeSpan` everyone may use. Use it like this: 12 | 13 | ```c# 14 | machine.Update(TimeSpan.FromMilliseconds(gameTime.ElapsedGameTime.TotalMilliseconds)); 15 | ``` 16 | 17 | 18 | 19 | # Finite-State-Machine 20 | 21 | This project provides a Finite-State-Machine (FSM) as a portable class library (PCL) designed to be used in games. 22 | 23 | Furthermore it implements even a Stack-Based-FSM (SBFSM). So you may tell it to 'continue with the last state before the active one'. 24 | 25 | You describe your FSM using a nice and well documented DSL (Domain Specific Language). 26 | 27 | If you're looking for a Java version of this project, check out [Olards Java Version on GitHub](https://github.com/Olard/JavaStateMachine). 28 | 29 | > **If you like this repo, please don't forget to star it.** 30 | > **Thank you.** 31 | 32 | 33 | 34 | #### ![Icon](https://github.com/UnterrainerInformatik/FiniteStateMachine/raw/master/StateMachine/icon.png)StateMachine 35 | 36 | Is the generic implementation in the form of a PCL (portable code library). 37 | It references no other library (no dependencies). 38 | 39 | Nice if you want to use it outside of MonoGame. 40 | 41 | ## Description 42 | 43 | This replaces the code we usually had for keyboard-input (run-left-right-duck-jump), clicked buttons on the GUI (idle-over-down-refreshing), tower-states (idle-aiming-firing-reloading) or for the connection procedure when setting up peer2peer connections in our games (more complex; example further down). 44 | 45 | The idea is to generate a single FSM for every 'layer' of input that your engine allows. 46 | In our example it's a multi-button GUI. Some of the buttons will stay pressed and the GUI will enter a 'selection-grid-mode' until the left button is pressed again. Some of them just do immediate actions and become immediately released afterwards. Some of both of those have a refresh-time and stay disabled for that period. 47 | In this example the state of the GUI ('selection-grid-mode' or not) would be such a machine. 48 | 49 | We place those machines in a single class where they could 'talk with each other' by reading their respective states. That way it is possible to construct and react on compound states. 50 | 51 | ### Example 52 | 53 | ![State-Machine-Image jumping-diving](https://github.com/UnterrainerInformatik/FiniteStateMachine/raw/master/docs/jumping_diving.png) 54 | 55 | ```C# 56 | Fsm.Builder(State.STANDING) 57 | .State(State.DUCKING) 58 | .TransitionTo(State.STANDING).On(Trigger.UP) 59 | .State(State.STANDING) 60 | .TransitionTo(State.DUCKING).On(Trigger.DOWN) 61 | .TransitionTo(State.JUMPING).On(Trigger.UP) 62 | .State(State.JUMPING) 63 | .TransitionTo(State.DIVING).On(Trigger.DOWN) 64 | .State(State.DIVING) 65 | .Build(); 66 | ``` 67 | 68 | 69 | 70 | A nice and more complex example for such a machine is the setup of a multiplayer game. 71 | It would be like the following: 72 | 73 | ##### Server: 74 | 75 | * Send the 'load level' signal to other players 76 | * Load level 77 | * Display 'waiting for other players' message-box 78 | * Wait for all other players to finish loading 79 | * Send the level-data to other players and wait for acknowledgement from each of them 80 | * Remove the 'waiting for other players' message-box 81 | * Send 'start' signal to other players 82 | * Start the game 83 | 84 | 85 | ### Test-drive 86 | 87 | Time to take it for a test-drive. 88 | 89 | The motivation for this project came from a nice article I found [here](http://gameprogrammingpatterns.com/state.html) which comes with some examples. We tried to solve the proposed problems with our new project. 90 | 91 | *By the way: [This](http://gameprogrammingpatterns.com/) seems to be a great book, so try to support the author in any way possible for you.* 92 | 93 | He's making a point using a FSM that looks like this: 94 | 95 | * ducking --(release down)--> standing 96 | * standing --(press down)--> ducking 97 | * standing --(press B)--> jumping 98 | * jumping --(press down)--> diving 99 | 100 | So the file ```GameProgrammingPatterns1.cs``` in the test-folder contains that machine. 101 | 102 | ## Usage 103 | 104 | This is a short paragraph that is about an example what configuring a state machine actually looks like. 105 | 106 | ![State-Machine-Image walking_running](https://github.com/UnterrainerInformatik/FiniteStateMachine/raw/master/docs/walking_running.png) 107 | 108 | ![State-Machine-Image standing_ducking](https://github.com/UnterrainerInformatik/FiniteStateMachine/raw/master/docs/standing_ducking.png) 109 | 110 | ```c# 111 | private enum VState { DUCKING, STANDING, JUMPING, DESCENDING, DIVING }; 112 | private enum VTrigger { DOWN_RELEASED, DOWN_PRESSED, UP_PRESSED, SPACE_PRESSED }; 113 | 114 | private enum HState { STANDING, RUNNING_LEFT, RUNNING_RIGHT, WALKING_LEFT, 115 | WALKING_RIGHT, WALKING_DELAY_LEFT, WALKING_DELAY_RIGHT}; 116 | private enum HTrigger { LEFT_PRESSED, LEFT_RELEASED, RIGHT_PRESSED, RIGHT_RELEASED, SPACE_PRESSED }; 117 | 118 | private Fsm verticalMachine; 119 | private Fsm horizontalMachine; 120 | 121 | private Keys[] lastKeysPressed; 122 | private Hero hero; 123 | 124 | public void main() { 125 | horizontalMachine = Fsm.Builder(STANDING) 126 | .State(STANDING) 127 | .TransitionTo(WALKING_LEFT).On(LEFT_PRESSED) 128 | .TransitionTo(WALKING_RIGHT).On(RIGHT_PRESSED) 129 | .OnEnter(e => { 130 | ConsoleOut(); 131 | hero.HAnimation = HAnimation.STANDING; 132 | hero.delayTimer.StopAndReset(); 133 | }) 134 | .State(WALKING_LEFT) 135 | .TransitionTo(WALKING_DELAY_LEFT).On(LEFT_RELEASED) 136 | .OnEnter(e => { 137 | ConsoleOut(); 138 | hero.HAnimation = HAnimation.WALK_LEFT; 139 | hero.delayTimer.StopAndReset(); 140 | }) 141 | .State(WALKING_RIGHT) 142 | .TransitionTo(WALKING_DELAY_RIGHT).On(RIGHT_RELEASED) 143 | .OnEnter(e => { 144 | ConsoleOut(); 145 | hero.HAnimation = HAnimation.WALK_RIGHT; 146 | hero.delayTimer.StopAndReset(); 147 | }) 148 | .State(WALKING_DELAY_LEFT) 149 | .TransitionTo(WALKING_RIGHT).On(RIGHT_PRESSED) 150 | .TransitionTo(RUNNING_LEFT).On(LEFT_PRESSED) 151 | .OnEnter(e => { 152 | hero.delayTimer.Start(); 153 | }) 154 | .Update(a => { 155 | hero.delayTimer.Update(a.ElapsedTimeSpan); 156 | if(hero.delayTimer) { 157 | horizontalMachine.JumpTo(STANDING); 158 | } 159 | }) 160 | .State(WALKING_DELAY_RIGHT) 161 | .TransitionTo(WALKING_LEFT).On(LEFT_PRESSED) 162 | .TransitionTo(RUNNING_RIGHT).On(RIGHT_PRESSED) 163 | .OnEnter(e => { 164 | hero.delayTimer.Start(); 165 | }) 166 | .Update(a => { 167 | hero.delayTimer.Update(a.ElapsedTimeSpan); 168 | if(hero.delayTimer) { 169 | horizontalMachine.JumpTo(STANDING); 170 | } 171 | }) 172 | .State(RUNNING_LEFT) 173 | .TransitionTo(STANDING).On(LEFT_RELEASED) 174 | .OnEnter(e => { 175 | ConsoleOut(); 176 | hero.HAnimation = HAnimation.RUNNING_LEFT; 177 | hero.delayTimer.StopAndReset(); 178 | }) 179 | .State(RUNNING_RIGHT) 180 | .TransitionTo(STANDING).On(RIGHT_RELEASED) 181 | .OnEnter(e => { 182 | ConsoleOut(); 183 | hero.HAnimation = HAnimation.RUNNING_RIGHT; 184 | hero.delayTimer.StopAndReset(); 185 | }) 186 | .GlobalTransitionTo(STANDING).On(SPACE_PRESSED) 187 | .Build(); 188 | 189 | verticalMachine = Fsm.Builder(STANDING) 190 | .State(STANDING) 191 | .TransitionTo(DUCKING).On(DOWN_PRESSED) 192 | .TransitionTo(JUMPING).On(UP_PRESSED) 193 | .OnEnter(e => { 194 | ConsoleOut(); 195 | hero.VAnimation = VAnimation.IDLE; 196 | }) 197 | .OnExit(Console.Out.WriteLine($"From [{e.From}] with [{e.Input}] to [{e.To}]")) 198 | .State(DUCKING) 199 | .TransitionTo(STANDING).On(DOWN_RELEASED) 200 | .OnEnter(e => { 201 | ConsoleOut(); 202 | hero.VAnimation = VAnimation.DUCKING; 203 | }) 204 | .OnExit(ConsoleOut) 205 | .State(JUMPING) 206 | .TransitionTo(DIVING).On(DOWN_PRESSED) 207 | .OnEnter(e => { 208 | ConsoleOut(); 209 | hero.VAnimation = VAnimation.JUMPING; 210 | }) 211 | .OnExit(ConsoleOut) 212 | .Update(a => { 213 | hero.height += a.ElapsedTimeSpan.TotalSeconds * 100F; 214 | if(hero.height >= 200F) 215 | verticalMachine.TransitionTo(DESCENDING); 216 | }) 217 | .State(DESCENDING) 218 | .TransitionTo(DIVING).On(DOWN_PRESSED) 219 | .OnEnter(e => { 220 | ConsoleOut(); 221 | hero.VAnimation = VAnimation.DESCENDING; 222 | }) 223 | .OnExit(ConsoleOut) 224 | .Update(a => { 225 | hero.height -= a.ElapsedTimeSpan.TotalSeconds * 100F; 226 | if(hero.height <= 0F) { 227 | hero.height = 0F; 228 | verticalMachine.TransitionTo(STANDING); 229 | } 230 | }) 231 | .State(DIVING) 232 | .TransitionTo(DESCENDING).On(DOWN_RELEASED) 233 | .OnEnter(e => { 234 | ConsoleOut(); 235 | hero.VAnimation = VAnimation.DIVING; 236 | }) 237 | .OnExit(ConsoleOut) 238 | .Update(a => { 239 | hero.height -= a.ElapsedTimeSpan.TotalSeconds * 150F; 240 | if(hero.height <= 0F) { 241 | hero.height = 0F; 242 | verticalMachine.TransitionTo(STANDING); 243 | } 244 | }) 245 | .GlobalTransitionTo(STANDING).On(SPACE_PRESSED) 246 | .Build(); 247 | } 248 | 249 | protected override void Update(GameTime gameTime) { 250 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 251 | Keyboard.GetState().IsKeyDown(Keys.Escape)) 252 | Exit(); 253 | 254 | var s = Keyboard.GetState(); 255 | if (s.IsKeyDown(Keys.Up)) 256 | verticalMachine.Trigger(UP_PRESSED); 257 | if (s.IsKeyDown(Keys.Down)) 258 | verticalMachine.Trigger(DOWN_PRESSED); 259 | if (!s.IsKeyDown(Keys.Down) && lastKeysPressed.Contains(Keys.Down)) 260 | verticalMachine.Trigger(DOWN_RELEASED); 261 | 262 | if (s.IsKeyDown(Keys.Left)) 263 | horizontalMachine.Trigger(LEFT_PRESSED); 264 | if (s.IsKeyDown(Keys.Right)) 265 | horizontalMachine.Trigger(RIGHT_PRESSED); 266 | if (!s.IsKeyDown(Keys.Right) && lastKeysPressed.Contains(Keys.Right)) 267 | horizontalMachine.Trigger(RIGHT_RELEASED); 268 | if (!s.IsKeyDown(Keys.Left) && lastKeysPressed.Contains(Keys.Left)) 269 | horizontalMachine.Trigger(LEFT_RELEASED); 270 | 271 | lastKeysPressed = s.GetPressedKeys(); 272 | 273 | // Update the machines themselves. 274 | verticalMachine.Update(TimeSpan.FromMilliseconds( 275 | gameTime.ElapsedGameTime.TotalMilliseconds)); 276 | horizontalMachine.Update(TimeSpan.FromMilliseconds( 277 | gameTime.ElapsedGameTime.TotalMilliseconds)); 278 | } 279 | 280 | private void ConsoleOut(TransitioningValueArgs e) { 281 | Console.Out.WriteLine($"From [{e.From}] with [{e.Input}] to [{e.To}]"); 282 | } 283 | ``` 284 | 285 | Another example with a spell-button that has a refresh-time: 286 | 287 | ![State-Machine-Image button](https://github.com/UnterrainerInformatik/FiniteStateMachine/raw/master/docs/button.png) 288 | 289 | ```c# 290 | private enum State { IDLE, OVER, PRESSED, REFRESHING }; 291 | private enum Trigger { MOUSE_CLICKED, MOUSE_RELEASED, MOUSE_OVER, MOUSE_LEAVE }; 292 | 293 | private Dictionary> buttonMachines = new 294 | Dictionary>(); 295 | 296 | private void CreateMachineFor(Button button) 297 | buttonMachines.Add(button, Fsm.Builder(IDLE) 298 | .State(IDLE) 299 | .TransitionTo(OVER).On(MOUSE_OVER) 300 | .OnEnter(e => { 301 | button.State = ButtonState.IDLE; 302 | }) 303 | .State(OVER) 304 | .TransitionTo(IDLE).On(MOUSE_LEAVE) 305 | .TransitionTo(PRESSED).On(MOUSE_CLICKED) 306 | .OnEnter(e => { 307 | button.State = ButtonState.OVER; 308 | }) 309 | .State(PRESSED) 310 | .TransitionTo(IDLE).On(MOUSE_LEAVE).If(button.Kind == Kind.FLIPBACK) 311 | .TransitionTo(REFRESHING).On(MOUSE_RELEASED) 312 | .OnEnter(e => { 313 | button.State = ButtonState.DOWN; 314 | }) 315 | .State(REFRESHING) 316 | .OnEnter(e => { 317 | hero.doSpell(button.DoAssociatedSpell()); 318 | button.RefreshTimer.Start(); 319 | button.State = ButtonState.REFRESHING; 320 | }) 321 | .Update(a => { 322 | if(button.RefreshTimer.Value <= 0F) { 323 | button.RefreshTimer.StopAndReset(); 324 | machine.JumpTo(IDLE); 325 | } 326 | }) 327 | .Build(); 328 | } 329 | 330 | public void main() { 331 | Button b1 = new Button("name1", "someText", ...); 332 | Button b2 = new Button("name2", "someOtherText", ...); 333 | 334 | CreateMachineFor(b1); 335 | CreateMachineFor(b2); 336 | ... 337 | } 338 | ``` 339 | 340 | There also is the `After` feature ported from the MG version. Use it like that: 341 | 342 | ```c# 343 | Fsm.Builder(State.STANDING) 344 | .State(State.DUCKING) 345 | .TransitionTo(State.STANDING).After(TimeSpan.fromMilliseconds(500)) 346 | ... 347 | ``` 348 | 349 | This functionality is achieved by updating the `After` conditions **before** evaluating the `Update` function - Be advised that this happens directly before the `Update` call with the `TimeSpan` you've specified in the call to `Update`. If the `After` function triggers, the call to `Update` will be omitted. 350 | 351 | ## Inspired by: 352 | 353 | * [Fluent-State-Machine](https://github.com/Real-Serious-Games/Fluent-State-Machine) - by RoryDungan (MIT License) 354 | * [Nate](https://github.com/mmonteleone/nate) - by mmonteleone (MIT License) 355 | * [PlantUML - PlantText](https://www.planttext.com/) - PlantUML website. You can layout your PlantUML text and generate images out of them there. 356 | -------------------------------------------------------------------------------- /StateMachine.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2024 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestGame", "TestGame\TestGame.csproj", "{B7DCCAB0-9899-421D-90F5-8E694EEE403F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGameStateMachine", "MonoGameStateMachine\MonoGameStateMachine.csproj", "{A1AD3096-CCDB-4B43-9104-295B7F59F3D3}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NUnitTests", "NUnitTests\NUnitTests.csproj", "{61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateMachine", "StateMachine\StateMachine.csproj", "{BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F}.Debug|Any CPU.ActiveCfg = Debug|x86 23 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F}.Debug|Any CPU.Build.0 = Debug|x86 24 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F}.Debug|x86.ActiveCfg = Debug|x86 25 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F}.Debug|x86.Build.0 = Debug|x86 26 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F}.Release|Any CPU.ActiveCfg = Release|x86 27 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F}.Release|x86.ActiveCfg = Release|x86 28 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F}.Release|x86.Build.0 = Release|x86 29 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Debug|x86.ActiveCfg = Debug|Any CPU 32 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Debug|x86.Build.0 = Debug|Any CPU 33 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Release|x86.ActiveCfg = Release|Any CPU 36 | {A1AD3096-CCDB-4B43-9104-295B7F59F3D3}.Release|x86.Build.0 = Release|Any CPU 37 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Debug|x86.Build.0 = Debug|Any CPU 41 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Release|x86.ActiveCfg = Release|Any CPU 44 | {61DE316C-7E56-4CF1-8F1A-8D0B58FCFBA7}.Release|x86.Build.0 = Release|Any CPU 45 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Debug|x86.ActiveCfg = Debug|Any CPU 48 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Debug|x86.Build.0 = Debug|Any CPU 49 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Release|x86.ActiveCfg = Release|Any CPU 52 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C}.Release|x86.Build.0 = Release|Any CPU 53 | EndGlobalSection 54 | GlobalSection(SolutionProperties) = preSolution 55 | HideSolutionNode = FALSE 56 | EndGlobalSection 57 | GlobalSection(ExtensibilityGlobals) = postSolution 58 | SolutionGuid = {2D96EB88-64BA-42D2-92D3-264388C3AA17} 59 | EndGlobalSection 60 | EndGlobal 61 | -------------------------------------------------------------------------------- /StateMachine.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | NEVER 3 | CHOP_IF_LONG 4 | *************************************************************************** 5 | This is free and unencumbered software released into the public domain. 6 | 7 | Anyone is free to copy, modify, publish, use, compile, sell, or 8 | distribute this software, either in source code form or as a compiled 9 | binary, for any purpose, commercial or non-commercial, and by any 10 | means. 11 | 12 | In jurisdictions that recognize copyright laws, the author or authors 13 | of this software dedicate any and all copyright interest in the 14 | software to the public domain. We make this dedication for the benefit 15 | of the public at large and to the detriment of our heirs and 16 | successors. We intend this dedication to be an overt act of 17 | relinquishment in perpetuity of all present and future rights to this 18 | software under copyright law. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 24 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 25 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | For more information, please refer to <http://unlicense.org> 29 | *************************************************************************** 30 | True 31 | True 32 | True 33 | True 34 | True 35 | True 36 | True 37 | True 38 | True 39 | True -------------------------------------------------------------------------------- /StateMachine/Events/IfArgs.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace StateMachine.Events 29 | { 30 | public struct IfArgs 31 | { 32 | public TState Source { get; } 33 | public TState Target { get; } 34 | 35 | public IfArgs(TState source, TState target) 36 | { 37 | Source = source; 38 | Target = target; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /StateMachine/Events/StateChangeArgs.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace StateMachine.Events 29 | { 30 | public struct StateChangeArgs 31 | { 32 | public Fsm Fsm; 33 | public State From { get; } 34 | public State To { get; } 35 | public TTrigger Input { get; } 36 | 37 | public StateChangeArgs(Fsm fsm, State from, 38 | State to, TTrigger input) 39 | { 40 | Fsm = fsm; 41 | From = from; 42 | To = to; 43 | Input = input; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /StateMachine/Events/UpdateArgs.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | 30 | namespace StateMachine.Events 31 | { 32 | public struct UpdateArgs 33 | { 34 | public Fsm Machine { get; } 35 | public State State { get; } 36 | public TimeSpan ElapsedTimeSpan { get; } 37 | 38 | public UpdateArgs(Fsm machine, State state, TimeSpan elapsedTimeSpan) 39 | { 40 | Machine = machine; 41 | State = state; 42 | ElapsedTimeSpan = elapsedTimeSpan; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /StateMachine/Fluent/Api/BuilderFluent.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace StateMachine.Fluent.Api 29 | { 30 | public interface BuilderFluent 31 | { 32 | /// 33 | /// Enables the stack and turns this Finite-State-Machine (FSM) into a Stack-Based-FSM (SBFSM).
34 | /// Beware that you will have to specify "ClearsStack()" on some of the states, as otherwise the stack will grow and 35 | /// never be cleared. 36 | ///
37 | BuilderFluent EnableStack(); 38 | 39 | /// 40 | /// Sets a global transition to a state.
41 | /// This will generate a transision that will be triggered regardless of the current state's position in the graph and 42 | /// regardless of the transitions connected to that state.

43 | /// Think of it as a 'catch all' transition.
44 | /// Usually you would use such global transitions to reset a graph when ESC is pressed or something like that. 45 | ///

46 | /// The state the global transition should lead to. 47 | GlobalTransitionFluent GlobalTransitionTo(TS state); 48 | 49 | /// 50 | /// Generates a new state.
51 | /// An FSM can have multiple states connected via transitions.
52 | /// Only a single transition is active at any given time and that transition will receive 'Update(TData)' calls and 53 | /// will be checked for transitions that may be triggered by an input, which, in turn, would activate the next state. 54 | ///
55 | /// The state. 56 | StateFluent State(TS state); 57 | 58 | /// 59 | /// Builds this instance of an FSM (or SBFSM). 60 | /// 61 | Fsm Build(); 62 | } 63 | } -------------------------------------------------------------------------------- /StateMachine/Fluent/Api/FluentImplementation.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using System.Collections.Generic; 30 | using StateMachine.Events; 31 | 32 | namespace StateMachine.Fluent.Api 33 | { 34 | public class FluentImplementation : GlobalTransitionBuilderFluent, 35 | TransitionStateFluent 36 | { 37 | public Dictionary, List>> AfterEntries { get; set; } = new Dictionary, List>>(); 38 | public List> GlobalAfterEntries { get; set; } = new List>(); 39 | 40 | protected FsmModel FsmModel { get; set; } = new FsmModel(); 41 | protected TS startState; 42 | 43 | protected Tuple currentState; 44 | protected Tuple currentTransition; 45 | protected Tuple currentGlobalTransition; 46 | 47 | protected Dictionary, StateModel> stateModels = 48 | new Dictionary, StateModel>(); 49 | 50 | protected Dictionary, TransitionModel> transitionModels = 51 | new Dictionary, TransitionModel>(); 52 | 53 | protected Dictionary, TransitionModel> globalTransitionModels = 54 | new Dictionary, TransitionModel>(); 55 | 56 | public FluentImplementation(TS startState) 57 | { 58 | this.startState = startState; 59 | } 60 | 61 | public Fsm Build() 62 | { 63 | if (FsmModel.States[startState] == null) 64 | { 65 | throw FsmBuilderException.StartStateCannotBeNull(); 66 | } 67 | 68 | FsmModel.Current = FsmModel.States[startState]; 69 | FsmModel.StartState = FsmModel.Current; 70 | var m = new Fsm(FsmModel); 71 | m.AfterEntries = AfterEntries; 72 | m.GlobalAfterEntries = GlobalAfterEntries; 73 | return m; 74 | } 75 | 76 | public TransitionStateFluent After(TimeSpan timeSpan) 77 | { 78 | var key = currentTransition; 79 | if (!AfterEntries.TryGetValue(key, out var l)) 80 | { 81 | l = new List>(); 82 | AfterEntries.Add(key, l); 83 | } 84 | l.Add(new Timer(key.Item2, timeSpan.TotalMilliseconds, TimeUnit.MILLISECONDS)); 85 | return this; 86 | } 87 | 88 | public TransitionStateFluent AfterGlobal(TimeSpan timeSpan) 89 | { 90 | var key = currentGlobalTransition; 91 | GlobalAfterEntries.Add(new Timer(key.Item1, timeSpan.TotalMilliseconds, TimeUnit.MILLISECONDS)); 92 | return this; 93 | } 94 | 95 | public StateFluent State(TS state) 96 | { 97 | currentState = Tuple.Create(state); 98 | if (!FsmModel.States.ContainsKey(state)) 99 | { 100 | stateModels[currentState] = new StateModel(state); 101 | FsmModel.States[state] = new State(stateModels[currentState]); 102 | } 103 | return this; 104 | } 105 | 106 | public StateFluent OnEnter(Action> enter) 107 | { 108 | stateModels[currentState].AddEnteredHandler(enter); 109 | return this; 110 | } 111 | 112 | public StateFluent OnExit(Action> exit) 113 | { 114 | stateModels[currentState].AddExitedHandler(exit); 115 | return this; 116 | } 117 | 118 | public StateFluent Update(Action> update) 119 | { 120 | stateModels[currentState].AddUpdatedHandler(update); 121 | return this; 122 | } 123 | 124 | public GlobalTransitionFluent GlobalTransitionTo(TS state) 125 | { 126 | currentGlobalTransition = Tuple.Create(state); 127 | if (globalTransitionModels.ContainsKey(currentGlobalTransition)) return this; 128 | 129 | globalTransitionModels[currentGlobalTransition] = new TransitionModel(startState, state); 130 | FsmModel.GlobalTransitions[state] = 131 | new Transition(globalTransitionModels[currentGlobalTransition]); 132 | return this; 133 | } 134 | 135 | public GlobalTransitionBuilderFluent OnGlobal(TT trigger) 136 | { 137 | globalTransitionModels[currentGlobalTransition].Triggers.Add(trigger); 138 | return this; 139 | } 140 | 141 | public GlobalTransitionBuilderFluent IfGlobal( 142 | Func, bool> condition) 143 | { 144 | globalTransitionModels[currentGlobalTransition].Conditions.Add(condition); 145 | return this; 146 | } 147 | 148 | public TransitionFluent TransitionTo(TS state) 149 | { 150 | currentTransition = Tuple.Create(currentState.Item1, state); 151 | if (!transitionModels.ContainsKey(currentTransition)) 152 | { 153 | transitionModels[currentTransition] = new TransitionModel(currentState.Item1, 154 | state); 155 | stateModels[currentState].Transitions[state] = 156 | new Transition(transitionModels[currentTransition]); 157 | } 158 | return this; 159 | } 160 | 161 | public TransitionStateFluent On(TT trigger) 162 | { 163 | transitionModels[currentTransition].Triggers.Add(trigger); 164 | return this; 165 | } 166 | 167 | public TransitionStateFluent If(Func, bool> condition) 168 | { 169 | transitionModels[currentTransition].Conditions.Add(condition); 170 | return this; 171 | } 172 | 173 | public StateFluent ClearsStack() 174 | { 175 | FsmModel.States[currentState.Item1].ClearStack = true; 176 | return this; 177 | } 178 | 179 | public BuilderFluent EnableStack() 180 | { 181 | FsmModel.StackEnabled = true; 182 | return this; 183 | } 184 | 185 | public TransitionFluent PopTransition() 186 | { 187 | TransitionTo(default(TS)); 188 | transitionModels[currentTransition].Pop = true; 189 | return this; 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /StateMachine/Fluent/Api/GlobalTransitionBuilderFluent.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace StateMachine.Fluent.Api 29 | { 30 | public interface GlobalTransitionBuilderFluent : 31 | GlobalTransitionFluent, 32 | BuilderFluent 33 | { 34 | } 35 | } -------------------------------------------------------------------------------- /StateMachine/Fluent/Api/GlobalTransitionFluent.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using StateMachine.Events; 30 | 31 | namespace StateMachine.Fluent.Api 32 | { 33 | public interface GlobalTransitionFluent 34 | { 35 | /// 36 | /// Specifies the trigger, that has to be served as input in order to walk the global transition you're currently 37 | /// describing. 38 | /// 39 | /// The trigger. 40 | GlobalTransitionBuilderFluent OnGlobal(TT trigger); 41 | 42 | /// 43 | /// Specifies the condition, that has to be met, in addition to the trigger, to walk the global transition you're 44 | /// currently describing. 45 | /// 46 | /// The condition. 47 | GlobalTransitionBuilderFluent IfGlobal(Func, bool> condition); 48 | 49 | /// 50 | /// Automatically walks the transition you're currently describing, if the specified amount of time has passed. 51 | /// 52 | /// The amount of time. 53 | /// 54 | TransitionStateFluent AfterGlobal(TimeSpan timeSpan); 55 | } 56 | } -------------------------------------------------------------------------------- /StateMachine/Fluent/Api/StateFluent.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using StateMachine.Events; 30 | 31 | namespace StateMachine.Fluent.Api 32 | { 33 | public interface StateFluent : BuilderFluent 34 | { 35 | /// 36 | /// Adds a new transition to the state, you currently describe. 37 | /// 38 | /// The state the transition will lead to. 39 | TransitionFluent TransitionTo(TS state); 40 | 41 | /// 42 | /// Adding a transition that, being triggered, will result in the last state on the stack being popped and set to be 43 | /// the current one.
44 | /// For this to work 'EnableStack' has to be set (this machine has to be a Stack-Based-FSM (SBFSM)). 45 | ///
46 | TransitionFluent PopTransition(); 47 | 48 | /// 49 | /// Called when the state, you currently describe, is entered. 50 | /// 51 | /// The state change arguments. 52 | StateFluent OnEnter(Action> stateChangeArgs); 53 | 54 | /// 55 | /// Called when the state, you currently describe, is exited. 56 | /// 57 | /// The state change arguments. 58 | StateFluent OnExit(Action> stateChangeArgs); 59 | 60 | /// 61 | /// Called when the FSM's 'Update(TData)' method is called and the state, you currently describe, is active. 62 | /// 63 | /// The update arguments. 64 | StateFluent Update(Action> updateArgs); 65 | 66 | /// 67 | /// Clears the stack when the state, you currently describe, is entered. 68 | /// 69 | StateFluent ClearsStack(); 70 | } 71 | } -------------------------------------------------------------------------------- /StateMachine/Fluent/Api/TransitionFluent.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using StateMachine.Events; 30 | 31 | namespace StateMachine.Fluent.Api 32 | { 33 | public interface TransitionFluent 34 | { 35 | /// 36 | /// Automatically walks the transition you're currently describing, if the specified amount of time has passed. 37 | /// 38 | /// The amount of time. 39 | /// 40 | TransitionStateFluent After(TimeSpan timeSpan); 41 | /// 42 | /// Specifies the trigger, that has to be served as input in order to walk the transition you're currently describing. 43 | /// 44 | /// The trigger. 45 | TransitionStateFluent On(TT trigger); 46 | 47 | /// 48 | /// Specifies the condition, that has to be met, in addition to the trigger, to walk the transition you're currently 49 | /// describing. 50 | /// 51 | /// The condition. 52 | TransitionStateFluent If(Func, bool> condition); 53 | } 54 | } -------------------------------------------------------------------------------- /StateMachine/Fluent/Api/TransitionStateFluent.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace StateMachine.Fluent.Api 29 | { 30 | public interface TransitionStateFluent : TransitionFluent, 31 | StateFluent 32 | { 33 | } 34 | } -------------------------------------------------------------------------------- /StateMachine/Fsm.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using System.Collections.Generic; 30 | using System.Text; 31 | using StateMachine.Events; 32 | using StateMachine.Fluent.Api; 33 | 34 | namespace StateMachine 35 | { 36 | public class Fsm 37 | { 38 | private FsmModel Model { get; } = new FsmModel(); 39 | 40 | public State Current => Model.Current; 41 | public State StartState => Model.StartState; 42 | public Stack> Stack => Model.Stack; 43 | 44 | public Dictionary, List>> AfterEntries { get; set; } = 45 | new Dictionary, List>>(); 46 | 47 | public List> GlobalAfterEntries { get; set; } = new List>(); 48 | 49 | /// When the model is null 50 | public Fsm(FsmModel model) 51 | { 52 | Model = model ?? throw FsmBuilderException.ModelCannotBeNull(); 53 | if (Model.StackEnabled && !model.Current.ClearStack) 54 | { 55 | Model.Stack.Push(model.Current); 56 | } 57 | } 58 | 59 | /// When the initial state is null 60 | public Fsm(State current, bool stackEnabled = false) 61 | { 62 | Model.StackEnabled = stackEnabled; 63 | Model.Current = current ?? throw FsmBuilderException.StateCannotBeNull(); 64 | if (Model.StackEnabled && !current.ClearStack) 65 | { 66 | Model.Stack.Push(current); 67 | } 68 | } 69 | 70 | /// 71 | /// Gets you a builder for a Finite-State-Machine (FSM). 72 | /// 73 | /// The start state's key. 74 | /// 75 | public static BuilderFluent Builder(TS startState) 76 | => new FluentImplementation(startState); 77 | 78 | /// When the handler is null 79 | public Fsm AddStateChangeHandler( 80 | EventHandler> e) 81 | { 82 | if (e == null) throw FsmBuilderException.HandlerCannotBeNull(); 83 | Model.StateChanged += e; 84 | return this; 85 | } 86 | 87 | /// When the state is null or the state has already been added before 88 | public Fsm Add(State state) 89 | { 90 | if (state == null) throw FsmBuilderException.StateCannotBeNull(); 91 | if (Model.States.ContainsKey(state.Identifier)) throw FsmBuilderException.StateCanOnlyBeAddedOnce(state); 92 | 93 | Model.States.Add(state.Identifier, state); 94 | return this; 95 | } 96 | 97 | /// 98 | /// When the transition is null or another transition already leads to the same 99 | /// target state 100 | /// 101 | public Fsm Add(Transition t) 102 | { 103 | if (t == null) throw FsmBuilderException.TransitionCannotBeNull(); 104 | Model.GlobalTransitions.Add(t.Target, t); 105 | return this; 106 | } 107 | 108 | public void JumpTo(TS state, bool isPop = false) 109 | { 110 | State s; 111 | if (Model.States.TryGetValue(state, out s)) 112 | { 113 | DoTransition(state, default(TT), isPop); 114 | } 115 | } 116 | 117 | private void DoTransition(TS state, TT input, bool isPop) 118 | { 119 | if (state == null || input == null) return; 120 | 121 | var old = Model.Current; 122 | if (Model.StackEnabled && isPop) 123 | { 124 | Model.Stack.Pop(); 125 | Model.Current = Model.Stack.Peek(); 126 | } 127 | else 128 | { 129 | Model.Current = Model.States[state]; 130 | if (Model.StackEnabled) 131 | Model.Stack.Push(Model.Current); 132 | } 133 | 134 | if (Model.StackEnabled && Model.Current.ClearStack) 135 | { 136 | Model.Stack.Clear(); 137 | } 138 | 139 | if (Model.Current.Equals(old)) return; 140 | 141 | var args = 142 | new StateChangeArgs(this, old, Model.Current, input); 143 | Exited(old, args); 144 | Entered(args); 145 | StateChanged(args); 146 | } 147 | 148 | private void Entered(StateChangeArgs args) 149 | { 150 | ResetCurrentAfterEntries(); 151 | Model.Current.RaiseEntered(args); 152 | } 153 | 154 | private void Exited(State old, StateChangeArgs args) 155 | { 156 | old.RaiseExited(args); 157 | } 158 | 159 | private void StateChanged(StateChangeArgs args) 160 | { 161 | Model.RaiseStateChanged(args); 162 | } 163 | 164 | public void Trigger(TT input) 165 | { 166 | if (input == null) return; 167 | 168 | foreach (var g in Model.GlobalTransitions.Values) 169 | { 170 | if (!g.Process(Model.Current, input)) continue; 171 | 172 | DoTransition(g.Target, input, g.Pop); 173 | return; 174 | } 175 | 176 | var t = Model.Current.Process(input); 177 | if (t != null) 178 | { 179 | DoTransition(t.Target, input, t.Pop); 180 | } 181 | } 182 | 183 | public void Update(TimeSpan elapsedTime) 184 | { 185 | // After-entries on transitions. 186 | foreach (var k in Current.Model.Transitions.Keys) 187 | { 188 | if (!AfterEntries.TryGetValue(new Tuple(Current.Identifier, k), out var currentAfterEntries)) 189 | continue; 190 | if (CheckAfterEntries(currentAfterEntries, Current.Model.Transitions, elapsedTime)) 191 | return; 192 | } 193 | 194 | // Global after-entries. 195 | if (CheckAfterEntries(GlobalAfterEntries, Model.GlobalTransitions, elapsedTime)) 196 | return; 197 | 198 | Model.Current.RaiseUpdated(new UpdateArgs(this, Current, elapsedTime)); 199 | } 200 | 201 | private bool CheckAfterEntries(List> afterEntries, 202 | Dictionary> transitions, TimeSpan timeSpan) 203 | { 204 | for (var i = 0; i < afterEntries.Count; i++) 205 | { 206 | var e = afterEntries[i]; 207 | if (!transitions.TryGetValue(e.Target, out var t)) continue; 208 | if (!t.ConditionsMet(Current.Identifier)) continue; 209 | 210 | var timerMax = e.Time; 211 | var r = e.Tick(timeSpan.TotalMilliseconds); 212 | afterEntries[i] = e; 213 | 214 | if (!r.HasValue) continue; 215 | 216 | // It triggered. 217 | DoTransition(e.Target, default(TT), t.Pop); 218 | Update(timeSpan.Subtract(TimeSpan.FromMilliseconds(timerMax))); 219 | return true; 220 | } 221 | 222 | return false; 223 | } 224 | 225 | private void ResetCurrentAfterEntries() 226 | { 227 | foreach (var k in Current.Model.Transitions.Keys) 228 | { 229 | if (!AfterEntries.TryGetValue(new Tuple(Current.Identifier, k), out var currentAfterEntries)) continue; 230 | for (var i = 0; i < currentAfterEntries.Count; i++) 231 | { 232 | var e = currentAfterEntries[i]; 233 | e.Reset(); 234 | currentAfterEntries[i] = e; 235 | } 236 | } 237 | } 238 | 239 | public string Normalize(string str) 240 | { 241 | StringBuilder sb = new StringBuilder(); 242 | foreach (char c in str) 243 | { 244 | if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_' || c == '-') 245 | { 246 | sb.Append(c); 247 | } 248 | } 249 | 250 | return sb.ToString(); 251 | } 252 | 253 | public String GetPlantUml() 254 | { 255 | StringBuilder sb = new StringBuilder(); 256 | sb.Append("@startuml\n"); 257 | sb.Append($"title {Normalize(GetType().Name)}\n"); 258 | sb.Append("left to right direction\n\n"); 259 | 260 | WriteStates(sb); 261 | WriteTransitions(sb); 262 | WriteStartState(sb); 263 | WriteEndStates(sb); 264 | 265 | sb.Append("@enduml\n"); 266 | 267 | return sb.ToString(); 268 | } 269 | 270 | private void WriteStates(StringBuilder sb) 271 | { 272 | foreach (var state in Model.States) 273 | { 274 | sb.Append($"state \"{state.Key}\" as {Normalize(state.Key.ToString())}\n"); 275 | } 276 | } 277 | 278 | private void WriteTransitions(StringBuilder sb) 279 | { 280 | foreach (var state in Model.States) 281 | { 282 | foreach (var transition in state.Value.Transitions) 283 | { 284 | foreach (var trigger in transition.Value.Triggers) 285 | { 286 | sb.Append( 287 | $"{Normalize(transition.Value.Source.ToString())} --> " + 288 | $"{Normalize(transition.Value.Target.ToString())} : " + 289 | $"\"{Normalize(trigger.ToString())}\"\n"); 290 | } 291 | } 292 | } 293 | } 294 | 295 | private void WriteStartState(StringBuilder sb) 296 | { 297 | sb.Append($"[*] --> {Normalize(StartState.ToString())}\n"); 298 | } 299 | 300 | private void WriteEndStates(StringBuilder sb) 301 | { 302 | foreach (var state in Model.States) 303 | { 304 | if (state.Value.EndState) 305 | sb.Append($"{Normalize(state.Key.ToString())} --> [*]\n"); 306 | } 307 | } 308 | } 309 | } -------------------------------------------------------------------------------- /StateMachine/FsmBuilderException.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | 30 | namespace StateMachine 31 | { 32 | public class FsmBuilderException : Exception 33 | { 34 | private FsmBuilderException(string message) : base(message) 35 | { 36 | } 37 | 38 | public static FsmBuilderException StateCanOnlyBeAddedOnce( 39 | State state) 40 | => new FsmBuilderException($"A state [{state.Identifier}] has already been added. You can only one add" + 41 | " a state with a unique identifier once."); 42 | 43 | public static FsmBuilderException TargetStateCannotBeNull() 44 | => new FsmBuilderException("The target of a transition cannot be null."); 45 | 46 | public static FsmBuilderException TransitionCannotBeNull() 47 | => new FsmBuilderException("The transition cannot be null."); 48 | 49 | public static FsmBuilderException StateCannotBeNull() => new FsmBuilderException("The state cannot be null."); 50 | 51 | public static FsmBuilderException StartStateCannotBeNull() 52 | => new FsmBuilderException("The start state cannot be null."); 53 | 54 | public static FsmBuilderException ModelCannotBeNull() => new FsmBuilderException("The model cannot be null."); 55 | 56 | public static FsmBuilderException TriggerAlreadyDeclared(TT trigger) 57 | => new FsmBuilderException($"The transition already contains the trigger [{trigger}]"); 58 | 59 | public static FsmBuilderException HandlerCannotBeNull() 60 | => new FsmBuilderException("The handler you want to add cannot be null."); 61 | } 62 | } -------------------------------------------------------------------------------- /StateMachine/FsmModel.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using System.Collections.Generic; 30 | using StateMachine.Events; 31 | 32 | namespace StateMachine 33 | { 34 | public class FsmModel 35 | { 36 | public event EventHandler> StateChanged; 37 | 38 | public State Current { get; set; } 39 | public State StartState { get; set; } 40 | 41 | public Stack> Stack { get; } = new Stack>(); 42 | public bool StackEnabled { get; set; } 43 | 44 | public Dictionary> States { get; } = new Dictionary>(); 45 | 46 | public Dictionary> GlobalTransitions { get; } = 47 | new Dictionary>(); 48 | 49 | public void AddStateChangedHandler( 50 | EventHandler> e) 51 | { 52 | StateChanged += e ?? throw FsmBuilderException.HandlerCannotBeNull(); 53 | } 54 | 55 | public void RaiseStateChanged(StateChangeArgs e) => StateChanged?.Invoke(this, e); 56 | } 57 | } -------------------------------------------------------------------------------- /StateMachine/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("StateMachine")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Unterrainer Informatik OG")] 11 | [assembly: AssemblyProduct("StateMachine")] 12 | [assembly: AssemblyCopyright("Copyright Unterrainer Informatik © 2017")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | [assembly: NeutralResourcesLanguage("en")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Build and Revision Numbers 25 | // by using the '*' as shown below: 26 | // [assembly: AssemblyVersion("1.0.*")] 27 | [assembly: AssemblyVersion("1.2.0.1")] 28 | [assembly: AssemblyFileVersion("1.2.0.1")] 29 | -------------------------------------------------------------------------------- /StateMachine/State.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using System.Collections.Generic; 30 | using StateMachine.Events; 31 | 32 | namespace StateMachine 33 | { 34 | public class State 35 | { 36 | public StateModel Model { get; set; } 37 | 38 | public TS Identifier => Model.Identifier; 39 | public Dictionary> Transitions => Model.Transitions; 40 | public bool EndState => Model.EndState; 41 | 42 | public bool ClearStack 43 | { 44 | get => Model.ClearStack; 45 | set => Model.ClearStack = value; 46 | } 47 | 48 | public void RaiseUpdated(UpdateArgs args) => Model.RaiseUpdated(args); 49 | public void RaiseEntered(StateChangeArgs args) => Model.RaiseEntered(args); 50 | public void RaiseExited(StateChangeArgs args) => Model.RaiseExited(args); 51 | 52 | public State(StateModel model) 53 | { 54 | Model = model; 55 | } 56 | 57 | public State(TS identifier) : this(new StateModel(identifier)) 58 | { 59 | } 60 | 61 | /// When the transition is null 62 | public State Add(Transition t) 63 | { 64 | if (t == null) throw FsmBuilderException.TransitionCannotBeNull(); 65 | 66 | t.Source = Identifier; 67 | Model.Transitions.Add(t.Target, t); 68 | return this; 69 | } 70 | 71 | public State AddTransisionOn(TT trigger, TS target) 72 | => Add(new Transition(trigger, Identifier, target)); 73 | 74 | public State AddPopTransisionOn(TT trigger) => Add(new Transition(trigger, Identifier)); 75 | 76 | public Transition Process(TT input) 77 | { 78 | foreach (var t in Model.Transitions.Values) 79 | { 80 | if (t.Process(this, input)) 81 | { 82 | return t; 83 | } 84 | } 85 | return null; 86 | } 87 | 88 | public override string ToString() => Model.Identifier.ToString(); 89 | } 90 | } -------------------------------------------------------------------------------- /StateMachine/StateMachine.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 10.0 6 | Debug 7 | AnyCPU 8 | {BAEAB06B-25D7-4D2A-B21D-4435C0DB794C} 9 | Library 10 | StateMachine 11 | StateMachine 12 | en-US 13 | 512 14 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | Profile111 16 | v4.5 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /StateMachine/StateMachine.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Unterrainer Informatik OG Team 8 | Public Domain 9 | http://unlicense.org 10 | https://github.com/UnterrainerInformatik/FiniteStateMachine/raw/master/StateMachine/icon.png 11 | https://github.com/UnterrainerInformatik/FiniteStateMachine 12 | false 13 | This project implements a Finite-State-Machine (FSM) as a PCL (portable class library) designed to be used in games. 14 | Furthermore it implements even a Stack-Based-FSM (SBFSM). So you may tell it to 'continue with the last state before the active one'. 15 | You describe your FSM using a nice and well documented DSL (Domain Specific Language). 16 | 17 | This replaces the code we usually had for keyboard-input (run-left-right-duck-jump), clicked buttons on the GUI (idle-over-down-refreshing), tower-states (idle-aiming-firing-reloading) or for the connection procedure when setting up peer2peer connections in our games.... 18 | Build complex machines holding the state of your game components using an intuitive fluent DSL. 19 | 20 | Copyright 2017 21 | en-US 22 | state finite machine gamestate transition fluent after update timespan 23 | 24 | -------------------------------------------------------------------------------- /StateMachine/StateModel.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using System.Collections.Generic; 30 | using StateMachine.Events; 31 | 32 | namespace StateMachine 33 | { 34 | public class StateModel 35 | { 36 | public event Action> Entered; 37 | public event Action> Exited; 38 | public event Action> Updated; 39 | 40 | public TS Identifier { get; private set; } 41 | public bool EndState { get; set; } 42 | public bool ClearStack { get; set; } 43 | 44 | public Dictionary> Transitions { get; } = 45 | new Dictionary>(); 46 | 47 | public StateModel(TS identifier) 48 | { 49 | Identifier = identifier; 50 | } 51 | 52 | /// When the handler is null 53 | public void AddEnteredHandler(Action> e) 54 | { 55 | Entered += e ?? throw FsmBuilderException.HandlerCannotBeNull(); 56 | } 57 | 58 | public void RaiseEntered(StateChangeArgs e) => Entered?.Invoke(e); 59 | 60 | /// When the handler is null 61 | public void AddExitedHandler(Action> e) 62 | { 63 | Exited += e ?? throw FsmBuilderException.HandlerCannotBeNull(); 64 | } 65 | 66 | public void RaiseExited(StateChangeArgs e) => Exited?.Invoke(e); 67 | 68 | /// When the handler is null 69 | public void AddUpdatedHandler(Action> e) 70 | { 71 | Updated += e ?? throw FsmBuilderException.HandlerCannotBeNull(); 72 | } 73 | 74 | public void RaiseUpdated(UpdateArgs data) => Updated?.Invoke(data); 75 | } 76 | } -------------------------------------------------------------------------------- /StateMachine/TimeUnit.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace StateMachine 29 | { 30 | public enum TimeUnit : long 31 | { 32 | MILLISECONDS = 1, 33 | SECONDS = 1000, 34 | MINUTES = SECONDS * 60, 35 | HOURS = MINUTES * 60, 36 | DAYS = HOURS * 24, 37 | WEEKS = DAYS * 7 38 | } 39 | } -------------------------------------------------------------------------------- /StateMachine/Timer.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | namespace StateMachine 29 | { 30 | public struct Timer 31 | { 32 | public double Value { get; set; } 33 | public TimeUnit Unit { get; set; } 34 | public double Time { get; set; } 35 | public TS Target { get; set; } 36 | 37 | public Timer(TS target, double value, TimeUnit unit) 38 | { 39 | Target = target; 40 | Value = value; 41 | Unit = unit; 42 | Time = 0; 43 | Reset(); 44 | } 45 | 46 | public void Reset() 47 | { 48 | Time = Value * (long) Unit; 49 | } 50 | 51 | /// 52 | /// Lets the specified time tick away and subtracts it from the Timer.
53 | /// If the timer triggered the time that was left after the Timer triggered is returned. 54 | ///
55 | /// The time to tick away. 56 | /// Null if the timer didn't trigger, a positive value otherwise. 57 | public double? Tick(double timeInMillis) 58 | { 59 | Time -= timeInMillis; 60 | if (!(Time <= 0D)) return null; 61 | 62 | var d = Time * -1D; 63 | Reset(); 64 | return d; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /StateMachine/Transition.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System.Collections.Generic; 29 | using System.Collections.ObjectModel; 30 | using StateMachine.Events; 31 | 32 | namespace StateMachine 33 | { 34 | public class Transition 35 | { 36 | private TransitionModel Model { get; set; } 37 | 38 | public TS Target => Model.Target; 39 | public ISet Triggers => Model.Triggers; 40 | 41 | public bool Pop => Model.Pop; 42 | 43 | public TS Source 44 | { 45 | get => Model.Source; 46 | set => Model.Source = value; 47 | } 48 | 49 | public Transition(TransitionModel model) 50 | { 51 | Model = model; 52 | } 53 | 54 | /// When target is null 55 | public Transition(Collection triggers, TS source, TS target) 56 | { 57 | if (target == null) throw FsmBuilderException.TargetStateCannotBeNull(); 58 | 59 | Model = new TransitionModel(source, target); 60 | Model.Triggers.UnionWith(triggers); 61 | } 62 | 63 | /// 64 | /// 65 | /// Generates a new Transition that will pop from the stack and therefore doesn't need a target.
66 | /// The target will vary depending on the items on the stack. 67 | ///
68 | /// The triggers. 69 | /// The source. 70 | public Transition(Collection triggers, TS source) 71 | : this(triggers, source, default(TS)) 72 | { 73 | Model.Pop = true; 74 | } 75 | 76 | /// 77 | /// When target is null 78 | public Transition(TT trigger, TS source, TS target) 79 | : this(new Collection {trigger}, source, target) 80 | { 81 | } 82 | 83 | /// 84 | /// 85 | /// Generates a new Transition that will pop from the stack and therefore doesn't need a target.
86 | /// The target will vary depending on the items on the stack. 87 | ///
88 | /// The trigger. 89 | /// The source. 90 | public Transition(TT trigger, TS source) 91 | : this(new Collection {trigger}, source) 92 | { 93 | } 94 | 95 | /// When trigger has been declared before 96 | public void Add(TT trigger) 97 | { 98 | if (Model.Triggers.Contains(trigger)) throw FsmBuilderException.TriggerAlreadyDeclared(trigger); 99 | Model.Triggers.Add(trigger); 100 | } 101 | 102 | public bool Process(State from, TT input) 103 | => Model.Triggers.Contains(input) && ConditionsMet(from.Identifier); 104 | 105 | public bool ConditionsMet(TS state) 106 | => Model.Conditions.TrueForAll(x => x(new IfArgs(state, Model.Target))); 107 | 108 | public override string ToString() => $"{Model.Source}-({string.Join(",", Model.Triggers)})->{Model.Target}"; 109 | } 110 | } -------------------------------------------------------------------------------- /StateMachine/TransitionModel.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using System.Collections.Generic; 30 | using StateMachine.Events; 31 | 32 | namespace StateMachine 33 | { 34 | public class TransitionModel 35 | { 36 | public ISet Triggers { get; set; } = new HashSet(); 37 | public TS Source { get; set; } 38 | public TS Target { get; private set; } 39 | public bool Pop { get; set; } 40 | 41 | public List, bool>> Conditions { get; } = new List, bool>>(); 42 | 43 | public TransitionModel(TS source, TS target) 44 | { 45 | Source = source; 46 | Target = target; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /StateMachine/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/StateMachine/icon.png -------------------------------------------------------------------------------- /TestGame/Content/Content.mgcb: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------- Global Properties ----------------------------# 3 | 4 | /outputDir:bin/$(Platform) 5 | /intermediateDir:obj/$(Platform) 6 | /platform:Windows 7 | /config: 8 | /profile:Reach 9 | /compress:False 10 | 11 | #-------------------------------- References --------------------------------# 12 | 13 | 14 | #---------------------------------- Content ---------------------------------# 15 | 16 | #begin spaceman.png 17 | /importer:TextureImporter 18 | /processor:TextureProcessor 19 | /processorParam:ColorKeyColor=255,0,255,255 20 | /processorParam:ColorKeyEnabled=True 21 | /processorParam:GenerateMipmaps=False 22 | /processorParam:PremultiplyAlpha=True 23 | /processorParam:ResizeToPowerOfTwo=False 24 | /processorParam:MakeSquare=False 25 | /processorParam:TextureFormat=Color 26 | /build:spaceman.png 27 | 28 | -------------------------------------------------------------------------------- /TestGame/Content/spaceman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/TestGame/Content/spaceman.png -------------------------------------------------------------------------------- /TestGame/Game1.cs: -------------------------------------------------------------------------------- 1 | // *************************************************************************** 2 | // This is free and unencumbered software released into the public domain. 3 | // 4 | // Anyone is free to copy, modify, publish, use, compile, sell, or 5 | // distribute this software, either in source code form or as a compiled 6 | // binary, for any purpose, commercial or non-commercial, and by any 7 | // means. 8 | // 9 | // In jurisdictions that recognize copyright laws, the author or authors 10 | // of this software dedicate any and all copyright interest in the 11 | // software to the public domain. We make this dedication for the benefit 12 | // of the public at large and to the detriment of our heirs and 13 | // successors. We intend this dedication to be an overt act of 14 | // relinquishment in perpetuity of all present and future rights to this 15 | // software under copyright law. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | // OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | // For more information, please refer to 26 | // *************************************************************************** 27 | 28 | using System; 29 | using Microsoft.Xna.Framework; 30 | using Microsoft.Xna.Framework.Graphics; 31 | using Microsoft.Xna.Framework.Input; 32 | using MonoGame.Extended.Animations; 33 | using MonoGame.Extended.Animations.SpriteSheets; 34 | using MonoGame.Extended.TextureAtlases; 35 | using StateMachine; 36 | using SpriteExtensions = MonoGame.Extended.Sprites.SpriteExtensions; 37 | 38 | namespace TestGame 39 | { 40 | /// 41 | /// This is the main type for your game. 42 | /// 43 | public class Game1 : Game 44 | { 45 | GraphicsDeviceManager graphics; 46 | SpriteBatch spriteBatch; 47 | private TextureAtlas atlas; 48 | private AnimatedSprite hero; 49 | private bool lastWasRight; 50 | 51 | Fsm heroStateMachine; 52 | 53 | public Game1() 54 | { 55 | graphics = new GraphicsDeviceManager(this); 56 | Content.RootDirectory = "Content"; 57 | } 58 | 59 | protected override void Initialize() 60 | { 61 | Window.Title = $"StateMachine - {GetType().Name}"; 62 | base.Initialize(); 63 | } 64 | 65 | /// 66 | /// LoadContent will be called once per game and is the place to load 67 | /// all of your content. 68 | /// 69 | protected override void LoadContent() 70 | { 71 | // Create a new SpriteBatch, which can be used to draw textures. 72 | spriteBatch = new SpriteBatch(GraphicsDevice); 73 | 74 | var texture = Content.Load("spaceman"); 75 | atlas = TextureAtlas.Create("hero-atlas", texture, 50, 50, 24, 1, 1); 76 | 77 | var factory = new SpriteSheetAnimationFactory(atlas); 78 | factory.Add("idle_left", new SpriteSheetAnimationData(new[] {1})); 79 | factory.Add("idle_right", new SpriteSheetAnimationData(new[] {0})); 80 | factory.Add("walk_left", new SpriteSheetAnimationData(new[] {16, 17, 18, 19, 20, 21, 22, 23})); 81 | factory.Add("walk_right", new SpriteSheetAnimationData(new[] {8, 9, 10, 11, 12, 13, 14, 15})); 82 | factory.Add("jump_left", new SpriteSheetAnimationData(new[] {5, 7}, isLooping: false)); 83 | factory.Add("jump_right", new SpriteSheetAnimationData(new[] {4, 6}, isLooping: false)); 84 | factory.Add("duck_left", new SpriteSheetAnimationData(new[] {3})); 85 | factory.Add("duck_right", new SpriteSheetAnimationData(new[] {2})); 86 | 87 | hero = new AnimatedSprite(factory); 88 | 89 | heroStateMachine = Fsm.Builder("idle_left") 90 | .State("idle_left") 91 | .Update(args => hero.Play(args.State.Identifier)) 92 | .TransitionTo("walk_left").On(Keys.Left) 93 | .TransitionTo("duck_left").On(Keys.Down) 94 | .TransitionTo("jump_left").On(Keys.Up) 95 | .TransitionTo("idle_right").On(Keys.Right) 96 | .State("idle_right") 97 | .Update(args => hero.Play(args.State.Identifier)) 98 | .TransitionTo("walk_right").On(Keys.Right) 99 | .TransitionTo("duck_right").On(Keys.Down) 100 | .TransitionTo("jump_right").On(Keys.Up) 101 | .TransitionTo("idle_left").On(Keys.Left) 102 | .State("walk_left") 103 | .Update(args => { 104 | if (Keyboard.GetState().IsKeyDown(Keys.Left)) 105 | { 106 | hero.Play(args.State.Identifier); 107 | } 108 | else 109 | { 110 | args.Machine.JumpTo("idle_left"); 111 | } 112 | }) 113 | .TransitionTo("duck_left").On(Keys.Down) 114 | .TransitionTo("jump_left").On(Keys.Up) 115 | .TransitionTo("idle_right").On(Keys.Right) 116 | .State("walk_right") 117 | .Update(args => { 118 | if (Keyboard.GetState().IsKeyDown(Keys.Right)) 119 | { 120 | hero.Play(args.State.Identifier); 121 | } 122 | else 123 | { 124 | args.Machine.JumpTo("idle_right"); 125 | } 126 | }) 127 | .TransitionTo("duck_right").On(Keys.Down) 128 | .TransitionTo("jump_right").On(Keys.Up) 129 | .TransitionTo("idle_left").On(Keys.Left) 130 | .State("jump_left") 131 | .OnEnter(args => hero.Play(args.To.Identifier).OnCompleted = () => args.Fsm.JumpTo("idle_left")) 132 | .State("jump_right") 133 | .OnEnter(args => hero.Play(args.To.Identifier).OnCompleted = () => args.Fsm.JumpTo("idle_right")) 134 | .State("duck_left") 135 | .Update(args => { 136 | if (Keyboard.GetState().IsKeyDown(Keys.Down)) 137 | { 138 | hero.Play(args.State.Identifier); 139 | } 140 | else 141 | { 142 | args.Machine.JumpTo("idle_left"); 143 | } 144 | }) 145 | .TransitionTo("walk_right").On(Keys.Right) 146 | .TransitionTo("walk_left").On(Keys.Left) 147 | .TransitionTo("jump_left").On(Keys.Up) 148 | .State("duck_right") 149 | .Update(args => { 150 | if (Keyboard.GetState().IsKeyDown(Keys.Down)) 151 | { 152 | hero.Play(args.State.Identifier); 153 | } 154 | else 155 | { 156 | args.Machine.JumpTo("idle_right"); 157 | } 158 | }) 159 | .TransitionTo("walk_right").On(Keys.Right) 160 | .TransitionTo("walk_left").On(Keys.Left) 161 | .TransitionTo("jump_right").On(Keys.Up) 162 | .Build(); 163 | } 164 | 165 | 166 | 167 | /// 168 | /// Allows the game to run logic such as updating the world, 169 | /// checking for collisions, gathering input, and playing audio. 170 | /// 171 | /// Provides a snapshot of timing values. 172 | protected override void Update(GameTime gameTime) 173 | { 174 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 175 | Keyboard.GetState().IsKeyDown(Keys.Escape)) 176 | Exit(); 177 | 178 | hero.Position = new Vector2(GraphicsDevice.Viewport.Width / 2f, GraphicsDevice.Viewport.Height / 2f); 179 | 180 | if (Keyboard.GetState().CapsLock) 181 | { 182 | UpdateOld(gameTime); 183 | } 184 | else 185 | { 186 | UpdateNew(gameTime); 187 | } 188 | 189 | hero.Update(gameTime); 190 | base.Update(gameTime); 191 | } 192 | 193 | protected void UpdateNew(GameTime gameTime) 194 | { 195 | foreach (Keys key in Keyboard.GetState().GetPressedKeys()) 196 | { 197 | heroStateMachine.Trigger(key); 198 | } 199 | heroStateMachine.Update(TimeSpan.FromMilliseconds(gameTime.ElapsedGameTime.TotalMilliseconds)); 200 | } 201 | 202 | protected void UpdateOld(GameTime gameTime) { 203 | var s = Keyboard.GetState(); 204 | if (s.IsKeyDown(Keys.Left)) 205 | { 206 | hero.Play("walk_left"); 207 | lastWasRight = false; 208 | } 209 | else if (s.IsKeyDown(Keys.Right)) 210 | { 211 | hero.Play("walk_right"); 212 | lastWasRight = true; 213 | } 214 | else if (s.IsKeyDown(Keys.Up)) 215 | { 216 | if (lastWasRight) 217 | { 218 | hero.Play("jump_right"); 219 | } 220 | else 221 | { 222 | hero.Play("jump_left"); 223 | } 224 | } 225 | else if (s.IsKeyDown(Keys.Down)) 226 | { 227 | if (lastWasRight) 228 | { 229 | hero.Play("duck_right"); 230 | } 231 | else 232 | { 233 | hero.Play("duck_left"); 234 | } 235 | } 236 | else 237 | { 238 | if (lastWasRight) 239 | { 240 | hero.Play("idle_right"); 241 | } 242 | else 243 | { 244 | hero.Play("idle_left"); 245 | } 246 | } 247 | } 248 | 249 | /// 250 | /// This is called when the game should draw itself. 251 | /// 252 | /// Provides a snapshot of timing values. 253 | protected override void Draw(GameTime gameTime) 254 | { 255 | GraphicsDevice.Clear(Color.CornflowerBlue); 256 | 257 | spriteBatch.Begin(); 258 | SpriteExtensions.Draw(hero, spriteBatch); 259 | spriteBatch.End(); 260 | 261 | base.Draw(gameTime); 262 | } 263 | } 264 | } -------------------------------------------------------------------------------- /TestGame/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/TestGame/Icon.ico -------------------------------------------------------------------------------- /TestGame/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TestGame 4 | { 5 | #if WINDOWS || LINUX 6 | /// 7 | /// The main class. 8 | /// 9 | public static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | using (var game = new Game1()) 18 | game.Run(); 19 | } 20 | } 21 | #endif 22 | } 23 | -------------------------------------------------------------------------------- /TestGame/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TestGame")] 8 | [assembly: AssemblyProduct("TestGame")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyCompany("Unterrainer Informatik OG")] 12 | [assembly: AssemblyCopyright("Copyright Unterrainer Informatik © 2017")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("970f1365-9c71-43ef-a57f-4340e2ce3c88")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.1.2.1")] 35 | [assembly: AssemblyFileVersion("1.1.2.1")] 36 | -------------------------------------------------------------------------------- /TestGame/TestGame.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x86 7 | 8.0.30703 8 | 2.0 9 | {B7DCCAB0-9899-421D-90F5-8E694EEE403F} 10 | WinExe 11 | Properties 12 | TestGame 13 | TestGame 14 | 512 15 | Windows 16 | v4.5.1 17 | 18 | 19 | 20 | x86 21 | true 22 | full 23 | false 24 | bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\ 25 | DEBUG;TRACE;WINDOWS 26 | prompt 27 | 4 28 | 29 | 30 | x86 31 | pdbonly 32 | true 33 | bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\ 34 | TRACE;WINDOWS 35 | prompt 36 | 4 37 | 38 | 39 | Icon.ico 40 | 41 | 42 | app.manifest 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ..\packages\MonoGame.Extended.0.6.602\lib\portable-net45+win8+wpa81\MonoGame.Extended.dll 52 | True 53 | 54 | 55 | ..\packages\MonoGame.Extended.Animations.0.6.602\lib\portable-net45+win8+wpa81\MonoGame.Extended.Animations.dll 56 | True 57 | 58 | 59 | $(MonoGameInstallDirectory)\MonoGame\v3.0\Assemblies\Windows\MonoGame.Framework.dll 60 | 61 | 62 | ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 63 | True 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | {baeab06b-25d7-4d2a-b21d-4435c0db794c} 80 | StateMachine 81 | 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /TestGame/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /TestGame/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | true/pm 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /TestGame/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/docs/button.png -------------------------------------------------------------------------------- /docs/jumping_diving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/docs/jumping_diving.png -------------------------------------------------------------------------------- /docs/standing_ducking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/docs/standing_ducking.png -------------------------------------------------------------------------------- /docs/walking_running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnterrainerInformatik/FiniteStateMachine/090f13d5acd481a2da8206efcbb7526e523f814a/docs/walking_running.png --------------------------------------------------------------------------------