PiF 8

  <terug>
- Oplossingen voor de cursorbewegingen uit PiF 7
- Sudokuvakjes invullen en wissen


Oplossingen
voor de cursorbewegingen uit PiF 7

WEST en OOST blijven altijd op dezelfde regel.
 : west ( vaknr -- vaknr )
   dup z mod 0=    \ Begin van een regel?
   if z + then
   1- ;

 : oost ( vaknr -- vaknr )
   1+
   dup z mod 0=    \ Begin van een regel?
   if z - then ;
NOORD en ZUID lopen gevaar om buiten de sudoku te raken.
 : noord ( vaknr -- vaknr )
   z -
   dup 0<          \ Buiten de sudoku?
   if zz + then ;

 : zuid ( vaknr -- vaknr )
   z +
   dup zz < 0=     \ Buiten de sudoku?
   if zz - then ;
of
 : zuid ( vaknr -- vaknr )
   z +
   dup zz < ?exit  \ Binnen de sudoku gebleven!
   zz - ;
of
 : zuid ( vaknr -- vaknr ) \ Vergelijk deze met NOORD
   [ zz z - ] literal -    \ zz - z +
   dup 0<
   if zz + then ;
Nog eens dezelfde woordjes, maar nu zonder IF (goede hersengymnastiek!).
 : west  ( vaknr -- vaknr )       z 2dup mod 0= and +   1- ;
 : oost  ( vaknr -- vaknr )  1+   z 2dup mod 0= and - ;
 : noord ( vaknr -- vaknr )  z -  s>d zz and + ;
 : zuid  ( vaknr -- vaknr )
            [ zz z - ] literal -  s>d zz and + ;
De kortere IF-loze versies zet ik in de file sudo3.f


Een stack met de ingevulde vakjes

Uitgangspunt bij het wissen van vakjes is: van alle ingevulde vakjes is steeds alleen het laatst ingevulde vakje wisbaar.

Bij het invullen moet je dus bijhouden in welke volgorde dat gebeurt (de historie). Bij het wissen kun je dan de weg terug afleggen. De meest voor de hand liggende structuur hiervoor is een stack (last in - first out) met de nummers van ingevulde vakjes. Het is onhandig om de Forth data stack ermee te belasten, want dat blokkeert het gewone stackverkeer.

Zelf een stack bouwen is gelukkig vrij simpel:

 create (historie zz allot align \ Genoeg bytes voor alle vaknummers
 (historie 1- value hp           \ Stackpointer, wijst naar topelement.

 : >h ( x -- )    hp 1+ dup to hp c! ;
 : h> ( -- x )    hp c@ hp 1- to hp ;
 : hleeg? ( -- vlag )    hp (historie u< ;
 : h-ini ( -- )   (historie 1- to hp ;     \ Initiëer de stack
Er is geen controle op underflow. Kijk, voordat je H> gebruikt, met HLEEG? of de stack misschien leeg is.
Aan een eventuele foutmelding met programmastop hebben we niks. Er kunnen namelijk ingevulde vakjes in de sudoku voorkomen die niet op de historie-stack staan. Dat zijn de vakjes die in de sudokuopgave al ingevuld waren en die natuurlijk niet gewist mogen worden.

Als het programma verder goed in elkaar zit, kan een vakje hoogstens eenmaal op stack voorkomen. Overflowdetectie zou daarom alleen in een testfase tijdelijk zinvol kunnen zijn.

Het aantal vakjes in de sudoku mag niet groter zijn dan 256 (vaknrs 0..255) want dan passen de vaknummers niet meer in een byte. Voor grotere sudoku's moet de stack met cellen werken i.p.v. met bytes.


Pseudo-code

 : VAKJE-INVULLEN
   Is er een geldig symbool ingetypt?
   IF  Is het cursorvakje al ingevuld?      ?EXIT
       Noteer de icode in het vakje.
       Zet het vaknr op de historie-stack.
   THEN
 ;
Het is vaak handig om vooraf een pseudo-code te schrijven.
In pseudo-code kun je overzichtelijk weergeven wat (het 'hoe' komt later wel) er moet gebeuren zonder dat je je bekommert om technische details. Geen DUP's en DROP's. Alles gewoon in het nederlands. Van Forth gebruik je dan alleen de besturingswoorden ( : EXIT ?EXIT IF THE ELSE BEGIN AGAIN WHILE REPEAT UNTIL DO LOOP ; ).

Met pseudo-code kun je voorkomen dat je al coderende verdwaalt in een woord dat al maar dikker en dikker dreigt te worden omdat je er aan begint voordat je weet waar het zal eindigen en er steeds weer dingen aan toevoegt die op het laatste moment noodzakelijk blijken te zijn of mogelijkheden moet uitsplitsen die ineens een uitzondering gaan vormen zodat de IF-THEN nestingen liefst ook nog met ELSE erbij groeien als kool en het stackgedrag alleen nog proefondervindelijk achteraf vast te stellen is waarmee er een voor derden ondoordringbare puree ontstaat die je zelf een dag later ook niet meer begrijpt net als deze veel te lange zin.

Zo moet het dus niet (om het effect te verhogen heb ik even geen komma's gebruikt). Een uitgebreid commentaar bij dit soort code maakt de code niet beter.

Hoe minder commentaar er nodig is, hoe beter de code.

En nu we het toch over commentaar hebben,

1)    negate z + true ;      \ Verwissel van teken en tel er z bij op.
2)    negate z + true ;      \ Bereken de icode.
van 1) wordt je natuurlijk niets wijzer, 2) geeft een hint over wat er gaande is, het 'hoe' zie je wel in de code. Deze code vind je een tiental regels verderop.


Een vakje invullen

De onderdelen van de pseudo-code gaan we nu in code omzetten.

SYMBOOL? stelt vast of het een geldig symbool betreft.
Als het antwoord TRUE is, wordt de interne code er bijgeleverd. Dat is handig omdat we anders bij een geldig symbool vervolgens nog een keer de SCAN moeten toepassen om de icode te berekenen. Nu hebben we hem meteen klaar staan voor verdere verwerking.

 : symbool? ( key -- false | icode true )
   (symbolen z rot scan nip  \ Positie vanaf stringeinde
   dup 0= ?exit
   negate z + true ;         \ Bereken de icode.
INGEVULD? kijkt of een vakje al ingevuld is en NOTEER zet de icode in het sudokuvakje.
 : ingevuld? ( hier -- vlag )    (sdk + c@ z < ;
 : noteer    ( icode hier -- )   (sdk + c! ;

In het woord DOEN komt nu de regel:

( key) dup symbool? if hier invullen exit then
Alle ingrediënten voor het woord INVULLEN zijn nu beschikbaar.
 : invullen ( icode hier -- )
   dup ingevuld? if 2drop exit then
   tuck               \ hier icode hier
   noteer             \ Noteer in de sudoku.
   >h ;               \ Historie-administratie
Tot zover zijn we afgelopen zaterdag (8 dec 2007) gekomen.
Nu volgen de opgaven voor de volgende keer.


Opgave 8

 : doen ( key -- key )
   [char] [ over = if  hier west  to hier  exit then
   [char] ] over = if  hier oost  to hier  exit then
   [char] = over = if  hier noord to hier  exit then
   [char] ' over = if  hier zuid  to hier  exit then
   dup symbool?    if  hier invullen exit then
 \  [char] \ over = if  ... exit then                   <-- Opgave 8.1
 \  bl       over = if  hier WIS exit then              <-- Opgave 8.2
 \ ...   ( Andere opties)
 ;

1. Als je een backslash intypt gaat de cursor naar het laatst ingevulde vakje, als dat er tenminste is. Welke code komt er op de plaats van de puntjes te staan?

2. Met de spatiebalk kun je een vakje wissen, maar dat lukt alleen wanneer de cursor op het laatst ingevulde vakje staat.
: WIS ( hier -- ) ... ;

In sudo3.f vind je de code tot nu toe.
In deze file staat ook al de code die bij PiF 9 hoort voor het importeren van sudoku's. Daardoor wordt er, na het laden van de file, een heuse (demo) sudoku zichtbaar als je SU intypt.


 
Groeten,
A.N.
  <terug>