#!/usr/bin/env escript %% %CopyrightBegin% %% %% SPDX-License-Identifier: Apache-2.0 %% %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% main(_Args) -> ok = shell:start_interactive({noshell, raw}), io:put_chars("\e[?1049h"), %% Enable alternate screen buffer io:put_chars("\e[?25l"), %% Hide the cursor draw_board(), loop({0, "X", list_to_tuple(lists:duplicate(9, ""))}), io:put_chars("\e[?25h"), %% Show the cursor io:put_chars("\e[?1049l"), %% Disable alternate screen buffer ok. draw_board() -> io:put_chars("\e[5;0H"), %% Move cursor to top left io:put_chars( [" ╔═══════╤═══════╤═══════╗\r\n", " ║ │ │ ║\r\n", " ║ │ │ ║ Place an X by pressing Enter\r\n", " ║ │ │ ║\r\n", " ╟───────┼───────┼───────╢\r\n", " ║ │ │ ║\r\n", " ║ │ │ ║\r\n", " ║ │ │ ║\r\n", " ╟───────┼───────┼───────╢\r\n", " ║ │ │ ║\r\n", " ║ │ │ ║\r\n", " ║ │ │ ║\r\n", " ╚═══════╧═══════╧═══════╝\r\n"]), ok. loop(State) -> io:put_chars(draw_state(State)), case handle_input(io:get_chars("", 30), State) of stop -> stop; NewState -> io:put_chars(clear_selection(State)), loop(NewState) end. %% Clear/draw the selection markers, making sure %% not to overwrite if a X or O exists. %% \b = Move cursor left %% \e[C = Move cursor right %% \n = Move cursor down clear_selection({Pos, _, _}) -> [set_position(Pos), " ","\b\b\b\b\b\b\b\n", " \e[C\e[C\e[C\e[C\e[C ", "\b\b\b\b\b\b\b\n"," "]. draw_selection({Pos, _, _}) -> [set_position(Pos), "┌─────┐","\b\b\b\b\b\b\b\n", "│\e[C\e[C\e[C\e[C\e[C│", "\b\b\b\b\b\b\b\n","└─────┘"]. %% Set the cursor position to be at the top %% left of the field of the given position set_position(Pos) -> Row = 6 + (Pos div 3) * 4, Col = 7 + (Pos rem 3) * 8, io_lib:format("\e[~p;~pH",[Row, Col]). %% Update selection and whos turn it is draw_state({_, Turn, _} = State) -> [draw_selection(State), io_lib:format("\e[7;45H~s",[Turn])]. %% Draw X or O draw_marker(Pos, Turn) -> [set_position(Pos), "\e[C\e[C\e[C\n", Turn]. handle_input({ok, Chars}, State) -> handle_input({ok, Chars}, State); handle_input(eof, _State) -> stop; handle_input("\e[A" ++ Rest, {Pos, Turn, State}) -> %% Up key handle_input(Rest, {max(0, Pos - 3), Turn, State}); handle_input("\e[B" ++ Rest, {Pos, Turn, State}) -> %% Down key handle_input(Rest, {min(8, Pos + 3), Turn, State}); handle_input("\e[C" ++ Rest, {Pos, Turn, State}) -> %% right key handle_input(Rest, {min(8, Pos + 1), Turn, State}); handle_input("\e[D" ++ Rest, {Pos, Turn, State}) -> %% left key handle_input(Rest, {max(0, Pos - 1), Turn, State}); handle_input("\r" ++ Rest, {Pos, Turn, State} = OldState) -> NewState = case element(Pos+1, State) of "" when Turn =:= "X" -> io:put_chars(draw_marker(Pos, Turn)), {Pos, "O", setelement(Pos+1, State, Turn)}; "" when Turn =:= "O" -> io:put_chars(draw_marker(Pos, Turn)), {Pos, "X", setelement(Pos+1, State, Turn)}; _ -> io:put_chars("\^G"), OldState end, handle_input(Rest, NewState); handle_input("q" ++ _, _State) -> stop; handle_input([_ | T], State) -> handle_input(T, State); handle_input([], State) -> State.