I have been practicing algorithms and I have found one that took me weeks and still couldn't solve. I couldn't come up with a complete algorithm but I have been working on an idea, and the code I wrote so far is :
Note: The reason I shared comprehensive problem is not to enlong question rather that I might have misunderstood the main point of problem at first place.
PROBLEM
A PropBot can only make two distinct movements. It can either move 10 cm forward, or turn towards the right by 45 degrees. Each of these individual movements takes one second of time.
Input
Your module has two inputs: the Cartesian coordinates of a point on the plane that the PropBot wants to get as close to as possible, and the maximum number of seconds that can be used to do this. At the beginning of the navigation, the robot is located at the origin, pointed in the +x direction. The number of seconds will be an integer between 0 and 24, inclusive. Both the x and y coordinates of the desired destination point will be a real number between -100 and 100, inclusive. The first entry in the input file will be the number of test
cases, t (0 < t ≤ 100). Following this line will be t lines, with each line containing three entries separated by spaces. The first entry will be the number of seconds PropBot has to get close to the point. The second entry is the x-coordinate of the point, and the third entry is the y coordinate of the point.
Output
Your program must return the distance between the goal point and the closest point the robot can get to within the given time. Your result should include at least one digit to the left of the decimal point, and exactly six digits to the right of the decimal point. To eliminate the chance of round off error affecting the results, we have constructed the test data so the seventh digit to the right of the decimal point of the true result is never a 4 or a 5.
Sample Input
2
24 5.0 5.0
9 7.0 17.0
Sample Output
0.502525 <- HOW ?
0.100505 OK
JAVA CODE
Enum Direction
public enum Direction {
EAST(1), N_EAST(2), NORTH(3), N_WEST(4), WEST(5), S_WEST(6), SOUTH(7), S_EAST(8);
private int direction;
private int index;
Direction(){
direction = 1;
index = 0;
}
Direction(int dir){
direction = dir;
}
int getDirection(){
return direction;
}
public int incrementDir(){
if(direction > 1 && direction <= 8){
direction = 8 - index++;
// Rotate towards right side
}
else if(direction == 1){
direction = 8;
index = 1;
}
return direction;
}
}
Abstract - Calculation.java
import java.awt.Point;
public abstract class Calculation {
public static Direction getDir(Point p){
int line = getCloseLine(p);
switch (line) {
case 1:
return Direction.EAST;
case 2:
return Direction.N_EAST;
// 2nd Quadrant
case 3:
return Direction.NORTH;
case 4:
return Direction.N_WEST;
// 3rd Quadrant
case 5:
return Direction.WEST;
case 6:
return Direction.S_WEST;
// 4th Quadrant
case 7:
return Direction.SOUTH;
case 8:
return Direction.S_EAST;
default:
return Direction.EAST;
}
}
public static int getSelectedLine(Point p){
int a = getCloseLine(p);
return a;
}
public static int getQuadrant(Point target) {
double x = target.getX();
double y = target.getY();
if (x > 0 && y > 0)
return 1;
else if (x < 0 && y > 0)
return 2;
else if (x < 0 && y < 0)
return 3;
else if (x > 0 && y < 0)
return 4;
// Means point lies on an Axis not in any Quadrant
return -1;
}
public static int getAxis(Point target) {
double x = target.getX();
double y = target.getY();
if (x > 0 && y == 0)
return 1;
else if (x == 0 && y > 0)
return 2;
else if (x < 0 && y == 0)
return 3;
else if (x == 0 && y < 0)
return 4;
else if( x == 0 && y == 0)
return 0;
return -1;
}
public static double getAngle(Point v2) {
double d = v2.getY() / v2.getX();
double ang = Math.toDegrees(Math.atan(d));
return ang;
}
public static int getSector(Point point) {
double angle = getAngle(point);
int quad = getQuadrant(point);
if(quad == -1)
return -1;
switch (quad) {
case 1:
if (angle < 45.0)
return 1;
else
return 2;
case 2:
if (angle < -45.0)
return 3;
else
return 4;
case 3:
if (angle < 45.0)
return 5;
else
return 6;
case 4:
if (angle < -45.0)
return 7;
else
return 8;
}
return -1;
}
public static int getCloseLine(Point p) {
int sec = getSector(p);
double angle = getAngle(p);
System.out.println("ANGLE : " + angle);
if(sec == -1){
int axis = getAxis(p);
switch(axis){
case 1:
return 1;
case 2:
return 3;
case 3:
return 5;
case 4:
return 7;
case 0:
return 0;
}
}
switch (sec) {
case 1:
if (angle < 22.5)
return 1;
else
return 2;
case 2:
if (angle < 67.5)
return 2;
else
return 3;
// 2nd Quadrant
case 3:
if (angle < -67.5)
return 3;
else
return 4;
case 4:
if (angle < -22.5)
return 4;
else
return 5;
// 3rd Quadrant
case 5:
if (angle < 22.5)
return 5;
else
return 6;
case 6:
if (angle < 67.5)
return 6;
else
return 7;
// 4th Quadrant
case 7:
if (angle < -67.5)
return 7;
else
return 8;
case 8:
if (angle < -22.5)
return 8;
else
return 1;
}
return -1;
}
}
Main.java
import java.awt.Point;
import java.util.Scanner;
public class Main {
public static void main(String[] args)
{
Scanner s = new Scanner(System.in);
int time = 0;
Point p = new Point(0, 0);
System.out.println("Enter time: ");
time = s.nextInt();
System.out.println( " X: ");
p.x = s.nextInt();
System.out.println( " Y: " );
p.y = s.nextInt();
if(!(time > 0 && time <= 24)){
s.close();
System.err.println("INPUT ERROR!");
return;
}
// Initialize bot facing +x direction
Bot b = new Bot(p, Direction.EAST);
int line = Calculation.getCloseLine(p);
while(time != 0 && b.getDirectionInt() != line){
// Rotate the face towards the point
b.rotate();
time--;
}
s.close();
}
}
Bot.java
import java.awt.Point;
public class Bot {
private Point location;
private Direction direction;
public Bot(){
location = new Point(0, 0);
direction = Direction.EAST;
}
public Bot(Point loc, Direction dir){
location = loc;
direction = dir;
}
public Point move(){
return location;
}
public int rotate(){
direction.incrementDir();
return direction.getDirection();
}
public int getDirectionInt(){
return direction.getDirection();
}
}
My approach was to divide the Cartesian plane into sectors and get a closet line to the input point and rotate the bot and then move ahead.
First issue : I got how the second case output was evaluated but I don't have any idea about the first one.
Line distribution as follows :
Second issue : If the bot moves diagonally (45 degrees) and then move either horizontally or vertically, after that it seems as if the whole Cartesian plane has moved and the code I wrote is not valid anymore.
Is my approach correct? if yes then how I improve it further?
If my approach is wrong? Please suggest a better alternative.
I think you could create a graph for every possible stop of the robot and its orientation. Which will be a triple (x, y, orientation) and orientation is one of 0,1,..,7. For each orientation i moving forward is given by the vector orientation_vec[i]
import networkx as nx
def one_second(G):
# In each step you can either go forward or turn. Create one edge
# for each node and these two possible moves.
orientation_vec = [(10000000, 0), (7071067, -7071067), (0, -10000000),
(-7071067, -7071067), (-10000000, 0), (-7071067, 7071067),
(0, 10000000), (7071067, 7071067)]
for node in G.nodes():
# edge to next orientation
G.add_edge(node, (node[0], node[1], (node[2] + 1)%8))
# edge going forward
G.add_edge(node, (node[0] + orientation_vec[node[2]][0],
node[1] + orientation_vec[node[2]][1],
node[2]))
def create_graph(max_time):
G = nx.Graph()
G.add_node(n=(0, 0, 0))
for t in range(max_time):
one_second(G)
print(len(G.nodes()))
We can now go through all nodes and find the closest to the target.
Once the graph is created we can find the shortest path by using dijkstra or A*. I used the networkx package for that.
import math
def find_closest_path(paths, end):
min_dist = end[0]**2 + end[1]**2
best_path = None
for key in paths.keys():
end_path = paths[key][-1]
path_to_end = (end_path[0] - end[0])**2 + (end_path[1]-end[1])**2
if path_to_end < min_dist:
min_dist = path_to_end
best_path = paths[key]
min_dist = math.sqrt(min_dist)/10**6
x = [p[0]/10**6 for p in best_path]
y = [p[1]/10**6 for p in best_path]
return min_dist, x, y
def robot_path(end, max_time):
create_graph(max_time)
paths = nx.single_source_shortest_path(G, (0, 0, 0), cutoff=max_time)
return find_closest_path(paths, end)
A plotting function i copy pasted from stackoverflow somewhere.
from pylab import *
import matplotlib.pyplot as plt
def plot_robot_path(x,y, end):
assert len(x) == len(y)
color=['b']*len(x) + ['r']
fig = plt.figure()
ax = fig.add_subplot(111)
scatter(x+[end[0]], y+[end[1]], s=100 ,marker='o', c=color)
[plot([x[i], x[i+1]], [y[i], y[i+1]], '-', linewidth=3 ) for i in range(len(x)-1)]
left,right = ax.get_xlim()
low,high = ax.get_ylim()
arrow(left, 0, right -left, 0, length_includes_head = True, head_width = 0.15 )
arrow(0, low, 0, high-low, length_includes_head = True, head_width = 0.15 )
grid()
show()
If we run it i get the following results:
end1=(5*10**6, 5*10**6)
max_time = 24
min_dist1, x1, y1 = robot_path(end1, max_time)
print(min_dist1)
plot_robot_path(x1, y1, (5, 5))
max_time=9
end2=(7*10**6, 17*10**6)
min_dist2, x2, y2 = robot_path(end2, max_time)
print(min_dist2)
plot_robot_path(x2, y2, (7, 17))
Here's compact backtracking solution:
import math
min_d = 1e+10
c = 0.70710678
xt = 5.0
yt = 5.0
def solve(remaining_t, x, y, dir_x, dir_y):
global min_d
d = math.sqrt((x - xt)**2 + (y - yt)**2)
if d < min_d:
min_d = d
if remaining_t == 0 or d - remaining_t * 10 >= min_d:
return
solve(remaining_t - 1, x + dir_x, y + dir_y, dir_x, dir_y)
solve(remaining_t - 1, x, y, c * (dir_x - dir_y), c * (dir_x + dir_y))
solve(24, 0.0, 0.0, 10.0, 0.0)
print(min_d)
It takes ~5 sec. on my computer.
A bit of explanation:
min_d - is the current minimum distance, we initialized it with a large value (should be greater than any distance could possibly be)
The solve function takes the following parameters:
remaining_t - remaining time in seconds, on each step it's decreased by 1
x and y - current coordinates of the bot
dir_x, dir_y - coordinates of bot's current direction vector. This vector has length 10 an we start with this vector pointing towards the x axis: (10, 0)
At each step the solve function considers current distance to the target point and updates min_d if needed. If there's no remaining time left or we have gone too far from the target point we stop going forward immediately. Otherwise we try 1) go forward 2) turn 45 degrees.
When bot is going forward (considering current direction) x becomes x+dir_x and y becomes y+dir_y. The direction vector stays the same.
When the bot turns, its coordinates stay the same but the direction vector changes. See https://en.m.wikipedia.org/wiki/Rotation_matrix (our constant c = sin 45 = cos 45)
Here is the Java Code for Algrid's answer :
public class Main {
static double min_d = 1e+10;
// diagonal distance factor cos(45), needs to multiply with hypotenuse
static double c = 0.707110678;
static double xt = 7.0;
static double yt = 17.0;
public static void solve(int time, double x, double y, double dirX, double dirY) {
double d = Math.sqrt(Math.pow((x - xt), 2) + Math.pow((y - yt), 2));
if( d < min_d )
min_d = d;
if( time == 0 || (d-time * 10) >= min_d ){
return;
}
solve(time - 1, x + dirX, y + dirY, dirX, dirY);
solve(time - 1, x, y, c * (dirX - dirY), c * (dirX + dirY));
}
public static void main(String[] args)
{
solve(9, 0.0, 0.0, 10.0, 0.0);
}
}// Class END
I am currently working on a 2D side scroller and have implemented the techniques use in this article for a grapple hook, and it works really well. My problem is I want my player to be able to swing around the rope a little bit to gain a bit of momentum, but currently I can't stop the player from moving all the way up to 90 degrees either side. What techniques can be applied to force this limit?
I have tried using a separate player speed for swinging but this only slows the process down I can still swing up to 90 deg each side.
Here's my update function in the player
public void update(float dt){
//handle friction and air resistance
if(dx !=0){
if(touchingGround) {
// apply friction
if (dx > 0) {
dx -= retardation;
} else {
dx += retardation;
}
} else {
//applied air resistance
if (dx > 0) {
dx -= airResistance;
} else {
dx += airResistance;
}
}
}
// handle gravity
dy -= Constants.GRAVITY * dt;
if(dy < -terminalVelocity){
dy = -terminalVelocity;
}
/*
Handle Player movement
*/
if(right){
if(dx <= maxSpeed){
dx += acceleration;
}
dx = maxSpeed;
}
if(left){
if(dx <= -maxSpeed){
dx -= acceleration;
}
dx = -maxSpeed;
}
if(isGrappling){
//If we collide with something we need to stop grappling
if(hasCollided){
isGrappling = false;
} else {
// This algorithm from here:
// http://gamedev.stackexchange.com/questions/61596/player-rope-swing
float currentD = (float) Math.sqrt(((grappleX - x) * (grappleX - x)) + ((grappleY - y) * (grappleY - y)));
float prevX = getX(), prevY = getY();
if (currentD > grappleRadius) {
Vector2 hookPos = new Vector2(grappleX, grappleY);
Vector2 testPos = (new Vector2(x, y).sub(hookPos)).nor();
y = (hookPos.y + testPos.y * grappleRadius);
x = (hookPos.x + testPos.x * grappleRadius);
// s = d / t
dx += (x - prevX) / dt;
dy += (y - prevY) / dt;
}
}
}
/*
Collision Detection, handle last always!
*/
float oldX = getX(), oldY = getY();
boolean collisionX = false, collisionY = false;
// move on x
x += dx * dt;
// calculate the increment for step in #collidesLeft() and #collidesRight()
increment = collisionLayer.getTileWidth();
increment = getWidth() < increment ? getWidth() / 2 : increment / 2;
if(dx < 0) // going left
collisionX = collidesLeft();
else if(dx > 0) // going right
collisionX = collidesRight();
// react to x collision
if(collisionX) {
setX(oldX);
dx = 0;
}
// move on y
y += dy * dt;
// calculate the increment for step in #collidesBottom() and #collidesTop()
increment = collisionLayer.getTileHeight();
increment = getHeight() < increment ? getHeight() / 2 : increment / 2;
if(dy < 0) {
touchingGround = collisionY = collidesBottom();
// we can only jump 2 times before we have to touch the floor again
if(collisionY){
numberOfJumps = 2;
}
} else if(dy > 0) {
collisionY = collidesTop();
}
// react to y collision
if(collisionY) {
setY(oldY);
dy = 0;
}
hasCollided = collisionX || collisionY;
}
As I am not using any physics engine I chose to just emulate the physics by limiting the angle at which the player can apply force to the swing.
// check if angle permits movement
if(grappleAngle < Math.PI/9 && grappleAngle > -Math.PI/9) {
// handle momentum gaining on rope
if (right) {
dx += swingAcceleration * dt;
}
if (left) {
dx -= swingAcceleration * dt;
}
}
I am making a tank game like the Atari tank game and I ran into some troubles. I'm trying to make the enemy's tank move towards the player's tank but it can't move diagonally since the player isn't allowed to do that also. However, the way I implemented it, it goes diagonally when the distance from the x and y axis are equal to each other. Is there a way I can make it so that it would be forced go in one direction for a while after changing direction? The way do it is that it will compare its x and y values with the player's tank ( the tank that is being passed in ) and the four cases are for if the x-component is bigger than y and is it on theright or left, and if the y-component is bigger than xand is it above or below the player's tank. Thank you for the help!
public void enemyMove(Tank t) {
if ( Math.abs(getX() - t.getX()) >= Math.abs(getY() - t.getY()) && getX() > t.getX() )
goLeft();
else if ( Math.abs(getX() - t.getX()) > Math.abs(getY() - t.getY()) && getX() < t.getX() )
goRight();
else if ( Math.abs(getX() - t.getX()) <= Math.abs(getY() - t.getY()) && getY() > t.getY() )
goUp();
else if ( Math.abs(getX() - t.getX()) < Math.abs(getY() - t.getY()) && getY() < t.getY() )
goDown();
setX(getX() + dx);
setY(getY() + dy);
}
public void goUp() {
dx = 0;
dy = -1;
}
public void goDown() {
dx = 0;
dy = 1;
}
public void goLeft() {
dx = -1;
dy = 0;
}
public void goRight() {
dx = 1;
dy = 0;
}
You can use the Manhattan distance and find the median then move the tank in both directions.
The code below is a little gross, but should do what you want. In your current code the enemy is moving one pixel in the X direction in the first frame, then one pixel in the Y direction in the next frame, which makes the movement look diagonal. The code below sets a short-term target point for the enemy tank to head towards in either the X or Y direction, at some distance away defined by MOVE_BLOCK. The enemy will move until it passes that target point, then recalculate which direction it should be moving. Note that enemyMove will be called every time a new frame is created, so probably 60 times a second.
// The tank will move this distance in x or y before changing directions
private final int MOVE_BLOCK = 120;
// The short-term target of the enemy tank
Point target = null;
public void enemyMove(Tank t) {
/* true if enemy is moving left and enemy is already left of target point,
* or moving right and right of target... */
boolean passedTarget = target == null ||
(dx < 0 && getX() < target.getX()) ||
(dx > 0 && getX() > target.getX()) ||
(dy < 0 && getY() < target.getY()) ||
(dy > 0 && getY() > target.getY());
// Calculate a new target point if the old target was passed
if(passedTarget) {
int xDist = Math.abs(getX() - t.getX());
int yDist = Math.abs(getY() - t.getY());
if ( xDist > yDist ) {
// Cover the remaining distance when close to the target
int moveLength = xDist < MOVE_BLOCK ? xDist : MOVE_BLOCK;
if( getX() >= t.getX() )
goLeft();
target = new Point(getX() - moveLength, getY());
else
goRight();
target = new Point(getX() + moveLength, getY());
} else {
int moveLength = yDist < MOVE_BLOCK ? yDist : MOVE_BLOCK;
if ( getY() >= t.getY() ) {
goUp();
target = new Point(getX(), getY() - moveLength);
} else {
goDown();
target = new Point(getX(), getY() + moveLength);
}
}
}
setX(getX() + dx);
setY(getY() + dy);
}
So I am working on a blackjack game, I have wrote a render process which will render a card going out of the cards stack and sliding to the place where it shows all dealer's cards.
My method works fine, except one problem which I will elaborate:
Whenever Y coordinate reaches the target Y coordinate first, the sprite will only move on X-asis because it cant move Y anymore, instead of making a straight angle to the point.
So what it will do is move up diagonally and then instantly go to the right (in my case)
GIF:
(source: gyazo.com)
MP4 (choose mp4 in the (..) menu http://gyazo.com/bec6daadcb46bedc4777a3e4c5ff8c77)
As you can see, it does what I just said.
What did I do wrong? how can I make it motion in a straight angle to the target without going diagonal up and than turn right away?
My process code:
// If the target is on the right side of the start point
if (startPoint.getX() < endPoint.getX()) {
if (current.getX() < endPoint.getX()) {
current.x += moveSpeed;
if (current.getX() > endPoint.getX()) {
current.x = (int) endPoint.getX();
}
}
else {
xDone = true;
}
}
else {
if (current.getX() > endPoint.getX()) {
current.x -= moveSpeed;
if (current.getX() < endPoint.getX()) {
current.x = (int) endPoint.getX();
}
}
else {
xDone = true;
}
}
// Vise-versa
if (startPoint.getY() < endPoint.getY()) {
if (current.getY() < endPoint.getY()) {
current.y += moveSpeed;
if (current.getY() > endPoint.getY()) {
current.y = (int) endPoint.getY();
}
}
else {
yDone = true;
}
}
else {
if (current.getY() > endPoint.getY()) {
current.y -= moveSpeed;
if (current.getY() < endPoint.getY()) {
current.y = (int) endPoint.getY();
}
}
else {
yDone = true;
}
}
// Callback, dw about it
CardContainer.getCardSprite(CardContainer.SPECIAL, 0).drawSprite((int) current.getX(), (int) current.getY());
// Alert finished, in poisiuton
if (xDone && yDone) {
ch.submitCard(card);
}
current = current position
startPoint = the start point
endPoint = the end point
Thanks!
EDited code:
private void applyMovement(double alpha) {
double dx = endPoint.getX() - startPoint.getX();
double dy = endPoint.getY() - startPoint.getY();
this.current.setLocation(startPoint.getX() + alpha * dx, startPoint.getY() + alpha * dy);
}
public void process() {
double alpha = (double) stepsDone / distance;
applyMovement(alpha);
stepsDone++;
// Callback, dw about it
CardContainer.getCardSprite(CardContainer.SPECIAL, 0).drawSprite((int) current.getX(), (int) current.getY());
// Alert finished, in poisiuton
if (stepsDone >= distance) {
ch.submitCard(card);
}
}
Distance calculation:
this.distance = (int) start.distance(end);
Used Point2D distance method:
public double distance(Point2D pt) {
double px = pt.getX() - this.getX();
double py = pt.getY() - this.getY();
return Math.sqrt(px * px + py * py);
}
I would recommend to not use any form of "slope" in such a computation. You will run into problems when the difference in x-direction approaches zero, because then the slope will tend towards infinity.
Assuming that your points are Point2D.Double (or something similar - you should include this kind of information in your questions!), you can compute the movement as follows:
private Point2D.Double initial = ... // The initial position
private Point2D.Double current = ... // The current position
private Point2D.Double target = ... // The target position
void applyMovment(double alpha) {
double dx = target.getX() - initial.getX();
double dy = target.getY() - initial.getY();
current.x = initial.getX() + alpha * dx;
current.y = initial.getY() + alpha * dy;
}
The applyMovment method sketched here can be called with a double value between 0.0 and 1.0, where 0.0 corresponds to the initial position and 1.0 corresponds to the target position. This is just a Linear Interpolation.
So for example, when you have some sort of loop for the animation, you can use the method as follows:
int numberOfSteps = 10;
for (int i=0; i<=numberOfSteps; i++)
{
double alpha = (double)i / numberOfSteps;
applyMovement(alpha);
repaint();
}
This works for any arrangement of the start- and end points, without any sign- or direction issues. It just interpolates between the two positions.
Your calculation needs to be based upon moving the currentY and currentX along a specific line, not a specific set of intervals (moveSpeed). The formula for graphing points on a line is:
y = mx + b
Where x and y are the varying points, m is equal to the slope of the line, and b is what's called the y-intercept.
Your slope is calculated by the formula:
double slope = ((double) endPoint.getY() - startPoint.getY()) / ((double) endPoint.getX() - startPoint.getX());
And the Y intercept can be calculated by just plugging in a bunch of known values once you have them:
double yIntercept = (double) endPoint.getY() - (slope * endPoint.getX())
Then, just loop through the count of the difference in X:
for (int xVal = startPoint.getX(); xVal < endPoint.getX(); xVal++){
currentX = xVal;
currentY = (slope * xVal) + yIntercept;
}
And you should be good.
Warning: this is all off of the top of my head, I don't know if it'll compile.
I'm having a problem where an image rotates based on the difference between the players position and the mouse position which works but the image isn't actually pointing at the mouse. I want the top center of the image to be pointing at the mouse and it just seems that the angle is always off. I'll post the code below.
Input input = gc.getInput();
if (input.isKeyDown(Input.KEY_W))
{
position.y -= 3;
}
if (input.isKeyDown(Input.KEY_A))
{
position.x -= 3;
}
if (input.isKeyDown(Input.KEY_S))
{
position.y += 3;
}
if (input.isKeyDown(Input.KEY_D))
{
position.x += 3;
}
float xDistance = input.getAbsoluteMouseX() - position.x;
float yDistance = input.getAbsoluteMouseY() - position.y;
double angleToTurn = Math.toDegrees(Math.atan2(yDistance, xDistance));
entityImage.setRotation((float) angleToTurn);
The javadoc for Math.atan(double) says that the returning value can range from -pi/2 to pi/2.So you need to compensate for that return value.
double angleToTurn = Math.toDegrees(Math.atan2(yDistance, xDistance));
if(angleToTurn < 0)
{
angleToTurn += 360;
}