rev. 10jan2008

PiF 9

  <terug>
Invoer en uitvoer van sudoku's


Nog meer over sudoku's?

Er zijn belangrijker zaken op de wereld dan sudoku's, maar het prettige van sudoku's als programmeeronderwerp is dat het voor iedereen toegankelijk is. Je hoeft geen wiskundige of wat voor wetenschapper dan ook te zijn, geen hardware deskundige of electronica knutselaar. Wat je nodig hebt is Forth en een beetje gezond verstand.

Het programma dat we nu hebbben kun je met van alles uitbreiden. Het aardige daaraan is dat ook de beginnende forther nu eens een groter programma ziet, en meemaakt hoe het tot stand komt. En het ook begrijpt (hoop ik dan). Bovendien kunnen er allerlei aspecten van Forth de revue passeren, want het programma focust niet op één bepaald specialisme.

Op het gevaar af dat je denkt "hou nou eens op over die sudoku's" ga ik dus toch nog maar een stapje verder.


Invoer van een (gedeeltelijk) ingevulde sudoku

Op zoek naar een methode om een sudoku-opgave aan het programma door te geven kom je al gauw op het idee om een commando te definiëren dat een string op stack nodig heeft. Zoiets als:
 s" 4::3:7:1:39:::5:::::::4:::6 enz. " !sudoku
Dit heeft als nadeel dat de string onhanteerbaar lang is. Opknippen in stukjes maakt het ook niet eenvoudiger. Daarom stel ik het volgende voor:
 sudoku
4::3:7:1:  39:::5:::  ::::4:::6 enz.
Het woord SUDOKU vist zelf de sudokugegevens uit de invoerstroom. Er zijn geen hulptekens of aanhalingstekens nodig en het formaat van de gegevens is volkomen vrij, alleen het aantal tekens en hun volgorde is van belang. De gegevens hoeven niet op één regel te staan. Drie regels met 3 groepjes van 9 karakters (of negen regels van 9 karakters) lijkt me handzaam, maar het mag anders.
Het protocol daarbij is:
  1. Negeer spaties.
  2. Een geldig symbool wordt ingevuld.
  3. Ieder ander karakter representeert een leeg vakje.
  4. Stop met inlezen als de sudoku vol is.

Drie voorbeelden, alle met dezelfde sudoku:
sudoku
4-- 3-7 -1-
39- --5 ---
--- -4- --6

-5- --- --3
--- 1-8 4--
--2 --- -7-

--8 --- --9
--1 -3- ---
6-- --- --5
sudoku
4..3.7.1.  39...5...  ....4...6
.5......3  ...1.84..  ..2....7.
..8.....9  ..1.3....  6.......5
sudoku
400 307 010   390 005 000   000 040 006
050 000 003   000 108 400   002 000 070
008 000 009   001 030 000   600 000 005

Je zou SUDOKU en de gegevens erachter met de hand in Forth in kunnen typen, maar de bedoeling is natuurlijk dat je ze in een file zet die je gewoon kunt 'includen'.
- Let op: ga eerst met [escape] uit het SU-programma, zodat de ok-prompt verschijnt.

Als je het woord SUDOKU met zijn gegevens erachter in een file met de muis selecteert en kopieert kun je die in Win32forth rechtstreeks in het Win32forth-venster plakken bij de ok-prompt.

De pseudo-code voor SUDOKU

: SUDOKU ( <sudokugegevens> -- )
  Lees het volgende woord uit de invoerstroom.
  BEGIN   Verwijder het eerste karakter uit het woord.
          Is dat een geldig symbool?
          IF    Zet het in het volgende vakje.
          ELSE  Zet het 'leegteken' in het volgende vakje.
          THEN
          Alle vakjes ingevuld?      ?EXIT
          Is de string "op"?
          IF  Lees het volgende woord uit de invoerstroom.
          THEN
  AGAIN ;


BL WORD

Met BL WORD leest Forth een woord uit de invoerstroom. Het resultaat komt op stack als het adres van een counted string.

Aan het eind van een regel komt BL WORD niet verder, hij gaat dan strings met lengte nul afgeven. Over die hobbel moet we hem dus heen helpen.
REFILL ( -- true | false ) is een forthwoord dat de volgende regel probeert te ontvangen in de invoerbuffer. TRUE betekent dat dat gaat lukken, FALSE betekent dat het niet kan (bijvoorbeeld aan het eind van de laatste regel van een file). Met behulp van REFILL maken we het woord BL-WORD (aaneengeschreven) dat nooit een nulstring oplevert. Als REFILL een FALSE oplevert wordt het programma afgebroken met een foutmelding.

: bl-word ( -- counted-string )
  begin bl word
        dup c@ ?exit     \ Er is tekst gevonden.
        drop refill      \ Volgende regel in de invoerbuffer.
        0=               \ Niet mogelijk?
  until -39 throw ;      \ Onverwacht inputeinde


Nog een paar hulpwoordjes

SCHOON markeert alle vakjes als 'leeg' en initieert de historie-stack. Daardoor hoef je bij het inlezen alleen nog maar iets te doen als er een geldig symbool voorbij komt.
: schoon ( -- ) (sdk zz 255 fill h-ini ; \ Wis de hele sudoku
Verder is het handig om iedere sudoku van een naam te voorzien. Daarvoor zijn NAAM en het hulpwoordje !STRING nodig.
create naam 1 c, char ? c, 30 allot align
Ruimte voor de naam: count en 31 karakters.
: !string ( adr len adr2 -- )  \ Zet de string adr,len als
  tuck c! count move ;         \    counted string in adr2.
In het hoofdwoord SU komt een extra regel die de naam laat zien:
        cr space naam count type


Het woord SUDOKU

Het eerste woord na SUDOKU is de naam, daarna volgt de inhoud van de sudoku.
: sudoku ( <sudokunaam> <sudoku-inhoud> -- )
  schoon
  bl-word count            \ Naam
  31 min naam !string      \ Naam opslaan.
  bl-word count            \ Eerste brok sudoku-inhoud
  zz 0
  do   dup 0=                    \ De string is leeg?
       if   2drop bl-word count  \ Volgende woord
       then over c@              \ Volgende karakter
       symbool? if i noteer then
       1 /string
  loop 2drop ;

Zie helemaal onderaan in de file sudo3.f


Opgave 9

Maak het woord .SUDOKU dat de stand van het moment afdrukt in een vorm die je met SUDOKU weer kunt inlezen.

: .SUDOKU ( -- ) ... ;

De afgedrukte tekst kan dan rechtstreeks of in een log-file met de muis geselecteerd worden en naar een file gekopieerd worden om te bewaren.


 
Groeten,
A.N.
  <terug>