Newsgroup: it.comp.lang.c
Titolo: Problema con programmazione modulare

 Ciao,
sto imparando il C e in particolare voglio impratichirmi di programmazione 
modulare. Così, ho ripreso un vecchio progetto (un programma per giocare a 
scacchi) che avevo sviluppato su un unico file per metterlo a posto e 
svilupparlo ulteriormente. Dato che un anno fa, quando ero agli inizi, il 
mio prof incoraggiava a usare variabili globali, sto avendo difficoltà a 
mettere a posto le cose senza passare alle funzioni un numero spropositato 
di parametri.
Ho definito la scacchiera sul file "gioco.h" come
typedef enum {VUOTO, PEDONE_GIOC_CORR, TORRE_GIOC_CORR, CAVALLO_GIOC_CORR, 
ALFIERE_GIOC_CORR, REGINA_GIOC_CORR, RE_GIOC_CORR,
                     PEDONE_AVV, TORRE_AVV, CAVALLO_AVV, ALFIERE_AVV, 
REGINA_AVV, RE_AVV } tipoPedina;
La scacchiera era globale, ma una volta portata nel main, anche se la passo 
come parametro, il compilatore mi segnala un errore nelle funzioni che usano 
la scacchiera e i cui prototipi non sono in "gioco.h" (uso gcc).
Ho provato a risolvere usando gli extern, ma non credo di aver capito come 
funzionano. Devo anteporre "extern" alla dichiarazione o quando uso quella 
variabile?
Altro problema è che alcune funzioni hanno fino a 10 parametri e il codice 
diventa brutto da vedere... voi le lascereste così come sono o dichiarareste 
qualche variabile come extern?
grazie,
 ciau
--
We've all heard that a million monkeys banging on a million typewriters
will eventually reproduce the entire works of Shakespeare. Now, thanks
to the Internet, we know this is not true.      -- Robert Silensky
~> 
http://scriverefantascienza.blogspot.com/
 

 qualche variabile come extern?
Difficile dirlo. Probabilmente dovresti crearti delle strutture dati che 
racchiudano gran parte di queste variabili. Ovviamente questo va fatto 
in modo coerente, quindi e' difficile dare suggerimenti. Dopodiche' 
passi i puntatori alle strutture.
Per quanto riguarda "extern", devi sapere la differenza tra 
dichiarazione e definizione. La keyword "extern" va nella dichiarazione. 
Serve per dichiarare che una variabile viene definita in un altro 
modulo. Nella definizione, invece, non ci va extern.
E.

 in un altro modulo. Nella definizione, invece, non ci va extern.
cioč, nel mio caso, dopo aver definito tipoPedina in "gioco.h", posso 
dichiarare la scacchiera nel main come "extern scacchiera[8][8]? E se ho un 
secondo header file "io.h" in cui ci sono prototipi di funzioni che usano il 
tipo tipoPedina come parametro, devo specificare "extern tipoPedina <nome 
param>"?
Ora che ci penso, perché č tanto importante non usare variabili globali? :)


 "extern scacchiera[8][8]"? E se
extern tipoPedina scacchiera[8][8], volevo dire :)


 Ora che ci penso, perché č tanto importante non usare variabili globali? :)
Piu' che altro, direi, per non avere conflitti con i nomi e per avere la 
modularita'. Ma non č una regola assoluta, a volte una variabile globale 
puo' risparmiarti molti mal di testa.
E.

 extern.
Scusa ma sono un po' confuso... in definitiva, perché, se definisco e 
dichiaro la scacchiera nel main ("tipoPedina scacchiera[8][8]", dove 
tipoPedina è un typedef contenuto in "gioco.h") il compilatore mi segnala un 
errore nelle funzioni che usano la scacchiera (anche se la passo come 
parametro) se i prototipi di quelle funzioni non sono nello stesso file in 
cui ho definito il tipo tipoPedina?
Se ho capito bene, una dichiarazione è un costrutto che associa degli 
attributi a una variabile/funzione. Ad esempio,
struct {
    ...
};
è una dichiarazione, ma non è una definizione perché non sto allocando 
memoria. Ma allora, qual è l'effetto dell'istruzione "extern int i"? Devo 
usare la keyword extern anche per ogni assegnazione successiva ad "i" 
all'interno della funzione che mi interessa? E' corretto, dopo la 
definizione nel main, scrivere in una funzione
int foo(args) {
    extern tipoPedina scacchiera[8][8];
    /* modifico scacchiera... */
    return 1;
}
questo se ho ben capito mi permette di usare la scacchiera senza passarla 
per parametro e senza renderla globale? Ma quando entro nella funzione il 
valore assegnato alla scacchiera non viene azzerato e una volta uscito dalla 
funzione ritorna al main modificata? Ed il suo scope è solo quello del main 
e della funzione? La distinzione tra definizione e dichiarazione mi sembra 
un po' sottile... se ho ben capito non ci sono definizioni senza 
dichiarazioni?
Credo che molto di quello che ho scritto sopra sia sbagliato, perché mi hai 
detto che "extern" va nel .h, ma dove nel .h? scrivo semplicemente "extern 
tipoPedina scacchiera[8][8]" insieme ai prototipi delle funzioni e questo mi 
assicura che nelle funzioni i cui prototipi si trovano lì posso modificare 
la scacchiera come se fosse passata per parametro? Perché ho provato, ma 
continuo ad avere errori...
grazie per la pazienza :)


 continuo ad avere errori...
Non solo le funzioni definite li' ma tutto il codice successivo alla 
dichiarazione puo' accedere la variabile, che e' globale. Inoltre "come 
se fosse passata per parametro" non mi pare il modo giusto di 
intenderlo. La "extern" e' semplicemente una variabile globale, statica, 
  definita altrove.
E.

 mio prof incoraggiava a usare variabili globali,
Bravo il professore: esattamente il contrario di ciò che si dovrebbe
insegnare. Devi invece cercare di fare a meno delle variabili globali e
passare i dati che servono alle funzioni. Ti eviterà un sacco di problemi.
Un metodo comodo per isolare dati e logica è quello di creare strutture e
metodi che lavorano su quelle strutture, esattamente come avviene nei
linguaggi object-oriented con le classi.
Per esempio, puoi scrivere (vado al volo, non ho testato
approfonditamente):
#include <stdio.h>
#include <stdlib.h>
typedef enum { pawn, rook, knight, bishop, queen, king } piece_t;
typedef enum { black, white } color_t;
/* player piece */
typedef struct {
    color_t color;
    piece_t piece;    
    int x, y;
    char *name;
} player_piece_t, *pplayer_piece_t;
pplayer_piece_t player_piece_new(color_t c, piece_t p, int x, int y, char
*name) {
   pplayer_piece_t instance = malloc(sizeof *instance);
   if (instance) {
      instance->color = c;
      instance->piece = p;
      instance->x = x;
      instance->y = y;           
      instance->name = name;
   }
   return instance;
}
void player_piece_delete(pplayer_piece_t self) {
    if (self) free(self);
}
/* chessboard */
typedef struct {
   pplayer_piece_t pieces[8][8];
} chessboard_t, *pchessboard_t;
void chessboard_initialize(pchessboard_t self) {
   int x, y; 
   for (x=0; x<8; x++)
       for (y=2; y<6; y++)
          self->pieces[x][y]=0;
   for (x=0; x<8; x++) {
     self->pieces[x][1] = player_piece_new(black, pawn, x, 1, "BP ");
     self->pieces[x][6] = player_piece_new(white, pawn, x, 1, "WP ");     
   }
   self->pieces[0][0] = player_piece_new(black, rook, 0, 0, "BQR");
   self->pieces[1][0] = player_piece_new(black, rook, 1, 0, "BQK");
   self->pieces[2][0] = player_piece_new(black, rook, 2, 0, "BQB");
   self->pieces[3][0] = player_piece_new(black, rook, 3, 0, "BQ ");      
   self->pieces[4][0] = player_piece_new(black, rook, 4, 0, "BK ");      
   self->pieces[5][0] = player_piece_new(black, rook, 5, 0, "BKB");      
   self->pieces[6][0] = player_piece_new(black, rook, 6, 0, "BKK");       
 
   self->pieces[7][0] = player_piece_new(black, rook, 7, 0, "BKR");
   self->pieces[0][7] = player_piece_new(black, rook, 0, 7, "WQR");
   self->pieces[1][7] = player_piece_new(black, rook, 1, 7, "WQK");
   self->pieces[2][7] = player_piece_new(black, rook, 2, 7, "WQB");
   self->pieces[3][7] = player_piece_new(black, rook, 3, 7, "WQ ");      
   self->pieces[4][7] = player_piece_new(black, rook, 4, 7, "WK ");      
   self->pieces[5][7] = player_piece_new(black, rook, 5, 7, "WKB");      
   self->pieces[6][7] = player_piece_new(black, rook, 6, 7, "WKK");       
 
   self->pieces[7][7] = player_piece_new(black, rook, 7, 7, "WKR");   
}     
void chessboard_finalize(pchessboard_t self) {
   int x, y;
   for (x=0; x<8; x++)
     for (y=0; y<8; y++)
       player_piece_delete(self->pieces[x][y]);
}    
pchessboard_t chessboard_new() {
    pchessboard_t instance = malloc(sizeof *instance);
    if (instance) 
      chessboard_initialize(instance);
    return instance;
}
void chessboard_draw(pchessboard_t self) {
     if (self) {
        int x, y;       
        printf("|-----|-----|-----|-----|-----|-----|-----|-----|n");
        for (y=0; y<8; y++) {
          printf("|     |     |     |     |     |     |     |     |n");   
 
          for (x=0; x<8; x++) {
              if (self->pieces[x][y]) 
                printf("| %s ", self->pieces[x][y]->name);
              else 
                printf("|     ");
          } 
          printf("|n|     |     |     |     |     |     |     |     |n"); 
 
          printf("|-----|-----|-----|-----|-----|-----|-----|-----|n");   
 
        }      
     }
}
void chessboard_delete(pchessboard_t self) {
     if (self) {
        chessboard_finalize(self);       
        free(self);              
     }
}
int main()
{
    pchessboard_t myChessboard = chessboard_new();
    chessboard_draw(myChessboard);
    chessboard_delete(myChessboard);
    return 0;
}
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 approfonditamente):
Sì, è un po' quello che ho fatto con molte delle funzioni che ho scritto: 
purtroppo però, quando le funzioni diventano complesse, inevitabilmente 
finisco per passare molti parametri. Ad esempio, la funzione che mi dice se 
c'è scacco matto (che chiamo quando un re è sotto scacco) deve:
- far fare al re tutte le mosse consentite e guardare se questo rimane sotto 
scacco per ognuna di queste;
- se la pedina che dà scacco è una sola (nota che non è sempre così):
    1. stabilire se posso mangiare quella pedina (faccio questo con un FOR 
su tutte le mie pedine e chiamando per ciascuna di esse la funzione 
"controllaSemantica()" (che mi dice se si tratta di una mossa valida) sulla 
mossa che porta la i-esima pedina dalla posizione corrente a quella della 
pedina che mi mette sotto scacco);
    2. se la pedina che dà scacco è un alfiere, torre o regina, stabilire se 
posso posizionare una pedina tra il re sotto scacco e quella pedina.
dunque devo passare almeno un flag che mi dica qual è il re sotto scacco, un 
tipoPedina che dica qual è la pedina che mette sotto scacco, e tutti gli 
argomenti sia di "eSottoScacco()" che di "controllaSemantica()", che sono 
molti... forse questo non è nemmeno l'esempio migliore, ma quel che voglio 
dire è che quando scrivo funzioni sempre più complesse che usano molte sub, 
inevitabilmente devo passare parametri su parametri... per questo mi 
chiedevo se l'uso di 'extern' sia considerato buona programmazione. Visto 
che mi hai sconsigliato di usare variabili globali, deduco che non usi 
nemmeno quella keyword e in una situazione come questa preferiresti passare 
molti parametri, eventualmente raggruppati semanticamente in struct (e.g. 
una struttura contenente 7-8 flags)? Comunque dichiarare la sola base dati 
(scacchiera) come globale non dovrebbe essere fonte di troppi errori, no?
grazie :)


 c'è scacco matto (che chiamo quando un re è sotto scacco) deve:
Ho l'impressione che stai complicando inutilmente il codice. Per il tuo 
programma e' naturale avere una struttura che rappresenta lo stato della 
scacchiera. Ovviamente lo stato comprende, oltre alla posizione di tutti 
i pezzi, informazioni quali: a chi tocca, e' stato mosso il re?, da 
quante mosse non viene mangiato un pezzo... etc.
Dunque la scacchiera e' l'unica informazione che serve a tutte le tue 
funzioni. Per esempio non e' necessario specificare quale re si sta 
considerando, perche' il re da considerare e' quello del giocatore che 
deve muovere. Inoltre non dovresti considerare il "checkIfMate" come una 
funzione particolare. Dovresti invece gia' avere una funzione che data 
una "scacchiera" ti restituisce (in sequenza) tutte le possibili mosse. 
Per sapere se c'e' scacco matto puoi usare questa funzione, controllando 
se per qualunque mossa del bianco esiste una mossa del nero che mangia 
il re bianco.
E.

(scacchiera) come globale non dovrebbe essere fonte di troppi errori, no?
Il punto è che non ti deve servire dichiarare globale alcunché.

 quante mosse non viene mangiato un pezzo... etc.
Formalmente, a mio parere, queste sono informazioni del controller del
gioco e non della scacchiera (che è solo una superficie su cui si muovono
i pezzi). Il controller del gioco dovrebbe mantenere tutte le
informazioni, appunto, relative al gioco.


 informazioni, appunto, relative al gioco.
quoto.
deve esserci la struttura schacchiera che contiene la posizione
attuale dei pezzi, su cui far andare le routine per decidere come
muovere, in piu' un altra struttura (o piu' strutture) che contiene
storico delle mosse, tempo di gioco, a chi tocca , ecc.
Ciao Jack


Io a suo tempo iniziai a scrivere un programma per giocare a scacchi. 
Non l'ho mai finito, ma qualche riflessione l'ho fatta... Le 
informazioni aggiuntive che ho indicato sono fondamentali per sapere 
quali sono le mosse possibili. Dunque e' naturale che vengano attaccate 
alla scacchiera. Che poi ci sia una struttura a parte che contenga solo 
la posizione dei pezzi, e' naturale (ma potrebbe essere semplicemente un 
array). Ad esempio se vuoi sapere quali mosse puo' fare il re, devi 
sapere se e' gia' stato mosso, e se quindi puo' arroccare (per la 
precisione ci sono anche altre condizioni da verificare, per l'arrocco).
Per dirla in altri termini, e' naturale, in un gioco, avere una 
struttura che contenga tutte le informazioni sulla posizione attuale. 
Ovvero le informazioni che sono NECESSARIE per sapere quali sono le 
mosse possibili.
E.

 alla scacchiera. 
Appunto, č ciň che č demandato al controller del gioco, non alla
scacchiera in sé.


 č sicuramente meglio se riscritto cosě:
interessante... credevo non fosse sintatticamente corretto dichiarare le 
variabili se non a inizio funzione :)
Proprio qualche giorno fa ho preso in biblioteca di ateneo questo libro 
sulla programmazione OO, mi sembra davvero ben fatto:
http://www.amazon.com/Introduction-Object-Oriented-Programming-Timothy-Budd/dp/0201760312
che spiega quello che chiama "responsability-driven design". Mi 
consigliereste altri libri sull'argomento (l'inglese non č un problema)? 
L'informatica non č il mio indirizzo di studi, ma tutto ciň che riguarda 
l'OOP mi interessa, avrň un esame di Java il prossimo trimestre e 
approfondire non guasta di certo... ma esami a parte, ho come l'impressione 
che una buona percentuale di chi studia ing. delle telecomunicazioni qui al 
Politecnico finisca per fare il programmatore ;)


 Non l'ho mai finito, ma qualche riflessione l'ho fatta... Le
OT: qualche suggerimento di massima per un algoritmo rudimentale che calcoli 
la mossa migliore del computer?
Di certo non posso ricorrere per tutte le possibili mosse fino allo scacco 
matto, per cui pensavo di fermarmi a una profondità assegnata 
(presumibilmente 5-6 mosse). In pratica, per ogni mossa possibile del 
computer calcolo la risposta più forte da parte dell'"umano", dove per 
sapere se una mossa è forte o no conto il bilancio delle pedine mangiate 
(pedoni=1, alfieri e cavalli=3, torri=5, regina=9, re=+Inf), e così via per 
5-6 volte, dopodiché scelgo la prima mossa della sequenza che dà il bilancio 
migliore per il computer. Ovviamente è dispendioso, ma non mi viene in mente 
niente di meglio del bruteforce...


 niente di meglio del bruteforce...
In effetti l'idea è grossomodo giusta. Bisogna dividere il problema in 
due parti: (1) la valutazione statica, (2) la ricerca nell'albero delle 
possibili mosse.
Per (1) si intende una funzione che data la posizione corrente ti da' 
una stima di "quanto sta bene" il bianco e "quanto sta bene" il nero. 
Questa stima puo' essere fatta in molti modi diversi piu' o meno 
grossolani. La cosa piu' semplice e' dare un valore ad ogni pezzo (1 per 
la pedina, 3 per l'alfiere...) e tornare la somma. Inoltre se la partita 
e' vinta (o persa) bisogna tornare il valore massimo possibile (minimo).
Una volta costruita la (1), si puo' usare la (2) per esplorare una parte 
dell'albero delle mosse. Invece che scendere ad una profondita' 
prefissata conviene "sfoltire" l'albero escludendo, in base alla 
valutazione statica, le mosse non convenienti. Ovvero conviene 
approfondire dapprima le mosse che danno una buona valutazione statica, 
e approfodire successivamente (e se c'e' tempo) le altre.
Per esempio se dopo una mossa del bianco il nero puo' mangiare il re, la 
mossa del bianco va' scartata senza considerare le altre possibili mosse 
successive del nero. Un altro esempio, se il bianco mette la regina 
sotto scacco, la prima mossa del nero che devo prendere in 
considerazione e' quella che mangia la regina (cosi' farebbe anche un 
giocatore di scacchi umano!). In effetti il valutatore statico (anche 
quello piu' stupido) valuta buona una tale mossa del nero (perchè la 
regina vale 5 punti).
Ovviamente la valutazione va fatta con un metodo di min-max. Una mossa 
del bianco e' buona se ogni mossa successiva del nero e' cattiva. E 
viceversa.
E.

Politecnico finisca per fare il programmatore ;)
Che cosa triste :-(

 Che cosa triste :-(
Per me non sarebbe l'ideale, ma non mi dispiacerebbe, mi piace 
programmare... e -- ma posso benissimo sbagliarmi dato che la mia esperienza 
lavorativa nell'ICT si limita a un tirocinio l'estate scorsa ;) -- penso che 
un datore di lavoro guardi più alla persona con l'attitudine (e media esami) 
giusta che a quella con la laurea giusta, per cui non credo che lo 
svantaggio rispetto a un ing. informatico sarebbe incolmabile.
In ogni caso, degli ing. delle telecom che conosco che lavorano in Italia, 
quelli davvero bravi fanno ricerca in università o prendono il dottorato per 
poi insegnare (e sono ben pagati), chi non può o non vuole e ha voti 
comunque alti trova un buon lavoro in Telecom o Motorola, e una buona fetta 
di chi si laurea con voti medi fa mesi su mesi di tirocini sottopagati in 
aziende medio-piccole, oppure si dà alla programmazione... per questo sto 
tenendo la media alta per fare una 'doppia laurea' in Svezia e sperabilmente 
trovar lavoro lì, dove a detta di molti sia opportunità di carriera che 
stipendi per chi inizia nel settore telecom sono decisamente migliori.
Per caso conoscete ing. delle telecom in Italia che facciano un lavoro che 
abbia a che fare con quello che hanno studiato? Sarei felice di essere 
contraddetto ;).


mi hai fatto vedere prima, o era solo un esempio un po' eccessivo? ;)
Quell'esempio non è affatto eccessivo. Tieni presente che in C++ è
possibile dichiarare l'indice dentro il for:
for (int i=0; i<x; i++) { ...