Simulazione di finali di un campionato di calcio
Il procedimento di questo programmino, creato in occasione degli ultimi Mondiali sudafricani, ha origini molto lontane. Inizialmente l’ho sviluppato in un modello Excel dotato di macro VBA, più avanti ho sfruttato un controllo DataGridView inserito in una Window Form. In entrambi i casi ho ripreso il ben noto schema piramidale inserito nelle celle dell’uno o dell’altro.
Esaminando infine la griglia (Grid) di una finestra WPF mi sono infine detto che la si può direttamente utilizzare, mutatis mutandis, in modo più diretto e ottenendo una creatura meno pesante.
L’idea base si può grossomodo schematizzare con la tabella seguente:
|
|
|
Valori
|
|
|
|
|
|
|
|
Prima squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 1
|
|
|
|
|
|
Seconda squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Finalista 1
|
|
|
|
Terza squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 2
|
|
|
|
|
|
Quarta squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Campione
|
|
Quinta squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 3
|
|
|
|
|
|
Sesta squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Finalista 2
|
|
|
|
Settima squadra
|
|
6
|
|
|
|
|
|
|
|
|
|
|
|
Semifinalista 4
|
|
|
|
|
|
Ottava squadra
|
|
6
|
|
|
|
|
|
|
Nota. Per semplicità e... pigrizia mi sono limitato ai quarti di finale (cosa tra l’altro tipica di altre finali, per esempio di atletica), ma chi ne ha voglia può ampliare il discorso agli ottavi, aggiungendo sulla sinistra una colonna con 16 finaliste.
Dalla tabella precedente non è arduo evincere di tradurre l’idea in una Window WPF al centro della quale si abbia un container Grid (ma come avete fatto a indovinare?), nelle cui celle si trovano delle TextBox, con aggiunta di uno o più pulsanti di comando.
Chi fosse interessato al programmino per vedere da vicino l'effetto che fa può chiedermelo direttamente tramite email: giannigiac@tin.it .
Al lancio si presenta sostanzialmente conforme alla tabella precedente, con aggiunta di un pulsante in alto.
Si noterà subito che i nomi delle 8 squadre in ballo sono generici, mentre i valori, che esprimono valutazioni (si suggeriscono voti da 1 a 10) su ciascuna di esse, sono allineati alla sufficienza per tutti.
Nota Non ho previsto, per brevità, nessun controllo sui valori che debbono essere rigorosamente numerici (ma utenti così idioti ce ne sono ancora in giro?).
Va da sé che prima di avviare la Simulazione col pulsante così etichettato, occorre sostituire “Prima squadra”, “Seconda squadra” ecc. coi nomi reali, idem per i voti uniformati sul 6. Dopo di che l’algoritmo introduce un fattore di (pseudo)casualità per tener conto della aleatorietà tipica delle competizioni calcistiche (la palla è tonda, come si sa). Tale procedimento verrà descritto insieme al codice, anticipo solo che esso nelle TextBox da “Semifinalista1” a “Semifinalista4” inserisce i rispettivi vincitori fra Prima e seconda squadra, Terza e quarta squadra ecc. Cosa accade nel resto della piramide degradante segue la stessa logica, ergo la faccenda si commenta da sola.
Il codice XAML
La struttura dei contenitori facenti parte della nostra Window1 (o, in WPF 4.0 , MainWindow) prevede un DockPanel che al suo interno incapsula due Grid, la prima di una sola riga e tre colonne, potendo così ospitare fino a tre pulsanti, di cui uno soltanto viene qui utilizzato (chi vuole ne può aggiungere altri) mentre la seconda corrisponde alla tabella su esposta.
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Finali Campionato mondiale" Height="520" Width="500">
<DockPanel LastChildFill="True" Background="LightGreen">
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="95.743*" />
<ColumnDefinition Width="95.743*" />
<ColumnDefinition Width="95.743*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20*" />
</Grid.RowDefinitions>
<Button Click="AvviaSimulaz" Grid.Column="1" Margin="10" Height="30">
Simulazione
</Button>
</Grid>
<Grid Name="GrigliaSquadre" Width="420.084" Height="400">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="78.179*" />
<ColumnDefinition Width="2.858*" />
<ColumnDefinition Width="95.743*" />
<ColumnDefinition Width="2.858*" />
<ColumnDefinition Width="95.743*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="30*" />
<RowDefinition Height="62*" />
<RowDefinition Height="145.5*" />
</Grid.RowDefinitions>
<Label Content ="Valori:" Foreground ="Yellow" Background="Black" VerticalContentAlignment="Center" FontSize="14" Width="49.976" HorizontalAlignment="Right" />
<TextBox Name="Squad1" Grid.Row="1" Grid.Column="0" Background="LightBlue">
Prima squadra
</TextBox>
<Label Grid.Row="1" Grid.Column="1" Foreground ="DarkRed" FontWeight="Bold" Grid.ColumnSpan="4" Margin="32.657,0,2.648,0">SEMIFINALI</Label>
<Label Grid.Row="3" Grid.Column="4" Foreground ="DarkRed" FontWeight="Bold" Grid.ColumnSpan="2" Margin="2.848,0,0,0">FINALI</Label>
<Label Grid.Row="6" Grid.Column="5" Foreground ="DarkRed" FontWeight="Bold" Grid.ColumnSpan="2" Margin="0,25.5,2.848,2.003" Grid.RowSpan="2">CAMPIONE</Label>
<TextBox Name="Va11" Text="6" FontSize="9" Grid.Row="1" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad2" Grid.Row="3" Grid.Column="0" Background="LightBlue">
Seconda squadra
</TextBox>
<TextBox Name="Va12" Text="6" FontSize="9" Grid.Row="3" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad3" Grid.Row="5" Grid.Column="0" Background="LightBlue">
Terza squadra
</TextBox>
<TextBox Name="Va13" Text="6" FontSize="9" Grid.Row="5" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad4" Grid.Row="7" Grid.Column="0" Background="LightBlue">
Quarta squadra
</TextBox>
<TextBox Name="Va14" Text="6" FontSize="9" Grid.Row="7" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad5" Grid.Row="9" Grid.Column="0" Background="LightBlue">
Quinta squadra
</TextBox>
<TextBox Name="Va15" Text="6" FontSize="9" Grid.Row="9" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center"/>
<TextBox Name="Squad6" Grid.Row="11" Background="LightBlue">
Sesta squadra
</TextBox>
<TextBox Name="Val6" Text="6" FontSize="9" Grid.Row="11" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad7" Grid.Row="13" Grid.Column="0" Background="LightBlue">
Settima squadra
</TextBox>
<TextBox Name="Va17" Text="6" FontSize="9" Grid.Row="13" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center" />
<TextBox Name="Squad8" Grid.Row="15" Grid.Column="0" Background="LightBlue">
Ottava squadra
</TextBox>
<TextBox Name="Va18" Text="6" FontSize="9" Grid.Row="15" BorderThickness="4"
HorizontalAlignment="Right" Width="32.756" TextAlignment="Center"/>
<TextBox Name="Semfin1" Grid.Row="2" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 1
</TextBox>
<TextBox Name="Semfin2" Grid.Row="6" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 2
</TextBox>
<TextBox Name="Semfin3" Grid.Row="10" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 3
</TextBox>
<TextBox Name="Semfin4" Grid.Row="14" Grid.Column="2" Background="Aquamarine" Grid.ColumnSpan="2" Margin="0,0,2.648,0">
Semifinalista 4
</TextBox>
<TextBox Name="Fin1" Grid.Row="4" Grid.Column="4" Background="LightBlue">
Finalista 1
</TextBox>
<TextBox Name="Fin2" Grid.Row="12" Grid.Column="4" Background="LightBlue">
Finalista 2
</TextBox>
<TextBox Name="Campione" Grid.Row="8" Grid.Column="6" Foreground="White" Background="DarkBlue" >
Campione
</TextBox>
</Grid>
</DockPanel>
</Window>
L’analisi dello schema XAML è affidato al commento autonomo del lettore, faccio solo notare i nomi parlanti affibbiati alle varie TextBox: Squad1, Squad2, ..., Squad8 (le 8 squadre ovviamente), Val1, Val2, ..., Val8 (le rispettive valutazioni) e via di seguito con Semfin1, ..., Semfin4 poi Fin1 e Fin2 fino al Campione.
Tutto il codice VB, routine per routine
Premesso che al Button etichettato Simulazione, anonimo perché oltretutto è solo soletto, viene associata con Click="AvviaSimulaz" l’omonima routine, secondo una sintassi più consona all’ambiente WPF, qui sotto riporto l’intero listato.
Class Window1
Dim SquadreValori As New Dictionary(Of String, Double)
Dim SquadValCaricate = False
Sub CaricaSquadreValori()
SquadreValori.Add(Squad1.Text, CType(Va11.Text, Double))
SquadreValori.Add(Squad2.Text, CType(Va12.Text, Double))
SquadreValori.Add(Squad3.Text, CType(Va13.Text, Double))
SquadreValori.Add(Squad4.Text, CType(Va14.Text, Double))
SquadreValori.Add(Squad5.Text, CType(Va15.Text, Double))
SquadreValori.Add(Squad6.Text, CType(Val6.Text, Double))
SquadreValori.Add(Squad7.Text, CType(Va17.Text, Double))
SquadreValori.Add(Squad8.Text, CType(Va18.Text, Double))
SquadValCaricate = True
End Sub
Function Vincitore(ByVal Rivale1 As String, ByVal Rivale2 As String) As String
If SquadreValori(Rivale1) * (1 + 1.5 * Rnd()) > _
SquadreValori(Rivale2) * (1 + 1.5 * Rnd()) Then
Return Rivale1
Else
Return Rivale2
End If
End Function
Sub AggiornaValori()
SquadValCaricate = False
SquadreValori.Clear()
CaricaSquadreValori()
End Sub
Private Sub AvviaSimulaz(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
If Not SquadValCaricate Then
CaricaSquadreValori()
End If
AggiornaValori()
' Ottavi (o come diavolo si chiamano)
Randomize()
Semfin1.Text = Vincitore(Squad1.Text, Squad2.Text)
Semfin2.Text = Vincitore(Squad3.Text, Squad4.Text)
Semfin3.Text = Vincitore(Squad5.Text, Squad6.Text)
Semfin4.Text = Vincitore(Squad7.Text, Squad8.Text)
' Semifinali
Fin1.Text = Vincitore(Semfin1.Text, Semfin2.Text)
Fin2.Text = Vincitore(Semfin3.Text, Semfin4.Text)
' Finali
Campione.Text = Vincitore(Fin1.Text, Fin2.Text)
End Sub
End Class
Commenti tacitiani.
Una serie di SquadreValori.Add(Squad1.Text, CType(Va11.Text, Double)) e seguenti carica nella Dictionary SquadreValori, definita a livello Dichiarazioni, i testi delle squadre reali – “Brasile”, “Germania”... - che l’utente avrà la bontà d’inserire al posto dei generici “Prima squadra”, “Seconda squadra, ... in veste di chiave, mentre i corrispondenti valori sono attinti dai vari Val1.Text, Val2.Text, ... previo cast nel formato Double anche a beneficio dei pignoli che volessero assegnare voti tipo 8,45.
Il clou si ha nella funzione Vincitore, di cui riporto nuovamente le istruzioni qui sotto per comodità di chi legge:
If SquadreValori(Rivale1) * (1 + 1.5 * Rnd()) > _
SquadreValori(Rivale2) * (1 + 1.5 * Rnd()) Then
Return Rivale1
Else
Return Rivale2
End If
Nota. Chi vuole può sostituire la pur vetusta ma sintetica Rnd con la più moderna System.Random.Next...
L’algoritmo pesca dalla dictionary SquadreValori la valutazione pertinente alle chiavi Rivale1 e Rivale2 cui si aggiunge un pizzico di aleatorietà variabile da 0 a 1,5. Dal confronto emerge come Vincitore una delle due rivali.
Compreso questo meccanismo, il mestiere della routine d’evento AvviaSimulaz appare chiaro, pertanto non voglio tediare la gente con ulteriori chiose.
Una variante più stringata (ma cervellotica)
Potrebbe piovere qualche critica da chi ama la compattezza, per il fatto che tutte le caselle di testo vengono chiamate in gioco per nome, ad una ad una. Ma tale scelta a mio parere era inevitabile, dato il carattere sparso delle caselle di testo nella nostra griglia. Comunque a scopo di dibattito ecco una routine avente lo scopo di esplorare e segnalare con delle MessageBox tutte e sole le TextBox delle prime 8 squadre affiancandole ai rispettivi valori:
Sub EsploraSquadEtValori
Static swPrimogiro As Boolean = False
Dim DepCtrl
Dim DepNomeCtrl = ""
For Each Ctrl In GrigliaSquadre.Children
If TypeOf Ctrl Is TextBox Then
Dim NomeCtrl = Ctrl.Name.ToString
If NomeCtrl <> "" And Ctrl.Getvalue(Grid.ColumnProperty) = 0 Then
If Not swPrimogiro Then
DepCtrl = Ctrl
DepNomeCtrl = NomeCtrl
Else
MessageBox.Show(DepNomeCtrl & " - " & NomeCtrl)
End If
swPrimogiro = Not swPrimogiro
End If
End If
Next
End Sub
Il punto è che GrigliaSquadre.Children fornisce tutti i controlli che tale griglia incorpora, di qui la necessità di discriminare quelli di tipo TextBox (TipeOf Ctrl Is TextBox) nonché quelli che giacciono in colonna 0.
Il resto del procedimento si basa sul fatto che, si badi bene, pure i valori sono in colonna 0 (definizione implicita: rivedere tali tag, notando in esse l’assenza dell’attributo Grid.Column) e, conseguentemente, l’esplorazione alterna Squad1 con Val1, Squad2 con Val2, ..., Squad8 con Val8. Infine il gioco dello switch di primo giro swPrimogiro in combutta coi depositi DepCtrl e DepNomeCtrl è un classico su cui non mi dilungo.
Altre possibilità
Ai più esperti affido il compitino di sostituire MessageBox.Show(...) con l’aggiornamento della Dictionary SquadreValori. Ma ne vale la pena?
Piuttosto, chi ne avesse voglia e tempo potrebbe cimentarsi con modifiche e/o ampliamenti come il controllo dei valori (con rigetto dei non numerici) e la finale per il terzo e quarto posto.
Un’ulteriore possibilità (che ho implementato temporibus illis in Excel) potrebbe consistere nell’iterazione in un numero N sufficientemente elevato di cicli For i = 1 To N della Sub AvviaSimulaz aggiornando ad ogni giro un array Vett(7, 1) recante le varie squadre nella prima colonna e, nella seconda, il conteggio delle vincite. In tal modo, al termine, la squadra che avrà vinto più campionati simulati potrebbe considerarsi come il vincitore più probabile.
Gianni Giaccaglini