Visualizzazione post con etichetta windows. Mostra tutti i post
Visualizzazione post con etichetta windows. Mostra tutti i post

sabato 10 giugno 2017

Creazione di un servizio Windows con Visual Studio

CREAZIONE DI UN SERVIZIO WINDOWS CON VISUAL STUDIO
In un sistema operativo Windows, un servizio è un’entità, a cui è associato un eseguibile, che effettua operazioni senza l’intervento di un utente specifico, perché il servizio “appartiene” al sistema operativo.
Solitamente è utilizzato per operazioni in cui non deve essere richiesto l’intervento dell’utente.
Ma vediamo come creare un servizio windows con Visual Studio.
Visual Studio solitamente ha tra i suoi Modelli di progetto anche il Servizio Windows, comunque è possibile creare direttamente una classe che implementa la classe base  System.ServiceProcess.ServiceBase. Questa classe contiene un metodo astratto OnStart(string[] args) , che verrà chiamato quando il servizio viene avviato.
Purtroppo però non è possibile eseguire il debug su Visual Studio come eseguiamo il debug con altri programmi che presentano il metodo main. Per provare il nostro programma dobbiamo compilare l’eseguibile exe che per funzionare dovrà essere installato sul sistema. Ecco che la sovrastruttura .NET ci agevola il tutto aggiungendo un installer al nostro progetto.
Un Installer per un servizio non è altro che una classe che eredita da una classe della piattaforma .NET i cui metodi verranno chiamati da un apposito tool (InstallUtil) per installare il servizio.
 Ecco i due modi possibili per aggiungere un installer alla nostra classe:
1.     Apriamo la nostra classe in Design Mode, e cliccando col tasto destro su uno spazio vuoto, scegliamo “Add Installer”
2.     Aggiungendo una classe che eredita da System.Configuration.Install.Installer
Seguendo il secondo metodo, aggiungiamo manualmente una classe e la facciamo ereditare da System.Configuration.Install.Installer.
A questa classe aggiungiamo due proprietà:
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
La prima proprietà è l’installer relativo al processo che verrà lanciato dal nostro servizio, mentre la secondo è l’installer relativo al processo vero e proprio.
Impostiamo i campi dell’oggetto serviceProcessInstaller1:
·        Parent: è il nome della classe di tipo System.Configuration.Install.Installer che contiene il serviceInstaller
·        Account: il tipo di “privilegio” che avrà il nostro processo. Sono 4 i tipi:
o   LocalService: Utente con i più bassi privilegi
o   NetworkService: Medio privilegio
o   LocalSystem: Utente coi più alti privilegi
o   User: Avvio del servizio con un utente in particolare
Per il nostro esempio, scegliamo il secondo, quindi this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
Impostiamo invece quelli dell’oggetto  serviceInstaller1:
·        L’unico campo rilevante è il ServiceName, ovvero il nome del nostro servizio, quello che comparirà tra i servizi del tool mmc.exe
Adesso che il nostro servizio base è impostato, aggiungiamo un qualcosa che ci dice che il nostro servizio funziona, altrimenti lo vedremo con lo stato “in esecuzione” non trovando nessuna evidenza del fatto che stia funzionando
APPLICAZIONE: AGGIUNTA DI UN TIMER
Aggiungiamo una proprietà di tipo System.Timers.Timer alla nostra classe relativa al servizio.
Facendo un override del metodo OnStart(string[] args), che verrà eseguito non appena viene avviato il processo, aggiungiamo il codice dove inizializziamo il nostro timer:

protected override void OnStart(string[] args)
        {
            timer1 = new Timer(5000);
            timer1.Elapsed += Tick;
            timer1.Enabled = true;
            timer1.Start();
        }
Il nostro è quindi partito, eseguendo il metodo Tick ogni 5000 millisecondi.
Nel metodo Tick metteremo del codice che scrive un file di testo nella root del nostro hard disk. E’ importante che per fare questo, il tipo di servizio venga lanciato coi privilegi di LocalSystem, come detto sopra.
Di seguito il metodo Tick:

protected override void Tick(object sender, ElapsedEventArgs e)
        {
            StreamWriter t = new StreamWriter("C:\\DMTService_log.txt", true);
            t.WriteLine(DateTime.Now + "-evento tick");
            t.Close();
        }

Adesso che il nostro servizio è pronto, compiliamo il tutto e passiamo all’installazione del servizio.

INSTALLAZIONE DEL SERVIZIO
L’installazione del servizio verrà fatta col metodo installutil.exe, un tool della piattaforma che troviamo nella directory della nostra installazione .Net.
Per esempio, aprendo cmd.exe, scriviamo sulla console:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe C:\Users\Alberto\Documents\DMTDocumentService\DMTDocumentService\bin\Release\DMTDocumentService.exe
A questo punto il servizio è installato e quindi basta andare su Gestione Attività di Windows, tab Servizi, cercare il nostro servizio e attivarlo.

Basta andare sulla root “C:\” e vedremo il file DMTService_log.txt con il log che si aggiorna ogni 5 secondi. 

giovedì 28 gennaio 2016

Come collegare il gamepad della prima console xbox sul PC

Avevo in cantina una vecchia xbox, la prima serie. Essendo obsoleta, ho pensato di recuperarne almeno il sempre valido joystick, o gamepad che dir si voglia. 
Ha 2 analogici, 2 triggers, 1 d-pad e 8 tasti: il gamepad della xbox 360 (nativamente supportato su windows, e con presa USB) ha solo un tasto in più.
Il modello in questione è il controller s, il secondo gamepad usato per questa console

Questo gamepad ha una presa proprietaria della Microsoft e credo che non appartenga a nessun standard, almeno stando alle informazioni che sono riuscito a racimolare sul web. 
Il vantaggio è che questa presa è assolutamente analoga a quella USB: in poche parole, non basta essere un elettrotecnico per trasformare questa presa in una USB e quindi, collegarla al PC.
Infatti sia la presa USB che la presa del gamepad hanno lo stesso numero di fili aventi lo stesso colore  (4 fili, in verità il gamepad ne ha uno in
La presa del gamepad
più, il giallo, che non viene utilizzato però). In fin dei conti, basta prendere un vecchio cavo USB e recidere in due sia quest'ultimo che il filo del gamepad, quindi collegare i fili; se si è bravi si può saldare ogni singolo filo e isolare singolarmente i quattro fili. 

Fatto questo, collegare il gamepad al PC e Windows riconoscerà una nuova periferica, ma fallirà nel trovare i driver appropriati. Allora ecco che un nerd della rete è venuto in aiuto: ha sviluppato i driver necessari per far funzionare e riconoscere il gamepad al PC come una
I fili collegati
periferica di gioco. Se avete un sistema a 32 bit potete scaricare da qui l'installer (scegliete l'ultima versione) che installerà tutto automaticamente, riavviate e il gioco è fatto. 
Se invece avete un sistema a 64bit questi driver, compilati per 32bit, non funzioneranno. Ma ecco che un altro bravo nerd della rete è venuto di nuovo in aiuto e ha compilato i driver anche per i sistemi a 64bit. Vi illustro in alcuni passaggi quello che ho fatto:
  1. Da questa pagina ho copiato sugli appunti i caratteri strani che stanno tra "begin" e "end" (compresi), dunque ho aperto Notepad++ (dovrebbe andar bene anche il blocco note, ma se usate ancora quest'ultimo siete sfigati), incollato il tutto e salvato come file avente estensione .7z
  2. Ho scaricato un encoder/decoder per decodificare il tutto da qui ed estratto il file UUDECODE.EXE sulla stessa cartella del file .7z
  3. Ho aperto il prompt (dal menù Start, scrivere cmd e premere invio), mi sono spostato sulla cartella (cd "percorso cartella) e scritto UUDECODE <nomefile.7z>  (con il nome del file .7z che avete salvato al punto 1) a premuto invio
  4. Poi ho aperto il file xbcd_108.7z che si è creato (l'ho aperto con 7zip, ma va bene anche winrar) ed estratto il contenuto in una cartella.
  5. Sono andato nel Pannello di Controllo->Sistema->Gestione dispositivi e individuato la periferica non riconosciuta (contrassegnata da un punto esclamativo su sfondo giallo), dunque cliccato con tasto destro->Proprietà, nella finestra sono andato nella sezione "Driver" e cliccato su "Aggiorna driver..." e poi "Cerca il software del driver nel computer", dunque ho scelto la sottocartella "x64" della cartella dove ho scompattato xbcd_108.7z
  6. Dopo avere atteso un po', Windows ci chiede se siamo sicuri di voler installare un software senza firma; scegliamo si. 
I sistemi Windows a 64bit controllano a ogni avvio se ci sono driver senza firma digitale, in tal caso li blocca e ne impedisce l'esecuzione. In poche parole,allo stato attuale, il gamepad ancora non vi funziona. Se voi avete un modo di mettere la firma digitale bene, altrimenti dovrete eludere il succitato controllo. All'avvio di Windows, premete continuamente F8 e scegliere l'ultima opzione, quella relativa al controllo dei driver non firmati. Questo farà si che all'avvio non verranno controllati i driver e finalmente il nostro gamepad funzionerà. Andate nel pannello di controllo e cercate "gioco", dovreste trovare "Configura dispositivi di gioco USB". Da lì dovreste vedere se il gamepad funziona bene. 

In aggiuntiva, dovrete installare il software che gestisce le periferiche di gioco, cioè un software che sfrutta al meglio le potenzialità di questo gamepad con cui possiamo associare le funzioni di gioco a ogni tasto del gamepad; all'inizio io ho usato il molto valido Pinnacle Game Profiler, ma vista la sua natura shareware (ormai mi è scaduto, comunque il prezzo è molto basso) sono passato a una valida alternativa freeware: Xpadder. Potrete seguire le istruzioni di questo video per la configurazione.

martedì 12 novembre 2013

Calcolare il determinante di una matrice di qualsiasi ordine in maniera ricorsiva - Software grafico Windows

Tempo fa ho scritto un post che descriveva il metodo per calcolare il determinante di una matrice in maniera ricorsiva (metodo di Laplace) e mostravo la funzione creata per C++ per calcolare il determinante data una matrice quadrata di un qualsiasi ordine. 

Ho creato un'interfaccia grafica scritta con GTK+ dove si può comodamente inserire la matrice di cui si vuole calcolare il determinante. Per ora il programma è compilato con windows perché in questo momento non ho un linux a portata di mano, quindi l'eseguibile che potrete scaricare è per windows. 

Il programma inizia con un prompt, chiedendo all'utente di inserire l'ordine della matrice:
subito dopo, inserendo un ordine di 3 per esempio, avremo la schermata:

in cui potremmo inserire i valori e calcolare il determinante:

Il programma non è limitato a un certo ordine, teoricamente si può inserire qualsiasi ordine, salvo tempi che si allungano per via dell'elaborazione prima della parte grafica, poi (più veloce) la parte computazionale. I problemi derivati dall'uso di GTK+ su windows è che queste librerie sono nate in ambiente linux, e il porting per windows è complesso e macchinoso rispetto altre librerie grafiche. Ma io sono affezionato a Ubuntu e Gnome :)

Lo potete scaricare da qui (windows):
Istruzioni:
estraete i files in una cartella e lanciate "Determinante.bat", il quale lancerà a sua volta "Determinante.exe" contenuto nella cartella "lib". Ho dovuto fare questo trucchetto perché il programma per funzionare ha bisogno delle librerie GTK+.

A breve anche la versione per linux.

giovedì 31 ottobre 2013

Libreria crt Borland-style per C++ (Windows)

Preso dalla nostalgia dei primi anni di programmazione sul vecchio Borland Pascal (e il suo fantastico IDE in modalità testo in DOS), ho pensato di ricreare la vecchia libreria "crt" della Borland sul C++, per la console di Windows. 

Ho creato una classe "Console", che scrive e legge sullo stdin e stout e non faccio uso delle librerie standard del C++ (quindi è una classe windows-dipendente), fornendo alcuni scarni metodi per un minimo di I/O. Invero faccio uso di una libreria standard del c/c++ (stdarg) per consentire alla funzione printf (scritta da me) di ricevere un numero indefinito di parametri.
Header della classe Console:
class Console
{
    public:
        Console();
        ~Console();
        void ClrEol();
        void ClrScr();
        void cursorbig();
        void cursoroff();
        void cursoron();
        void Delay(DWORD);
        bool GotoXY(int xx,int yy);
        bool KeyPressed();
        char ReadKey();
        void Sound(DWORD,DWORD);
        void TextBackground(DWORD val);
        void TextColor(DWORD val);
        int WhereX() { return coord.X; }
        int WhereY() { return coord.Y; }
        DWORD GetTextColor() { return tc; }
        DWORD GetTextBackground() { return bc; }
        HANDLE GetHandle(){return handle;}
        DWORD printf(const char *lpBuffer,...);
        DWORD printc(char c);
        void ReadLn(int&);
        void ReadLn(char*);
        void saveScreen();
        void restoreScreen();
    protected:
    private:
        DWORD tc;
        DWORD bc;
        COORD coord;
        HANDLE handle;
        LPSTR charbuf;
        LPWORD attrbuf;
        bool bufpieno;
        void Setx(int n){coord.X=n;}
        void Sety(int n){coord.Y=n;}
};

Nota bene: libreria fortemente ancorata a Windows, senza il quale non può funzionare.

Qui potete leggere la documentazione, mentre da qui potete scaricare i file sorgente.

giovedì 1 marzo 2012

Risolutore "Solitario Klondike", codice sorgente e spiegazione.

Per completare il precedente post, ecco questa parte dedicata al codice.

Il “Risolutore solitario Klondike” è stato scritto in C++, con alcune parti scritte in C (è questa la potenza e versatilità del C++…mantenere la compatibilità col caro vecchio C), soprattutto quelle parti di codice inerenti all’interfaccia grafica.
La collezione di classi principale è in C++ standard quindi OS indipendente, le parti di codice della GUI sono molto limitate. Quindi il programma è stato pensato per funzionare su Windows (Win32) e Linux(GTK+/Gnome).
Analizziamo prima il cuore del programma, ovvero la collezione di classi OS indipendente che creano la parte “logica” dell’applicazione,
Comincio a presentare la gerarchia, e poi analizzo le classi una per una.
 e invece questa è la classificazione fatta da me dei gruppi di carte che sono sul tavolo:
p1,p2,p3 e p4 sono di tipo Pila.
master è di tipo MazzoMaster.
terra è di tipo GruppoDiCarte.
 s2,s3,s4,s5,s6 ed s7(ovvero le carte sotto le scale, coperte) sono di tipo GruppoDiCarte.
g1,g2,g3,g4,g5,g6,g7 (ovvero le scale) sono di tipo GruppoOrdinato.
La classe fondamentale di cui si servono tutte le altre è Carta, di cui riporto il file "Carta.h" a cui appartiene:



#pragma once
#include 
#include 
using namespace std;
enum Seme {
 CUORI,QUADRI,PICCHE,FIORI,
};
enum Colore {
 ROSSO,NERO,
};
class Carta {
friend ostream& operator<<(ostream&,Carta&);
public:
 Carta(unsigned int,Seme,string);
 Carta(Carta&);
 unsigned int getValore() const;
 Seme getSeme() const;
 Colore getColore() const;
 void stampa() const;
 string getResource() const;
private:
 unsigned int valore;
 Seme seme;
 string risorsa;
 
 string getStringaSeme() const;
};
La classe presenta due costruttori, uno che crea la carta con il suo valore e il suo seme, e un altro che è un costruttore di copie (che comunque potrei togliere perché non ne faccio uso). Da notare l'importante metodo string getResource() const; che restituisce il percorso del file che contiene l'immagine associata alla carta, scelta appositamente in base al valore e il seme. L'overloading del'operatore << non fa altro che passare una sua stringa rappresentativa a uno stream (per esempio "2 di cuori"). Adesso presento la classe GruppoDiCarte che è una classe quasi-astratta, che rappresenta un gruppo generico di carte messe alla rinfusa.
#pragma once
#include "Carta.h"
#include 
using namespace std;
class GruppoOrdinato;
#define MAX_CARTE 52

class GruppoDiCarte{
friend ostream& operator<<(ostream&,GruppoDiCarte&);
public:
 GruppoDiCarte();
 GruppoDiCarte(unsigned int);
 unsigned int getNCarte(){return nCarte;}
 void setCarta(unsigned int);
 void mescola();
 virtual void ordina();
 virtual Carta* getPrimo(){return NULL;}
 virtual bool attaccaGruppo(GruppoOrdinato*){return NULL;}
 void stampa();
       Carta* pop();
 void push(Carta*);
 Carta* vediCarta();
 virtual bool attacca(Carta*);
 void reverse();
protected:
 unsigned int nCarte;
 Carta* carte[MAX_CARTE];
};
Da notare già quanti metodi virtuali ho inserito. Sono dei metodi "vuoti", potevo anche farli virtuali puri, ma poi non mi avrebbe permesso di instanziare oggetti dalla classe. Ad ogni modo questi metodi verranno chiamati solamente dalle classi figlie. Presenta due costruttori, uno che crea un gruppo di carte con 0 elementi (vuoto) e un altro che crea un gruppo di carte con un numero determinato di elementi, che saranno ovviamente inizializzati al valore delle carte tramite il metodo void setCarta(unsigned int); Poi ci sono i metodi void mescola(); e virtual void ordina(); inseriti per completezza, visto che non ne faccio uso nel programma. Per adesso tralasciamo i metodi virtual bool attaccaGruppo(GruppoOrdinato*); virtual bool attacca(Carta*); che saranno specializzati nelle classi figlie. Particolare importanza hanno i metodi Carta* pop(); void push(Carta*); e Carta* vediCarta(); visto che saranno utilizzati frequentemente in tutto il programma. push(Carta*) aggiunge una carta al mazzetto, mentre pop() la preleva, invece vediCarta() mostra la carta che si trova in cima alla pila (infatti ogni gruppo di carte, eccetto alcuni, viene trattato come se fosse una struttura di tipo LIFO), senza modificare il mazzetto, ovvero senza prelevarla. Una regola categorica per il corretto uso di queste classi è che ad ogni istruzione pop() eseguita su qualsiasi gruppo di carte, deve seguire una istruzione push() su un altro gruppo di carte, per conservare il numero totale di carte. Infatti se tolgo una carta a un gruppo senza inserirla in un altro gruppo, succede che perdo quella carta, scalando di uno il numero totale di carte. Un classico uso che ne faccio è questo: supponiamo un gruppo di carte di nome "g1" e una ltro di nome "g2", e che io voglia passare una carta da g2 a g1. g1.push(g2.pop()); oppure, verificando se effettivamente, stando alle regole del gioco, posso eseguire la suddetta operazione:
Carta* c = g2.vediCarta();  //guardo la carta
if (c==NULL)                //se il mazzo è vuoto ritorno
 return false;
bool result = g1.attacca(c);     //verifico se posso fare l'operazione
if (result) {               // in caso affermativo...
 g2.pop();            //prelevo la carta da g2, visto che l'ho già attaccata a g1
 return true;
}
Adesso analizziamo le classi che ereditano da GruppoDiCarte, ovvero GruppoOrdinato,MazzoMaster,Pila,Mazzo. Analizziamo prima mazzo, che rappresenta un mazzo intero di 52 carte.
#pragma once
#include "GruppoDiCarte.h"
#include 
using namespace std;

class Mazzo : public GruppoDiCarte {
public:
 Mazzo();
 void ordina();
private:
 string toString(int);
};
La classe (che ricordiamo eredita da GruppoDiCarte) non fa altro che creare un gruppo di carte di 52 elementi e mescolarli. Riporto il costruttore di questa classe che crea il mazzo e simula il mescolamento delle carte:
#include "Mazzo.h"
#include 
#include 

Mazzo::Mazzo() {
 nCarte = 52;
 for (int i=0;i<13;i++){
  carte[i] = new Carta(i+1,CUORI,"carte\\"+toString(i+1)+"c.bmp");
  carte[i+13] = new Carta(i+1,QUADRI,"carte\\"+toString(i+1)+"q.bmp");
  carte[i+26] = new Carta(i+1,PICCHE,"carte\\"+toString(i+1)+"p.bmp");
  carte[i+39] = new Carta(i+1,FIORI,"carte\\"+toString(i+1)+"f.bmp");
 }
 /*          mescolo le carte    */
 srand(time(NULL));
 int c;
 int d;
 Carta* app;  //carta di "appoggio"
 for (int i=0;i<208;i++){
  c = rand() % 52;
  d = rand() % 52;
  app = carte[c];
  carte [c] = carte[d];
  carte [d] = app;
 }
}
Passiamo a MazzoMaster, ovvero il mazzo posto in alto a sinistra:
#pragma once
#include "GruppoDiCarte.h"

class MazzoMaster : public GruppoDiCarte {
public:
 Carta* daiCarta();
 void setTerra(GruppoDiCarte*);
private:
 GruppoDiCarte* terra;
};
che aggiunge i metodi Carta* daiCarta(); e void setTerra(GruppoDiCarte*); dove il primo serve per scoprire una carta e il secondo serve per passare il gruppo di carte già scoperte alla classe, in modo che se MazzoMaster si esaurisce, "capovolga" le carte che ci sono a terra. Passiamo ancora a Pila (il nome non ha il noto significato in informatica, ma non sapevo che nome scegliere). Le pile sul tavolo sono quattro, si trovano in alto a destra e raggruppano le carte di uno stesso seme in ordine crescente.
#pragma once
#include "GruppoDiCarte.h"
#include 
#include 

class Pila : public GruppoDiCarte {
friend ostream& operator<<(ostream&, Pila&);
public:
 Pila();
 bool attacca(Carta* c);
 Seme getSeme();
 Carta* getPrimo(){return NULL;}
 bool attaccaGruppo(GruppoOrdinato*){return false;}
private:
 Seme seme;
};
bool attacca(Carta* c); è un metodo specializzato che è virtual della classe madre, e restituisce true se la carta può effettivamente essere attaccata al gruppo (stando alle regole del gioco), false altrimenti. Seme getSeme(); ritorna il seme del gruppo (visto che sono tutte carte di uno stesso seme),Carta* getPrimo() è una funzione ridefinita in modo che ritorni sempre NULL, e allo stesso modo ridefinisco bool attaccaGruppo() visto che non ha senso attaccare un gruppo di carte a questo, dato che andranno attaccate una per una. Passiamo all'ultima classe che rappresenta un gruppo di carte, ovvero GruppoOrdinato, quei gruppi di carte scoperti (7 in tutto) e ordinati in ordine decrescenti a colori alterni:
#pragma once
#include "GruppoDiCarte.h"
#include 
using namespace std;

class GruppoOrdinato : public GruppoDiCarte {
friend ostream& operator<<(ostream&, GruppoOrdinato&);
public:
 GruppoOrdinato();
 bool attacca(Carta*);
 bool attaccaGruppo(GruppoOrdinato*);
 Carta* getPrimo();
 void setAttaccaPrimaVolta(); //uso riservato solamente a Tavolo
private:
 bool attaccaPrimaVolta;
};
bool attacca(Carta*); e bool attaccaGruppo(GruppoOrdinato*); sappiamo già a cosa servono, bool attaccaPrimaVolta; è una variabile usata per "capire" se la carta che verrà attaccata è attaccata dal primo utilizzo o meno. void setAttaccaPrimaVolta(); resetta questa variabile al valore originario, pertanto non andrebbe utilizzata e il suo utilizzo è riservato alla classe Tavolo. Utilizzare questo metodo sarebbe deleterio e il programma non funzionerebbe come dovrebbe. Rimane l'ultima classe da analizzare (oltre a Risolutore), ovvero Tavolo.
#pragma once
#include "Mazzo.h"
#include "GruppoOrdinato.h"
#include "Pila.h"
#include "MazzoMaster.h"
#include 
using namespace std;
class Tavolo {
friend ostream& operator<<(ostream&,Tavolo&);
public:
 Tavolo(Mazzo*);
 void info();
 void checkGruppiOrdinati();
 unsigned int contaCarte();
 MazzoMaster& getMaster(){return master;}
 GruppoDiCarte& getTerra(){return terra;}
 GruppoOrdinato& getg1(){return g1;}
 GruppoOrdinato& getg2(){return g2;}
 GruppoOrdinato& getg3(){return g3;}
 GruppoOrdinato& getg4(){return g4;}
 GruppoOrdinato& getg5(){return g5;}
 GruppoOrdinato& getg6(){return g6;}
 GruppoOrdinato& getg7(){return g7;}
 GruppoDiCarte& gets2(){return s2;}
 GruppoDiCarte& gets3(){return s3;}
 GruppoDiCarte& gets4(){return s4;}
 GruppoDiCarte& gets5(){return s5;}
 GruppoDiCarte& gets6(){return s6;}
 GruppoDiCarte& gets7(){return s7;}
 Pila& getp1(){return p1;}
 Pila& getp2(){return p2;}
 Pila& getp3(){return p3;}
 Pila& getp4(){return p4;}

private:
 MazzoMaster master;
 GruppoDiCarte terra;

 GruppoOrdinato g1;
 GruppoOrdinato g2;
 GruppoOrdinato g3;
 GruppoOrdinato g4;
 GruppoOrdinato g5;
 GruppoOrdinato g6;
 GruppoOrdinato g7;
 

 GruppoDiCarte s2;
 GruppoDiCarte s3;
 GruppoDiCarte s4;
 GruppoDiCarte s5;
 GruppoDiCarte s6;
 GruppoDiCarte s7;

 Pila p1;
 Pila p2;
 Pila p3;
 Pila p4;
};
Praticamente il tavolo contiene i puntatore a tutti i gruppi di carte che sono presenti sul tavolo di un giocatore che gioca al solitario. Potete confrontare i membri di questa classe con l’immagine postata sopra. Da notare i metodi info(), che stampa su console informazioni sulle carte che contiene ogni gruppo; checkGruppiOrdinati() che controlla se c’è qualche GruppoOrdinato che non contiene alcuna carta, e quindi deve essere scoperta una carta del GruppoDiCarte che sta proprio sotto lo stesso; contacarte() che conta il numero totale di carte, mi è stato utile in debug per evitare la “scomparsa” (o l’”apparizione”) di alcune carte. Ultima classe fondamentale: il Risolutore, ovvero colui che secondo una logica ben precisa, muove le carte per vincere il giuoco.
#pragma once
#include "costanti.h"
#include "Tavolo.h"
#include "Interfaccia.h"
class InterfacciaWin;


class Risolutore {
public:
 Risolutore(Tavolo* t,Interfaccia*);
 bool mossa();
 bool getVincita(){return vincita;}
private:
 Tavolo* tavolo;
 int chiamateMaster; //tiene conto del numero di volte che io prendo una carta dal master
 int chiamateCiclo; //tiene conto del numero di volte che io chiamo mossa()
 GruppoDiCarte* gruppi[11];

 bool gruppi_pile();
 bool terra_gruppi();
 bool gruppi_gruppi();
 bool vincita;
 InterfacciaWin* interfaccia;
};
Il risolutore per giocare ha bisogno dell’oggetto Tavolo, e di un’interfaccia grafica, per comunicare gli spostamenti di carte. Il cuore di questa classe è contenuta nel metodo mossa(), che chiama ripetutamente i tre metodi privati della stessa classe. Ok, abbiamo presentato la parte logica della classe, passiamo alla parte fisica, o meglio, alla parte visiva, quella che ci fa vedere che cosa sta facendo il computer: INTERFACCIA GRAFICA L’interfaccia grafica per ora è stata implementata per Win32, ma sto lavorando per la versione Linux (GTK+).
#pragma once
#include "costanti.h"
#include "Tavolo.h"
#include "Risolutore.h"
class Risolutore;

#ifdef WINDOWS_APP
#include 
class InterfacciaWin {
public:
 InterfacciaWin(unsigned int,Tavolo*);
 void aggiungiRisolutore();
 bool mossa();
 char** clickOnMaster();
 void transizione(int,int);
 void aggiungiFinestra(HWND);
private:
 Tavolo* tavolo;
 Risolutore* risolutore;
 bool setRisolutore;
 unsigned int weight;
 
    HWND hwnd;
};
#define Interfaccia InterfacciaWin
#endif

#ifdef LINUX_APP
class InterfacciaLinux {
public:
 InterfacciaLinux(unsigned int,Tavolo*);
 void aggiungiRisolutore();
 bool mossa();
 char** clickOnMaster();
 void transizione(int,int);
private:
 Tavolo* tavolo;
 Risolutore* risolutore;
 bool setRisolutore;
 unsigned int weight;
};
#define Interfaccia InterfacciaLinux
#endif
Come vedete, siccome è OS dipendente, ho provveduto a creare le direttive per il precompilatore in modo tale che se WINDOWS_APP è definito, compila per windows, altrimento se LINUX_APP è definito, compila per LINUX. Nota che PER ORA, la funzione della classe Interfaccia è abbastanza futile, visto che potrei anche farne a meno. Ma l’ho messa nel caso mi servisse più avanti, quando continuerò a sviluppare questo programma permettendo di fare giocare anche un umano (potete infatti notare il metodo aggiungiRisolutore() , che come dice il nome, attiva la modalità automatica, altrimenti se non c’è il risolutore, vuol dire che sarà un umano a giocare. Stay connected!

 Per finire…beh, ecco il codice sorgente per voi: sono dei fottutissimi file .h e .cpp, quindi compilabili con qualunque compilatore all’altezza. Nessun progetto, nessun vincolo di sistema, only raw material. P.S. ci sono solo 2 file che non ho analizzato: “WinMain.c” e “Windows.h”, che sono troppo specifici del sistema operativo, ma comunuque lo trovate nel codice sorgente ;)
Fine del post chilometrico.
Alberto

giovedì 23 febbraio 2012

Risolutore automatico del solitario Klondike

Ho creato un risolutore automatico del famoso solitario di Windows, Apple, Gnome, KDE e chi più ne ha più ne metta. Scritto in C++ per Win32.

La mia intenzione era di crere un risolutore in grado di vincere ogni partita, ma mi sbagliavo di grosso; infatti sappiamo che dipende tutto dalle carte, e possono presentarsi situazioni (frequenti) in cui è impossibile vincere, come in questo caso, per esempio:

 Le carte per "sbloccare" il gioco sono 2: il nove di picche e il quattro di fiori, che quarda caso si trovano sotto il dieci di cuori e il nove di quadri.

Comunque, il computer vince 1 volta su 5, e non sono poche!
Metto il file exe scaricabile per Windows, conm poche modifiche ne farò anche una versione per KDE. Le mosse le fa il computer ad ogni click del mouse (mi è stato utile per seguire le mosse del computer). Risoluzione consigliata: almeno 800x600 pixel.
Scusate le carte ritagliate male e prese da wikipedia XD. Nel prossimo post darò i dettagli tecnici di questo programma.
 Download
Alberto

giovedì 22 dicembre 2011

Windows: riprodurre un'onda sonora tramite l'interfaccia "waveOut"

Per continuare l'argomento intrapreso due settimane fa, ovvero Un semplice programma in C per leggere i file RIFF WAVE, passiamo adesso alla riproduzione di un'onda PCM. Intanto, a differenza del precedente post, dimentichiamoci della struttura del file WAV e di come aprirlo e suonarlo (rimanderò ancora più avanti l'argomento), ma preoccupiamoci di generare un'onda qualsiasi (per semplicità genereremo un'onda sinusoidale), e comunicare in qualche modo con la scheda audio per inviarle dei dati grezzi e quindi, sentire un suono dalle casse del proprio computer.

La piattaforma in cui faremo ciò è Windows, e useremo alcune sue API di basso livello per comunicare con la scheda audio (come al solito, rimanderò più avanti nel caso di Linux). Sebbene utilizzeremo le API di Win32, scordatevi comunque di vedere una finestra bella colorata con tanti tasti e caselle di input: quel che vedremo è una scarna finestra di console che si chiude non appena finisce di riprodurre il suono.

Il linguaggio è sempre il C.
Prima cosa essenziale: creiamoci una bella enumerazione che ci consente di manipolare le caratteristiche dell'onda senza andare a cercare fra il codice:

enum {
TIME = 5, // Durata della traccia (secondi)
CHANNELS = 1,         // Numero dei canali
SAMPLE_RATE = 44100, // Frequenza di campionamento (Hz)
BITS_PER_SAMPLE = 8, // Bits per campione
BLOCK_ALIGN = CHANNELS * BITS_PER_SAMPLE / 8, // Allineamento del blocco
BYTES_PER_SECOND  SAMPLE_RATE * BLOCK_ALIGN, // Frequenza di campionamento in Hz
SAMPLES = CHANNELS * SAMPLE_RATE * TIME, // Numero di campioni richiesti per la durata scelta
};
E' necessario che abbiate letto almeno il precedente post  per capire cosa sia per esempio la frequenza di campionamento o le altre cose (se non lo sapete già).

PASSO 1: APRIRE IL DEVICE

Cominciamo a scrivere la nostra main e a creare le variabili necessarie per aprire il device.



int main() {    
    HWAVEOUT hWaveOut; /* device handle */
    WAVEFORMATEX wfx; /* una struttura che riassume le caratteristiche dell'audio */
    MMRESULT result;/* per il valore di ritorno di waveOut */
    char* block;/* il puntatore ai nostri campioni */
    /*
     * inizializzo la struttura
     */
    wfx.nSamplesPerSec = SAMPLE_RATE;
    wfx.wBitsPerSample = BITS_PER_SAMPLE;
    wfx.nChannels = CHANNELS;
    wfx.nBlockAlign = BLOCK_ALIGN;
    wfx.nAvgBytesPerSec = BYTES_PER_SECOND;
    wfx.cbSize = 0; /* lunghezza delle informazioni extra */
    wfx.wFormatTag = WAVE_FORMAT_PCM  /* onda PCM; */

    if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0,0,CALLBACK_NULL) != MMSYSERR_NOERROR) {
        fprintf(stderr, "Impossibile aprire il device.\n");
        return 1;
    }
    else printf("Il device è stato aperto correttamente.\n");
    waveOutClose(hWaveOut); 
}


Questo programma per ora non fa niente, si limita ad aprire il device e a richiuderlo, ma comunque introduce molte caratteristiche interessanti. La funzione-chiave è waveOutOpen che accetta sei parametri:

LPHWAVEOUT phwo : in questo parametro passato per indirizzo, verrà "depositato" l'handle del dispositivo ((hWaveOut)
UINT uDeviceID : il  valore viene impostato a "WAVE_MAPPER" in modo che la funzione scelga il dispositivo adatto a riprodurre il formato specificato
LPWAVEFORMATEX pwfx : il puntatore alla struttura, ovvero &wfx
DWORD dwCallback : indirizzo dell'eventuale funzione CALLBACK, che in questo contesto non ci serve (più avanti si). Lo impostiamo a 0
DWORD dwInstance : specifica l'istanza del processo. Lo impostiamo a 0
DWORD fdwOpen : specifica alcuni comportamenti della funzione (callback, flags) che per ora ignoreremo. Lo impostiamo a CALLBACK_NULL(nessuna chiamata di callback)

Benissimo, adesso che abbiamo aperto il device, dobbiamo trovare un modo per  comunicare con lui. Ci arriveremo al passo 3, per ora creiamo l'onda.

PASSO 2 : CREIAMO UN'ONDA SINUSOIDALE



    int tone = 440;
    float volume = 10.0f;
    float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone;
    int i;
    if ((block = (char*)malloc(SAMPLES))==NULL)
       return 1;
    for (i=0 ; i < SAMPLES; i++ ) 
        block[i] = (char) ( cos( (float) i * oscillation ) * volume + 128.0f );

Questo pezzetto di codice alloca la memoria necessaria per memorizzare i campioni e la inizializza con i valori dell'onda.
Sappiamo che questa è l'equazione di un'onda sinusoidale di ampiezza A, pulsazione w e fase f. Possiamo dire lecitamente che l'ampiezza non è altro che il volume(intensità) dell'onda, la fase è nulla (non è altro che una traslazione orizzontale dell'onda  che non ci interessa) e la pulsazione è una funzione della frequenza secondo questa equazione:
dove f questa volta è la frequenza dell'onda. Non ho studiato teoria dei segnali, ma secondo il teorema del campionamento di Nyquist c'è una condizione: la frequenza di campionamento deve essere almeno il doppio della massima frequenza dello spettro dell'onda. Lo spettro acustico che un essere umano percepisce va da 16 Hz a 20000 Hz più o meno, quindi come frequenza di campionamento per memorizzare i campioni viene scelta 44100 Hz, che è diciamo uno standard, utilizzata negli ormai comuni CD Audio. Sempre secondo il teorema del campionamento,  l'equazione esatta dell'onda è questa:
dove n, un numero intero, rappresenta l'indice del campione (da 0 a SAMPLES nel codice sopra), Tc è il tempo che intercorre fra un campione e un altro, ossia l'inverso della frequenza di campionamento, fc è la frequenza di campionamento e f0 è la frequenza del segnale analogico.
In base a tutto ciò si spiega facilmente il codice sopra:  float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone; definisce la pulsazione costante mentre il ciclo for memorizza campione per campione tramite l'espressione block[i] = (char) ( cos( (float) i * oscillation ) * volume + 128.0f ); .

Da notare che il codice che utilizzo utilizza il puntatore a char* per memorizzare i campioni. char è un byte sostanzialmente, quindi se modifico BITS_PER_SAMPLE nell'enumerazione e metto 16, per esempio come valore, si avrà uno stravolgimento totale. Meglio non modificarlo! In C++ potremmo creare una funzione che memorizza i dati passandogli un tipo tramite un template, in modo da ovviare alla cosa. Ma non preoccupiamoci, almeno per ora.

PASSO 3 : INVIAMO I DATI AL DEVICE



void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
    WAVEHDR header;
    /*
     * Inizializza l'intestazione con la lunghezza dei dati
     * e naturalmente il puntatore.
     */
    ZeroMemory(&header, sizeof(WAVEHDR));
    header.dwBufferLength = size;
    header.lpData = block;
    /*
     * crea l'intestazione
     */
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * scrive i dati in maniera asincrona
     * (il controllo passerà subito al programma)
     */
    waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * Aspetta un po'
     */
    Sleep(500);
    while(waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
      Sleep(100);
}

Una bella funzione. Il codice è tutto commentato quindi non ci dovrebbero essere problemi. In soldoni: preparo l'header, cancello tutta la memoria occupata dall'header, inizializzo l'header, creo l'intestazione, scrivo i dati, faccio un ciclo fin quando la riproduzione finisce, e chiudo.
Quindi, in definitiva, ecco il codice completo:

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <math.h>
#include <stdint.h>
enum {

        TIME                = 5,         // Durata della traccia (secondi)
        FMT_SIZE            = 16,        // Dimensione dell'Fmt Chunk
        CHANNELS            = 1,         // Numero dei canali
        SAMPLE_RATE            = 44100,   // Frequenza di campionamento (Hz)
        BITS_PER_SAMPLE        = 8,      // Bits per campione

        BLOCK_ALIGN            = CHANNELS * BITS_PER_SAMPLE / 8,    // Allineamento del blocco
        BYTES_PER_SECOND    = SAMPLE_RATE * BLOCK_ALIGN,            // Frequenza di campionamento in Hz

        SAMPLES = CHANNELS * SAMPLE_RATE * TIME,  // Numero di campioni richiesti per la durata scelta
    };
    
    

void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
    WAVEHDR header;
    /*
     * Inizializza l'intestazione con la lunghezza dei dati
     * e naturalmente il puntatore.
     */
    ZeroMemory(&header, sizeof(WAVEHDR));
    header.dwBufferLength = size;
    header.lpData = block;
    /*
     * crea l'intestazione
     */
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * scrive i dati in maniera asincrona
     * (il controllo passerà subito al programma)
     */
    waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * Aspetta un po'
     */
    Sleep(500);
    while(waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
      Sleep(100);
}





int main() {
    HWAVEOUT hWaveOut; /* device handle */
    WAVEFORMATEX wfx; /* una struttura che riassume le caratteristiche dell'audio */
    MMRESULT result;/* per il valore di ritorno di waveOut */
    char* block;/* il puntatore ai nostri campioni */
    /*
     * inizializzo la struttura
     */
    wfx.nSamplesPerSec = SAMPLE_RATE;
    wfx.wBitsPerSample = BITS_PER_SAMPLE;
    wfx.nChannels = CHANNELS;
    wfx.nBlockAlign = BLOCK_ALIGN;
    wfx.nAvgBytesPerSec = BYTES_PER_SECOND;
    wfx.cbSize = 0; /* lunghezza delle informazioni extra */
    wfx.wFormatTag = WAVE_FORMAT_PCM  /* onda PCM; */

    if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0,0,CALLBACK_NULL) != MMSYSERR_NOERROR) {
        fprintf(stderr, "Impossibile aprire il device.\n");
        return 1;
    }
    else printf("Il device è stato aperto correttamente.\n");


    int tone = 440;  //"La" centrale
    float volume = 10.0f;
    float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone;
    int i;
    if ((block = (char*)malloc(SAMPLES))==NULL)
       return 1;
    for (i=0 ; i < SAMPLES; i++ ) 
        block[i] = (char) ( cos( (float) i * oscillation ) * volume + 128.0f );
    }
    writeAudioBlock(hWaveOut, block, SAMPLES); 
    waveOutClose(hWaveOut);
}


Non dimentichiamoci di linkare al compilatore la libreria winmm  altrimenti il linker ci da errore.

Per dubbi, problemi e chiarimenti commentate pure.
Alberto


Siti da cui ho preso informazioni: