/** Let's draw an abacus. */ import java.awt.event.MouseListener; import java.awt.event.MouseEvent; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.geom.Ellipse2D; import javax.swing.JPanel; import java.awt.Color; public class AbacusPanel extends JPanel { //private Rectangle box; private Rectangle bar; private Rectangle [] rod; private Ellipse2D [][] bead; // can't put ".Double" here private String value; // can initialize in constructor private static final int NUM_COLUMNS = 14; private static final int BEAD_WIDTH = 30; private static final int BOX_X = 100; private static final int BOX_Y = 100; private static final int BOX_HEIGHT = 30; private static final int BOX_WIDTH = 20; private static final int PANEL_HEIGHT = BEAD_WIDTH * (3+1+8); private static final int PANEL_WIDTH = BEAD_WIDTH * 2 * NUM_COLUMNS; // constructor - create a panel at some default location public AbacusPanel() { // inner class is mouse listener class DetectInput implements MouseListener { // When the user clicks somewhere on the abacus, this is a // signal that we need to change a digit value, unless the // click shouldn't register because it's not on a legal pixel. // x will tell us which digit to look at. // y will tell us what the new desired digit value will be. // Note that we can't change a 1 and 5 at the same time. public void mousePressed(MouseEvent e) { int x = e.getX(); int y = e.getY(); //box.setLocation(x, y); value = resetValue(x, y); repaint(); } public void mouseReleased(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} } setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT)); //box = new Rectangle(BOX_X, BOX_Y, BOX_WIDTH, BOX_HEIGHT); DetectInput listener = new DetectInput(); addMouseListener (listener); // We need 13 * 12 beads. In practice many will be invisible. // i is the rod number (column) ! value = ""; for (int i = 0; i < NUM_COLUMNS; ++i) value += "0"; bead = new Ellipse2D[NUM_COLUMNS][12]; for (int i = 0; i < NUM_COLUMNS; ++i) for (int j = 0; j < 12; ++j) { bead[i][j] = new Ellipse2D.Double(BEAD_WIDTH/2 + 2*BEAD_WIDTH*i, 0 + BEAD_WIDTH*j, BEAD_WIDTH, BEAD_WIDTH); } // We need 13 rods. rod = new Rectangle[NUM_COLUMNS]; for (int i = 0; i < NUM_COLUMNS; ++i) { // Draw rod[i]. The x values should be ~ 25, 75, 125, etc. // Let's make them 5 pixels across. rod[i] = new Rectangle(BEAD_WIDTH - 2 + 2*BEAD_WIDTH*i, 0, 4, PANEL_HEIGHT); } // Put the bar between pixel rows ~ 120 and 150. bar = new Rectangle(0, 3*BEAD_WIDTH, PANEL_WIDTH, BEAD_WIDTH); } public String resetValue(int x, int y) { // First see if (x, y) is actually inside the square // defining a bead. // The value of x will tell us which rod or digit place to look at. // The value of x needs to be within 15 of the center of // 0-60, 60-120, 120-180, etc., assuming width is 30. if (x % (2 * BEAD_WIDTH) < BEAD_WIDTH/2 || x % (2 * BEAD_WIDTH) > 3*BEAD_WIDTH/2) { System.out.printf("pixel x (%d) not on a bead position.\n", x); return value; } // Determine rod (digit place) number, 0 for far left, 12 far right. int i = x / (2 * BEAD_WIDTH); int j = y / BEAD_WIDTH; // Only the following j values are ever legal: // 1, 2, 4-10. if (j <= 0 || j == 3 || j >= 11) { System.out.printf("Bead j number (%d) not movable.\n", j); return value; } // It's only legal to shift beads up or down to change // a value. The top section is easy, because all we do // is add or subtract 5. // At this point, we know j = 1,2,4-10. int digitValue = Integer.parseInt("" + value.charAt(i)); // If we just clicked on a j=1 bead, we're trying to add 5. if (j == 1 && digitValue <= 4) { digitValue += 5; value = value.substring(0,i) + "" + digitValue + value.substring(i+1); System.out.printf("New value is %s\n", value); return value; } else if (j == 1) { System.out.printf("Digit value (%d) too big to add 5 to.\n", digitValue); return value; } // If we clicked on a j=2 bead, we're trying to subtract 5. if (j == 2 && digitValue >= 5) { digitValue -= 5; value = value.substring(0,i) + "" + digitValue + value.substring(i+1); System.out.printf("New value is %s\n", value); return value; } else if (j == 2) { System.out.printf("Digit value (%d) too low to subtract 5 from.\n", digitValue); return value; } // Need to handle cases of j=4 thru 10. // When does each value make sense? // j=4 makes sense if digit value is 1,2,3,4 or 6,7,8,9. // We are trying to subtract 1 to reduce digit to 0 or 5. // j=5 makes sense if digit value is 2,3,4, 7,8,9 // we are trying to reduce value to 1 or 6. // j=6 makes sense if digit value is 3,4, 8,9 // we are trying to reduce value to 2 or 7. // j=7 makes sense if digit value is 4, 9 // we are trying to reduce value to 3 or 8. // j=7 also makes sense if digit value is 0, 5 // we are trying to raise digit value to 1 or 6. // j=8 makes sense if digit value is 0,1, 5,6 // we are trying to raise digit value to 2 or 7. // j=9 makes sense if digit value is 0,1,2, 5,6,7 // we are trying to raise digit value to 3 or 8. // j=10 makes sense if digit value is 0,1,2,3, 5,6,7,8 // we are trying to raise digit value to 4 or 9. if (j == 4 && digitValue >= 1 && digitValue <= 4) digitValue = 0; else if (j == 4 && digitValue >= 6 && digitValue <= 9) digitValue = 5; else if (j == 5 && digitValue >= 2 && digitValue <= 4) digitValue = 1; else if (j == 5 && digitValue >= 7 && digitValue <= 9) digitValue = 6; else if (j == 6 && digitValue >= 3 && digitValue <= 4) digitValue = 2; else if (j == 6 && digitValue >= 8 && digitValue <= 9) digitValue = 7; else if (j == 7 && digitValue == 4) digitValue = 3; else if (j == 7 && digitValue == 9) digitValue = 8; else if (j == 7 && digitValue == 0) digitValue = 1; else if (j == 7 && digitValue == 5) digitValue = 6; else if (j == 8 && digitValue >= 0 && digitValue <= 1) digitValue = 2; else if (j == 8 && digitValue >= 5 && digitValue <= 6) digitValue = 7; else if (j == 9 && digitValue >= 0 && digitValue <= 2) digitValue = 3; else if (j == 9 && digitValue >= 5 && digitValue <= 7) digitValue = 8; else if (j == 10 && digitValue >= 0 && digitValue <= 3) digitValue = 4; else if (j == 10 && digitValue >= 5 && digitValue <= 8) digitValue = 9; else { System.out.printf("Unable to move bead in 1's section. j=%d, digitValue=%d\n", j, digitValue); return value; } // Normal case of being able to move beads in 1's area. value = value.substring(0,i) + "" + digitValue + value.substring(i+1); System.out.printf("New value is %s\n", value); return value; // If we get this far, do nothing. Should have sanity check? //return value; } // The purpose of paintComponent() is to "paint" the geometric figures that // we have in our panel. It's analogous to paint() in an applet. // We need to call the generic JPanel paintComponent() first so that the // panel starts out clean. public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; //g2.draw(box); // Draw beads first, so rods can be drawn on top of them. g2.setColor(Color.RED); for (int i = 0; i < NUM_COLUMNS; ++i) for (int j = 0; j < 12; ++j) { // Only draw the visible beads! if (isVisible(i, j)) { g2.fill(bead[i][j]); g2.draw(bead[i][j]); } } // Draw vertical rods. g2.setColor(Color.GREEN); for (int i = 0; i < NUM_COLUMNS; ++i) { g2.fill(rod[i]); g2.draw(rod[i]); } // Draw the horizontal bar separting fives from ones. g2.setColor(Color.BLUE); g2.fill(bar); g2.draw(bar); } // Should we draw this bead? // The value of i is simply which decimal place value we're on. // The value of j is very important. // j values of 0,1,2 refer to the position of the 5's // j value of 3 is always invisible // j values of 4-11 are for the 1's. // For each value of the digit value, which beads should be visible? // Let's suppose we wanted to display, in part, 0123456789. // In the following figure, "Y" means yes. Blank is no. // 0 1 2 3 4 5 6 7 8 9 // bead 0 Y Y Y Y Y Y Y Y Y Y // bead 1 Y Y Y Y Y // bead 2 Y Y Y Y Y // bead 3 --------------------------------------- // bead 4 Y Y Y Y Y Y Y Y // bead 5 Y Y Y Y Y Y // bead 6 Y Y Y Y // bead 7 Y Y Y Y // bead 8 Y Y Y Y // bead 9 Y Y Y Y Y Y // bead 10 Y Y Y Y Y Y Y Y // bead 11 Y Y Y Y Y Y Y Y Y Y public boolean isVisible(int i, int j) { int digitValue = Integer.parseInt("" + value.charAt(i)); switch (digitValue) { case 0: switch(j) { case 0: case 1: case 7: case 8: case 9: case 10: case 11: return true; default: return false; } case 1: switch(j) { case 0: case 1: case 4: case 8: case 9: case 10: case 11: return true; default: return false; } case 2: switch(j) { case 0: case 1: case 4: case 5: case 9: case 10: case 11: return true; default: return false; } case 3: switch(j) { case 0: case 1: case 4: case 5: case 6: case 10: case 11: return true; default: return false; } case 4: switch(j) { case 0: case 1: case 4: case 5: case 6: case 7: case 11: return true; default: return false; } case 5: switch(j) { case 0: case 2: case 7: case 8: case 9: case 10: case 11: return true; default: return false; } case 6: switch(j) { case 0: case 2: case 4: case 8: case 9: case 10: case 11: return true; default: return false; } case 7: switch(j) { case 0: case 2: case 4: case 5: case 9: case 10: case 11: return true; default: return false; } case 8: switch(j) { case 0: case 2: case 4: case 5: case 6: case 10: case 11: return true; default: return false; } case 9: switch(j) { case 0: case 2: case 4: case 5: case 6: case 7: case 11: return true; default: return false; } } // if for some reason we get this far return false; } }