Scala 3, 1492 771 bytes
A port of @Doorknob's Ruby answer in Scala.
Saved 721 bytes thanks to @ceilingcat
Golfed version. Attempt This Online!
object m{def t()=scala.io.StdIn.readLine().trim.replace('j','i').toUpperCase.filter(_.isLetter).toList
def f(t:Array[Array[Char]],c:Char):(Int,Int)={t.zipWithIndex.collectFirst{case(r,i)if r.contains(c)=>(i,r.indexOf(c))}.getOrElse(throw new Exception(s""))}
def main(A:Array[String]):Unit={val k=t().distinct
var F=(k++("ABCDEFGHIKLMNOPQRSTUVWXYZ".filterNot(k.contains))).toArray
val T=Array.tabulate(5,5)((r,c)=>F(r*5+c))
val s=t().foldLeft(List[Char]()){(a,c)=>if(a.nonEmpty&&a.last==c)a:+'X':+c else a:+c}
println(s"${(if(s.size%2<1)s else s:+'X').sliding(2,2).toList.flatMap{p=>val(g,h)=(p(0),p.last)
val(r,c)=f(T,g)
val(q,d)=f(T,h)
if(r==q){List(T(r)((c+1)%5),T(q)((d+1)%5))}else if(c==d){List(T((r+1)%5)(c),T((q+1)%5)(d))}else{List(T(r)(d),T(q)(c))}}.mkString}")}}
Ungolfed version. Attempt This Online!
object Main {
// Function to transform the input string
def transform(text: String): List[Char] = {
// Replace 'j' with 'i', convert to uppercase, remove non-alphabet characters, and split into characters
text.replace('j', 'i').toUpperCase.filter(_.isLetter).toList
}
// Function to generate the Playfair cipher key table
def generateKeyTable(key: String): Array[Array[Char]] = {
val keyChars = transform(key).distinct
val remainingChars = "ABCDEFGHIKLMNOPQRSTUVWXYZ".filterNot(keyChars.contains)
val combinedChars = (keyChars ++ remainingChars).toArray
// Create the 5x5 key table
Array.tabulate(5, 5)((row, col) => combinedChars(row * 5 + col))
}
// Function to prepare the message for encryption
def prepareMessage(msg: String): List[List[Char]] = {
val msgChars = transform(msg)
val preparedMsg = msgChars.foldLeft(List[Char]()) { (acc, char) =>
if (acc.nonEmpty && acc.last == char) acc :+ 'X' :+ char else acc :+ char
}
val evenPreparedMsg = if (preparedMsg.size % 2 == 0) preparedMsg else preparedMsg :+ 'X'
evenPreparedMsg.sliding(2, 2).toList
}
// Function to find coordinates of a character in the key table
def findCoords(tbl: Array[Array[Char]], char: Char): (Int, Int) = {
tbl.zipWithIndex.collectFirst {
case (row, rowIndex) if row.contains(char) => (rowIndex, row.indexOf(char))
}.getOrElse(throw new NoSuchElementException(s"Character $char not found in the table"))
}
// Function to encrypt a message using the Playfair cipher
def encryptMessage(tbl: Array[Array[Char]], msg: List[List[Char]]): String = {
msg.flatMap { pair =>
val (c1, c2) = (pair.head, pair.last)
val (c1Row, c1Col) = findCoords(tbl, c1)
val (c2Row, c2Col) = findCoords(tbl, c2)
if (c1Row == c2Row) {
// Same row
List(tbl(c1Row)((c1Col + 1) % 5), tbl(c2Row)((c2Col + 1) % 5))
} else if (c1Col == c2Col) {
// Same column
List(tbl((c1Row + 1) % 5)(c1Col), tbl((c2Row + 1) % 5)(c2Col))
} else {
// Rectangle swap
List(tbl(c1Row)(c2Col), tbl(c2Row)(c1Col))
}
}.mkString
}
def main(args: Array[String]): Unit = {
val key = scala.io.StdIn.readLine().trim
val msg = scala.io.StdIn.readLine().trim
// Generate the key table and prepare the message
val keyTable = generateKeyTable(key)
val preparedMsg = prepareMessage(msg)
// Encrypt the message
val encryptedMsg = encryptMessage(keyTable, preparedMsg)
// Output the encrypted message
println(s"Encrypted Message: $encryptedMsg")
}
}