Here's a very basic (although structured, functional, and extensible) implementation. I didn't include any error checking of user input. So the user needs to make sure that there's the right number (25 by default) of characters in the key, split doubletons in the plain text, make sure only alphabet characters are in the plaintext or ciphertext, and make sure that texts have an even number of characters. However, I did generalize it so that you are not restricted to (the traditional) 5x5 alphabet: You can use any rectangle (just change R and C in the module locals).
A basic implementation of the Playfair cipher (see "Playfair cipher" in Wikipedia).
There is no error checking of user input, and the user has to split doubletons on
option `Written 19-Apr-2013 by Carl Love.`;
R:= 5, C:= 5, #For a 5x5=25 alphabet
#Convert number in 1..25 into row-column coordinates
to_row_col:= proc(n) local r,c; r:= iquo(n-1, C, 'c'); (r+1, c+1) end proc,
#Convert row-column coordinates to number in 1..25
from_row_col:= (r,c)-> C*(r-1)+c,
rotf:= k-> k mod C + 1, #mod C, shifted by 1 (rotate forward)
rotd:= k-> k mod R + 1, #mod R, shifted by 1 (rotate down)
#For a pair in 1..R*C, return the corresponding Playfair pair. Note that if
#we just consider the indices in the array, then the mapping from index-pair
#to index-pair is a constant, independent of the cipher key.
local r1, r2, c1, c2;
if a=b then error "expected unequal arguments" end if;
if r1=r2 then (from_row_col(r1,rotf(c1)), from_row_col(r2,rotf(c2)))
elif c1=c2 then (from_row_col(rotd(r1),c1), from_row_col(rotd(r2),c2))
else (from_row_col(r1,c2), from_row_col(r2,c1))
EnDig:= table(), #Encrypt: Digraph to Digraph
DeDig:= table() #Decrypt: Digraph to Digraph
local i, j, i2, j2, DigPlain, DigCrypt;
#Encrypt and decrypt every possible digraph.
for i to R*C do for j to R*C do
if i=j then next end if;
end do end do;
cat(seq(EnDig[plaintext[2*k-1..2*k]], k= 1..iquo(length(plaintext),2)))
cat(seq(DeDig[ciphertext[2*k-1..2*k]], k= 1..iquo(length(ciphertext),2)))