in

dotNet Umbria

Il primo User Group in Umbria sul mondo .Net

NHooligans

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.

Only published comments... Jun 15 2008, 04:42 PM by maiorfi

Comments

 

pomarc said:

decisamente bulo.

lo stampo (il grigetto del testo ha già reclamato un paio di diottrie), lo leggo e ve fo arsapé.

June 26, 2008 11:33 PM

About maiorfi

Sopravvivo dal 1971. Pigio per la prima volta i tasti di gomma del Sinclair Spectrum nel 1982, grazie all'acquisto presso la famosa G.B.C. di Temperini in Via Pellas da parte di mio padre. Entro in possesso di un Commodore Amiga 1000 in una anno che ricordo a fatica tra il 1986 e il 1987. Inizia il quegli anni la sperimentazione della frustrazione per la difficoltà di utilizzo delle API C per lo sviluppo di applicazioni integrate nel sistema operativo "Workbench" (ispirato al sistema Macintosh), un po' dovuta anche agli unici 2 compilatori presenti sul mercato: Aztec e Lattice, amichevoli come il software per la perforazione delle schede. La frustrazione veniva lenita parzialmente solo dalle ore trascorse giocando a KickOff di Dino Dini. Finalmente nei primi anni 90 approdo al mondo dei PC, con un 386sx assemblato della Olidata. In quegli anni il Turbo Pascal prima e il Turbo C dopo riescono a far sembrare lo sviluppo un esperienza tutto sommato divertente. Nel 1993 installo per la prima volta una copia (ovviamente abusiva) di Windows 3.1, con la quale inizio a sperimentare, grazie anche alla strepitosa libreria OWL di Borland integrata in "Borland C++" (primo ambiente object-oriented degno di questo nome), lo sviluppo di applicazioni desktop (anche se a quei tempi l'espressione "desktop" era sconosciuta e superflua, visto che tutte le applicazioni windows avevano di fatto un'interfaccia utente). Negli anni a seguire il mio interesse si focalizza su Visual C++, MFC, COM, ATL e su tutto quanto oramai inizia ad orbitare intorno alla piattaforma di sviluppo Microsoft "unmanaged", tanto che nel 1996 impronto la mia tesi di laurea, svolta con e per la società Eles di Todi, interamente su COM/OLE/ActiveX, ed in particolare sulle architetture per l'integrazione binaria dei componenti software. Credo che la tesi abbia effettivamente totalizzato 2 lettori, me e il mio revisore compresi. Dopo la laurea ed un anno di militare speso smanettando in Clipper presso il Nucleo Elaborazione Dati del Distretto Militare di Perugia (allora gestito dal famoso Colonnello Ansalone e dal famigerato Maresciallo Venditto), vengo assunto dalla (oramai defunta) Sinfor s.r.l. come lead (and only) developer. Ricordo tanti piccoli progetti Visual C++/MFC e VB6, qualcuno fatto male e qualcuno fatto peggio, ma complessivamente utili al loro scopo: far sì che io riuscissi a millantare un'esperienza ed una capacità che non avevo, contribuendo a farmi guadagnare uno stipendio che non meritavo. Nel 1999 lascio la Sinfor (anticipando il termine dei 2 anni del contratto di formazione!) per fondare la Innovactive Engineering s.r.l. insieme ai miei 3 soci (a dire il vero uno dei 3 soci è cambiato qualche tempo dopo, ma a chi importa?). Stentiamo un po' ad affrontare il seppur ricchissimo ed apertissimo mercato umbro ma riusciamo alla fine a consegnare il nostro primo prodotto: un gestionale per l'amministrazione di una società che opera nel campo assicurativo/assistenziale. Non è un successo né in termini economici né in termini di soddisfazione professionale (ad eccezione di quanto concerne la realizzazione del più lento e inaffidabile ORMapper della storia) ma da qualcosa bisognava pure cominciare. Negli anni intorno al 2001-2002 ci spostiamo all'interno della sede di Carsoli del Sole24Ore, coinvolti in qualità di consulenti per lo sviluppo con ASP/COM+/VB6/VC++. E' il periodo delle "vacche grasse" (dovuto alla famosa bolla speculativa della new economy), purtroppo mai più ripresentatosi negli anni successivi. Rientrati stabilmente a Perugia, dal 2003 in poi iniziamo un'attività più propriamente "aziendale" e cresciamo fino a raggiungere quota 7, grazie alla acquisizione delle nostre tre colonne portanti: Fabrizio, Valerio e Gianluca.
dotNet Umbria 2007-2008
Powered by Community Server (Commercial Edition), by Telligent Systems