Info

Programmer by day, artist by night

Archive for

From the Course Programming Methodology CS 106A Offered at Stanford University

Having spent more hours than I am willing to admit, finally solved Karel the Robot Problem 3 (from Assignment 1).

Please don’t look at the solution until you have solved it yourself. Otherwise you will be depriving yourself of a cool mental accomplishment.

Having said that, here’s my train of thoughts as I was trying to solve the problem:

1. Thought that ignoring the checkerboard might lead to a simpler solution. Think linearly about Karel dropping beepers alternatively on each move “over a single horizontal line” till the end is reached. Later turn the horizontal line into a checkerboard. Made some progress but the solution started getting increasingly complex.

2. Realized that the core of the problem is really the turns Karel needs to make while traversing through the checkerboard. Dropping beepers at alternate locations isn’t the real problem. So I ignored the beeper issue. This lead to a cleaner solution.

3. Karel’s movement itself shouldn’t be in any if/else control statement. It makes a mess of the code because of too many if/else checks. Karel should just move forward. Then figure out which direction it should face. You can see this behavior in lines 26-29 below. Lines 31-49 shows the logic behind setting Karel’s direction.

4. Finally the beeper code is in line 17, 19. Karel moves twice in each iteration of the while loop. Line 19 checks for even-count columns in Karel’s world. Line 21 puts a last beeper for odd-count column worlds.

5. Line 15 checks for a single-count column world.

I Googled for other solutions to compare after solving the problem. Feeling happy that the solution below is 50 lines of code. If you can think of a simpler solution then please let me know.

/*
 * File: CheckerboardKarel.java
 * ----------------------------
 * When you finish writing it, the CheckerboardKarel class should draw
 * a checkerboard using beepers, as described in Assignment 1.  You
 * should make sure that your program works for all of the sample
 * worlds supplied in the starter folder.
 */

import stanford.karel.*;

public class CheckerboardKarel extends SuperKarel {

	public void run() {
		if (frontIsBlocked()) turnLeft();		
		while (frontIsClear()) {
			if (noBeepersPresent()) putBeeper();
			moveKarelForward();
			if (frontIsClear()) {
				moveKarelForward();
				if (noBeepersPresent()) putBeeper();
			}
		}
	}
	
	private void moveKarelForward() {
		move();
		setKarelsDirection();
	}
	
	private void setKarelsDirection() {
		if (facingEast()) {
			if (frontIsBlocked()) {
				turnLeft();
			}
		} else if (facingWest()) {
			if (frontIsBlocked()) {
				turnRight();
			}
		} else if (facingNorth()) {
			if (rightIsBlocked()) {
				if (leftIsClear()) {
					turnLeft();	
				}				
			} else if (leftIsBlocked()) {
				turnRight();
			}
		}
	}
}

Testing Checkerboard Karel in different worlds:

Platform: iOS 8.x or later
Device: iPhone
Language: Swift
Motivation: when user unplugs their headphones, you wan’t to stop playing audio (iTunes like behavior) in your app

The following code snippet shows how to detect if headphones are plugged in (when the app starts or is woken up). The println() command below shows the state of the headphone connection.

let currentRoute = AVAudioSession.sharedInstance().currentRoute
if currentRoute.outputs != nil {
    for description in currentRoute.outputs {
        if description.portType == AVAudioSessionPortHeadphones {
            println("headphone plugged in")
        } else {
            println("headphone pulled out")
        }
    }
} else {
    println("requires connection to device")
}

In Line 4 above, we are checking if portType equals AVAudioSessionPortHeadphones. The full list of output port types are listed below:

  • AVAudioSessionPortLineOut
  • AVAudioSessionPortHeadphones
  • AVAudioSessionPortBluetoothA2DP
  • AVAudioSessionPortBuiltInReceiver
  • AVAudioSessionPortBuiltInSpeaker
  • AVAudioSessionPortHDMI
  • AVAudioSessionPortAirPlay
  • AVAudioSessionPortBluetoothLE

However while the app is running you can’t use the above code snippet to detect active changes in headphones connection. You will need to setup a notification observer for AVAudioSessionRouteChangeNotification in your app with the code below.

NSNotificationCenter.defaultCenter().addObserver(
    self,
    selector: "audioRouteChangeListener:",
    name: AVAudioSessionRouteChangeNotification,
    object: nil)

In line 3 above, the audioRouteChangeListener: is the method that will get notified once a change in headphones connection occurs. The println() statements below show the change in headphones connection to the device.

dynamic private func audioRouteChangeListener(notification:NSNotification) {
    let audioRouteChangeReason = notification.userInfo![AVAudioSessionRouteChangeReasonKey] as UInt

    switch audioRouteChangeReason {
    case AVAudioSessionRouteChangeReason.NewDeviceAvailable.rawValue:
        println("headphone plugged in")
    case AVAudioSessionRouteChangeReason.OldDeviceUnavailable.rawValue:
        println("headphone pulled out")
    default:
        break
    }
}

In line 4 above, we are checking switch-case values for AVAudioSessionRouteChangeReasonKey (identified by the variable audioRouteChangeReason). We are interested in two cases, NewDeviceAvailable (headphones plugged in) and OldDeviceUnavailable (headphone pulled out). The full list of cases are provided below:

  • Unknown
  • NewDeviceAvailable
  • OldDeviceUnavailable
  • CategoryChange
  • Override
  • WakeFromSleep
  • NoSuitableRouteForCategory
  • RouteConfigurationChange

A demo app utilizing the code above can be found here:
https://github.com/sanjibahmad/Is-Headphone-Plugged-In

Here’s a video showing how the app works:

 

Thanks to Udacity for the motivation to write this.