implemented column moving heuristics, restart command and undo fixes
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user