/* Copyright 1976-2000 by Robert Levad Update Record: RFL 2000 modify to run in Windows using Regina Rexx RFL 1986 Generate Magic Squares Of All Sizes in VM/REXX (previously limited to small squares by available computing power) RFL 1985 Re-write in Apple Basic with performance improvements RFL 1976 This program originally written in FORTRAN - WATFIV for a computer science class at Iowa State University. At that time it was quite a bit less efficient and did not include this method for odd size squares, which I wrked out later. The main point of the project was to write a program that could generate magic squares of all sizes, but my research failed to uncover a workable algorith for Single Even sizes. In testing different methods of making Double Even squares more easily, I developed a method for doing Single Even squares also. Definition of a magic square: Although there are other types of squares called "magic", this program deals with the standard definition as follows. Magic squares are arrays of numbers where the rows, columns and the diagonals add up to the same number, (called the square's constant). Also, no number is repeated. For example, a 3 by 3 square consists of all the numbers from 1 to 15. This is one such square of size 3. 8 3 4 1 5 9 6 7 2 in this case, the rows add to 15 8+3+4=15 1+5+9=15 6+7+2=15 the columns add to 15 8+1+6=15 3+5+7=15 4+9+2=15 and the diagonals add to 15 8+5+2=15 6+5+4=15 There are 3 basic types of squares. "Odd", where the number of rows and columns is an odd number (3,5,7 etc.) "Double Even", where the number of rows and columns is an even number divisible by 4. (4,8,12 etc.) "Single Even", where the number of rows and columns is an even number not divisible by 4. (6,10,14 etc.) Language reference: As this program is written in REXX. It is necessary to run it in the REXX environment. This is a quite powerful language that can also be fairly easy to learn. To obtain the version of REXX that will allow this program to run on a PC, download it from: http://www.lightlink.com/hessling/downloads.html After extraction, Open "magic.rex" with "regina.exe" and it will prompt for the size of square to build. Historical references: The method shown here uses variations on techniques documented by Philippe de La Hire (1640-1718). http://www-groups.dcs.st-and.ac.uk/history/Mathematicians/La_Hire.html http://www.newadvent.org/cathen/08742b.htm */ /* magic square builder program begins here */ trace 'o' /* get the SIZE for the square */ parse arg size /* prompt for input if not entered on the command line */ if size = "" then do say "Please enter an integer for the size of square to make" parse pull size end /* check to see if a valid number was entered */ select when datatype(size,"NUM") = 0 then signal error when size <> trunc(size) then signal error when size < 3 then signal error when size > 100 then do say "Generating large squares can take considerable processing power and time." say "Are you sure you want to proceed?" pull answer if answer <> "Y" then signal exit end otherwise nop end /********************************************************************** * Got The Size, Now Set Up The Arrays And Calculate The Constant * * Size = Size Of The Magic Square On each Side * * Constant = What each row, column and diagonal add up to, * * calculated by the formula: (size * (size * Size + 1)) / 2 * * e.g. for size = 3: constant = 15 = (3*(3*3+1))/2. * **********************************************************************/ base. = 0 /* initialize array to zeros */ magic. = 0 /* initialize array to zeros */ match. = 0 /* initialize array to zeros */ magic_test = "MAGIC" /* initialize value */ length = length(size*size) + 1 /* length of output line */ constant = size * (size * size + 1) / 2 /* constant for square */ outfile = "c:\temp\MAGIC"size".TXT" /* output file */ wrk1file = "c:\temp\WRK1.TXT" /* output file */ wrk2file = "c:\temp\WRK2.TXT" /* output file */ 'del c:\temp\wrk1.txt' 'del c:\temp\wrk2.txt' 'del c:\temp\magic*.txt' select when size/4 = trunc(size/4) then square = "DOUBLE" when size/2 = trunc(size/2) then square = "SINGLE" when datatype(size,"NUM") = 1 & size = trunc(size) then square = "ODD" otherwise square = "NOT VALID" end select when square = "ODD" then do /***************************************************************** * fill base square for type ODD * * this is how a square of size "7" is initially filled * ***************************************************************** * 4 5 6 7 1 2 3 * * 3 4 5 6 7 1 2 * * 2 3 4 5 6 7 1 * * 1 2 3 4 5 6 7 * * 7 1 2 3 4 5 6 * * 6 7 1 2 3 4 5 * * 5 6 7 1 2 3 4 * *******************************/ startvalue = trunc(size/2) + 2 do row = 1 to size by 1 if row//50 = 0 then say "Filling base table row "row if startvalue = 1 then startvalue = size else startvalue = startvalue - 1 cellvalue = startvalue do column = 1 to size by 1 base.row.column = cellvalue if cellvalue = size then cellvalue = 1 else cellvalue = cellvalue + 1 end end end when square = "DOUBLE" then do /***************************************************************** * fill base square where size is divisible by 4 * * this is how a square of size "8" is initially filled * ***************************************************************** * 1 2 3 4 5 6 7 8 * * 8 7 6 5 4 3 2 1 * * 1 2 3 4 5 6 7 8 * * 8 7 6 5 4 3 2 1 * * 8 7 6 5 4 3 2 1 * * 1 2 3 4 5 6 7 8 * * 8 7 6 5 4 3 2 1 * * 1 2 3 4 5 6 7 8 * ***********************************/ direction = "RIGHT" do row = 1 to size by 1 if row//50 = 0 then say "Filling base table row "row select when direction = "RIGHT" then do do column = 1 to size by 1 base.row.column = column end direction = "LEFT" end when direction = "LEFT" then do do column = size to 1 by -1 base.row.column = size-column+1 end if row ^= size/2 then direction = "RIGHT" end otherwise signal error end end end when square = "SINGLE" then do /***************************************************************** * routine for squares where size is divisible by 2 but not by 4 * * this is how a square of size "6" is initially filled * ***************************************************************** * 1 2 3 4 5 6 * * 6 5 4 3 2 1 * * 1 2 3 4 5 6 * * 6 5 4 3 2 1 * * 6 5 4 3 2 1 * * 1 2 3 4 5 6 * ***************************/ direction = "RIGHT" do row = 1 to size by 1 if row//50 = 0 then say "Filling base table row "row select when direction = "RIGHT" then do do column = 1 to size by 1 base.row.column = column end direction = "LEFT" end when direction = "LEFT" then do do column = size to 1 by -1 base.row.column = size-column+1 end if row ^= size/2+1 then direction = "RIGHT" end otherwise signal error /* should never occur */ end end /******************************* * swap cells at ends of row 2 * ******************************* * 1 2 3 4 5 6 * * "1" 5 4 3 2 "6" * * 1 2 3 4 5 6 * * 6 5 4 3 2 1 * * 6 5 4 3 2 1 * * 1 2 3 4 5 6 * ***************************/ swap_row = 2 swap_column1 = 1 swap_column2 = size parse value base.swap_row.swap_column1 base.swap_row.swap_column2 with base.swap_row.swap_column2 base.swap_row.swap_column1 /*********************************************** * swap cells at ends of row just above center * *********************************************** * 1 2 3 4 5 6 * * 1 5 4 3 2 6 * * "6" 2 3 4 5 "1" * * 6 5 4 3 2 1 * * 6 5 4 3 2 1 * * 1 2 3 4 5 6 * ***************************/ swap_row = size / 2 swap_column1 = 1 swap_column2 = size parse value base.swap_row.swap_column1 base.swap_row.swap_column2 with base.swap_row.swap_column2 base.swap_row.swap_column1 /*********************************************** * swap center cells of row 1 down from center * *********************************************** * 1 2 3 4 5 6 * * 1 5 4 3 2 6 * * 6 2 3 4 5 1 * * 6 5 "3" "4" 2 1 * * 6 5 4 3 2 1 * * 1 2 3 4 5 6 * ***************************/ swap_row = size / 2 + 1 swap_column1 = size / 2 swap_column2 = size / 2 + 1 parse value base.swap_row.swap_column1 base.swap_row.swap_column2 with base.swap_row.swap_column2 base.swap_row.swap_column1 /*********************************** * swap center cells of bottom row * *********************************** * 1 2 3 4 5 6 * * 1 5 4 3 2 6 * * 6 2 3 4 5 1 * * 6 5 3 4 2 1 * * 6 5 4 3 2 1 * * 1 2 "4" "3" 5 6 * ***************************/ swap_row = size swap_column1 = size / 2 swap_column2 = size / 2 + 1 parse value base.swap_row.swap_column1 base.swap_row.swap_column2 with base.swap_row.swap_column2 base.swap_row.swap_column1 end otherwise signal error end /*********************************** * take the resulting square * *********************************** * 1 2 3 4 5 6 * * 1 5 4 3 2 6 * * 6 2 3 4 5 1 * * 6 5 3 4 2 1 * * 6 5 4 3 2 1 * * 1 2 4 3 5 6 * *************************** ***************************************************** * generate values that match the formula * * (cell value * size ) - size and transpose * ***************************************************** * 0 30 30 30 0 0 * * 6 24 24 6 24 6 * * 18 18 12 12 18 12 * * 12 12 18 18 12 18 * * 24 6 6 24 6 24 * * 30 0 0 0 30 30 * **************************** ************************************************************ * then add the base square values to the 2nd square values * * which results in a magic square * * (0+1=1)(2+30=32)(3+30=33)(4+30=34)(5+0=5)(6+0=6) etc * ************************************************************ * 1 32 33 34 5 6 * * 7 29 28 9 26 12 * * 24 20 15 16 23 13 * * 18 17 21 22 14 19 * * 30 11 10 27 8 25 * * 31 2 4 3 35 36 * ***************************************************************** * this portion is the same no matter which type square is built * *****************************************************************/ say "Building the Magic Square" do row = 1 to size by 1 base1line = "" base2line = "" if row//50 = 0 then say "Building Row "row do column = 1 to size by 1 base1line = base1line||right(base.row.column,length) baserow = size - column + 1 basecolumn = row magic.row.column = base.baserow.basecolumn magic.row.column = magic.row.column * size - size base2line = base2line||right(magic.row.column,length) magic.row.column = magic.row.column + base.row.column end call lineout wrk1file,base1line call lineout wrk2file,base2line end /***************************************************************** * output the results to a text file * *****************************************************************/ width = length*size call lineout outfile,"This is a square of type:" select when square = "ODD" then call lineout outfile,'"Odd" (not divisible by 2)' when square = "SINGLE" then call lineout outfile,'"Single Even" (divisible by 2 but not by 4)' when square = "DOUBLE" then call lineout outfile,'"Double Even" (divisible by 4)' end call lineout outfile,"This is a square of size:" call lineout outfile,'"'size'"' "("size "elements by "size" elements)" call lineout outfile,' ' do row = 1 to size by 1 line = "" do column = 1 to size by 1 line = line||right(magic.row.column,length) end call lineout outfile,line end /**************************************************************** * Test the square to see if it is truly magic * * Set value to MAGIC and change to SORRY if there is a failure * ****************************************************************/ test_right = "MAGIC" test_left = "MAGIC" test_row. = "MAGIC" test_column. = "MAGIC" test_match. = "MAGIC" /****************** * test diagonals * ******************/ say "Testing the Diagonals" right_diagonal = 0 left_diagonal = 0 do count = 1 to size by 1 row = count right_column = count left_column = size - count + 1 right_diagonal = right_diagonal + magic.row.right_column left_diagonal = left_diagonal + magic.row.left_column end /***************************** * test the rows and columns * *****************************/ say "Testing the rows" rowsum. = 0 do row = 1 to size by 1 if row//50 = 0 then say "Testing row " row do column = 1 to size by 1 rowsum.row = rowsum.row + magic.row.column end end say "Testing the columns" columnsum. = 0 do column = 1 to size by 1 if column//50 = 0 then say "Testing column " column do row = 1 to size by 1 columnsum.column = columnsum.column + magic.row.column end end /************************* * test for duplications * *************************/ say "Testing for Duplicates" do row = 1 to size by 1 if row//50 = 0 then say "Testing for duplicates in row " row do column = 1 to size by 1 index = magic.row.column match.index = index end end /******************************************* * verify that the square passes all tests * *******************************************/ if right_diagonal ^= constant then do right_test = 'RIGHT DIAGONAL 'right_diagonal' DID NOT MATCH THE CONSTANT 'constant call lineout outfile,'Right Diagonal 'right_diagonal' did not match the constant 'constant magic_test = "SORRY" end if left_diagonal ^= constant then do left_test = 'LEFT DIAGONAL 'left_diagonal' DID NOT MATCH THE CONSTANT 'constant call lineout outfile,'Left Diagonal 'left_diagonal' did not match the constant 'constant magic_test = "SORRY" end do count = 1 to size if rowsum.count ^= constant then do test_row.count = 'ROW 'count' DOES NOT MATCH THE CONSTANT 'constant call lineout outfile,'Row 'count' does not match the constant 'constant magic_test = "SORRY" end if columnsum.count ^= constant then do test_column.count = 'COLUMN 'count' DOES NOT MATCH THE CONSTANT 'constant call lineout outfile,'Column 'count' does not match the constant 'constant magic_test = "SORRY" end end do count = 1 to size*size if match.count ^= count then do test_match.count = 'ELEMENT 'count' IS NOT FOUND IN THE ARRAY, ARRAY IS NOT MAGIC' call lineout outfile,'Element 'count' is not found in the array, the array is not magic' magic_test = "SORRY" end end if magic_test = "SORRY" then do call lineout outfile,'Sorry, this is not a magic square, please check the results' end else do call lineout outfile," " call lineout outfile,"The right diagonal adds up to "right_diagonal call lineout outfile,"The left diagonal adds up to "left_diagonal call lineout outfile,"Each row adds up to "constant call lineout outfile,"Each column adds up to "constant call lineout outfile,"Each number is found only once in the square" call lineout outfile,"This array is therefor a magic square" 'start netscape.exe 'outfile /* alternate to netscape * 'start C:\Progra~1\Intern~1\iexplore.exe 'outfile */ end exit: exit error: say 'THE VALUE ENTERED IS NOT A POSITIVE INTEGER GREATER THAN 2' 'pause' exit