GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
############################################################################# ## #W puzzle.g GAP 4 package `browse' Thomas Breuer ## #Y Copyright (C) 2006, Lehrstuhl D für Mathematik, RWTH Aachen, Germany ## ############################################################################# ## #F BrowsePuzzle( [<m>, <n>[, <pi>]] ) ## ## <#GAPDoc Label="Puzzle_section"> ## <Section Label="sec:mnpuzzle"> ## <Heading>A Puzzle</Heading> ## <Index Subkey="A Puzzle">game</Index> ## ## We consider an <M>m</M> by <M>n</M> rectangle of squares numbered from ## <M>1</M> to <M>m n - 1</M>, the bottom right square is left empty. ## The numbered squares are permuted ## by successively exchanging the empty square and a neighboring square ## such that in the end, the empty cell is again in the bottom right corner. ## ## <Table Align="|c|c|c|c|"> ## <HorLine/> ## <Row> ## <Item><M> 7</M></Item> ## <Item><M>13</M></Item> ## <Item><M>14</M></Item> ## <Item><M> 2</M></Item> ## </Row> ## <HorLine/> ## <Row> ## <Item><M> 1</M></Item> ## <Item><M> 4</M></Item> ## <Item><M>15</M></Item> ## <Item><M>11</M></Item> ## </Row> ## <HorLine/> ## <Row> ## <Item><M> 6</M></Item> ## <Item><M> 8</M></Item> ## <Item><M> 3</M></Item> ## <Item><M> 9</M></Item> ## </Row> ## <HorLine/> ## <Row> ## <Item><M>10</M></Item> ## <Item><M> 5</M></Item> ## <Item><M>12</M></Item> ## <Item><M> </M></Item> ## </Row> ## <HorLine/> ## </Table> ## ## The aim of the game is to order the numbered squares via these moves. ## <P/> ## For the case <M>m = n = 4</M>, the puzzle is (erroneously?) known under ## the name <Q>Sam Loyd's Fifteen</Q>, see <Cite Key="LoydFifteenWeb"/> ## and <Cite Key="HistGames"/> for more information and references. ## ## <ManSection> ## <Func Name="BrowsePuzzle" Arg="[m, n[, pi]]"/> ## ## <Returns> ## a record describing the initial and final status of the puzzle. ## </Returns> ## ## <Description> ## This function shows the rectangle in a window. ## <P/> ## The arguments <A>m</A> and <A>n</A> are the dimensions of the rectangle, ## the default for both values is <M>4</M>. ## The initial distribution of the numbers in the squares can be prescribed ## via a permutation <A>pi</A>, the default is a random element in the ## alternating group on the points <M>1, 2, \ldots, <A>m</A> <A>n</A> - 1</M>. ## (Note that the game has not always a solution.) ## <P/> ## In any case, the empty cell is selected, and the selection can be ## moved to neighboring cells via the arrow keys, ## or to any place in the same row or column via a mouse click. ## <P/> ## The return value is a record with the components ## <C>dim</C> (the pair <C>[ m, n ]</C>), ## <C>init</C> (the initial permutation), ## <C>final</C> (the final permutation), and ## <C>steps</C> (the number of transpositions that were needed). ## ## <Example><![CDATA[ ## gap> BrowseData.SetReplay( Concatenation( ## > BrowsePuzzleSolution.steps, "Q" ) ); ## gap> BrowsePuzzle( 4, 4, BrowsePuzzleSolution.init );; ## gap> BrowseData.SetReplay( false ); ## ]]></Example> ## ## An implementation using only mouse clicks but no key strokes ## is available in the &GAP; package <Package>XGAP</Package> ## (see <Cite Key="XGAP"/>). ## <P/> ## <E>Implementation remarks</E>: ## The game board is implemented via a browse table, ## without row and column labels, ## with static header, dynamic footer, and individual <C>minyx</C> function. ## Only one mode is needed in which one cell is selected, ## and besides the standard actions for quitting the table, asking for help, ## and saving the current window contents, ## only the four moves via the arrow keys and mouse clicks are admissible. ## <Index>mouse events</Index> ## <P/> ## Some standard <Ref Func="NCurses.BrowseGeneric"/> functionality, ## such as scrolling, selecting, and searching, ## are not available in this application. ## <P/> ## The code can be found in the file <F>app/puzzle.g</F> of the package. ## </Description> ## </ManSection> ## </Section> ## <#/GAPDoc> ## BindGlobal( "BrowsePuzzle", function( arg ) local m, n, pi, numbers, matrix, box, move, mode, max, empty, fmatrix, i, j, table; # Get and check the arguments. if Length( arg ) = 0 then m:= 4; n:= 4; pi:= Random( AlternatingGroup( 15 ) ); elif Length( arg ) = 2 and IsPosInt( arg[1] ) and IsPosInt( arg[2] ) then m:= arg[1]; n:= arg[2]; pi:= Random( AlternatingGroup( m*n-1 ) ); elif Length( arg ) = 3 and IsPosInt( arg[1] ) and IsPosInt( arg[2] ) then m:= arg[1]; n:= arg[2]; pi:= arg[3]; else Error( "usage: BrowsePuzzle( [<m>, <n>[, <pi>]] )" ); fi; if not IsPerm( pi ) or m*n-1 < LargestMovedPointPerm( pi ) then Error( "<pi> must be a permutation on 1 .. ", m*n-1 ); fi; numbers:= ListPerm( pi ); Append( numbers, [ Length( numbers )+1 .. m*n ] ); matrix:= List( [ 1 .. m ], i -> numbers{ [ n*(i-1)+1 .. n*i ] } ); box:= [ m, n ]; # Supported actions are # - moving the free position, # - entering the help mode, # - and leaving the table move:= function( t, y, x ) local new, help1, help2; new:= box + [ y, x ]; if 0 < new[1] and 0 < new[2] and new[1] <= m and new[2] <=n then help1:= t.work.mainFormatted[ 2 * box[1] ][ 2 * box[2] ]; help2:= t.work.mainFormatted[ 2 * new[1] ][ 2 * new[2] ]; t.work.mainFormatted[ 2 * box[1] ][ 2 * box[2] ]:= help2; t.work.mainFormatted[ 2 * new[1] ][ 2 * new[2] ]:= help1; help1:= matrix[ box[1] ][ box[2] ]; help2:= matrix[ new[1] ][ new[2] ]; matrix[ box[1] ][ box[2] ]:= help2; matrix[ new[1] ][ new[2] ]:= help1; t.dynamic.Return.steps:= t.dynamic.Return.steps + 1; t.dynamic.Return.final:= t.dynamic.Return.final * ( help1, help2 ); box:= new; t.dynamic.selectedEntry:= t.dynamic.selectedEntry + 2 * [ y, x ]; t.dynamic.changed:= true; fi; return t.dynamic.changed; end; # Construct the mode. mode:= BrowseData.CreateMode( "puzzle", "select_entry", [ # standard actions [ [ "E" ], BrowseData.actions.Error ], [ [ "q", [ [ 27 ], "<Esc>" ] ], BrowseData.actions.QuitMode ], [ [ "Q" ], BrowseData.actions.QuitTable ], [ [ "?", [ [ NCurses.keys.F1 ], "<F1>" ] ], BrowseData.actions.ShowHelp ], [ [ [ [ NCurses.keys.F2 ], "<F2>" ] ], BrowseData.actions.SaveWindow ], # moves via arrow keys [ [ [ [ NCurses.keys.RIGHT ], "arrow right" ] ], rec( helplines:= [ "move the free position one cell to the right" ], action:= t -> move( t, 0, 1 ) ) ], [ [ [ [ NCurses.keys.LEFT ], "arrow left" ] ], rec( helplines:= [ "move the free position one cell to the left" ], action:= t -> move( t, 0, -1 ) ) ], [ [ [ [ NCurses.keys.DOWN ], "arrow down" ] ], rec( helplines:= [ "move the free position one cell down" ], action:= t -> move( t, 1, 0 ) ) ], [ [ [ [ NCurses.keys.UP ], "arrow up" ] ], rec( helplines:= [ "move the free position one cell up" ], action:= t -> move( t, -1, 0 ) ) ], # moves via mouse actions [ [ "M" ], BrowseData.actions.ToggleMouseEvents ], [ [ [ [ NCurses.keys.MOUSE, "BUTTON1_PRESSED" ], "<Mouse1Down>" ], [ [ NCurses.keys.MOUSE, "BUTTON1_CLICKED" ], "<Mouse1Click>" ] ], rec( helplines:= [ "move the free position to the clicked cell" ], action:= function( t, data ) local pos, diff, i; pos:= BrowseData.PositionInBrowseTable( t, data ); if pos[1] = "main" and IsEvenInt( pos[2][1] ) and IsEvenInt( pos[2][2] ) then if pos[2][1] = t.dynamic.selectedEntry[1] then diff:= pos[2][2] - t.dynamic.selectedEntry[2]; for i in [ 1 .. AbsInt( diff / 2 ) ] do t.dynamic.changed:= move( t, 0, SignInt( diff ) ); od; elif pos[2][2] = t.dynamic.selectedEntry[2] then diff:= pos[2][1] - t.dynamic.selectedEntry[1]; for i in [ 1 .. AbsInt( diff / 2 ) ] do t.dynamic.changed:= move( t, SignInt( diff ), 0 ); od; fi; fi; return t.dynamic.changed; end ) ], ] ); # Construct the browse table. max:= Length( String( m * n - 1 ) ); empty:= RepeatedString( " ", max + 2 ); fmatrix:= []; for i in [ 1 .. m ] do fmatrix[ 2*i ]:= []; for j in [ 1 .. n ] do fmatrix[ 2*i ][ 2*j ]:= [ empty, [ NCurses.attrs.BOLD, true, Concatenation( " ", String( matrix[i][j], max ), " " ) ], empty ]; od; od; table:= rec( work:= rec( align:= "ct", minyx:= [ 4 * m + 6, n * ( 3 + max ) + 1 ], header:= [ "", [ NCurses.attrs.UNDERLINE, true, Concatenation( String(m), " by ", String(n), " puzzle" ) ], "" ], main:= [], m:= m, n:= n, mainFormatted:= fmatrix, sepRow:= "-", sepCol:= "|", footer:= function( t ) if IsOne( t.dynamic.Return.final ) then return [ "", [ Concatenation( String( t.dynamic.Return.steps ), " steps " ), NCurses.ColorAttr( "red", -1 ), "(done)" ] ]; else return [ "", Concatenation( String( t.dynamic.Return.steps ), " steps" ) ]; fi; end, availableModes:= [ mode ], cacheEntries:= true, SpecialGrid:= BrowseData.SpecialGridLineDraw, ), dynamic:= rec( activeModes:= [ mode ], selectedEntry:= [ 2*m, 2*n ], Return:= rec( dim:= [ m, n ], init:= pi, final:= pi, steps:= 0, ), ), ); # Leave the marked cell empty. table.work.mainFormatted[ 2*m ][ 2*n ][2]:= empty; # Show the browse table, and return its return value. return NCurses.BrowseGeneric( table ); end ); ############################################################################# ## #V BrowsePuzzleSolution ## BindGlobal( "BrowsePuzzleSolution", rec( dim := [ 4, 4 ], init := ( 1, 7,15,12, 9, 6, 4, 2,13,10, 8,11, 3,14, 5), steps := [259,259,259,260,260,260,258,261,261,261,259,260,258,260,259,261,258,258, 261,259,259,260,258,260,260,259,261,261,258,260,258,261,261,259,259,260, 258,261,259,260,260,260,258,258,258,261,259,260,258,261,259,259,260,258, 258,261,261,259,259,261,258,260,260,260,259,261,261,258,261,259,260,260, 260,258,258,261,261,261,259,260,260,260,258,261,261,261,259,260,260,258, 260,259,261,261,261,258,260,260,260,259,261,261,258,260,260,259,261,261, 258,261,259,260,260,260,258,261,261,259,260,260,258,261,261,261], ) ); ############################################################################# ## #E