I'm building Minesweeper following this video: https://www.youtube.com/watch?v=JwcyxuKko_M
But I need a MVC pattern for this and I'm stuck with a NullPointer here in the line with the stream in View:
public class View{
Model model;
Field f; //here's where I'm trying to access the List created in Field class
Stage primarystage;
Text number = new Text();
public View(Model model, Stage primaryStage){
this.model = model;
this.primarystage = primaryStage;
Pane root = new Pane();
root.setPrefSize(model.WIDTH, model.HEIGHT);
//iterate through rows and columns to fill board with random bombs
for (int y = 0; y < model.Y_FIELDS; y++) {
for (int x = 0; x < model.X_FIELDS; x++) {
Field field = new Field(x, y, Math.random() < 0.2);
model.array[x][y] = field;
root.getChildren().add(field);
}
}
for (int y = 0; y < model.Y_FIELDS; y++) {
for (int x = 0; x < model.X_FIELDS; x++) {
Field field = model.array[x][y];
//trying to access the method getSurrounding from class Field with f
long bombs = f.getSurrounding(field).stream().filter(b -> b.isBomb).count(); //number of bombs
if (bombs > 0)
field.bomb.setText(String.valueOf(bombs));
}
}
Scene scene = new Scene(root, model.getWidth(), model.getHeight());
getStage().setScene(scene);
}
Here's the Field class:
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import java.util.ArrayList;
import java.util.List;
public class Field extends StackPane{
//information for each field: x coordinate, y coordinate, is it a bomb or not
int x;
int y;
boolean isBomb; //determines whether or not the field is a bomb
int bombsNum = 0; //counts how many bombs are surrounding the field
Model model;
Rectangle board = new Rectangle(model.FIELD_SIZE - 2, model.FIELD_SIZE - 2);
Text bomb = new Text();
public Field(int x, int y, boolean isBomb){
this.x = x;
this.y = y;
this.isBomb = isBomb;
board.setFill(Color.LAVENDER);
board.setStroke(Color.BLACK);
bomb.setText(isBomb ? "X" : "");
getChildren().addAll(board,bomb);
setTranslateX(x * model.FIELD_SIZE);
setTranslateY(y * model.FIELD_SIZE);
}
public List<Field> getSurrounding(Field field){
//looks at all the fields surrounding the current field
List<Field> surrounding = new ArrayList<>();
int[] coordinates = new int[]{
-1,-1, //top left field
-1, 0, //left field
-1, 1, //bottom left field
0,-1, //top middle field
0, 1, //bottom middle field
1,-1, //top right field
1, 0, //right field
1, 1 //bottom right field
};
for (int i = 0; i < coordinates.length; i++) {
int columnX = coordinates[i]; //considers the x coordinate of a surrounding field
int rowY = coordinates[++i]; //considers the y coordinate of a surrounding field
int newX = field.x + columnX; //sets the x coordinate of a surrounding field
int newY = field.y + rowY; //sets the y coordinate of a surrounding field
if (newX >= 0 && newX < model.X_FIELDS //make sure it's not out of bounds
&& newY >= 0 && newY < model.Y_FIELDS) {
surrounding.add(model.array[newX][newY]);
}
}
return surrounding;
}
}
And the Variables in Model:
public static final int FIELD_SIZE = 40;
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
//sets number of fields in x and y axis
public static final int X_FIELDS = WIDTH / FIELD_SIZE;
public static final int Y_FIELDS = HEIGHT / FIELD_SIZE;
public Field[][] array = new Field[X_FIELDS][Y_FIELDS];
Why isn't it working? I'm trying to pass a list (containing all neighbors of a field) into the stream and filter for the ones containing bombs and then counts those bombs so I can display that number on the current field so the player will know. Why doesn't it recognize the list I passed? Or is it the filter that's confused? Thank you.
Exception in Application start method
Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
at View.<init>(View.java:46)
at Main.start(Main.java:10)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
I think you have a design problem in getSurrounding(). At the moment you require to pass a Field as an argument but as far as I understand this parameter is non-sense. When you want to grab the surroundings of a Field you don't need to provide another Field ! Just use the current one (this) !
public List<Field> getSurrounding() {
//looks at all the fields surrounding the current field
List<Field> surrounding = new ArrayList<>();
int[] coordinates = new int[]{
-1,-1, //top left field
-1, 0, //left field
-1, 1, //bottom left field
0,-1, //top middle field
0, 1, //bottom middle field
1,-1, //top right field
1, 0, //right field
1, 1 //bottom right field
};
for (int i = 0; i < coordinates.length; i++) {
int columnX = coordinates[i]; //considers the x coordinate of a surrounding field
int rowY = coordinates[++i]; //considers the y coordinate of a surrounding field
int newX = this.x + columnX; //sets the x coordinate of a surrounding field
int newY = this.y + rowY; //sets the y coordinate of a surrounding field
if (newX >= 0 && newX < model.X_FIELDS
&& newY >= 0 && newY < model.Y_FIELDS) {
surrounding.add(model.array[newX][newY]);
}
}
return surrounding;
}
Thefore the line that causes you problem at the moment can be changed to:
long bombs = field.getSurrounding().stream().filter(b -> b.isBomb).count();
It means you can get rid of the view attribute Field f because it is now unused. That was the reason why it was null because it had no purpose, therefore was never initialized.
Related
I am creating a 3D plot in JavaFX using the same technique discussed in this answer, with a MeshView and a PhongMaterial to provide the colours. However, only the top side of the material is coloured, and if the user rotates the camera to view from below, it's impossible to determine the shape of the plot because it is all black.
My questions:
Is there any way to set the material of the reverse side of the mesh?
If not, is there a good approach to "faking" it? I would imagine creating a new mesh upside-down in exactly the same position would cause rendering issues; is the best approach to do that but apply a very small offset so that the two meshes are not exactly on top of each other?
Edit: I have included some example code below, which is cut down from my real code but contains enough to illustrate the problem. By default it displays the top of the mesh, which is coloured red in this example. If you change the line that reads new Rotate(-30, Rotate.X_AXIS) so that the angle becomes +30 rather than -30, it will rotate the camera to show the underside of the mesh, which you will see appears black.
package test;
import javafx.application.Application;
import javafx.scene.DepthTest;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TestApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
TestPlot tp = new TestPlot();
tp.setPrefSize(600, 400);
Scene scene = new Scene(tp);
stage.setScene(scene);
stage.show();
}
class TestPlot extends Region {
private final PerspectiveCamera camera = new PerspectiveCamera(true);
private double[][] data = new double[500][500];
private final StackPane root = new StackPane();
private final SubScene subscene;
public TestPlot() {
subscene = new SubScene(root, 1, 1, true, SceneAntialiasing.BALANCED);
subscene.setCamera(camera);
getChildren().add(subscene);
widthProperty().addListener((obs, oldVal, newVal) -> refreshPlot());
heightProperty().addListener((obs, oldVal, newVal) -> refreshPlot());
refreshPlot();
}
private void refreshPlot() {
// Set the subscene bounds to match the plot bounds, in case the plot was
// resized
subscene.setHeight(this.getHeight());
subscene.setWidth(this.getWidth());
// Clear any existing stuff
root.getChildren().clear();
root.setStyle("-fx-background-color: rgba(0, 0, 0, 0);");
root.getChildren().add(camera);
camera.getTransforms().clear();
int xDataPoints = data.length;
int zDataPoints = data[0].length;
// Create data mesh
TriangleMesh mesh = new TriangleMesh();
for (int x = 0; x < xDataPoints; x++) {
for (int z = 0; z < zDataPoints; z++) {
// Invert the data as JavaFX meshes are positive-down, whereas we expect
// the plot to be positive-up
mesh.getPoints().addAll(x, (float) (-data[x][z]), z);
}
}
// Create faces from data mesh
for (int x = 0; x < xDataPoints - 1; x++) {
for (int z = 0; z < zDataPoints - 1; z++) {
int tl = x * zDataPoints + z; // top-left
int bl = x * zDataPoints + z + 1; // bottom-left
int tr = (x + 1) * zDataPoints + z; // top-right
int br = (x + 1) * zDataPoints + z + 1; // bottom-right
int offset = (x * (zDataPoints - 1) + z) * 8 / 2; // div 2 because we have u AND v in the list
// working
mesh.getFaces().addAll(bl, offset + 1, tl, offset + 0, tr, offset + 2);
mesh.getFaces().addAll(tr, offset + 2, br, offset + 3, bl, offset + 1);
}
}
// Create data mesh texture map
for (float x = 0; x < xDataPoints - 1; x++) {
for (float z = 0; z < zDataPoints - 1; z++) {
float x0 = x / xDataPoints;
float z0 = z / zDataPoints;
float x1 = (x + 1) / xDataPoints;
float z1 = (z + 1) / zDataPoints;
mesh.getTexCoords().addAll( //
x0, z0, // 0, top-left
x0, z1, // 1, bottom-left
x1, z1, // 2, top-right
x1, z1 // 3, bottom-right
);
}
}
// Create texture material
Image diffuseMap = createTexture(data);
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(diffuseMap);
// Create & add mesh view
MeshView meshView = new MeshView(mesh);
meshView.setTranslateZ(-zDataPoints);
meshView.setMaterial(material);
meshView.setCullFace(CullFace.NONE);
meshView.setDrawMode(DrawMode.FILL);
meshView.setDepthTest(DepthTest.ENABLE);
root.getChildren().addAll(meshView);
double biggestAxisSize = xDataPoints;
double z = -(0.5 * biggestAxisSize) / Math.tan(0.5 * Math.toRadians(camera.getFieldOfView()));
camera.getTransforms().addAll(
new Translate(0, 0, -zDataPoints / 3.0),
new Rotate(-30, Rotate.X_AXIS),
new Translate(0, 0.5, z)
);
camera.setFarClip(biggestAxisSize * 200.0);
}
private Image createTexture(double[][] data) {
int width = data.length;
int height = data[0].length;
WritableImage wr = new WritableImage(width, height);
PixelWriter pw = wr.getPixelWriter();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
pw.setColor(x, y, Color.RED);
}
}
return wr;
}
}
}
Modify the cullFace property of your MeshView:
meshView.setCullFace(CullFace.NONE);
Also you need to add ambient light to the scene. The normals of the surface are automatiacally determined and the scalar product used with won't be positive, if the normal is facing away from the light source...
root.getChildren().add(new AmbientLight(Color.WHITE));
can someone help me?
I made a grid for my board. When I'm trying to get Point position of the square in the board, console returns only (0,0).
This is my point code:
public struct Point
{
public int X {get; set;}
public int Y {get; set;}
public Point(int x, int y){
this.X = x;
this.Y = y;
}
}
This is script where every square get a point in the grid when instantiated:
public Point GridPosition { get; set; }
public void Setup(Point gridPos, Vector3 worldPos)
{
this.GridPosition = gridPos;
transform.position = worldPos;
}
private void OnMouseDown(){
Debug.Log (GridPosition.X + ", "+ GridPosition.Y );
}
And this is my main script with Dictionary part:
public static Dictionary<Point,Grid> Tiles { get; set; }
void Start()
{
CreateLevel ();
}
void CreateLevel()
{
Tiles = new Dictionary<Point,Grid> ();
}
private void PlaceTilesColliders(Vector3 tileStart, float tileOffset){
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
TileCollider.GetComponent<Grid> ().Setup (new Point (x, y), new Vector3 (tileStart.x + (tileOffset * x), tileStart.y - (tileOffset * y), 0));
Tiles.Add (new Point (x, y), Instantiate(TileCollider).GetComponent<Grid>());
}
}
}
So, console return every time (0,0), don't matter which square was clicked.
Can someone explain me how to get true point position of the square in the grid?
Try Instantiate first, then configure the resulting new Grid and add to the dictionary.
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
GameObject newGrid = Instantiate(TileCollider);
newGrid.GetComponent<Grid>().Setup(new Point (x, y), new Vector3 (tileStart.x + (tileOffset * x), tileStart.y - (tileOffset * y), 0));
Tiles.Add(new Point (x, y), newGrid.GetComponent<Grid>());
}
}
I would recommend, though, that you pay attention to parenting, as right now the instantiated objects have no parent.
avariant is essentially correct with their answer, but I'd like to point out what your code is actually doing and why you're getting the values you're getting.
Lets look at this loop:
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
TileCollider.GetComponent<Grid> ().Setup (new Point (x, y), new Vector3 (tileStart.x + (tileOffset * x), tileStart.y - (tileOffset * y), 0));
Tiles.Add (new Point (x, y), Instantiate(TileCollider).GetComponent<Grid>());
}
}
We loop over Y and X (this is fine) and then we call TileCollider.GetComponent<Grid> (). Wait, hold on, TileCollider? What is this? This can't be one of our files in the scene, we haven't used our X and Y coordinates to go fetch a GameObject from the scene to get this reference...
That means anything we do to it has no effect on our tiles in the game world! And because the reference isn't updated, we continuously update it's values to the new X and Y positions, overwriting what we'd already done and having no effect on anything.
Oops.
And this is why avariant says that you need to call Instantiate and create a new tile, then get the component from that GameObject and call Setup() on it.
Im having trouble finding a null pointer exception in some lejos code, which is for the EV3 lego robot.
Below is the class state and constructor:
public class Mapper {
private LineMap CurrentMap;
private Line[] lines;
private boolean[] userDrawn;
private Rectangle boundary = new Rectangle(0, 0, 594, 891);
private int counter;
/**
* Initializes an empty map with just a boundary
*
* #author Ben
*/
public Mapper(){
counter = 0;
lines = new Line[counter];
userDrawn = new boolean[counter];
CurrentMap = new LineMap(lines,boundary);
}
And the function causing me grief
public void addLine(float x1, float y1, float x2, float y2, boolean isUserDrawn){
counter++;
Line[] oldLines = lines;
boolean[] oldUserDrawn = userDrawn;
lines = new Line[counter];
userDrawn = new boolean[counter];
for(int i = 0; i < counter - 1; i++){
lines[i] = oldLines[i];
userDrawn[i] = oldUserDrawn[i];
}
lines[counter-1] = new Line(x1,y1,x2,y2);
if(isUserDrawn == true){
userDrawn[counter - 1] = true;
}
else{
userDrawn[counter - 1] = false;
}
CurrentMap = new LineMap(lines,boundary);
}
Any ideas for what might be a source of a null pointer exception:
Dont worry, problem not in this code. And has been solved.
I have created wafermap chart. I want to make chips(die) in wafer selectable on mouse click and insert labels as well lines from one chip to another. Anyone expert in jfree chart?
wafermap chart
Here are the basic pieces for a tooltip listener for wafer maps, which is a form of selecting the die. Add the following to WaferMapPlot:
public String findChipAtPoint(double x, double y, Rectangle2D plotArea){
double[] xValues = this.getChipXValues(plotArea, dataset.getMaxChipX()
+ 2, dataset.getChipSpace());
double startX = xValues[1];
double chipWidth = xValues[0];
int ychips = this.dataset.getMaxChipY()+ 2;
double[] yValues = this.getChipYValues(plotArea, ychips,
dataset.getChipSpace());
double startY = yValues[1];
double chipHeight = yValues[0];
double chipSpace = dataset.getChipSpace();
int chipX = (int)Math.floor((x - startX + chipWidth + chipSpace) /
(chipWidth + chipSpace));
int chipY = (int)Math.floor((y - startY + chipHeight + chipSpace) /
(chipHeight + chipSpace));
chipX = chipX - dataset.getXOffset() - 1;
chipY = ychips - chipY - dataset.getYOffset() - 1;
StringBuilder sb = new StringBuilder("(");
Number value = dataset.getChipValue(chipX, chipY);
if (value instanceof Double)
value = value.intValue();
sb.append(chipX).append(",").append(chipY).append(") ").append(
(value == null) ? "" : value.toString());
return sb.toString();
}
Then make a subclass of ChartPanel that will be the listener:
public class WaferMapChartPanel extends ChartPanel {
WaferMapPlot waferPlot = null;
WaferMapDataset dataSet = null;
public WaferMapChartPanel(JFreeChart chart){
super(chart);
waferPlot = (WaferMapPlot)chart.getPlot();
if (waferPlot != null)
dataSet = waferPlot.getDataset();
}
/**
* Returns a string for the tooltip.
* #param e the mouse event.
* #return A tool tip or <code>null</code> if no tooltip is available.
*/
#Override
public String getToolTipText(MouseEvent e) {
if (waferPlot != null){
Object source = e.getSource();
if (source instanceof WaferMapChartPanel){
WaferMapChartPanel chartSource= (WaferMapChartPanel)e.getSource();
Rectangle2D plotArea = chartSource.getChartRenderingInfo().getPlotInfo().getPlotArea();
Insets insets = this.getInsets();
double x = (e.getX() - insets.left) / this.getScaleX();
double y = (e.getY() - insets.top) / this.getScaleY();
return waferPlot.findChipAtPoint(x, y, plotArea);
}
}
return "";
}
}
This creates a tooltip of the die's x,y and bin or whatever value you are using instead of bin.
I have a CustomPieView which is made of several pie slices. I have to draw something in the middle of every pie section.
//Inside Activity's onCreate Method
public void onCreate(Bundle savedInstanceState){
int size = 400;
int bgColor = 0xffa11b1;
ViewPieChart piechart = (ViewPieChart) findViewById(R.id.pieChartView);
piechart.setCallBack(this);
piechart.setLayoutParams(new LayoutParams(size, size));
piechart.setGeometry(size, size, 2, 2, 2, 2, 2130837504);
piechart.setSkinparams(bgColor);
piechart.setData(piedata, maxCount);
piechart.invalidate();
}
//CustomPieView extends View
public void setGeometry(int width, int height, int gapleft, int gapright, int gaptop, int gapbottom, int overlayid) {
mWidth = width;
mHeight = height;
mGapleft = gapleft;
mGapright = gapright;
mGapBottm = gapbottom;
mGapTop = gaptop;
}
protected void onDraw(Canvas canvas){
canvas.drawColor(Color.DKGRAY);
mBagpaints.setAntiAlias(true);
mBagpaints.setStyle(Paint.Style.FILL);
mBagpaints.setColor(0x88FF0000);
mBagpaints.setStrokeWidth(0.0f);
mLinePaints.setAntiAlias(true);
mLinePaints.setColor(Color.WHITE);
mLinePaints.setStrokeWidth(3.0f);
mLinePaints.setStyle(Paint.Style.STROKE);
sLinePaint.setAntiAlias(true);
sLinePaint.setColor(Color.BLACK);
sLinePaint.setStrokeWidth(3.0f);
sLinePaint.setStyle(Paint.Style.STROKE);
RectF mOvals = new RectF(mGapleft, mGapTop, mWidth - mGapright, mHeight
- mGapBottm);
mStart = START_INC;
PieDetailsItem item;
for (int i = 0; i < mdataArray.size(); i++) {
item = (PieDetailsItem) mdataArray.get(i);
mBagpaints.setColor(item.color);
mSweep = (float) 360* ((float) item.count / (float) mMaxConnection);
canvas.drawArc(mOvals, mStart, mSweep, true, mBagpaints);
canvas.drawArc(mOvals, mStart, mSweep, true, mLinePaints);
// The function below is setting the global vars
cSegX, cSegY to the center of the segment. Not Working!!
calculateMidPointOfPieSegment(mSweep);
canvas.drawPoint(cSegX, cSegY, sLinePaint);
mStart = mStart + mSweep;
}
mState = IS_DRAW;
callBack.onDrawFinished(null);
}
private float calculateRadius(){
float width = mWidth/2;
float height = mHeight/2;
if(width < height){
return width;
}else{
return height;
}
}
private void calculateMidPointOfPieSegment(float sweepAngle){
cSegX = (float)((calculateRadius()/2)*Math.cos(Math.toRadians(sweepAngle/2))+(mWidth/2));
cSegY = (float)((calculateRadius()/2)*Math.sin(Math.toRadians(sweepAngle/2))+(mHeight/2));
}
Just see the dots in the image below, it has to be in the center of every arc.
Screenshot
Ref.
Complete Source
Let me know if this clears it up. This is the basic logic you want to implement.
//rimPoint = (rX, rY)
//centerPoint = (cX,cY)
//theta is the angle of the the midpoint measured anticlockwise from the x axis,
//or the average of the two angles making up the slice(measured anticlockwise from
//the x axis)
private void calculateMidPointOfPieSegment(cX,cY, theta)
{
float rX = cX + calculateRadius()*(cos(theta))
float rY = cY + calculateRadius()*(sin(theta))
cSegX = (rX+cX)/2;
cCegY = (rY+cY)/2;
}
If theta isn't in radians, sub it out for theta*Math.PI/180