in

dotNet Umbria [DNU]

Il primo User Group in Umbria sul mondo .Net

NHooligans

June 2008 - Posts

  • Progetto NHooligans – Parte 2 – Finalmente un po’ di codice…

    Resistere per ben due post all’inserimento di snippet di codice non è stato facile e proprio per questo motivo iniziamo oggi ad addentrarci in qualche esempio di utilizzo delle API del sistema che gestisce la nostra simulazione. Come giustamente mi è stato fatto notare da Paolo, l’uso del framework NSteer (oggetto dei post precedenti) potrebbe vincolare troppo la libertà in eventuali future evoluzioni del sistema di simulazione. Per questo motivo cercheremo di astrarre, laddove possibile, la concreta implementazione di NSteer degli “attori” che partecipano alla simulazione, mantenendo focalizzata la nostra attenzione soprattutto sulle interazioni tra gli oggetti descritti dalle interfacce introdotte nel post precedente. In altri termini, quando parleremo ad esempio di “Scena” faremo sì riferimento agli aspetti peculiari di un oggetto che implementa l’interfaccia IScene definita all’interno di NSteer, ma non necessariamente all’implementazione dell’unico tipo di scena definito in NSteer, ossia la GdiScene.

    Già da una prima frettolosa analisi del sistema che realizzeremo appare evidente che, in un verso o nell’altro, dovremo “allestire” un’istanza di simulazione per ciascuna "sfida” giocata all’interno di NHooligans. E’ facile immaginare inoltre che il servizio (in senso generico, non in senso Windows Service o WebService) che costituirà l’host per la simulazione in questione dovrà costruire le condizioni sulle quali la simulazione costruirà il proprio contesto di esecuzione, condizioni quali la definizione dei partecipanti (ossia degli agenti), le caratteristiche fisiche e cinematiche di ciascun oggetto definito all’interno del “mondo” della simulazione (massa, accelerazione e velocità massime, ecc.) nonché ovviamente ciò che dà effettivamente un senso alla simulazione: il “programma” seguito da ciascun agente per muoversi e, soprattutto, gonfiare di botte i propri simili della squadra avversaria.

    Quale che sia la conformazione del servizio “host”, la simulazione da allestire seguirà inevitabilmente i passi illustrati di seguito, che fanno riferimento ad una situazione facilmente riscontrabile anche nel demo di NSteer incluso nei sorgenti allegati nel primo post di questa sezione.

    La composizione degli oggetti che partecipano alla realizzazione di una simulazione è illustrata nel diagramma che segue, in cui l’host è rappresentato da un Form Winforms denominato con grande fantasia “Form1”:

    CD_NSteerDemo

    I primi servizi da inizializzare sono quelli che descrivono il “mondo” in cui la simulazione ha luogo e la “scena” che ne darà una rappresentazione (presumibilmente ma non necessariamente grafica), attraverso il retrieving di un riferimento alle interfacce IWorldService e IScene.

    Qualora, come avviene tipicamente, si voglia confinare il raggio di azione degli agenti ad una regione limitata, dovremo creare un’istanza di comportamento che, applicata a ciascun agente, ne limiterà il movimento. Il tipo di comportamento in questione è già definito in NSteer e prende il nome di RectangleWorldGlobalBehavior. Per inizializzarlo è sufficiente impostare punto di partenza (di tipo PointF, ossia punto 2D con coordinate di tipo float) e dimensione (di tipo SizeF) della sua proprietà “Region”, come illustrato nel seguente snippet:

    RectangleWorldGlobalBehavior region = new RectangleWorldGlobalBehavior(this.simulationComponents);
    region.Region = new RectangleF(new PointF(0, 0), worldService.World.WorldSize);

    Per inizializzare il servizio di gestione della “vicinanza"  tra agenti, rappresentato dall’interfaccia INeighborhoodService, è necessario creare un’istanza del contenitore utilizzato dal sistema per accedere alla informazioni posizionali relative degli agenti. All’interno di NSteer sono definiti due diversi contenitori (che implementano l’interfaccia INeighborhood, di cui il più performante è il BinLatticeNeihborhood, basato su una struttura bidimensionale di “Bins”, ossia di caselle caratterizzate da una coppia di indici che ne descrive la posizione all’interno del contenitore (in termini di riga e colonna) e da una lista di agenti presenti in quella casella. Un’inizializzazione tipica del BinLatticeNeihborhood è la seguente:

    INeighborhoodService neighborhood = this.simulationComponents.NeighborhoodService;
     
    BinLatticeNeihborhood bins = new BinLatticeNeihborhood(
        this.simulationComponents,
        16, 16,
        this.sceneControl.Width,
        this.sceneControl.Height);
     
    neighborhood.Neighborhood = bins;

    Gli ostacoli presenti all’interno della simulazione possono essere facilmente creati ed inseriti nel servizio che li gestisce attraverso uno snippet di questo tipo, in cui vengono definiti due ostacoli circolari in posizioni diverse e di raggio rispettivamente 50 e 15:

    IObstacleService obstacleService = this.simulationComponents.ObstacleService;
     
    CircleObstacle obstacle = new CircleObstacle(new PointF(100, 100), 50);
    obstacleService.ObstacleManager.AddObstacle(obstacle);
     
    CircleObstacle obstacle2 = new CircleObstacle(new PointF(200, 200), 15);
    obstacleService.ObstacleManager.AddObstacle(obstacle2);

    Arriviamo finalmente alla creazione di un agente, caratterizzato nello snippet che segue dai seguenti aspetti salienti:

    • Corpo con comportamento assimilabile ad un punto dotato di massa
    • Area visiva “conica” (o meglio, in 2D, angolare)
    • Comportamento composito di tipo “prioritario”, in cui viene assunto come comportamento dell’agente il primo, all’interno dei comportamenti “componenti”, per il quale la norma dell’accelerazione sia maggiore di una certa soglia
    • Comportamento componente di tipo “comportamento composito pesato”, in cui i vari comportamenti componenti vengono sommati ciascuno con un proprio “peso”. In altri termini tale comportamento farà sì che vengano contemporaneamente tenuti in considerazione criteri diversi di movimento facendo sì che la “somma” di tutti i criteri propenda in favore dei criteri ritenuti più importanti
    • Comportamento componente (del composito pesato) di tipo ObstacleAvoidance (per evitare gli ostacoli) con peso 10
    • Comportamento componente (sempre del composito pesato) di tipo LocalBehavior (per rimanere nell’area di confine) con peso 5
    • Comportamento componente (del composito prioritario) di tipo FlockBehavior che permette all’agente di rimanere accanto ai propri “simili”. Poiché il comportamento composito prioritario prevede un ordine di priorità (decrescente) dato dall’ordine di inserimento, il FlockBehavior apporterà il proprio contributo solo se l’agente non si trova a ridosso di un ostacolo o del confine dell’area permessa
    • Comportamento componente (del composito prioritario) di tipo SeekBehavior che spinge l’agente verso il “target” assegnato (nel nostro esempio la posizione del cursore del mouse)
    • Velocità massima pari a 3 (non chiediamoci l’unità di misura)

    Agent agent = new Agent(this.simulationComponents);
    agent.Body = new PointMassBody();
     
    agent.Vision = new ConeVision();
     
    // Comportamento composito "prioritario"
    PriorityBehavior behavior = new PriorityBehavior();
    agent.Behavior = behavior;
     
    // Comportamento componente del prioritario e composito "pesato"
    WeightedSumBehavior wo = new WeightedSumBehavior();
    behavior.Behaviors.Add(wo);
     
    // Comportamento componente del pesato di tipo "confine"
    LocalBehavior region = new LocalBehavior(this.region);
    wo.AddBehavior(region,5);
     
    // Comportamento componente del pesato di tipo "evita gli ostacoli"
    ObstacleAvoidanceBehavior obstacleAvoidance = new ObstacleAvoidanceBehavior();
    obstacleAvoidance.Probe = new TridentObstacleProbe();
    wo.AddBehavior(obstacleAvoidance,10);
     
    // Comportamento componente del prioritario di tipo "stormo"
    FlockBehavior flock = new FlockBehavior();
    behavior.Behaviors.Add(flock);
     
    // Comportamento componente del prioritario di tipo "insegui"
    MouseTargetPredictor mouse = new MouseTargetPredictor();
    mouse.Style = MouseTargetPredictorStyle.Move;
    mouse.OwnerControl = this.sceneControl;
    SeekBehavior seek = new SeekBehavior();
    seek.TargetPredictor = mouse;
    seek.TargetTracker = new ArrivalTargetTracker();
     
    behavior.Behaviors.Add(seek);
     
    // Accelerazione, Velocità e Posizione iniziali
    agent.Body.Update(new PointF(), new PointF(0, 0), new PointF(sceneControl.Width / 2, sceneControl.Height / 2));
     
    // Velocità massima
    UniformPointSaturator velsat = new UniformPointSaturator();
     
    velsat.MaxNorm = 3f;
    agent.Body.VelocitySaturator = velsat;

    A questo punto uno step della simulazione può essere calcolato utilizzando il metodo SimulateTurn() dell’oggetto Simulator. Nell’esempio allegato a NSteer tale metodo viene chiamato ogni qual volta il controllo contenuto all’interno del Form che costituisce l’interfaccia utente dell’applicazione necessita di essere ridisegnato. Tale necessità viene inoltre “forzata” in corrispondenza del gestore di evento dell’evento Idle dell’oggetto Application di Winforms, come illustrato nello snippet seguente, in cui la simulazione viene rispettivamente avviata e bloccata da due pulsanti denominati (guarda caso) “startButton” e “stopButton”:

    private void startButton_Click(object sender, EventArgs e)
    {
       this.stopButton.Enabled = true;
       this.startButton.Enabled = false;
       Application.Idle+=new EventHandler(Application_Idle);
    }
     
    private void stopButton_Click(object sender, EventArgs e)
    {
       Application.Idle -= new EventHandler(Application_Idle);
       this.stopButton.Enabled = false;
       this.startButton.Enabled = true;
    }
     
    private void Application_Idle(Object sender, EventArgs e)
    {
       this.sceneControl.Invalidate();
    }

    Per oggi ci fermiamo qui, ma per mantenere viva l’attenzione su quanto ci aspetta propongo non uno, ma addirittura due “approfondimenti” pratici di quanto abbiamo visto.

    Il primo è la realizzazione di un ostacolo di tipo rettangolare da integrare in NSteer, al quale potrebbe seguire, per i più solerti, l'implementazione di un ostacolo di tipo "polirettangolare", ossia definito da un insieme di rettangoli adiacenti.

    Il secondo spunto, probabilmente più divertente, prevede la realizzazione di una semplice applicazione Winforms che, utilizzando NSteer, definisca un'area rettangolare di dimensioni 100x100 unità logiche con al centro un ostacolo circolare di raggio 30. In questo ambiente, 10 agenti con posizione iniziale (0,0), velocità e accelerazione nulla e velocità massima 3 devono arrivare nel più breve numero di step di simulazione possibili all'angolo opposto, dove per tempo più breve si intende quello dell'ultimo arrivato.

    Come premio (o come penitenza?) i primi (o i soli) che posteranno del materiale interessante in merito ai due approfondimenti proposti avranno la facoltà di scegliere per primi i componenti dello sviluppo di NHoolingans da realizzare tra quelli che verranno resi a breve disponibili.

  • Progetto NHooligans - Parte 1 - Concetti, entità e interfacce del sistema di simulazione

    In considerazione della straordinaria risposta al mio post nella sezione "futuri appuntamenti" (intendo straordinariamente scarsa), provo ad addentrarmi un po' più nel concreto del sistema di simulazione che costituirà l'ambiente all'interno del quale interagiranno i vari agenti (denominati "hooligan") comandati dai programmi "contendenti".

    Facendo riferimento alla terminologia utilizzata nel framework NSteer, intorno al quale verrà sviluppato il sistema di simulazione, si individuano i seguenti "attori", ciascuno con le proprie peculiarità (o, in altri termini, ciascuno caratterizzato dall'implementazione di una o più delle interfacce definite dal framework:

    • Un oggetto di tipo "agente" (Agent, in NSteer), descrive un'entità che vive all'interno del "mondo" (World, in NSteer). Un agente ha un "corpo" (Body, in NSteer), un sistema di "visione" (Vision, in NSteer) e un "comportamento" (Behavior, in NSteer).
    • Il "corpo" di un agente definisce le proprietà cinematiche dell'agente (posizione, velocità e accelerazione).
    • Un sistema di "visione" definisce la regione in cui un agente può "vedere" gli altri agenti o gli ostacoli presenti, tipicamente, nelle sue vicinanze.
    • Il "comportamento" di un agente determina, ad ogni passo della simulazione, la forza (in senso vettoriale) cui sarà sottoposto il suo "corpo".
    • Gli agenti sono confinati all'interno del "mondo".
    • Il "simulatore" (Simulator, in NSteer) determina, ad ogni passo della simulazione, la situazione cinematica (posizione, velocità e accelerazione) di tutti gli agenti presenti nel sistema.
    • Il sistema di simulazione implementato in NSteer definisce quattro servizi per gestire gli agenti, il mondo, gli ostacoli e la "prossimità" di un agente nei confronti degli altri rispettivamente mediante i servizi AgentService, WorldService, ObstacleService e NeighborhoodService.
    • Per quanto concerne la visualizzazione della simulazione NSteer definisce il concetto di Sprite, un oggetto 2D caratterizzato da attributi quali colori, visibilità, forma, ecc. e il concetto di "Scena" (Scene, in NSteer), che astrae gli aspetti legati al rendering della simulazione.

    Prima che vi sloghiate la mandibola per gli sbadigli, vi incollo un'immagine relativa ad uno step della simulazione realizzata all'interno del demo che accompagna il framework NSteer.

    NSteerDemo

    Nello screenshot è possibile individuare una decina di agenti (i punti contornati dal cerchietto rosa, che rappresenta lo spazio occupato dall'agente) ciascuno dei quali ha un proprio "orientamento" ed un proprio "cono visivo" (rappresentato dai 3 segmenti terminati da pallini che escono dal centro dell'agente). I comportamenti di ciascun agente sono descritti dall'etichetta che compare accanto a ciascun cerchietto rosa. I comportamenti definiti all'interno del framework NSteer sono già parecchi, ma è possibile definirne di nuovi o scrivendoli da zero o utilizzando uno dei comportamenti "contenitore" già definiti in NSteer. I 3 cerchi viola rappresentano degli ostacoli circolari. Nell'immagine gli agenti al centro della finestra hanno attivato il comportamento "Seek" (ossia insegui) nei confronti del pallino verde in alto (comandato dal movimento del cursore del mouse nell'applicazione di esempio). Gli altri hanno attivato invece un comportamento composito caratterizzato dalla somma del comportamento "Confine" (ossia confina, argina) e del comportamento "Avoid" (ossia evita), rispettivamente per rimanere all'interno della regione permessa (rappresentata dal riquadro interno) e per evitare l'ostacolo che hanno accanto.

    Anche se nello step di simulazione immortalato da questo screenshot non è evidente (perché in questo screenshot altri comportamenti avevano priorità), gli agenti tendono a rimanere uniti ma a non sovrapporsi grazie ad un altro comportamento: il FlockBehavior (dove flock significa "stormo").Questo comportamento ha una valenza "storica" particolare perché è proprio dalla scomposizione delle regole che guidano il comportamento di uno stormo di uccelli che Reynolds ha tratto lo spunto per il proprio lavoro. Per rendervi conto meglio di quello che Reynolds ha inventato (o forse dovrei dire "scoperto") vi consiglio di dare un'occhiata ai demo allegati, ed in particolare (premento Tab si passa da un demo al successivo) al demo "Boids".

    Nel prossimo post inizieremo a dare un'occhiata a qualche snippet di codice, per poi fare chiarezza sui "lotti" di progetto disponibili per l'assegnazione a chi vorrà partecipare attivamente allo sviluppo di NHooligans.

  • Sì, va bene, sviluppiamo un software insieme…ma quale?

    Ciao a tutti. Riprendo il primo post in cui si è parlato di NHooligans dalla sezione Eventi del Forum per mettere subito qualcosa qui dentro prima che Paolo si arrabbi e chiuda il Blog…

    Premetto di nuovo (ed è la terza volta…basta!) che lo scopo del progetto in questione non dovrebbe essere, almeno a mio avviso, quello del software da realizzare in se', quanto piuttosto un'opportunità per sperimentare metodologie, architetture e tecnologie che favoriscano lo sviluppo collaborativo anche all'interno di un gruppo di sviluppatori che non condividono un luogo di lavoro "fisico", bensì uno spazio virtuale (termine che sembra uscito dalla bocca di uno di quegli informatici degli anni 60 che stavano davanti ai calcolatori con il camice) rappresentato da un misto di sistemi informativi quali email, forum, blog, controllo sorgenti, ecc.

    In breve, l'idea per il soggetto dello sviluppo è quella di un gioco on-line ispirato al celeberrimo (e antichissimo) "core wars" (la guerra dei nuclei, nella traduzione italiana di "Le Scienze"), ossia di un gioco in cui i concorrenti sono rappresentati da agenti autonomi il cui comportamento è stabilito da programmi realizzati da giocatori "umani" che (indirettamente) concorrono al raggiungimento di un obiettivo. Anche se molti sono i giochi che si sono ispirati a core wars negli anni a venire (p-robots, c-robots e ants per citarne alcuni), il capostipite resta ancora l'unico caso in cui i programmi che si sfidano sono al contempo avatar e software di controllo degli avatar stessi: un'idea a mio parere di rara genialità (anche se, a quanto dice wikipedia, pare che Dewdney, l'autore del gioco, si sia in realtà ispirato ad un altro gioco chiamato Darwin, ideato nei laboratori della Bell negli anni 60, ma a noi che ce frega?).

    Ovviamente, essendo qui tutti appassionati di sviluppo .NET (qualcuno anche perché non ha una ragazza), l'idea è quella di realizzare un sistema in cui degli oggetti .NET, compilati ed ospitati in un assembly, comandino degli agenti autonomi che si sfidano in una sorta di "arena" a suon di..."mazzate". Mi spiego meglio: l'ambientazione di riferimento sarebbe quella, sicuramente italiana almeno quanto la pizza ma anche un po'inglese, di due tifoserie avversarie che si gonfiano come zampogne nel tentativo di raggiungere una meta (che nel caso reale manca, ma questo dimostra ancora una volta che l'informatica è un mondo estremamente più concreto di quello reale) rappresentata da una bandiera (della tifoseria avversaria, si intende), da conquistare e, presumibilmente, in seguito bruciare sghignazzando. Proprio per questo non mi dispiacerebbe se il progetto si chiamasse "NHooligans", anche se è un nome un po' lassativo. In alternativa potremmo usare, come fa mamma Microsoft, il nome di qualche frazione locale, tipo "Ramazzano", ma forse è meglio NHooligans...

    Lo schema del gioco dovrebbe essere questo: si sfidano due tifoserie, ognuna rappresentata da N agenti autonomi, detti "hooligan" (ironicamente al contempo tifosi e agenti, ah ah), ciascuno dei quali viene "pilotato" da uno di M programmi, con la possibilità di attribuire teoricamente anche un programma differente per cisacun hooligan. Ciascun hooligan è rappresentato nell'arena di gioco da un'area circolare che ne rappresenta, per così dire, l'ingombro. Gli hooligan, amici o nemici, non possono "compenetrarsi", ossia i loro "cerchi" non possono sovrapporsi. Ogni hooligan viene dotato inizialmente di una riserva di energia (ad es. 100 unità), che viene decrementata sia per effetto del muoversi (per più tempo e più forte corre, più si affatica) sia, soprattutto, per effetto delle botte che prende. Inoltre, l'energia residua di un hooligan determina la velocità massima con cui può muoversi e i danni che fa quando picchia un avversario. Ogni qualvolta due hooligan avversari si trovano a distanza minima (la somma dei due raggi, ossia quando si toccano) essi si sottraggono punti energia in ragione dei loro rispettivi stati di salute (uno sano picchia di più di uno già malconcio). Quando l'energia di un hooligan arriva a zero, questi viene ovviamente "rimosso" dall'arena. Se un hooligan si trova a contatto con più avversari, questo subirà un danno maggiore della somma dei danni dei singoli avversari, per effetto di una "sinergia" che spesso manca nei film tipo quelli con Bruce Lee in cui , anche se sono 100 contro 1, i cattivi sfidano il protagonista uno per volta, per onestà. Se un hooligan riesce a raggiungere il punto dell'arena che contiene la bandiera avversaria, la sua squadra vince e totalizza i punti corrispondenti alla somma dei punti energia degli hooligan ancora in gioco.

    Anche se seguiranno altri dettagli nei prossimi post, una prima idea che vorrei proporre è quella di utilizzare nei programmi di controllo degli hooligan delle "primitive" complesse del tipo "segui quell'holigan", oppure "voi 4 rimanete vicini", oppure "evita quell'ostacolo", oppure "vigila in questa zona", oppure (e più spesso) "scappa". Per far questo potremmo utilizzare il framework NSteer, sviluppato da Jonathan de Halleux a partire dal lavoro originale di Craig W. Reynolds della Sony, che per primo ha ideato e sviluppato gli algoritmi "comportamentali" di cui sopra, senz'altro motivato dalla partecipazione a diversi progetti di sviluppo per software playstation.

    Se ne avete voglia, potete intanto dare un'occhiata al progetto NSteer (dalla solution togliete il progetto di test e lanciate il demo winform per farvi un'idea) e, perché no, anche alla pubblicazione originale (del 1999) di Craig W. Reynolds e ai relativi demo animati.

    Posted Jun 11 2008, 07:47 AM by maiorfi with no comments
    Filed under:
dotNet Umbria 2007-2008
Powered by Community Server (Commercial Edition), by Telligent Systems