/* Guesser.java (C)2001 by Harald Bögeholz (hwb@heise.de) */ import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.Random; public class Guesser extends Applet implements ActionListener, ItemListener { private int history_length; private int history_mask; private int counter_bits; private int counter_sign; private int counter_max; private int counter_min; private int[] table; private int history; int previous_history; private int guessed_total; private int guessed_right; private Random random; private Button resetButton; private Checkbox detailsCheckbox; private Button zeroButton; private Button oneButton; private Button randomButton; private Choice hlengthChoice; private Choice cbitsChoice; private CanvasDetails detailsCanvas; private Panel topbar; public void init() { random = new Random(); resetButton = new Button("reset"); resetButton.addActionListener(this); detailsCheckbox = new Checkbox("show details"); detailsCheckbox.addItemListener(this); zeroButton = new Button("0"); zeroButton.addActionListener(this); oneButton = new Button("1"); oneButton.addActionListener(this); randomButton = new Button("1000 random bits"); randomButton.addActionListener(this); hlengthChoice = new Choice(); for (int i=1; i<= 9; ++i) hlengthChoice.add(i + "-bit history"); hlengthChoice.select(4); // default: 5 history bits hlengthChoice.addItemListener(this); cbitsChoice = new Choice(); for (int i=1; i <= 8; ++i) cbitsChoice.add(i + "-bit counters"); cbitsChoice.select(1); // default: 2-bit counters cbitsChoice.addItemListener(this); topbar = new Panel(); topbar.setLayout( new FlowLayout(FlowLayout.LEFT)); topbar.add(resetButton); topbar.add(zeroButton); topbar.add(oneButton); topbar.add(randomButton); topbar.add(hlengthChoice); topbar.add(cbitsChoice); topbar.add(detailsCheckbox); detailsCanvas = new CanvasDetails(); detailsCanvas.setBackground(Color.white); detailsCanvas.addKeyListener( new KeyAdapter() { public void keyTyped(KeyEvent e) { if (e.getKeyChar() == '0') process_guess(false); else if (e.getKeyChar() == '1') process_guess(true); } } ); setLayout( new BorderLayout() ); add(topbar, BorderLayout.NORTH); add(detailsCanvas, BorderLayout.CENTER); reset_table(); } /* init() */ private void reset_table() { history_length = hlengthChoice.getSelectedIndex() + 1; counter_bits = cbitsChoice.getSelectedIndex() + 1; history_mask = (1 << history_length) - 1; counter_sign = 1 << (counter_bits-1); counter_max = counter_sign - 1; counter_min = -counter_sign; table = new int[1 << history_length]; for (int i=0; i < (1<= 1000000) { // avoid numbers getting too large guessed_total = 1; guessed_right = 0; } if (guess() == g) ++guessed_right; if (g) { if (table[history] != counter_min) --table[history]; } else { if (table[history] != counter_max) ++table[history]; } previous_history = history; history = (history << 1) + (g?1:0) & history_mask; detailsCanvas.repaint(); } /* process_guess() */ public void actionPerformed(ActionEvent e) { if (e.getSource() == zeroButton) process_guess(false); else if (e.getSource() == oneButton) process_guess(true); else if (e.getSource() == resetButton) { reset_table(); detailsCanvas.repaint(); } else if (e.getSource() == randomButton) { for (int i=0; i<1000; ++i) process_guess((random.nextInt() & 1) != 0); } } /* actionPerformed() */ public void itemStateChanged(ItemEvent e) { if (e.getSource() == hlengthChoice || e.getSource() == cbitsChoice) reset_table(); detailsCanvas.repaint(); } class CanvasDetails extends Canvas { Color colorCurrent = new Color(220, 255, 220); Color colorPrevious = new Color(255, 220, 220); Color colorBoth = new Color(185, 255, 185); public void paint(Graphics g) { if (detailsCheckbox.getState()) paint_details(g); // show internal state else { // show only hit rate in percent if (guessed_total > 0) { Dimension d = this.getSize(); Font font = this.getFont(); // keep the compiler happy FontMetrics fm = getFontMetrics(font); // keep compiler happy double scale = 1.0; int pointsize = 200; while (pointsize > 4) { font = new Font("SansSerif", Font.BOLD, pointsize); fm = getFontMetrics(font); if (d.width * 1.0 / fm.stringWidth("100%") < scale) scale = d.width * 1.0 / fm.stringWidth("100%"); else break; pointsize = (int)(pointsize * scale); } String text = (guessed_right*1000/guessed_total+5) / 10 + "%"; g.setColor(Color.black); g.setFont(font); g.drawString(text, (d.width - fm.stringWidth(text))/2, fm.getHeight() - fm.getMaxDescent() + (d.height-fm.getHeight())/2); } } } /* paint() */ public void paint_details(Graphics g) { Dimension d = this.getSize(); int table_lines = 1 << ((history_length+1)/2); int table_columns = 1 << (history_length / 2); final int spacer = 5; // can't be static in an inner class :-( Font font = this.getFont(); // keep the compiler happy FontMetrics fm = getFontMetrics(font); // keep the compiler happy double scale = 1.0; int pointsize; int firstline_offset; String dummy_index = ""; for (int i=0; i 0) topline += " (" + (guessed_right*1000/guessed_total + 5) / 10+ "%)"; String dummy_topline = "History: " + dummy_index + ", next guess = 0, statistics: 000000 of 000000 " + "guessed correctly (100%)"; // use a 100pt dummy font to calculate font metrics; hopefully, it // will scale proportionally to a smaller point size pointsize = 100; // top line must fit in the window while (pointsize > 4) { scale = 1.0; font = new Font("SansSerif", Font.BOLD, pointsize); fm = getFontMetrics(font); if (d.width * 1.0 / fm.stringWidth(dummy_topline) < scale) { scale = d.width * 1.0 / fm.stringWidth(dummy_topline); pointsize = (int)(pointsize * scale); } else break; } g.setColor(Color.black); g.setFont(font); g.drawString(topline, 0, fm.getHeight() - fm.getMaxDescent()); firstline_offset = fm.getHeight() + spacer; pointsize = 100; while (pointsize > 4) { scale = 1.0; font = new Font("SansSerif", Font.PLAIN, pointsize); fm = getFontMetrics(font); // table and top line must fit vertically if ((d.height-firstline_offset)*1.0 / (fm.getHeight()*table_lines) < scale) scale = (d.height-firstline_offset)*1.0 / (fm.getHeight()*table_lines); // columns must fit horizontally if (d.width*1.0 / (fm.stringWidth(dummy_entry)*table_columns) < scale) scale = d.width*1.0 / (fm.stringWidth(dummy_entry)*table_columns); pointsize = (int)(pointsize * scale); if (scale >= 1) break; } g.setFont(font); int counter_width = fm.stringWidth(""+counter_min); int line_height = fm.getHeight(); int column_width = fm.stringWidth(dummy_entry); int descent = fm.getMaxDescent(); int subcolumn_offset = fm.stringWidth(dummy_index); for (int y=0; y