// SatelliteRushTest // By Thomas Padron-McCarthy (padrone@lysator.liu.se) // Latest change to this file: August 7, 2008 // No copyright. No warranty. No nothing. Share and enjoy! import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import javax.microedition.location.*; import java.util.Timer; import java.util.TimerTask; import java.util.Random; import java.util.Date; import java.util.Vector; import java.util.Enumeration; import henson.midp.Float11; // Some user-configurable game preferences class Preferences { public final double PADDLE_WIDTH_DEFAULT = 15.0; public final double BASE_SPEED_DEFAULT = 2.0; public final boolean SHOW_PATH_DEFAULT = true; public final boolean SATELLITE_MODE_DEFAULT = true; private double paddle_width_percent; // As a percentage of the screen width, 1..100 private double paddle_width_pixels; // In pixels, set from the paddle_width_percent private double base_speed_percent; // As a percentage of the screen diagonal per second private double base_speed_pixels; // In pixels per second, set from the base_speed_percent private boolean show_path; private double screen_width; private double screen_diagonal; boolean satellite_mode; public Preferences(int screen_width, int screen_height) { if (screen_width <= 0 || screen_height <= 0) throw new IllegalArgumentException("Impossible screen size"); this.screen_width = screen_width; this.screen_diagonal = Math.sqrt(screen_width * screen_width + screen_height * screen_height); resetDefaults(); } public void setPaddleWidth(double paddle_width_percent) { this.paddle_width_percent = paddle_width_percent; this.paddle_width_pixels = paddle_width_percent / 100.0 * screen_width; } public void setBaseSpeed(double base_speed_percent) { this.base_speed_percent = base_speed_percent; this.base_speed_pixels = base_speed_percent / 100.0 * screen_diagonal; } public double getPaddleWidthPercent() { return paddle_width_percent; } public double getPaddleWidthPixels() { return paddle_width_pixels; } public double getBaseSpeedPercent() { return base_speed_percent; } public double getBaseSpeedPixels() { return base_speed_pixels; } public void setShowPath(boolean show_path) { this.show_path = show_path; } public boolean getShowPath() { return show_path; } public void setSatelliteMode(boolean satellite_mode) { this.satellite_mode = satellite_mode; } public boolean getSatelliteMode() { return satellite_mode; } public void resetDefaults() { setPaddleWidth(PADDLE_WIDTH_DEFAULT); setBaseSpeed(BASE_SPEED_DEFAULT); setShowPath(SHOW_PATH_DEFAULT); setSatelliteMode(SATELLITE_MODE_DEFAULT); } } // Preferences // A Form that shows, and lets the user edit, some configuration options class PreferencesForm extends Form implements CommandListener { private Preferences preferences; private Display display; private Displayable window_under; private RushCanvas rush_canvas; private Coordinates current_coordinates; private Coordinates left_corner; private Coordinates right_corner; private double baseline; private double baseline_position; private LocationProvider location_provider; private Location current_location; private Command play_command = new Command("Play", Command.OK, 1); private Command save_command = new Command("Save", Command.SCREEN, 2); private Command start_command = new Command("Start listening", Command.SCREEN, 3); private Command left_command = new Command("Left corner", Command.SCREEN, 4); private Command right_command = new Command("Right corner", Command.SCREEN, 5); private Command defaults_command = new Command("Restore defaults", Command.SCREEN, 6); private Command stop_command = new Command("Stop listening", Command.STOP, 7); private Command edit_criteria_command = new Command("Edit Criteria", Command.SCREEN, 8); private Command about_command = new Command("About", Command.SCREEN, 9); private Command help_command = new Command("Help", Command.SCREEN, 9); private TextField paddle_item; private TextField speed_item; private ChoiceGroup path_item; private ChoiceGroup mode_item; private StringItem distance_item; private StringItem position_item; private StringItem current_coordinates_item; private StringItem left_item; private StringItem right_item; private boolean is_listening = false; private boolean waiting_for_left = false; private boolean waiting_for_right = false; public PreferencesForm(Preferences preferences, Display display, Displayable window_under, RushCanvas rush_canvas) { super("Preferences"); this.preferences = preferences; this.display = display; this.window_under = window_under; this.rush_canvas = rush_canvas; paddle_item = new TextField("Paddle size (in percent of screen width)", "", 10, TextField.DECIMAL); paddle_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(paddle_item); speed_item = new TextField("Base speed (in percent of screen diagonal)", "", 10, TextField.DECIMAL); speed_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(speed_item); path_item = new ChoiceGroup("Show ball path:", ChoiceGroup.MULTIPLE); path_item.append("Yes, show", null); path_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(path_item); mode_item = new ChoiceGroup("Mode:", ChoiceGroup.EXCLUSIVE); mode_item.append("Satellite mode", null); mode_item.append("Boring mode", null); mode_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(mode_item); distance_item = new StringItem("Baseline length: ", ""); distance_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(distance_item); position_item = new StringItem("Baseline position: ", ""); position_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(position_item); current_coordinates_item = new StringItem("Current: ", ""); current_coordinates_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(current_coordinates_item); left_item = new StringItem("Left corner: ", ""); left_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(left_item); right_item = new StringItem("Right corner: ", ""); right_item.setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_NEWLINE_AFTER); append(right_item); load_values(); update_locations(); addCommand(play_command); addCommand(save_command); addCommand(defaults_command); addCommand(left_command); addCommand(right_command); addCommand(start_command); addCommand(stop_command); addCommand(edit_criteria_command); addCommand(help_command); addCommand(about_command); setCommandListener(this); } private String coordinates2string(Coordinates coordinates) { double lat = coordinates.getLatitude(); double lon = coordinates.getLongitude(); String lat_string = Coordinates.convert(lat, Coordinates.DD_MM_SS); String lon_string = Coordinates.convert(lon, Coordinates.DD_MM_SS); String value = "Lat. " + lat_string + ", long. " + lon_string; return value; } // Do this right! This is just an approximation, since I forget how to do it. public double OLD_baseline_position() { double distance2left = left_corner.distance(current_coordinates); double distance2right = right_corner.distance(current_coordinates); if (distance2right > baseline) return 0.0; else if (distance2left > baseline) return 1.0; else { return distance2left / baseline; } } public double get_baseline_position() { double distance2left = left_corner.distance(current_coordinates); if (distance2left == 0) { // System.out.println("get_baseline_position() = 0"); return 0; } double distance2right = right_corner.distance(current_coordinates); if (distance2right == 0) { // System.out.println("get_baseline_position() = 1"); return 1; } double cosA = (distance2left * distance2left + baseline * baseline - distance2right * distance2right) / (2 * distance2left * baseline); double distance2left_along_baseline = distance2left * cosA; if (distance2left_along_baseline < 0) { // System.out.println("get_baseline_position() = 0"); return 0; } else if (distance2left_along_baseline > baseline) { // System.out.println("get_baseline_position() = 1"); return 1; } else { // System.out.println("get_baseline_position() = " + distance2left_along_baseline / baseline); return distance2left_along_baseline / baseline; } } static private double round2(double x) { return ((int)(x * 100)) / 100.0; } private void update_locations() { if (left_corner == null || right_corner == null) distance_item.setText("n/a"); else distance_item.setText("" + round2(baseline) + " m"); if (current_coordinates == null || left_corner == null || right_corner == null) position_item.setText("n/a"); else { baseline_position = get_baseline_position(); position_item.setText("" + round2(baseline_position * 100) + " %"); } if (current_coordinates == null) current_coordinates_item.setText("null"); else current_coordinates_item.setText(coordinates2string(current_coordinates)); if (waiting_for_left) ; else if (left_corner == null) left_item.setText("null"); else left_item.setText(coordinates2string(left_corner)); if (waiting_for_right) ; else if (right_corner == null) right_item.setText("null"); else right_item.setText(coordinates2string(right_corner)); } // update_locations public void commandAction(Command c, Displayable s) { if (c == play_command) { if (preferences.getSatelliteMode() && (left_corner == null || right_corner == null)) MyAlert.show("Corner(s) missing", "In satellite mode, you must first set the left and the right corners, using GPS.", display); else if (preferences.getSatelliteMode() && !is_listening) MyAlert.show("Not listening", "In satellite mode, you must first start listening to GPS location updates.", display); else if (preferences.getSatelliteMode() && baseline < 30) MyAlert.show("Short baseline", "Your baseline is just " + round2(baseline) + " meters long. " + "I suggest a baseline of 100 meters or longer.", display); else display.setCurrent(window_under); } else if (c == save_command) { save_values(); } else if (c == defaults_command) { load_defaults(); } else if (c == left_command) { left(); } else if (c == right_command) { right(); } else if (c == start_command) { start_listening(); } else if (c == stop_command) { stop_listening(); } else if (c == edit_criteria_command) { edit_criteria(); } else if (c == about_command) { rush_canvas.about(); } else if (c == help_command) { rush_canvas.help(); } else { MyAlert.show("Internal error", "This can't happen.", display); } } private void save_values() { try { preferences.setPaddleWidth(Double.parseDouble(paddle_item.getString())); preferences.setBaseSpeed(Double.parseDouble(speed_item.getString())); boolean[] flag_array = new boolean[1]; path_item.getSelectedFlags(flag_array); preferences.setShowPath(flag_array[0]); flag_array = new boolean[2]; mode_item.getSelectedFlags(flag_array); preferences.setSatelliteMode(flag_array[0]); } catch (NumberFormatException e) { MyAlert.show("NumberFormatException", "Not the right kind of number. This can't happen.", display); } catch (Throwable e) { MyAlert.show("Throwable", "Some other error. Try again.", display); } } private void load_values() { paddle_item.setString("" + preferences.getPaddleWidthPercent()); speed_item.setString("" + preferences.getBaseSpeedPercent()); path_item.setSelectedIndex(0, preferences.getShowPath()); if (preferences.getSatelliteMode()) mode_item.setSelectedIndex(0, true); else mode_item.setSelectedIndex(1, true); } private void load_defaults() { preferences.resetDefaults(); load_values(); } private void left() { if (!is_listening) { MyAlert.show("Not listening", "Before you can set a corner, you must start listening to GPS location updates.", display); return; } Runnable r = new Runnable() { public void run() { try { if (location_provider == null) location_provider = LocationProvider.getInstance(criteria); QualifiedCoordinates qc; double accuracy; do { current_location = location_provider.getLocation(-1); qc = current_location.getQualifiedCoordinates(); current_coordinates = qc; accuracy = qc.getHorizontalAccuracy(); if (accuracy > 10) { left_item.setText("accuracy = " + accuracy + ", trying again..."); } } while (accuracy > 10); left_corner = current_coordinates; if (right_corner != null) baseline = left_corner.distance(right_corner); } catch(LocationException e) { MyAlert.show("LocationException", "There was a problem when trying to get a LocationProvider or a Location: " + e.toString(), display); } catch(InterruptedException e) { MyAlert.show("InterruptedException", "Reached timeout when trying to get a LocationProvider or a Location: " + e.toString(), display); } catch(Throwable e) { MyAlert.show("Throwable", "Some other error when trying to get a LocationProvider or a Location: " + e.toString(), display); } finally { update_locations(); } } // run }; // Runnable left_item.setText("waiting..."); if (is_listening) { waiting_for_left = true; } else { Thread t = new Thread(r); t.start(); } } // left private void right() { if (!is_listening) { MyAlert.show("Not listening", "Before you can set a corner, you must start listening to GPS location updates.", display); return; } Runnable r = new Runnable() { public void run() { try { if (location_provider == null) location_provider = LocationProvider.getInstance(criteria); QualifiedCoordinates qc; double accuracy; do { current_location = location_provider.getLocation(-1); qc = current_location.getQualifiedCoordinates(); current_coordinates = qc; accuracy = qc.getHorizontalAccuracy(); if (accuracy > 10) { right_item.setText("accuracy = " + accuracy + ", trying again..."); } } while (accuracy > 10); right_corner = current_coordinates; if (left_corner != null) baseline = left_corner.distance(right_corner); } catch(LocationException e) { MyAlert.show("LocationException", "There was a problem when trying to get a LocationProvider or a Location: " + e.toString(), display); } catch(InterruptedException e) { MyAlert.show("InterruptedException", "Reached timeout when trying to get a LocationProvider or a Location: " + e.toString(), display); } catch(Throwable e) { MyAlert.show("Throwable", "Some other error when trying to get a LocationProvider or a Location: " + e.toString(), display); } finally { update_locations(); } } // run }; // Runnable right_item.setText("waiting..."); if (is_listening) { waiting_for_right = true; } else { Thread t = new Thread(r); t.start(); } } // right private void get_position() { Runnable r = new Runnable() { public void run() { try { if (location_provider == null) location_provider = LocationProvider.getInstance(criteria); current_location = location_provider.getLocation(-1); if (current_location.isValid()) { QualifiedCoordinates qc = current_location.getQualifiedCoordinates(); current_coordinates = qc; } } catch(LocationException e) { MyAlert.show("LocationException", "There was a problem when trying to get a LocationProvider or a Location: " + e.toString(), display); } catch(InterruptedException e) { MyAlert.show("InterruptedException", "Reached timeout when trying to get a LocationProvider or a Location: " + e.toString(), display); } catch(Throwable e) { MyAlert.show("Throwable", "Some other error when trying to get a LocationProvider or a Location: " + e.toString(), display); } finally { update_locations(); } } // run }; // Runnable current_coordinates_item.setText("waiting..."); if (!is_listening) { Thread t = new Thread(r); t.start(); } } // get_position class MyLocationListener implements LocationListener { public MyLocationListener() { } public void locationUpdated(LocationProvider provider, Location location) { System.out.println("PRINTLN: " + "*** New location:"); if (location == null || !location.isValid()) { System.out.println("PRINTLN: " + "invalid location (null or not valid)"); return; } current_location = location; current_coordinates = current_location.getQualifiedCoordinates(); if (waiting_for_left) { QualifiedCoordinates qc = current_location.getQualifiedCoordinates(); double accuracy = qc.getHorizontalAccuracy(); if (accuracy > 10) { left_item.setText("accuracy = " + accuracy + ", trying again..."); } else { waiting_for_left = false; left_corner = current_coordinates; if (right_corner != null) baseline = left_corner.distance(right_corner); } } if (waiting_for_right) { QualifiedCoordinates qc = current_location.getQualifiedCoordinates(); double accuracy = qc.getHorizontalAccuracy(); if (accuracy > 10) { right_item.setText("accuracy = " + accuracy + ", trying again..."); } else { waiting_for_right = false; right_corner = current_coordinates; if (left_corner != null) baseline = left_corner.distance(right_corner); } } update_locations(); double lat = current_coordinates.getLatitude(); double lon = current_coordinates.getLongitude(); System.out.println("PRINTLN: " + " Latitude = " + lat); System.out.println("PRINTLN: " + " Longitude = " + lon); System.out.println("PRINTLN: " + "---"); } public void providerStateChanged(LocationProvider provider, int newState) { System.out.println("PRINTLN: " + "*** New provider state:"); System.out.println("PRINTLN: " + "" + newState); } } private void start_listening() { if (is_listening) { MyAlert.show("Already listening", "This program already is listening to GPS location updates.", display); return; } Runnable r = new Runnable() { public void run() { try { if (location_provider == null) location_provider = LocationProvider.getInstance(criteria); LocationListener listener = new MyLocationListener(); location_provider.setLocationListener(listener, -1, -1, -1); is_listening = true; } catch(LocationException e) { MyAlert.show("LocationException", "There was a problem when trying to get a LocationProvider or starting to listen: " + e.toString(), display); } catch(Throwable e) { MyAlert.show("Throwable", "Some other error when trying to get a LocationProvider or starting to listen: " + e.toString(), display); } } // run }; // Runnable Thread t = new Thread(r); t.start(); } private void stop_listening() { if (!is_listening) { MyAlert.show("Not listening", "There is no need to stop listening when you haven't started!", display); return; } Runnable r = new Runnable() { public void run() { try { if (location_provider != null) location_provider.setLocationListener(null, -1, -1, -1); is_listening = false; } catch(Throwable e) { MyAlert.show("Throwable", "Some other error when trying to get a LocationProvider or starting to listen: " + e.toString(), display); } } // run }; // Runnable Thread t = new Thread(r); t.start(); } // stop_listening private Criteria criteria = new Criteria(); private CriteriaForm criteria_form = null; private void edit_criteria() { if (criteria_form == null) criteria_form = new CriteriaForm(criteria, display, this); display.setCurrent(criteria_form); } } // PreferencesForm // An Alert, with an image and a static show method class MyAlert extends Alert { private static Image image = null; public MyAlert(String title, String text) { super(title, text, null, null); if (image == null) { try { image = Image.createImage("/warning.png"); } catch (java.io.IOException e) { // Ok, the image didn't work. Ignore it. } } setImage(image); setTimeout(Alert.FOREVER); } public static void show(String title, String text, Display display) { MyAlert alert = new MyAlert(title, text); display.setCurrent(alert); } } // Used by YesOrNoDialogue to report the user's answer to the caller interface YesOrNoCallback { public void gotAnswer(boolean answer); } // A dialogue that waits for a "yes" or "no" answer from the user class YesOrNoDialogue extends Form implements CommandListener { private Command yes_command = new Command("Yes", Command.OK, 0); private Command no_command = new Command("No", Command.CANCEL, 0); YesOrNoCallback callback; private Display display; public YesOrNoDialogue(String question, YesOrNoCallback callback, Display display) { super("Question"); this.callback = callback; this.display = display; addCommand(yes_command); addCommand(no_command); setCommandListener(this); // StringItem question_item = new StringItem("Question:", question); StringItem question_item = new StringItem(null, question); question_item.setLayout(Item.LAYOUT_CENTER | Item.LAYOUT_VCENTER); append(question_item); } public void commandAction(Command c, Displayable s) { if (c == no_command) { callback.gotAnswer(false); } else if (c == yes_command) { callback.gotAnswer(true); } else { MyAlert.show("Internal error", "Internal error. This can't happen.", display); } } } // YesOrNoDialogue // A Form that shows a help text for this program class HelpForm extends Form implements CommandListener { private Command back_command = new Command("Back", Command.BACK, 0); private Display display; private Displayable window_under; public HelpForm(Display display, Displayable window_under) { super("Instructions"); this.display = display; this.window_under = window_under; addCommand(back_command); setCommandListener(this); String help_text = "Short instructions:" + "\n" + "\n" + "Start by selecting 'Satellite mode' or 'Boring mode'. If using Satellite mode, then go to the two corners of the baseline and give commands to set the left corner and the right corner. Then save the configuration, and start playing. Run fast!" + "\n" + "\n" + "More detailed instructions for Satellite mode:" + "\n" + "\n" + "1. Find an open space. Avoid trees and tall buildings, since they block the GPS satellites. You should be able to move along a straight line for 50 to 150 meters (yards). Preferably without crossing roads and railroad tracks." + "\n" + "2. Start the program." + "\n" + "3. In the 'Preferences' screen, you might want to change the speed of the ball or the size of the paddle." + "\n" + "4. Give the command 'Start listening'. The device might show a dialogue to confirm that the program can use the GPS." + "\n" + "5. Go to one end of the line you want to use as baseline." + "\n" + "6. Give the command 'Left corner'." + "\n" + "7. Stay there, until the text after 'Left corner' in the 'Preferences' screen changes from 'null' to coordinates with a latitude and a longitude." + "\n" + "8. Go to the other end of your baseline." + "\n" + "9. Give the command 'Right corner'." + "\n" + "10. Stay there, until the text after 'Right corner' changes from 'null' to some coordinates." + "\n" + "11. Give the command 'Save'." + "\n" + "12. You might want to move towards the middle of the baseline before starting to play." + "\n" + "13. Give the command 'Play'." + "\n" + "14. Run!" + "\n" + "\n" + "Tips:" + "\n" + "\n" + "1. Use a long baseline, at least 100 meters (yards). The error in GPS locations can be (much) more than 10 meters, and the longer the baseline, the less this error matters." + "\n" + "2. Hurry to (approximately) the right position to meet the ball, and wait there. It can take a number of seconds for the GPS to update the location."; StringItem help_item = new StringItem(null, help_text); append(help_item); } public void commandAction(Command c, Displayable s) { if (c == back_command) { display.setCurrent(window_under); } else { MyAlert.show("Internal error", "Internal error. This can't happen.", display); } } } // HelpForm // A Form that shows some information about this program class AboutForm extends Form implements CommandListener { private Command back_command = new Command("Back", Command.BACK, 0); private Display display; private Displayable window_under; public AboutForm(Display display, Displayable window_under) { super("About SatelliteRushTest"); this.display = display; this.window_under = window_under; addCommand(back_command); setCommandListener(this); String info_text = "This is SatelliteRushTest, a more physical version of an old game idea." + " SatelliteRushTest is a technical test version, so don't expect it to be very good-looking or easy to use." + " The real game will be called SatelliteRush." + "\n" + "This program uses henson.midp.Float by Nikolay Klimchuk (henson.newmail.ru)."; StringItem info_item = new StringItem(null, info_text); append(info_item); StringItem version_item = new StringItem("Version:", "0.2 (August 7, 2008)"); append(version_item); StringItem author_item = new StringItem("Author:", "Thomas Padron-McCarthy"); append(author_item); StringItem mail_item = new StringItem("E-mail:", "padrone@lysator.liu.se"); append(mail_item); } public void commandAction(Command c, Displayable s) { if (c == back_command) { display.setCurrent(window_under); } else { MyAlert.show("Internal error", "Internal error. This can't happen.", display); } } } // AboutForm // A Form that shows, and lets the user edit, data about a Criteria object class CriteriaForm extends Form implements CommandListener { private Command save_command = new Command("Save", Command.OK, 0); private Command back_command = new Command("Back", Command.BACK, 0); private Command reset_command = new Command("Reset", Command.SCREEN, 0); private Criteria criteria; private Display display; private Displayable window_under; private TextField h_a_item; private TextField v_a_item; private TextField time_item; private ChoiceGroup power_item; private ChoiceGroup cost_item; private ChoiceGroup speed_item; private ChoiceGroup altitude_item; private ChoiceGroup address_item; public CriteriaForm(Criteria criteria, Display display, Displayable window_under) { super("Criteria"); this.criteria = criteria; this.display = display; this.window_under = window_under; h_a_item = new TextField("Horizontal accuracy (in meters)", "", 30, TextField.NUMERIC); append(h_a_item); v_a_item = new TextField("Veritical accuracy (in meters):", "", 30, TextField.NUMERIC); append(v_a_item); time_item = new TextField("Preferred response time (in seconds):", "", 30, TextField.DECIMAL); append(time_item); power_item = new ChoiceGroup("Power consumption:", ChoiceGroup.EXCLUSIVE); power_item.append("No requirements", null); power_item.append("Only low power consumption allowed", null); power_item.append("Average power consumption allowed", null); power_item.append("High power consumption allowed", null); append(power_item); cost_item = new ChoiceGroup("Cost allowed:", ChoiceGroup.MULTIPLE); cost_item.append("Yes, allowed", null); append(cost_item); speed_item = new ChoiceGroup("Speed and course required:", ChoiceGroup.MULTIPLE); speed_item.append("Yes, required", null); append(speed_item); altitude_item = new ChoiceGroup("Altitude required:", ChoiceGroup.MULTIPLE); altitude_item.append("Yes, required", null); append(altitude_item); address_item = new ChoiceGroup("Address info required:", ChoiceGroup.MULTIPLE); address_item.append("Yes, required", null); append(address_item); load_values(); addCommand(save_command); addCommand(back_command); addCommand(reset_command); setCommandListener(this); } public void setCriteria(Criteria criteria) { this.criteria = criteria; load_values(); } public void commandAction(Command c, Displayable s) { if (c == save_command) { save_values(); display.setCurrent(window_under); } else if (c == back_command) { display.setCurrent(window_under); } else if (c == reset_command) { load_values(); } else { MyAlert.show("Internal error", "This can't happen.", display); } } public void load_values() { h_a_item.setString("" + criteria.getHorizontalAccuracy()); v_a_item.setString("" + criteria.getVerticalAccuracy()); time_item.setString("" + criteria.getPreferredResponseTime() / 1000.0); int power = criteria.getPreferredPowerConsumption(); switch (power) { case Criteria.NO_REQUIREMENT: power_item.setSelectedIndex(0, true); break; case Criteria.POWER_USAGE_LOW: power_item.setSelectedIndex(1, true); break; case Criteria.POWER_USAGE_MEDIUM: power_item.setSelectedIndex(2, true); break; case Criteria.POWER_USAGE_HIGH: power_item.setSelectedIndex(3, true); break; default: MyAlert.show("Internal error", "This can't happen.", display); break; } cost_item.setSelectedIndex(0, criteria.isAllowedToCost()); speed_item.setSelectedIndex(0, criteria.isSpeedAndCourseRequired()); altitude_item.setSelectedIndex(0, criteria.isAltitudeRequired()); address_item.setSelectedIndex(0, criteria.isAddressInfoRequired()); } public void save_values() { try { criteria.setHorizontalAccuracy(Integer.parseInt(h_a_item.getString())); criteria.setVerticalAccuracy(Integer.parseInt(v_a_item.getString())); criteria.setPreferredResponseTime((int)(Double.parseDouble(time_item.getString()) * 1000)); switch (power_item.getSelectedIndex()) { case 0: criteria.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT); break; case 1: criteria.setPreferredPowerConsumption(Criteria.POWER_USAGE_LOW); break; case 2: criteria.setPreferredPowerConsumption(Criteria.POWER_USAGE_MEDIUM); break; case 3: criteria.setPreferredPowerConsumption(Criteria.POWER_USAGE_HIGH); break; default: MyAlert.show("Internal error", "This can't happen.", display); break; } boolean[] flag_array = new boolean[1]; cost_item.getSelectedFlags(flag_array); criteria.setCostAllowed(flag_array[0]); speed_item.getSelectedFlags(flag_array); criteria.setSpeedAndCourseRequired(flag_array[0]); altitude_item.getSelectedFlags(flag_array); criteria.setAltitudeRequired(flag_array[0]); address_item.getSelectedFlags(flag_array); criteria.setAddressInfoRequired(flag_array[0]); } catch (NumberFormatException e) { MyAlert.show("NumberFormatException", "Not the right kind of number. This can't happen.", display); } catch (Throwable e) { MyAlert.show("Throwable", "Some other error. Try again.", display); } } } // CriteriaForm // The SatelliteRushTest midlet class itself public class SatelliteRushTest extends MIDlet implements CommandListener { RushCanvas canvas; private Display display; private Command exit_command = new Command("Avsluta", Command.EXIT, 0); public SatelliteRushTest() { } public void destroyApp(boolean unconditional) { } public void pauseApp() { } public void startApp() { display = Display.getDisplay(this); canvas = new RushCanvas(this); // display.setCurrent(canvas); canvas.edit_preferences(); } public void finished(long winning_time_ms) { Alert textrutan = new Alert("Du vann!", "Tid: " + (winning_time_ms + 500) / 1000 + " sekunder", null, AlertType.INFO); textrutan.setTimeout(Alert.FOREVER); textrutan.addCommand(exit_command); textrutan.setCommandListener(this); display.setCurrent(textrutan); } public void commandAction(Command c, Displayable d) { if (c == exit_command) { destroyApp(true); notifyDestroyed(); return; } } } // SatelliteRushTest class Brick { public int x, y; public int radius; public int color; } class RushCanvas extends GameCanvas implements CommandListener, YesOrNoCallback { private Command exit_command = new Command("Exit", Command.EXIT, 4); private Command about_command = new Command("About", Command.SCREEN, 3); private Command help_command = new Command("Help", Command.SCREEN, 3); private Command preferences_command = new Command("Configuration", Command.SCREEN, 2); private Display display; public void commandAction(Command c, Displayable s) { if (c == exit_command) { YesOrNoDialogue dialogue = new YesOrNoDialogue("Do you really want to quit?", this, display); display.setCurrent(dialogue); } else if (c == about_command) about(); else if (c == help_command) help(); else if (c == preferences_command) edit_preferences(); else MyAlert.show("Internal error", "Internal error. This can't happen.", display); } public void gotAnswer(boolean answer) { if (answer) { parent_midlet.notifyDestroyed(); } else { display.setCurrent(this); } } private AboutForm about_form = null; public void about() { if (about_form == null) about_form = new AboutForm(display, display.getCurrent()); display.setCurrent(about_form); } private HelpForm help_form = null; public void help() { if (help_form == null) help_form = new HelpForm(display, display.getCurrent()); display.setCurrent(help_form); } private Preferences preferences = new Preferences(getWidth(), getHeight()); private PreferencesForm preferences_form; public void edit_preferences() { if (preferences_form == null) preferences_form = new PreferencesForm(preferences, display, this, this); pause_time = System.currentTimeMillis(); display.setCurrent(preferences_form); } public final int MAX_BRICKS = 8; public final double BASE_SPEED = getWidth() / 100.0; // Thanks to: http://devlinslab.blogspot.com/2007/10/input-handling-keypress-with-repeat.html // key repeat rate in milliseconds public static final int KEY_REPEAT_RATE = 100; // 0 = as fast as possible (?) private int paddle_step = getWidth() / 20; private boolean left_is_down = false; private boolean right_is_down = false; private long left_keytick = 0; private long right_keytick = 0; private Timer timer; private int max_brick_radius; private Brick[] bricks; private int nr_bricks; double ball_x, ball_y; double ball_dx_factor, ball_dy_factor; int ball_radius; int paddle_x; int paddle_y; int paddle_width; private Graphics g; long wait_until = 0; long start_time = 0; long winning_time; Random prng = new Random(); private SatelliteRushTest parent_midlet; public RushCanvas(SatelliteRushTest parent_midlet) { super(false); // setFullScreenMode(true); this.parent_midlet = parent_midlet; display = Display.getDisplay(parent_midlet); addCommand(exit_command); addCommand(preferences_command); addCommand(about_command); addCommand(help_command); setCommandListener(this); int screen_width = getWidth(); int screen_height = getHeight(); int usable_screen_height = (int)(0.6 * (screen_height - 5)); if (usable_screen_height > screen_width) max_brick_radius = usable_screen_height / MAX_BRICKS / 2; else max_brick_radius = screen_width / MAX_BRICKS / 2; int nr_brick_rows = usable_screen_height / (2*max_brick_radius); double row_size = 1.0 * usable_screen_height / nr_brick_rows; int nr_brick_columns = screen_width / (2*max_brick_radius); double column_size = 1.0 * screen_width / nr_brick_columns; nr_bricks = nr_brick_rows * nr_brick_columns; bricks = new Brick[nr_bricks]; for (int row = 0; row < nr_brick_rows; ++row) { for (int column = 0; column < nr_brick_columns; ++column) { Brick b = new Brick(); b.radius = prng.nextInt(max_brick_radius / 2 + 1) + max_brick_radius / 2; if (b.radius > max_brick_radius) b.radius = max_brick_radius; /* b.x = (int)((column + 0.5) * column_size); b.y = (int)((row + 0.5) * row_size); */ b.x = (int)(column * column_size + b.radius + prng.nextDouble() * (column_size - 2*b.radius)); b.y = (int)(row * row_size + b.radius + prng.nextDouble() * (row_size - 2*b.radius)); b.color = prng.nextInt(0xffffff + 1); bricks[row * nr_brick_columns + column] = b; } } System.out.println("screen_width = " + screen_width); System.out.println("usable_screen_height = " + usable_screen_height); System.out.println("max_brick_radius = " + max_brick_radius); System.out.println("nr_brick_columns = " + nr_brick_columns); System.out.println("nr_brick_rows = " + nr_brick_rows); System.out.println("nr_bricks = " + nr_bricks); ball_x = screen_width / 2; ball_y = screen_height - 5 - ball_radius*2 - 30; ball_dx_factor = 1.0; ball_dy_factor = -1.0; ball_radius = 5; paddle_width = (int)preferences.getPaddleWidthPixels(); paddle_x = screen_width / 2; paddle_y = screen_height - 5; } public void showNotify() { timer = new Timer(); g = getGraphics(); TimerTask task = new TimerTask() { public void run() { if (preferences.getSatelliteMode()) process_gps_position(); else process_keys(); update_state(); render(); flushGraphics(); } }; timer.schedule(task, 0, 20); } public void hideNotify() { if (timer != null) { timer.cancel(); timer = null; } } private void process_gps_position() { int baseline_pixels = getWidth(); paddle_width = (int)preferences.getPaddleWidthPixels(); double baseline_position = preferences_form.get_baseline_position(); paddle_x = (int)(baseline_position * (baseline_pixels - paddle_width) + paddle_width / 2); } private void process_keys() { int keys = getKeyStates(); long now = System.currentTimeMillis(); left_is_down = false; if ((keys & LEFT_PRESSED) != 0) { long elapsed = now - left_keytick; if (elapsed >= KEY_REPEAT_RATE) { left_is_down = true; left_keytick = now; } } right_is_down = false; if ((keys & RIGHT_PRESSED) != 0) { long elapsed = now - right_keytick; if (elapsed >= KEY_REPEAT_RATE) { right_is_down = true; right_keytick = now; } } if (left_is_down) { paddle_x -= paddle_step; if (paddle_x < paddle_width / 2) paddle_x = paddle_width / 2; } if (right_is_down) { paddle_x += paddle_step; if (paddle_x > getWidth() - paddle_width / 2) paddle_x = getWidth() - paddle_width / 2; } } private long previous_update_time = 0; private long pause_time = 0; private void update_state() { long now = System.currentTimeMillis(); paddle_width = (int)preferences.getPaddleWidthPixels(); if (wait_until > now) return; if (start_time == 0) { start_time = now; previous_update_time = now; } // if (nr_bricks == 0) { if (nr_bricks == 0) { if (timer != null) { timer.cancel(); timer = null; } parent_midlet.finished(winning_time); return; } long period; // Number of milliseconds since last update if (pause_time != 0) { period = pause_time - previous_update_time; pause_time = 0; } else { period = now - previous_update_time; } previous_update_time = now; double base_speed_pixels = preferences.getBaseSpeedPixels(); double ball_dx = base_speed_pixels * ball_dx_factor * period / 1000.0; double ball_dy = base_speed_pixels * ball_dy_factor * period / 1000.0; ball_x += ball_dx; ball_y += ball_dy; int screen_width = getWidth(); int screen_height = getHeight(); if (ball_x + ball_radius >= screen_width) { ball_dx_factor *= (-1.005 + prng.nextDouble() / 100); // To avoid the ball getting stuck in a fixed rut ball_x = screen_width - ball_radius - 0.5; // To avoid the ball getting "stuck" at the wall } else if (ball_x - ball_radius <= 0) { ball_dx_factor *= (-1.005 + prng.nextDouble() / 100); ball_x = ball_radius + 0.5; // To avoid the ball getting "stuck" } if (ball_y - ball_radius <= 0) { ball_dy_factor *= (-1.005 + prng.nextDouble() / 100); ball_y = ball_radius + 0.5; // To avoid the ball getting "stuck" } else if (ball_y + ball_radius >= screen_height - 5) { if (ball_x + ball_radius > paddle_x - paddle_width / 2 && ball_x - ball_radius < paddle_x + paddle_width / 2) { System.out.println("Hit the paddle!"); ball_dy_factor *= (-1.005 + prng.nextDouble() / 100); ball_y = paddle_y - ball_radius - 0.5; // To avoid the ball getting "stuck" } else { System.out.println("Missed the paddle!"); /* try { Manager.playTone(69, 250, 100); } catch (MediaException me) { System.out.println("MediaException: " + me); } try { Manager.playTone(40, 1000, 100); } catch (MediaException me) { System.out.println("MediaException: " + me); } */ ball_x = screen_width / 2; ball_y = screen_height - 5 - ball_radius*2 - 30; ball_dx_factor = 1.0; ball_dy_factor = -1.0; wait_until = now + 2000; previous_update_time = wait_until; } } for (int i = 0; i < nr_bricks; ++i) { Brick b = bricks[i]; // To avoid some floating-point calculations, check a square first. if ( Math.abs(b.x - ball_x) <= b.radius + ball_radius + 1 && Math.abs(b.y - ball_y) <= b.radius + ball_radius + 1 && Math.sqrt((b.x - ball_x) * (b.x - ball_x) + (b.y - ball_y) * (b.y - ball_y)) < b.radius + ball_radius) { System.out.println("Popped a brick!"); /* try { Manager.playTone(100, 1000, 100); } catch (MediaException me) { System.out.println("MediaException: " + me); } */ double current_heading = Float11.atan(ball_dy_factor / ball_dx_factor); if (ball_dx_factor < 0) current_heading += Math.PI; // System.out.println("current_heading = " + current_heading); double normal = Float11.atan((ball_y-b.y)/(ball_x-b.x)); if ((ball_x - b.x) < 0) normal += Math.PI; double speed_factor = Math.sqrt(ball_dx_factor * ball_dx_factor + ball_dy_factor * ball_dy_factor); // Bounce back! double new_heading = 2 * normal - current_heading - Math.PI; speed_factor *= 1.03; ball_dx_factor = speed_factor * Math.cos(new_heading); ball_dy_factor = speed_factor * Math.sin(new_heading); bricks[i] = bricks[nr_bricks - 1]; nr_bricks--; // if (nr_bricks == 0) { if (nr_bricks == 0) { winning_time = now - start_time; System.out.println("Time: " + (winning_time + 500) / 1000 + " seconds!"); wait_until = now + 2000; previous_update_time = wait_until; } break; } } // Avoid ball directions in too right angles (very small x or y speed components) double base_speed = preferences.getBaseSpeedPixels(); double ball_dx_factor_abs = Math.abs(ball_dx_factor); double ball_dy_factor_abs = Math.abs(ball_dy_factor); if (ball_dx_factor_abs < ball_dy_factor_abs / 10) { if (ball_dx_factor > 0) ball_dx_factor = ball_dy_factor_abs / 10; else ball_dx_factor = -ball_dy_factor_abs / 10; } if (ball_dy_factor_abs < ball_dx_factor_abs / 10) { if (ball_dy_factor > 0) ball_dy_factor = ball_dx_factor_abs / 10; else ball_dy_factor = -ball_dx_factor_abs / 10; } // System.out.println("ball_x = " + ball_x + ", ball_y = " + ball_y + ", nr_bricks = " + nr_bricks); } private void render() { g.setColor(0xffffff); int screen_width = getWidth(); int screen_height = getHeight(); g.fillRect(0, 0, screen_width, screen_height); for (int i = 0; i < nr_bricks; ++i) { // System.out.println("Drawing i = " + i); Brick b = bricks[i]; g.setColor(b.color); g.fillArc(b.x - b.radius, b.y - b.radius, b.radius*2, b.radius*2, 0, 360); g.setColor(0x000000); g.drawArc(b.x - b.radius, b.y - b.radius, b.radius*2, b.radius*2, 0, 360); } g.setColor(0x000000); if (preferences.getShowPath()) { g.setColor(0xff0000); double speed_factor = Math.sqrt(ball_dx_factor * ball_dx_factor + ball_dy_factor * ball_dy_factor); double screen_diagonal = Math.sqrt(screen_width * screen_width + screen_height * screen_height); double speed_scale = screen_diagonal / speed_factor; g.drawLine((int)ball_x, (int)ball_y, (int)(ball_x + ball_dx_factor * speed_scale), (int)(ball_y + ball_dy_factor * speed_scale)); g.setColor(0x000000); } g.fillArc((int)(ball_x - ball_radius), (int)(ball_y - ball_radius), ball_radius * 2, ball_radius * 2, 0, 360); paddle_width = (int)preferences.getPaddleWidthPixels(); g.fillRect(paddle_x - paddle_width / 2, paddle_y, paddle_width, 5); } } // class RushCanvas