Lotto.cs 12.5 KB
Newer Older
M1888's avatar
M1888 committed
1
using System;
M1888's avatar
M1888 committed
2
using System.Collections.Concurrent;
M1888's avatar
M1888 committed
3
using System.Collections.Generic;
M1888's avatar
M1888 committed
4 5
using System.Collections.ObjectModel;
using System.ComponentModel;
M1888's avatar
M1888 committed
6
using System.Diagnostics;
M1888's avatar
M1888 committed
7
using System.Linq;
M1888's avatar
M1888 committed
8
using System.Runtime.CompilerServices;
M1888's avatar
M1888 committed
9
using System.Text;
M1888's avatar
M1888 committed
10
using System.Threading;
M1888's avatar
M1888 committed
11 12
using System.Threading.Tasks;

M1888's avatar
M1888 committed
13

M1888's avatar
M1888 committed
14 15
namespace Lottokone.Model
{
M1888's avatar
M1888 committed
16
    public class Lotto : INotifyPropertyChanged
M1888's avatar
M1888 committed
17
    {
M1888's avatar
M1888 committed
18 19 20 21
        // Mitataan kunkin kierroksen kesto, jotta
        // osataan näyttää nättiä statistiikkaa.
        private Stopwatch stopwatch;

M1888's avatar
M1888 committed
22 23
        // Neljä eri listaa eri voittotasoille:
        // 5, 6, 6+1 ja 7 oikein.
M1888's avatar
M1888 committed
24 25 26 27 28 29 30 31 32 33 34 35
        // Näihin laitetaan joka kierroksella voittaneet pelaajat,
        // ja sitten jaetaan niistä tarkistuksen jälkeen voitot.
        // 
        // Pienempiä voittotasoja ei tarvitse listata, koska niiden voittosumma
        // ei riipu voittajien määristä vaan on aina sama. Ne voidaan hoitaa helpommin.
        //
        // Olisin mielummin toteuttanut tämän indeksoituna listana:
        // eli oikein[5] takaa löytyisi lista viisi oikein pelaajista, jne..
        // Parallelismin kannalta tämä oli kuitenkin hankalaa, ja valmiiden
        // thread-safe ConcurrentBagien käyttö on järkevämpää, vaikkakin rumaa.
        private ConcurrentBag<Pelaaja> oikein5;
        private ConcurrentBag<Pelaaja> oikein6;
M1888's avatar
M1888 committed
36
        private ConcurrentBag<Pelaaja> oikein6_1;
M1888's avatar
M1888 committed
37 38
        private ConcurrentBag<Pelaaja> oikein7;

M1888's avatar
M1888 committed
39 40 41 42 43 44 45
        private short viikko;
        public short Viikko
        {
            get
            {
                return viikko;
            }
M1888's avatar
M1888 committed
46 47 48
            set
            {
                viikko = value;
M1888's avatar
M1888 committed
49
                if (viikko > 52)
M1888's avatar
M1888 committed
50 51 52 53 54 55
                {
                    viikko = 1;
                    Vuosi++;
                }
                RaisePropertyChanged();
            }
M1888's avatar
M1888 committed
56 57 58 59 60 61 62 63 64
        }

        private short vuosi;
        public short Vuosi
        {
            get
            {
                return vuosi;
            }
M1888's avatar
M1888 committed
65 66 67 68 69
            set
            {
                vuosi = value;
                RaisePropertyChanged();
            }
M1888's avatar
M1888 committed
70 71
        }

M1888's avatar
M1888 committed
72 73 74 75 76 77 78 79
        public int Kierros
        {
            get
            {
                return ((Vuosi - 1) * 52) + Viikko;
            }
        }

M1888's avatar
M1888 committed
80 81
        public ConcurrentBag<Pelaaja> Pelaajat { get; set; }

M1888's avatar
M1888 committed
82 83 84 85
        // Tässä pidetään joka kierroksella jaettujen eri voittoluokkien
        // palkkioiden määrät.
        private int[] voitot;
        public int[] Voitot
M1888's avatar
M1888 committed
86 87 88
        {
            get
            {
M1888's avatar
M1888 committed
89
                return voitot;
M1888's avatar
M1888 committed
90 91 92
            }
        }

M1888's avatar
M1888 committed
93 94
        private int potti;
        public int Potti
M1888's avatar
M1888 committed
95 96 97 98 99
        {
            get
            {
                return potti;
            }
M1888's avatar
M1888 committed
100 101 102 103 104
            set
            {
                potti = value;
                RaisePropertyChanged();
            }
M1888's avatar
M1888 committed
105 106
        }

M1888's avatar
M1888 committed
107
        public event PropertyChangedEventHandler PropertyChanged;
M1888's avatar
M1888 committed
108

M1888's avatar
M1888 committed
109 110 111 112 113 114 115
        private Voittorivi voittorivi;
        public Voittorivi Voittorivi
        {
            get
            {
                return voittorivi;
            }
M1888's avatar
M1888 committed
116 117 118 119 120 121 122
            set
            {
                voittorivi = value;
                RaisePropertyChanged();
            }
        }

M1888's avatar
M1888 committed
123 124 125 126 127 128 129 130 131 132
        // StatusHistory pitää sisällään kaikki tilaviestit, jotka lottokone suustaan päästää.
        // Status-string taas on wrapperi historian ympärille, niin saadaan helposti viimeisin
        // viesti sekä voidaan lisätä viestejä historiaan helposti vain asettamalla Statukseen jotain.
        //   Esim.  Status = "Jee";
        // StatusLock on objekti joka lukitaan kun StatusHistorya päivitetään.
        // UI threadissa kutsutaan BindingOperations.EnableCollectionSynchronization(),
        // jolle annetaan tämä sama objekti, niin UI-thread ei sörki kokoelmaa samalla
        // kun me päivitetään sitä muualla.
        public object StatusLock { get; set; }
        public ObservableCollection<string> StatusHistory;
M1888's avatar
M1888 committed
133 134 135 136
        public string Status
        {
            get
            {
M1888's avatar
M1888 committed
137
                return StatusHistory.FirstOrDefault();
M1888's avatar
M1888 committed
138 139 140
            }
            set
            {
M1888's avatar
M1888 committed
141 142 143 144 145 146 147
                lock (StatusLock)
                {
                    // En saanut ListBoxin autoskrollausta toimimaan uusimpaan itemiin (joka siis tulee
                    // alimmaiseksi), joten lisätään uudet viestit ylimmäiseksi ja ongelma poistuu. :)
                    StatusHistory.Insert(0, value);
                    RaisePropertyChanged();
                }
M1888's avatar
M1888 committed
148 149 150
            }
        }

M1888's avatar
M1888 committed
151 152
        // Lasketaan kierroksella mukana olleiden rivien määrä.
        public int Rivimaara
M1888's avatar
M1888 committed
153 154 155
        {
            get
            {
M1888's avatar
M1888 committed
156
                int n = 0;
M1888's avatar
M1888 committed
157
                foreach (Pelaaja p in Pelaajat)
M1888's avatar
M1888 committed
158 159 160 161
                {
                    n += p.Rivit.Count();
                }
                return n;
M1888's avatar
M1888 committed
162 163 164
            }
        }

M1888's avatar
M1888 committed
165 166 167
        // Konstruktori lottokoneen pääluokalle
        public Lotto()
        {
M1888's avatar
M1888 committed
168
            Potti = 0;
M1888's avatar
M1888 committed
169 170 171 172 173 174 175 176 177 178 179 180 181 182

            voitot = new int[6];
            // voitot[0]: 7 oikein
            // voitot[1]: 6+1 oikein
            // voitot[2]: 6 oikein
            // voitot[3]: 5 oikein
            // voitot[4]: 4 oikein
            // voitot[5]: 3+1 oikein
            // näistä potista riippumattomia voittosummia on 7, 4 ja 3+1 oikein,
            // joten ne voidaan asettaa tässä.
            voitot[0] = 1_250_000;
            voitot[4] = 10;
            voitot[5] = 2;

M1888's avatar
M1888 committed
183 184
            Viikko = 0;
            Vuosi = 1;
M1888's avatar
M1888 committed
185
            Pelaajat = new ConcurrentBag<Pelaaja>();
M1888's avatar
M1888 committed
186 187 188 189 190 191 192
            oikein5 = new ConcurrentBag<Pelaaja>();
            oikein6 = new ConcurrentBag<Pelaaja>();
            oikein6_1 = new ConcurrentBag<Pelaaja>();
            oikein7 = new ConcurrentBag<Pelaaja>();
            StatusLock = new object();
            StatusHistory = new ObservableCollection<string>();
            Status = "Veikkaus: Pelaa maltilla :):):):)";
M1888's avatar
M1888 committed
193

M1888's avatar
M1888 committed
194
            stopwatch = Stopwatch.StartNew();
M1888's avatar
M1888 committed
195
            LisaaPelaajia(Properties.Settings.Default.Pelaajia, Properties.Settings.Default.Riveja);
M1888's avatar
M1888 committed
196
            stopwatch.Stop();
M1888's avatar
M1888 committed
197
            Status = $"Lisätty {Pelaajat.Count():N0} pelaajaa ({stopwatch.ElapsedMilliseconds}ms)";
M1888's avatar
M1888 committed
198 199
        }

M1888's avatar
M1888 committed
200
        public void LisaaPelaajia(int pelaajia, int riveja)
M1888's avatar
M1888 committed
201
        {
M1888's avatar
M1888 committed
202
            for (int i = 0; i < pelaajia; i++)
M1888's avatar
M1888 committed
203
            {
M1888's avatar
M1888 committed
204
                Pelaajat.Add(new Pelaaja(riveja, Properties.Settings.Default.Voimassa));
M1888's avatar
M1888 committed
205
            }
M1888's avatar
M1888 committed
206 207
        }

M1888's avatar
M1888 committed
208 209 210 211 212
        public void PoistaPelaajia(int maara)
        {
            Pelaajat = new ConcurrentBag<Pelaaja>(Pelaajat.Skip(maara).ToList());
        }

M1888's avatar
M1888 committed
213 214
        public void Tick()
        {
M1888's avatar
M1888 committed
215 216 217
            stopwatch.Reset();
            stopwatch.Start();

M1888's avatar
M1888 committed
218
            Viikko++;
M1888's avatar
M1888 committed
219

M1888's avatar
M1888 committed
220 221 222 223 224 225 226 227 228 229 230
            // Tyhjennetään voittajien listat vain jos viime kierroksella oli voittajia.
            // ConcurrentBagia ei saa helposti tyhjennettyä, joten luodaan uusi päälle.
            if (!oikein5.IsEmpty)
                oikein5 = new ConcurrentBag<Pelaaja>();
            if (!oikein6.IsEmpty)
                oikein6 = new ConcurrentBag<Pelaaja>();
            if (!oikein6_1.IsEmpty)
                oikein6_1 = new ConcurrentBag<Pelaaja>();
            if (!oikein7.IsEmpty)
                oikein7 = new ConcurrentBag<Pelaaja>();

M1888's avatar
M1888 committed
231
            Voittorivi = new Voittorivi();
M1888's avatar
M1888 committed
232

M1888's avatar
M1888 committed
233
            EtsiVoittajat();
M1888's avatar
M1888 committed
234
            RaisePropertyChanged("Rivimaara");
M1888's avatar
M1888 committed
235
            Potti = (int)(Rivimaara * Properties.Settings.Default.Rivihinta);
M1888's avatar
M1888 committed
236 237 238

            JaaVoitot();
            stopwatch.Stop();
M1888's avatar
M1888 committed
239 240 241

            Status = $"-Kierros {Kierros} ({stopwatch.ElapsedMilliseconds}ms)-";

M1888's avatar
M1888 committed
242 243
        }

M1888's avatar
M1888 committed
244 245 246
        // Voittajat täytyy erikseen etsiä ja sitten vasta jakaa voitot.
        // Samassa loopissa niitä ei voi tehdä, koska voittosumma riippuu voittajien
        // määrästä. Se on selvillä vasta kun kaikki on käyty läpi.
M1888's avatar
M1888 committed
247
        private void EtsiVoittajat()
M1888's avatar
M1888 committed
248
        {
M1888's avatar
M1888 committed
249
            Parallel.ForEach(Pelaajat, (p) =>
M1888's avatar
M1888 committed
250
            {
M1888's avatar
M1888 committed
251 252 253
                // päivitetään lottorivit
                p.Tick();
                foreach (Lottorivi r in p.Rivit)
M1888's avatar
M1888 committed
254
                {
M1888's avatar
M1888 committed
255
                    switch (r.Oikein(Voittorivi))
M1888's avatar
M1888 committed
256
                    {
M1888's avatar
M1888 committed
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
                        // 3+1 ja 4 oikein on tasamääräisiä voittoja, joten ne voidaan jakaa
                        // suoraan täällä. Isommat voitot koostetaan ConcurrentBageihin,
                        // kunnes voittajien määrä on selvillä.
                        case 3:
                            // kolme oikein, niin tarvitaan vielä lisänumero
                            if(r.Numerot.Contains(Voittorivi.Lisanumero))
                            {
                                // Pottia voi käsitellä useampi säie samaan aikaan, siksi
                                // System.Threading.Intelocked.Add() joka on atominen operaatio
                                Interlocked.Add(ref potti, -2);

                                // tätä pelaajaa ei taas käsitellä kuin me tässä ja nyt.
                                p.Saldo += 2;
                            }
                            break;
M1888's avatar
M1888 committed
272
                        case 4:
M1888's avatar
M1888 committed
273 274
                            Interlocked.Add(ref potti, -10);
                            p.Saldo += 10;
M1888's avatar
M1888 committed
275 276
                            break;
                        case 5:
M1888's avatar
M1888 committed
277
                            oikein5.Add(p);
M1888's avatar
M1888 committed
278 279
                            break;
                        case 6:
M1888's avatar
M1888 committed
280 281 282 283 284
                            // Täällä pitää erotella vielä 6 ja 6+1 oikein rivit
                            if (r.Numerot.Contains(Voittorivi.Lisanumero))
                                oikein6_1.Add(p);
                            else
                                oikein6.Add(p);
M1888's avatar
M1888 committed
285 286
                            break;
                        case 7:
M1888's avatar
M1888 committed
287
                            oikein7.Add(p);
M1888's avatar
M1888 committed
288 289 290
                            break;
                        default:
                            break;
M1888's avatar
M1888 committed
291 292
                    }
                }
M1888's avatar
M1888 committed
293 294 295 296
            });
        }

        // https://fi.wikipedia.org/wiki/Lotto_(Veikkaus)#Voittoluokat_ja_voiton_todenn%C3%A4k%C3%B6isyys
M1888's avatar
M1888 committed
297
        // 3+1: 2 e 
M1888's avatar
M1888 committed
298 299 300
        // 4 oikein: 10 e
        // 5 oikein: 3% potista voittajien kesken
        // 6 oikein: 2,5% potista voittajien kesken
M1888's avatar
M1888 committed
301
        // 6+1: 3,8% potista voittajien kesken
M1888's avatar
M1888 committed
302
        // 7: päävoitto
M1888's avatar
M1888 committed
303 304 305

        // Luotetaan, että aiemmat tarkistukset on oikein, eikä
        // listoilla ole pelaajia joiden rivit eivät ole voittaneet...
M1888's avatar
M1888 committed
306 307
        private void JaaVoitot()
        {
M1888's avatar
M1888 committed
308 309

            if (!oikein5.IsEmpty)
M1888's avatar
M1888 committed
310
            {
M1888's avatar
M1888 committed
311
                int voittajia = oikein5.Count();
M1888's avatar
M1888 committed
312
                voitot[3] = ((potti / 100) * 3) / voittajia;
M1888's avatar
M1888 committed
313

M1888's avatar
M1888 committed
314
                Status = $"5 oikein voittoja {voittajia:N0} kpl ({voitot[3]:C0})";
M1888's avatar
M1888 committed
315

M1888's avatar
M1888 committed
316
                while (oikein5.TryTake(out Pelaaja p) != false)
M1888's avatar
M1888 committed
317
                {
M1888's avatar
M1888 committed
318 319
                    p.Saldo += voitot[3];
                    Potti -= voitot[3];
M1888's avatar
M1888 committed
320 321
                }
            }
M1888's avatar
M1888 committed
322

M1888's avatar
M1888 committed
323 324 325
            if (!oikein6.IsEmpty)
            {
                int voittajia = oikein6.Count();
M1888's avatar
M1888 committed
326
                voitot[2] = (int)((potti / 100) * 2.5) / voittajia;
M1888's avatar
M1888 committed
327

M1888's avatar
M1888 committed
328
                Status = $"6 oikein voittoja {voittajia:N0} kpl ({voitot[2]:C0})";
M1888's avatar
M1888 committed
329 330

                while (oikein6.TryTake(out Pelaaja p) != false)
M1888's avatar
M1888 committed
331
                {
M1888's avatar
M1888 committed
332 333
                    p.Saldo += voitot[2];
                    Potti -= voitot[2];
M1888's avatar
M1888 committed
334
                }
M1888's avatar
M1888 committed
335 336
            }

M1888's avatar
M1888 committed
337 338 339
            if(!oikein6_1.IsEmpty)
            {
                int voittajia = oikein6_1.Count();
M1888's avatar
M1888 committed
340
                voitot[1] = (int)((potti / 100) * 3.8) / voittajia;
M1888's avatar
M1888 committed
341

M1888's avatar
M1888 committed
342
                Status = $"6+1 oikein voittoja {voittajia:N0} kpl ({voitot[1]:C0})";
M1888's avatar
M1888 committed
343 344 345

                while (oikein6.TryTake(out Pelaaja p) != false)
                {
M1888's avatar
M1888 committed
346 347
                    p.Saldo += voitot[1];
                    Potti -= voitot[1];
M1888's avatar
M1888 committed
348 349 350 351
                }
            }

            if (!oikein7.IsEmpty)
M1888's avatar
M1888 committed
352 353
            {
                int voittajia = oikein7.Count();
M1888's avatar
M1888 committed
354
                voitot[0] = (int)(voitot[0] / voittajia);
M1888's avatar
M1888 committed
355

M1888's avatar
M1888 committed
356
                Status = $"!!! 7 oikein !!!! {voitot[0]:C0} ({voittajia:N0} kpl) !!!";
M1888's avatar
M1888 committed
357 358 359

                while (oikein7.TryTake(out Pelaaja p) != false)
                {
M1888's avatar
M1888 committed
360
                    p.Saldo += voitot[0];
M1888's avatar
M1888 committed
361
                }
M1888's avatar
M1888 committed
362
                voitot[0] = 1_250_000;
M1888's avatar
M1888 committed
363
            }
M1888's avatar
M1888 committed
364 365
            else
            {
M1888's avatar
M1888 committed
366 367 368
                // n. 20 miljoonaa lienee hyvä maksimi päävoitto
                if(voitot[0] < 20_000_000)
                    voitot[0] = (int)(voitot[0] * 1.07);
M1888's avatar
M1888 committed
369
            }
M1888's avatar
M1888 committed
370
            RaisePropertyChanged("Voitot");
M1888's avatar
M1888 committed
371 372 373 374
        }

        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
M1888's avatar
M1888 committed
375
            if (PropertyChanged != null)
M1888's avatar
M1888 committed
376
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
M1888's avatar
M1888 committed
377 378 379
        }
    }
}