Interpolation over a network - receiving and drawing an opponent in 1v1 game

Hello. I’m creating a 1v1 game with an authoritative server and two clients.

My whole network is based on UDP packets:

  • sending positions from clients
  • sending confirmations them from the server (and deleting old ones on client)
  • sending ‘non cheated’ positions to the opponent from the server

Everything was okay, until the last issue appeared.

When I send positions from the server to the opponent, they are being received in different timings. An example:

    • Position 1 comes in 43 ms
    • Position 2 comes in 15 ms (from the previous one)
    • Position 3 comes in 34 ms
    • Position 4 comes in 10 ms
    • Position 5 comes in 35 ms

As we all know - THIS IS NETWORKING, it is impossible to make it ‘stable’ that every packet comes within a constant: 30 ms time (omg that would be perfect).

And here comes my problem, I don’t know how to adjust the interpolation that the ‘opponent’ is drawn as smooth as a normal player (me on my phone).

When the process is static (I click arrows and move my player ‘step by step’) it is really, really smooth. My gameloop has some timings and it works really well.
But when I receive my opponent’s position and set his ‘desiredPosition(x,y)’ his movement is not smooth, his ‘positions’ are not ‘updating’ in a constant time but with random delays (cause we get these positions in different timings).

And example of my issue:

The gameloop:

@Override
		public void run(){
			
			long beginTime;		// the time when the cycle begun
			long timeDiff;		// the time it took for the cycle to execute
			int sleepTime;		// ms to sleep (<0 if we're behind)
			int framesSkipped;	// number of frames being skipped 
	        
	
			while(match_running){
				beginTime = System.currentTimeMillis();
				framesSkipped = 0;	// resetting the frames skipped
				// update game state 
                
                // MOST IMPORTANT METHODS - look below
                player_me.updatePosition(); // move me (smoothly) to desired position
                player_op.updatePosition(); // move the opponent (smoothly)

				// render state to the screen
				// draws the canvas on the panel
				repaint();	
				
				// calculate how long did the cycle take
				timeDiff = System.currentTimeMillis() - beginTime;
				// calculate sleep time
				sleepTime = (int)(FRAME_PERIOD - timeDiff);
				
				if (sleepTime > 0) {
					// if sleepTime > 0 we're OK
					try {
						// send the thread to sleep for a short period
						// very useful for battery saving
						Thread.sleep(sleepTime);	
					} catch (InterruptedException e) {}
				}
				
				
				while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
					// we need to catch up
					// update without rendering
					player_me.updatePosition(); // move me to desired position
                    player_op.updatePosition(); // move the opponent
					
					// add frame period to check if in next frame
					sleepTime += FRAME_PERIOD;	
					framesSkipped++;
				}

				
				// sending positions every 30 seconds
				time_now = System.currentTimeMillis();
				if(time_now >= (packet_past_time + 30)) {
					sendPositions();
					packet_past_time = System.currentTimeMillis();
				}
			}
		}

Listening for ‘opponent’s positions’:

  public void received(Object o){
        if(object instanceof OpponentPositionList)
            player_op.setDesiredPosition(object.x, object.y);  
    }

So we set his new ‘desired position’. The Gameloop is always calling his ‘updatePosition()’ which should move him to the ‘desired position’. And it works totally smoothly with ‘Player Me’ cause it updates positions constantly, but it freezes when receiving positions ‘not constantly’ from the opponent:

  public void updatePosition(){
		 double xDistance = desiredX - this.x;
		 double yDistance = desiredY - this.y;

		 double distance = Math.sqrt(xDistance * xDistance + yDistance * yDistance);
		 
		   if (distance > 1) {
		       this.x += xDistance * 0.5;
		       this.y += yDistance * 0.5;
		   }else{
			  this.x = Math.round(this.desiredX);
			  this.y = Math.round(this.desiredY);
		   }
	}

The main question is:

If positions come not ‘constantly’, with random 10-50 ms delays how to draw the opponent as smooth as a normal player on this device? With some sort of ‘timed interpolation’ or something?
Any suggestions would be awesome. Thanks!