commit 34f30cd92bf011d6f31d9f1c028187a14f81fe8a
parent b5a10f1a8efb303071203b4e34b6602478c6c253
Author: Luxferre <lux@ferre>
Date:   Wed, 17 Jan 2024 16:22:32 +0200
implemented column moving heuristics, restart command and undo fixes
Diffstat:
| M | nnfc.awk | | | 121 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ | 
1 file changed, 66 insertions(+), 55 deletions(-)
diff --git a/nnfc.awk b/nnfc.awk
@@ -1,13 +1,11 @@
 # no-nonsense FreeCell in POSIX AWK
 # run as: [n]awk -f nnfc.awk [-v ASCII=1] [-v MONOCHROME=1] [-v SEED=x] [-v NAH=1]
-# commands: q - quit, u - undo, ah - autohome, h - help,
+# commands: q - quit, u - undo, h - help, r - restart the run
 # [number][number] - move from column/freecell to column/freecell/foundation:
 # - 0 is foundation (can be omitted)
 # - 1 to 8 are column numbers
 # - a, b, c, d are freecell numbers
-# bulk move/supermove operation between columns: [number][number] [number]
-# the first two numbers _must_ only be column numbers (1 to 8)
-# the second (space separated) number is how many top cards we move
+# bulk move/supermove operation between columns is performed automatically when possible
 # SEED variable can be used to init games M$-style
 # created by Luxferre in 2024, released into public domain
 
@@ -53,6 +51,11 @@ function getsuit(n) {return int(n/13)}
 # get card color by numeric value: 0 is red, 1 is black
 function getcolor(n, si) {si = getsuit(n); return (si > 0 && si < 3) ? 0 : 1}
 
+# main card stacking condition
+function stackcond(hi, lo) {
+  return (getcolor(hi) != getcolor(lo)) && ((lo%13) == (hi%13) - 1)
+}
+
 # Traditional M$ FreeCell LCG for deal emulation
 function fclcg() {
   fc_lstate = (214013 * fc_lstate + 2531011) % 2147483648
@@ -85,28 +88,30 @@ function render(i, j, res) {
   print res # finally print out the resulting field
 }
 
+function undo(i) { # undo functionality
+  if(undoidx > 0) {
+    for(i=0;i<undomoves;i++) {
+      undoidx--
+      if(undoidx >= 0) {
+        assocdes(undos["table",undoidx], table, ",")
+        assocdes(undos["fnd",undoidx], fnd, ",")
+        assocdes(undos["fcell",undoidx], fcell, ",")
+        assocdes(undos["tablens",undoidx], tablens, ",")
+      }
+    }
+    if(undoidx < 0) undoidx = 0
+  } else print "Nowhere to undo!"
+}
+
 # main logic/move function (also handles the undo)
 # statuses: 0 - invalid move, 1 - continue, 2 - victory
-function domove(cmd,  valid, cvalid, nums, ngrp, from, to, amt, i, si, ec, efc) {
-  if(cmd == "u") { # undo logic
-    if(undoidx > 0) {
-      undoidx--
-      assocdes(undos["table",undoidx], table, ",")
-      assocdes(undos["fnd",undoidx], fnd, ",")
-      assocdes(undos["fcell",undoidx], fcell, ",")
-      assocdes(undos["tablens",undoidx], tablens, ",")
-    } else print "Nowhere to undo!"
-    return 1 # continue
-  }
+function domove(cmd,  valid, nums, from, to, amt, i, si, ec, efc) {
   valid = 0 # invalid by default until all checks are done
-  # if cmd is not q, h, ah or u, then it must be one or two "numbers"
-  split(cmd, ngrp, " ") # parse the command as a whole
-  split(ngrp[1], nums, "") # parse the first part
+  # if cmd is not q, h, r or u, then it must be two "digits"
+  split(cmd, nums, "") # parse the command as a whole
   from = nums[1] # location from which we're moving
   to = nums[2] # location to which we're moving
-  amt = int(ngrp[2]) # get the amount of cards being moved
-  if(amt < 1)  amt = 1  # it should be at least 1
-  if(amt > 20) amt = 20 # and at most 20
+  amt = 1 # the amount of cards being moved (default 1)
   # identify free cells
   if(from == "a") from = 9
   if(from == "b") from = 10
@@ -120,33 +125,37 @@ function domove(cmd,  valid, cvalid, nums, ngrp, from, to, amt, i, si, ec, efc) 
   to = int(to)
   rfrom = from - 1 # real from location
   rto = to - 1 # real to location
-  if(amt > 1) { # constraints for moving a stack of cards
-    cvalid = 0 # it uses its own validation flag
-    if(from > 0 && from < 9 && to > 0 && to < 9) { # both must be columns
-      efc = 0 # count empty freecells here
-      ec = 0  # count empty columns and final result here
-      for(i=0;i<4;i++) if(fcell[i] == -1) efc++
-      for(i=0;i<8;i++) if(tablens[i] == 0) ec++
-      # now, calculate the max card amount to move
-      ec = (2^ec) * (efc + 1) # base formula
-      if(tablens[rto] == 0) ec /= 2 # halve if moving to an empty column
-      if(amt <= ec) { # amount constraint is satisfied, now check the stack
-        efc = tablens[rfrom] - amt # reuse for starting index in source tableau
-        ec = 1 # reuse for successful stack arrangement flag
-        for(i=0;i<amt-1;i++) {
-          si = table[rfrom,efc+i] # the current value 
-          cval = table[rfrom,efc+i+1] # the next value
-          if(cval == "") break # we're out of bounds
-          if(!(getcolor(cval) != getcolor(si) && (cval%13) == (si%13) - 1)) {
-            ec = 0
-            break
-          }
-        }
-        if(ec == 1) cvalid = 1 # only then the move is valid
-      }
+  # if moving between columns, we can move over 1 card, detect how many 
+  if(from > 0 && from < 9 && to > 0 && to < 9) { # both must be columns
+    # calculate how many cards we can theoretically move in a stack
+    # starting index is the last in the tableau
+    for(i=tablens[rfrom]-1;i>1;i--) { # check the primary condition
+      si = table[rfrom,i-1] # the previous value 
+      cval = table[rfrom,i] # the current value
+      if(length(cval) == 0 || length(si) == 0) break # we're out of bounds
+      # increase the amount as long as the condition is satisfied
+      if(stackcond(si, cval)) amt++
     }
-    # in any other case, the move is directly invalid
-    if(cvalid == 0) return 0
+    # calculate the max amount of cards we're allowed to move
+    efc = 0 # count empty freecells here
+    ec = 0  # count empty columns and final result here
+    for(i=0;i<4;i++) if(fcell[i] == -1) efc++
+    for(i=0;i<8;i++) if(tablens[i] == 0) ec++
+    # now, calculate the max card amount to move
+    ec = (2^ec) * (efc + 1) # base formula
+    if(tablens[rto] == 0) ec /= 2 # halve if moving to an empty column
+    if(amt > ec) amt = ec # hard limit the amount of moved cards to the max
+    # now check how much of the stack actually matches the target
+    if(tablens[rto] > 0) { # we're moving to a non-empty column
+      efc = tablens[rfrom]-amt # index of the first stack element in the tableau
+      si = table[rto,tablens[rto]-1] # target value
+      for(i=efc;i<tablens[rfrom];i++) { # iterate over the stack
+        # subtract the amount as long as the condition is not met
+        if(stackcond(si, table[rfrom,i])) break
+        else amt--
+      }
+      if(amt < 1) amt = 1 # try moving one card in any case
+    } 
   }
   if(from > 0 && from < 13 && to > -1 && to < 13) { # first check
     # determine what card we're trying to move
@@ -177,7 +186,7 @@ function domove(cmd,  valid, cvalid, nums, ngrp, from, to, amt, i, si, ec, efc) 
               else cval = int(cval)
             }
             si = table[rto,tablens[rto]-1] # target value
-            if((getcolor(cval) != getcolor(si) && (cval%13) == (si%13) - 1) || si == "") {
+            if(stackcond(si, cval) || length(si) == 0) {
               table[rto,tablens[rto]] = cval # copy the card there
               tablens[rto]++ # increase tableau length
               if(from < 9) { # moving from a tableau
@@ -199,6 +208,7 @@ function domove(cmd,  valid, cvalid, nums, ngrp, from, to, amt, i, si, ec, efc) 
     }
   }
   if(valid == 1) { # the move is valid and performed
+    undomoves++ # increase recent move counter
     undoidx++ # increment the undo index
     # save the new playfield data under it
     undos["table",undoidx] = assocser(table, ",")
@@ -244,22 +254,17 @@ function help() {
   print "-v MONOCHROME=1\tdon't use colors"
   print "-v SEED=xxxxx\tset game seed number (M$-compatible)"
   print "-v NAH=1\tdisable autohoming after every move (use ah for this)"
-  print "Commands: q - quit, u - undo, ah - autohome, h -  this help"
+  print "Commands: q - quit, u - undo, r - restart, h -  this help"
   print "Moves: [digit][digit], where \"digits\" are:"
   print "- 0 is foundation (can be omitted)"
   print "- 1 to 8 are column (tableau) numbers"
   print "- a, b, c, d are freecell IDs"
-  print "Supermoves between columns: [digit][digit] [number]"
-  print "The first two digits must only be column numbers (1 to 8)"
-  print "The space-separated number is how many top cards we move"
   print "Examples:"
   print "7a\tmove from column 7 to freecell a"
   print "63\tmove from column 6 to column 3"
   print "d1\tmove from freecell d to column 1"
   print "4\tmove from column 4 to the foundation"
   print "c\tmove from freecell c to the foundation"
-  print "25 3\tmove top 3 cards from column 2 to column 5"
-  print "ah\tmove all eligible cards to the foundation"
   return 1 # continue
 }
 
@@ -323,10 +328,16 @@ BEGIN { # main code part
     if(cmd == "q") {print "Quitting..."; break}
     else if(cmd == "h")  res = help()
     else if(cmd == "ah") res = autohome()
-    else { # pass the command to the logic function
+    else if(cmd == "u")  undo()
+    else if(cmd == "r") { # restart logic
+      while(undoidx > 0) undo()
+      undomoves = 0
+    } else { # pass the command to the logic function
+      undomoves = 0 # set global undo move counter
       res = domove(cmd) 
       if(res == 0) print "Invalid move!"
       else if(NAH != 1 && res < 2) res = autohome()
+      print "Undomoves:", undomoves
     }
     render()
     if(res == 2) {print "Victory!"; break}