Issue creating my Pokemon game global organisation - c++

For context I am recreating a Pokemon game in c++(I am new to C++ i have only 1 year xp in programming and i was programming in C). But i have issue with the organisation of my code.
Here is my problem: I don't know what to put in class and what to put in struct.
For example in my current code my Pokemon and my Attaque are 2 different class. I have done that because of the fact that I want the 2 of them to be init with info in a file. But if the Pokemon are fighting they need to have access to the Attaque. But when generating my Pokemon some stat change when it's affected by item. Like shiny % is affected by Shiny Charm. So i need to have access to my bag too.
The problem here is that I am making a lot of class friend (right now Attaque is friend of Pokemon and Pokemon is friend of bag). And I have read that it's maybe an organisation code issue.
I have thought about making getters for example for the Shiny Charm, but I don't know if it's good or not.
Here is my Pokemon class. nature_e, status_e and type_e are enum class.
class Pokemon {
private:
bool m_is_shiny { false };
bool m_is_pokerus { false };
short m_level { 0 };
int m_id { 0 };
int m_exp { 0 };
std::array<int, 2> m_texture_coord { 0, 0 };
std::array<unsigned char, 6> m_iv {0, 0, 0, 0, 0, 0};
std::array<unsigned char, 6> m_ev {0, 0, 0, 0, 0, 0};
std::array<unsigned char, 6> m_base_stat {0, 0, 0, 0, 0, 0};
std::array<unsigned char, 8> m_stat {0, 0, 0, 0, 0, 0, 0, 0};
nature_e m_nature {nature_e::NONE};
status_e m_status {status_e::NONE};
std::array<type_e, 2> m_type {type_e::NONE, type_e:: NONE};
Texture2D m_texture;
std::string m_name { "Non" };
std::string m_item { "non" };
std::string m_sprite_path { "None" };
std::array<Attaque, 4> m_atk { Attaque(), Attaque(), Attaque(), Attaque() };
public:
Pokemon()
{
}
Pokemon(int id, short level);
};
Here is my Attaque class:
class Attaque {
private:
bool m_phy_or_spe;
int m_damage;
type_e m_type;
double m_effect_precision;
double m_precision;
std::string m_name;
public:
Attaque()
{
}
Attaque(int id);
};

Related

Create variants of templated class with different initialization

I am creating multiple different types of encoders where the main difference is the different data structures used to initialize the class. My header is something like this
struct tagTypeInfo {
uint16_t start;
uint16_t last;
uint16_t count;
std::string name;
rdwrT rdwr;
};
template <typename T>
class encodedTag
{
public:
encodedTag(vector<tagTypeInfo> tagInfo_) : tagInfo(tagInfo_)
{
int start = 0;
for(auto & tag : tagInfo)
{
tag.start = start;
tag.last = start + tag.count - 1;
start = start + tag.count;
}
}
uint16_t encode(uint16_t tag, T tagType)
{
assert(tag<tagInfo[tagType].count)
return( tagInfo[tagType].start + tag );
}
std::tuple<uint16_t, T> decode(uint16_t encodedTag)
{
int type = 0;
uint16_t tag;
// simple linear search as there are only a few entries
for (auto it = begin(tagInfo); it != end(tagInfo); it++)
{
if (encodedTag >= it->start && encodedTag < it->last )
{
// tag is in the range
return {encodedTag - it->start , (T)type};
}
type++;
}
assert(false);
return {0,(T)0};
}
std::string getName(T tagType) {return(tagInfo[tagType].name);}
rdwrT getRdwr(T tagType) {return(tagInfo[tagType].rdwr);}
private:
std::vector<tagTypeInfo> tagInfo;
};
extern std::vector<tagTypeInfo> rdTag;
extern std::vector<tagTypeInfo> wrTag;
//using rdTagEncode = encodedTag<rdTagT>(rdTag) <-- error
The cpp file contains:
std::vector<tagTypeInfo> rdTag {
{0, 0, NUM_HOSTRDTAG, "HostRdTag", RDWR_RD},
{0, 0, NUM_SYSRDTAG, "SysRdTag", RDWR_RD},
{0, 0, NUM_GCRDTAG, "GCRdTag", RDWR_RD}
};
std::vector<tagTypeInfo> wrTag {
{0, 0, NUM_HOSTWRTAG, "HostWrTag", RDWR_WR},
{0, 0, NUM_SYSWRTAG, "SysWrTag", RDWR_WR},
{0, 0, NUM_GCWRTAG, "GCWrTag", RDWR_WR}
};
My goal is to be able to just declare an encoder in the code elsewhere with
rdTagEncode myEncode;
However I cant seem to figure out the right syntax to do this. Any suggestions?
Using a derived class was the best solution. Thanks for the suggestion #appleapple
class encodedRdTag : public encodedTag<rdTagTypeT>
{
public:
encodedRdTag() : encodedTag({
{0, 0, NUM_HOSTRDTAG, "HostRdTag", RDWR_RD},
{0, 0, NUM_SYSRDTAG, "SysRdTag", RDWR_RD},
{0, 0, NUM_GCRDTAG, "GCRdTag", RDWR_RD}
}) {};
};
class encodedWrTag : public encodedTag<wrTagTypeT>
{
public:
encodedWrTag() : encodedTag({
{0, 0, NUM_HOSTRDTAG, "HostRdTag", RDWR_RD},
{0, 0, NUM_SYSRDTAG, "SysRdTag", RDWR_RD},
{0, 0, NUM_GCRDTAG, "GCRdTag", RDWR_RD}
}) {};
};
For completeness, here is the "dispatch based on type" method which I mentioned in comment above.
NOTE: this code doesn't work in original question as the rdTag and wrTag are same type thus independent of the class template T, but according to your own answer this may be actually what happends.
#include <vector>
struct tagTypeInfo{};
struct rdTagTypeT{};
struct wrTagTypeT{};
std::vector<tagTypeInfo> get_default_info(rdTagTypeT); // return wrTag
std::vector<tagTypeInfo> get_default_info(wrTagTypeT); // return wdTag
template <typename T>
struct encodedTag
{
encodedTag():encodedTag(get_default_info(T{})){}
encodedTag(std::vector<tagTypeInfo> tagInfo) : tagInfo(tagInfo){};
std::vector<tagTypeInfo> tagInfo;
};
using encodedRdTag = encodedTag<rdTagTypeT>;
using encodedWrTag = encodedTag<wrTagTypeT>;
void foo(){
encodedRdTag rd;
encodedWrTag rw;
}

How do I access list of structs with an integer argument (c++)?

I have a series of structs:
const struct weapon Dagger = { 1, 40, 5, 20, 30, "Dagger" };
const struct weapon Sword = { 2, 35, 25, 40, 60, "Sword" };
const struct weapon Axe = { 4, 50, 10, 70, 80, "Axe" };
const struct ...
I want to arrange them so I can access each one by integer. I am trying to build a function that takes int x and int y as arguments, and returns the indexed struct's data. For example, if the function takes 2 and 3, respectively, the value 35 from the weapon struct will be returned. Intuitively, I imagined the function body looking something like return weapon[x].y, although this does not work.
I do not know how to do this. The best alternative I can see is to use arrays instead of structs. Should I do this instead?
If you are only after the numeric data, then you could use two arrays: one for the data itself, and one for accessor function pointers:
struct weapon
{
int val;
int hp;
int foo;
// ...
std::string name;
};
const weapon w[] = {
{ 1, 40, 5, 20, 30, "Dagger" },
{ 2, 35, 25, 40, 60, "Sword" },
{ 4, 50, 10, 70, 80, "Axe" },
};
using accessor = int weapon::*;
const accessor acc[] = {
&weapon::val,
&weapon::hp,
&weapon::foo,
// ...
};
Now to look up property j in weapon i (both zero-indexed), you could say:
w[i].*acc[j]
Alternatively, you could perhaps represent your data as an array of pairs:
std::pair<const char*, std::array<int, 5>> w_alt[] = {
{ "Dagger", { 1, 40, 5, 20, 30 } },
{ "Sword", { 2, 35, 25, 40, 60 } },
{ "Axe", { 4, 50, 10, 70, 80 } },
};
Now the ith weapon's anme is w_alt[i].first, and its jth property is w_alt[i].second[j].
If you want a container that has indexed access, you probably want std::vector. Something like:
std::vector<weapon> WeaponVector;
For the first part of your question (selecting weapon based on index), using an array is a simple option, e.g.:
std::array<weapon, 3> const weapons =
{ { 1, 40, 5, 20, 30, "Dagger" }
, { 2, 35, 25, 40, 60, "Sword" }
, { 4, 50, 10, 70, 80, "Axe" }
};
Then, if you want, you can make specific references:
weapon const &Sword = weapons[1];
or make indices which you'll use to access the array:
enum weapon_id { Dagger, Sword, Axe };
// ...
do_something( weapons[Sword] );
Using std::array instead of a C-style array weapon const weapons[] =
has a drawback that you can't deduce the dimension, but it has other benefits that make up for this.
For the second part, you may add a function that looks up the weapon properties by index. For example:
int weapon_lookup_number(weapon const &w, int index)
{
switch(index)
{
case 0: return w.id;
case 1: return w.power;
// etc.
}
}
with another function for looking up the string.
Using template specialization it'd be possible to have a function that looks up the member by index and evaluates to the correct type, however that would only work in the case of the index being known at compile-time.
If your struct becomes:
struct weapon {
int val;
int hp;
int foo;
// ...
std::string name;
};
You could use a std::vector<weapon> Weapons to store the structs, sort it using:
std::sort(Weapons.begin(), Weapons.end(),
[](const Weapons& w1,const Weapons& w2) -> bool { return w1.val > w2.val; }))
and access them using the vector indexes like: weapons[some_num].hp;, where some_num will correspond to the wanted val.

Looking for an easy way to reinitialize a struct

I have a struct called CoolStruct:
struct CoolStruct
{
int id;
uint32 type;
uint32 subtype;
String name;
};
I have a vector of these structs as well:
std::vector<CoolStruct> coolVector;
I want to create a bunch of structs which have predefined values to push_back into this coolVector. I'd like to keep the code from getting cludgy and ugly. I would really like to keep this notation:
CoolStruct t = {1, EQData::EQ_EFFECT_TYPE_PARAMETRIC, 0, T("Parametric")};
coolVector.push_back(t);
CoolStruct t = {2, EQData::EQ_EFFECT_TYPE_FILTER_LOW_PASS,EQData::EQ_FILTER_TYPE_FILTER_BUTTERWORTH_12DB, T("Low Pass")};
coolVector.push_back(t);
But of course this doesn't work... Not allowed to do a reinitialization. Is there any other solution to make this as readable as possible? The only alternative I can think of is it manually set each paramater of the struct:
t.id = whatever; t.type = somethingelse; t.subtype = thisisalotofcode; t.name = justtosetupthisvector;
coolVector.push_back(t);
how about:
CoolStruct t1 = {1, EQData::EQ_EFFECT_TYPE_PARAMETRIC, 0, T("Parametric")};
coolVector.push_back(t1);
CoolStruct t2 = {2, EQData::EQ_EFFECT_TYPE_FILTER_LOW_PASS,EQData::EQ_FILTER_TYPE_FILTER_BUTTERWORTH_12DB, T("Low Pass")};
coolVector.push_back(t2);
In C++0x, I think you should be able to do:
CoolStruct t;
t = {1, EQData::EQ_EFFECT_TYPE_PARAMETRIC, 0, T("Parametric")};
coolVector.push_back(t);
t = {2, EQData::EQ_EFFECT_TYPE_FILTER_LOW_PASS,EQData::EQ_FILTER_TYPE_FILTER_BUTTERWORTH_12DB, T("Low Pass")};
coolVector.push_back(t);
or even:
coolVector.push_back({1, EQData::EQ_EFFECT_TYPE_PARAMETRIC, 0, T("Parametric")});
coolVector.push_back({2, EQData::EQ_EFFECT_TYPE_FILTER_LOW_PASS,EQData::EQ_FILTER_TYPE_FILTER_BUTTERWORTH_12DB, T("Low Pass")});
In fact, if you really want to get creative (and you don't have any previous elements that you want to keep), you can replace the whole vector with this syntax:
coolVector = {
{1, EQData::EQ_EFFECT_TYPE_PARAMETRIC, 0, T("Parametric")},
{2, EQData::EQ_EFFECT_TYPE_FILTER_LOW_PASS,EQData::EQ_FILTER_TYPE_FILTER_BUTTERWORTH_12DB, T("Low Pass")}
};
if you add a simple constructor:
struct CoolStruct
{
CoolStruct(int id, uint32 type, uint32 subtype, String name) : id(id), type(type), subtype(subtype), name(name) {}
int id;
uint32 type;
uint32 subtype;
String name;
};
you can then do this:
CoolVector.push_back(CoolStruct(1, EQData::EQ_EFFECT_TYPE_PARAMETRIC, 0, T("Parametric")));
CoolVector.push_back(CoolStruct(2, EQData::EQ_EFFECT_TYPE_FILTER_LOW_PASS,EQData::EQ_FILTER_TYPE_FILTER_BUTTERWORTH_12DB, T("Low Pass")));

How to put initialized structs in a struct?

I have a struct :
typedef struct
{
int nNum;
string str;
}KeyPair;
Then I initialize my struct into something like this:
KeyPair keys[] =
{
{0, "tester"},
{2, "yadah"},
{0, "tester"}
};
And yet, let's say a number of other initializations:
KeyPair keysA[] =
{
{0, "tester"},
{2, "yadah"},
{0, "tester"}
};
KeyPair keysB[] =
{
{0, "testeras"},
{2, "yadahsdf"},
{3, "testerasss"}
};
KeyPair OtherkeysA[] =
{
{1, "tester"},
{2, "yadah"},
{3, "tester"}
};
and like 20 more of 'em.
Now, how do I create another struct and initialize it such that it contains these initiazed KeyPairs?
The reason for this is because I will repetitively call a function whose parameters would come for these structs. And I DO NOT want to do it this way:
pressKeyPairs( keys, sizeof( keys) / sizeof( keys[0] ) );
pressKeyPairs( keysA, sizeof( keysA) / sizeof( keysA[0] ) );
pressKeyPairs( keysB, sizeof( keysB) / sizeof( keysB[0] ) );
pressKeyPairs( OtherkeysA, sizeof( OtherkeysA) / sizeof( OtherkeysA[0] ) );
and so on...
So I would like to just loop through a struct containing these inilialized instantiations of KeyPairs...
OR I would like to put these initialized instances of KeyPairs into a vector and just loop through the vector... How do I do that?
Assuming that you have a fixed number key pairs, you could use a structure member function:
typedef struct KeyPairs {
KeyPair keysA[3];
KeyPair keysB[3];
KeyPair otherKeysA[3];
void init() {
keysA[0].nNum = 0;
keysA[0].str = "tester";
keysA[1].nNum = 2;
keysA[1].str = "yadah";
keysA[2].nNum = 0;
keysA[2].str = "tester";
// and so on for other keys
}
} KeyPairs;
Then use it like so:
KeyPairs pairs;
pairs.init();
How about doing real C++ and using constructors ?
(note that typedefs are implicits for structs in C++)
struct KeyPair
{
int nNum;
string str;
public:
KeyPair() {}
KeyPair(int n, string s) : nNum(n), str(s) {}
};
And then use another struct :
struct TripleKeyPair
{
KeyPair keys[3];
TripleKeyPair()
{
// Your initialisation code goes here
}
};
And finally, I wouldn't advice using names such as :
KeysA, KeysB, KeysC ...
Arrays are exactly for this. Why note use std::vector ?
How about using "null" objects as delimiters in the array? You would have to use constructors though:
struct KeyPair
{
KeyPair() : fIsEmpty(true) {}
KeyPair(int nNum_, const char *szStr) : nNum(nNum_), str(szStr), fIsEmpty(false) {}
int nNum;
string str;
bool fIsEmpty;
};
Then you can initialize it like this:
KeyPair allKeys[] =
{
KeyPair(0, "testeras"),
KeyPair(2, "yadahsdf"),
KeyPair(3, "testerasss"),
KeyPair(),
KeyPair(0, "tester"),
KeyPair(2, "yadah"),
KeyPair(3, "tester"),
KeyPair(1, "moreyadah"),
KeyPair()
};
And the iteration is trivial if you implement a kind of strlen() analog for KeyPair object array.

State machine implementation

I have a state machine as described below.
We can start in one of two starting states, but we must hit all 4 states of the handshake. From there, we can either transfer a payload of data or receive a payload of data. Then, we return to our original starting state.
Handshake:
-> StartingState1 -> FinalState1 -> StartingState2 -> FinalState2
-> StartingState2 -> FinalState2 -> StartingState1 -> FinalState1
Payload Transfer:
-> SendPayload -> SendEnd -> StartingState?
-> ReceivePayload -> ReceiveEnd -> StartingState?
The code below represents my current architecture. Unfortunately, at the end of each process, I don't have enough information from within the states to know what the next state is I should hit.
Does anybody have any suggestions on how to improve this architecture based on my requirements?
Thanks,
PaulH
class MyMachine;
class Payload;
class IState
{
MyMachine* context_;
IState( MyMachine* context ) : context_( context) {};
virtual void Consume( byte data );
void ChangeState( IState* state )
{
context_->SetState( state );
}
}
class FinalState1 : IState
{
void Consume( byte data )
{
// Either go to StartingState1, SendPayload, or ReceivePayload.
// How can I tell from within the context of this state where I
// should go?
}
}
class StartingState1 : IState
{
void Consume( byte data )
{
if ( /*some condition*/ )
{
ChangeState( new FinalState1( context_ ) );
}
}
}
class MyMachine
{
IState* state_;
Payload* payload_;
void Start1( Mode mode )
{
state_ = new StartingState1( this );
}
void Start2( Mode mode )
{
state_ = new StartingState2( this );
}
void Consume( byte data )
{
state_->Consume( data );
}
void SetPayload( const Payload* payload )
{
payload_ = payload;
}
const Payload* GetPayload()
{
return payload_;
}
void SetState( State* state )
{
delete state_;
state_ = state;
}
}
// get a byte of data from some source
byte GetData();
void main()
{
MyMachine machine;
Payload payload;
machine.SetPayload( payload );
machine.Start1( Mode::SendPayload );
// could also call:
// machine.Start1( Mode::ReceivePayload );
// machine.Start2( Mode::SendPayload );
// machine.Start2( Mode::ReceivePayload );
for(;;)
{
machine.Consume( GetData() );
}
}
What you have doesn't represent the possible states of your system completely, but it's easy to transform it so that it does. You need additional states to represent the difference between being in state 1 and not having been in state 2, and being in state 1, whilst having been in state 2 (and the same for state 2). So you need:
S1 S2 F1 F2 S12 F12 S21 F21
SP SE
RP RE
with transitions
S1 --> F1
F1 --> S12
S12 --> F12
F12 --> SP or F12 --> RP
S2 --> F2
F2 --> S21
S21 --> F21
F21 --> SP or F21 --> RP
SP --> SE
RP --> RE
SE --> S1 or SE --> S2
RE --> S1 or RE --> S2
The key difference is the introduction of new states S12, F12, S21 and F21. In terms of implementation you could almost certainly just derive S12 from S2, F12 from F2, S21 from S1 and F21 from F2 and override the transition function to go to the correct state.
(Apologies for acronymising all your states).
Did you look at boost::statechart library?
I suggest designing from the point of view of function object or function pointers.
A simple state machine can be implemented using an array or std::map. Use the current state as an index and retrieve either the new state or a pointer to the state function.
More complex state machines travel from one state to another based on a transition or event. Simply implemented, this requires a 'nested' array. A container of transition containers. The first access gives you the transition table for a state. Use the current transition as an index into the transition table to return a function pointer of the function that handles this transition.
There different data structures that can be used, all depending on the complexity of your state machine.
A nice idea is to have a table driven state machine. This allows the engine to be coded and tested once. Changing the state machine involves changing the data in the table. The table may be able to exist outside of the executable, which means that the executable doesn't have to change. This concept can be expanded by using dynamic libraries, reducing the need to change the executable.
This is just my suggestion, I could be wrong (paraphrased from Dennis Miller).
Here is an example using the method suggested by Thomas:
#include <cassert>
#include <iostream>
#include <map>
class Machine;
typedef void (*StateFunctionPtr)(Machine& context);
// State "do" functions
void starting1(Machine& context) {std::cout << "S1 ";}
void final1(Machine& context) {std::cout << "F1 ";}
void starting2(Machine& context) {std::cout << "S2 ";}
void final2(Machine& context) {std::cout << "F2 ";}
void sendPayload(Machine& context) {std::cout << "SP ";}
void sendEnd(Machine& context) {std::cout << "SE ";}
void receivePayload(Machine& context) {std::cout << "RP ";}
void receiveEnd(Machine& context) {std::cout << "RE ";}
namespace State
{
enum Type {start, handshake1, handshake2, handshake3,
handshake4, xferPayload, endPayload};
};
// Aggregate of state, "mode" variables, and events.
struct StateKey
{
// Needed for use as map key
bool operator<(const StateKey& rhs) const
{
return
(state < rhs.state)
|| ( (state == rhs.state) && (isReceiving < rhs.isReceiving) )
|| ( (state == rhs.state) && (isReceiving == rhs.isReceiving)
&& (startsAt2 < rhs.startsAt2) );
}
bool startsAt2;
bool isReceiving;
State::Type state;
};
struct StateEffect
{
StateFunctionPtr function; // "do" function
State::Type newState; // state to transition to
};
struct StatePair
{
StateKey key;
StateEffect effect;
};
const StatePair stateTable[] =
{
{{0, 0, State::start}, {&starting1, State::handshake1}},
{{0, 0, State::handshake1}, {&final1, State::handshake2}},
{{0, 0, State::handshake2}, {&starting2, State::handshake3}},
{{0, 0, State::handshake3}, {&final2, State::handshake4}},
{{0, 0, State::handshake4}, {&sendPayload, State::xferPayload}},
{{0, 0, State::xferPayload}, {&sendEnd, State::endPayload}},
{{0, 0, State::endPayload}, {&starting1, State::handshake1}},
{{0, 1, State::start}, {&starting1, State::handshake1}},
{{0, 1, State::handshake1}, {&final1, State::handshake2}},
{{0, 1, State::handshake2}, {&starting2, State::handshake3}},
{{0, 1, State::handshake3}, {&final2, State::handshake4}},
{{0, 1, State::handshake4}, {&receivePayload, State::xferPayload}},
{{0, 1, State::xferPayload}, {&receiveEnd, State::endPayload}},
{{0, 1, State::endPayload}, {&starting1, State::handshake1}},
{{1, 0, State::start}, {&starting2, State::handshake1}},
{{1, 0, State::handshake1}, {&final2, State::handshake2}},
{{1, 0, State::handshake2}, {&starting1, State::handshake3}},
{{1, 0, State::handshake3}, {&final1, State::handshake4}},
{{1, 0, State::handshake4}, {&sendPayload, State::xferPayload}},
{{1, 0, State::xferPayload}, {&sendEnd, State::endPayload}},
{{1, 0, State::endPayload}, {&starting2, State::handshake1}},
{{1, 1, State::start}, {&starting2, State::handshake1}},
{{1, 1, State::handshake1}, {&final2, State::handshake2}},
{{1, 1, State::handshake2}, {&starting1, State::handshake3}},
{{1, 1, State::handshake3}, {&final1, State::handshake4}},
{{1, 1, State::handshake4}, {&receivePayload, State::xferPayload}},
{{1, 1, State::xferPayload}, {&receiveEnd, State::endPayload}},
{{1, 1, State::endPayload}, {&starting2, State::handshake1}}
};
class Machine
{
public:
Machine()
{
// Initialize state chart map from constant state table
const size_t tableSize = sizeof(stateTable) / sizeof(stateTable[0]);
for (size_t row=0; row<tableSize; ++row)
{
stateChart_[stateTable[row].key] = stateTable[row].effect;
}
}
// If startsAt2==true, then FSM will start with starting2 handshake function
void reset(bool startsAt2, bool isReceiving)
{
stateKey_.startsAt2 = startsAt2;
stateKey_.isReceiving = isReceiving;
stateKey_.state = State::start;
}
void step()
{
StateChart::const_iterator iter = stateChart_.find(stateKey_);
assert(iter != stateChart_.end());
const StateEffect& effect = iter->second;
effect.function(*this);
stateKey_.state = effect.newState;
}
private:
typedef std::map<StateKey, StateEffect> StateChart;
StateChart stateChart_;
StateKey stateKey_;
};
int main()
{
Machine machine;
machine.reset(true, false);
for (int i=0; i<20; ++i)
{
machine.step();
}
}
It compiles and works on my machine. You might want to add the following features:
Entry/exit functions in StateEffect
Event "triggers" in StateKey
Generalize into a template.
Add enough generic features to it, and it will start resembling a Boost.StateChart wannabe. ;-)
You can model your state machine using a Petri net. This allows you to define both very simple and very complex state machines.
To implement the state machine/ petri net you specified you can use an engine like the PTN Engine.
It allows you to declaratively define the whole state machine in the Petri net constructor. You can integrate your own functions to be called when reaching a given state, as well as functions to trigger state changes.