I figured out how to drag to highlight multiple cells in a GridLayout-designed grid (which wasn't too hard) and how to drag a cell from one such grid to another (which involved brute force and math, but it turned out not to be all that hard, either).
But the code looks and feels hacked.
How should I have done it?
Here are code fragments that typify what I did to drag content (one char):
For each cell in txtUser[] grid add mouse listener to identify the cell about to be dragged and also access its content:
txtUser[i].addMouseListener(new java.awt.event.MouseListener()
{
public void mousePressed(MouseEvent e) {
currentUserCell.index = interp(e.getXOnScreen(), ulcUser.x, txtUser[0].getWidth());
if(txtUser[currentUserCell.index].getText().length() > 0)
currentUserCell.content = txtUser[currentUserCell.index].getText().charAt(0);
}
Here's interp(), which converts from absolute screen pixel (x) to (returned) grid element number, given the upper-left corner of the text field array and the width of one element:
static int interp(int x, int ulc, int w){
return (x - ulc)/w;
}
If the user moves the frame, interp() above doesn't work, requiring need to reorient():
void reorient(){
ulcGrid = new Point(cells[ 0][ 0].getLocationOnScreen().x, cells[ 0][ 0].getLocationOnScreen().y);
ulcUser = new Point(txtUser[ 0] .getLocationOnScreen().x, txtUser[ 0] .getLocationOnScreen().y);
}
(I tried to use relative pixel locations, but couldn't make it work. I may revisit this.)
In order to drop the dragged content, the destination had better be inbounds():
boolean inbounds(int r, int c){
return ! (r >= N || c >= N || r < 0 || c < 0);
}
If inbounds, the letter is dropped, as long as destination is empty:
public void mouseReleased(MouseEvent e) {
int x, y;
if(! dragging)
return;
dragging = false;
x = e.getLocationOnScreen().x;
y = e.getLocationOnScreen().y;
int c = Utilities.interp(x, ulcGrid.x);
int r = Utilities.interp(y, ulcGrid.y);
if(! inbounds(r, c))
return;
if(cells[r][c].getText().length() > 0)
return;
cells[r][c].setText("" + currentUserCell.content);
The previous method required a MouseMotionAdapter for each cell of the source array.
And it just seems so hacked. One reason I say this is that I rely on several global variables, such as ulcGrid and ulcUser and currentUserCell and dragging:
private void txtUserMouseDragged(MouseEvent evt)
{
dragging = true;
}
I had a nice learning experience, but I'd rather have more-professional-looking code, most notably with fewer global variables. (I realize that a good start would be to not rely on absolute pixel addresses.)
So I'm asking where to find a better way, specifically how to identify the drag source and destination cells of a one- or two-dimensional array of text fields.
=================
--EDIT--
My program works. My question is about whether there is a library that would make it easier and more reliable than what I've written to drag from the one-dimenional array at the bottom of the screen below onto the large grid.
But now that I've read the comments, maybe this is just another bad question that should be deleted.
Related
I am building a text editor and trying to add the ability to select lines using the line number margin. My current approach is to use mouseDragged to update the selected lines. This works fine when doing slow mouse movements, but when doing faster movements the selection isn't able to keep up and just stops updating.
I have tried using a new thread for the processing of the selected range but it still freezes.
Update: Changed to a mouse range of two values (min/max) rather than every line - this fixed the issue
The mouseDragged method
private void mouseDragged(MouseEvent event, Mouse mouse) {
int eventY = event.getY();
int currentLineNumber = this.getLineNumber(eventY);
mouse.endRange(currentLineNumber);
if(mouse.getRange()[0] != mouse.getRange()[1]) {
this.selectLineRange(mouse);
} else {
this.selectLineForOffset(eventY);
}
}
Mouse state
private class Mouse {
int mouseY = -1;
int[] range = new int[2];
private void resetMouse(boolean resetBeginLine) {
this.mouseY = -1;
this.range = new int[2];
}
void endRange(int lineNumber) {
range[1] = lineNumber;
}
void beginRange(int lineNumber) {
range[0] = lineNumber;
}
int[] getRange() {
return range;
}
boolean validRange() {
return ((range[0] | range[1]) > 0);
}
}
And finally the select line range method
private void selectLineRange(Mouse mouse) {
if (mouse.validRange()) {
int minLine = Math.min(mouse.getRange()[0], mouse.getRange()[1]);
int maxLine = Math.max(mouse.getRange()[0], mouse.getRange()[1]);;
Element root = editor.getDocument().getDefaultRootElement();
int startSelection = root.getElement(minLine).getStartOffset();
int endSelection = root.getElement(maxLine).getEndOffset();
//editor.setCaretPosition(mouse.mouseDirection == Direction.UP ? startSelection : endSelection - 1);
editor.select(startSelection, Math.max(endSelection - 1, 0));
}
}
Having code in the same function that knows both about the concept of a mouse and the concept of a document is a recipe for disaster. Split your code into multiple functions where each function works at a different level of abstraction.
All you need is to know at which Y the mouse went down, and at which Y the mouse currently is. From this, you can at any given moment re-calculate the range of selected lines. First you convert viewport-Y to workspace-Y, then you convert workspace-Y to line-number, and voila, you have each line number.
This: selectedLineNumbers.add(currentLineNumber); presumes that you will receive a mouse event on each line. If you don't, then your list will contain gaps. And you won't, because mouse events come few and far apart when you are moving the mouse too quickly. That's why your selectedLineNumbers should be a range, (startingLineNumber, endingLineNumber) not a list of distinct line numbers.
I'm working on a game in java, based on the Atari game adventure. I got the basic KeyListener part working fine, but then I added another if statement, using another class, to test if if the player was going to hit a wall, and stopping movement if that was the case. The method I used also used if statements, and when I ran the code, it had MAJOR lag. I tried a while loop first, but that made it lag even worse. Anyway to make this not lag so much? It doesn't seem that complex a program to run, and I still have to add yet another if statement to make be able to move into another room, so I have to do something to massively cut down on the lag.
Here is the class:
class Player extends JPanel implements KeyListener{
private char c = 'e';
int x = 400;
int y = 400;
int mapX = 0;
int mapY = 0;
public Player() {
this.setPreferredSize(new Dimension(800, 500));
addKeyListener(this);
}
public void addNotify() {
super.addNotify();
requestFocus();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Environment Layout = new Environment();
Layout.drawRoom(mapX,mapY,g);
g.fillRect(x , y , 20, 20);
}
public void keyPressed(KeyEvent e) { }
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) {
c = e.getKeyChar();
repaint();
Environment Layout = new Environment();
if(Layout.isWall(x,y,c)){}
else{
if (c == 'a'){
x = x - 3;
}
else if (c == 'w'){
y = y - 3;
}
else if (c == 's'){
y = y + 3;
}
else if (c == 'd'){
x = x + 3;
}
}
}
public static void main(String[] s) throws IOException{
JFrame f = new JFrame();
f.getContentPane().add(new Player());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
}
The draw room method I used in this was just to put the background of the room into place.
Here is the isWall method from the Environment class:
public boolean isWall(int moveX, int moveY, char let){
BufferedImage room = null;
try {
room = ImageIO.read(new File(xNum + "," + yNum + ".png"));
}
catch (IOException e) {
}
int[][] walls = convertImage(room);
boolean blocked = false;
if(let == 'w') {
if(walls[moveY-8][moveX] == -3584){blocked = true;}
}
else if(let == 's') {
if(walls[moveY+20][moveX] == -3584){blocked = true;}
}
else if(let == 'a') {
if(walls[moveY][moveX-5] == -3584){blocked = true;}
}
else if(let == 'd') {
if(walls[moveY][moveX+20] == -3584){blocked = true;}
}
return blocked;
}
the convertImage method just converts the image of the room into an int array, for the value of the colors. -3584 is the color of the walls. It's possible this is what's lagging it, but this seemed like the best way for each room to have the walls done automatically.
I also tried a timer, but either I did that wrong, or it just didn't help.
I can give more of my code if that's needed, but help with this would be much appreciated. I'm relatively new to this kind of stuff, so it's likely I'm missing something big. Thanks.
The lag here is almost certainly not from the if statements. Those are really fast. I think the bigger issue is in isWall. Notice that any time you want to check for whether a wall is present, you
Open a file,
read the file contents,
convert the file contents from an image to a grid of pixels, and
read exactly one pixel.
Reading files from disk is extremely slow compared to looking at values in memory. For example, a regular magnetic hard drive works at around 7200 RPM, so the seek time is measured in milliseconds. On the other hand, your processor can do about a billion operations per second, so other operations take nanoseconds. That means that a disk read is roughly a million times slower than other operations, which is almost certainly where you're getting the lag from!
To fix this, consider rewriting your isWall code so that you only read the file and do the conversion once and, having done that, then just look up the part of the image you need. This converts doing tons of (glacially slow) file reads to one single (slow but inevitable) file read followed by tons of fast memory reads.
You appear to be moving your walls further than you are moving your player.
Is it possible that your player object is getting stuck in a wall there by producing "blocked = true" continuously?
Your character gets +- 3 in every direction, however your walls seem inconsistent and range from 8 up to 20 down to 5 left to 20 right.
This is an extension to #templatetypedef's answer.
Instead of loading the image files upon calling the isWall method, you might want to consider caching all of the walls on game start.
So I am thinking;
have a HashMap data structure keyed by <String, Integer>. Where String is your coordinates. E.g. coordinate string = "100,238"
parse all the .png image files in the directories and store the coordinates as key and the value can just be any dummy value like 1 or 2.
Then when isWall() is invoked. Given the X and Y coordinate, build the coordinate string as mentioned in point 1 and check if the key exists. If it does then we know it is a piece of wall else not.
This should drastically reduce the I/O disk contention.
In future, if you would like to extend the solution to incorporate APIs like isTreasureChest() or isMonster(). It can be extended by building a immutable class call "Room" or "Tile" to represent the object. Then modify the HashMap to take in <String, Room>.
I'm currently trying to create a program using processing that draws lines from all mouseclicks to new mouseclicks.
As a side it also should save the clicks in a two-dimensional array.
public void setup() {
size(500,500);
}
int clickcount = 0;
int storedclicks = 10000;
int[] mouseclickX = new int [storedclicks];
int[] mouseclickY = new int [storedclicks];
public void draw() {
background(80);
drawing();
}
public void mousePressed() {
if(clickcount >= storedclicks) {
clickcount = 0;
}
mouseclickX[clickcount] = mouseX;
mouseclickY[clickcount] = mouseY;
clickcount++;
}
public void drawing() {
beginShape(LINES);
for (int i = 0; i < storedclicks; i++) {
vertex(mouseclickX[i], mouseclickY[i]);
}
endShape();
}
}
Something works with the code I have now, but something doesn't add up for me. As it is now, on first click I get a line from the upper left corner, next click that line disappears and I get a new line from the ending point of that line and the first line disappears.
Next click a new line from the corner to clicking point comes (line nr. 2 still present). And then it just continues.
I figured if I changed the storeclicks to a number like 5, it doesn't do the from corner thing, just a new line from every previous click position.
It sounds a little confusing, so here's a picture to help (after 3 clicks):
A few notes:
I'd use a lifo ring buffer (you could simulate it with a linked list) or similar to store the clicks, that way you don't have to check storedclicks separately and it should make drawing easier since the head of the buffer moves when you remove elements from the front.
Additionally I'd only draw lines if there are at least two points in the list/buffer.
Third, to prevent synchronization issues (updating only x or y) I'd use a list/buffer/array of Point objects (make your own or use java.awt.Point) rather than two separate x and y arrays.
As for your drawing code you should loop over the stored points rather than all elements (most of which might be empty), i.e. like this:
When using your code:
if( clickcount > 1 ) {
for (int i = 0; i < clickcount ; i++) {
vertex(mouseclickX[i], mouseclickY[i]);
}
}
When using a list/ring buffer as well as Point objects:
if( list.size() > 1 ) {
for (Point clickpos : list ) {
vertex(clickpos.x, clickpos.y);
}
}
Finally, if processing is similar to OpenGL (I don't know processing) the shape type LINES would draw a line between two vertices, i.e. every uneven vertex is a line start and every even vertex is a line end. What you probably want is something like LINE_STRIP (don't know the name or whether it exists in processing) which makes the renderer draw lines between all vertices (i.e. from 0 to 1, from 1 to 2 etc.)
Edit:
As an explanation for the situation in the image you posted, I assume the clicks are ordered from right to left (indices 0, 1, 2). If that's the case then I'd explain it like this (see above for more info):
The first line is drawn between points 0 and 1, which is ok.
The second line is drawn between points 2 and 3 (see paragraph about the loop as well as LINES resp. line strips), which is not what you want.
The third line (if storedclicks > 5) will be drawn between points 4 and 5, which is a point since both vertices have the coordinates 0/0.
The above bulletpoint is repeated for all following lines (again see the paragraph about the loop).
As you can see there are two problems:
You'd want to draw lines between 0 and 1 and between 1 and 2 which is why I suggested a line strip (if that exists in processing, otherwise you'd have to reuse the last point).
You'd want to stop at index 2, i.e. ignore all indices starting from 3, which is why I mentioned not using storedclicks in the loop condition).
Just to add to the existing great answers, here's a hacky approach that simply takes advantage of not clearing the background:
int lastClickX = -1;
int lastClickY = -1;
void setup(){
size(500,500);
background(80);
}
void draw(){}
void mouseReleased(){
if(lastClickX >= 0 && lastClickY >= 0) line(mouseX,mouseY,lastClickX,lastClickY);
lastClickX = mouseX;
lastClickY = mouseY;
}
You can run this as demo bellow:
var lastClickX = -1;
var lastClickY = -1;
function setup(){
createCanvas(500,500);
background(80);
}
function draw(){}
function mouseReleased(){
if(lastClickX >= 0 && lastClickY >= 0) line(mouseX,mouseY,lastClickX,lastClickY);
lastClickX = mouseX;
lastClickY = mouseY;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.17/p5.min.js"></script>
Bare in mind, although this method doesn't store position, it simply takes advantage of not clearing the screen, which isn't the most flexible of options.
To understand what's happening in your sketch, look to the reference.
This code:
beginShape(LINES);
vertex(30, 20);
vertex(85, 20);
vertex(85, 75);
vertex(30, 75);
endShape();
Generates this image:
(source: processing.org)
So, that's why you're getting gaps where your line segments aren't drawn. Instead of using LINES, you probably want to use the no-args beginShape() function along with the endShape(CLOSE) function:
(source: processing.org)
noFill();
beginShape();
vertex(30, 20);
vertex(85, 20);
vertex(85, 75);
vertex(30, 75);
endShape(CLOSE);
As for why you're getting a point at 0,0: keep in mind that int arrays are initialized to contain all zeroes by default. So when you loop through your entire array, you end up drawing a bunch of points at 0,0. Instead, you probably want a separate variable that keeps track of the total number of clicks (which you stop updating when you reach the maximum number of clicks you want to hold).
As a side it also should save the clicks in a two-dimensional array.
Note that you aren't storing the clicks in a two-dimensional array. You're storing them in two one-dimensional arrays, also known as parallel arrays. This is generally a bad design. Instead, consider using Processing's PVector class along with an ArrayList instead of an array. That way you can keep track of how many valid clicks you have without needing the separate variable I mentioned above.
Putting it all together, it might look something like this:
ArrayList<PVector> clicks = new ArrayList<PVector>();
public void setup() {
size(500, 500);
}
public void draw() {
background(80);
drawing();
}
public void mousePressed() {
clicks.add(new PVector(mouseX, mouseY));
if(clicks.size() > 5){
clicks.remove(0);
}
}
public void drawing() {
noFill();
beginShape();
for(PVector v : clicks){
vertex(v.x, v.y);
}
endShape(CLOSE);
}
draws lines from all mouseclicks to new mouseclicks.
If instead of a closed polygon, you want something like a spider web that connects every point to every other point, then you just have to loop through each click and draw the connecting lines:
public void drawing() {
for (PVector v1 : clicks) {
for (PVector v2 : clicks) {
line(v1.x, v1.y, v2.x, v2.y);
}
}
}
I'm making a game where i need to give my objects collision, but i have many fast small objects and normal collision algorithms (Intersection of shapes and such) do not work, because the position+speed iteration advances the walls and there's never actually an Intersection.
So i've started constructing my own (Maybe it already exists but i didnt see it anywhere) collision algorithm based on saving the last position the object was.
Please see the following image:
The idea is demonstrated in frame 1 and 2 of the image. Basicly by checking if there's a wall between the left side of the last rectangle and the right side of the new rectangle, i never skip zones while i check collision, and there's no risk of skipping a wall (so i thought).
This is the code of the algorithm:
private void bounce(GameElement b, Terrain t)
{
Rectangle tR = t.getRectangle();
int tRleft = tR.x;
int tRright = tR.x+tR.width;
int tRup = tR.y;
int tRdown = tR.y+tR.height;
Rectangle bRnow = b.getRectangle();
int bRnowLeft = bRnow.x;
int bRnowRight = bRnow.x+bRnow.width;
int bRnowUp = bRnow.y;
int bRnowDown = bRnow.y+bRnow.height;
Rectangle bRlast = b.getRectangleLast();
int bRlastLeft = bRlast.x;
int bRlastRight = bRlast.x+bRlast.width;
int bRlastUp = bRlast.y;
int bRlastDown = bRlast.y+bRlast.height;
boolean leftRight = false, rightLeft=false, upDown=false, downUp=false;
boolean betweenX = false, betweenY = false;
if(bRnow.x>bRlast.x)leftRight=true;
if(bRnow.x<bRlast.x)rightLeft=true;
if(bRnow.y>bRlast.y)upDown=true;
if(bRnow.y<bRlast.y)downUp=true;
if(bRlastRight>tRleft && bRlastLeft<tRright) betweenX = true;
if(bRlastDown>tRup && bRlastUp<tRdown) betweenY=true;
if(leftRight)
if((tRleft>bRnowLeft || tRleft>bRlastLeft) && tRleft<bRnowRight && betweenY)
{
b.setX(tR.x-bRnow.width - 1);
}
if(rightLeft)
if((tRright<bRnowRight || tRright<bRlastRight) && tRright>bRnowLeft && betweenY)
{
b.setX(tR.x+tR.width + 1);
}
if(upDown)
if((tRup>bRnowUp || tRup>bRlastUp) && tRup<bRnowDown && betweenX)
{
b.setY(tR.y-bRnow.height - 1);
}
if(downUp)
if((tRdown<bRnowDown || tRdown<bRlastDown) && tRdown>bRnowUp && betweenX)
{
b.setY(tR.y+tR.height + 1);
}
}
Its called bounce because its not really organized atm, i still have to think how to structure the algorithm so it becomes more generalized and pratical (Would appreciate help on that too)
This way of doing collision has one bug at the moment which is seen in image 3 (sorry for drawing circles, they are supposed to be squares) because FAST objects still pass diagonals :/ On the other hand, direct hits on walls are pretty neat.
How could i improve, optimize and organize this algorithm? Or is there any better algorithm and im just thinking too much for nothing? I appreciate your help.
Axis aligned bounding box trees are usually well suited to detecting object collisions. Here is a tutorial with some code - its examples are for 3D collision detection, but the data structure can be easily adapted to 2D collision detection.
I am creating a program that displays several bases and the amount of troops each base has. There are two types of bases, friendly and enemy bases. Each Base extends GCompound and consists of a GRect and a GLabel(to display the number of troops). Two arrays are used to keep track of the bases, one for friendly, one for enemy.
I want the user to be able to press the mouse down on one friendly base and release on a different friendly base, causing the troop amount to be transferred from the first base to the second one.
My problem currently is I am only able to detect the base the user presses the mouse down on, and not the base that the mouse is released on. I am using the method getElementAt from the ACM library to return the GObject that a mouse action takes place on.
Code for the mouse press:
public void mousePressed(MouseEvent e){
int mouseX = e.getX();
int mouseY = e.getY();
for(int i = 0; i < PlayerBaseArray.length; i++){
if(getClickedObject(mouseX, mouseY) == PlayerBaseArray[i]){ //Checks to see if the clicked base is in the Array of friendly bases.
pressedIndex = i;
pressedBaseTroopCount = PlayerBaseArray[pressedIndex].getTroopCount();
}
}
}
Code for the mouse release:
public void mouseReleased(MouseEvent m){
int mouseX = m.getX();
int mouseY = m.getY();
for(int i = 0; i < PlayerBaseArray.length; i++){
if(getClickedObject(mouseX, mouseY) == PlayerBaseArray[i]){ // This is always false for some reason.
PlayerBaseArray[i].changeTroopCount(pressedBaseTroopCount);
PlayerBaseArray[pressedIndex].changeTroopCount(-pressedBaseTroopCount);
}
}
}
Method to see what object is clicked:
private GObject getClickedObject(int x, int y){
GObject clicked = getElementAt(x, y);
if(clicked == null) return null;
else return clicked;
}
For some reason the if statement in mouseReleased() is never true, even though it works properly in mousePressed(). Any idea on why the if statement in mouseReleased() does not work?
I've tried researching the problem to no avail, and instead of wasting another night on it I thought I would ask here. Thanks!
You shouldn't use the == operator to compare Objects. You should use the .equals() method:
if (getClickedObject(mouseX, mouseY).equals(PlayerBaseArray[i]))
Put simply, you can only use == only when comparing java primitives (eg int, long etc).
For all "normal" objects, always use objecta.equals(objectb).
This article discusses this issue in more detail.
Attention anal retentives:
Yes, you are right, under some circumstances you can safely use == between objects such as String contants, Integers between -128 and 127, etc, etc. But this guy needed a clear answer and not to be confused. Please resist the temptation to comment about this.