Extended Straddling Checkerboard: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|Phix}}: moot point tweaks)
(→‎{{header|jq}}: more idiomatic version of decode)
 
(4 intermediate revisions by 3 users not shown)
Line 5: Line 5:
Implement encoding and decoding of a message using the extended straddling checkerboard, CT-37w, as described in the reference below.
Implement encoding and decoding of a message using the extended straddling checkerboard, CT-37w, as described in the reference below.


You may switch the codes for F/L (99) and SUPP (98) to help differentiate the code for '9' from that of '999', so we would then have F/L (98) and SUPP (99) as follows:
You may switch the codes for F/L (99) and SUPP (98) to help differentiate the code for '9' from that of '999' and, if you do that, then digits only needed to be doubled rather than tripled. So we would then have the following checkerboard:
<pre>
<pre>
A E I N O T CODE
A E I N O T CODE
Line 20: Line 20:


0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
00 11 22 33 44 55 66 77 88 99
000 111 222 333 444 555 666 777 888 999
</pre>
</pre>


Line 34: Line 34:
;Reference:
;Reference:
:* Cipher Machines web page on Checkerboards: [https://www.ciphermachinesandcryptology.com/en/table.htm Checkerboards]
:* Cipher Machines web page on Checkerboards: [https://www.ciphermachinesandcryptology.com/en/table.htm Checkerboards]

=={{header|jq}}==
'''Adapted from [[#Wren|Wren]]'''

'''Works with jq, the C implementation of jq'''

'''Works with gojq, the Go implementation of jq'''

'''Works with jaq, the Rust implementation of jq'''
<syntaxhighlight lang="jq">
### Generic utility
# Emit a stream of the constituent codepoints:
def chars: explode[] | [.] | implode;

### The checkerboard
def efigs: "0123456789";

def drow1: "012345";

def checkerboard:
def row1: "AEINOT";
def row2: "BCDFGHJKLM";
def row3: "PQRSUVWXYZ";
def row4: " .";
{ ewords: {
"SPC": "90", "DOT": "91",
"ACK": "92", "REQ": "93", "MSG": "94", "RV": "95",
"GRID": "96", "SEND": "97", "FSL": "98", "SUPP": "99"
},
emap: {},
dmap: {},
dwords:{}
}
| reduce range(0; row1|length) as $i (.; .emap[row1[$i:$i+1]] = ($i|tostring) )
| reduce range(0; row2|length) as $i (.; .emap[row2[$i:$i+1]] = ((70 + $i)|tostring))
| reduce range(0; row3|length) as $i (.; .emap[row3[$i:$i+1]] = ((80 + $i)|tostring))
| reduce range(0; row4|length) as $i (.; .emap[row4[$i:$i+1]] = ((90 + $i)|tostring))

| reduce (.emap|keys[]) as $k (.; .dmap[.emap[$k]] = $k)
| reduce (.ewords|keys[]) as $k (.; .dwords[.ewords[$k]] = $k) ;

def encode:
(ascii_upcase|split(" ")) as $words
| ($words|length) as $wc
| checkerboard
| .res = ""
| reduce range(0; $wc) as $i (.;
$words[$i] as $word
| .add = ""
| if .ewords[$word]
then .add = .ewords[$word]
elif .ewords[$word[0:-1]] and ($word|endswith("."))
then .add = .ewords[$word[0:-1]] + .ewords["DOT"]
elif $word|startswith("CODE")
then .add = "6" + $word[4:]
else .figs = false
| reduce ($word|chars) as $c (.;
if (efigs|contains($c))
then if .figs
then .add += 2 * $c
else .figs = true
| .add += .ewords["FSL"] + 2 * $c
end
else .emap[$c] as $ec
| if ($ec|not)
then "Message contains unrecognized character '\($c)'" | error
else if .figs
then .add += .ewords["FSL"] + $ec
| .figs = false
else .add += $ec
end
end
end )
| if .figs and ($i < $wc - 1)
then .add += .ewords["FSL"]
else .
end
end
| .res += .add
| if ($i < $wc - 1) then .res += .ewords["SPC"] else . end
)
| .res ;

def decode:
{s: .} + checkerboard
| .ewords["FSL"] as $fsl
| .res = ""
| .figs = false
| until (.s == "";
.s[0:1] as $c
| .ix = -1
| if .figs
then if (.s | startswith($fsl) | not)
then .res += $c
else .figs = false
end
| .s |= .[2:]
else .ix = (drow1|index($c))
| if .ix and .ix >= 0
then .res += .dmap[drow1[.ix:.ix+1]]
| .s |= .[1:]
elif $c == "6"
then .res += "CODE" + .s[1:4]
| .s |= .[4:]
elif $c == "7" or $c == "8"
then .s[1:2] as $d
| .res += .dmap[$c + $d]
| .s |= .[2:]
elif $c == "9"
then .s[1:2] as $d
| if $d == "0"
then .res += " "
elif $d == "1"
then .res += "."
elif $d == "8"
then .figs |= not
else .res += .dwords[$c + $d]
end
| .s |= .[2:]
end
end )
| .res ;

### Demonstration
def demo:
"Message:\n\(.)",
(encode
| "\nEncoded:\n\(.)",
"\nDecoded:\n\(decode)" );

"Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March"
| demo
</syntaxhighlight>
{{output}}
<pre>
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March

Encoded:
0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175

Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
</pre>


=={{header|Julia}}==
=={{header|Julia}}==
Line 76: Line 220:
if contains(efigs, c)
if contains(efigs, c)
if figs
if figs
add *= c^3
add *= c^2
else
else
figs = true
figs = true
add *= fsl * c^3
add *= fsl * c^2
end
end
else
else
Line 112: Line 256:
if s[i:i+1] != fsl
if s[i:i+1] != fsl
res *= c
res *= c
i += 3
i += 2
else
else
figs = false
figs = false
Line 156: Line 300:


Encoded:
Encoded:
07279239092908848482907983749190629190979073848257518290982200000098909990549075819070889098119890790827175
0727923909290884848290798374919062919097907384825751829098222000000000989099905490758190708890981119890790827175


Decoded:
Decoded:
Line 280: Line 424:


=={{header|Python}}==
=={{header|Python}}==
See the discussion for the different checkerboard table handling.
<syntaxhighlight lang="python">""" rosettacode.org/wiki/Extended_Straddling_Checkerboard """
<syntaxhighlight lang="python">""" rosettacode.org/wiki/Extended_Straddling_Checkerboard """


Line 294: Line 439:
'SUPP': 'π',
'SUPP': 'π',
}
}
SDICT = {v: k for (k, v) in WDICT.items()} # reversed WDICT for reverse lookup on decode
# reversed WDICT for reverse lookup on decode
SDICT = {v: k for (k, v) in WDICT.items()}


# CT37w at https://www.ciphermachinesandcryptology.com/en/table.htm
# web site CT37w, but '/' (FIG) char is 98 not 99 to help differentiate from code for 9 of '999'
CT37w = [['', 'A', 'E', 'I', 'N', 'O', 'T', 'κ', '', '', '',],
CT37w = [['', 'A', 'E', 'I', 'N', 'O', 'T', 'κ', '', '', '',],
['7', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',],
['7', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',],
['8', 'P', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z',],
['8', 'P', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z',],
['9', ' ', '.', 'α', 'ρ', 'μ', 'ν', 'γ', 'σ', '/', 'π',],]
['9', ' ', '.', 'α', 'ρ', 'μ', 'ν', 'γ', 'σ', 'π', '/'],]


# Modified CT37w: web site CT37w, but exchange '/' (FIG) char and 'π'
# to help differentiate the '999' encoding for a '9' from a terminator code
CT37w_mod = [['', 'A', 'E', 'I', 'N', 'O', 'T', 'κ', '', '', '',],
['7', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',],
['8', 'P', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z',],
['9', ' ', '.', 'α', 'ρ', 'μ', 'ν', 'γ', 'σ', '/', 'π',],]



def xcb_encode(message, nchangemode='98', code='κ', table=CT37w, wdict=WDICT):
def xcb_encode(message, table=CT37w, code='κ', wdict=WDICT):
"""
"""
Encode with extended straddling checkerboard. Default checkerboard is
Encode with extended straddling checkerboard. Default checkerboard is
Line 313: Line 466:
numericmode, codemode = False, False
numericmode, codemode = False, False
codemodecount = 0
codemodecount = 0
if table[-1][-1] == '/':
nchangemode = '99'
digit_repeats = 3
else:
nchangemode = '98'
digit_repeats = 2

# replace terms found in dictionary with a single char symbol that is in the table
# replace terms found in dictionary with a single char symbol that is in the table
s = reduce(lambda x, p: x.replace(
s = reduce(lambda x, p: x.replace(
p[0], p[1]), wdict.items(), message.upper())
p[0], p[1]), wdict.items(), message.upper())
for c in s:
for c in s:
if c.isnumeric():
if c.isnumeric():
if codemode: # codemode symbols are preceded by the CODE digit '6' then as-is
if codemode: # codemode symbols are preceded by the CODE digit '6' then as-is
encoded.append(c)
encoded.append(c)
codemodecount += 1
codemodecount += 1
Line 324: Line 484:
codemode = False
codemode = False


else: # numeric numbers are triplicate encoded so '3' -> '333'
else:
if not numericmode:
if not numericmode:
numericmode = True
numericmode = True
encoded.append(nchangemode) # FIG
encoded.append(nchangemode) # FIG


encoded.append(c*3)
encoded.append(c*digit_repeats)


else:
else:
codemode = False
codemode = False
if numericmode:
if numericmode:
encoded.append(nchangemode) # end numericmode with the FIG numeric code for '/' (98)
# end numericmode with the FIG numeric code for '/' (98)
encoded.append(nchangemode)
numericmode = False
numericmode = False


Line 350: Line 511:




def xcb_decode(s, nchangemode='98', code='κ', table=CT37w, sdict=SDICT):
def xcb_decode(s, table=CT37w, code='κ', sdict=SDICT):
""" Decode extended straddling checkerboard """
""" Decode extended straddling checkerboard """
numbers = {c*3: c for c in list('0123456789')}
prefixes = sorted([row[0] for row in table], reverse=True)
prefixes = sorted([row[0] for row in table], reverse=True)
pos = 0
pos, numericmode, codemode = 0, False, False
numericmode = False
codemode = False
decoded = []
decoded = []
if table[-1][-1] == '/':
nchangemode = '99'
digit_repeats = 3
else:
nchangemode = '98'
digit_repeats = 2
numbers = {c*digit_repeats: c for c in list('0123456789')}
while pos < len(s):
while pos < len(s):
if numericmode:
if numericmode:
if s[pos:pos+3] in numbers:
if s[pos:pos+digit_repeats] in numbers:
decoded.append(numbers[s[pos:pos+3]])
decoded.append(numbers[s[pos:pos+digit_repeats]])
pos += 2
pos += digit_repeats - 1
elif s[pos:pos+2] == nchangemode:
elif s[pos:pos+2] == nchangemode:
numericmode = False
numericmode = False
pos += 1
pos += 1
elif decoded[-1] == '9': # error, so backtrack if last was 9
decoded.pop()
numericmode = False
pos -= digit_repeats - 1


elif codemode:
elif codemode:
Line 377: Line 546:
numericmode = not numericmode
numericmode = not numericmode
pos += 1
pos += 1

else:
else:
for p in prefixes:
for p in prefixes:
Line 386: Line 556:
if c == code:
if c == code:
codemode = True
codemode = True

pos += n
pos += n
break
break
Line 400: Line 571:
print('Encoded: ', xcb_encode(MESSAGE))
print('Encoded: ', xcb_encode(MESSAGE))
print('Decoded: ', xcb_decode(xcb_encode(MESSAGE)))
print('Decoded: ', xcb_decode(xcb_encode(MESSAGE)))
print('Encoded: ', xcb_encode(MESSAGE, CT37w_mod))
print('Decoded: ', xcb_decode(xcb_encode(MESSAGE, CT37w_mod), CT37w_mod))


</syntaxhighlight>{{out}}
</syntaxhighlight>{{out}}
<pre>
<pre>
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
Encoded: 072792390929088484829094919062919097907384825751829099222000000000999098905490758190708890991119990790827175
Encoded: 072792390929088484829094919062919097907384825751829098222000000000989099905490758190708890981119890790827175
Decoded: ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
Encoded: 0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175
Decoded: ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
Decoded: ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
</pre>
</pre>
Line 410: Line 585:
=={{header|Wren}}==
=={{header|Wren}}==
{{libheader|Wren-str}}
{{libheader|Wren-str}}
For consistency with the Python example, this uses the CT-37w checkerboard with F/L changed to 98 and SUPP to 99.
<syntaxhighlight lang="wren">import "./str" for Str
<syntaxhighlight lang="wren">import "./str" for Str


Line 457: Line 631:
if (efigs.contains(c)) {
if (efigs.contains(c)) {
if (figs) {
if (figs) {
add = add + c * 3
add = add + c * 2
} else {
} else {
figs = true
figs = true
add = add + fsl + c * 3
add = add + fsl + c * 2
}
}
} else {
} else {
Line 494: Line 668:
if (s[i..i+1] != fsl) {
if (s[i..i+1] != fsl) {
res = res + c
res = res + c
i = i + 3
} else {
} else {
figs = false
figs = false
i = i + 2
}
}
i = i + 2
} else if ((ix = drow1.indexOf(c)) >= 0) {
} else if ((ix = drow1.indexOf(c)) >= 0) {
res = res + dmap[drow1[ix]]
res = res + dmap[drow1[ix]]
Line 539: Line 712:


Encoded:
Encoded:
0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175
072792390929088484829094919062919097907384825751829098222000000000989099905490758190708890981119890790827175


Decoded:
Decoded:

Latest revision as of 01:56, 8 April 2024

Extended Straddling Checkerboard is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

An extended Straddling Checkerboard, is like the regular Straddling checkerboard, but allows word dictionaries and arbitrary functional codes such as FIGURE, where you can specify a number literally.

Task

Implement encoding and decoding of a message using the extended straddling checkerboard, CT-37w, as described in the reference below.

You may switch the codes for F/L (99) and SUPP (98) to help differentiate the code for '9' from that of '999' and, if you do that, then digits only needed to be doubled rather than tripled. So we would then have the following checkerboard:

  A   E   I   N   O   T  CODE
  0   1   2   3   4   5   6

  B   C   D   F   G   H   J   K   L   M
 70  71  72  73  74  75  76  77  78  79

  P   Q   R   S   U   V   W   X   Y   Z
 80  81  82  83  84  85  86  87  88  89

SPC (.) ACK REQ MSG  RV GRD SND F/L SUP
 90  91  92  93  94  95  96  97  98  99

  0   1   2   3   4   5   6   7   8   9
 00  11  22  33  44  55  66  77  88  99

There is no need to create a word dictionary for CODE (6). It suffices to just include CODE followed by some 3 digit number in the message to be encoded.

Test your solution by encoding and decoding the message:

'Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March'

Related task
Reference

jq

Adapted from Wren

Works with jq, the C implementation of jq

Works with gojq, the Go implementation of jq

Works with jaq, the Rust implementation of jq

### Generic utility
# Emit a stream of the constituent codepoints:
def chars: explode[] | [.] | implode;

### The checkerboard
def efigs: "0123456789";

def drow1: "012345";

def checkerboard:
  def row1: "AEINOT";
  def row2: "BCDFGHJKLM";
  def row3: "PQRSUVWXYZ";
  def row4: " .";
  { ewords: {
     "SPC":  "90",  "DOT": "91",
     "ACK":  "92",  "REQ": "93", "MSG": "94", "RV": "95",
     "GRID": "96", "SEND": "97", "FSL": "98", "SUPP": "99"
    },
    emap: {},
    dmap: {},
    dwords:{}
  }
  | reduce range(0; row1|length) as $i (.; .emap[row1[$i:$i+1]] = ($i|tostring) )
  | reduce range(0; row2|length) as $i (.; .emap[row2[$i:$i+1]] = ((70 + $i)|tostring))
  | reduce range(0; row3|length) as $i (.; .emap[row3[$i:$i+1]] = ((80 + $i)|tostring))
  | reduce range(0; row4|length) as $i (.; .emap[row4[$i:$i+1]] = ((90 + $i)|tostring))

  | reduce (.emap|keys[])   as $k (.; .dmap[.emap[$k]] = $k)
  | reduce (.ewords|keys[]) as $k (.; .dwords[.ewords[$k]] = $k) ;

def encode:
  (ascii_upcase|split(" ")) as $words
  | ($words|length) as $wc
  | checkerboard
  | .res = ""
  | reduce range(0; $wc) as $i (.;
      $words[$i] as $word
      | .add = ""
      |  if .ewords[$word]
         then .add = .ewords[$word]
         elif .ewords[$word[0:-1]] and ($word|endswith("."))
         then .add = .ewords[$word[0:-1]] + .ewords["DOT"]
         elif $word|startswith("CODE")
         then .add = "6" + $word[4:]
         else .figs = false
         | reduce ($word|chars) as $c (.;
             if (efigs|contains($c))
             then if .figs
                  then .add += 2 * $c
                  else .figs = true
                  | .add += .ewords["FSL"] + 2 * $c
                  end
             else .emap[$c] as $ec
             | if ($ec|not) 
               then  "Message contains unrecognized character '\($c)'" | error
               else if .figs
                    then .add += .ewords["FSL"] + $ec
                    | .figs = false
                    else .add += $ec
                    end
                end
             end )
         | if .figs and ($i < $wc - 1)
           then .add += .ewords["FSL"]
           else .
           end
         end 
         | .res += .add
         | if ($i < $wc - 1) then .res += .ewords["SPC"] else . end
  )
  | .res ;

def decode:
  {s: .} + checkerboard
  | .ewords["FSL"] as $fsl
  | .res = ""
  | .figs = false
  | until (.s == "";
      .s[0:1] as $c
      | .ix = -1
      | if .figs
        then if (.s | startswith($fsl) | not)
             then .res += $c
             else .figs = false
             end
             | .s |= .[2:]
        else .ix = (drow1|index($c))
        | if .ix and .ix >= 0
          then .res += .dmap[drow1[.ix:.ix+1]]
          | .s |= .[1:]
          elif $c == "6"
          then .res += "CODE" + .s[1:4]
          | .s |= .[4:]
          elif $c == "7" or $c == "8"
          then .s[1:2] as $d
          | .res += .dmap[$c + $d]
          | .s |= .[2:]
          elif $c == "9"
          then .s[1:2] as $d
          | if $d == "0"
            then .res += " "
            elif $d == "1"
            then .res +=  "."
            elif $d == "8"
            then .figs |= not
            else .res += .dwords[$c + $d]
            end
            | .s |= .[2:]
          end
         end )
  | .res ;

### Demonstration
def demo:
  "Message:\n\(.)",
   (encode
    | "\nEncoded:\n\(.)",
      "\nDecoded:\n\(decode)" );

"Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March"
| demo
Output:
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March

Encoded:
0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175

Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH

Julia

Translation of: Wren
const row1, row2, row3, row4 = "AEINOT", "BCDFGHJKLM", "PQRSUVWXYZ", " ."
const emap = Dict{String,String}()
for (row, k) in [(row1, -1), (row2, 69), (row3, 79), (row4, 89)]
    for i in eachindex(row)
        emap[string(row[i])] = string(i + k)
    end
end
const dmap = Dict{String,String}(v => k for (k, v) in emap)

const ewords = Dict{String,String}(
    "ACK" => "92",
    "REQ" => "93",
    "MSG" => "94",
    "RV" => "95",
    "GRID" => "96",
    "SEND" => "97",
    "SUPP" => "99",
)
const dwords = Dict{String,String}(v => k for (k, v) in ewords)

const efigs, spc, dot, fsl, drow1 = "0123456789", "90", "91", "98", "012345"

function encode(s)
    s, res = uppercase(s), ""
    words = split(s, r"\s")
    wc = length(words)
    for i = 1:wc
        word, add = words[i], ""
        if haskey(ewords, word)
            add = ewords[word]
        elseif haskey(ewords, word[begin:end-1]) && word[end] == "."
            add = ewords[word[begin:end-1]] * dot
        elseif startswith(word, "CODE")
            add = "6" * word[begin+4:end]
        else
            figs = false
            for c in word
                if contains(efigs, c)
                    if figs
                        add *= c^2
                    else
                        figs = true
                        add *= fsl * c^2
                    end
                else
                    ec = get(emap, string(c), "")
                    isempty(ec) && error("Message contains unrecognized character $c.")
                    if figs
                        add *= fsl * ec
                        figs = false
                    else
                        add *= ec
                    end
                end
            end
            if figs && i <= wc - 1
                add *= fsl
            end
        end
        res *= add
        if i <= wc - 1
            res *= spc
        end
    end
    return res
end

function decode(s)
    res, sc, figs, i = "", length(s), false, 1
    while i <= sc
        ch = s[i]
        c = string(ch)
        if figs
            if s[i:i+1] != fsl
                res *= c
                i += 2
            else
                figs = false
                i += 2
            end
        elseif !((ix = findfirst(==(ch), drow1)) isa Nothing)
            res *= dmap[string(drow1[ix])]
            i += 1
        elseif c == "6"
            res *= "CODE" * s[i+1:i+3]
            i += 4
        elseif c == "7" || c == "8"
            d = string(s[i+1])
            res *= dmap[c*d]
            i += 2
        elseif c == "9"
            d = string(s[i+1])
            if d == "0"
                res *= " "
            elseif d == "1"
                res *= "."
            elseif d == "8"
                figs = !figs
            else
                res *= dwords[c*d]
            end
            i += 2
        end
    end
    return res
end

const msg = "Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March"
println("Message:\n$msg")
enc = encode(msg)
println("\nEncoded:\n$enc")
dec = decode(enc)
println("\nDecoded:\n$dec")
Output:
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March

Encoded:
07279239092908848482907983749190629190979073848257518290982200000098909990549075819070889098119890790827175

Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH

Phix

Translation of: Julia

Note this includes the two tiny tweaks to encode digits in two characters, as per "Moot point" on the talk page.

with javascript_semantics
constant emap = new_dict(),
         dmap = new_dict(),
         ewds = new_dict(),
         dwds = new_dict(),
         spc = "90", dot = "91", fsl = "98"
for d in {{"AEINOT",-1},{"BCDFGHJKLM",69},{"PQRSUVWXYZ",79},{" .",89}} do
    integer k = d[2]
    for i,ch in d[1] do
        string ik = sprintf("%d",i+k)
        setd(ch,ik,emap)
        setd(ik,ch,dmap)
    end for
end for
for d in {{"ACK","92"},{"REQ","93"},{"MSG","94"},{"RV","95"},
          {"GRID","96"},{"SEND","97"},{"SUPP","99"}} do
    string {k,v} = d
    setd(k,v,ewds)          
    setd(v,k,dwds)          
end for

function encode(string s)
    string res = ""
    sequence words = split(upper(s))
    integer wc = length(words)
    for i=1 to wc do
        string wrd = words[i], a = ""
        if getd_index(wrd,ewds) then
            a = getd(wrd,ewds)
        elsif getd_index(wrd[1..-2]) and wrd[$] == '.' then
            a = getd(wrd[1..-2],ewds) & dot
        elsif begins("CODE",wrd) then
            a = "6" & wrd[5..$]
        else
            bool figs = false
            for c in wrd do
                if find(c,"0123456789") then -- (efigs)
                    if not figs then figs = true; a &= fsl end if
--                  a &= repeat(c,3)
                    a &= repeat(c,2)
                elsif not getd_index(c,emap) then
                    throw("Message contains unrecognized character %c.",{c})
                else
                    if figs then figs = false; a &= fsl end if
                    a &= getd(c,emap)
                end if
            end for
            if figs and i<=wc-1 then a &= fsl end if
        end if
        res &= a
        if i <= wc-1 then res &= spc end if
    end for
    return res
end function

function decode(string s)
    string res = ""
    integer sc = length(s), figs = false, i = 1
    while i <= sc do
        integer c = s[i]
        if figs then
            if s[i..i+1] != fsl then
                res &= c
--              i += 3
                i += 2
            else
                figs = false
                i += 2
            end if
        elsif find(c,"012345") then -- row 1
            res &= getd(c&"",dmap)
            i += 1
        elsif c == '6' then
            res &= "CODE" & s[i+1..i+3]
            i += 4
        elsif c == '7'
           or c == '8' then
            res &= getd(s[i..i+1],dmap)
            i += 2
        elsif c == '9' then
            integer d = s[i+1]
            if d == '0' then
                res &= " "
            elsif d == '1' then
                res &= "."
            elsif d == '8' then
                figs = not figs
            else
                res &= getd(s[i..i+1],dwds)
            end if
            i += 2
        end if
    end while
    return res
end function

constant msg = "Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March",
         enc = encode(msg),
         unc = decode(enc)
printf(1,"Message:\n%s\n\nEncoded:\n%s\n\nDecoded:\n%s\n",{msg,enc,unc})
Output:
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March

Encoded:
07279239092908848482907983749190629190979073848257518290982200000098909990549075819070889098119890790827175

Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH

Python

See the discussion for the different checkerboard table handling.

""" rosettacode.org/wiki/Extended_Straddling_Checkerboard """

from functools import reduce

WDICT = {
    'CODE': 'κ',
    'ACK': 'α',
    'REQ': 'ρ',
    'MSG': 'μ',
    'RV': 'ν',
    'GRID': 'γ',
    'SEND': 'σ',
    'SUPP': 'π',
}
# reversed WDICT for reverse lookup on decode
SDICT = {v: k for (k, v) in WDICT.items()}

# CT37w at https://www.ciphermachinesandcryptology.com/en/table.htm
CT37w = [['',  'A', 'E', 'I', 'N', 'O', 'T', 'κ', '',  '',  '',],
         ['7', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',],
         ['8', 'P', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z',],
         ['9', ' ', '.', 'α', 'ρ', 'μ', 'ν', 'γ', 'σ', 'π', '/'],]

# Modified CT37w: web site CT37w, but exchange '/' (FIG) char and 'π'
# to help differentiate the '999' encoding for a '9' from a terminator code
CT37w_mod = [['',  'A', 'E', 'I', 'N', 'O', 'T', 'κ', '',  '',  '',],
             ['7', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',],
             ['8', 'P', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y', 'Z',],
             ['9', ' ', '.', 'α', 'ρ', 'μ', 'ν', 'γ', 'σ', '/', 'π',],]


def xcb_encode(message, table=CT37w, code='κ', wdict=WDICT):
    """
        Encode with extended straddling checkerboard. Default checkerboard is
        CT37w at https://www.ciphermachinesandcryptology.com/en/table.htm
        The numeric mode has the numbers as repeated in triplicate
        The CODE mode expects a 3-digit numeric code
    """
    encoded = []
    numericmode, codemode = False, False
    codemodecount = 0
    if table[-1][-1] == '/':
        nchangemode = '99'
        digit_repeats = 3
    else:
        nchangemode = '98'
        digit_repeats = 2

    # replace terms found in dictionary with a single char symbol that is in the table
    s = reduce(lambda x, p: x.replace(
        p[0], p[1]), wdict.items(), message.upper())
    for c in s:
        if c.isnumeric():
            if codemode:  # codemode symbols are preceded by the CODE digit '6' then as-is
                encoded.append(c)
                codemodecount += 1
                if codemodecount >= 3:
                    codemode = False

            else:  
                if not numericmode:
                    numericmode = True
                    encoded.append(nchangemode)  # FIG

                encoded.append(c*digit_repeats)

        else:
            codemode = False
            if numericmode:
                # end numericmode with the FIG numeric code for '/' (98)
                encoded.append(nchangemode)
                numericmode = False

            if c == code:
                codemode = True
                codemodecount = 0

            for row in table:
                if c in row:
                    k = row.index(c)
                    encoded.append(str(row[0]) + str(k-1))
                    break

    return ''.join(encoded)


def xcb_decode(s, table=CT37w, code='κ', sdict=SDICT):
    """ Decode extended straddling checkerboard """
    prefixes = sorted([row[0] for row in table], reverse=True)
    pos, numericmode, codemode = 0, False, False
    decoded = []
    if table[-1][-1] == '/':
        nchangemode = '99'
        digit_repeats = 3
    else:
        nchangemode = '98'
        digit_repeats = 2
    numbers = {c*digit_repeats: c for c in list('0123456789')}
    while pos < len(s):
        if numericmode:
            if s[pos:pos+digit_repeats] in numbers:
                decoded.append(numbers[s[pos:pos+digit_repeats]])
                pos += digit_repeats - 1
            elif s[pos:pos+2] == nchangemode:
                numericmode = False
                pos += 1
            elif decoded[-1] == '9':  # error, so backtrack if last was 9
                decoded.pop()
                numericmode = False
                pos -= digit_repeats - 1

        elif codemode:
            if (s[pos:pos+3]).isnumeric():
                decoded.append(s[pos:pos+3])
                pos += 2

            codemode = False

        elif s[pos:pos+2] == nchangemode:
            numericmode = not numericmode
            pos += 1

        else:
            for p in prefixes:
                if s[pos:].startswith(p):
                    n = len(p)
                    row = next(i for i, r in enumerate(table) if p == r[0])
                    c = table[row][int(s[pos+n])+1]
                    decoded.append(c)
                    if c == code:
                        codemode = True

                    pos += n
                    break

        pos += 1

    return reduce(lambda x, p: x.replace(p[0], p[1]), sdict.items(), ''.join(decoded))


if __name__ == '__main__':

    MESSAGE = 'Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March'
    print(MESSAGE)
    print('Encoded: ', xcb_encode(MESSAGE))
    print('Decoded: ', xcb_decode(xcb_encode(MESSAGE)))
    print('Encoded: ', xcb_encode(MESSAGE, CT37w_mod))
    print('Decoded: ', xcb_decode(xcb_encode(MESSAGE, CT37w_mod), CT37w_mod))
Output:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March
Encoded:  072792390929088484829094919062919097907384825751829099222000000000999098905490758190708890991119990790827175
Decoded:  ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH
Encoded:  0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175
Decoded:  ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH

Wren

Library: Wren-str
import "./str" for Str

var row1 = "AEINOT"
var row2 = "BCDFGHJKLM"
var row3 = "PQRSUVWXYZ"
var row4 = " ."

var emap = {}
for (i in 0...row1.count) emap[row1[i]] = i.toString
for (i in 0...row2.count) emap[row2[i]] = (70 + i).toString
for (i in 0...row3.count) emap[row3[i]] = (80 + i).toString
for (i in 0...row4.count) emap[row4[i]] = (90 + i).toString
var ewords = {
    "ACK": "92", "REQ": "93", "MSG": "94", "RV": "95",
    "GRID": "96", "SEND": "97", "SUPP": "99"
}
var efigs = "0123456789"
var spc = "90"
var dot = "91"
var fsl = "98"

var dmap = {}
var dwords = {}
for (k in emap.keys) dmap[emap[k]] = k
for (k in ewords.keys) dwords[ewords[k]] = k
var drow1 = "012345"

var encode = Fn.new { |s|
    s = Str.upper(s)
    var res = ""
    var words = s.split(" ")
    var wc = words.count
    for (i in 0...wc) {
        var word = words[i]
        var add = ""
        if (ewords.containsKey(word)) {
            add = ewords[word]
        } else if (ewords.containsKey(word[0...-1]) && word[-1] == ".") {
            add = ewords[word[0...-1]] + dot
        } else if (word.startsWith("CODE")) {
            add = "6" + word[4..-1]
        } else {
            var figs = false
            for (c in word) {
                if (efigs.contains(c)) {
                    if (figs) {
                        add = add + c * 2
                    } else {
                        figs = true
                        add = add + fsl + c * 2
                    }
                } else {
                    var ec = emap[c]
                    if (!ec) {
                        Fiber.abort("Message contains unrecognized character '%(c)'.")
                    }
                    if (figs) {
                        add = add + fsl + ec
                        figs = false
                    } else {
                        add = add + ec
                    }
                }
            }
            if (figs && i < wc - 1) add = add + fsl
        }
        res = res + add
        if (i < wc - 1) res = res + spc
    }
    return res
}

var decode = Fn.new { |s|
    var res = ""
    var sc = s.count
    var figs = false
    var i = 0
    while (i < sc) {
        var c = s[i]
        var ix = -1
        if (figs) {
            if (s[i..i+1] != fsl) {
                res = res + c
            } else {
                figs = false
            }
            i = i + 2
        } else if ((ix = drow1.indexOf(c)) >= 0) {
            res = res + dmap[drow1[ix]]
            i = i + 1
        } else if (c == "6") {
            res = res + "CODE" + s[i+1..i+3]
            i = i + 4
        } else if (c == "7" || c == "8") {
            var d = s[i+1]
            res = res + dmap[c + d]
            i = i + 2
        } else if (c == "9") {
            var d = s[i+1]
            if (d == "0") {
                res = res + " "
            } else if (d == "1") {
                res = res + "."
            } else if (d == "8") {
                figs = !figs
            } else {
                res = res + dwords[c + d]
            }
            i = i + 2
        }
    }
    return res
}
               
var msg = "Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March"
System.print("Message:\n%(msg)")
var enc = encode.call(msg)
System.print("\nEncoded:\n%(enc)")
var dec = decode.call(enc)
System.print("\nDecoded:\n%(dec)")
Output:
Message:
Admin ACK your MSG. CODE291 SEND further 2000 SUPP to HQ by 1 March

Encoded:
0727923909290884848290949190629190979073848257518290982200000098909990549075819070889098119890790827175

Decoded:
ADMIN ACK YOUR MSG. CODE291 SEND FURTHER 2000 SUPP TO HQ BY 1 MARCH