Welcome to the Ninja Monkey Function Blocks repository! This is the companion source code for the fun and insightful Ninja Monkey video series on OOP in PLC programming using TwinCAT 3 4026. π₯
π₯ watch part 1 π₯ watch part 2
In this video, youβll learn how to build a candy vending machine π with candies π¬ and consumers π§ββοΈπ§ββοΈ. Along the way, the code and video will guide you through key concepts of Object-Oriented Programming (OOP) in PLCs:
- ποΈ Classes, Methods, and Properties
- π Access Specifiers (who can access what!)
- π Inheritance (like family traits for code)
- π Abstraction (simplifying complexity)
- π οΈ Dynamic Object Creation (objects on the fly!)
- π€ Techniques like delegation and dependency injection.
β οΈ Note: This video does not coverInterfacesβthatβs reserved for another tutorial. Stay tuned!
Whether you're just starting out with PLC programming or looking to expand your OOP skills, this project is designed to teach and inspire. With clear explanations and practical examples, youβll be able to take your PLC programming to the next level. So grab some candy π¬, dive in, and letβs build something sweet!
classDiagram
direction LR
AbstractCandy --|> AnyToTArgConverter
LoggerProvider --* AbstractCandy
AbstractCandyConsumer <-- AbstractCandy :use
CandyDispatcherFactory --* AbstractCandy
AbstractCandyDispatcher --o AbstractCandy
AbstractCandyDispatcher <|-- AnyConcreteDispatcher
AnyConcreteDispatcher <-- CandyDispatcherFactory :creates
AnyToTArgConverter <|-- AbstractLogger
AbstractLogger <|-- AdsLogger
AdsLogger --* LoggerProvider
IndividualCandyPreferences <|-- AjninsCandyPreferences
AbstractCandyConsumer <|-- NinjaMonkey
NinjaMonkey o-- IndividualCandyPreferences
NinjaMonkey *-- AbstractNinjaCandyConsumeBehavior
AbstractCandy <|-- AnyConcreteCandy
GreedyCandyMachine --|> AbstractCandyMachine
AnyConcreteCandy <-- GreedyCandyMachine :creates
PickyCandyMachine --|> AbstractCandyMachine
AnyConcreteCandy <-- PickyCandyMachine :creates
CommonCandyMachine --|> AbstractCandyMachine
AnyConcreteCandy <-- CommonCandyMachine :creates
CandyMachineProvider *-- AbstractCandyMachine
AbstractNinjaCandyConsumeBehavior *-- CandyMachineProvider
AbstractNinjaCandyConsumeBehavior <|-- GreedyNinjaCandyBehavior
AbstractNinjaCandyConsumeBehavior <|-- PickyNinjaCandyBehavior
AbstractNinjaCandyConsumeBehavior <|-- CommonNinjaCandyBehavior
classDiagram
class CandyTypes{
<<Enumeration>>
+ CANDY_BARS = 1
+ CANDY_CORN = 2
+ CANDY_STICKS = 4
+ CARAMEL_CANDY = 8
+ CHOCOLATE_CANDY = 16
+ GUMMY_CANDY = 32
+ MINTS = 64
+ LOLLIPOPS = 128
+ JAWBREAKERS = 256
+ HARD_CANDY = 512
+ LICORICE_CANDY = 1024
+ SOUR_CANDY = 2048
}
class ConsumerTypes{
<<Enumeration>>
+ COMMON = 0
+ GREEDY
+ PICKY
}
class TasteScale{
<<Enumeration>>
+ DISGUSTING = -1
+ OKAY = 0
+ DELICIOUS = 1
}
classDiagram
class CandyTypes{
<<Alias>>
UINT 0..500
}
class CandyName{
<<Alias>>
Tc2_System.T_MaxString
}
class ConsumerName{
<<Alias>>
Tc2_System.T_MaxString
}
class SatisfactionLevelInPercent{
<<Alias>>
UINT 0..100
}
classDiagram
class AnyToTArgConverter{
<<Abstract>>
# anyToArg(in :ANY) Tc2_Utilities.T_Arg
- anyTypeToArg(in :__SYSTEM.AnyType) Tc2_Utilities.T_Arg
- dereferenceAnys(in :__SYSTEM.AnyType,[out] out :__SYSTEM.AnyType)
- typeClassToArgType(typeClass :__SYSTEM.TYPE_CLASS, size :UDINT) Tc2_Utilities.E_ArgType
}
classDiagram
AnyToTArgConverter <|-- AbstractLogger
class AbstractLogger{
<<Abstract>>
+ critical(message :Tc2_System.T_MaxString)*
+ debug(message :Tc2_System.T_MaxString)*
+ error(message :Tc2_System.T_MaxString)*
+ info(message :Tc2_System.T_MaxString)*
+ run()*
+ warning(message :Tc2_System.T_MaxString)*
}
class AnyToTArgConverter{
<<Abstract>>
}
classDiagram
AbstractLogger <|-- AdsLogger :is a
AdsLogger *-- AdsLogMessage :has
class AdsLogger{
- ~~get~~ isBufferEmpty :BOOL
- ~~get~~ isBufferFull :BOOL
+ critical(message :Tc2_System.T_MaxString)
+ debug(message :Tc2_System.T_MaxString)
+ error(message :Tc2_System.T_MaxString)
+ info(message :Tc2_System.T_MaxString)
+ run()
+ warning(message :Tc2_System.T_MaxString)
+ FB_exit(bInCopyCode :BOOL) BOOL
- appendNewMessage(messageType :DWORD, [ref] message :Tc2_System.T_MaxString)
- incrementBufferPointer(bufferPointer :DWORD) DWORD
- printMessage(msg :POINTER TO AdsLogMessage)
- resize()
}
class AdsLogMessage{
+ ~~get~~ message :Tc2_System.T_MaxString
+ ~~get~~ messageType :DWORD
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL, msg :Tc2_System.T_MaxString, msgType :DWORD) BOOL
}
class AbstractLogger{
<<Abstract>>
}
classDiagram
LoggerProvider *-- AdsLogger :has a
LoggerProvider --> AbstractLogger :use
class LoggerProvider{
+ ~~get~~ logger :REFERENCE TO AbstractLogger
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL, otherLogger :REFERENCE TO AbstractLogger) BOOL
}
class AdsLogger
class AbstractLogger{
<<Abstract>>
}
classDiagram
IndividualCandyPreferences --> CandyTypes :use
IndividualCandyPreferences --> TasteScale :use
class IndividualCandyPreferences{
<<Abstract>>
+ howIsTheCandy(candyType :CandyTypes) TasteScale*
}
class CandyTypes{
<<Enumeration>>
}
class TasteScale{
<<Enumeration>>
}
classDiagram
IndividualCandyPreferences <|-- AjninsCandyPreferences :is a
AjninsCandyPreferences --> CandyTypes :use
AjninsCandyPreferences --> TasteScale :use
class AjninsCandyPreferences{
<<Final>>
+ howIsTheCandy(candyType :CandyTypes) TasteScale
}
class IndividualCandyPreferences{
<<Abstract>>
}
class CandyTypes{
<<Enumeration>>
}
class TasteScale{
<<Enumeration>>
}
classDiagram
AbstractCandyConsumer *-- SatisfactionLevelInPercent :has a
ConsumerName <-- AbstractCandyConsumer :use
AbstractCandyConsumer --> CandyAmountInGram :use
class AbstractCandyConsumer{
<<Abstract>>
+ ~~get~~ levelOfSatisfaction :SatisfactionLevelInPercent
+ ~~get~~ name :ConsumerName*
+ eatCandyBars(amount :CandyAmountInGram)*
+ eatCandyCorn(amount :CandyAmountInGram)*
+ eatCandySticks(amount :CandyAmountInGram)*
+ eatCaramelCandy(amount :CandyAmountInGram)*
+ eatChoclateCandy(amount :CandyAmountInGram)*
+ eatGummyCandy(amount :CandyAmountInGram)*
+ eatJawbreakers(amount :CandyAmountInGram)*
+ eatLicoriceCandy(amount :CandyAmountInGram)*
+ eatLollipops(amount :CandyAmountInGram)*
+ eatMints(amount :CandyAmountInGram)*
+ eatSourCandy(amount :CandyAmountInGram)*
}
class CandyAmountInGram{
<<Alias>>
}
class ConsumerName{
<<Alias>>
}
class SatisfactionLevelInPercent{
<<Alias>>
}
classDiagram
AbstractCandyConsumer <|-- NinjaMonkey :is a
CommonNinjaCandyBehavior --* NinjaMonkey :has a
ConsumerName --* NinjaMonkey :has a
NinjaMonkey --> TasteScale :use
NinjaMonkey --> CandyAmountInGram :use
NinjaMonkey --> IndividualCandyPreferences :use
NinjaMonkey --> AbstractNinjaCandyConsumeBehavior :use
class NinjaMonkey{
<<Final>>
+ name :ConsumerName
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL, ninjaCandyBehavior :REFERENCE TO AbstractNinjaCandyConsumeBehavior, candyPreferences :REFERENCE TO IndividualCandyPreferences) BOOL
+ lootCandies(execute :BOOL)
+ eatCandyBars(amount :CandyAmountInGram)
+ eatCandyCorn(amount :CandyAmountInGram)
+ eatCandySticks(amount :CandyAmountInGram)
+ eatCaramelCandy(amount :CandyAmountInGram)
+ eatChoclateCandy(amount :CandyAmountInGram)
+ eatGummyCandy(amount :CandyAmountInGram)
+ eatHardCandy(amount :CandyAmountInGram)
+ eatJawbreakers(amount :CandyAmountInGram)
+ eatLicoriceCandy(amount :CandyAmountInGram)
+ eatLollipops(amount :CandyAmountInGram)
+ eatMints(amount :CandyAmountInGram)
+ eatSourCandy(amount :CandyAmountInGram)
- removeNodesFromPath([ref] path :Tc2_System.T_MaxString, numberOfNodesToRemove :UINT, delimiter :STRING(1))
- getTastiness(candyType :CandyTypes) TasteScale
}
class AbstractCandyConsumer{
<<Abstract>>
}
class AbstractNinjaCandyConsumeBehavior{
<<Abstract>>
}
class IndividualCandyPreferences{
<<Abstract>>
}
class CommonNinjaCandyBehavior
class ConsumerName{
<<Alias>>
}
class TasteScale{
<<Enumeration>>
}
class CandyAmountInGram{
<<Alias>>
}
classDiagram
AbstractCandyDispatcher --> CandyAmountInGram :use
AbstractCandyDispatcher --> AbstractCandyConsumer :use
class AbstractCandyDispatcher{
<<Abstract>>
+ destruct() POINTER TO AbstractCandyDispatcher
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)*
# eatCandiesIsImpossible(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram) BOOL
}
class CandyAmountInGram{
<<Alias>>
}
class AbstractCandyConsumer{
<<Alias>>
}
classDiagram
direction LR
AbstractCandyDispatcher <|-- CandyBarDispatcher :is a
AbstractCandyDispatcher <|-- CandyCornDispatcher :is a
AbstractCandyDispatcher <|-- CandySticksDispatcher :is a
AbstractCandyDispatcher <|-- CaramelCandyDispatcher :is a
AbstractCandyDispatcher <|-- ChoclateCandyDispatcher :is a
AbstractCandyDispatcher <|-- GummyCandyDispatcher :is a
HardCandyDispatcher --|> AbstractCandyDispatcher :is a
JawbreakersDispatcher --|> AbstractCandyDispatcher :is a
LicoriceCandyDispatcher --|> AbstractCandyDispatcher :is a
LollipopsDispatcher --|> AbstractCandyDispatcher :is a
MintsDispatcher --|> AbstractCandyDispatcher :is a
SourCandyDispatcher --|> AbstractCandyDispatcher :is a
class AbstractCandyDispatcher{
<<Abstract>>
}
class CandyBarDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class CandyCornDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class CandySticksDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class CaramelCandyDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class ChoclateCandyDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class GummyCandyDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class HardCandyDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class JawbreakersDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class LicoriceCandyDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class LollipopsDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class MintsDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
class SourCandyDispatcher{
<<Final>>
+ eat(consumer :REFERENCE TO AbstractCandyConsumer, amount :CandyAmountInGram)
}
classDiagram
direction LR
CandyDispatcherFactory --> CandyBarDispatcher :creates
CandyDispatcherFactory --> CandyCornDispatcher :creates
CandyDispatcherFactory --> CandySticksDispatcher :creates
CandyDispatcherFactory --> CaramelCandyDispatcher :creates
CandyDispatcherFactory --> ChoclateCandyDispatcher :creates
CandyDispatcherFactory <-- GummyCandyDispatcher :creates
CandyDispatcherFactory --> AbstractCandyDispatcher :use
HardCandyDispatcher <-- CandyDispatcherFactory :creates
JawbreakersDispatcher <-- CandyDispatcherFactory :creates
LicoriceCandyDispatcher <-- CandyDispatcherFactory :creates
LollipopsDispatcher <-- CandyDispatcherFactory :creates
MintsDispatcher <-- CandyDispatcherFactory :creates
SourCandyDispatcher <-- CandyDispatcherFactory :creates
CandyTypes <-- CandyDispatcherFactory :use
class CandyDispatcherFactory{
<<Final>>
+ getNewCandyDispatcherFor(candyType :CandyTypes) POINTER TO AbstractCandyDispatcher
}
class AbstractCandyDispatcher{
<<Abstract>>
}
class CandyTypes{
<<Enumeration>>
}
class CandyBarDispatcher
class CandyCornDispatcher
class CandySticksDispatcher
class CaramelCandyDispatcher
class ChoclateCandyDispatcher
class GummyCandyDispatcher
class HardCandyDispatcher
class JawbreakersDispatcher
class LicoriceCandyDispatcher
class LollipopsDispatcher
class MintsDispatcher
class SourCandyDispatcher
classDiagram
AbstractCandy *-- CandyAmountInGram :has a
AbstractCandy *-- CandyName :has a
AbstractCandy *-- CandyTypes :has a
AbstractCandy *-- FB_FormatString :has a
AbstractCandy --> ConsumerName
AbstractCandy --> SatisfactionLevelInPercent
AnyToTArgConverter <|-- AbstractCandy
AbstractCandyConsumer <-- AbstractCandy :use
AbstractCandyDispatcher --o AbstractCandy :has
CandyDispatcherFactory --* AbstractCandy :has a
LoggerProvider --* AbstractCandy :has
class AbstractCandy{
<<Abstract>>
+ ~~get~~ amountOfCandy :CandyAmountInGram
+ ~~get~~ nameOfCandy :CandyName
+ ~~get~~ typeOfCandy :CandyTypes
+ initializeDispatchers()
+ FB_exit(bInCopyCode :BOOL) BOOL
+ eatMe(consumer :REFERENCE TO AbstractCandyConsumer) POINTER TO AbstractCandy
# destruct() POINTER TO AbstractCandy
# addNewDispatcher(candyType :CandyTypes)
# logCandyConsume(consumer :REFERENCE TO AbstractCandyConsumer)
- freeDispatchers()
}
class AbstractCandyConsumer{
<<Abstract>>
}
class CandyAmountInGram{
<<Alias>>
}
class CandyName{
<<Alias>>
}
class CandyTypes{
<<Enumaertion>>
}
class AbstractCandyDispatcher{
<<Abstract>>
}
class CandyDispatcherFactory
class LoggerProvider
class FB_FormatString
class AnyToTArgConverter{
<<Abstract>>
}
class ConsumerName{
<<Alias>>
}
class SatisfactionLevelInPercent{
<<Alias>>
}
classDiagram
direction LR
AbstractCandy <|-- Alpenliebe :is a
AbstractCandy <|-- Altoids :is a
AbstractCandy <|-- BobsSweetStripes :is a
AbstractCandy <|-- BrachsCandyCorn :is a
AbstractCandy <|-- ChupaChups :is a
AbstractCandy <|-- SkittlesSour :is a
SnickersBar --|> AbstractCandy :is a
TwizzlersLicorizeTwists --|> AbstractCandy :is a
WonkaGobstopper --|> AbstractCandy :is a
MnMs --|> AbstractCandy :is a
Poppins --|> AbstractCandy :is a
Goldbears --|> AbstractCandy :is a
class AbstractCandy{
<<Abstract>>
}
class Alpenliebe{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class Altoids{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class BobsSweetStripes{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class BrachsCandyCorn{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class ChupaChups{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class SkittlesSour{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class SnickersBar{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class TwizzlersLicorizeTwists{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class WonkaGobstopper{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class MnMs{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class Poppins{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
class Goldbears{
<<Final>>
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL) BOOL
}
classDiagram
AbstractCandyMachine --> CandyTypes :use
AbstractCandyMachine --> AbstractCandy
class AbstractCandyMachine{
<<Abstract>>
+ getNewCandy(candyType :CandyTypes) REFERENCE TO AbstractCandy*
# isCandyTypeInvalidForMachine(candyType :CandyTypes)
}
class CandyTypes{
<<Enumeration>>
}
class AbstractCandy{
<<Abstract>>
}
classDiagram
direction LR
CommonCandyMachine --|> AbstractCandyMachine :is a
CommonCandyMachine --> CandyTypes :use
CommonCandyMachine --> AbstractCandy :use
SnickersBar <-- CommonCandyMachine :creates
BrachsCandyCorn <-- CommonCandyMachine :creates
BobsSweetStripes <-- CommonCandyMachine :creates
ChupaChups <-- CommonCandyMachine :creates
WonkaGobstopper <-- CommonCandyMachine :creates
TwizzlersLicorizeTwists <-- CommonCandyMachine :creates
Altoids <-- CommonCandyMachine :creates
SkittlesSour <-- CommonCandyMachine :creates
class CommonCandyMachine{
<<Final>>
getNewCandy(candyType :CandyTypes) REFERENCE TO AbstractCandy
}
class AbstractCandyMachine{
<<Abstract>>
}
class CandyTypes{
<<Enumeration>>
}
class AbstractCandy{
<<Abstract>>
}
class SnickersBar
class BrachsCandyCorn
class BobsSweetStripes
class ChupaChups
class WonkaGobstopper
class TwizzlersLicorizeTwists
class Altoids
class SkittlesSour
classDiagram
direction LR
GreedyCandyMachine --|> AbstractCandyMachine :is a
GreedyCandyMachine --> CandyTypes :use
GreedyCandyMachine --> AbstractCandy :use
SnickersBar <-- GreedyCandyMachine :creates
BrachsCandyCorn <-- GreedyCandyMachine :creates
BobsSweetStripes <-- GreedyCandyMachine :creates
ChupaChups <-- GreedyCandyMachine :creates
WonkaGobstopper <-- GreedyCandyMachine :creates
Alpenliebe <-- GreedyCandyMachine :creates
TwizzlersLicorizeTwists <-- GreedyCandyMachine :creates
Altoids <-- GreedyCandyMachine :creates
SkittlesSour <-- GreedyCandyMachine :creates
class GreedyCandyMachine{
<<Final>>
getNewCandy(candyType :CandyTypes) REFERENCE TO AbstractCandy
}
class AbstractCandyMachine{
<<Abstract>>
}
class CandyTypes{
<<Enumeration>>
}
class AbstractCandy{
<<Abstract>>
}
class SnickersBar
class BrachsCandyCorn
class BobsSweetStripes
class Alpenliebe
class ChupaChups
class WonkaGobstopper
class TwizzlersLicorizeTwists
class Altoids
class SkittlesSour
classDiagram
direction LR
PickyCandyMachine --|> AbstractCandyMachine :is a
PickyCandyMachine --> CandyTypes :use
PickyCandyMachine --> AbstractCandy :use
SnickersBar <-- PickyCandyMachine :creates
BrachsCandyCorn <-- PickyCandyMachine :creates
BobsSweetStripes <-- PickyCandyMachine :creates
Alpenliebe <-- PickyCandyMachine :creates
MnMs <-- PickyCandyMachine :creates
Goldbears <-- PickyCandyMachine :creates
Poppins <-- PickyCandyMachine :creates
ChupaChups <-- PickyCandyMachine :creates
WonkaGobstopper <-- PickyCandyMachine :creates
TwizzlersLicorizeTwists <-- PickyCandyMachine :creates
Altoids <-- PickyCandyMachine :creates
SkittlesSour <-- PickyCandyMachine :creates
class PickyCandyMachine{
<<Final>>
getNewCandy(candyType :CandyTypes) REFERENCE TO AbstractCandy
}
class AbstractCandyMachine{
<<Abstract>>
}
class CandyTypes{
<<Enumeration>>
}
class AbstractCandy{
<<Abstract>>
}
class SnickersBar
class BrachsCandyCorn
class BobsSweetStripes
class Alpenliebe
class MnMs
class Goldbears
class Poppins
class ChupaChups
class WonkaGobstopper
class TwizzlersLicorizeTwists
class Altoids
class SkittlesSour
classDiagram
CandyMachineProvider <|-- NinjaIncCandyMachineProvider :is a
NinjaIncCandyMachineProvider --> ConsumerTypes :use
NinjaIncCandyMachineProvider *-- GreedyCandyMachine :has a
NinjaIncCandyMachineProvider *-- PickyCandyMachine :has a
CommonCandyMachine --* CandyMachineProvider :has a
AbstractCandyMachine <-- CandyMachineProvider :use
class CandyMachineProvider{
<<Abstract>>
+ getACandyMachine() REFERENCE TO AbstractCandyMachine
}
class AbstractCandyMachine{
<<Abstract>>
}
class CommonCandyMachine
class GreedyCandyMachine
class PickyCandyMachine
class ConsumerTypes{
<<Enumeration>>
}
class NinjaIncCandyMachineProvider{
+ FB_Init(bInitRetains :BOOL, bInCopyCode :BOOL, consumerType :ConsumerTypes)
}
classDiagram
CandyMachineProvider <-- AbstractNinjaCandyConsumeBehavior :use
SatisfactionLevelInPercent <-- AbstractNinjaCandyConsumeBehavior :use
IndividualCandyPreferences <-- AbstractNinjaCandyConsumeBehavior :use
AbstractCandyConsumer <-- AbstractNinjaCandyConsumeBehavior :use
AbstractNinjaCandyConsumeBehavior --> TasteScale :use
AbstractNinjaCandyConsumeBehavior *-- NinjaIncCandyMachineProvider :has a
AbstractNinjaCandyConsumeBehavior --> CandyAmountInGram :use
AbstractNinjaCandyConsumeBehavior --> ConsumerTypes :use
class AbstractNinjaCandyConsumeBehavior{
<<Abstract>>
+ calculateSatisfactionLevel(tastiness :TasteScale, amountOfCandy :CandyAmountInGram, [ref] actualSatisfactionLevel :SatisfactionLevelInPercent)*
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL, candyMachineProvider :REFERENCE TO CandyMachineProvider) BOOL
+ lootACandyMachine(consumer :REFERENCE TO AbstractCandyConsumer, preferences :REFERENCE TO IndividualCandyPreferences REF= 0)
}
class NinjaIncCandyMachineProvider
class CandyMachineProvider{
<<Abstract>>
}
class IndividualCandyPreferences{
<<Abstract>>
}
class TasteScale{
<<Enumeration>>
}
class CandyAmountInGram{
<<Alias>>
}
class SatisfactionLevelInPercent{
<<Alias>>
}
class AbstractCandyConsumer{
<<Abstract>>
}
class ConsumerTypes{
<<Enumeration>>
}
classDiagram
AbstractNinjaCandyConsumeBehavior <|-- CommonNinjaCandyBehavior :is a
CommonNinjaCandyBehavior --> TasteScale :use
CommonNinjaCandyBehavior --> SatisfactionLevelInPercent :use
CommonNinjaCandyBehavior --> CandyAmountInGram :use
class CommonNinjaCandyBehavior{
+ calculateSatisfactionLevel(tastiness :TasteScale, amountOfCandy :CandyAmountInGram, [ref] actualSatisfactionLevel :SatisfactionLevelInPercent)
}
class AbstractNinjaCandyConsumeBehavior{
<<Abtsract>>
}
class TasteScale{
<<Enumeration>>
}
class CandyAmountInGram{
<<Alias>>
}
class SatisfactionLevelInPercent{
<<Alias>>
}
classDiagram
AbstractNinjaCandyConsumeBehavior <|-- GreedyNinjaCandyBehavior :is a
NinjaIncCandyMachineProvider --* GreedyNinjaCandyBehavior :has a
candyMachineProvider <-- GreedyNinjaCandyBehavior :use
GreedyNinjaCandyBehavior --> TasteScale :use
GreedyNinjaCandyBehavior --> SatisfactionLevelInPercent :use
GreedyNinjaCandyBehavior --> CandyAmountInGram :use
GreedyNinjaCandyBehavior --> ConsumerTypes :use
class GreedyNinjaCandyBehavior{
+ calculateSatisfactionLevel(tastiness :TasteScale, amountOfCandy :CandyAmountInGram, [ref] actualSatisfactionLevel :SatisfactionLevelInPercent)
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL, candyMachineProvider :REFERENCE TO CandyMachineProvider) BOOL
}
class AbstractNinjaCandyConsumeBehavior{
<<Abtsract>>
}
class TasteScale{
<<Enumeration>>
}
class ConsumerTypes{
<<Enumeration>>
}
class CandyAmountInGram{
<<Alias>>
}
class SatisfactionLevelInPercent{
<<Alias>>
}
class NinjaIncCandyMachineProvider
class candyMachineProvider{
<<Abstract>>
}
classDiagram
AbstractNinjaCandyConsumeBehavior <|-- PickyNinjaCandyBehavior :is a
NinjaIncCandyMachineProvider --* PickyNinjaCandyBehavior :has a
candyMachineProvider <-- PickyNinjaCandyBehavior :use
PickyNinjaCandyBehavior --> TasteScale :use
PickyNinjaCandyBehavior --> SatisfactionLevelInPercent :use
PickyNinjaCandyBehavior --> CandyAmountInGram :use
PickyNinjaCandyBehavior --> ConsumerTypes :use
class PickyNinjaCandyBehavior{
+ calculateSatisfactionLevel(tastiness :TasteScale, amountOfCandy :CandyAmountInGram, [ref] actualSatisfactionLevel :SatisfactionLevelInPercent)
+ FB_init(bInitRetains :BOOL, bInCopyCode :BOOL, candyMachineProvider :REFERENCE TO CandyMachineProvider) BOOL
}
class AbstractNinjaCandyConsumeBehavior{
<<Abtsract>>
}
class TasteScale{
<<Enumeration>>
}
class ConsumerTypes{
<<Enumeration>>
}
class CandyAmountInGram{
<<Alias>>
}
class SatisfactionLevelInPercent{
<<Alias>>
}
class NinjaIncCandyMachineProvider
class candyMachineProvider{
<<Abstract>>
}
- .\
- src\ root folder for all project sources
- .gitignore the git ignore file for this project
- .gitattributes the git attribute file for the LFS support
- README.md the file you read right now
initial commit
This major release marks the completion of the foundational features of the Candy Vending Machine Project. The following is a detailed summary of the changes and features implemented in this version:
-
Candy Machine Framework:
- Introduced
AbstractCandyMachineto establish a flexible foundation for different consumer behaviors. - Implemented concrete candy machines:
CommonCandyMachineas the default.PickyCandyMachinetailored for selective consumers like Ajnin.GreedyCandyMachinedesigned for voracious consumers like Yeknom.
- Added
AbstractCandyMachineProviderto manage shared and custom candy machines with efficient memory usage viaVAR_STAT.
- Introduced
-
Candy Consumer:
- Developed
AbstractCandyConsumerto define candy consumption behaviors. - Created the first concrete consumer,
NinjaMonkey:- Automatically sets its name using instance paths with
{attribute 'reflection'}. - Supports default and custom behaviors/preferences.
- Implements
lootCandiesandeatmethods.
- Automatically sets its name using instance paths with
- Developed
-
Candy Preferences:
- Introduced
IndividualCandyPreferencesfor custom preferences. - Implemented
AjninsCandyPreferencesfor picky monkeys; no preferences needed for greedy consumers like Yeknom.
- Introduced
-
Candies:
- Built
AbstractCandyas the foundation for all candies. - Added methods for logging, dispatcher management, and consumption handling.
- Introduced concrete candies (
Goldbears,MnMs,Poppins) for diverse use cases.
- Built
-
AbstractLogger:
- Defined an abstraction for flexible logging with methods for different log levels (
critical,error,warning,info,debug). - Includes a
runmethod for cyclic processing of buffered log messages.
- Defined an abstraction for flexible logging with methods for different log levels (
-
AdsLogger:
- Implemented a concrete logger with an internal ring buffer queue for message storage.
- Supports dynamic resizing and ensures messages are printed with a 30ms delay for orderly outputs.
- Integrated cleanup through the
FB_exitmethod.
-
LoggerProvider:
- Centralized logging management with
AdsLoggeras the default. - Ensures a single logger instance across the entire project.
- Centralized logging management with
-
Integration:
- Integrated the new logger into
AbstractCandy.logCandyConsumefor well-ordered log messages. - Set up the
LoggerProviderin the main task for consistent pre-use configuration.
- Integrated the new logger into
-
Behavior:
- Added
AbstractNinjaCandyConsumeBehaviorto define how monkeys loot candy machines and calculate satisfaction levels. - Implemented:
CommonNinjaCandyBehaviorwith satisfaction calculated using a logarithm (base 6.359).GreedyNinjaCandyBehaviorandPickyNinjaCandyBehaviorwith tailored satisfaction calculations and distinct default providers.
- Added
-
Utility Enhancements:
- Created
AdsLogMessageas a DTO for buffered ADS log messages with dynamic creation support.
- Created
-
Testing and Integration:
- Added monkey instances in the main task to verify functionality.
- Log messages are now well-ordered, providing consistent and clear feedback.
Version 1.0.0.0 provides a robust and scalable software for the Candy Vending Machine Project, supporting diverse consumer behaviors, flexible logging, and customizable candy handling. This release lays a solid foundation for future enhancements and showcases the power of abstraction and modular design.
π¬ Ninja Monkeys are ready to loot candy machines in style! πβ¨