File 2532-system-Update-tic-tac-toe-to-use-io_ansi.patch of Package erlang

From 1c835698a94ceb5f07946ab82804d3ff1479c42e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lukas=20Backstr=C3=B6m?= <lukas@erlang.org>
Date: Fri, 13 Jun 2025 11:47:57 +0200
Subject: [PATCH 12/17] system: Update tic-tac-toe to use io_ansi

---
 lib/stdlib/doc/assets/tic-tac-toe.es        | 85 +++++++++++----------
 lib/stdlib/doc/guides/terminal_interface.md | 79 ++++++++++---------
 lib/stdlib/src/io_ansi.erl                  |  4 +-
 3 files changed, 89 insertions(+), 79 deletions(-)

diff --git a/lib/stdlib/doc/assets/tic-tac-toe.es b/lib/stdlib/doc/assets/tic-tac-toe.es
index 83fa40f547..c8c5b7cae5 100755
--- a/lib/stdlib/doc/assets/tic-tac-toe.es
+++ b/lib/stdlib/doc/assets/tic-tac-toe.es
@@ -23,16 +23,18 @@
 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.
+    try
+        %% Enable alternate screen buffer, hide cursor and enable keypad_transmit_mode
+        io_ansi:fwrite([alternate_screen, cursor_hide, keypad_transmit_mode]),
+        draw_board(),
+        loop({0, "X", list_to_tuple(lists:duplicate(9, ""))}),
+        timer:sleep(5000)
+    after ->
+        io_ansi:fwrite([alternate_screen_off, cursor_show, keypad_transmit_mode_off])
+    end.
 
 draw_board() ->
-    io:put_chars("\e[5;0H"), %% Move cursor to top left
+    io_ansi:fwrite([{cursor, 6, 0}]),
     io:put_chars(
       ["     ╔═══════╤═══════╤═══════╗\r\n",
        "     ║       │       │       ║\r\n",
@@ -50,76 +52,81 @@ draw_board() ->
     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)
+    io_ansi:fwrite(lists:flatten(draw_state(State))),
+    case io:get_chars("", 1024) of
+        eof -> stop;
+        Chars ->
+            case handle_input(io_ansi:scan(Chars), State) of
+                stop -> stop;
+                NewState ->
+                    io_ansi:fwrite(clear_selection(State)),
+                    loop(NewState)
+            end
     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","       "].
+     "       ",{cursor_backward, 7}, cursor_down,
+     " ",{cursor_forward,5}," ",
+     {cursor_backward, 7}, cursor_down,
+     "       "].
 
 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","└─────┘"].
+     "┌─────┐",
+     {cursor_backward, 7}, cursor_down,
+     "│",{cursor_forward,5},"│",
+     {cursor_backward, 7}, cursor_down,
+     "└─────┘"].
 
 %% 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]).
+    {cursor, Row + 1, Col - 1}.
 
 %% Update selection and whos turn it is
 draw_state({_, Turn, _} = State) ->
     [draw_selection(State),
-     io_lib:format("\e[7;45H~s",[Turn])].
+     {cursor, 8, 44}, Turn].
 
 %% Draw X or O
 draw_marker(Pos, Turn) ->
-    [set_position(Pos), "\e[C\e[C\e[C\n", Turn].
+    [set_position(Pos), {cursor_forward, 3}, cursor_down, Turn].
 
-handle_input(eof, _State) ->
-    stop;
-handle_input("\e[A" ++ Rest, {Pos, Turn, State}) ->
+handle_input([kcursor_up | Rest], {Pos, Turn, State}) ->
     %% Up key
     handle_input(Rest, {max(0, Pos - 3), Turn, State});
-handle_input("\e[B" ++ Rest, {Pos, Turn, State}) ->
+handle_input([kcursor_down | Rest], {Pos, Turn, State}) ->
     %% Down key
     handle_input(Rest, {min(8, Pos + 3), Turn, State});
-handle_input("\e[C" ++ Rest, {Pos, Turn, State}) ->
+handle_input([kcursor_forward | Rest], {Pos, Turn, State}) ->
     %% right key
     handle_input(Rest, {min(8, Pos + 1), Turn, State});
-handle_input("\e[D" ++ Rest, {Pos, Turn, State}) ->
+handle_input([kcursor_backward | Rest], {Pos, Turn, State}) ->
     %% left key
     handle_input(Rest, {max(0, Pos - 1), Turn, State});
-handle_input("\r" ++ Rest, {Pos, Turn, State} = OldState) ->
+handle_input([<<"\r",Rest/binary>> | T], {Pos, Turn, State} = OldState) ->
     NewState =
         case element(Pos+1, State) of
             "" when Turn =:= "X" ->
-                io:put_chars(draw_marker(Pos, Turn)),
+                io_ansi:fwrite(draw_marker(Pos, Turn)),
                 {Pos, "O", setelement(Pos+1, State, Turn)};
             "" when Turn =:= "O" ->
-                io:put_chars(draw_marker(Pos, Turn)),
+                io_ansi:fwrite(draw_marker(Pos, Turn)),
                 {Pos, "X", setelement(Pos+1, State, Turn)};
-            _ -> io:put_chars("\^G"), OldState
+            _ -> io_ansi:fwrite("\^G"), OldState
         end,
-    handle_input(Rest, NewState);
-handle_input("q" ++ _, _State) ->
+    handle_input([Rest | T], NewState);
+handle_input([<<"q",_Rest/binary>> | _T], _State) ->
     stop;
-handle_input([_ | T], State) ->
+handle_input([<<>> | T], State) ->
+    handle_input(T, State);
+handle_input([C | T], State) ->
+    io_ansi:fwrite([cursor_save, {cursor, 40, 0}, "Unknown character: ~p", cursor_restore],[C]),
     handle_input(T, State);
 handle_input([], State) ->
     State.
\ No newline at end of file
diff --git a/lib/stdlib/doc/guides/terminal_interface.md b/lib/stdlib/doc/guides/terminal_interface.md
index 8d2b914aa1..014c54992a 100644
--- a/lib/stdlib/doc/guides/terminal_interface.md
+++ b/lib/stdlib/doc/guides/terminal_interface.md
@@ -45,18 +45,18 @@ Let us start by drawing the board which will look like this:
 ```
 
 
-We will use the alternate screen buffer for our game so first we need to set that up:
+We will use the alternate screen buffer for our game so first we need to set
+that up using `m:io_ansi`:
 
 ```
 #!/usr/bin/env escript
 main(_Args) ->
     
-    io:put_chars("\e[?1049h"), %% Enable alternate screen buffer
-    io:put_chars("\e[?25l"), %% Hide the cursor
+    %% Enable alternate screen buffer and hide cursor
+    io_ansi:fwrite([alternate_screen, cursor_hide]),
     draw_board(),
     timer:sleep(5000),
-    io:put_chars("\e[?25h"), %% Show the cursor
-    io:put_chars("\e[?1049l"), %% Disable alternate screen buffer
+    io_ansi:fwrite([alternate_screen_off, cursor_show]),
     ok.
 ```
 
@@ -64,7 +64,8 @@ We then use the box drawing parts of Unicode to draw our board:
 
 ```
 draw_board() ->
-    io:put_chars("\e[5;0H"), %% Move cursor to top left
+    %% Place cursor at row 6 column 0
+    io_ansi:fwrite([{cursor, 6, 0}])
     io:put_chars(
       ["     ╔═══════╤═══════╤═══════╗\r\n",
        "     ║       │       │       ║\r\n",
@@ -87,7 +88,7 @@ shell from running in `cooked` to `raw` mode. This is done by calling
 [`shell:start_interactive({noshell, raw})`](`shell:start_interactive/1`).
 We can then use `io:get_chars/2` to read key strokes from the user. The key
 strokes will be returned as [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code), 
-so we will have need to handle the codes for up, down, left, right and enter.
+so we will use `io_ansi:scan/1` to interpret them.
 
 It could look something like this:
 
@@ -95,38 +96,41 @@ It could look something like this:
 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),
-    io:put_chars("\e[?25h"), %% Show the cursor
-    io:put_chars("\e[?1049l"), %% Disable alternate screen buffer
-    ok.
+    try
+        %% Enable alternate screen buffer, hide cursor and enable keypad_transmit_mode
+        io_ansi:fwrite([alternate_screen, cursor_hide, keypad_transmit_mode]),
+        draw_board(),
+        loop(0)
+    after ->
+        io_ansi:fwrite([alternate_screen_off, cursor_show, keypad_transmit_mode_off]),
+    end.
 
 loop(Pos) ->
-    io:put_chars(draw_selection(Pos)),
-    %% Read at most 1024 characters from stdin.
-    Chars = io:get_chars("", 1024),
-    case handle_input(Chars, Pos) of
-        stop -> stop;
-        NewPos ->
-            io:put_chars(clear_selection(Pos)),
-            loop(NewPos)
+    io_ansi:fwrite(lists:flatten(draw_state(Pos))),
+    case io:get_chars("", 1024) of
+        eof -> stop;
+        Chars ->
+            case handle_input(io_ansi:scan(Chars), Pos) of
+                stop -> stop;
+                NewPos ->
+                    io_ansi:fwrite(clear_selection(Pos)),
+                    loop(NewPos)
+            end
     end.
 
-handle_input("\e[A" ++ Rest, Pos) ->
+handle_input([kcursor_up | Rest], {Pos, Turn, State}) ->
     %% Up key
     handle_input(Rest, max(0, Pos - 3));
-handle_input("\e[B" ++ Rest, Pos) ->
+handle_input([kcursor_down | Rest], {Pos, Turn, State}) ->
     %% Down key
     handle_input(Rest, min(8, Pos + 3));
-handle_input("\e[C" ++ Rest, Pos) ->
+handle_input([kcursor_forward | Rest], {Pos, Turn, State}) ->
     %% right key
     handle_input(Rest, min(8, Pos + 1));
-handle_input("\e[D" ++ Rest, Pos) ->
+handle_input([kcursor_backward | Rest], {Pos, Turn, State}) ->
     %% left key
     handle_input(Rest, max(0, Pos - 1));
-handle_input("q" ++ _, _State) ->
+handle_input([<<"q",_Rest/binary>> | _T], _State) ->
     stop;
 handle_input([_ | T], State) ->
     handle_input(T, State);
@@ -145,29 +149,28 @@ routines.
 ```
 %% 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","       "].
+     "       ",{cursor_backward, 7}, cursor_down,
+     " ",{cursor_forward,5}," ",
+     {cursor_backward, 7}, cursor_down,
+     "       "].
 
 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","└─────┘"].
+     "┌─────┐",
+     {cursor_backward, 7}, cursor_down,
+     "│",{cursor_forward,5},"│",
+     {cursor_backward, 7}, cursor_down,
+     "└─────┘"].
 
 %% 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]).
+    {cursor, Row + 1, Col - 1}.
 ```
-{: #monospace-font }
 
 Now we have a program where we can move the marker around the board.
 To complete the game we need to add some state so that we know which
diff --git a/lib/stdlib/src/io_ansi.erl b/lib/stdlib/src/io_ansi.erl
index 673fc228be..739942156a 100644
--- a/lib/stdlib/src/io_ansi.erl
+++ b/lib/stdlib/src/io_ansi.erl
@@ -2178,8 +2178,8 @@ Scan the string for virtial terminal sequences.
 The recognized VTSs will be converted into the corresponding `t:vts/0`.
 
 If you intend to parse arrow keys it is recommended that you first set the terminal in
-application mode by using `io_ansi:format(standard_out, [keypad_transmit_mode], [], [])`.
-This will make it easier for io_ansi to correctly detect arrow keys.
+application mode by using `keypad_transmit_mode/0`. This will make it easier for
+`m:io_ansi` to correctly detect arrow keys.
 
 Any unrecognized [control sequence introducers](https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands),
 will be placed in a tuple tagged with `csi`.
-- 
2.51.0

openSUSE Build Service is sponsored by