Affrontando il problema if web è possibile creare un motore di regole in cui ogni specifica regola è codificata in modo indipendente. Un ulteriore affinamento per questo sarebbe quello di creare un linguaggio specifico di dominio (DSL) per creare le regole, tuttavia un solo DSL sposta solo il problema da una base di codice (principale) a un'altra (DSL). Senza struttura, il DSL non andrà meglio della lingua madre (Java, C # ecc.), Quindi torneremo ad esso dopo aver trovato un approccio strutturale migliorato.
Il problema fondamentale è che stai riscontrando un problema di modellizzazione. Ogni volta che incontri situazioni combinatorie come questa è un chiaro segno che l'astrazione del tuo modello che descrive la situazione è troppo approssimativa. Molto probabilmente stai combinando elementi che dovrebbero appartenere a diversi modelli in una singola entità.
Se continui a scomporre il tuo modello alla fine dissolverai completamente questo effetto combinatorio. Tuttavia, quando prendi questa strada, è facile perdersi nel tuo design creando un pasticcio ancora più grande, il perfezionismo qui non è necessariamente tuo amico.
Le macchine a stati finiti e i motori delle regole sono solo un esempio di come questo problema può essere scomposto e reso più gestibile. L'idea principale qui è che un buon modo per sbarazzarsi di un problema combinatorio come questo è spesso quello di creare un disegno e ripeterlo fino alla nausea in livelli annidati di astrazione fino a quando il sistema non si esibisce in modo soddisfacente. Simile a come i frattali vengono utilizzati per creare modelli intricati. Le regole rimangono invariate, indipendentemente dal fatto che si guardi il proprio sistema al microscopio o dall'alto.
Esempio di applicazione di questo al tuo dominio.
Stai cercando di modellare il modo in cui le mucche si muovono attraverso un terreno. Sebbene la tua domanda manchi di dettagli, immagino che la tua grande quantità di if includa frammenti di decisione come if cow.isStanding then cow.canRun = true
ma ti impantanerai mentre aggiungi dettagli del terreno, ad esempio. Quindi, per ogni azione che si desidera intraprendere, è necessario verificare tutti gli aspetti a cui è possibile pensare e ripetere queste verifiche per la successiva azione possibile.
Innanzitutto abbiamo bisogno del nostro design ripetibile, che in questo caso sarà un FSM per modellare gli stati mutevoli della simulazione. Quindi la prima cosa che vorrei fare è implementare un FSM di riferimento, definendo un'interfaccia di stato , un'interfaccia di transizione e forse un contesto di transizioneche può contenere informazioni condivise da rendere disponibili alle altre due. Un'implementazione di base di FSM passerà da una transizione all'altra indipendentemente dal contesto, è qui che entra in gioco un motore di regole. Il motore di regole incapsula in modo chiaro le condizioni che devono essere soddisfatte se deve avvenire la transizione. Un motore di regole qui può essere semplice come un elenco di regole ognuna con una funzione di valutazione che restituisce un valore booleano. Per verificare se deve avvenire una transizione, ripetiamo l'elenco delle regole e se una di esse viene valutata come falsa, la transizione non ha luogo. La transizione stessa conterrà il codice comportamentale per modificare lo stato corrente dell'FSM (e altre possibili attività).
Ora, se comincio a implementare la simulazione come un singolo grande FSM a livello di DIO, finirò con MOLTISSIMI possibili stati, transizioni ecc. ora una regola che esegue un test su una specifica informazione del contesto (che a questo punto contiene praticamente tutto) e ogni corpo IF si trova da qualche parte nel codice di transizione.
Inserisci la ripartizione dei frattali: il primo passo sarebbe quello di creare un FSM per ogni mucca in cui gli stati sono gli stati interni della mucca (in piedi, correre, camminare, pascolare ecc.) E le transizioni tra di loro sarebbero influenzate dall'ambiente. È possibile che il grafico non sia completo, ad esempio il pascolo è accessibile solo dallo stato permanente, qualsiasi altra transizione viene dissalata perché semplicemente assente dal modello. Qui separa efficacemente i dati in due diversi modelli, la mucca e il terreno. Ognuno con le proprie proprietà impostate. Questa suddivisione ti consentirà di semplificare la progettazione generale del motore. Ora invece di avere un singolo motore di regole che decide tutto quello che hai più, più semplici motori di regole (uno per ogni transizione) che decidono su dettagli molto specifici.
Poiché sto riutilizzando lo stesso codice per FSM, questa è fondamentalmente una configurazione di FSM. Ricordi quando abbiamo menzionato DSL prima? È qui che la DSL può fare molto bene se hai molte regole e transizioni da scrivere.
Andando più in profondità
Ora DIO non ha più a che fare con tutta la complessità nella gestione degli stati interni della mucca, ma possiamo spingerci oltre. C'è ancora molta complessità nella gestione del terreno, ad esempio. Qui è dove decidi dove è sufficiente la ripartizione. Se ad esempio nel tuo DIO finisci per gestire la dinamica del terreno (erba lunga, fango, fango secco, erba corta ecc.) Possiamo ripetere lo stesso schema. Non c'è nulla che ti impedisca di incorporare tale logica nel terreno stesso estraendo tutti gli stati del terreno (erba lunga, erba corta, fangoso, secco, ecc.) In un nuovo terreno FSM con transizioni tra gli stati e forse semplici regole. Ad esempio, per arrivare allo stato fangoso il motore delle regole dovrebbe controllare il contesto per trovare liquidi, altrimenti non è possibile. Ora DIO è diventato ancora più semplice.
È possibile completare il sistema di FSM rendendoli autonomi e dando a ciascuno di essi un thread. Quest'ultimo passaggio non è necessario ma ti consente di modificare dinamicamente l'interazione del sistema regolando il modo in cui deleghi il tuo processo decisionale (avvio di un FSM specializzato o solo ritorno di uno stato predeterminato).
Ricordi come abbiamo detto che le transizioni potrebbero anche svolgere "altri possibili compiti"? Esploriamo ciò aggiungendo la possibilità per diversi modelli (FSM) di comunicare tra loro. È possibile definire un set di eventi e consentire a ciascun FSM di registrare il listener per questi eventi. Pertanto, se, ad esempio, una mucca entra in un esagono di terreno, l'esagono può registrare gli ascoltatori per i cambiamenti di transizione. Qui diventa un po 'complicato perché ogni FSM è implementato a un livello molto alto senza alcuna conoscenza del dominio specifico che ospita. Tuttavia è possibile ottenere ciò facendo in modo che la mucca pubblichi un elenco di eventi e la cella può registrarsi se vede eventi a cui può reagire. Una buona gerarchia della famiglia di eventi qui è un buon investimento.
Puoi approfondire ancora modellando i livelli di nutrienti e il ciclo di crescita dell'erba, con ... hai indovinato ... un FSM di erba incorporato nel modello del cerotto del terreno.
Se spingi l'idea abbastanza lontano, DIO ha ben poco da fare in quanto tutti gli aspetti sono praticamente autogestiti, liberando tempo da dedicare a cose più divine.
Ricapitolare
Come indicato sopra, la FSM qui non è la soluzione, ma solo un mezzo per illustrare che la soluzione a tale problema non si trova nel codice per dire, ma come si modella il problema. Ci sono probabilmente altre soluzioni possibili e molto probabilmente molto meglio della mia proposta di FSM. Tuttavia, l'approccio dei "frattali" rimane un buon modo per gestire questa difficoltà. Se eseguito correttamente, è possibile allocare dinamicamente livelli più profondi laddove conta, fornendo modelli più semplici laddove conta di meno. È possibile mettere in coda le modifiche e applicarle quando le risorse diventano più disponibili. In una sequenza d'azione potrebbe non essere così importante calcolare il trasferimento di nutrienti dalla mucca all'erba. Puoi comunque registrare queste transizioni e applicare le modifiche in un secondo momento o semplicemente approssimare con un'ipotesi istruita semplicemente sostituendo i motori delle regole o forse sostituendo l'implementazione di FSM del tutto con una versione ingenua più semplice per gli elementi che non sono nel campo diretto di interesse (quella vacca all'altra estremità del campo) per consentire interazioni più dettagliate per ottenere il focus e una maggiore quota di risorse. Tutto ciò senza mai rivisitare il sistema nel suo insieme; poiché ogni parte è ben isolata diventa più facile creare un rimpiazzo di sostituzione che limiti o estenda la profondità del modello. Usando un design standard puoi basarti su questo e massimizzare gli investimenti fatti in strumenti ad-hoc come un DSL per definire regole o un vocabolario standard per eventi, iniziando di nuovo a un livello molto alto e aggiungendo i perfezionamenti necessari. poiché ogni parte è ben isolata diventa più facile creare un rimpiazzo di sostituzione che limiti o estenda la profondità del modello. Usando un design standard puoi basarti su questo e massimizzare gli investimenti fatti in strumenti ad-hoc come un DSL per definire regole o un vocabolario standard per eventi, iniziando di nuovo a un livello molto alto e aggiungendo i perfezionamenti necessari. poiché ogni parte è ben isolata diventa più facile creare un rimpiazzo di sostituzione che limiti o estenda la profondità del modello. Usando un design standard puoi basarti su questo e massimizzare gli investimenti fatti in strumenti ad-hoc come un DSL per definire regole o un vocabolario standard per eventi, iniziando di nuovo a un livello molto alto e aggiungendo i perfezionamenti necessari.
Vorrei fornire un esempio di codice, ma questo è tutto ciò che posso permettermi di fare in questo momento.