Skip to content

Instantly share code, notes, and snippets.

@jrg94
Last active June 26, 2021 09:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jrg94/bd3574d9ce15e78d91906e3065643587 to your computer and use it in GitHub Desktop.
Save jrg94/bd3574d9ce15e78d91906e3065643587 to your computer and use it in GitHub Desktop.
Bicycle Simulator
/**
* A bicycle consists of a chain assembly, a handle assembly, and a frame.
* This class can be used to simulate a bicycle.
*/
public class Bicycle {
private BrakeAssembly brakeAssembly;
private ChainAssembly chainAssembly;
private HandleAssembly handleAssembly;
/**
* The default constructor for creating a bike.
*/
public Bicycle(BrakeAssembly brakeAssembly, ChainAssembly chainAssembly, HandleAssembly handleAssembly) {
this.handleAssembly = handleAssembly;
this.chainAssembly = chainAssembly;
this.brakeAssembly = brakeAssembly;
}
/**
* Changes the pedal rate of the bike by some number of cycles.
*
* @param numCycles the number of cycles to accelerate the bike by
*/
public void pedal(double numCycles) {
chainAssembly.accelerate(numCycles);
}
/**
* Changes the brake rate of the bike by some number of cycles.
*
* @param numCycles the number of cycles to brake the bike by
*/
public void brake(double numCycles) {
brakeAssembly.brake(numCycles);
}
/**
* Changes the angle of the front axle the bike.
*
* @param the angle to change the front axle by
*/
public void turn(double angle) {
handleAssembly.turn(angle);
}
public double distance() {
return chainAssembly.distance(chainAssembly.getPedalRate() - brakeAssembly.getBrakeRate());
}
/**
* Reports the state of the bike system as a string.
*/
public String state() {
String pedalRate = String.format("The current pedal rate is: %f.", chainAssembly.getPedalRate());
String brakeRate = String.format("The current brake rate is: %f.", brakeAssembly.getBrakeRate());
String angle = String.format("The current angle is: %f.", handleAssembly.getAngle());
String distance = String.format("During this event, the bike traveled %f meters.", this.distance());
return String.format("%s\n%s\n%s\n%s\n", pedalRate, brakeRate, angle, distance);
}
/**
* Copies the bike to be used as a snapshot of its state.
*/
public Bicycle copy() {
Bicycle copy = new Bicycle(this.brakeAssembly.copy(), this.chainAssembly.copy(), this.handleAssembly.copy());
return copy;
}
}
public class BrakeAssembly {
private double brakeRate; // cycles/event
private static final double MINIMUM_BRAKING = 0;
/**
* The defaul constructor
*/
public BrakeAssembly() {
this.brakeRate = 0;
}
/**
* Increments the brake rate by some constant value.
*
* @param brakeRate the rate of braking in number of cycles
*/
public void brake(double brakeRate) {
if (this.brakeRate + brakeRate < MINIMUM_BRAKING) {
this.brakeRate = MINIMUM_BRAKING;
} else {
this.brakeRate += brakeRate;
}
}
/**
* Returns the brake rate.
*/
public double getBrakeRate() {
return this.brakeRate;
}
/**
* Copies the brake assembly.
*/
public BrakeAssembly copy() {
BrakeAssembly assembly = new BrakeAssembly();
assembly.brake(this.getBrakeRate());
return assembly;
}
}
/**
* A ChainAssembly consists of a pedal, two gears, a tire, and a chain.
* This class can be used to drive the front gear via the pedal method which
* drives the chain that drives the rear gear. The output of the pedal
* method can be used to drive a TireAssembly.
*/
public class ChainAssembly {
private Gear frontGear;
private Gear backGear;
private Tire tire;
private double pedalRate; // cycles/event
private static final double MINIMUM_PEDALING = 0;
private static final double FULL_CYCLE = 360; // degrees
/**
* The default constructor.
*/
public ChainAssembly(Gear frontGear, Gear backGear, Tire tire) {
this.frontGear = frontGear;
this.backGear = backGear;
this.tire = tire;
this.pedalRate = 0;
}
/**
* Drives the main gear by a number of cycles.
*
* @param cycles the number of rotations of the main gear
* @return the number of degrees the back gear was rotated by
*/
private double driveMainGear(double cycles) {
double teeth = this.frontGear.rotateByAngle(cycles * FULL_CYCLE);
double degrees = this.backGear.rotateByTeeth(teeth);
return degrees;
}
/**
* Accelerates the pedal by a number of cycles.
*
* @param cycles the number of rotations of the pedal
*/
public void accelerate(double cycles) {
if (this.pedalRate + cycles < MINIMUM_PEDALING) {
this.pedalRate = MINIMUM_PEDALING;
} else {
this.pedalRate += cycles;
}
}
/**
* Computes the distance covered by the tire given some number
* of pedal cycles. This function doesn't use pedal rate because
* distance is a function of pedal rate AND brake rate.
*
* @param cycles the number of cycles of the pedal for this event
* @return the distance covered by the tire
*/
public double distance(double cycles) {
if (cycles <= 0) {
return 0;
} else {
double degrees = this.driveMainGear(cycles);
double distance = tire.rotate(degrees);
return distance;
}
}
/**
* Returns the pedal rate.
*/
public double getPedalRate() {
return this.pedalRate;
}
/**
* Copies this chain assembly.
*/
public ChainAssembly copy() {
ChainAssembly assembly = new ChainAssembly(this.frontGear.copy(), this.backGear.copy(), this.tire.copy());
assembly.accelerate(this.getPedalRate());
return assembly;
}
}
/**
* A gear is a circular object that consists of
* evenly spaced teeth. All teeth are identical,
* so we don't have to worry about modeling them.
*/
public class Gear {
private int teethCount;
private static final double FULL_CYCLE = 360; // degrees
/**
* The default constructor
*/
public Gear(int teethCount) {
this.teethCount = teethCount;
}
/**
* Rotates the gear by some angle.
*
* @param degrees some angle
* @return the number of teeth rotated
*/
public double rotateByAngle(double degrees) {
return (degrees / FULL_CYCLE) * this.teethCount;
}
/**
* Rotates the gear by some number of teeth.
*
* @param teeth some number of teeth
* @return the degrees the gear was rotated by
*/
public double rotateByTeeth(double teeth) {
return (teeth / this.teethCount) * FULL_CYCLE;
}
/**
* Copies this gear.
*/
public Gear copy() {
Gear copy = new Gear(this.teethCount);
return copy;
}
}
/**
* A Handle Assembly consists of a tire and a set of handles.
*/
public class HandleAssembly {
private Tire tire;
private double angle;
private static final double MAX_ANGLE = 45; // degrees
/**
* Default constructor
*/
public HandleAssembly(Tire tire) {
this.tire = tire;
}
/**
* Turns the handle system by some degrees. There are bounds
* on both ends specified by the MAX_ANGLE constant.
*
* @param degrees some angle
*/
public void turn(double degrees) {
if (this.angle + degrees > MAX_ANGLE) {
this.angle = MAX_ANGLE;
} else if (this.angle + degrees < -MAX_ANGLE) {
this.angle = -MAX_ANGLE;
} else {
this.angle += degrees;
}
}
/**
* Returns the angle of the handle assembly.
*/
public double getAngle() {
return this.angle;
}
/**
* Copies this handle assembly
*/
public HandleAssembly copy() {
HandleAssembly copy = new HandleAssembly(this.tire.copy());
copy.turn(this.getAngle());
return copy;
}
}
import java.util.Scanner;
import java.util.ArrayList;
/**
* The simulator is a utility class that offers simulation
* funtions for bikes.
*/
public class Simulator {
/**
* Processes a command on the bike.
*/
public static void processCommand(String command, Bicycle bike) {
switch (command.toLowerCase()) {
case "brake": bike.brake(1.0); break;
case "release": bike.brake(-1.0); break;
case "pedal": bike.pedal(0.5); break;
case "coast": bike.pedal(-0.5); break;
case "left": bike.turn(-5); break;
case "right": bike.turn(5); break;
default: // Do nothing
}
}
/**
* Issues the directions for the simulator.
*/
private static void simulatorDirections() {
System.out.println("-----------------------------------------------");
System.out.println("Simulation has begun!");
System.out.println("This simulator is event driven.");
System.out.println("At each iteration, you'll have the choice to [pedal/coast], turn [left/right], [brake/release], or do nothing.");
System.out.println("Selecting [pedal/coast] will change the rate of pedaling by +/-.5 cycles/event.");
System.out.println("Selecting [brake/release] will icrement the rate of braking by +/-1.0 cycles/event.");
System.out.println("Selecting [left/right] will change the angle of the front wheel by +/-5 degrees.");
System.out.println("When you're finished, use the [exit] command");
System.out.println("-----------------------------------------------");
}
/**
* A fun method which can be used to analyze the trajectory of
* the bike over all the events.
*/
public static void analyzeEvents(ArrayList<Bicycle> events) {
double distance = 0;
for (Bicycle event : events) {
distance += event.distance();
}
System.out.printf("In total, the bike traveled %f meters.\n", distance);
}
/**
* Runs the actual simulator given the bike.
*/
public static void runSimulator(Bicycle bike, Scanner in) {
simulatorDirections();
ArrayList<Bicycle> events = new ArrayList<Bicycle>();
events.add(bike.copy());
System.out.print("Issue a command:");
String command = in.nextLine();
while (!command.equalsIgnoreCase("exit")) {
processCommand(command, bike);
System.out.println(bike.state());
events.add(bike.copy());
System.out.print("Issue a command:");
command = in.nextLine();
}
analyzeEvents(events);
}
/**
* Runs the prompts to generate a bike
*
* @param in the Scanner to be used for prompting
* @return the bike
*/
public static Bicycle buildBike(Scanner in) {
System.out.println("Welcome to the bike simulator! Let's build a bike.");
System.out.print("Please choose to [run] the default bike or [build] your own:");
String choice = in.nextLine();
while (choice.isEmpty() && !choice.equalsIgnoreCase("run") && !choice.equalsIgnoreCase("build")) {
System.out.println("Sorry, your choice was not valid.");
System.out.print("Did you want to [run] the default bike or [build] your own?");
choice = in.nextLine();
}
if (choice.equalsIgnoreCase("run")) {
return buildDefaultBike();
} else {
return buildCustomBike(in);
}
}
/**
* Builds a custom bike (no error handling).
*
* @param in the scanner to request user input
* @return the user's custom bike
*/
public static Bicycle buildCustomBike(Scanner in) {
System.out.print("Choose a number of teeth for the main gear:");
Gear main = new Gear(in.nextInt());
System.out.print("Choose a number of teeth for the rear gear:");
Gear rear = new Gear(in.nextInt());
System.out.print("Choose a radius for your tires:");
double radius = in.nextDouble();
Tire front = new Tire(radius);
Tire back = new Tire(radius);
ChainAssembly chainAssembly = new ChainAssembly(main, rear, back);
HandleAssembly handleAssembly = new HandleAssembly(front);
BrakeAssembly brakes = new BrakeAssembly();
return new Bicycle(brakes, chainAssembly, handleAssembly);
}
/**
* Builds a default bicycle that has a 2.75 gear ratio
* and .3 meter radius tires.
*
* @return a default bike
*/
public static Bicycle buildDefaultBike() {
Gear main = new Gear(44);
Gear rear = new Gear(16);
Tire front = new Tire(.3);
Tire back = new Tire(.3);
ChainAssembly chainAssembly = new ChainAssembly(main, rear, back);
HandleAssembly handleAssembly = new HandleAssembly(front);
BrakeAssembly brakes = new BrakeAssembly();
return new Bicycle(brakes, chainAssembly, handleAssembly);
}
/**
* The main method which launches the simulator.
*/
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Bicycle bike = buildBike(in);
runSimulator(bike, in);
}
}
/**
* The Tire class represents a single tire system.
*/
public class Tire {
private double radius; // meters
private static final double FULL_CYCLE = 360; // degrees
/**
* The default constructor
*/
public Tire(double radius) {
this.radius = radius;
}
/**
* Rotates the tire by some angle.
*
* @param degrees some angle
* @return the linear distance traveled by the tire
*/
public double rotate(double degrees) {
double circumference = 2 * Math.PI * this.radius;
double distance = circumference * (degrees / FULL_CYCLE);
return distance;
}
/**
* Copies this tire.
*/
public Tire copy() {
Tire copy = new Tire(this.radius);
return copy;
}
}
@jrg94
Copy link
Author

jrg94 commented Apr 10, 2019

Potential features:

  • Introduce an array of gears to the chain assembly
    • Allows for gear switching like on a mountain bike
  • Model the pedal which introduces force calculations
    • Show the difference between pedals of various radii
    • Show the difference between gears of different radii

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment