Step by Step: 4 Bilder 1 Wort - Lösungshelfer

    • VB.NET

    Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von sonne75.

      Step by Step: 4 Bilder 1 Wort - Lösungshelfer

      Hallo werte Community,

      einige von euch kennen vielleicht schon dieses Ratespiel, das seit einiger Zeit (nicht nur?) auf Smartphones sein Unwesen treibt: "4 Bilder 1 Wort". Es beinhaltet im Grunde (mittlerweile) über 1400 Rätsel, die aus jeweils auf 4 Bildern, 12 Buchstaben und einem Eingabefeld für die Lösung bestehen. Die 4 Bilder haben alle eine Gemeinsamkeit - eine gemeinsame Bedeutung, Bezeichnung, Tätigkeit. Zumindest wird das behauptet (ich stimme mit den Erstellern der einzelnen Rätsel nicht immer überein). ;) Und nach dem Wort für diese Gemeinsamkeit wird gesucht.

      Beispiel: Auf den vier Bildern sieht man einen roten Planeten, einen Schokoriegel, ein Sternzeichensymbol und einen griechische Statue eines Kriegers. Die 12 Buchstaben sind scheinbar vollkommen zufällig, aber irgendwo sind auch die Buchstaben M, A, R und S mit dabei - natürlich nicht unbedingt in der Reihenfolge. Das einzige was man noch weiß: Das gesuchte Wort hat vier Buchstaben.

      Da die Macher der Bilderrätsel manchmal etwas sehr abstruse Vorstellungen davon haben, was auf manchen Bildern zu sehen sein soll und wie das mit den anderen zusammenpasst, und ob man in einem Spiel, das den deutschen Sprachschatz nutzt, so Begriffe wie "Facebook", "Hiphop" oder "Pasta" vorkommen können, darüber lässt sich sicher auch gut streiten, ist es manchmal wirklich knifflig auf den richtigen Begriff zu kommen. Zum Glück gibt es bereits im Internet mehrere Seiten, die mögliche Lösungen präsentieren (einige wenige werden sogar recht zügig auf den aktuellen Stand gebracht, wenn es mal wieder ein Rätsel-Update gab).
      Das Finden der richtigen Lösung (bzw. des richtigen Rätsels, um die Lösung abzugreifen) ist dabei mit ein paar Hürden verbunden: Zum einen müssen die Bilder mit Worten beschrieben werden, da sie vermutlich aus Copyright-Gründen nicht einfach so auf einer x-beliebigen Webseite verwendet werden dürfen, und wenn der Lösungsanbieter meint, da ist eine Apfelsine zu sehen und man selbst sucht aber nach Orange, dann findet man halt im ersten Moment nichts. Eine andere Hürde ist, dass die Rätsel bei jedem Spieler in einer anderen (zufälligen) Reihenfolge erscheinen. Die Nummer der Aufgabe bzw. die Anzahl der bisher gelösten Aufgaben helfen also nicht wirklich dabei, die passende Zeile mit der gesuchten Lösung zu finden.

      Langer Rede kurzer Sinn: Ich habe mir zu diesem Zweck ein kleines Programm geschrieben - zunächst in C# - und dachte mir, das wäre ein prima - weil auch übersichtliches - Projekt um es mit euch zu teilen, es zu diesem Zweck in VB neu entstehen und euch am Entwicklungsprozess teilhaben zu lassen.

      Grundkonzept

      Ziel des Programms soll es sein, dass man ohne Beschreibung der Bilder auskommt und dabei stattdessen all die anderen Informationen mit ins Boot holt, die so ein Rätsel bereitstellt: also Anzahl der Buchstaben des Lösungsworts und die 12 bereitgestellten Buchstaben zum Lösen (von denen einige unbrauchbar sind). Es ist außerdem bekannt, dass es kein Wort mit weniger als 3 (da bin ich allerdings nicht sicher, bisher gibts noch kein zweibuchstabiges Wort) oder mit mehr als 8 Buchstaben gibt (mehr als 8 Buchstaben gibt das Layout des Spiels nicht her).

      Ich möchte also mit Hilfe der 12 verfügbaren Buchstaben alle die möglichen Lösungswörter finden, die sich mit diesem Satz bilden lassen. Als zusätzliche Einschränkung sollen hiervon wiederum nur diejenigen gewählt werden, bei denen die Anzahl der Buchstaben des Lösungsworts übereinstimmt. Die möglichen Lösungswörter sollen als eine Liste von Wörtern zur Verfügung stehen, damit wir nicht irgendwelche Lexika wälzen oder referenzieren müssen und dabei so viele mögliche oder gar unsinnige Lösungsvorschläge entstehen können, die das Spiel selbst sowieso (noch) nicht beinhaltet.
      Auch hierbei kann es noch passieren, dass zu einem Buchstabensatz mehrere Lösungswörter passen, aber die Menge ist dann auf jeden Fall auf einen Blick überschaubar und kann dann mit Hilfe der Bilder vom Spieler leicht weiter eingeschränkt werden. In ganz harten Fällen, wo selbst der Bildinhalt eine der mehrfach vorgegebenen Lösungsmöglichkeiten nicht preisgibt, muss halt das altbewährte Try-and-Error herhalten. Mit dem eingeschränkten (zur Auswahl passenden) Satz an Vorschlägen ist das aber durchaus weniger aufwändig als sämtliche Wörter auszuprobieren oder gar alle 12 Buchstaben zu permutieren.
      In den Weiten des Internets habe ich diese Seite gefunden, die auf einer HTML-Seite eine komplette Tabelle mit allen möglichen Lösungen enthält: touchportal.de/4bilder/4-bilder-1-wort-komplettlosung/

      Start

      Irgendwo müssen wir mit dem Projekt ja beginnen, also starten wir die Entwicklungsumgebung und legen ein neues Windows Forms Projekt an. Um die Form kümmer ich mich aber erst später. Jetzt gehts erstmal um die Datenbasis.

      Wortliste

      Um also Wörter finden zu können, brauchen wir eine Liste aller möglichen Lösungen. Auf den ersten Blick bietet sich der Typ List(Of String) an. Mir schwebte aber eher eine Liste vor, die direkt auch eine Methode zum Finden der Wörter bereithält. Und das was wir an Informationen für die Suche haben, hilft der List(Of T) leider nicht bei der Suche. Aus den drei offensichtlichen Ansätzen
      1. Eine Funktion schreiben, die mit der List(Of String), den 12 Buchstaben und der Wortlänge gefüttert wird und eine List(Of String) zurückgibt
      2. die Funktion aus (1) als Erweiterungsfunktion programmieren
      3. eine eigene Klasse aus List(Of String) ableiten und dort die Suchfunktion einbauen
      habe ich für mich die dritte Variante gewählt.
      Die Funktion MatchingWords erwartet dabei einen beliebig langen Satz an Buchstaben (es können hier auch weniger oder auch mehr als 12 Buchstaben sein) und optional die Anzahl der Buchstaben des gesuchten Worts. Wird die Anzahl nicht angegeben, sollen alle passenden Wörter unabhängig von deren Länge ermittelt werden. Das Ergebnis ist natürlich wiederum eine Wortliste.

      VB.NET-Quellcode

      1. Public Class Wordlist
      2. Inherits List(Of String)
      3. Public Function MatchingWords(ByVal availableChars As String, Optional ByVal wordLength As Integer = 0) As Wordlist
      4. Dim result As New Wordlist()
      5. For Each word As String In Me
      6. If (wordLength = 0 OrElse word.Length = wordLength) Then
      7. Dim found As Boolean = True
      8. If Not String.IsNullOrEmpty(availableChars) Then
      9. Dim availCharsCopy As String = availableChars.ToUpperInvariant()
      10. For Each c As Char In word.ToUpperInvariant()
      11. Dim p As Integer = availCharsCopy.IndexOf(c)
      12. If (p < 0) Then
      13. found = False
      14. Exit For
      15. End If
      16. availCharsCopy = availCharsCopy.Remove(p, 1)
      17. Next
      18. End If
      19. If found Then
      20. result.Add(word)
      21. End If
      22. End If
      23. Next
      24. Return result
      25. End Function
      26. End Class


      Wortliste füllen

      Bevor wir überhaupt irgendein Ergebnis bekommen, muss natürlich eine Wortliste zur Verfügung stehen, die mit den passenden Lösungen gefüllt ist. Da die Wörter alle auf einer Webseite zur Verfügung stehen, die zudem (zumindest zurzeit) bei Rätsel-Updates relativ zeitnah aktualisiert wird, bietet es sich an, diese jeweils direkt von dort zu beziehen.
      Da ich mir die Scherereien mit schlecht programmiertem HTML oder XML schenken will, benutze ich keine Klasse zum Interpretieren solcher Dokumente, sondern wusel mich auf String-Ebene durch den Quelltext. Die fragliche Seite stellt die Lösungen in einer HTML-Tabelle dar, wobei alle Lösungswörter selbst in Tabellenzellen (<td>) stehen, die mit dem Attribut class="column-5" versehen sind.
      Als zusätzliche Schwierigkeit kommt hinzu, dass hier manchmal zwei alternative Lösungen in einer Zelle stehen - vermutlich weil der Anbieter der Lösung nicht mehr genau wusste, ob nun z.B. "Disaster" oder "Desaster" die richtige Lösung war. Zum Glück folgt er dabei (bisher) immer demselben Schema, dass zwei Wörter durch "/" voneinander getrennt sind. Wir nehmen der Einfachheit halber beide mit auf und überlassen es dem zur Verfügung stehenden Buchstabensatz bzw. der Intelligenz des Benutzers, zu entscheiden, was nun tatsächlich die richtige Lösung ist.
      Desweiteren gibt es - aus einem mir unerfindlichen Grund - in der Tabelle einige Lösungen, die mit "Level: xxx" oder "Level xxx" angezeigt werden. Das möchte ich nach Möglichkeit ebenfalls filtern.
      Als Ergebnis kam eine Funktion heraus, die die Informationen aus dem Web lädt, die Wörter wie beschrieben herausfiltert und als Wortliste zur Verfügung stellt.
      Da sie direkt mit der Klasse Wordlist in Abhängigkeit steht und eine Instanz davon zurückgibt, fand ich es logisch, sie als Shared-Funktion dieser Klasse zu implementieren:

      VB.NET-Quellcode

      1. Public Class Wordlist
      2. Inherits List(Of String)
      3. '...
      4. Public Shared Function LoadFromWeb() As Wordlist
      5. Const WEB_ADDRESS As String = "http://touchportal.de/4bilder/4-bilder-1-wort-komplettlosung/"
      6. Dim result As New Wordlist()
      7. Dim wc As New Net.WebClient()
      8. Using inStream As IO.Stream = wc.OpenRead(WEB_ADDRESS)
      9. Using reader As New IO.StreamReader(inStream)
      10. Do
      11. Dim line As String = reader.ReadLine()
      12. If line Is Nothing Then
      13. Return result
      14. End If
      15. If Not String.IsNullOrEmpty(line) Then
      16. line = line.ToUpperInvariant() 'alles in Großbuchstaben wandeln
      17. Dim p As Integer = -1
      18. Do
      19. p = line.IndexOf("<TD CLASS=""COLUMN-5""", p + 1)
      20. If p >= 0 Then
      21. Dim subLine As String = line.Substring(p)
      22. Dim p2 As Integer = subLine.IndexOf(">"c)
      23. If p2 >= 0 Then
      24. subLine = subLine.Substring(p2 + 1)
      25. p2 = subLine.IndexOf("<"c)
      26. If p2 >= 0 Then
      27. Dim solutionText As String = subLine.Substring(0, p2)
      28. If solutionText <> "LEVEL" Then
      29. solutionText = solutionText.Replace("LEVEL", "")
      30. End If
      31. Dim solutions As String() = solutionText.Split(" :/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
      32. For Each wort In solutions
      33. If Not result.Contains(wort) Then
      34. result.Add(wort)
      35. End If
      36. Next
      37. End If
      38. End If
      39. End If
      40. Loop Until p < 0
      41. End If
      42. Loop
      43. End Using
      44. End Using
      45. Return result
      46. End Function
      47. End Class

      Damit sind alle Voraussetzungen erfüllt und wir können uns an die Form machen.

      MainForm

      So einfach wie möglich: Eine TextBox als Eingabefeld. Wir setzen - finde ich persönlich gefälliger - die Eigenschaft CharacterCasing auf Upper und MaxLength auf 12. Alles andere ist mehr oder weniger "egal".
      Für die Auswahl, wieviele Buchstaben das Lösungswort haben sollte, habe ich eine Reihe aus 8 RadioButtons vorgesehen, jeweils beschriftet mit "Alle" und den Zahlen "2" bis "8". Ich hasse es, sowas von vornherein auf die Form zu klatschen, also platzier ich nur einen Button und lasse die restlichen 7 dynamisch generieren, sozusagen als Kopie des vorgegebenen, wobei der vorplatzierte praktisch die Position vorgibt und alle anderen sich dahinter reihen sollen.
      Zu guter Letzt wollen wir auch die Ergebnisse irgendwo sehen, also setzen wir eine beliebige Listbox irgendwo hin. Die muss ja nur anzeigen. Mehr nicht.


      Im Form_Load Event richten wir das alles soweit her, d.h. wir besorgen uns die Wortliste aus dem Web und erzeugen sieben Kopien unseres Buttons. Den Click-Handler weisen wir auch gleich zu, und damit wir uns nicht von Meldungen im Fehlerfenster nerven lassen müssen, legen wir dafür euch gleich den Rumpf mit an:

      VB.NET-Quellcode

      1. Public Class Form1
      2. Private _allSolutions As Wordlist
      3. Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
      4. _allSolutions = Wordlist.LoadFromWeb()
      5. TextBox1.MaxLength = 12
      6. TextBox1.CharacterCasing = CharacterCasing.Upper
      7. ' Eventhandler für den ersten Button einrichten
      8. Dim refButton As RadioButton = Button1
      9. AddHandler refButton.Click, AddressOf Me.ButtonClick
      10. 'Buttons "2" bis "8" erzeugen und am jeweils vorherigen Button ausrichten
      11. For i As Integer = 2 To 8
      12. Dim btn As RadioButton = New RadioButton()
      13. btn.Appearance = refButton.Appearance
      14. btn.AutoSize = True
      15. btn.Location = New Point(refButton.Right + 1, refButton.Top)
      16. btn.Name = String.Format("btn{0}", i)
      17. btn.Text = i.ToString()
      18. AddHandler btn.Click, AddressOf Me.ButtonClick
      19. Me.Controls.Add(btn)
      20. 'neuen Button für den nächsten als Referenz merken
      21. refButton = btn
      22. Next
      23. 'alle Tastatureingaben zunächst zum KeyPress-Handler der Form umlenken
      24. Me.KeyPreview = True
      25. 'TODO:
      26. 'Jetzt kann man - damit es schön ist - noch Breite von Text- und ListBox an die Buttonzeile anpassen
      27. 'und die Breite der Form ändern, damit alles schön sichtbar ist und nicht so "hingerotzt" aussieht.
      28. End Sub
      29. Private Sub ButtonClick(sender As Object, e As EventArgs)
      30. ' kommt noch...
      31. End Sub
      32. End Class


      Buttons per Taste

      Bei den ersten Versionen meines Programms hatte mich dann aber doch eine Kleinigkeit gestört. Hatte ich brav meine 12 Buchstaben abgetippt und wollte nun die Auswahl auf Wörter bestimmter Länge begrenzen, musste ich mit der rechten Hand von der Tastatur auf die Maus wechseln um den Button zu klicken. Alternativ hätte ich mehrere TABs und ENTER nehmen können, aber das ist doch viel zu viel Tastengefrickel. Auch Shortcut-Keys, die ich dann mit Alt+irgendwas erreiche war mir zu viel Verrenkung, immerhin gebe ich ins Textfeld auschließlich Buchstaben ein. Wieso also nicht die Zahlen für den Direktzugriff auf die Buttons missbrauchen?
      So kam es, dass im vorigen Code-Abschnitt sich die Zeile "Me.KeyPreview = True" eingeschlichen hat.
      Um die Tasten dann auch entsprechend abzufangen, überschreibe ich einfach die OnKeyPress-Methode der Form-Klasse. Dort werden nicht nur die Tasten "0" und "2" bis "8" auf die entsprechenden Button-Clicks umgelenkt, sondern ich fange zudem die Escape-Taste ab, um das Programm mit Hilfe eines Tastendrucks beenden zu können. Ich liebe solche Shortcuts ;)

      VB.NET-Quellcode

      1. Protected Overrides Sub OnKeyPress(e As System.Windows.Forms.KeyPressEventArgs)
      2. Dim key As Short = Convert.ToInt16(e.KeyChar)
      3. If key = Keys.Escape Then
      4. Me.Close()
      5. Else
      6. Dim btn As RadioButton = Nothing
      7. If key = Keys.D0 Then
      8. btn = Button1
      9. ElseIf key >= Keys.D2 And key <= Keys.D8 Then
      10. btn = CType(Controls.Item(String.Format("btn{0}", e.KeyChar)), RadioButton)
      11. End If
      12. If btn IsNot Nothing Then
      13. e.Handled = True
      14. btn.PerformClick()
      15. Return
      16. End If
      17. End If
      18. MyBase.OnKeyPress(e)
      19. End Sub

      Natürlich könnte man hier aus der gedrückten Taste gleich die Wortlänge ermitteln und die Listbox entsprechend füllen, aber es ging mir hierbei darum, exakt das Auslösen des Buttons mit der entsprechenden Zifferntaste zu bewirken. Wollte ich irgendwann das Verhalten eines Buttons ändern, müsste ich dann Code an zwei unterschiedlichen Stellen ändern. Das halte ich für ineffizient. Darum der Umweg über den Button und PerformClick().

      Lösungen suchen (und finden)

      Das wichtigste fehlt ja eigentlich noch: Nämlich die Verknüpfung der eingegebenen Buchstaben mit der Suchfunktion. Das erledigen wir einfach im TextChanged-Event der Textbox, so sehen wir ggf. schon während der Eingabe der verfügbaren Buchstaben, was eventuell als Lösung in Betracht kommen könnte. Da wir über die Buttons später steuern wollen, auf wieviel Buchstaben die Lösungswörter beschränkt sein sollen, benutzen wir dafür eine eigene Variable auf Klassenebene.

      VB.NET-Quellcode

      1. Private _wordlength As Integer
      2. Private Sub FindAndDisplay()
      3. Dim result As Wordlist = _allSolutions.MatchingWords(TextBox1.Text, _wordlength)
      4. ListBox1.Items.Clear()
      5. For Each solution In result
      6. ListBox1.Items.Add(solution)
      7. Next
      8. End Sub


      Restliche Logik

      Fehlen noch die eigentlichen Events für Button.Click und TextBox.TextChanged:

      VB.NET-Quellcode

      1. Private Sub TextBox1_TextChanged(sender As System.Object, e As System.EventArgs) Handles TextBox1.TextChanged
      2. FindAndDisplay()
      3. If TextBox1.Text.Length = 12 Then
      4. TextBox1.SelectAll()
      5. End If
      6. End Sub
      7. Private Sub ButtonClick(sender As Object, e As EventArgs)
      8. Integer.TryParse(CType(sender, RadioButton).Text, _wordlength)
      9. FindAndDisplay()
      10. End Sub

      Das Integer.TryParse macht aus dem Text "Alle" des ersten Buttons automatisch den Wert 0. Das ist recht praktisch, weil das genau der Wert ist den wir brauchen, um die Lösungsliste nicht auf eine bestimmte Wortlänge einzuschränken.

      Was fehlt?
      Ein paar Kleinigkeiten kann man sicher noch verbessern. Zum einen das Layout, wie an der entsprechenden Stelle aber schon angemerkt.
      Dann ist mir aufgefallen, dass wenn man mit seinen Wurstfingern statt eine der Ziffern 2 bis 8 oder 0 doch mal die 1 oder 9 erwischen sollte, dass diese dann doch wieder an das Textfeld geht. Das kann man aber auch noch abfangen. (Hausaufgabe! ;) )
      Dadurch dass bei jedem Programmstart die Webseite abgerufen und analysiert wird zieht der Start sich für so eine Pipifax-Anwendung ziemlich hin. Eine Verbesserung könnte es sein, die Daten aus dem Web nur auf spezielle Anforderung (Button?) zu laden und dann lokal in einer Datei oder Datenbank zu speichern, die stattdessen verwendet wird.

      Vielleicht kennt und nutzt ja der eine oder andere von euch die App "4 Bilder 1 Wort" und findet mit der Hilfe dieses Tools endlich die Lösung, an der er seit Wochen festhängt und deshalb nicht weiterspielen kann? :D
      Dateien
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Arby“ () aus folgendem Grund: Projektdateien hinzugefügt

      Damit habe ich mich bisher noch nicht bewusst auseinandergesetzt und ich muss zugeben dass ich jetzt spontan auch nicht wüsste wie mir das helfen könnte. Hast du nen Vorschlag wie man da rangehen könnte?
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.
      1. Verwende kein Deutsch-Englisch-Mischmasch sondern nur eins von beidem (vorzugsweise Englisch)
      2. Sieh dir mal das hier an msdn.microsoft.com/en-us/library/ms229042.aspx, dein Code ist nicht sehr schön anzusehen
      3. In deiner Logik gibts auch ein paar Macken.
      Zum Beispiel hier:

      VB.NET-Quellcode

      1. Public Function FindePassende(ByVal BuchstabenAuswahl As String, Optional ByVal AnzahlBuchstaben As Integer = 0) As Wortliste
      BuchstabenAuswahl wäre wohl eher als Char-Array zu deklarieren. Und normalerweise verwendet man -1 um anzuzeigen, dass keine bestimmte Anzahl angegeben wurde (siehe Substring, IndexOf, Split usw.).
      Oder auch das:

      VB.NET-Quellcode

      1. If e.KeyChar = "0"c OrElse (e.KeyChar >= "2"c AndAlso e.KeyChar <= "8"c) Then
      2. Dim btn As Button
      3. If e.KeyChar = "0"c Then
      4. btn = Button1
      5. Else
      6. btn = CType(Me.Controls.Find(String.Format("btn{0}", e.KeyChar), True)(0), Button)
      7. End If
      8. If btn IsNot Nothing Then
      9. e.Handled = True
      10. btn.PerformClick()
      11. Return
      12. End If
      Sehr schlechter Stil. Konvertiere den Char doch einfach gleich in ne Zahl und setzte diese als Wortlänge fest. Ebenso kannst du dann auch beim Zuweisen der Click-Handler für die Buttons eine Lambda-Expression verwenden, so kannst du jedem Button direkt weine Zahl zuweisen unds sparst dir das ganze Konvertieren das Button-Textes.
      "_wortleange" sollte imo auch keine globale Variable sein. Übergib das einfach der "FindeLoesungen"-Funktion, denn nur die verarbeitet den Wert.
      @Artentus
      1. Ich bin selbst normalerweise aus der Fraktion, die genau diesen Standpunkt vertritt. Aber manchmal gehts trotzdem auch mit mir durch. Kann ich irgendwie nix gegen machen ;) In der Regel verwende ich sogar Englisch. In diesem Fall dachte ich bewusst deutsche Bezeichner zu verwenden, wegen des Charakters eines Guides. Ist mir nichtmal aufgefallen, dass ich das nicht durchgängig gemacht habe...
      2. Nein, ich habe jetzt weder Zeit noch Lust mir (wieder) den gesamten Designguide durchzulesen. Auf was genau beziehst du dich?
      3. Ja, da ist noch ein wenig Verbesserungspotential drin. Der Grund, warum ich _wortlaenge als Memberfield und nicht als Funktionsparameter gewählt habe, war, dass es "eigentlich" vorgesehen war, dass die Methode auch im TextChanged-Event aufgerufen werden sollte. Aus irgendeinem Grund hab ich den Teil aber komplett vergessen mit aufzunehmen. Ist mir erst gestern Abend aufgefallen und wird noch von mir korrigiert - kann ich aber frühestens am Montag machen.
      Mit diesen Lambda-Expression steh ich allerdings auf Kriegsfuß - mit denen muss ich mich erst anfreunden, bis dahin muss ich ohne sowas auskommen, wenn ich "mal eben" sowas umsetzen will.
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.
      du hast zwar absichtlich keinen Download drangemacht, damit Anfänger sich mehr damit auseinandersetzen, aber imo setzen die sich grad deswegen ühaupt nicht damit auseinander.
      So wie ich mich auch nur unzureichend damit auseinandersetze, nämlich zureichend würde bedeuten, ich würde eine entsprechende Solution aufsetzen, und dazu bin ich zu faul, und ausserdem käst es mich an, denn ich weiß doch, dass du genau so eine Solution bei dir doch schon lange hast, und sie nur nicht rausrückst.
      Zum verzippen (nur der Sources!) kannichdir SolutionExplorer andienen - dassis gleichzeitig auch ein praktisches Backup-Tool.
      Also was 2. betrifft: Parameter schreibt man klein.
      Aber auch Dinge wie Klassendesign gehören da dazu. Da ich mir eigentlich nur den Code angesehen habe, aber nicht die Texte, hab ich keinen genauen Plan, wie ich das ordentlich implementieren würde, aber auf jeden Fall wirkt deine Struktur nicht sonderlich gut verwoben. Vielleicht spiele ich hier auch gerade wieder den "Astronauten", aber ich bin seit einiger Zeit sehr stark dazu übergegangen, möglichst alles in Klassen und Interfaces auszulagern und diese dann stark miteinander interagieren zu lassen, anstatt alles in einer Instanz zu regeln. Der Code wirkt so einfach "Vollständiger", weil alles einen bestimmten Platzt in einem großen Gebilde hat, wohingegen sowas wie das hier (so hab ich früher übrigens auch programmiert, ist wohl eine Frage der Übung) eher alleine steht.
      Wahrscheinlich hab ich gerade nen ziemlichen Stuss gelabert, ich wollte nur mal so zum Ausdruck bringen, was mein Gefühl mir sagt (entgegen der Meinungen anderer bin ich davon überzeugt, dass Intuition und eine Bindung zum Code wichtig ist). ^^

      Artentus schrieb:

      Also was 2. betrifft: Parameter schreibt man klein.

      Und die Stringkonstante vermutlich in Caps. Du hast recht, das ist nicht sehr konsistent.

      Artentus schrieb:

      Aber auch Dinge wie Klassendesign gehören da dazu. [...]
      Wahrscheinlich hab ich gerade nen ziemlichen Stuss gelabert, [...]

      Ich würd jetzt nicht hingehen und von "Stuss" reden, aber das Projekt ist halt nicht so umfangreich, dass ich da fünf oder mehr Klassen draus bauen kann. Wenn du es dir etwas genauer ansiehst, stellst du vielleicht fest, dass nicht alles in einer Klasse (bzw. in der Form) steckt. Aber mehr als die eine zusätzliche Klasse, die die Liste von Wörtern repräsentiert, gibt das Projekt meiner Meinung nach einfach nicht her...

      Aber ich werde mir EDRs Anmerkung zu Herzen nehmen und bei Gelegenheit das Projekt als Paket hinzufügen, dann wird es sicher einfacher sich da reinzudenken...
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.
      Wir Astronauten können aus so ziemlich allem ne Klasse/Interface oder sonst was machen. Man muss halt immer abschätzen, inwiefern es noch sinnvoll ist.
      Mach es einfach so, wie du es für richtig hältst, wahrscheinlich hab ich einfach schon ne zu große Dosis @~blaze~: abbekommen (wobei ich ja denke, die Dosis kann nicht groß genug sein :P).
      Kleines Update:

      Ich habe mir ein paar der kritisierten Punkte zu Herzen genommen und den Code diesbezüglich ein bisschen angepasst. Allerdings habe ich nicht alle "Wünsche" dabei berücksichtigt, und das größtenteils mit voller Absicht:

      1. Die sogenannten Designguides sind ne tolle Sache, ich mag es auch, wenn alles einheitlich ist und liebe am .NET-Visual Studio, dass es bei uns im Büro endlich keinen Streit mehr darüber gibt, ob z.B. öffnende geschweifte Klammern in einem C/C#-Code in eine neue Zeile gehören oder nicht. Aber ich stimme nicht immer 100 % mit den "Vorgaben" überein was die Benennung von Bezeichnern angeht. Immerhin habe ich es mir seit VS.NET bereits abgewöhnt, ein Prefix vor meine Variablen zu setzen, der den Variablentyp angibt. Ich habe es mit aber angewöhnt, Variablen (in der Regel) mit kleinem Buchstaben beginnen zu lassen, es sei denn, es handelt sich um public Member. Und um lokale von (privaten) Instanzvariablen zu unterscheiden, bekommen letztere von mir grundsätzlich einen "_" vorangestellt.

      2. Artentus' Einwand zu der OnKeyPress-Funktion, dass man doch den Tastencode direkt ohne Umweg über den Button in der Wortlänge speichern könnte, stimmt ich nicht zu. Ich habs im Text des ersten Beitrags auch schon entsprechend ergänzt, aber gerade von der Logik her finde ich meinen Ansatz besser, denn schließlich soll die Taste für den User das gleiche sein wie das Klicken des entsprechenden Buttons. Soll sich also irgendwann das Verhalten des Buttons ändern, bräuchte ich jetzt nur den Click-Handler des Buttons anzufassen. Mit Artentus' Vorschlag müsste ich diese Änderung auch an (mindestens) einer zweiten Stelle im Code berücksichtigen.

      Im übrigen habe ich nun das Projekt auch als ZIP hinzugefügt.
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.
      mein gott - hab ich gebraucht, um zu kapieren, was das Proggi eiglich macht (ich kenne halt dieses komische Online-Spiel nicht):

      Es findet alle Worte, die sich aus den eingegebenen Buchstaben zusammensetzen lassen. Optional auch eingeschränkt auf eine bestimmte Wortlänge.
      Dabei lädt es die Wortliste aus dem Internet.

      Algorithmisch recht hübsch, und ich find auch sauber gecodet.

      Interessant, dassich an dem Beispiel auch paar meiner Guidelines zeigen kann, nämlich: code so knapp wie möglich, dann ist das wesentliche leichter zu erkennen.
      Etwa das Gui könntest du komplett im Designer machen, und dann vereinfacht sich die Logik in frmMain so:

      VB.NET-Quellcode

      1. Public Class frmMain
      2. Private _allSolutions As Wordlist = Wordlist.LoadFromWeb2()
      3. Private _wordlength As Integer
      4. Protected Overrides Sub OnKeyPress(e As System.Windows.Forms.KeyPressEventArgs)
      5. Dim key = CType(Convert.ToInt16(e.KeyChar), Keys)
      6. Dim btn As RadioButton = Nothing
      7. Select Case key
      8. Case Keys.Escape : Me.Close()
      9. Case Keys.D0 : btn = Button1
      10. Case Keys.D2 To Keys.D8
      11. btn = FlowLayoutPanel1.Controls.OfType(Of RadioButton)().FirstOrDefault(Function(bt) bt.Text = e.KeyChar)
      12. End Select
      13. If btn IsNot Nothing Then
      14. e.Handled = True
      15. btn.PerformClick()
      16. 'Return 'die Weiterleitung an die Basis-Methode sollte man nur in gut begründeten Fällen unterdrücken
      17. End If
      18. MyBase.OnKeyPress(e)
      19. End Sub
      20. Private Sub TextBox1_TextChanged(sender As System.Object, e As System.EventArgs) Handles TextBox1.TextChanged
      21. ListBox1.DataSource = _allSolutions.MatchingWords(TextBox1.Text, _wordlength)
      22. If TextBox1.Text.Length = 12 Then TextBox1.SelectAll()
      23. End Sub
      24. Private Sub ButtonClick(sender As Object, e As EventArgs) Handles Button1.Click, Button2.Click, Button3.Click, Button4.Click, Button5.Click, Button6.Click, Button7.Click, Button8.Click
      25. Integer.TryParse(CType(sender, RadioButton).Text, _wordlength)
      26. ListBox1.DataSource = _allSolutions.MatchingWords(TextBox1.Text, _wordlength)
      27. End Sub
      28. End Class
      Das geht locker auf einen Bildschirm, und es ist immer ein gutes Zeichen, wenn der Code einer Klasse auf einen Bildschirm geht, da wird die Kohärenz der Klasse (die innere Zusammengehörigkeit) klar ersichtlich.

      Imo gibts keinen Grund, je ein schlampiges Gui zu machen. (Insbesondere das Verhalten deines Guis beim Resizen ist eine Peinlichkeit.)
      Das Layout-System in WinForms einzusetzen ist eigentlich ein Kinderspiel, und reine Übungssache.
      Also je öfter du das einsetzst, desto besser wirst du, und desto leichter wird es.

      Eine andere meine Richtlinie ist, Verschachtelung zu vermeiden. Lieber ein einzeiliges if, ein Continue Do, als sich da 6 Ebenen tief einzubuddeln, und am Ende mit sowas klarkommen zu müssen:

      VB.NET-Quellcode

      1. End If
      2. Next
      3. End If
      4. End If
      5. End If
      6. Loop Until p < 0
      7. End If
      8. Loop
      9. End Using
      10. End Using
      Meine Variante:

      VB.NET-Quellcode

      1. Public Shared Function LoadFromWeb2() As Wordlist
      2. Const WEB_ADDRESS As String = "http://touchportal.de/4bilder/4-bilder-1-wort-komplettlosung/"
      3. Dim result As New Wordlist()
      4. Dim wc As New Net.WebClient()
      5. Using inStream As IO.Stream = wc.OpenRead(WEB_ADDRESS), reader As New StreamReader(inStream)
      6. Do
      7. Dim line As String = reader.ReadLine()
      8. If line Is Nothing Then Return result 'Stream-Ende -> Ergebnis zurückgeben
      9. If line = "" Then Continue Do 'leere Zeilen übergehen
      10. line = line.ToUpperInvariant() 'alles in Großbuchstaben wandeln
      11. 'theoretisch könnte das gesuchte <TD>-Tag mehrmals pro Zeile vorkommen,
      12. 'also behandeln wir das in einer Do-While-Schleife und merken uns die
      13. 'aktuelle Startposition für die Suche in p.
      14. Dim p As Integer = -1
      15. Do
      16. p = line.IndexOf("<TD CLASS=""COLUMN-5""", p + 1)
      17. If p < 0 Then Continue Do
      18. 'Tabellenzelle gefunden, schließendes ">" suchen
      19. Dim subLine As String = line.Substring(p)
      20. Dim p2 As Integer = subLine.IndexOf(">"c)
      21. If p2 < 0 Then Continue Do
      22. 'alles bis zum nächsten "<"-Zeichen (Start des </TD> Tags) nehmen
      23. subLine = subLine.Substring(p2 + 1)
      24. p2 = subLine.IndexOf("<"c)
      25. If p2 < 0 Then Exit Do
      26. 'wenn nur "Level" drinsteht, muss das als Lösung angenommen und darf nicht gefiltert werden
      27. 'Ansonsten haben wir eine Zeile erwischt, wo "Level xxx" oder "Level: xxx" drinsteht,
      28. 'dann muss das "Level" weg.
      29. Dim solutionText = subLine.Substring(0, p2).Replace("LEVEL", "")
      30. 'Leerzeichen, Doppelpunkt und Schrägstrich als Worttrenner nehmen, dabei leere Split-Ergebnisse verwerfen
      31. Dim solutions As String() = solutionText.Split(" :/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
      32. For Each entry As String In solutions
      33. 'identische Lösungswörter werden nur einmal gebraucht
      34. If Not result.Contains(entry) Then result.Add(entry)
      35. Next
      36. Loop Until p < 0
      37. Loop
      38. End Using
      39. 'Eigentlich überflüssig -> sinnvoller wäre es ggf., hier eine Exception zu werfen, da man
      40. 'hier nur dann rauskommt - wenn überhaupt - wenn beim Lesen der Webseite irgendwas schiefgelaufen ist
      41. 'Return result 'also weg damit
      42. End Function
      Es ist original dein Code, und nur durch Umstellung sind etwa 30% Zeilen eingespart (und es geht wieder auf einen Bildschirm :)), sowie 7 Verschachtelungs-Ebenen.
      Dateien

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Arby schrieb:

      Ich möchte also mit Hilfe der 12 verfügbaren Buchstaben alle die möglichen Lösungswörter finden, die sich mit diesem Satz bilden lassen. Als zusätzliche Einschränkung sollen hiervon wiederum nur diejenigen gewählt werden, bei denen die Anzahl der Buchstaben des Lösungsworts übereinstimmt. Die möglichen Lösungswörter sollen als eine Liste von Wörtern zur Verfügung stehen

      Aber... da steht's doch auch ;)
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.