nnfc

No-nonsense FreeCell game
git clone git://git.luxferre.top/nnfc.git
Log | Files | Refs

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:
Mnnfc.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}