0

i try to move a label in a jframe from place to place by using animation of timer in swing and i always get the last animation that the timer sends for example:

a cetrian label has a variable "place" that holds the label's place every moment place is a variable that 0 < place < 101 and its first value is 1 i also have an enum list that shows all that 100 points x and y so if for example that label place is 52 so:

label.setposition(pointslist.place.getx(),pointslist.place.gety());

in this way it sets the position of the label on place 52

public void move_player1{

    timer = new Timer(20, new ActionListener(){  
        @Override
        public void actionPerformed(ActionEvent e){
            //Move 1 px everytime   
            if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y==pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x+1, player1.getLocation().y); 
            if(player1.getLocation().x==pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x, player1.getLocation().y-1);
            if(player1.getLocation().x>pointslist.values()[place].getx()&&player1.getLocation().y==pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x-1, player1.getLocation().y);
            if(player1.getLocation().x>pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x-1, player1.getLocation().y-1);
            if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y<pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x+1, player1.getLocation().y+1);
            if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x-1, player1.getLocation().y-1);
            if(player1.getLocation().x>pointslist.values()[place].getx()&&player1.getLocation().y<pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x-1, player1.getLocation().y+1);
            if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
            player1.setLocation(player1.getLocation().x+1, player1.getLocation().y-1);
            if(player1.getLocation().x==pointslist.values()[place].getx()&&player1.getLocation().y==pointslist.values()[place].gety()){
                timer.stop();
            }
        }               
    });
    timer.start();
}

now lets say that in other function i get a random number x and makes the label go through all the points one by one x times like that:

int x=*random* (for example 6);
 for(int i=0;i<x;i++)
            {
                place++;
                move_player1();
            }

if the player stands on point 52 i want to see in the gui the player moving point by point until it arrives 58 but it doesnt work and i see in the gui the player moving directly from point 52 to 58 when i want it to go only 1 by 1 how can i solve it? how can i stop the 2nd timer from running before the first finish ?

--------edit: here is a runnable example

mainframe:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

public class mainframe extends JFrame{

    
    private JPanel gamepanel;
    private JPanel DicePanel;
    private Die Dice;
    
    private final int WIDTH=850;
    private final int HEIGHT=610;
    private final String BACKGROUNDPATH=".\\images\\lns.png";
    
    public JButton button;
    public int place;
    JLabel player1;
    Timer timer;
    
    public mainframe(){
        
    
        this.setSize(WIDTH,HEIGHT);     
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);
        
        gamepanel=new JPanel();
        gamepanel.setBounds(new Rectangle(570,HEIGHT));
        gamepanel.setLayout(new BorderLayout());
        
        JLabel backgroundimg=new JLabel(new ImageIcon(BACKGROUNDPATH));
        gamepanel.add(backgroundimg);
        
        player1=new JLabel(new ImageIcon(".\\images\\player1.png"));    
        player1.setBounds(pointslist.POINT1.getx(),pointslist.POINT1.gety(),59,29);
        backgroundimg.add(player1);
        place=1;    
        
        
        DicePanel=new JPanel();
        DicePanel.setLayout(new BorderLayout());
        DicePanel.setPreferredSize(new Dimension(WIDTH-gamepanel.getWidth()-15,HEIGHT));
        Dice=new Die();
        
        
        
        

        
        //Button
        button = new JButton("ROLL THE DICE !");
        button.setFont(new Font("Arial",Font.BOLD,20));
        button.setPreferredSize(new Dimension(200,100));
        DicePanel.add(button,BorderLayout.SOUTH);
        this.setLayout(new BorderLayout());
        this.add(gamepanel,BorderLayout.CENTER);
        this.add(DicePanel,BorderLayout.LINE_END);

        
        button.addActionListener(new ActionListener(){
            
            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                Dice.roll();
                int dienum=Dice.getFaceValue();
                System.out.println(dienum);
                for(int i=0;i<dienum;i++)
                {
                    place++;
                    move_player1();
                }
    }
    
    public void move_player1()
    {
        
          timer = new Timer(50, new ActionListener(){  
                @Override
                public void actionPerformed(ActionEvent e){
                    //Move 1 px everytime   
                
                    if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y==pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x+1, player1.getLocation().y); 
                    if(player1.getLocation().x>pointslist.values()[place].getx()&&player1.getLocation().y==pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x-1, player1.getLocation().y);
                    if(player1.getLocation().x==pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x, player1.getLocation().y-1);
                    if(player1.getLocation().x>pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x-1, player1.getLocation().y-1);
                    if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y<pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x+1, player1.getLocation().y+1);
                    if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x-1, player1.getLocation().y-1);
                    if(player1.getLocation().x>pointslist.values()[place].getx()&&player1.getLocation().y<pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x-1, player1.getLocation().y+1);
                    if(player1.getLocation().x<pointslist.values()[place].getx()&&player1.getLocation().y>pointslist.values()[place].gety())
                        player1.setLocation(player1.getLocation().x+1, player1.getLocation().y-1);
                    
                    
                    
                    if(player1.getLocation().x==pointslist.values()[place].getx()&&player1.getLocation().y==pointslist.values()[place].gety())
                        {
                        timer.stop();
                        }
                            
                    }
                
                    
                               
          });
          timer.start();
    }
});
}

    public static void main(String[] args) {
           
          mainframe frame = new mainframe();
          frame.setVisible(true);
    }
    }

The Dice class:

class Die{

// Note: If we changed the class definition to "public class Die"
// then we would put this class definition in a separate file Die.java

//  Represents one die (singular of dice) with faces showing values
//  between 1 and 6.

   private final int MAX = 6;  // maximum face value

   private int faceValue;  // current value showing on the die

   //-----------------------------------------------------------------
   //  Constructor: Sets the initial face value.
   //-----------------------------------------------------------------
   public Die()
   {
      faceValue = 1;
   }

   // Alternate Constructor

   public Die(int value)
   {
      faceValue = value;
   }

   //-----------------------------------------------------------------
   //  Rolls the die and returns the result.
   //-----------------------------------------------------------------
   public int roll()
   {
      faceValue = (int)(Math.random() * MAX) + 1;

      return faceValue;
   }

   //-----------------------------------------------------------------
   //  Face value mutator.
   //-----------------------------------------------------------------
   public void setFaceValue (int value)
   {
      faceValue = value;
   }

   //-----------------------------------------------------------------
   //  Face value accessor.
   //-----------------------------------------------------------------
   public int getFaceValue()
   {
      return faceValue;
   }

// Returns a string representation of this die. 
       public String toString() 
      { 
             String result = Integer.toString(faceValue); 
             return result; 
        } 

}

and the enum of all the positions:

public enum pointslist {


NOPOINT(-10,-10),
POINT1(15,500),
POINT2(70,500),
POINT3(125,500),
POINT4(180,500),
POINT5(230,500),
POINT6(285,500),
POINT7(338,500),
POINT8(390,500),
POINT9(445,500),
POINT10(500,500),

POINT11(500,450),
POINT12(445,450),
POINT13(390,450),
POINT14(338,450),
POINT15(285,450),
POINT16(230,450),
POINT17(180,450),
POINT18(125,450),
POINT19(70,450),
POINT20(15,450),

POINT21(15,395),
POINT22(70,395),
POINT23(125,395),
POINT24(180,395),
POINT25(230,395),
POINT26(285,395),
POINT27(338,395),
POINT28(390,395),
POINT29(445,395),
POINT30(500,395),

POINT31(500,342),
POINT32(445,342),
POINT33(390,342),
POINT34(338,342),
POINT35(285,342),
POINT36(230,342),
POINT37(180,342),
POINT38(125,342),
POINT39(70,342),
POINT40(15,342),

POINT41(15,290),
POINT42(70,290),
POINT43(125,290),
POINT44(180,290),
POINT45(230,290),
POINT46(285,290),
POINT47(338,290),
POINT48(390,290),
POINT49(445,290),
POINT50(500,290),

POINT51(500,235),
POINT52(445,235),
POINT53(390,235),
POINT54(338,235),
POINT55(285,235),
POINT56(230,235),
POINT57(180,235),
POINT58(125,235),
POINT59(70,235),
POINT60(15,235),

POINT61(15,180),
POINT62(70,180),
POINT63(125,180),
POINT64(180,180),
POINT65(230,180),
POINT66(285,180),
POINT67(338,180),
POINT68(390,180),
POINT69(445,180),
POINT70(500,180),

POINT71(500,130),
POINT72(445,130),
POINT73(390,130),
POINT74(338,130),
POINT75(285,130),
POINT76(230,130),
POINT77(180,130),
POINT78(125,130),
POINT79(70,130),
POINT80(15,130),

POINT81(15,75),
POINT82(70,75),
POINT83(125,75),
POINT84(180,75),
POINT85(230,75),
POINT86(285,75),
POINT87(338,75),
POINT88(390,75),
POINT89(445,75),
POINT90(500,75),

POINT91(500,20),
POINT92(445,20),
POINT93(390,20),
POINT94(338,20),
POINT95(285,20),
POINT96(230,20),
POINT97(180,20),
POINT98(125,20),
POINT99(70,20),
POINT100(15,20);



private final int x;
private final int y;

pointslist(int x,int y)
{
    this.x=x;
    this.y=y;
}
public int getx(){return x;};
public int gety(){return y;};

}

the game board : lns.png

the player icon : player1.png

please pay attention that if for example the player needs to move from square 8 to square 14 it takes the short way instead of going through all the 9 10 11 12 13 i dont want it i want it to make all the way to a certian square

Community
  • 1
  • 1
YoYo
  • 23
  • 4
  • 1
    A `Timer` acts as a pseudo loop, so each time it ticks, is a iteration of this loop, so your `for-loop` isn't going to help you – MadProgrammer Apr 14 '17 at 02:16
  • 2
    The concept is relatively simple (no offensive) and there are dozens if not hundreds of examples showing the basic principle. You have a start point, you have an end point, you have a vector (of speed), you just need to update the location by the vector amount until it reaches or exceeds the target point – MadProgrammer Apr 14 '17 at 02:31
  • i don't have a vector of speed and i don't see how a vector of speed can help me i don't want to change the label's speed only the way it moves i had a problem in the question half pargraph was hidden read it again please – YoYo Apr 14 '17 at 11:42
  • Your vector is +1 position – MadProgrammer Apr 14 '17 at 20:36
  • Unless you can supply a runnable example, I doubt there's much more we can do to help you – MadProgrammer Apr 14 '17 at 20:43
  • Alright i edited and added the example you can run and watch the problem thank you for the help by the way – YoYo Apr 15 '17 at 01:23

1 Answers1

0

Start by removing the call to move_player1() from thefor-loop...

@Override
public void actionPerformed(ActionEvent e) {
    // TODO Auto-generated method stub
    Dice.roll();
    int dienum = Dice.getFaceValue();
    System.out.println(dienum);
    for (int i = 0; i < dienum; i++) {
        place++;
    }
    move_player1();
}

This is creating n number of times which will only mess with anything you do again in the future.

Next, add a call to repaint in the Timer's ActionListener to trigger a new paint cycle

timer = new Timer(50, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        //...
        player1.getParent().repaint();
    }

});

it doesnt solve my problem as i said the fact that the player goes through the lines when it doesnt need pay attention that when the label needs to arrive on a square in the upper line it does not take the "long way" it just jumps to there and what i want is the player to go a square by square until it arrives the destination.

have you gotten what i want to do? i just dont want the player to go straight to some point i want it to go through all the squares in the way before. if for example you can get the number 99 from the dice the place would be on the first try 100 and then the label animation will go from 1 to 100 straightly(through 1,20,21,40,41,60,61,80,81,100) and i dont want it to be like that i want it to pass through all the 99 squares before

So, basically what you really want is some kind of time line/key frame animation

See:

For an additional concept, you could also have a look at Can't move JLabel on JPanel

ive looked at them all and i dont see how it can help me i dont do a spiral or square movement my movement are more close to a snake movement i also have no problem with the label flies out from the screen i just tried to understand how it was working and i didnt get the role that the timeline class takes

Okay, you still have a timeline. Each point is key frame. Basically, you want to move from one index point on the board to another, each index point has an associated point.

So, I have a simple Mover class, which takes a from index and a to index. I then have a model which given an index point will return the associated Point. This means that my Mover class just needs to increment the index point (until it reaches the target point), it reports to an observer that the position has been updated, which then updates the position of the player on the screen...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test1 {

    public static void main(String[] args) {
        new Test1();
    }

    public Test1() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }

                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public interface Positioner {

        public void movePlayerTo(int to);

        public void playerStoppedAt(int at);
    }

    public class TestPane extends JPanel implements Positioner {

        private BufferedImage background;
        private Player player;
        private PointList pointList = new PointList();

        private int currentPoint = 0;

        public TestPane() throws IOException {
            background = ImageIO.read(getClass().getResource("/Board.png"));
            setLayout(null);
            player = new Player();
            add(player);

            movePlayerTo(currentPoint);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    int roll = (int) ((Math.random() * 6) + 1);
                    System.out.println(currentPoint + " + " + roll);
                    Mover mover = new Mover(currentPoint, currentPoint + roll, TestPane.this);
                    mover.start();
                }
            });
        }

        @Override
        public void playerStoppedAt(int at) {
            // Does the player need to move directly to a new
            // location, ie slide down or climb up
            Integer next = pointList.nextPoint(at);
            if (next != null) {
                // Move direclty to next position
            }
        }

        @Override
        public void movePlayerTo(int to) {
            currentPoint = to;
            player.setLocation(pointList.pointAt(currentPoint));
            repaint();
        }

        @Override
        public Dimension getPreferredSize() {
            return background == null ? new Dimension(200, 200) : new Dimension(background.getWidth(), background.getHeight());
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (background != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.drawImage(background, 0, 0, this);
                g2d.dispose();
            }
        }

    }

    public class Mover {

        private int point;
        private int from;
        private int to;
        private Positioner positioner;
        private Timer timer;

        public Mover(int from, int to, Positioner positioner) {
            this.from = from;
            this.to = to;
            this.positioner = positioner;
        }

        public void start() {
            point = from;
            timer = new Timer(500, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    point++;
                    if (point >= to) {
                        point = to;
                        timer.stop();
                        positioner.playerStoppedAt(point);
                    } else {
                        positioner.movePlayerTo(point);
                    }
                }
            });
            timer.start();
        }

    }

    public class Player extends JPanel {

        private BufferedImage background;

        public Player() throws IOException {
            setOpaque(false);
            background = ImageIO.read(getClass().getResource("/Player.png"));
            setSize(getPreferredSize());
        }

        @Override
        public Dimension getPreferredSize() {
            return background == null ? new Dimension(200, 200) : new Dimension(background.getWidth(), background.getHeight());
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (background != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.drawImage(background, 0, 0, this);
                g2d.dispose();
            }
        }

    }

    public class PointList {

        private List<Point> points = new ArrayList<>(100);
        private Map<Integer, Integer> nextPoint = new HashMap<>();

        public PointList() {
            points.add(new Point(15, 500));
            points.add(new Point(70, 500));
            points.add(new Point(125, 500));
            points.add(new Point(180, 500));
            points.add(new Point(230, 500));
            points.add(new Point(285, 500));
            points.add(new Point(338, 500));
            points.add(new Point(390, 500));
            points.add(new Point(445, 500));
            points.add(new Point(500, 500));
            points.add(new Point(500, 450));
            points.add(new Point(445, 450));
            points.add(new Point(390, 450));
            points.add(new Point(338, 450));
            points.add(new Point(285, 450));
            points.add(new Point(230, 450));
            points.add(new Point(180, 450));
            points.add(new Point(125, 450));
            points.add(new Point(70, 450));
            points.add(new Point(15, 450));
            points.add(new Point(15, 395));
            points.add(new Point(70, 395));
            points.add(new Point(125, 395));
            points.add(new Point(180, 395));
            points.add(new Point(230, 395));
            points.add(new Point(285, 395));
            points.add(new Point(338, 395));
            points.add(new Point(390, 395));
            points.add(new Point(445, 395));
            points.add(new Point(500, 395));
            points.add(new Point(500, 342));
            points.add(new Point(445, 342));
            points.add(new Point(390, 342));
            points.add(new Point(338, 342));
            points.add(new Point(285, 342));
            points.add(new Point(230, 342));
            points.add(new Point(180, 342));
            points.add(new Point(125, 342));
            points.add(new Point(70, 342));
            points.add(new Point(15, 342));
            points.add(new Point(15, 290));
            points.add(new Point(70, 290));
            points.add(new Point(125, 290));
            points.add(new Point(180, 290));
            points.add(new Point(230, 290));
            points.add(new Point(285, 290));
            points.add(new Point(338, 290));
            points.add(new Point(390, 290));
            points.add(new Point(445, 290));
            points.add(new Point(500, 290));
            points.add(new Point(500, 235));
            points.add(new Point(445, 235));
            points.add(new Point(390, 235));
            points.add(new Point(338, 235));
            points.add(new Point(285, 235));
            points.add(new Point(230, 235));
            points.add(new Point(180, 235));
            points.add(new Point(125, 235));
            points.add(new Point(70, 235));
            points.add(new Point(15, 235));
            points.add(new Point(15, 180));
            points.add(new Point(70, 180));
            points.add(new Point(125, 180));
            points.add(new Point(180, 180));
            points.add(new Point(230, 180));
            points.add(new Point(285, 180));
            points.add(new Point(338, 180));
            points.add(new Point(390, 180));
            points.add(new Point(445, 180));
            points.add(new Point(500, 180));
            points.add(new Point(500, 130));
            points.add(new Point(445, 130));
            points.add(new Point(390, 130));
            points.add(new Point(338, 130));
            points.add(new Point(285, 130));
            points.add(new Point(230, 130));
            points.add(new Point(180, 130));
            points.add(new Point(125, 130));
            points.add(new Point(70, 130));
            points.add(new Point(15, 130));
            points.add(new Point(15, 75));
            points.add(new Point(70, 75));
            points.add(new Point(125, 75));
            points.add(new Point(180, 75));
            points.add(new Point(230, 75));
            points.add(new Point(285, 75));
            points.add(new Point(338, 75));
            points.add(new Point(390, 75));
            points.add(new Point(445, 75));
            points.add(new Point(500, 75));
            points.add(new Point(500, 20));
            points.add(new Point(445, 20));
            points.add(new Point(390, 20));
            points.add(new Point(338, 20));
            points.add(new Point(285, 20));
            points.add(new Point(230, 20));
            points.add(new Point(180, 20));
            points.add(new Point(125, 20));
            points.add(new Point(70, 20));
            points.add(new Point(15, 20));

            nextPoint.put(38, 4);
            nextPoint.put(33, 10);
            nextPoint.put(16, 36);
            nextPoint.put(40, 80);
            nextPoint.put(46, 74);
            nextPoint.put(31, 69);
            nextPoint.put(56, 24);
            nextPoint.put(65, 96);
            nextPoint.put(99, 77);
            nextPoint.put(91, 68);
        }

        public Point pointAt(int index) {
            index = Math.abs(index % points.size());
            return points.get(index);
        }

        public Integer nextPoint(int index) {
            return nextPoint.get(index);
        }

    }
}

Now, I did setup a check to see if the player should move up/or down from there new position, but I didn't setup the movement code, which would be required to move the object from a start Point to a end Point, instead of moving through an index point ;)

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • it doesnt solve my problem as i said the fact that the player goes through the lines when it doesnt need pay attention that when the label needs to arrive on a square in the upper line it does not take the "long way" it just jumps to there and what i want is the player to go a square by square until it arrives the destination – YoYo Apr 15 '17 at 10:50
  • have you gotten what i want to do? i just dont want the player to go straight to some point i want it to go through all the squares in the way before. if for example you can get the number 99 from the dice the place would be on the first try 100 and then the label animation will go from 1 to 100 straightly(through 1,20,21,40,41,60,61,80,81,100) and i dont want it to be like that i want it to pass through all the 99 squares before – YoYo Apr 15 '17 at 21:57
  • ive looked at them all and i dont see how it can help me i dont do a spiral or square movement my movement are more close to a snake movement i also have no problem with the label flies out from the screen i just tried to understand how it was working and i didnt get the role that the timeline class takes – YoYo Apr 16 '17 at 00:39
  • You want to move through each point? Then you need a list of points, on each iteration of the loop, you pop the next point of and update the position of the object, you keep doing until you run out of points - it's a basic timeline – MadProgrammer Apr 16 '17 at 01:23