Laden...

K.I. für Darts Spiel

Erstellt von elTorito vor 8 Jahren Letzter Beitrag vor 3 Jahren 3.318 Views
elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren
K.I. für Darts Spiel

Hi,

ich möchte meine DartsApp um einen Computer Spieler erweitern.

Ich brauche für einen Wurf eine Nummer und ein Multiplikator (x1,x2,x3).

Mein erster Ansatz war eine fertiges Dictionary mit Fertigen Spielwürfen:


Dictionary<int, List<DartShoot>>ComputerPersonsShoots = new Dictionary<int, List<DartShoot>>
        {
            {0, new List<DartShoot>
                {
                    new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=19,HittedTarget=HitTarget.Triple},
                    new DartShoot(){HitNumber=12,HittedTarget=HitTarget.Double}
                }
            },
....

Aus dem Dictionary hatte ich mir dann ein Zufälliges Spiel rausgesucht, und die Shoots nach und nach abgearbeitet. Was Doof ist, weil man nach einer Weile die Spielzüge kennen dürfte. Die Variante werde ich noch nehmen, um ein eigenes Spiel zu Speichern, um gegen sich selber zu spielen.

Nun habe ich eine Liste mit Nummern, und eine Liste mit HitTargets, wo ich mir zufällig eine Nummer, und ein Multipliaktor rauspicke.


List<int> HardLevelNo= new List<int>(new int[] {25,20,19,18,16,1,5,0});
List<HitTarget> HardLevelHitTargets = new List<HitTarget>() { HitTarget.Single, HitTarget.Triple, HitTarget.Double, HitTarget.Out, HitTarget.Triple, HitTarget.Triple, HitTarget.Double };

Wo ich mir dann jeweils ein ZufallWert raushole:


 public static IEnumerable<T> RandomEnumValue<T>(this IEnumerable<T> source, Random rng)
        {
            Random rnd = new Random();
            List<T> values = Enumerable.ToList(source);
            int size = values.Count - 1;
            while (true)
            {
                yield return values[rnd.Next(size)];
            }
        }

Random rand = new Random();
int randNo = RandomEnumValue(ComputerPersons.X01Extrem, rand).First();
HitTarget randHitTarget = RandomEnumValue(ComputerPersons.X01ExtremHitTargets, rand).First();
new DartShoot(randNo,randHitTarget);

Alternativ könnte man eine Liste oder Dictionary mit fertigen Würfen erstellen, und sich dann ein Zufälliges Paar heranholen:


new List<DartShoot>
{
new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Single},
new DartShoot(){HitNumber=5,HittedTarget=HitTarget.Double},
new DartShoot(){HitNumber=15,HittedTarget=HitTarget.Triple},
new DartShoot(){HitNumber=20,HittedTarget=HitTarget.Double},
...

Wenn der Computer z.B. noch 40 Punkte offen hat, und zum Gewinnen eine 20 x2 braucht. ist es wahsrcheinlicher dass er: Doppel 5 trifft, Doppel 1, 0, oder Einfach 20.

Von der Abhängikeit des Punktestand soll die Wahrscheinlichkeit hervorgehen welches Ziel der Computer Treffen kann. So oder so brauche ich ja einige Listen aus den ich mir die Sachen raushole. Nun überlege ich wie ich das am besten/realistischen steuern könnte.

Wie würdet Ihr das machen? Liste Nummern, Multiplaktor separat, oder eine Paarweise Liste? Oder ganz andere herangehensweise?

Danke

T
708 Beiträge seit 2008
vor 8 Jahren

Hi elTorito,

ich würde mir das so vorstellen:

  1. Evaluieren der Möglichkeiten
    Geht es noch um Punkte sammeln oder ist man bereits in der Endphase?
    Am Beginn des Spieles wirft man i.d.R. triple 20 oder mal triple 18, wenn die Darts ungünstig stecken.
    Das kann man per Zufall entscheiden.
    Zum Ende hin muss man natürlich die Restpunktezahl berücksichtigen.

  2. Entscheiden auf welche der Möglichkeiten geworfen wird.
    Triple 20 oder Triple 18 am Beginn
    Restwert / 2 auf Double oder Restwert/3 auf Triple

  3. Zufallswurf
    Nun läufst Du X-mal eine Schleife durch, die rausspringt, wenn Dein Random(0,20) Wert dem in Punkt 2) erhofften Wert entspricht.
    Wenn nicht, verwendest Du den letzten Random-Wert.
    Selbige Herangehensweise führst Du aus um Single, Double & Triple zu entscheiden.
    Entspricht der Zufallswert Deiner Erwartung, Aussprung aus der Schleife, sonst letzten Zufallswert verwenden.

Die Schwierigkeit Deiner KI kannst Du nun über "X" steuern. Also in dem Du die Wahrscheinlichkeit, das die erwartete Zahl getroffen wird, durch die Durchläufe der Schleife veränderst.

Also in Kurzversion:
Alle möglichen Wurf-Varianten berechnen.
Den nächsten "optimalen" Wurf daraus auswählen.
Per Zufallsannäherung den "optimal"-Wurf treffen, oder eben nicht. Dann ist der Wurf komplett Zufall.

3.170 Beiträge seit 2006
vor 8 Jahren

Hallo,

und auf jeden Fall aufpassen, dass Triple 25 ausgeschlossen ist 😉

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

P
1.090 Beiträge seit 2011
vor 8 Jahren

Ich würde einen anderen Ansatz nehmen.

Erstmal bestimmen auf welche Zahl geworfen wird und dann mit einer durch Zufall Abweichung und Richtung (360 Grad) bestimmen, dadurch kannst du dann bestimmen wo er getroffen hat. Je besser der Spieler je keiner die Abweichung. Grundlegend reichen dafür einfache Funktionen (Schlechter Spieler wirft um den Faktor 5 weiter daneben). Mit Hilfe der "Schwingungsfunktionen" solltest du damit aber jedes Wurfbild (Schußbild) abbilden können.

@MarsStein
Trippel 25 ist für den Godmod

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

C
2.121 Beiträge seit 2010
vor 8 Jahren

Mit welchem Treffer man mehr Chancen hat, hat meiner Meinung nichts mit KI zu tun. Zu welchem Zweck willst du da Listen im voraus füllen und auslesen?

Ein einfaches Feld triffst du leichter als ein doppeltes. Ein doppeltes leichter als ein dreifaches. Das könnte sich auf den "Level" des Computers auswirken. Wenn er gut ist zielt er auf doppelte Felder falls es ihm was bringt, wenn er sehr gut ist auf dreifache. Wenn er schwach ist, versucht er wenigstens ein normales Feld mit hohen Punkten zu treffen.
Wobei wahrscheinlich bei einer benötigten 6 auch ein Profi auf die einzelne 6 zielen wird statt auf Triple 2. Oder wie machen die Profis das?
Wann ein doppeltes oder dreifaches dir was bringt, ist reine Rechensache.

Wohin der Computer trifft ist eine Frage deiner Zielungenauigkeit, die du hinein rechnest.

F
174 Beiträge seit 2007
vor 8 Jahren

Das ganze kann man natürlich auch noch viel weiter treiben ...
Die Felder sind natürlich auch unterschiedlich groß. Die einfache 20 zu treffen, ist somit wahrscheinlicher, als eine triple / double zu treffen. Bei der 20 geht es viel mehr um die horizontale und nicht um die vertikale Abweichung. Da der Dart bedingt durch die Schwerkraft immer eine Kurve fliegt, ist die vertikale Abweichung also immer größer als die horizontale. Vorausgesetzt der Dartspieler ist in der Lage seinen Wurfarm einigermaßen gerade zu führen.

Spielst du Dart? Du wirst feststellen, dass es dir schwerer fallen dürfte, die 11 oder die 6 zu treffen, als die 20 oder die 3. Zumindest geht es dein meisten Spielern so.

Ich weiß ... man kann alles übertreiben, aber wenn man es genau nimmt, müsste auch das berücksichtigt werden.

Wobei wahrscheinlich bei einer benötigten 6 auch ein Profi auf die einzelne 6 zielen wird statt auf Triple 2. Oder wie machen die Profis das?

Das kommt darauf an. Wenn beim 501 / double out nur noch die 6 benötigt wird, um die Runde zu gewinnen, dann würde ich sagen, dass die meisten auf die 4 werfen und dann versuchen, mit double 1 raus zu kommen. Wobei 2 Rest immer ein Risiko darstellt, weil jeder verfehlte Wurf bedeutet, dass man wieder auf die 2 zurück fällt. Es sei denn, man wirft eine 0.

T
708 Beiträge seit 2008
vor 8 Jahren

So wie ich das verstanden habe, wollte elTorito eine einfache Lösung haben, die nicht auf festen Listen basiert.
Daher führt es doch ein bisschen weit, die Felder in Vektoren aufzuteilen, eine Schwingung zu berechnen und die Erdanziehungskraft zu berücksichtigen 😉

Bin aber gespannt was daraus wird!

F
174 Beiträge seit 2007
vor 8 Jahren

So wie ich das verstanden habe, wollte elTorito eine einfache Lösung haben, die nicht auf festen Listen basiert.
Daher führt es doch ein bisschen weit, die Felder in Vektoren aufzuteilen, eine Schwingung zu berechnen und die Erdanziehungskraft zu berücksichtigen 😉

Bin aber gespannt was daraus wird!

Ja klar. Ich wollte es nur erwähnt haben. Ich würde das bei der Programmierung sehr wahrscheinlich auch nicht berücksichtigen. Jeder Spieler wirft auch unterschiedlich stark, was sich wiederrum auf die Kurve auswirkt. Aber ja, du hast wahrscheinlich recht ... das führt zu weit.

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

Hallo,

danke für eure Antworten.

Sind ja einige Möglichkeiten dabei. 👍
Ganz kompliziert wollte ich es nicht machen.

Ich überlege nun wie folgt:

Am Anfang eine Liste mit allen möglichen Wurf Varianten (wie trib meinte), je nach Spielphase und Level des Spieler wird die Liste abgespeckt. Dann weiter entscheiden um die Liste evtl noch weiter zu reduzieren, und von dem was über bleibt hole ich mir ein Zufallswurf.

z.B. Profi Level:
Spiel fängt bei 501 Punkten an
ich biete an:
20,19,18,17

entscheide mich für die T20 und biete an:
T20,S20,T1,S1,T5,5

Anschliessend entscheiden, Treffer auf T20 oder Zufall(S20,T1,S1,5,T5)

Beim Semi Profi z.B. am Anfang:
Die Felder welche dem Profi angeboten wurden plus die angrenzenden Felder:
20,1,5,18,4,19,7,3,2

ich entscheide mich dafür dass er auf T18 geht und biete an:
T18,S18,D18,S1,D1,T1,S4,D4,T4, Out

So irgendwie stell ich mir das nun vor.

elTorito Themenstarter:in
177 Beiträge seit 2009
vor 8 Jahren

So. eine "K.I." habe ich fertig 😁

Ich Unterteile in Punkte Segmente:
Score >170 (High Segment)
Score > 120 && Score ≤ 170 (Medium Segment)
Score ≤ 120 && Score ≥ 60 (low Segment)
Score < 60 (Low segment)

Für jedes Segment habe ich eine Liste mit den Zahlen und Multipliktoren die in dem Segment getroffen werden können, so wie eine Liste mit den angrenzenden Feldern:


public static List<int> ExtremHighSegmentTargetNumbers = new List<int>
        {
            20,
            19,
            18,
            17
        };
 public static List<int> ExtremMediumSegmentTargetNumbers = new List<int>
        {
            25,
            20,
            19,
            18,
            17,
            16,
            15,
            14,
            13,
            12,
            11,
            1
        };
public static List<int> ExtremLowSegmentTargetNumbers = new List<int>
        {
            25,
            20,
            19,
            18,
            17,
            16,
            15,
            14,
            13,
            12,
            11,
            10,
            9,
            8,
            7,
            6,
            5,
            4,
            3,
            2,
            1,

        };

 public static List<HitTarget> ExtremHighSegmentHitTargets = new List<HitTarget>
        {
            HitTarget.Triple,
            HitTarget.Single
        };

 public static List<HitTarget> ExtremMediumSegmentHitTargets = new List<HitTarget>
        {
            HitTarget.Triple,
            HitTarget.Single,
            HitTarget.Double
        };
public static List<HitTarget> ExtremLowSegmentHitTargets = new List<HitTarget>
        {
            HitTarget.Triple,
            HitTarget.Single,
            HitTarget.Double,
            HitTarget.Out
        };

public static Dictionary<int, List<int>> ExtremHighSegmentNearTargetNumbers = new Dictionary<int, List<int>>
        {
            {20, new List<int>{5,1}},
            {19, new List<int>{7,3}},
            {18, new List<int>{1,4}},
            {17, new List<int>{3,2}},
        };

public static Dictionary<int, List<int>> ExtremMediumSegmentNearTargetNumbers = new Dictionary<int, List<int>>
        {
            {25, new List<int>{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}},    
            {20, new List<int>{5,1}},
            {19, new List<int>{7,3}},
            {18, new List<int>{1,4}},
            {17, new List<int>{3,2}},
            {16, new List<int>{7,8}},
            {15, new List<int>{2,10}},
            {14, new List<int>{11,9}},
            {13, new List<int>{4,6}},
            {12, new List<int>{5,9}},
            {11, new List<int>{14,8}},
            {1, new List<int>{20,18}},
        };

public static Dictionary<int, List<int>> ExtremLowSegmentNearTargetNumbers = new Dictionary<int, List<int>>
        {
            {25, new List<int>{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}},    
            {20, new List<int>{5,1}},
            {19, new List<int>{7,3}},
            {18, new List<int>{1,4}},
            {17, new List<int>{3,2}},
            {16, new List<int>{7,8}},
            {15, new List<int>{2,10}},
            {14, new List<int>{11,9}},
            {13, new List<int>{4,6}},
            {12, new List<int>{5,9}},
            {11, new List<int>{14,8}},
            {10, new List<int>{6,15}},
            {9, new List<int>{14,12}},
            {8, new List<int>{11,16}},
            {7, new List<int>{19,16}},
            {6, new List<int>{10,13}},
            {5, new List<int>{12,20}},
            {4, new List<int>{18,13}},
            {3, new List<int>{19,17}},
            {2, new List<int>{17,15}},
            {1, new List<int>{20,18}},
        };

Damit erzeuge ich je nach Segment eine Liste mit den möglichen Treffern in dem Segment:


 public static List<DartShoot> InitialShoots( List<int> initNumbers, List<HitTarget> initHitTargets, Dictionary<int, List<int>> dictNearShoots)
        {
            List<DartShoot> list = new List<DartShoot>();

            foreach (int i in initNumbers)
            {
                foreach (HitTarget t in initHitTargets)
                {
                    if (i == 25 && t == HitTarget.Triple)
                    {

                    }
                    else
                    {
                        list.Add(new DartShoot() { HitNumber = i, HittedTarget = t, NearShoots = GetNearShoots(i, dictNearShoots) });
                    }
                }
            }

            return list;
        }

Für die möglichen Treffer lade ich die anliegenden Felder :


public static List<DartShoot> GetNearShoots(int hitno, Dictionary<int,List<int>> dict)
        {
            List<DartShoot> list = new List<DartShoot>();
            if (dict.ContainsKey(hitno))
            {
                List<int> iList = dict[hitno];
                foreach (int i in iList)
                {
                    list.Add(new DartShoot() { HitNumber = i, HittedTarget = HitTarget.Single });
                    list.Add(new DartShoot() { HitNumber = i, HittedTarget = HitTarget.Double });
                    if (i != 25)
                        list.Add(new DartShoot() { HitNumber = i, HittedTarget = HitTarget.Triple });
                    list.Add(new DartShoot() { HitNumber = i, HittedTarget = HitTarget.Out });
                }
            }
            return list;
        }

Im Segment > 170:


listAllShoots =  DartShootsDict.ExtremHighSegment();
int initialTargetNo = EnumerableExtensions.RandomEnumValue(DartShootsDict.ExtremHighSegmentTargetNumbers, new Random()).First();
IEnumerable<DartShoot> list = from shoot in listAllShoots
                                              where ((shoot.HitNumber == initialTargetNo) && shoot.HittedTarget == HitTarget.Triple && shoot.HittedTarget != HitTarget.Out)
                                              select shoot;
                    
               
                IEnumerable<DartShoot> nearShootList = null;
                foreach(DartShoot s in list)
                {
                    if (s.NearShoots != null)
                    {
                        nearShootList = list.Concat(new List<DartShoot>(s.NearShoots));
                    }
                }

                HitTarget selHitTarget = EnumerableExtensions.RandomEnumValue(DartShootsDict.ExtremHighSegmentHitTargets, new Random()).First();
                IEnumerable<DartShoot> hittargetlist = from shoot in nearShootList
                                                       where (shoot.HittedTarget == selHitTarget)
                                                       select shoot;
DartShoot ds = EnumerableExtensions.RandomEnumValue(hittargetlist, new Random()).First();

Im Medium / Low Segment :


IEnumerable<DartShoot> list = from shoot in listAllShoots
                                              where ((shoot.HitNumber == initialTargetNo)) 
                                              select shoot;

Low Segment unter 60 Punkte:


IEnumerable<DartShoot> list = from shoot in listAllShoots
                                              where ((shoot.HitNumber == initialTargetNo && shoot.HittedTarget == HitTarget.Double))
                                              select shoot;

Unter 50 Punkte lasse ich noch eine Schleife laufen, welche alle Zahlen/2 prüft, und dann zufällig auswählt ob der Dart gewinnt oder ob es doch ein Zufallswurf wird:


private DartShoot GenerateRandomLowScoreShoot(int doubleNo)
        {
            bool finish = EnumerableExtensions.RandomEnumValue(DartShootsDict.BOut, new Random()).First();
            
            if (finish)
            {
                System.Diagnostics.Debug.WriteLine("Direct Out Shoot ");
                return new DartShoot() { HitNumber = doubleNo, HittedTarget = HitTarget.Double };
            }
            else
            {
                IEnumerable<DartShoot> lds = from shoot in DartShootsDict.ExtremLowSegment()
                                             where ((shoot.HitNumber == doubleNo && shoot.HittedTarget == HitTarget.Double))
                                             select shoot;
                IEnumerable<DartShoot> nearShootList = null;
                foreach (DartShoot s in lds)
                {
                    if (s.NearShoots != null)
                    {
                        nearShootList = lds.Concat(new List<DartShoot>(s.NearShoots));
                    }
                }
                HitTarget selHitTarget = GetRandomTarget(doubleNo, DartShootsDict.ExtremLowSegmentHitTargets);
                IEnumerable<DartShoot> hittargetlist = from shoot in nearShootList
                                                       where (shoot.HittedTarget == selHitTarget)
                                                       select shoot;
                return EnumerableExtensions.RandomEnumValue(hittargetlist, new Random()).First();
            }
        }

Hab jetzt paar Spielchen gemacht, die K.I. pendelt so zwischen Average 60 und 113 derzeit, und braucht 13 bis 25 Darts zum ausmachen , also recht große Streuung, werde da noch bisschen dran "pfeilen" 🙂 Im Unteren Segment überwirft die KI sich zu Oft. (2-3 mal) dass sind dann die 6-9 Darts Unterschied zwischen Profi und Amateur ungefähr.

Wünsche ein schönes Wochenende

Code werde ich noch aufräumen ...

Peter

S
39 Beiträge seit 2019
vor 3 Jahren

Hallöle.

Ich bin gerade bei der Boardsuche auf diesen Fred gestoßen. Ist das ganze noch aktuell? Ich hab das was schönes gebastelt, weil ich selbst eine Sammlung von Dartspielen gebaut habe und dort auch nach und nach einen Computergegner (Stärke 1-12) eingebaut habe.

Allerdings arbeite ich da mit "gewünschtem" Ziel und einer einstellbaren Ungenauigkeit. Das ganze aber mit Vektoren.

2 stupid 4 chess? No way.
2 stupid 4 C#? It seems so X(