Я пишу программу, которая аппроксимирует значение π, используя метод Монте-Карло.
Мой план состоит в том, чтобы визуально отобразить количество X точек и рассчитать результат на основе этого графика.
Дизайн пока такой:
Быстрое предупреждение: я новичок в Java — пожалуйста, простите за ужасную настройку кода или глупые вопросы.
При запуске программы в зависимости от скорости ввода произойдет одно из двух:
а.) Для медленных или средних скоростей две точки наносятся еще до запуска таймера и первая «реальная» точка наносится на карту.
б.) Для высоких или очень высоких скоростей первая точка наносится дважды.
По какой-то причине это может произойти и со вторым пунктом вместо первого.
Я в недоумении, почему это происходит. Вот некоторые вещи, которые я пробовал:
public class Main {
public static void main(String[] args) {
SimSetup simSetup = new SimSetup();
simSetup.simSetup();
SimFrame frame = new SimFrame();
SimDrawing d = new SimDrawing();
frame.add(d);
frame.setVisible(true);
}
}
public class SimSetup {
private final UserInput userInput = new UserInput();
public void simSetup() {
title.displayTitle();
boolean startupSuccess = false;
while (!startupSuccess) {
userInput.setPoints();
userInput.setPlotSpeed();
if ((userInput.setVerification(userInput.getPoints(),
userInput.getPlotSpeed()))) {
break;
}
}
SimDrawing.setPoints(userInput.getPoints());
SimDrawing.setSpeed(userInput.getPlotSpeed());
}
}
import java.util.InputMismatchException;
import java.util.Objects;
import java.util.Scanner;
public class UserInput {
private int numPoints;
private short plotSpeed;
private String verification;
public int getPoints() {
return numPoints;
}
public short getPlotSpeed() {
return plotSpeed;
}
public String getVerification() {
return verification;
}
public int setPoints() {
System.out.print("Enter the number of points to be plotted using a positive integer between 1 and 100,000: ");
try {
Scanner scanner = new Scanner(System.in);
numPoints = scanner.nextInt();
if (numPoints < 0) {
System.out.println("\nNumber of points must be a positive integer. Please try again.\n");
setPoints();
} else if (numPoints == 0) {
System.out.println("\nCan't generate a plot using zero points! Please try again.\n");
setPoints();
} else if (numPoints > 100000) {
System.out.println("\nNumber of points is too large. Please try again.\n");
setPoints();
}
} catch (InputMismatchException e) {
System.out.println("\nThe number of points must be an integer between 1 and 100,000. Please try again.\n");
setPoints();
}
return numPoints;
}
public short setPlotSpeed() {
System.out.print("\nSelect a plotting animation speed from the choices below:");
System.out.println("\n0 -- Slow\n1 -- Medium\n2 -- Fast\n3 -- Very Fast\n");
try {
Scanner scanner = new Scanner(System.in);
short speedChoice = scanner.nextShort();
if (speedChoice == 0) {
plotSpeed = 100;
} else if (speedChoice == 1) {
plotSpeed = 50;
} else if (speedChoice == 2) {
plotSpeed = 10;
} else if (speedChoice == 3) {
plotSpeed = 1;
}
} catch (InputMismatchException e) {
System.out.println("Speed must be an integer between 0 to 3. Please try again.");
}
return plotSpeed;
}
public boolean setVerification(long numPoints, short plotSpeed) {
if (numPoints > 50000 && (plotSpeed == 100 || plotSpeed == 50)) {
System.out.println("This combination of points and animation speed will result in long completion time.");
System.out.println("Are you sure? (Y to continue, N to re-enter choices.)");
try {
Scanner scanner = new Scanner(System.in);
verification = scanner.nextLine();
verification = verification.toLowerCase();
if (Objects.equals(verification, "n")) {
System.out.println("\nProcess aborted.\n");
return false;
} else if (!Objects.equals(verification, "n") && !Objects.equals(verification, "y")) {
System.out.println("\nVerification input not recognized. Please try again.");
setVerification(numPoints, plotSpeed);
return false;
}
} catch (Exception e) {
System.out.println("Exception: " + e);
}
}
return true;
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SimDrawing extends JPanel implements ActionListener {
int count = 0;
int max = 525;
int min = 75;
int circleRadius = 450;
int x = min + (int) (Math.random() * (max - min + 1));
int y = min + (int) (Math.random() * (max - min + 1));
private static int points;
private static short speed;
private int circlePoints;
private int outsidePoints;
Timer t = new Timer(speed, this);
public static void setPoints(int numPoints) {
points = numPoints;
}
public static void setSpeed(short plotSpeed) {
speed = plotSpeed;
}
public SimDrawing() {
t.start();
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("\nCount: " + count);
x = min + (int) (Math.random() * (max - min + 1));
y = min + (int) (Math.random() * (max - min + 1));
System.out.println("\nPoint generated: " + "(" + x + "," + y + ")");
repaint();
if (count == points) {
t.stop();
pointCheck();
}
count++;
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
double distanceFromCenter = (Math.sqrt(Math.pow((x - 75), 2) + Math.pow((y - 525), 2)));
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(2));
g2d.drawLine(74, 74, 526, 74);
g2d.drawLine(74, 74, 74, 526);
g2d.drawLine(74, 526, 526, 526);
g2d.drawLine(526, 74, 526, 526);
g2d.setStroke(new BasicStroke(3));
if (distanceFromCenter <= circleRadius) {
g2d.setColor(Color.BLACK);
g2d.drawLine(x, y, x, y);
System.out.println("Plotting inside point: " + "(" + x + "," + y + ")");
circlePoints++;
}
if (distanceFromCenter > circleRadius) {
g2d.setColor(Color.RED);
g2d.drawLine(x, y, x, y);
System.out.println("Plotting outside point: " + "(" + x + "," + y + ")");
outsidePoints++;
}
}
public void pointCheck() {
System.out.println("\nThe number of points inside the circle is: " + circlePoints);
System.out.println("The number of points outside the circle is: " + outsidePoints);
int totalMappedPoints = circlePoints + outsidePoints;
System.out.println("The total number of points is: " + totalMappedPoints);
}
}
import javax.swing.*;
import java.awt.*;
public class SimFrame extends JFrame {
public SimFrame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setAlwaysOnTop(true);
setTitle("Monte Carlo Method of Approximating π");
setSize(616, 639);
setResizable(false);
setLayout(new GridLayout());
setLocationRelativeTo(null);
}
}
И вам следует 1. вызвать метод рисования супервизора (который будет super.paintComponent(g);, если переопределить PaintComponent), 2., вероятно, лучше всего рисовать на BufferedImage, а затем отображать указанное изображение в графическом интерфейсе. Либо это, либо рисование путем перебора точек. 3. Я бы не смешивал консольный ввод-вывод с графическим вводом-выводом. Поскольку это программа с графическим интерфейсом, я бы получал все входные данные и показывал весь вывод через графический интерфейс.
Метод PaintComponent() (не Paint()) должен только рисовать текущее состояние компонента, а не изменять его. Утверждения: circlePoints++ и outsidePoints++; не должны быть в методе рисования. Для анимации вам следует использовать таймер поворота. Когда таймер сработает, вы обновите состояние этих переменных. Поэтому я предполагаю, что каждый раз, когда срабатывает таймер, вы добавляете новую точку в ArrayList. Затем метод рисования нарисует все точки в ArrayList.
Вам стоит прочитать руководство Выполнение индивидуальной живописи.




Я думаю, вы, возможно, упускаете из виду, что делает Component#paint(),
Когда JFrame обновляет экран, он очищает весь экран, и после этого все закрашивается.
Это означает, что весь ваш контент должен быть добавлен сразу
Надеюсь, это объясняет, что я имею в виду
Это означает, что в коде вам понадобится переменная в SimDrawing с именем
/**Vector2 is a base java class, i think, if not find one online*/
List<Vector2> plottedPoints = new ArrayList<>();
Затем нарисуйте эти точки в функции рисования.
for (Vector2 point : plottedPoints) {
g2d.setColor(getColorOfPoint(point));
g2d.drawOval(point.x-5, point.y-5, 10, 10);
}
Где-то еще в классе:
public static Color getColorOfPoint(Vector2 point) {
return SimDrawing.isInsideCircle(point) ? Color.Red : Color.Blue;
}
И дополнительно в методе SimDrawing#actionPerformed(ActionEvent)
points.add(new Vector2(x, y));
//Also add to the points in or outside
if (isInsideCircle(point))
pointsInside++;
else
pointsOutside++;
Где-то еще в том же классе:
public static Color isInsideCircle(Vector2 point) {
..The code you used earlier
}
У вас есть куча проблем с вашим кодом, большая часть которых описана в комментариях, но основные из них:
Лично я бы рассмотрел несколько изменений, в том числе:
Например:
import javax.swing.*;
public class MonteCarloMain {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Random Points");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MonteCarloView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
import java.awt.Point;
public record MonteCarloPoint(Point point, boolean inside) {
}
public enum PlotSpeed {
// let's use a log scale for speeds
SLOW(300), MEDIUM(102), FAST(35), VERY_FAST(12);
private final int speed;
PlotSpeed(int speed) {
this.speed = speed;
}
public int getSpeed() {
return speed;
}
}
import java.awt.Point;
import java.beans.PropertyChangeListener;
import javax.swing.Timer;
import javax.swing.event.SwingPropertyChangeSupport;
public class MonteCarloModel {
public static final String POINT = "point";
public static final String FINISHED = "finished";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private int pointsInsideCircle = 0;
private int pointsOutsideCircle = 0;
private int totalPoints = 0;
private Timer timer;
private int pointsToPlot;
private PlotSpeed plotSpeed;
private MonteCarloPoint[] points;
private int size = 0;
private int circleRadius = 0;
public MonteCarloModel(int points, PlotSpeed medium, int size, int circleRadius) {
pointsToPlot = points;
plotSpeed = medium;
this.size = size;
this.circleRadius = circleRadius;
}
// add prop change listeners
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(propertyName, listener);
}
public void startPlotting() {
points = new MonteCarloPoint[pointsToPlot];
pointsInsideCircle = 0;
pointsOutsideCircle = 0;
totalPoints = 0;
timer = new Timer(plotSpeed.getSpeed(), e -> {
if (totalPoints < pointsToPlot) {
int x = (int) (Math.random() * size);
int y = (int) (Math.random() * size);
Point newPoint = new Point(x, y);
boolean inside = isInsideCircle(x, y);
MonteCarloPoint newMyPoint = new MonteCarloPoint(newPoint, inside);
points[totalPoints] = newMyPoint;
if (inside) {
pointsInsideCircle++;
} else {
pointsOutsideCircle++;
}
pcSupport.firePropertyChange(POINT, null, newMyPoint);
totalPoints++;
} else {
timer.stop();
pcSupport.firePropertyChange(FINISHED, false, true);
}
});
timer.start();
}
public void stopPlotting() {
if (timer != null) {
timer.stop();
pcSupport.firePropertyChange(FINISHED, false, true);
}
}
private boolean isInsideCircle(int x, int y) {
int dx = x - size / 2;
int dy = y - size / 2;
return dx * dx + dy * dy <= circleRadius * circleRadius;
}
public int getPointsInsideCircle() {
return pointsInsideCircle;
}
public int getPointsOutsideCircle() {
return pointsOutsideCircle;
}
public int getTotalPoints() {
return totalPoints;
}
public int getPointsToPlot() {
return pointsToPlot;
}
public MonteCarloPoint[] getPoints() {
return points;
}
}
import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import javax.swing.*;
public class MonteCarloView extends JPanel {
public static final int MAX_POINTS = 100000;
public static final int PADDING = 20;
private static final int SIZE = 600;
private static final int CIRCLE_RADIUS = 250;
private PointPanel pointPanel = new PointPanel(CIRCLE_RADIUS, SIZE);
private JSpinner pointSpinner = new JSpinner(new SpinnerNumberModel(200, 1, MAX_POINTS, 1));
private JComboBox<PlotSpeed> speedCombo = new JComboBox<>(PlotSpeed.values());
private JButton startButton = new JButton("Start");
private JButton stopButton = new JButton("Stop");
private DefaultListModel<String> listModel = new DefaultListModel<>();
private JList<String> pointList = new JList<>(listModel);
private JProgressBar progressBar = new JProgressBar();
private MonteCarloModel pointModel = null;
public MonteCarloView() {
JPanel controlPanel = new JPanel();
controlPanel.add(new JLabel("Number of points: "));
controlPanel.add(pointSpinner);
controlPanel.add(new JLabel("Speed: "));
speedCombo.setSelectedItem(PlotSpeed.FAST);
controlPanel.add(speedCombo);
controlPanel.add(startButton);
controlPanel.add(stopButton);
startButton.setMnemonic(KeyEvent.VK_S);
stopButton.setMnemonic(KeyEvent.VK_T);
startButton.addActionListener(e -> startPlotting());
stopButton.addActionListener(e -> stopPlotting());
pointList.setVisibleRowCount(10);
pointList.setPrototypeCellValue("Point 123456: Outside");
pointList.setLayoutOrientation(JList.VERTICAL);
JScrollPane pointListScrollPane = new JScrollPane(pointList);
JPanel pointListWrapperPanel = new JPanel(new BorderLayout());
pointListWrapperPanel.add(pointListScrollPane, BorderLayout.CENTER);
pointListWrapperPanel.setBorder(BorderFactory.createTitledBorder("Points"));
progressBar.setStringPainted(true);
progressBar.setString(String.format("%d%%", 0));
JPanel progressPanel = new JPanel(new BorderLayout());
progressPanel.add(progressBar, BorderLayout.CENTER);
progressPanel.setBorder(BorderFactory.createTitledBorder("Progress"));
setBorder(BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING));
setLayout(new BorderLayout(PADDING, PADDING));
add(pointPanel, BorderLayout.CENTER);
add(controlPanel, BorderLayout.PAGE_END);
add(pointListWrapperPanel, BorderLayout.LINE_END);
add(progressPanel, BorderLayout.PAGE_START);
}
private void startPlotting() {
if (pointModel != null) {
pointModel.stopPlotting();
}
pointModel = new MonteCarloModel((int) pointSpinner.getValue(), (PlotSpeed) speedCombo.getSelectedItem(), SIZE, CIRCLE_RADIUS);
pointModel.addPropertyChangeListener(MonteCarloModel.POINT, evt -> {
MonteCarloPoint newPoint = (MonteCarloPoint) evt.getNewValue();
listModel.addElement(String.format("Point %d: %s", pointModel.getTotalPoints(), newPoint.inside() ? "Inside" : "Outside"));
pointPanel.setModel(pointModel);
int value = (pointModel.getTotalPoints() + 1) * 100 / pointModel.getPointsToPlot();
value = Math.min(100, value);
progressBar.setValue(value);
progressBar.setString(String.format("%d%%", value));
});
pointModel.addPropertyChangeListener(MonteCarloModel.FINISHED, evt -> {
startButton.setEnabled(true);
pointSpinner.setEnabled(true);
speedCombo.setEnabled(true);
});
startButton.setEnabled(false);
pointSpinner.setEnabled(false);
speedCombo.setEnabled(false);
listModel.clear();
progressBar.setValue(0);
pointModel.startPlotting();
}
private void stopPlotting() {
if (pointModel != null) {
pointModel.stopPlotting();
}
}
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.*;
public class PointPanel extends JPanel {
private static final int DIAMETER = 5;
private MonteCarloModel pointModel;
private int circleRadius = 0;
private int size = 0;
public PointPanel(int circleRadius, int size) {
setBackground(Color.WHITE);
setBorder(BorderFactory.createLineBorder(Color.BLACK));
this.circleRadius = circleRadius;
this.size = size;
}
@Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
if (isPreferredSizeSet()) {
return dim;
} else {
int w = Math.max(size, dim.width);
int h = Math.max(size, dim.height);
return new Dimension(w, h);
}
}
public void setModel(MonteCarloModel pointModel) {
// remove old listeners
if (this.pointModel != null) {
this.pointModel.removePropertyChangeListener(MonteCarloModel.POINT, evt -> {
repaint();
});
}
this.pointModel = pointModel;
pointModel.addPropertyChangeListener(MonteCarloModel.POINT, evt -> {
repaint();
});
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (pointModel == null) {
return;
}
g.setColor(Color.BLACK);
// rendering hints for better circle rendering
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawOval(size / 2 - circleRadius, size / 2 - circleRadius, 2 * circleRadius, 2 * circleRadius);
MonteCarloPoint[] points = pointModel.getPoints();
for (int i = 0; i < pointModel.getTotalPoints(); i++) {
g.setColor(points[i].inside() ? Color.BLUE : Color.RED);
g.fillOval(points[i].point().x - DIAMETER / 2, points[i].point().y - DIAMETER / 2, DIAMETER, DIAMETER);
}
}
}
Спасибо, что нашли время подробно все это описать! Ваша версия визуально ошеломляет. Очень признателен за объяснение недостатков моего кода. Однако есть одна вещь. Когда я попытался воссоздать вашу версию локально, она пожаловалась на PointPanel в верхней части класса MonteCarloView. Intellij продолжает спрашивать, является ли это зависимостью Maven, которую нужно импортировать, что не может быть правдой... Я думаю, вы сказали, что это JPanel. Должно ли быть это вместо этого? частная точка JPanel pointPanel = новая JPanel (CIRCLE_RADIUS, SIZE)
@trb7074: извини. Забыл выложить, а сейчас добавил. Не публикуйте это как решение для домашнего задания, поскольку оно, скорее всего, будет помечено.
Ваша панель должна переопределять
paintComponent, а неpaint.