g | x | w | all
Bytes Lang Time Link
nanAPL Dyalog Unicode230726T170532ZAdá
nanNibbles230726T125941Zxigoi
nanScala230725T070134Z138 Aspe
nanExcel230725T062716ZJos Wool
nanJapt v1.4.5221019T212558ZKamil Dr
nanRuby221019T211428ZJordan
nan221019T203308Zbigyihsu
393BRAINFUCK141213T095156ZDef
nanC++141211T073047ZBMac
nanR141210T220653ZMickyT
157Javascript141210T195018ZQwertiy
nanCJam141210T194749ZOptimize
nanReference answer141210T193250ZCaridorc

APL (Dyalog Unicode), 18 + 18 = 36 bytes

Decoder:

(⊂3⊥⍉' O'⍳⎕)⌷27↑⎕A

Encoder:

' OX'[⍉3 3 3⊤⎕A⍳⎕]

Try it online!

Nibbles, 12 + 14 = 26 bytes

Encoder, 12 bytes

.@.`@3%-$6 91=$"OX "

Attempt This Online!

-1 byte thanks to Dominic van Essen

.@.`@3%-%$32~27=$"OX "
.                      For each character c in
 @                      the input as a string
  .                     for each digit d in
   `@3                   convert to base 3
        %$32              ord(c) mod 32
       -    ~             minus 1
      %      27           mod 27
               =$        take the d-th character (1-indexed, wrapping)
                 "OX "    from "OX "

Decoder, 14 bytes

*" "`%.;@+`@3.$%/$10 3'a'"{"

Attempt This Online!

*" "`%.;@+`@3.$%/$10 3'a'"{"
*" "                         Join by spaces
    `%                   "{"  split on '{' characters
      .                        for each line l in
       ;@                       the input as a list of lines
         +            'a'        add the character code of 'a' to
          `@3                     convert from base 3
             .                     for each character c in
              $                     l
                /$10                ord(c) divided by 10
               %     3              modulo 3

I realized later that I could've simply used ?"OX"$ instead of %/$10 3. The byte count is equal.

Scala, 90+107=197 bytes

Port of @Jordan's Ruby answer in Scala.


Encoder

Golfed version. Attempt This Online!

s=>(('A'to'Z')++Seq(' '))(Integer.parseInt(s.map{case'O'=>'1'case'X'=>'2'case' '=>'0'},3))

Ungolfed version. Attempt This Online!

object Main {
  def main(args: Array[String]): Unit = {
    def f(s: String): Char = {
      val alphabet = ('A' to 'Z') ++ Seq(' ')
      val mapped = s.map {
        case 'O' => '1'
        case 'X' => '2'
        case ' ' => '0'
      }
      val idx = Integer.parseInt(mapped, 3)
      alphabet(idx)
    }

    println("["++f("XO ").toString++"]")
    println("["++f("XXX").toString++"]")
  }
}

Decoder

Golfed version. Attempt This Online!

c=>{val n=if(c<'A')26 else c-'A';Integer.toString(n,3).map{case'0'=>' ';case'1'=>'O';case'2'=>'X'}.reverse}

Ungolfed version. Attempt This Online!

object Main {
  def F(c: Char): String = {
    val n = if (c < 'A') 26 else (c - 'A').toInt
    val base3 = Integer.toString(n, 3)
    base3.map {
      case '0' => ' '
      case '1' => 'O'
      case '2' => 'X'
    }.reverse
  }

  def main(args: Array[String]): Unit = {
    println(F('V'))
    println(F(' '))
  }
}

Excel, 81 + 87 = 168 bytes

Encoder:

=CONCAT(SWITCH(0+MID(BASE(TEXT(CODE(A1)-97,"[=63]26"),3,3),{1,2,3},1),2,"X",1,"O"," "))

Decoder:

=CHAR(97+TEXT(MMULT(SWITCH(MID(A1,{1,2,3},1),"X",2,"O",1,),3^{2;1;0}),"[=26]63"))

Input in cell A1 in both cases.

Japt v1.4.5, 12 + 17 = 29 bytes

Encoder

;C+S gUn" OX

Try it

Decoder

;C+S aU s" OX" ù3

Try it

I tried some options that worked with codepoints rather than indexing into C+S, but they always ended up being longer in order to handle the space.

Explanations:

;C+S gUn" OX
;            # C = "abcdefghijklmnopqrstuvwxyz"
 C+S         # Add a space to the end of C
     g       # Get the character at index:
      U      #  Input string
       n" OX #  Converted to base 10 from base " OX"

;C+S aU s" OX" ù3
;                 # C = "abcdefghijklmnopqrstuvwxyz"
 C+S              # Add a space to the end of C
     aU           # Find the index of the input string
        s" OX"    # Convert to base " OX"
               ù3 # Left pad with spaces to length 3

Ruby, 44 + 54 = 98 bytes

Encoder

->s{[*?A..?Z," "][s.tr(" OX","012").to_i 3]}

Attempt This Online!

Decoder

->c{((c<?A??[:c).ord-65).digits(3).join.tr"012"," OX"}

Attempt This Online!

Go, 372 bytes \$= 29 + 171 + 170\$

import(."strings";."strconv")
func d(s string)(k string){for _,l:=range Split(s,"\n"){i,_:=ParseInt(NewReplacer(" ","0","O","1","X","2").Replace(l),3,64)
k+=string('A'+i)}
return ReplaceAll(k,"["," ")}
func e(s string)(k string){q:=ReplaceAll(s," ","[")
for _,r:=range q{k+=NewReplacer("0"," ","1","O","2","X").Replace(FormatInt(int64(r-'A'),3))+"\n"}
return Trim(k,"\n")}

Attempt This Online!

Explanation

Decoder

import(."strings";."strconv")
func d(s string)(k string){
for _,l:=range Split(s,"\n"){            // for each line...
i,_:=ParseInt(                           // turn the string into an int
    NewReplacer(" ","0","O","1","X","2") // define the replacements
    .Replace(l),                         // replace the chars
    3,64)                                // turn it into trinary
k+=string('A'+i)}                        // append to the output string
return ReplaceAll(k,"["," ")}            // replace `[` with spaces

Encoder

import(."strings";."strconv")
func e(s string)(k string){
q:=ReplaceAll(s," ","[")                // replace `[` with spaces 
for _,r:=range q{                       // for each character...
k+=NewReplacer("0"," ","1","O","2","X") // define the replacements...
    .Replace(                           // and apply onto...
    FormatInt(int64(r-'A'),3))          // the character, turned into trinary
    +"\n"}                              // and add a newline
return Trim(k,"\n")}                    // trim the final newline

BRAINFUCK, 393 bytes (encoder only)

Guys, we're making something for James Bond. Languages like javascript and C are way too readable! If you want something that the russians will never understand, brainfuck is the only option. It's surly not the shortest or fastest option, but no-one will understand it (and, let's be honest, everyone loves brainfuck)

The code has a couple of limitations though, It only encodes and only one character at a time. Also it doesn't manually stop, you have the stop it after it spits out the output. But hey, it works. This is the code:

>>>,>++++++++[<-------->-]<->+++<[>[->+>+<<]>[-<<-[>]>>>[<[-<->]<[>]>>[[-]>>+<]
>-<]<<]>>>+<<[-<<+>>]<<<]>>>>>[-<<<<<+>>>>>]<<<<[-<<+>>]+++<[>[->+>+<<]>[-<<-[
>]>>>[<[-<->]<[>]>>[[-]>>+<]>-<]<<]>>>+<<[-<<+>>]<<<]>>>>>[-<<<<<+>>>>>]<<<<<<
[->>>+<<<]+[[-]>[-<+<+<+>>>]<[>++++++++++++++++++++++++++++++++++++++++++++++
++++++<[-]]<-[>> ++++++++++++++++++++++++++++++++++++<<[-]]<--[>>>---<<<[-]]>>>.]

If you want to try it: just run the code with one capital letter as input.

If you want it commented just ask me nicely. A lot of things can be done better but I don't feel like optimizing it now (because brainfuck). Maybe I'll make a decoder too as reversing the process probably isn't that difficult.

C++, 168 + 135 = 303 bytes

EDIT: saved one byte by requiring uppercase input

I like doing these in C++ because I get to do all sorts of fun nastiness I would never ever do in C++ code.

Encoder (168):

Takes a string of uppercase letters and spaces as an argument.

#include<cstdio>
int main(int h,char**c){c[0][3]=0;for(c++;h=**c;c[0]++){h=h&32?26:h-65;for(int d=2,e;d+1;d--){h=(h-(e=h%3))/3;c[-1][d]=e&2?88:e*47+32;}printf(c[-1]);}}

Readable:

#include<cstdio>
int main(int h,char**c)
{
    c[0][3]=0;
    for(c++;h=**c;c[0]++)
    {
        h=h&32?26:h-65;
        for(int d=2,e;d+1;d--)
        {
            h=(h-(e=h%3))/3;
            c[-1][d]=e&2?88:e*47+32;
        }
        printf(c[-1]);
    }
}

Decoder (135):

Takes a string of X, O, and space as an argument.

#include<cstdio>
int b=9,v;int main(int h,char**c){for(c++;h=**c;c[0]++){v+=(h&16?2:h&1)*b;b/=3;if(!b)putchar(v==26?32:v+97),v=0,b=9;}}

Readable:

#include<cstdio>
int b=9,v;
int main(int h,char**c)
{
    for(c++;h=**c;c[0]++)
    {
        v+=(h&16?2:h&1)*b;
        b/=3;
        if(!b)putchar(v==26?32:v+97),v=0,b=9;
    }
}

R, 121 + 115 = 236

I think I got the spec correct

Decoder Function 121

d=function(s){i=rev(as.integer(unlist(strsplit(chartr('XO ','210',s),''))));c(letters,' ')[sum(3^(0:(length(i)-1))*i)+1]}

Encoder Function 115

e=function(s){i=which(c(letters,' ')==s)-1;paste(chartr("210","XO ",c(i%/%9%%3,i%/%3%%3,i%%3)),sep='',collapse='')}

This only works on lower case characters. Is that a requirement?

Quick Test

> mapply(d,c("OO "," OO"," OO","X O","XXX"))
OO   OO  OO X O XXX 
"m" "e" "e" "t" " " 
> mapply(e,unlist(strsplit('meet ','')))
    m     e     e     t       
"OO " " OO" " OO" "X O" "XXX" 

Javascript, ES6, 157 chars

Only encoder, 83

f=s=>s=="XXX"?" ":(parseInt(s.replace(/./g,x=>" OX".indexOf(x)),3)+10).toString(36)

Only decoder, 77

g=s=>(parseInt(10+s,36)-10).toString(3).substr(-3).replace(/./g,x=>" OX"[x])

Both, 157

p=parseInt;f=s=>s=="XXX"?" ":(p(s.replace(/./g,x=>" OX".indexOf(x)),3)+10).toString(36);g=s=>(p(10+s,36)-10).toString(3).substr(-3).replace(/./g,x=>" OX"[x])

Test

console.log(x=["   ", "XO ", "XXX"], x=x.map(f), x.map(g))


PS: Tried to perform some optimization, but failed: resulting code was longer then just concatenation. I don't publish it as it also contains a bug, which had already been fixed.

CJam, 24 + 25 = 49 bytes

Encoder

qN/{:i40f/3b'a+_'{=S@?}%

Decoder

q{_Sc=26@'a-?Zb" OX"f=N}%

Try it online here

Reference answer, completely ungolfed:

# Tic-Tac-Toe code encoder

def from_letter_to_decimal(letter):
    alphabet = "abcdefghijklmnopqrstuvwxyz "
    return alphabet.index(letter)

def from_decimal_to_trinary(decimal):
    digits = []
    while decimal:
        digits.append(str(decimal % 3))
        decimal //= 3
    return ''.join(list(reversed((digits))))

def from_trinary_to_line(trinary):
    replace_dict = {"0": " ",
                  "1": "O",
                  "2": "X"}
    for char in replace_dict:
        trinary = trinary.replace(char,replace_dict[char])
    return trinary


def encode_letter(letter):
    decimal = from_letter_to_decimal(letter)
    trinary = from_decimal_to_trinary(decimal)
    line = from_trinary_to_line(trinary)
    return line

def encode_text(text):
    return '\n'.join([encode_letter(letter) for letter in text])

print(encode_text("meet me under the red bridge"))

# Tic-Tac-Toe code decoder

boards = """
OO 
OO
OO
X O
XXX
OO 
OO
XXX
X X
OOO
O 
OO
OXX
XXX
X O
XO
OO
XXX
OXX
OO
O 
XXX
O
OXX
XX
O 
X 
OO

"""


def from_line_to_trinary(line):
    replace_dict = {" ": "0",
                  "O": "1",
                  "X": "2"}
    for char in replace_dict:
        line = line.replace(char,replace_dict[char])
    return line


def from_trinary_to_decimal(n):
    result = 0
    for position,i in enumerate(reversed(n)):
        result += int(i) * 3**position
    return result

def from_decimal_to_letter(n):
    alphabet = "abcdefghijklmnopqrstuvwxyz "
    return alphabet[n]

def decode_line(line):
    trinary = from_line_to_trinary(line)
    decimal = from_trinary_to_decimal(trinary)
    letter = from_decimal_to_letter(decimal)
    return letter

def decode_boards(boards):
    return ''.join([decode_line(line) for line in boards.splitlines() if not line==""])

print(decode_boards(boards))