├── printer_wins.jpg ├── README.md ├── LICENSE ├── ttt.ps └── ttt_full.ps /printer_wins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/PSTicTacToe/main/printer_wins.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSTicTacToe 2 | 3 | ### Play Tic-Tac-Toe Against your Printer 4 | 5 | Send `ttt.ps` to your printer: 6 | 7 | cat ttt.ps - | nc 192.168.2.10 9100 8 | 9 | Or use Ghostscript: 10 | 11 | gs ttt.ps 12 | 13 | Play by entering numbers from 1 to 9. 14 | 15 | The code will produce console + print output. 16 | 17 | ### Files 18 | 19 | Both files are equivalent. 20 | 21 | `ttt_full.ps` is the readable version. 22 | 23 | `ttt.ps` is the minified version. 24 | 25 | ### Algorithm 26 | 27 | Printer applies the following rules. 28 | 29 | Dead simple and keeps the fun. 30 | 31 | 1. printer attempts to block human 32 | 2. printer attempts to win 33 | 3. printer plays randomly 34 | 35 | ### Sample Output 36 | 37 | OX. 38 | .X. 39 | XO. 40 | 41 | human turn (1-9) >9 42 | 43 | OX. 44 | .X. 45 | XOO 46 | 47 | 48 | OXX 49 | .X. 50 | XOO 51 | 52 | PRINTER WINS 53 | 54 | ![Screenshot](printer_wins.jpg "PostScript Tic-Tac-Toe") 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nicolas Seriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ttt.ps: -------------------------------------------------------------------------------- 1 | %!PS 2 | % 3 | % Play Tic-Tac-Toe against your printer 4 | % 2024-04 Nicolas Seriot https://github.com/nst/PSTicTacToe 5 | % 6 | % On GhostScript: 7 | % gs ttt.ps 8 | % gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile="%d.pdf" ttt.ps 9 | % On a real PostScript printer: 10 | % cat ttt.ps - | nc 192.168.2.10 9100 11 | 12 | /d{def}def/e{exch}d/M{moveto}d/O{pop}d/g{getinterval}d/l{length}d/L{lineto}d/I{ 13 | if}d/P{putinterval}d/D(123456789)d/nf{0 b{46 eq{1 add}I}forall}d/R{/q false d 0 14 | 1 8{/i e d/A b dup l string cvs d A i 1 g(.)eq{[(X)(O)]{/p e d A i p P A B{b i 15 | (X)P/q true d exit}I}forall}I q{exit}I}for q}d/Q{/x rand nf mod d/c 0 d 0 1 b l 16 | 1 sub{/i e d b i 1 g(.)eq{c x eq{b i(X)P exit}I/c c 1 add d}I}for}d/r{{(human (\ 17 | 1-9)>)print flush(%lineedit)(r)file(________)readline O dup l 0 gt{0 1 g}{O ( )} 18 | ifelse/o e d D o search{O O O b o cvi 1 sub 1 g(.)eq{o cvi 1 sub exit}I}{O} 19 | ifelse(bad input)= S}loop}d/B{/z e d/N[[0 1 2][3 4 5][6 7 8][0 3 6][1 4 7][2 5 8 20 | ][0 4 8][2 4 6]]d/V false d[(O)(X)]{/p e d N{/T e d/V true d T{/U e d/V z U 1 g 21 | p eq V and d}forall V{exit}I}forall V{exit}I}forall V}d/S{0.2 setlinewidth 10 10 22 | scale 20 70 M 20 40 L 30 70 M 30 40 L 10 60 M 40 60 L 10 50 M 40 50 L stroke 0 1 23 | b l 1 sub{/i e d b i 1 g(.)ne{gsave 10 i 3 mod 10 mul add 3 add 70 i 3 idiv 10 24 | mul sub 7 sub M b i 1 g show grestore}I}for m null ne{10 30 M m show}I showpage} 25 | d/C{/E e d/K e d/m null d nf 0 eq{/m(TIE)d/E true d}I b B{/m(________WINS)d m 0 26 | K P/E true d}I E{S}I m null ne{quit}I}d/Courier findfont 5 scalefont setfont/b 27 | D d/m(HUMAN PLAYS O)d S/b(.........)d{/m null d b r(O)P(__HUMAN) false C R not{Q 28 | }I(PRINTER)true C}loop -------------------------------------------------------------------------------- /ttt_full.ps: -------------------------------------------------------------------------------- 1 | %!PS 2 | % 3 | % Play Tic-Tac-Toe against your printer 4 | % 2024-04 Nicolas Seriot https://github.com/nst/PSTicTacToe 5 | % 6 | % GhostScript: 7 | % gs ttt_full.ps 8 | % 9 | % Real PostScript printer: 10 | % cat ttt_full.ps - | nc 192.168.2.10 9100 11 | 12 | /ShowBoard { 13 | () = 14 | b 0 3 getinterval = 15 | b 3 3 getinterval = 16 | b 6 3 getinterval = 17 | () = 18 | m null ne { m = } if 19 | } def 20 | 21 | /numberOfFreePos { 0 b { 46 eq { 1 add } if } forall } def 22 | 23 | /printerTriesToWinOrBlock_b_ { 24 | /printerHasPlayed false def 25 | 26 | 0 1 8 { 27 | /i exch def 28 | /b2 b dup length string cvs def 29 | b2 i 1 getinterval (.) eq { 30 | [(X) (O)] { 31 | /p exch def 32 | 33 | b2 i p putinterval 34 | b2 _board_SomeoneHasWon_b_ { 35 | b i (X) putinterval 36 | /printerHasPlayed true def 37 | exit 38 | } if 39 | } forall 40 | } if 41 | printerHasPlayed { exit } if 42 | } for 43 | 44 | printerHasPlayed 45 | } def 46 | 47 | /printerPlaysRandomly { 48 | /x rand numberOfFreePos mod def 49 | /c 0 def 50 | 0 1 b length 1 sub { 51 | /i exch def 52 | b i 1 getinterval (.) eq { 53 | c x eq { 54 | b i (X) putinterval 55 | exit 56 | } if 57 | /c c 1 add def 58 | } if 59 | } for 60 | } def 61 | 62 | /readPos_i_ { 63 | { 64 | (human turn (1-9) >) print flush 65 | (%lineedit) (r) file ( ) readline 66 | pop 67 | dup 68 | 69 | length 0 gt { 0 1 getinterval } { pop ( ) } ifelse 70 | 71 | /o exch def 72 | 73 | (123456789) o search { 74 | pop pop pop 75 | 76 | b o cvi 1 sub 1 getinterval (.) eq { 77 | o cvi 1 sub 78 | exit 79 | } if 80 | } { 81 | pop 82 | } ifelse 83 | 84 | (bad input) = ShowBoard PrintBoard 85 | } loop 86 | } def 87 | 88 | /_board_SomeoneHasWon_b_ { 89 | /board exch def 90 | 91 | /lines [[0 1 2] [3 4 5] [6 7 8] [0 3 6] [1 4 7] [2 5 8] [0 4 8] [2 4 6]] def 92 | 93 | /isOver false def 94 | 95 | [(O) (X)] { 96 | /p exch def 97 | lines { 98 | /positions exch def 99 | 100 | /isOver true def 101 | positions { 102 | /pos exch def 103 | /isOver board pos 1 getinterval p eq isOver and def 104 | } forall 105 | 106 | isOver { exit } if 107 | } forall 108 | isOver { exit } if 109 | } forall 110 | isOver 111 | } def 112 | 113 | /PrintBoard { 114 | 4 setlinewidth 115 | 116 | 200 700 moveto 200 400 lineto 117 | 300 700 moveto 300 400 lineto 118 | 119 | 100 600 moveto 400 600 lineto 120 | 100 500 moveto 400 500 lineto 121 | 122 | stroke 123 | 124 | 0 1 b length 1 sub { 125 | /i exch def 126 | b i 1 getinterval (.) ne { 127 | gsave 128 | 0 0 moveto 129 | 100 i 3 mod 100 mul add 35 add 130 | 700 i 3 idiv 100 mul sub 65 sub moveto 131 | b i 1 getinterval show 132 | grestore 133 | } if 134 | } for 135 | 136 | m null ne { 137 | 100 300 moveto 138 | m show 139 | } if 140 | 141 | showpage 142 | } def 143 | 144 | /_s_b_checkGameState { 145 | /doPrint exch def 146 | /s7 exch def 147 | 148 | /m null def 149 | 150 | numberOfFreePos 0 eq { 151 | /m (TIE) def 152 | /doPrint true def 153 | } if 154 | 155 | b _board_SomeoneHasWon_b_ { 156 | /m ( WINS) def 157 | m 0 s7 putinterval 158 | /doPrint true def 159 | } if 160 | 161 | doPrint { 162 | ShowBoard PrintBoard 163 | } if 164 | m null ne { quit } if 165 | } def 166 | 167 | << /PageSize [595 842] >> setpagedevice 168 | 169 | /Courier findfont 48 scalefont setfont 170 | 171 | /b (123456789) def 172 | 173 | /m (HUMAN PLAYS O) def 174 | 175 | ShowBoard PrintBoard 176 | 177 | /b (.........) def 178 | 179 | { 180 | /m null def 181 | 182 | b readPos_i_ (O) putinterval 183 | 184 | ( HUMAN) false _s_b_checkGameState 185 | 186 | printerTriesToWinOrBlock_b_ not { 187 | printerPlaysRandomly 188 | } if 189 | 190 | (PRINTER) true _s_b_checkGameState 191 | 192 | } loop 193 | --------------------------------------------------------------------------------