#############################################################################
##
#W  demo.g            GAP 4 package `Browse'       Frank Lübeck/Thomas Breuer
##
##  This file implements the NCurses.Demo() function and installs example
##  applications from this package into a default demo.
##  

NCurses.ReadString := function(s)
  local f;
  f := InputTextString(s);
  Read(f);
  if not IsClosedStream(f) then
    CloseStream(f);
  fi;
end;


#############################################################################
##
#F  NCurses.Demo( [<inputs>] )
##
##  <#GAPDoc Label="Demo_man">
##  <ManSection>
##  <Func Name="NCurses.Demo" Arg="[inputs]"/>
## 
##  <Description>
##  Let <A>inputs</A> be a list of records, each with the components
##  <C>title</C> (a string), <C>inputblocks</C> (a list of strings,
##  each describing some &GAP; statements), and optionally <C>footer</C>
##  (a string) and <C>cleanup</C> (a string describing &GAP; statements).
##  The default is <C>NCurses.DemoDefaults</C>.
##  <P/>
##  <C>NCurses.Demo</C> lets the user choose an entry from <A>inputs</A>,
##  via <Ref Func="NCurses.Select"/>, and then executes the &GAP;
##  statements in the first entry of the <C>inputblocks</C> list of this
##  entry; these strings, together with the values of <C>title</C> and
##  <C>footer</C>, are shown in a window, at the bottom of the screen.
##  The effects of calls to functions using <C>ncurses</C> are shown in the
##  rest of the screen.
##  After the execution of the statements (which may require user input),
##  the user can continue with the next entry of <C>inputblocks</C>,
##  or return to the <C>inputs</C> selection (and thus cancel the current
##  <C>inputs</C> entry), or return to the execution of the beginning of the
##  current <C>inputs</C> entry.
##  At the end of the current entry of <C>inputs</C>,
##  the user returns to the <C>inputs</C> selection.
##  <P/>
##  The &GAP; statements in the <C>cleanup</C> component, if available,
##  are executed whenever the user does not continue;
##  this is needed for deleting panels and windows that are defined in the
##  statements of the current entry.
##  <P/>
##  Note that the &GAP; statements are executed in the <E>global</E> scope,
##  that is, they have the same effect as if they would be entered at the
##  &GAP; prompt.
##  Initially, <C>NCurses.Demo</C> sets the value of
##  <C>BrowseData.defaults.work.windowParameters</C> to the parameters that
##  describe the part of the screen above the window that shows the inputs;
##  so applications of <Ref Func="NCurses.BrowseGeneric"/> use automatically
##  the maximal part of the screen as their window.
##  It is recommended to use a screen with at least <M>80</M> columns
##  and at least <M>37</M> rows.
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##
NCurses.Demo:= function( arg )
    local inputs, indent, size, infosize, win, pan, oldparas, items, width,
          csize, begin, choices, leny, lenx, i, inp, j, spl, keepsel, sel, r,
          block, l, ch, continu, wback, wpan;

    if Length( arg ) = 0 then
      inputs:= NCurses.DemoDefaults;
    elif Length( arg ) = 1 and IsList( arg[1] )
         and ForAll( arg[1], x -> IsRecord( x ) and IsBound( x.title )
                                  and IsBound( x.inputblocks ) ) then
      inputs:= arg[1];
    else
      Error( "usage: NCurses.Demo( [<inputs>] )" );
    fi;

    indent:= "  ";

    # make sure that background is clean
    wback := NCurses.newwin(0,0,0,0);
    wpan := NCurses.new_panel(wback);
    size:= NCurses.getmaxyx(0);
    infosize:= [ 12, size[2] ];
    oldparas:= BrowseData.defaults.work.windowParameters;
    BrowseData.defaults.work.windowParameters:= [ size[1] - infosize[1],
                                                  size[2], 0, 0 ];

    # Prepare the choices.
    items:= List( inputs, x -> x.title );
    width:= Maximum( List( items, NCurses.WidthAttributeLine ) )
            + LogInt( Length( items ), 10 ) + 7;
    csize:= [ Minimum( Length( items ) + 3, size[1] ),
              Minimum( Maximum( width, 51 ), size[2] ) ];
    begin:= [ QuoInt( size[1] - csize[1], 2 ),
              QuoInt( size[2] - csize[2], 2 ) ];
    choices:= rec( items:= items,
                   none:= true,
                   size:= csize,
                   begin:= begin,
                   border:= true,
                 );

    # Check whether the inputs fit into the info window.
    leny:= infosize[1] - 4;
    lenx:= infosize[2] - 2 - Length( indent );
    for i in [ 1 .. Length( inputs ) ] do
      inp:= inputs[i].inputblocks;
      for j in [ 1 .. Length( inp ) ] do
        spl:= SplitString( inp[j], "", "\n" );
        if leny < Length( spl ) then
          Print( "#E  Please reduce entry ", i, ", step ", j, ", from ",
                 Length( spl ), " to ", leny, " lines.\n" );
        fi;
        if ForAny( spl, x -> lenx < Length( x ) ) then
          Print( "#E  Please reduce the lines of entry ", i, ", step ", j,
                 ", to length ", lenx, ".\n" );
        fi;
      od;
    od;

    keepsel:= false;
    sel:= 0;
    while true do
      sel:= sel + 1;
      if not keepsel then
        choices.select:= [ sel ];
        sel:= NCurses.Select( choices );
        if sel = false then
          break;
        fi;
        r:= inputs[ sel ];
      fi;

      win:= NCurses.newwin( infosize[1], infosize[2],
                            size[1] - infosize[1],
                            QuoInt( size[2] -  infosize[2] - 1, 2 ) );
      pan:= NCurses.new_panel(win);

      for block in r.inputblocks do
        keepsel:= false;
        NCurses.werase( win );
        NCurses.wborder( win, 0 );
        NCurses.PutLine( win, 1, 1, [ NCurses.attrs.BOLD, r.title ] );
        l:= List( SplitString( block, "", "\n" ),
                  a -> Concatenation( indent, a ) );
        NCurses.PutLine(win, 2, 1, l);
        if IsBound( r.footer ) and IsString( r.footer ) then
          NCurses.PutLine( win, infosize[1]-2, 1,
                           [ NCurses.attrs.BOLD, r.footer ] );
        fi;
        NCurses.update_panels();
        NCurses.doupdate();
        NCurses.ReadString( block );
        NCurses.PutLine( win, infosize[1] - 2 , 1, [ NCurses.attrs.BOLD, 
          "'q' to quit, 'b' to repeat, any other character to continue" ] );
        NCurses.top_panel(pan);
        NCurses.curs_set(0);
        NCurses.update_panels();
        NCurses.doupdate();
        ch:= NCurses.wgetch( win );
        continu:= true;
        if   ch = IntChar( 'q' ) then
          # return to the same item in the choices list
          continu:= false;
        elif ch = IntChar( 'b' ) then
          # repeat the current item
          keepsel:= true;
          continu:= false;
        fi;
        if not continu then
          sel:= sel - 1;
          if IsBound( r.cleanup ) then
            NCurses.ReadString( r.cleanup );
          fi;
          break;
        fi;
        NCurses.update_panels();
        NCurses.doupdate();
      od;

      # Clean up.
      NCurses.del_panel( pan );
      NCurses.delwin( win );
      NCurses.endwin();

    od;

    # clean background
    NCurses.del_panel( wpan );
    NCurses.delwin( wback );
    # Reset the default window parameters for `Browse'.
    BrowseData.defaults.work.windowParameters:= oldparas;
end;


#############################################################################
##
#V  NCurses.DemoDefaults
##
NCurses.DemoDefaults:= [];

Add( NCurses.DemoDefaults,
rec( title:= "Basic NCurses Functionality",
inputblocks:= [
"# set some flags for visual mode:\n\
#                no scroll, no echo, key translations ...\n\
NCurses.SetTerm();\n\
# windows are accessed by a number, main window is 0\n\
# go into visual mode:\n\
NCurses.wrefresh(0);\n\
",
"# create a new window of size the whole screen:\n\
win := NCurses.newwin(0,0,0,0);\n\
# wrap it in a panel:\n\
panel := NCurses.new_panel(win);\n\
# update buffers, refresh screen:\n\
NCurses.update_panels(); NCurses.doupdate();\n\
",
"# move cursor, top left corner is position 0, 0:\n\
NCurses.wmove(win, 1, 5);\n\
# write a string at cursor position:\n\
NCurses.waddstr(win, \"ABCDE\");\n\
",
"# print size of window:\n\
size := NCurses.getmaxyx(win);;\n\
NCurses.PutLine(win, 2, 0, [\"Size of window: \",\n\
                            String(size)]);\n\
",
"# at end of line cursor advances to next line:\n\
NCurses.wmove(win, 3, size[2]-3);;\n\
NCurses.waddstr(win, \"abcde\");;\n\
",
"# create another wrapped window:\n\
win2 := NCurses.newwin(12, size[2]-23, 1, 12);;\n\
pan2 := NCurses.new_panel(win2);;\n\
NCurses.wmove(win2, 0, 0);;\n\
str := List([33..126],CHAR_INT);; ConvertToStringRep(str);;\n\
NCurses.waddstr(win2, Concatenation(str, str, str));;\n\
# new panel is top:\n\
NCurses.update_panels(); NCurses.doupdate();\n\
",
"# make window/panel more visible with a border:\n\
# first with prescribed characters for lines and corners ...\n\
NCurses.wborder(win2, List(\"abcdefgh\", IntChar));;\n\
NCurses.update_panels(); NCurses.doupdate();\n\
",
"# now with default lines, depending on terminal capabilities:\n\
NCurses.wborder(win2, 0);;\n\
NCurses.update_panels(); NCurses.doupdate();\n\
",
"# moving the *panel*:\n\
for i in [0..2] do\n\
  NCurses.move_panel(pan2, 1, 10*i);\n\
  NCurses.update_panels(); NCurses.doupdate();\n\
  # sleep 200 ms\n\
  NCurses.napms(200);\n\
od;\n\
",
"# set attributes for next written characters:\n\
NCurses.werase(win);; NCurses.top_panel(panel);; \n\
for c in [\"STANDOUT\",\"UNDERLINE\",\"REVERSE\",\"BLINK\",\
\"DIM\",\"BOLD\"] do\n\
  NCurses.wattrset(win, NCurses.attrs.NORMAL);\n\
  NCurses.waddstr(win, Concatenation(c, \"\\t\\t\"));\n\
  NCurses.wattrset(win, NCurses.attrs.(c));\n\
  NCurses.waddstr(win, \"ABC 123\\n\");\n\
od; NCurses.update_panels();; NCurses.doupdate();;\n\
",
"# colors are accessed by 'color pairs' for fore-/background:\n\
NCurses.werase(win);; defc := NCurses.defaultColors;;\n\
NCurses.wmove(win, 0, 0);;\n\
for a in defc do for b in defc do\n\
  # NCurses.ColorAttr(a, a) give color a in default bg\n\
  NCurses.wattrset(win, NCurses.ColorAttr(a, b));\n\
  NCurses.waddstr(win, Concatenation(a,\"/\",b,\"\\t\"));\n\
od; od; NCurses.update_panels();; NCurses.doupdate();;\n\
",
"# change attribute in whole window:\n\
NCurses.wbkgd(win, NCurses.attrs.BOLD + \n\
                   NCurses.ColorAttr(\"yellow\",\"blue\"));;\n\
NCurses.wmove(win, 4, 10);; NCurses.wclrtobot(win);;\n\
NCurses.waddstr(win, \"\\n\\nThis is also used for new text!\\n\");;\n\
NCurses.update_panels();; NCurses.doupdate();;\n\
",
"# get other panel back, and show line drawing characters:\n\
NCurses.show_panel(pan2);;NCurses.werase(win2);;\n\
for c in RecNames(NCurses.lineDraw) do\n\
  NCurses.waddstr(win2, c);\n\
  NCurses.waddch(win2, '\\t');\n\
  NCurses.waddch(win2, NCurses.lineDraw.(c));\n\
  NCurses.waddch(win2, '\\t');\n\
od; NCurses.update_panels();; NCurses.doupdate();;\n\
",
"# draw some lines, NCurses.Grid takes crossings into account:\n\
NCurses.werase(win2);;\n\
NCurses.Grid(win2, 3, 20, 4, 30, [3,5,8,11,20], [4,12,13,25,30]);\n\
NCurses.update_panels();; NCurses.doupdate();;\n\
",
"# Do not forget to clean up before leaving the ncurses application.\n\
NCurses.del_panel( panel );;\n\
NCurses.delwin( win );;\n\
NCurses.del_panel( pan2 );;\n\
NCurses.delwin( win2 );;\n\
NCurses.endwin();;\n\
NCurses.update_panels();; NCurses.doupdate();;\n\
",
],
cleanup:= "NCurses.del_panel( panel );;\n\
NCurses.delwin( win );;\n\
NCurses.del_panel( pan2 );;\n\
NCurses.delwin( win2 );;\n\
NCurses.endwin();;\n\
NCurses.update_panels(); NCurses.doupdate();\n\
",
) );

Add( NCurses.DemoDefaults,
rec( title:= "NCurses Utilities",
inputblocks:= [
"# create a new window of size the whole screen:\n\
win := NCurses.newwin(0,0,0,0);\n\
# wrap it in a panel:\n\
pan := NCurses.new_panel(win);\n\
",
"# get a string interactively:\n\
userstr := NCurses.GetLineFromUser( rec(\n\
  prefix:= \" Type string and <Return>: \", begin:= [ 10, 2 ] ) );;\n\
NCurses.wmove(win, 0, 0);\n\
NCurses.waddstr(win, userstr);\n\
# go back to visual mode\n\
NCurses.update_panels();; NCurses.doupdate();;\n\
",
"# and another interactive utility:\n\
fruits := [\"apple\", \"pear\", \"banana\", \"orange\"];;\n\
r := rec(items := fruits);;\n\
res:= NCurses.Select(r);;\n\
NCurses.waddstr(win, Concatenation(\"\\nAh, you like \",\n\
                      fruits[res], \"s!\\n\"));;\n\
NCurses.update_panels();; NCurses.doupdate();;\n\
",
"# here is a simple pager utility:\n\
fname := Filename(DirectoriesPackageLibrary(\"Browse\"),\n\
                  \"../PackageInfo.g\");;\n\
sf := StringFile(fname);;\n\
NCurses.Pager(sf, true, 10, 60, 2, 3);;\n\
",
"# Do not forget to clean up before leaving the ncurses application.\n\
NCurses.del_panel( pan );;\n\
NCurses.delwin( win );;\n\
NCurses.endwin();;\n\
NCurses.update_panels();; NCurses.doupdate();;\n\
",
],
cleanup:= "NCurses.del_panel( pan );;\n\
NCurses.delwin( win );;\n\
NCurses.endwin();;\n\
NCurses.update_panels(); NCurses.doupdate();\n\
",
) );


#############################################################################
##
##  mouse.g
##
Add(NCurses.DemoDefaults, rec(
title := "Using mouse with ncurses (if terminal allows)",
inputblocks := [
"# just call the mouse demo function\n\
NCurses.MouseDemo();\n\
" 
] ));

#############################################################################
##
##  ctbldisp.g
##
Add( NCurses.DemoDefaults,
rec( title:= "Browsing in Character Tables (non-interactive)",
inputblocks:= [
"n:= [ 14, 14, 14 ];;  # ``do nothing'' input (means timeout)\n\
BrowseData.SetReplay( Concatenation(\n\
  \"DDRRUL\", n,              # scroll in the table by screens ...\n\
  \"dddddrrrrrlluu\", n, n,   # ... and by cells, ...\n\
  \"Q\" ) );                  # ... and quit\n\
Browse( CharacterTable( \"HN\" ) );\n\
",
"BrowseData.SetReplay( Concatenation(\n\
  \"se\", n,                # select an entry, ...\n\
  \"ddrruuuddlll\", n, n,   # ... move it around, ...\n\
  \"Q\" ) );                # ... and quit\n\
Browse( CharacterTable( \"HN\" ) );\n\
",
"BrowseData.SetReplay( Concatenation(\n\
  \"/135\",                   # enter the search pattern 135, ...\n\
  [ NCurses.keys.ENTER ], n,  # ... start the search, ...\n\
  \"nnnnn\", n, n,            # ... search further (five times), ...\n\
  \"Q\" ) );                  # ... and quit\n\
Browse( CharacterTable( \"HN\" ) );\n\
",
"BrowseData.SetReplay( Concatenation(\n\
  \"scsc\", n,  # sort & categorize by the first column, ...\n\
  \"sr\", n,    # ... select the first row, ...\n\
  \"dddd\", n,  # ... move down the selection, ...\n\
  \"x\", n, n,  # ... expand the selected category, ...\n\
  \"Q\" ) );    # ... and quit\n\
Browse( CharacterTable( \"HN\" ) );\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );


#############################################################################
##
##  tomdisp.g
##
Add( NCurses.DemoDefaults,
rec( title:= "Browsing in Tables of Marks (non-interactive)",
inputblocks:= [
"d:= [ NCurses.keys.DOWN ];;  r:= [ NCurses.keys.RIGHT ];;\n\
c:= [ NCurses.keys.ENTER ];;  n:= [ 14, 14, 14 ];;  # ``do nothing''\n\
BrowseData.SetReplay( Concatenation(\n\
  \"DDRRR\", n,  # scroll in the table, ...\n\
  \"?dddd\", n,  # ... ask for help, scroll in the help table, ...\n\
  \"Q\", n, n,   # ... leave the help table,\n\
  \"Q\" ) );     # ... and quit\n\
Browse( TableOfMarks( \"A10\" ) );\n\
",
"BrowseData.SetReplay( Concatenation(\n\
  \"/100\",       # enter a search pattern, ...\n\
  d, d, r, d, d, d, r,  # ... change search parameters, ...\n\
  c, \"nn\", n,   # ... search for the (exact) value 100 (three times)\n\
  c, n, n,        # (no more occurrences of 100, confirm)\n\
  \"Q\" ) );      # ... and quit\n\
Browse( TableOfMarks( \"A10\" ) );  BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );


#############################################################################
##
##  atlasbrowse.g
##
##  The code is contained in 'app/atlasrep_only.g'.
##


#############################################################################
##
##  manual.g
##
Add( NCurses.DemoDefaults,
rec( title:= "The GAP Manuals (non-interactive)",
inputblocks:= [
"n:= [ 14, 14, 14 ];;  # ``do nothing'' input (means timeout)\n\
BrowseData.SetReplay( Concatenation(\n\
  \"x\", n,      # expand the first chapter of the Tutorial, ...\n\
  \"d\", n,      # ... move down, ...\n\
  \"x\", n,      # ... expand the first section of the Tutorial, ...\n\
  \"d\", n, n,   # ... move to the text\n\
  \"Q\" ) );     # ... and quit\n\
BrowseGapManuals( \"inline/collapsed\" );\n\
",
"BrowseData.SetReplay( Concatenation(\n\
  \"/Browse\", [ NCurses.keys.ENTER ], n,  # go to the Browse manual,\n\
  \"xd\", n,                               # to its first chapter,\n\
  \"xdd\", n,                              # to its first section,\n\
  \"xd\", n, n,                            # to its text,\n\
  \"Q\" ) );                               # and quit\n\
BrowseGapManuals( \"inline/collapsed\" );\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );


#############################################################################
##
##  gapbibl.g
##
Add( NCurses.DemoDefaults,
rec( title:= "The GAP Bibliography (non-interactive)",
inputblocks:= [
"n:= [ 14, 14, 14 ];;  # ``do nothing'' input (means timeout)\n\
BrowseData.SetReplay( Concatenation(\n\
  \"scrrsc\", n,     # sort and categorize by year,\n\
  \"seddddd\", n,    # select an entry, scroll down,\n\
  \"xddddd\", n, n,  # expand a category row, scroll down,\n\
  \"Q\" ) );         # and quit\n\
BrowseBibliography();;\n\
",
"BrowseData.SetReplay( Concatenation(\n\
  \"scsc\", n,       # sort and categorize by authors,\n\
  \"Xse\", n,        # expand all category rows, select an entry,\n\
  \"dddddd\", n, n,  # scroll down,\n\
  \"Q\" ) );         # and quit\n\
BrowseBibliography();;\n\
",
"BrowseData.SetReplay( Concatenation(\n\
   \"scrrrsc\", n,             # sort and categorize by journal,\n\
   \"/J. Algebra\",            # enter a search string,\n\
   [ NCurses.keys.ENTER ], n,  # start the search,\n\
   \"nxddd\", n, n,            # search further, expand, scroll down,\n\
   \"Q\" ) );                  # and quit\n\
BrowseBibliography();;\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );


#############################################################################
##
##  filetree.g
##
##  The code is contained in 'app/io_only.g'.
##


#############################################################################
##
##  puzzle.g
##
Add( NCurses.DemoDefaults,
rec( title:= "Loyd's Fifteen (non-interactive)",
inputblocks:= [
"BrowseData.SetReplay( BrowsePuzzleSolution.steps );\n\
BrowsePuzzle( 4, 4, BrowsePuzzleSolution.init );;\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Loyd's Fifteen (interactive)",
inputblocks:= [
"BrowsePuzzle();;\n\
" ],
footer:= "(arrow keys to move, 'Q' to quit)",
) );


#############################################################################
##
##  solitair.g
##
Add( NCurses.DemoDefaults,
rec( title:= "Peg Solitaire, 33 holes (non-interactive)",
inputblocks:= [
"BrowseData.SetReplay( PegSolitaireSolutions.33 );\n\
PegSolitaire( 33 );\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Peg Solitaire, 33 holes (interactive)",
inputblocks:= [
"PegSolitaire( 33 );\n\
" ],
footer:= "(arrow keys to move, 'j' and arrow keys to jump, 'Q' to quit)",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Peg Solitaire, 37 holes (non-interactive)",
inputblocks:= [
"BrowseData.SetReplay( PegSolitaireSolutions.37 );\n\
PegSolitaire( 37 );\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Peg Solitaire, 37 holes (interactive)",
inputblocks:= [
"PegSolitaire( 37 );\n\
" ],
footer:= "(arrow keys to move, 'j' and arrow keys to jump, 'Q' to quit)",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Peg Solitaire, 45 holes (non-interactive)",
inputblocks:= [
"BrowseData.SetReplay( PegSolitaireSolutions.45 );\n\
PegSolitaire( 45 );\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Peg Solitaire, 45 holes (interactive)",
inputblocks:= [
"PegSolitaire( 45 );\n\
" ],
footer:= "(arrow keys to move, 'j' and arrow keys to jump, 'Q' to quit)",
) );


#############################################################################
##
##  rubik.g
##
Add( NCurses.DemoDefaults,
rec( title:= "Rubik's Cube (non-interactive)",
inputblocks:= [
"cubegens := [\n\
  ( 1, 3, 8, 6)( 2, 5, 7, 4)( 9,33,25,17)(10,34,26,18)(11,35,27,19),\n\
  ( 9,11,16,14)(10,13,15,12)( 1,17,41,40)( 4,20,44,37)( 6,22,46,35),\n\
  (17,19,24,22)(18,21,23,20)( 6,25,43,16)( 7,28,42,13)( 8,30,41,11),\n\
  (25,27,32,30)(26,29,31,28)( 3,38,43,19)( 5,36,45,21)( 8,33,48,24),\n\
  (33,35,40,38)(34,37,39,36)( 3, 9,46,32)( 2,12,47,29)( 1,14,48,27),\n\
  (41,43,48,46)(42,45,47,44)(14,22,30,38)(15,23,31,39)(16,24,32,40) \
];;\n\
",
"choice:= List( [ 1 .. 30 ], i -> Random( [ 1 .. 6 ] ) );;\n\
input:= \"tlfrbd\";;  input:= input{ choice };;\n\
input:= Concatenation(\n\
  input{ [ 1 .. 20 ] }, \"s\",    # switch to number display\n\
  input{ [ 21 .. 25 ] }, \"s\",   # switch to color display\n\
  input{ [ 26 .. 30 ] } );;\n\
",
"BrowseData.SetReplay( input );\n\
BrowseRubiksCube( Product( cubegens{ choice } ) );;\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Rubik's Cube (interactive)",
inputblocks:= [
"BrowseRubiksCube();;\n\
" ],
footer:= "('tlfrbdTLFRBD' to move, 'Q' to quit)",
) );


#############################################################################
##
##  knight.g
##
Add( NCurses.DemoDefaults,
rec( title:= "Changing Sides (non-interactive)",
inputblocks:= [
"BrowseData.SetReplay( BrowseChangeSidesSolutions[1] );\n\
BrowseChangeSides();\n\
BrowseData.SetReplay( false );\n\
" ],
footer:= "(enter 'Q' for interrupting)",
cleanup:= "BrowseData.SetReplay( false );\n\
",
) );

Add( NCurses.DemoDefaults,
rec( title:= "Changing Sides (interactive)",
inputblocks:= [
"BrowseChangeSides();\n\
" ],
footer:= "(triples of arrow keys to move, 'Q' to quit)",
) );


#############################################################################
##
##  sudoku.g
##
Add( NCurses.DemoDefaults,
rec( title:= "Sudoku (interactive)",
inputblocks:= [
"# generate a random game\n\
PlaySudoku();\n"],
footer :=  "(try 'h' for a hint, 's' for the solution, '?' for more help)",
) );


#############################################################################
##
#E

