
Voglio presentarvi un piccolo esercizio, utile per tutti coloro che stanno entrando ora nel mondo della programmazione procedurale, quella che molti studenti trovano nelle scuole superiori. Io ve lo presento come esercizio di C# (perchè al momento non ho un compilatore C su Windows), ma è semplicissimo adattarlo facilmente anche al linguaggio C tradizionale, ma soprattutto come modello di studio per problemi complessi che possono risolversi con qualche trucchetto matematico.
La morra cinese è quel semplice gioco tra due giocatori, dove ognuno dei due sceglie 1 tra i tre elementi: sasso, carta e forbici.
Le regole del gioco sono 3 e sono semplicissime:
- la carta vince sul sasso
- il sasso vince sulle forbici
- le forbici vincono sulla carta
Il gioco è estremamente semplice, supponiamo pertanto di definire dei valori numerici ai tre elementi, che richiederemo in input all’utente:
carta = 1 sasso = 2 forbici = 3
Dato questo elemento, possiamo fornire subito una soluzione basata unicamente sul costrutto if:
int g1;
int g2;
Console.WriteLine("1 CARTA, 2 SASSO, 3 FORBICI");
g1 = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("1 CARTA, 2 SASSO, 3 FORBICI");
g2 = Convert.ToInt32(Console.ReadLine());
if (g1 == g2)
Console.WriteLine("Pari");
else
{
if (g1 == 1 && g2 == 2 || g1 == 2 && g2 == 3 || g1 == 3 && g2 == 1)
Console.WriteLine("Vince giocatore 1");
else
Console.WriteLine("Vince giocatore 2");
}
“Facile!”. Sì, però secondo me c’è un modo più elegante di risolvere il problema. Ho ragionato un pò, e sono arrivato a queste conclusioni:
- Devo sempre essere in grado di poter sapere chi tra 2 giocatori vince, 1 se è il primo giocatore, 2 se è il secondo, 0 se sono in parità.
- La carta vince sempre sul sasso, il sasso vince sempre sulle forbici e le forbici sempre sulla carta. Non esiste una regola matematica valida per il gioco, perchè risulta che carta>sasso, sasso>forbici e forbici>carta… impossibile!
Però ho ragionato un altro pò, ed ecco cosa ho concluso:
carta+sasso -> val = 1 - 2 = -1 val<0 && abs(val)==1 -> vince il 1° carta+forbici -> val = 1 - 3 = -2 val<0 && abs(val)==2 -> vince il 2° sasso+forbici -> val = 2 - 3 = -1 val<0 && abs(val)==1 -> vince il 1° forbici+sasso -> val = 3 - 2 = 1 val>0 && abs(val)==1 -> vince il 2° forbici+carta -> val = 3 - 1 = 2 val>0 && abs(val)==2 -> vince il 1° sasso+carta -> val = 2 - 1 = 1 val>0 && abs(val)==1 -> vince il 2°
Posso perciò affermare che se il valore della differenza tra i numeri dei due elementi è minore di zero, allora il giocatore vincente è semplicemente il valore assoluto del risultato di tale differenza, mentre nel caso in cui la differenza sia maggiore di zero, allora il giocatore vincente sarà il primo se il risultato é 2 e il secondo se il risultato é 1. La partità è comunque in parità se il risultato é uguale a zero. Tradotto in codice viene:
int ris = g1 - g2;
if (ris < 0)
Console.WriteLine("Vince giocatore {0}", Math.Abs(ris));
else
switch (Math.Abs(ris))
{
case 0:
Console.WriteLine("Partita in parità");
break;
case 1:
Console.WriteLine("Vince giocatore 2");
break;
case 2:
Console.WriteLine("Vince giocatore 1");
break;
}
Ma visto che così non mi piace ancora più di tanto, ho deciso di dividere la responsabilità di calcolare il risultato da quella di scrivere a video, ecco perciò che ho creato una semplice funzione, Controlla(int g1, int g2), che prende in input gli elementi giocati dai due giocatori e restituisce il risultato al chiamante:
static int Controlla(int g1, int g2)
{
int ris = g1 - g2;
if (ris < 0)
return Math.Abs(ris);
else
switch (Math.Abs(ris))
{
case 1:
return 2;
case 2:
return 1;
default:
return 0;
}
}
TheStinger, che ringrazio, mi ha fatto giustamente notare che al posto dello switch la cosa più appropriata sarebbe:
static int Controlla(int g1, int g2)
{
int ris = g1 - g2;
if (ris == 0)
return 0;
if (ris < 0)
return Math.Abs(ris);
else
return 3 - ris;
}
Mentre il chiamante, il Main, si occupa della gestione dei dati:
static void Main(string[] args)
{
int g1 = -1;
int g2 = -1;
int uscita = -1;
do
{
Console.WriteLine("1 CARTA, 2 SASSO, 3 FORBICI");
g1 = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("1 CARTA, 2 SASSO, 3 FORBICI");
g2 = Convert.ToInt32(Console.ReadLine());
int controllo = Controlla(g1, g2);
if (controllo == 0)
Console.WriteLine("Partita in parità");
else
Console.WriteLine("Vince giocatore {0}", controllo);
Console.Write("Uscire? 1 No, 0 Si");
uscita = Convert.ToInt32(Console.ReadLine());
}
while (uscita != 0);
}
Molto più pulito rispetto a prima. Questo articolo è ovviamente rivolto a tutti quegli studenti delle superiori ai quali spesso viene posto questo quesito come compito per casa dai professori di informatica. Il concetto che voglio far capire è che spesso dietro agli algoritmi si trova uno studio, buttare giù codice senza averci ragionato prima spesso può solo aiutare a perdere il punto della situazione. Se volete chiedere chiarimenti, sempre a disposizione. A presto!
Mi chiamo Denis Billi, ho 25 anni e sono della provincia di Ravenna. Mi sono laureato nell'estate del 2008 presso la facoltà di Ingegneria Informatica dell'università di Bologna e attualmente sto seguendo i corsi per la Laurea Specialistica in Ingegneria Informatica sempre all'università di Bologna.
Carino come sistema :)
Grazie! Più che altro questo è solo un esercizio, lo studio in sè è pensato per dare l’idea dello studio che sta dietro agli algoritmi, senza doversi buttare subito sul codice, anche se alla prima impressione può sembrare la cosa più facile…
Ho visto il tuo blog, alcuni articoli sono molto interessanti. Se vuoi possiamo fare scambio link. Fammi sapere.
A presto!
Se devo dirti la verità a me piaceva quasi di più la prima soluzione…..andare a cercare una regola matematica per ogni cosa mi sembra un pò assurdo…..
E comunque la regola potrebbe essere sfruttato un pò meglio tipo:
int ris = g1 – g2;
if (ris == 0)
//parità
else if (ris < 0)
// vince il giocatore {Math.abs(ris)}
else
// vince il giocatore {3-ris};
TheStinger hai ragione, infatti come ho scritto nell’articolo il concetto che voglio far capire è che spesso dietro agli algoritmi si trova uno studio, buttare giù codice senza averci ragionato prima spesso può solo aiutare a perdere il punto della situazione. Il primo codice era solo per dare un’idea di un codice buttato giù direttamente.. Grazie per l’appunto alla soluzione comunque, effettivamente non ci avevo pensato!
PS: vai tranquillo per il doppio
Torna presto, è giusto confrontare diversi metodi di risoluzione!
Era meglio usare le parole al posto dei numeri, usando lo switch
Come si dice, Ogni scelta progettuale è a discrezione del progettista. C’è da dire che uno studente che studia C non lo può fare perchè non è possibile usare lo switch se non con degli interi e dei caratteri, con C# invece sì.
I più puristi potrebbero inoltre obiettare la tua scelta dicendo che uno switch con confronto di stringhe è molto più lento che delle semplici operazioni elementari, anche se oggi ha ben poco valore.
Posso aggiungere che in questo caso i confronti sono solo 6, ma nel caso di 100 confronti la questione si fa molto dura con lo switch…