import java.awt.*; import java.applet.*; import java.util.*; public class MagicSquare extends Applet implements Runnable { /************************************************************************/ /* */ /* Class MagicSquare: Magic Square Finder */ /* */ /* Copyright (C) 1996 by Yasusi Kanada */ /* */ /* This program can be freely distributed under the GNU General */ /* Public License. */ /* */ /* Initial Version = "ver 1.00, 1996-8-13"; */ String Version = "ver 1.05, 1996-11-23"; /* */ /************************************************************************/ /*======================================================================*/ /* Class global objects and methods */ /*======================================================================*/ protected int n = 4; /* default: 4 x 4 square */ private Thread solver; private int min(int x, int y) { return x <= y ? x : y; } /*======================================================================*/ /* Widget handler */ /*======================================================================*/ private TextField message_box; /* public inside the class */ private TextField n_box; /* to specify n (#queens) */ private TextField initial_frustration_box; private TextField frustration_factor_box; private void init_widgets() { setLayout(new FlowLayout()); add(new Label("Magic square solver using CCM (Chemical Casting Model) -- " + Version + " --")); add(new Button("Stop")); add(new Button("Restart")); Panel major_option_panel = new Panel(); major_option_panel.add(new Label("Major options: N =")); n_box = new TextField("3 "); major_option_panel.add(n_box); init_speed_choice(major_option_panel); init_rule_choice(major_option_panel); add(major_option_panel); Panel minor_option_panel = new Panel(); minor_option_panel.add (new Label("Minor options: Initial frustration =")); initial_frustration_box = new TextField("1e-15 "); minor_option_panel.add(initial_frustration_box); minor_option_panel.add(new Label("Frustration factor =")); frustration_factor_box = new TextField("2.0 "); minor_option_panel.add(frustration_factor_box); add(minor_option_panel); message_box = new TextField(80); add(message_box); } public boolean action(Event e, Object arg) { message_box.setText(""); if (speed_action(arg) || rule_action(arg)) { return true; } else if ("Restart".equals(arg)) { stop(); start(); return true; } else if ("Stop".equals(arg)) { stop(); return true; } else { return false; }; } /* Choice extension: */ private Choice new_choice(String[] labels, int default_index, Panel p) { Choice c = new Choice(); for (int i = 0; i < labels.length; i++) { c.addItem(labels[i]); }; c.select(default_index); p.add(c); return c; } private int find_index(Object arg, String[] labels) { for (int i = 0; i < labels.length; i++) { if (labels[i].equals(arg)) { return i; }; }; return -1; /* not found */ } /* Speed control: */ final int[] Sleep_time = {333, 50, 0}; /* in mili seconds */ final int Medium_speed_index = 1; final int Fast_speed_index = 2; final int Full_speed_index = 3; final int Speed_default_index = Medium_speed_index; final String[] Speed_choice_labels = {"Slow speed (3 rps)", "Medium speed (20 rps)", "Fast speed", "Full speed"}; private int speed_selected_index; private Choice speed_choice; private void init_speed_choice(Panel p) { /* Speed choice is created and initialized. */ speed_choice = new_choice(Speed_choice_labels, Speed_default_index, p); speed_selected_index = Speed_default_index; } private boolean speed_action(Object arg) { /* Event handler for speed control choice. */ /* Warning: This function is called not only when the event is */ /* caused by the speed control choice. */ int index = find_index(arg, Speed_choice_labels); if (index >= 0) { /* found */ speed_selected_index = index; display_switch = index == Fast_speed_index || index == Full_speed_index ? false /* no display while executing */ : true; return true; } else { /* not found */ return false; }; } /* Rule choice: */ final int Swap_index = 0; final int Rotate_index = 1; final int[] Rules = {Swap_index, Rotate_index}; final int Rule_default_index = Swap_index; final String[] Rule_choice_labels = {"Swapping rule", "Rotation rule"}; private int rule_selected_index; private Choice rule_choice; private void init_rule_choice(Panel p) { /* Rule choice is created and initialized. */ rule_choice = new_choice(Rule_choice_labels, Rule_default_index, p); rule_selected_index = Rule_default_index; } private boolean rule_action(Object arg) { int index = find_index(arg, Rule_choice_labels); if (index >= 0) { /* found */ rule_selected_index = index; return true; } else { return false; }; } /*======================================================================*/ /* Magic square viewer */ /*======================================================================*/ protected boolean display_switch = true; /* To display the map or not */ final int Common_offset = 8; protected int x_offset = 0; protected int y_offset = 170; protected int drawn_n; protected int queen_x_offset, queen_y_offset; int drawing_size, drawing_step, queen_size; int font_size; private void init_view() { } private void start_view() { drawing_step = (min(size().width - x_offset, size().height - y_offset) - 2 * Common_offset) / n; if (drawing_step <= 0) { message_box.setText("Applet size too small! "); drawing_step = 0; }; drawing_size = drawing_step * n; font_size = drawing_step * 3 / 5; } public void paint(Graphics g) { int x0 = x_offset + Common_offset; int y0 = y_offset + Common_offset; for (int i = 0; i <= n; i++) { int grid_coord = i * drawing_step; g.drawLine(x0, y0 + grid_coord, x0 + drawing_size - 1, y0 + grid_coord); g.drawLine(x0 + grid_coord, y0, x0 + grid_coord, y0 + drawing_size - 1); }; x0 = x0 + drawing_step / 10; y0 = y0 + drawing_step / 10; Font old_font = g.getFont(); g.setFont(new Font("TimesRoman", Font.PLAIN, font_size)); for (int i = 0; i < wm_size; i++) { g.drawString ("" + value_wm[i], x0 + x_coord_wm[i] * drawing_step, y0 + y_coord_wm[i] * drawing_step + drawing_step * 2 / 3); }; g.setFont(old_font); } /*======================================================================*/ /* Solver */ /*======================================================================*/ double initial_frustration = 1.0e-15; double frustration_factor = 2.0; protected int expected_summation; /* Working memory: */ private int[] value_wm, x_coord_wm, y_coord_wm; private double[] frustration_wm; private int[][][] summation_wm; private int[] n_summations; private int wm_size; private int n_tests; private int n_reactions; private int n_failed_tests; /* Order degree: */ private int order(int index, int value_difference) { int sum = 0; for (int i = 0; i < n_summations[index]; i++) { int diff = expected_summation - (summation_wm[index][i][0] + value_difference); sum -= diff * diff; } return sum; } private double mean_order() { /* Returns the mean order degree of the current WM. */ int total_order = 0; for (int i = 0; i < wm_size; i++) { total_order += order(i, 0); }; return total_order / (double)wm_size; } private void init_solver() { } private double get_option(TextField option, String format_error_message, double minimum_value, String value_error_message, double default_value) { double value; try { value = (new Double(option.getText())).doubleValue(); /* value = Integer.parseInt(option.getText()); */ } catch (NumberFormatException e) { message_box.setText(format_error_message); value = default_value; }; option.setText("" + value); if (value < minimum_value) { message_box.setText(value_error_message); value = default_value; }; return value; } /* Random color/index generators */ private Random selector = new Random(); private int random(int max) { return (selector.nextInt() & 0x7FFFFFFF) % max; } private void update_summations(int index, int difference) { for (int i = 0; i < n_summations[index]; i++) { summation_wm[index][i][0] += difference; }; } /* Rules */ private void swap_rule() { /* A rule to swap two columns of the magic square */ int index1, index2; n_tests++; /* Select columns to apply the rule: */ index1 = random(wm_size); do { index2 = random(wm_size); } while (index2 == index1); /* Compute LODs: */ int old_order = order(index1, 0) + order(index2, 0); int new_order = order(index1, value_wm[index2] - value_wm[index1]) + order(index2, value_wm[index1] - value_wm[index2]); if (old_order >= 0) { /* A local termination condition holds */ n_failed_tests++; } else if (((double)old_order - frustration_wm[index1] - frustration_wm[index2]) > new_order) { /* not to be reacted */ n_failed_tests++; frustration_wm[index1] *= frustration_factor; frustration_wm[index2] *= frustration_factor; } else { n_reactions++; n_failed_tests = 0; int v1 = value_wm[index1]; int v2 = value_wm[index2]; update_summations(index1, v2 - v1); update_summations(index2, v1 - v2); value_wm[index1] = v2; value_wm[index2] = v1; if (display_switch) { repaint(); try {solver.sleep(Sleep_time[speed_selected_index]);} catch (InterruptedException e) {}; }; frustration_wm[index1] = initial_frustration; frustration_wm[index2] = initial_frustration; }; } private void rotate_rule() { /* A rule to rotate three columns of the magic square */ int index1, index2, index3; n_tests++; /* Select columns to apply the rule: */ index1 = random(wm_size); do { index2 = random(wm_size); } while (index2 == index1); do { index3 = random(wm_size); } while (index3 == index1 || index3 == index2); /* Compute LODs: */ int old_order = order(index1, 0) + order(index2, 0) + order(index3, 0); int new_order = order(index1, value_wm[index2] - value_wm[index1]) + order(index2, value_wm[index3] - value_wm[index2]) + order(index3, value_wm[index1] - value_wm[index3]); if (old_order >= 0) { /* A local termination condition holds */ n_failed_tests++; } else if (((double)old_order - frustration_wm[index1] - frustration_wm[index2] - frustration_wm[index3]) > new_order) { /* not to be reacted */ n_failed_tests++; frustration_wm[index1] *= frustration_factor; frustration_wm[index2] *= frustration_factor; frustration_wm[index3] *= frustration_factor; } else { n_reactions++; n_failed_tests = 0; int v1 = value_wm[index1]; int v2 = value_wm[index2]; int v3 = value_wm[index3]; update_summations(index1, v2 - v1); update_summations(index2, v3 - v2); update_summations(index3, v1 - v3); value_wm[index1] = v2; value_wm[index2] = v3; value_wm[index3] = v1; if (display_switch) { repaint(); try {solver.sleep(Sleep_time[speed_selected_index]);} catch (InterruptedException e) {}; }; frustration_wm[index1] = initial_frustration; frustration_wm[index2] = initial_frustration; frustration_wm[index3] = initial_frustration; }; } /* Solver main part: */ public void run() { long initial_time; int max_tests = 1000; int max_tests_20 = 20 * max_tests; n = (int)get_option(n_box, "Format error -- n ", 3, "The value of N must be 3 or larger! ", 3); initial_frustration = get_option(initial_frustration_box, "Format error -- initial_frustration ", 0, "The value of initial frustration must be positive! ", 1e-15); frustration_factor = get_option(frustration_factor_box, "Format error -- frustration_factor ", 0, "The value of initial frustration must be positive! ", 2.0); init_wm(n); start_view(); repaint(); try {solver.sleep(50);} catch (InterruptedException e) {}; message_box.setText("Running..."); initial_time = System.currentTimeMillis(); n_tests = 0; n_reactions = 0; n_failed_tests = 0; while (true) { switch (rule_selected_index) { case Swap_index: swap_rule(); break; case Rotate_index: rotate_rule(); break; }; if (n_failed_tests >= max_tests) { /* termination condition */ if (n_tests < max_tests_20) { break; }; max_tests_20 = 2 * max_tests_20; max_tests = max_tests_20 / 20; }; if (speed_selected_index != Full_speed_index && n_tests % 10000 == 0) { message_box.setText ("Running... #tests = " + n_tests + " #reactions = " + n_reactions + " MOD = " + mean_order() + " Time = " + (System.currentTimeMillis() - initial_time) / 1000.0); repaint(); try {solver.sleep(50);} catch (InterruptedException e) {}; }; }; double order = mean_order(); message_box.setText ("Stoppped. #tests = " + n_tests + " #reactions = " + n_reactions + " MOD = " + order + (order < 0 ? " *** Failed! ***" : "") + " Time = " + (System.currentTimeMillis() - initial_time) / 1000.0); repaint(); }; /*======================================================================*/ /* Working memory (Initial queen layout) generation */ /*======================================================================*/ /* protected int summations[]; */ private void add2summation(int[] summation, int index) { summation[0] += value_wm[index]; summation_wm[index][n_summations[index]++] = summation; } private void init_wm(int n) { wm_size = n * n; value_wm = new int[wm_size]; x_coord_wm = new int[wm_size]; y_coord_wm = new int[wm_size]; frustration_wm = new double[wm_size]; summation_wm = new int[wm_size][][]; n_summations = new int[wm_size]; expected_summation = (n * (n * n + 1)) / 2; for (int i = 0; i < wm_size; i++) { x_coord_wm[i] = i % n; y_coord_wm[i] = i / n; value_wm[i] = i + 1; frustration_wm[i] = initial_frustration; }; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { n_summations[n * i + j]++; n_summations[n * j + i]++; }; n_summations[n * i + i]++; n_summations[n * i + n - i - 1]++; }; for (int i = 0; i < wm_size; i++) { summation_wm[i] = new int[n_summations[i]][]; n_summations[i] = 0; }; int[] up_diagonal_summation = new int[1]; int[] down_diagonal_summation = new int[1]; for (int i = 0; i < n; i++) { int[] row_summation = new int[1]; int[] column_summation = new int[1]; for (int j = 0; j < n; j++) { add2summation(row_summation, n * i + j); add2summation(column_summation, n * j + i); }; add2summation(up_diagonal_summation, n * i + i); add2summation(down_diagonal_summation, n * i + n - i - 1); }; } /*======================================================================*/ /* Dispatchers */ /*======================================================================*/ public void init() { init_widgets(); init_solver(); init_view(); } public void start() { solver = new Thread(this); solver.start(); } public void stop() { solver.stop(); } }