# Tic Tac Toe encoder/decoder

James Bond is about to start a new mission and you are his computer scientist friend. As Tic Tac Toe is becoming more and more popular you decide that nothing would be more underhanded than a Tic Tac Toe encoder/decoder™.

For each line the decoder will follow the following steps:

• Convert from 'O','X' and ' ' [space] to trinary

• ' ' -> 0

• 'O' -> 1

• 'X' -> 2

• Convert from trinary to decimal

• Convert from decimal to letter

Example: "XO "

• 210

• 21

• v

The encoder will do the reverse.

As code is much more precise than words, see the reference answer below in which I give an example (very long) implementation of the problem in Python:

Your decoder should be able to decode all the messages encoded with my encoder correctly and my decoder should be able to decode all the messages encoded with your encoder.

This is code-golf, the shortest wins.

• 0 must be converted to a, the alphabet ends with a whitespace, so 26-> ' ' [whitespace]

• "XXX" is the max, it translates to "222" that translates to 26.

• As in Tic Tac Toe the max line lenght is 3.

• X -> 2 means X should be replaced by 2

• Encoder/Decoder

• The encoder goes from plaintext to code.
• The decoder goes from code to plaintext.
• What happens when the result of the third step in encoding is grater than 26 ? Dec 10, 2014 at 19:19
• @optimizer It can't be: "OOO" is the max, it translates to "222" that translates to 26. Dec 10, 2014 at 19:21
• Also, should 1 mean a or 0 mean a ? because if 0 means a, then XXX, i.e. 26 means } Dec 10, 2014 at 19:37
• @Optimizer 0 means a Dec 10, 2014 at 20:01
• You have to put all these things in the questions as the spec. Do you believe that people will read the comments for sure ? Dec 10, 2014 at 20:08

## 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.

• Bit late to the party, but pretty please comment it? Jun 28, 2016 at 6:22

# 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;for(c++;h=**c;c++){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]);}}


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


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

• Jul 12, 2020 at 20:54

# CJam, 24 + 25 = 49 bytes

Encoder

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


Decoder

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


Try it online here

## 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.

• Your encoder/decoder fail for space/XXX Dec 10, 2014 at 20:23
• @Optimizer I've fixed this bug. I have to read the task more carefully before posting an answer... Dec 10, 2014 at 20:45
• Not your fault. The spec kept on revealing itself in parts. Dec 10, 2014 at 20:49
• Maybe my stupid actions will inspire you to improve something else: n=[36,3];r=s=>s.replace(/./g,x=>" OX"[x]||" OX".indexOf(x));f=(s,t,p=a=>(parseInt(a,n[1-t])+10-20*t).toString(n[t]))=>t?r(p(10+s).substr(-3)):s=="XXX"?" ":p(r(s)) usage: console.log(x=[" ", "XO ", "XXX"], x=x.map(z=>f(z,0)), x.map(z=>f(z,1))) Sep 15, 2018 at 23:24

## 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"


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

• Imports = 29 bytes
• Decoder = 171 bytes
• Encoder = 170 bytes
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


# 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.

# 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.

• 12 bytes for the encoder using minus-6-modulo-91... Jul 26 at 13:54
• @DominicvanEssen Thanks! Jul 26 at 14:26

# 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))

• I think you lost the leading spaces in the boards string in your decoder. Dec 10, 2014 at 19:41
• @optimizer: over here the first few lines show up as OO_ OO OO X_O, which would be mmmt. Dec 10, 2014 at 19:48
• @optimizer: oh wait, they're supposed to be right-aligned. duh. Dec 10, 2014 at 19:49

# 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!

# 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


# 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(' '))
}
}



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

Decoder:

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


Encoder:

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

Try it online!