├── html ├── webapp │ ├── soundmanager2-setup.js │ ├── WEB-INF │ │ └── web.xml │ ├── styles.css │ └── index.html ├── src │ └── com │ │ └── dozingcatsoftware │ │ └── bouncy │ │ ├── GdxDefinition.gwt.xml │ │ ├── client │ │ └── HtmlLauncher.java │ │ └── GdxDefinitionSuperdev.gwt.xml └── build.gradle ├── settings.gradle ├── android ├── assets │ ├── badlogic.jpg │ └── data │ │ └── field_layout.json ├── ic_launcher-web.png ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ └── values │ │ └── strings.xml ├── project.properties ├── src │ └── com │ │ └── dozingcatsoftware │ │ └── bouncy │ │ └── android │ │ └── AndroidLauncher.java ├── proguard-project.txt ├── AndroidManifest.xml └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── ios ├── robovm.properties ├── build.gradle ├── src │ └── com │ │ └── dozingcatsoftware │ │ └── bouncy │ │ └── IOSLauncher.java ├── Info.plist.xml └── robovm.xml ├── core ├── build.gradle └── src │ ├── Bouncy.gwt.xml │ └── com │ └── dozingcatsoftware │ └── bouncy │ ├── GameMessage.java │ ├── util │ ├── MathUtils.java │ ├── JSONString.java │ ├── JSONException.java │ ├── JSONUtils.java │ ├── JSONStringer.java │ ├── JSONWriter.java │ ├── JSONTokener.java │ └── JSONArray.java │ ├── IFieldRenderer.java │ ├── BaseFieldDelegate.java │ ├── GLFieldRenderer.java │ ├── elements │ ├── WallPathElement.java │ ├── BumperElement.java │ ├── SensorElement.java │ ├── Box2DFactory.java │ ├── DropTargetGroupElement.java │ ├── WallArcElement.java │ ├── WallElement.java │ ├── FlipperElement.java │ ├── FieldElement.java │ └── RolloverGroupElement.java │ ├── GameState.java │ ├── fields │ └── Field1Delegate.java │ ├── Bouncy.java │ ├── FieldLayout.java │ └── Field.java ├── desktop ├── src │ └── com │ │ └── dozingcatsoftware │ │ └── bouncy │ │ └── desktop │ │ └── DesktopLauncher.java └── build.gradle ├── .gitignore ├── README.md ├── gradlew.bat ├── gradlew └── COPYING /html/webapp/soundmanager2-setup.js: -------------------------------------------------------------------------------- 1 | window.SM2_DEFER = true; -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include "core", "desktop", "android", "html", "ios" -------------------------------------------------------------------------------- /html/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/assets/badlogic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libgdx/libgdx-demo-vector-pinball/HEAD/android/assets/badlogic.jpg -------------------------------------------------------------------------------- /android/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libgdx/libgdx-demo-vector-pinball/HEAD/android/ic_launcher-web.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libgdx/libgdx-demo-vector-pinball/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libgdx/libgdx-demo-vector-pinball/HEAD/android/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libgdx/libgdx-demo-vector-pinball/HEAD/android/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libgdx/libgdx-demo-vector-pinball/HEAD/android/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libgdx/libgdx-demo-vector-pinball/HEAD/android/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | libgdx-demo-vector-pinball 5 | 6 | 7 | -------------------------------------------------------------------------------- /ios/robovm.properties: -------------------------------------------------------------------------------- 1 | app.version=1.0 2 | app.id=com.dozingcatsoftware.bouncy.IOSLauncher 3 | app.mainclass=com.dozingcatsoftware.bouncy.IOSLauncher 4 | app.executable=IOSLauncher 5 | app.build=1 6 | app.name=libgdx-demo-vector-pinball 7 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "java" 2 | 3 | sourceCompatibility = 1.6 4 | 5 | dependencies { 6 | compile "com.badlogicgames.gdx:gdx:$gdxVersion" 7 | } 8 | 9 | sourceSets.main.java.srcDirs = [ "src/" ] 10 | 11 | eclipse.project { 12 | name = appName + "-core" 13 | } 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 21 13:08:26 CEST 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip 7 | -------------------------------------------------------------------------------- /core/src/Bouncy.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/GameMessage.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | /** Simple class to hold a message displayed in the ScoreView above the game field. */ 5 | 6 | public class GameMessage { 7 | public String text; 8 | public long duration; 9 | public long creationTime; 10 | } 11 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/MathUtils.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | public class MathUtils { 5 | 6 | public static float asFloat (Object obj, float defvalue) { 7 | if (obj instanceof Number) return ((Number)obj).floatValue(); 8 | return defvalue; 9 | } 10 | 11 | public static float asFloat (Object obj) { 12 | return asFloat(obj, 0f); 13 | } 14 | 15 | public static float toRadians (float degrees) { 16 | return (float)(Math.PI / 180) * degrees; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /ios/build.gradle: -------------------------------------------------------------------------------- 1 | sourceSets.main.java.srcDirs = [ "src/" ] 2 | 3 | sourceCompatibility = '1.7' 4 | [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 5 | 6 | ext { 7 | mainClassName = "com.dozingcatsoftware.bouncy.IOSLauncher" 8 | } 9 | 10 | launchIPhoneSimulator.dependsOn build 11 | launchIPadSimulator.dependsOn build 12 | launchIOSDevice.dependsOn build 13 | createIPA.dependsOn build 14 | 15 | robovm { 16 | archs = "thumbv7:arm64" 17 | } 18 | 19 | eclipse.project { 20 | name = appName + "-ios" 21 | natures 'org.robovm.eclipse.RoboVMNature' 22 | } -------------------------------------------------------------------------------- /html/webapp/styles.css: -------------------------------------------------------------------------------- 1 | canvas { 2 | cursor: default; 3 | outline: none; 4 | } 5 | 6 | .superdev { 7 | display: block; 8 | width: 150px; 9 | height: 25px; 10 | background: #4E9CAF; 11 | padding: 10px; 12 | text-align: center; 13 | border-radius: 5px; 14 | color: white; 15 | font-weight: bold; 16 | text-decoration: none; 17 | border-style: solid; 18 | border-color: rgb(0,0,0); 19 | border-width: 1px; 20 | } 21 | 22 | .superdev:hover { 23 | background: #2E6CAF; 24 | } 25 | 26 | .superdev:active { 27 | background: #1E4CAF; 28 | } -------------------------------------------------------------------------------- /android/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | -------------------------------------------------------------------------------- /desktop/src/com/dozingcatsoftware/bouncy/desktop/DesktopLauncher.java: -------------------------------------------------------------------------------- 1 | package com.dozingcatsoftware.bouncy.desktop; 2 | 3 | import com.badlogic.gdx.backends.lwjgl.LwjglApplication; 4 | import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; 5 | import com.dozingcatsoftware.bouncy.Bouncy; 6 | 7 | public class DesktopLauncher { 8 | public static void main (String[] arg) { 9 | LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); 10 | config.title = "Vector Pinball"; 11 | config.width = 480; 12 | config.height = 800; 13 | new LwjglApplication(new Bouncy(), config); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/src/com/dozingcatsoftware/bouncy/android/AndroidLauncher.java: -------------------------------------------------------------------------------- 1 | package com.dozingcatsoftware.bouncy.android; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.badlogic.gdx.backends.android.AndroidApplication; 6 | import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; 7 | import com.dozingcatsoftware.bouncy.Bouncy; 8 | 9 | public class AndroidLauncher extends AndroidApplication { 10 | @Override 11 | protected void onCreate (Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); 14 | initialize(new Bouncy(), config); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/IFieldRenderer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | /** This interface defines methods that draw graphical elements such as lines as circles to display the field. An implementation of 5 | * this interface is passed to FieldElement objects so they can draw themselves without depending directly on Android UI classes. */ 6 | 7 | public interface IFieldRenderer { 8 | 9 | public void drawLine (float x1, float y1, float x2, float y2, int r, int g, int b); 10 | 11 | public void fillCircle (float cx, float cy, float radius, int r, int g, int b); 12 | 13 | public void frameCircle (float cx, float cy, float radius, int r, int g, int b); 14 | } 15 | -------------------------------------------------------------------------------- /html/src/com/dozingcatsoftware/bouncy/GdxDefinition.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /html/src/com/dozingcatsoftware/bouncy/client/HtmlLauncher.java: -------------------------------------------------------------------------------- 1 | package com.dozingcatsoftware.bouncy.client; 2 | 3 | import com.badlogic.gdx.ApplicationListener; 4 | import com.badlogic.gdx.backends.gwt.GwtApplication; 5 | import com.badlogic.gdx.backends.gwt.GwtApplicationConfiguration; 6 | import com.dozingcatsoftware.bouncy.Bouncy; 7 | 8 | public class HtmlLauncher extends GwtApplication { 9 | 10 | @Override 11 | public GwtApplicationConfiguration getConfig () { 12 | return new GwtApplicationConfiguration(480, 800); 13 | } 14 | 15 | @Override 16 | public ApplicationListener createApplicationListener () { 17 | return new Bouncy(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/JSONString.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | /** The JSONString interface allows a toJSONString() method so that a class can change the behavior of 5 | * JSONObject.toString(), JSONArray.toString(), and JSONWriter.value(Object). 6 | * The toJSONString method will be used instead of the default behavior of using the Object's toString() 7 | * method and quoting the result. */ 8 | public interface JSONString { 9 | /** The toJSONString method allows a class to produce its own JSON serialization. 10 | * 11 | * @return A strictly syntactically correct JSON text. */ 12 | public String toJSONString (); 13 | } 14 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/JSONException.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | /** The JSONException is thrown by the JSON.org classes when things are amiss. 5 | * @author JSON.org 6 | * @version 2010-12-24 */ 7 | public class JSONException extends Exception { 8 | private static final long serialVersionUID = 0; 9 | private Throwable cause; 10 | 11 | /** Constructs a JSONException with an explanatory message. 12 | * @param message Detail about the reason for the exception. */ 13 | public JSONException (String message) { 14 | super(message); 15 | } 16 | 17 | public JSONException (Throwable cause) { 18 | super(cause.getMessage()); 19 | this.cause = cause; 20 | } 21 | 22 | public Throwable getCause () { 23 | return this.cause; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /html/src/com/dozingcatsoftware/bouncy/GdxDefinitionSuperdev.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /android/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /ios/src/com/dozingcatsoftware/bouncy/IOSLauncher.java: -------------------------------------------------------------------------------- 1 | package com.dozingcatsoftware.bouncy; 2 | 3 | import org.robovm.apple.foundation.NSAutoreleasePool; 4 | import org.robovm.apple.uikit.UIApplication; 5 | 6 | import com.badlogic.gdx.backends.iosrobovm.IOSApplication; 7 | import com.badlogic.gdx.backends.iosrobovm.IOSApplicationConfiguration; 8 | import com.dozingcatsoftware.bouncy.Bouncy; 9 | 10 | public class IOSLauncher extends IOSApplication.Delegate { 11 | @Override 12 | protected IOSApplication createApplication() { 13 | IOSApplicationConfiguration config = new IOSApplicationConfiguration(); 14 | return new IOSApplication(new Bouncy(), config); 15 | } 16 | 17 | public static void main(String[] argv) { 18 | NSAutoreleasePool pool = new NSAutoreleasePool(); 19 | UIApplication.main(argv, null, IOSLauncher.class); 20 | pool.close(); 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Java 2 | 3 | *.class 4 | *.war 5 | *.ear 6 | hs_err_pid* 7 | 8 | ## GWT 9 | war/ 10 | war/gwt_bree/ 11 | gwt-unitCache/ 12 | .apt_generated/ 13 | war/WEB-INF/deploy/ 14 | war/WEB-INF/classes/ 15 | .gwt/ 16 | gwt-unitCache/ 17 | www-test/ 18 | .gwt-tmp/ 19 | 20 | ## Android Studio and Intellij 21 | libs/armeabi/ 22 | libs/armeabi-v7a/ 23 | libs/x86/ 24 | gen/ 25 | .idea/ 26 | *.ipr 27 | *.iws 28 | *.iml 29 | out/ 30 | com_crashlytics_export_strings.xml 31 | android/libs 32 | 33 | ## Eclipse 34 | .classpath 35 | .project 36 | .metadata 37 | bin/ 38 | tmp/ 39 | *.tmp 40 | *.bak 41 | *.swp 42 | *~.nib 43 | local.properties 44 | .settings/ 45 | .loadpath 46 | .externalToolBuilders/ 47 | *.launch 48 | 49 | ## NetBeans 50 | nbproject/private/ 51 | build/ 52 | nbbuild/ 53 | dist/ 54 | nbdist/ 55 | nbactions.xml 56 | nb-configuration.xml 57 | 58 | ## Gradle 59 | versions.json 60 | .gradle 61 | build/ 62 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Libgdx demo - Vector Pinball 2 | ==================== 3 | ![vectorpinball](http://i.imgur.com/OTQxIy2.png) 4 | 5 | Vector Pinball is a 2D pinball simulation using Box2D. It was graciously donated to the libgdx effort by Brian from [Dozing Cat Software](www.dozingcatsoftware.com/) 6 | 7 | ### Try it here 8 | * [WebGL/Browser](http://libgdx.badlogicgames.com/demos/vectorpinball) 9 | * [Desktop](http://libgdx.badlogicgames.com/demos/vectorpinball/vectorpinball.jar) 10 | * [Android](http://libgdx.badlogicgames.com/demos/vectorpinball/vectorpinball.apk) 11 | 12 | ### Running 13 | * [Setup your development environment](https://github.com/libgdx/libgdx/wiki) 14 | * Clone the repository or download and extract the ZIP file 15 | * Import the project into your preferred development environment, run, debug and package it! 16 | * [Eclipse](https://github.com/libgdx/libgdx/wiki/Gradle-and-Eclipse) 17 | * [Intellij IDEA](https://github.com/libgdx/libgdx/wiki/Gradle-and-Intellij-IDEA) 18 | * [NetBeans](https://github.com/libgdx/libgdx/wiki/Gradle-and-NetBeans) 19 | * [Commandline|Gradle on the Commandline](https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline) 20 | -------------------------------------------------------------------------------- /ios/Info.plist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${app.name} 9 | CFBundleExecutable 10 | ${app.executable} 11 | CFBundleIdentifier 12 | ${app.id} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${app.name} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | ${app.version} 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | ${app.build} 25 | LSRequiresIPhoneOS 26 | 27 | UIViewControllerBasedStatusBarAppearance 28 | 29 | UIStatusBarHidden 30 | 31 | MinimumOSVersion 32 | 9.0 33 | UIRequiresFullScreen 34 | 35 | UIDeviceFamily 36 | 37 | 1 38 | 2 39 | 40 | UIRequiredDeviceCapabilities 41 | 42 | opengles-2 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /html/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | libgdx-demo-vector-pinball 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | SuperDev Refresh 13 |
14 | 15 | 16 | 17 | 32 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/BaseFieldDelegate.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | import com.badlogic.gdx.physics.box2d.Body; 5 | import com.dozingcatsoftware.bouncy.elements.DropTargetGroupElement; 6 | import com.dozingcatsoftware.bouncy.elements.FieldElement; 7 | import com.dozingcatsoftware.bouncy.elements.RolloverGroupElement; 8 | import com.dozingcatsoftware.bouncy.elements.SensorElement; 9 | 10 | /** This class implements the Field.Delegate interface and does nothing for each of the interface methods. Real delegates can 11 | * subclass this class to avoid having to create empty implementations for events they don't care about. If a field definition 12 | * doesn't specify a delegate class, an instance of this class will be used as a placeholder delegate. 13 | * @author brian */ 14 | public class BaseFieldDelegate implements Field.Delegate { 15 | 16 | @Override 17 | public void allDropTargetsInGroupHit (Field field, DropTargetGroupElement targetGroup) { 18 | } 19 | 20 | @Override 21 | public void allRolloversInGroupActivated (Field field, RolloverGroupElement rolloverGroup) { 22 | } 23 | 24 | @Override 25 | public void flipperActivated (Field field) { 26 | } 27 | 28 | @Override 29 | public void processCollision (Field field, FieldElement element, Body hitBody, Body ball) { 30 | } 31 | 32 | @Override 33 | public void gameStarted (Field field) { 34 | } 35 | 36 | @Override 37 | public void ballLost (Field field) { 38 | } 39 | 40 | @Override 41 | public void gameEnded (Field field) { 42 | } 43 | 44 | @Override 45 | public void tick (Field field, long msecs) { 46 | } 47 | 48 | @Override 49 | public void ballInSensorRange (Field field, SensorElement sensor) { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/GLFieldRenderer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | import com.badlogic.gdx.graphics.glutils.ShapeRenderer; 5 | import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; 6 | import com.badlogic.gdx.math.Matrix4; 7 | 8 | public class GLFieldRenderer implements IFieldRenderer { 9 | static final int CIRCLE_VERTICES = 10; 10 | ShapeRenderer renderer; 11 | 12 | public GLFieldRenderer () { 13 | renderer = new ShapeRenderer(500); 14 | } 15 | 16 | public void begin () { 17 | renderer.begin(ShapeType.Line); 18 | } 19 | 20 | @Override 21 | public void drawLine (float x1, float y1, float x2, float y2, int r, int g, int b) { 22 | float fr = r / 255f; 23 | float fg = g / 255f; 24 | float fb = b / 255f; 25 | renderer.setColor(fr, fg, fb, 1); 26 | renderer.line(x1, y1, x2, y2); 27 | } 28 | 29 | @Override 30 | public void fillCircle (float cx, float cy, float radius, int r, int g, int b) { 31 | end(); 32 | renderer.begin(ShapeType.Filled); 33 | float fr = r / 255f; 34 | float fg = g / 255f; 35 | float fb = b / 255f; 36 | renderer.setColor(fr, fg, fb, 1); 37 | renderer.circle(cx, cy, radius, 20); 38 | end(); 39 | begin(); 40 | } 41 | 42 | @Override 43 | public void frameCircle (float cx, float cy, float radius, int r, int g, int b) { 44 | end(); 45 | renderer.begin(ShapeType.Line); 46 | float fr = r / 255f; 47 | float fg = g / 255f; 48 | float fb = b / 255f; 49 | renderer.setColor(fr, fg, fb, 1); 50 | renderer.circle(cx, cy, radius, 20); 51 | end(); 52 | begin(); 53 | } 54 | 55 | public void end () { 56 | renderer.end(); 57 | } 58 | 59 | public void setProjectionMatrix (Matrix4 matrix) { 60 | renderer.setProjectionMatrix(matrix); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /desktop/build.gradle: -------------------------------------------------------------------------------- 1 | sourceCompatibility = 1.7 2 | sourceSets.main.java.srcDirs = [ "src/" ] 3 | sourceSets.main.resources.srcDirs = ["../android/assets"] 4 | 5 | project.ext.mainClassName = "com.dozingcatsoftware.bouncy.desktop.DesktopLauncher" 6 | project.ext.assetsDir = new File("../android/assets"); 7 | 8 | task run(dependsOn: classes, type: JavaExec) { 9 | main = project.mainClassName 10 | classpath = sourceSets.main.runtimeClasspath 11 | standardInput = System.in 12 | workingDir = project.assetsDir 13 | ignoreExitValue = true 14 | } 15 | 16 | task debug(dependsOn: classes, type: JavaExec) { 17 | main = project.mainClassName 18 | classpath = sourceSets.main.runtimeClasspath 19 | standardInput = System.in 20 | workingDir = project.assetsDir 21 | ignoreExitValue true 22 | debug = true 23 | } 24 | 25 | task dist(type: Jar) { 26 | manifest { 27 | attributes 'Main-Class': project.mainClassName 28 | } 29 | dependsOn configurations.runtimeClasspath 30 | from { 31 | configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } 32 | } 33 | with jar 34 | } 35 | 36 | dist.dependsOn classes 37 | 38 | 39 | eclipse { 40 | project { 41 | name = appName + "-desktop" 42 | linkedResource name: 'assets', type: '2', location: 'PARENT-1-PROJECT_LOC/android/assets' 43 | } 44 | } 45 | 46 | task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { 47 | doLast { 48 | def classpath = new XmlParser().parse(file(".classpath")) 49 | new Node(classpath, "classpathentry", [ kind: 'src', path: 'assets' ]); 50 | def writer = new FileWriter(file(".classpath")) 51 | def printer = new XmlNodePrinter(new PrintWriter(writer)) 52 | printer.setPreserveWhitespace(true) 53 | printer.print(classpath) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ios/robovm.xml: -------------------------------------------------------------------------------- 1 | 2 | ${app.executable} 3 | ${app.mainclass} 4 | ios 5 | ios 6 | conservative 7 | Info.plist.xml 8 | 9 | 10 | ../android/assets 11 | 12 | ** 13 | 14 | true 15 | 16 | 17 | 18 | com.badlogic.gdx.scenes.scene2d.ui.* 19 | com.badlogic.gdx.physics.bullet.** 20 | com.android.okhttp.HttpHandler 21 | com.android.okhttp.HttpsHandler 22 | com.android.org.conscrypt.** 23 | com.android.org.bouncycastle.jce.provider.BouncyCastleProvider 24 | com.android.org.bouncycastle.jcajce.provider.keystore.BC$Mappings 25 | com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi 26 | com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi$Std 27 | com.android.org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi 28 | com.android.org.bouncycastle.crypto.digests.AndroidDigestFactoryOpenSSL 29 | org.apache.harmony.security.provider.cert.DRLCertFactory 30 | org.apache.harmony.security.provider.crypto.CryptoProvider 31 | 32 | 33 | z 34 | 35 | 36 | UIKit 37 | OpenGLES 38 | QuartzCore 39 | CoreGraphics 40 | OpenAL 41 | AudioToolbox 42 | AVFoundation 43 | 44 | 45 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/WallPathElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.badlogic.gdx.physics.box2d.Body; 10 | import com.badlogic.gdx.physics.box2d.World; 11 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 12 | 13 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 14 | 15 | /** FieldElement subclass which represents a series of wall segments. The segments are defined in the "positions" parameter as a 16 | * list of [x,y] values, for example: { "class": "WallPathElement", "positions": [[5,5], [5,10], [8,10], [5, 15]] } 17 | * 18 | * @author brian */ 19 | 20 | public class WallPathElement extends FieldElement { 21 | 22 | List wallBodies = new ArrayList(); 23 | List lineSegments = new ArrayList(); 24 | 25 | public void finishCreate (Map params, World world) { 26 | List positions = (List)params.get("positions"); 27 | 28 | for (int i = 0; i < positions.size() - 1; i++) { 29 | List startpos = (List)positions.get(i); 30 | List endpos = (List)positions.get(i + 1); 31 | 32 | float[] segment = new float[] {asFloat(startpos.get(0)), asFloat(startpos.get(1)), asFloat(endpos.get(0)), 33 | asFloat(endpos.get(1))}; 34 | lineSegments.add(segment); 35 | 36 | Body wall = Box2DFactory.createThinWall(world, segment[0], segment[1], segment[2], segment[3], 0f); 37 | this.wallBodies.add(wall); 38 | } 39 | } 40 | 41 | @Override 42 | public Collection getBodies () { 43 | return wallBodies; 44 | } 45 | 46 | @Override 47 | public void draw (IFieldRenderer renderer) { 48 | int len = lineSegments.size(); 49 | for (int i = 0; i < len; i++) { 50 | float[] segment = lineSegments.get(i); 51 | renderer.drawLine(segment[0], segment[1], segment[2], segment[3], redColorComponent(DEFAULT_WALL_RED), 52 | greenColorComponent(DEFAULT_WALL_GREEN), blueColorComponent(DEFAULT_WALL_BLUE)); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /html/build.gradle: -------------------------------------------------------------------------------- 1 | gwt { 2 | gwtVersion=project.gwtVersion // Should match the gwt version used for building the gwt backend 3 | maxHeapSize="1G" // Default 256m is not enough for gwt compiler. GWT is HUNGRY 4 | minHeapSize="1G" 5 | 6 | src = files(file("src/")) // Needs to be in front of "modules" below. 7 | modules 'com.dozingcatsoftware.bouncy.GdxDefinition' 8 | devModules 'com.dozingcatsoftware.bouncy.GdxDefinitionSuperdev' 9 | 10 | compiler { 11 | strict = true; 12 | disableCastChecking = true; 13 | } 14 | } 15 | 16 | import org.wisepersist.gradle.plugins.gwt.GwtSuperDev 17 | 18 | def HttpFileServer server = null 19 | def httpFilePort = 8080 20 | 21 | task startHttpServer () { 22 | dependsOn draftCompileGwt 23 | 24 | String output = project.buildDir.path + "/gwt/draftOut" 25 | 26 | doLast { 27 | copy { 28 | from "webapp" 29 | into output 30 | } 31 | 32 | copy { 33 | from "war" 34 | into output 35 | } 36 | 37 | server = new SimpleHttpFileServerFactory().start(new File(output), httpFilePort) 38 | 39 | println "Server started in directory " + server.getContentRoot() + ", http://localhost:" + server.getPort() 40 | } 41 | } 42 | 43 | task superDev (type: GwtSuperDev) { 44 | dependsOn startHttpServer 45 | doFirst { 46 | gwt.modules = gwt.devModules 47 | } 48 | } 49 | 50 | 51 | task dist(dependsOn: [clean, compileGwt]) { 52 | doLast { 53 | file("build/dist").mkdirs() 54 | copy { 55 | from "build/gwt/out" 56 | into "build/dist" 57 | } 58 | copy { 59 | from "webapp" 60 | into "build/dist" 61 | } 62 | copy { 63 | from "war" 64 | into "build/dist" 65 | } 66 | } 67 | } 68 | 69 | task addSource { 70 | doLast { 71 | sourceSets.main.compileClasspath += files(project(':core').sourceSets.main.allJava.srcDirs) 72 | } 73 | } 74 | 75 | tasks.compileGwt.dependsOn(addSource) 76 | tasks.draftCompileGwt.dependsOn(addSource) 77 | 78 | sourceCompatibility = 1.6 79 | sourceSets.main.java.srcDirs = [ "src/" ] 80 | 81 | 82 | eclipse.project { 83 | name = appName + "-html" 84 | } 85 | 86 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/BumperElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.badlogic.gdx.math.Vector2; 10 | import com.badlogic.gdx.physics.box2d.Body; 11 | import com.badlogic.gdx.physics.box2d.World; 12 | import com.dozingcatsoftware.bouncy.Field; 13 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 14 | 15 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 16 | 17 | /** This FieldElement subclass represents a bumper that applies an impulse to a ball when it hits. The impulse magnitude is 18 | * controlled by the "kick" parameter in the configuration map. */ 19 | 20 | public class BumperElement extends FieldElement { 21 | 22 | Body pegBody; 23 | Collection pegBodySet; 24 | 25 | float radius; 26 | float cx, cy; 27 | float kick; 28 | 29 | public void finishCreate (Map params, World world) { 30 | List pos = (List)params.get("position"); 31 | this.radius = asFloat(params.get("radius")); 32 | this.cx = asFloat(pos.get(0)); 33 | this.cy = asFloat(pos.get(1)); 34 | this.kick = asFloat(params.get("kick")); 35 | 36 | pegBody = Box2DFactory.createCircle(world, cx, cy, radius, true); 37 | pegBodySet = Collections.singleton(pegBody); 38 | } 39 | 40 | @Override 41 | public Collection getBodies () { 42 | return pegBodySet; 43 | } 44 | 45 | @Override 46 | public boolean shouldCallTick () { 47 | // needs to call tick to decrement flash counter (but can use superclass tick() implementation) 48 | return true; 49 | } 50 | 51 | Vector2 impulseForBall (Body ball) { 52 | if (this.kick <= 0.01f) return null; 53 | // compute unit vector from center of peg to ball, and scale by kick value to get impulse 54 | Vector2 ballpos = ball.getWorldCenter(); 55 | float ix = ballpos.x - this.cx; 56 | float iy = ballpos.y - this.cy; 57 | float mag = (float)Math.sqrt(ix * ix + iy * iy); 58 | float scale = this.kick / mag; 59 | return new Vector2(ix * scale, iy * scale); 60 | } 61 | 62 | @Override 63 | public void handleCollision (Body ball, Body bodyHit, Field field) { 64 | Vector2 impulse = this.impulseForBall(ball); 65 | if (impulse != null) { 66 | ball.applyLinearImpulse(impulse, ball.getWorldCenter(), true); 67 | flashForFrames(3); 68 | } 69 | } 70 | 71 | @Override 72 | public void draw (IFieldRenderer renderer) { 73 | renderer.fillCircle(cx, cy, radius, redColorComponent(0), greenColorComponent(0), blueColorComponent(255)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/JSONUtils.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class JSONUtils { 11 | 12 | /** If argument is a JSONArray or JSONObject, returns the equivalent List or Map. If argument is JSONObject.NULL, returns null. 13 | * Otherwise, returns the argument unchanged. */ 14 | public static Object objectFromJSONItem (Object jsonItem) { 15 | if (jsonItem == JSONObject.NULL) { 16 | return null; 17 | } 18 | if (jsonItem instanceof JSONArray) { 19 | return listFromJSONArray((JSONArray)jsonItem); 20 | } 21 | if (jsonItem instanceof JSONObject) { 22 | return mapFromJSONObject((JSONObject)jsonItem); 23 | } 24 | return jsonItem; 25 | } 26 | 27 | /** Returns a List with the same objects in the same order as jsonArray. Recursively converts nested JSONArray and JSONObject 28 | * values to List and Map objects. */ 29 | public static List listFromJSONArray (JSONArray jsonArray) { 30 | List result = new ArrayList(); 31 | try { 32 | for (int i = 0; i < jsonArray.length(); i++) { 33 | Object obj = objectFromJSONItem(jsonArray.get(i)); 34 | result.add(obj); 35 | } 36 | } catch (JSONException ex) { 37 | throw new RuntimeException(ex); 38 | } 39 | return result; 40 | } 41 | 42 | /** Returns a List with the same keys and values as jsonObject. Recursively converts nested JSONArray and JSONObject values to 43 | * List and Map objects. */ 44 | public static Map mapFromJSONObject (JSONObject jsonObject) { 45 | Map result = new HashMap(); 46 | try { 47 | for (Iterator ki = jsonObject.keys(); ki.hasNext();) { 48 | String key = (String)ki.next(); 49 | Object value = objectFromJSONItem(jsonObject.get(key)); 50 | result.put(key, value); 51 | } 52 | } catch (JSONException ex) { 53 | throw new RuntimeException(ex); 54 | } 55 | return result; 56 | } 57 | 58 | /** Returns a List created by parsing the string argument as a JSON array and calling listFromJSONArray. */ 59 | public static List listFromJSONString (String jsonString) { 60 | try { 61 | return listFromJSONArray(new JSONArray(jsonString)); 62 | } catch (JSONException ex) { 63 | throw new RuntimeException(ex); 64 | } 65 | } 66 | 67 | /** Returns a Map created by parsing the string argument as a JSON object and calling mapFromJSONObject. */ 68 | public static Map mapFromJSONString (String jsonString) { 69 | try { 70 | return mapFromJSONObject(new JSONObject(jsonString)); 71 | } catch (JSONException ex) { 72 | throw new RuntimeException(ex); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/GameState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | public class GameState { 5 | 6 | boolean gameInProgress; 7 | 8 | int ballNumber; 9 | int extraBalls; 10 | int totalBalls = 3; 11 | 12 | long score; 13 | int scoreMultiplier; 14 | int bonusScore; 15 | int bonusMultiplier; 16 | boolean multiplierHeld; 17 | 18 | public void startNewGame () { 19 | score = 0; 20 | ballNumber = 1; 21 | scoreMultiplier = 1; 22 | bonusMultiplier = 1; 23 | multiplierHeld = false; 24 | gameInProgress = true; 25 | } 26 | 27 | public void doNextBall () { 28 | // if multipliers held, clear it, if not, reset multipliers to 1 29 | if (multiplierHeld) { 30 | multiplierHeld = false; 31 | } else { 32 | scoreMultiplier = 1; 33 | bonusMultiplier = 1; 34 | } 35 | 36 | if (extraBalls > 0) { 37 | --extraBalls; 38 | } else if (ballNumber < totalBalls) { 39 | ++ballNumber; 40 | } else { 41 | gameInProgress = false; 42 | } 43 | } 44 | 45 | public void addScore (long points) { 46 | score += points * scoreMultiplier; 47 | } 48 | 49 | public void addExtraBall () { 50 | ++extraBalls; 51 | } 52 | 53 | public void incrementScoreMultiplier () { 54 | ++scoreMultiplier; 55 | } 56 | 57 | public void incrementBonusMultiplier () { 58 | ++bonusMultiplier; 59 | } 60 | 61 | // autogenerated accessors 62 | 63 | public boolean isGameInProgress () { 64 | return gameInProgress; 65 | } 66 | 67 | public void setGameInProgress (boolean gameInProgress) { 68 | this.gameInProgress = gameInProgress; 69 | } 70 | 71 | public int getBallNumber () { 72 | return ballNumber; 73 | } 74 | 75 | public void setBallNumber (int ballNumber) { 76 | this.ballNumber = ballNumber; 77 | } 78 | 79 | public int getExtraBalls () { 80 | return extraBalls; 81 | } 82 | 83 | public void setExtraBalls (int extraBalls) { 84 | this.extraBalls = extraBalls; 85 | } 86 | 87 | public int getTotalBalls () { 88 | return totalBalls; 89 | } 90 | 91 | public void setTotalBalls (int totalBalls) { 92 | this.totalBalls = totalBalls; 93 | } 94 | 95 | public long getScore () { 96 | return score; 97 | } 98 | 99 | public void setScore (long score) { 100 | this.score = score; 101 | } 102 | 103 | public int getScoreMultiplier () { 104 | return scoreMultiplier; 105 | } 106 | 107 | public void setScoreMultiplier (int scoreMultiplier) { 108 | this.scoreMultiplier = scoreMultiplier; 109 | } 110 | 111 | public int getBonusScore () { 112 | return bonusScore; 113 | } 114 | 115 | public void setBonusScore (int bonusScore) { 116 | this.bonusScore = bonusScore; 117 | } 118 | 119 | public int getBonusMultiplier () { 120 | return bonusMultiplier; 121 | } 122 | 123 | public void setBonusMultiplier (int bonusMultiplier) { 124 | this.bonusMultiplier = bonusMultiplier; 125 | } 126 | 127 | public boolean isMultiplierHeld () { 128 | return multiplierHeld; 129 | } 130 | 131 | public void setMultiplierHeld (boolean value) { 132 | multiplierHeld = value; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/fields/Field1Delegate.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.fields; 3 | 4 | import com.dozingcatsoftware.bouncy.BaseFieldDelegate; 5 | import com.dozingcatsoftware.bouncy.Field; 6 | import com.dozingcatsoftware.bouncy.elements.DropTargetGroupElement; 7 | import com.dozingcatsoftware.bouncy.elements.RolloverGroupElement; 8 | import com.dozingcatsoftware.bouncy.elements.SensorElement; 9 | import com.dozingcatsoftware.bouncy.elements.WallElement; 10 | 11 | public class Field1Delegate extends BaseFieldDelegate { 12 | 13 | @Override 14 | public void allRolloversInGroupActivated (Field field, RolloverGroupElement rolloverGroup) { 15 | // rollover groups increment field multiplier when all rollovers are activated, also reset to inactive 16 | rolloverGroup.setAllRolloversActivated(false); 17 | field.getGameState().incrementScoreMultiplier(); 18 | field.showGameMessage(field.getGameState().getScoreMultiplier() + "x Multiplier", 1500); 19 | 20 | // extra ball for ramp shot if extra ball rollovers all lit 21 | if ("RampRollovers".equals(rolloverGroup.getElementID())) { 22 | RolloverGroupElement extraBallRollovers = (RolloverGroupElement)field.getFieldElementByID("ExtraBallRollovers"); 23 | if (extraBallRollovers.allRolloversActive()) { 24 | field.showGameMessage("Extra Ball!", 2000); 25 | field.getGameState().addExtraBall(); 26 | extraBallRollovers.setAllRolloversActivated(false); 27 | } 28 | } 29 | 30 | } 31 | 32 | @Override 33 | public void allDropTargetsInGroupHit (Field field, DropTargetGroupElement targetGroup) { 34 | // activate ball saver for left and right groups 35 | String id = targetGroup.getElementID(); 36 | if ("DropTargetLeftSave".equals(id)) { 37 | ((WallElement)field.getFieldElementByID("BallSaver-left")).setRetracted(false); 38 | field.showGameMessage("Left Save Enabled", 1500); 39 | } else if ("DropTargetRightSave".equals(id)) { 40 | ((WallElement)field.getFieldElementByID("BallSaver-right")).setRetracted(false); 41 | field.showGameMessage("Right Save Enabled", 1500); 42 | } 43 | // for all groups, increment extra ball rollover 44 | RolloverGroupElement extraBallRollovers = (RolloverGroupElement)field.getFieldElementByID("ExtraBallRollovers"); 45 | if (!extraBallRollovers.allRolloversActive()) { 46 | extraBallRollovers.activateFirstUnactivatedRollover(); 47 | if (extraBallRollovers.allRolloversActive()) { 48 | field.showGameMessage("Shoot Ramp for Extra Ball", 1500); 49 | } 50 | } 51 | } 52 | 53 | // support for enabling launch barrier after ball passes by it and hits sensor, and disabling for new ball or new game 54 | void setLaunchBarrierEnabled (Field field, boolean enabled) { 55 | WallElement barrier = (WallElement)field.getFieldElementByID("LaunchBarrier"); 56 | barrier.setRetracted(!enabled); 57 | } 58 | 59 | @Override 60 | public void ballInSensorRange (Field field, SensorElement sensor) { 61 | // enable launch barrier 62 | if ("LaunchBarrierSensor".equals(sensor.getElementID())) { 63 | setLaunchBarrierEnabled(field, true); 64 | } 65 | } 66 | 67 | @Override 68 | public void gameStarted (Field field) { 69 | setLaunchBarrierEnabled(field, false); 70 | } 71 | 72 | @Override 73 | public void ballLost (Field field) { 74 | setLaunchBarrierEnabled(field, false); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/SensorElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.badlogic.gdx.math.Vector2; 10 | import com.badlogic.gdx.physics.box2d.Body; 11 | import com.badlogic.gdx.physics.box2d.World; 12 | import com.dozingcatsoftware.bouncy.Field; 13 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 14 | 15 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 16 | 17 | /** This FieldElement subclass is used to identify areas on the table that should cause custom behavior when the ball enters. 18 | * SensorElements have no bodies and don't draw anything. The area they monitor can be a rectangle defined by the "rect" parameter 19 | * as a [xmin,ymin,xmax,ymax] list, a circle defined by the "center" and "radius" parameters. During every tick() invocation, a 20 | * sensor determines if any of the field's balls are within its area, and if so calls the field delegate's ballInSensorRange 21 | * method. 22 | * @author brian */ 23 | 24 | public class SensorElement extends FieldElement { 25 | 26 | float xmin, ymin, xmax, ymax; 27 | boolean circular = false; 28 | float cx, cy; // center for circular areas 29 | float radiusSquared; 30 | 31 | @Override 32 | public void finishCreate (Map params, World world) { 33 | if (params.containsKey("center") && params.containsKey("radius")) { 34 | this.circular = true; 35 | List centerPos = (List)params.get("center"); 36 | this.cx = asFloat(centerPos.get(0)); 37 | this.cy = asFloat(centerPos.get(1)); 38 | float radius = asFloat(params.get("radius")); 39 | this.radiusSquared = radius * radius; 40 | // create bounding box to allow rejecting balls without making distance calculations 41 | this.xmin = this.cx - radius / 2; 42 | this.xmax = this.cx + radius / 2; 43 | this.ymin = this.cy - radius / 2; 44 | this.ymax = this.cy + radius / 2; 45 | } else { 46 | List rectPos = (List)params.get("rect"); 47 | this.xmin = asFloat(rectPos.get(0)); 48 | this.ymin = asFloat(rectPos.get(1)); 49 | this.xmax = asFloat(rectPos.get(2)); 50 | this.ymax = asFloat(rectPos.get(3)); 51 | } 52 | } 53 | 54 | @Override 55 | public boolean shouldCallTick () { 56 | return true; 57 | } 58 | 59 | boolean ballInRange (Body ball) { 60 | Vector2 bpos = ball.getPosition(); 61 | // test against rect 62 | if (bpos.x < xmin || bpos.x > xmax || bpos.y < ymin || bpos.y > ymax) { 63 | return false; 64 | } 65 | // if circle, test (squared) distance to center 66 | if (this.circular) { 67 | float distSquared = (bpos.x - this.cx) * (bpos.x - this.cx) + (bpos.y - this.cy) * (bpos.y - this.cy); 68 | if (distSquared > this.radiusSquared) return false; 69 | } 70 | return true; 71 | } 72 | 73 | @Override 74 | public void tick (Field field) { 75 | int len = field.getBalls().size(); 76 | for (int i = 0; i < len; i++) { 77 | Body ball = field.getBalls().get(i); 78 | if (ballInRange(ball)) { 79 | field.getDelegate().ballInSensorRange(field, this); 80 | return; 81 | } 82 | } 83 | } 84 | 85 | @Override 86 | public Collection getBodies () { 87 | return Collections.EMPTY_SET; 88 | } 89 | 90 | @Override 91 | public void draw (IFieldRenderer renderer) { 92 | // no UI 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/Box2DFactory.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import com.badlogic.gdx.math.Vector2; 5 | import com.badlogic.gdx.physics.box2d.Body; 6 | import com.badlogic.gdx.physics.box2d.BodyDef; 7 | import com.badlogic.gdx.physics.box2d.CircleShape; 8 | import com.badlogic.gdx.physics.box2d.FixtureDef; 9 | import com.badlogic.gdx.physics.box2d.PolygonShape; 10 | import com.badlogic.gdx.physics.box2d.World; 11 | 12 | /** Methods to create Box2D shapes. 13 | * @author brian */ 14 | 15 | public class Box2DFactory { 16 | 17 | /** Creates a circle object with the given position and radius. Resitution defaults to 0.6. */ 18 | public static Body createCircle (World world, float x, float y, float radius, boolean isStatic) { 19 | CircleShape sd = new CircleShape(); 20 | sd.setRadius(radius); 21 | 22 | FixtureDef fdef = new FixtureDef(); 23 | fdef.shape = sd; 24 | fdef.density = 1.0f; 25 | fdef.friction = 0.3f; 26 | fdef.restitution = 0.6f; 27 | 28 | BodyDef bd = new BodyDef(); 29 | // bd.isBullet = true; 30 | bd.allowSleep = true; 31 | bd.position.set(x, y); 32 | Body body = world.createBody(bd); 33 | body.createFixture(fdef); 34 | if (isStatic) { 35 | body.setType(BodyDef.BodyType.StaticBody); 36 | } else { 37 | body.setType(BodyDef.BodyType.DynamicBody); 38 | } 39 | return body; 40 | } 41 | 42 | /** Creates a wall by constructing a rectangle whose corners are (xmin,ymin) and (xmax,ymax), and rotating the box 43 | * counterclockwise through the given angle. Restitution defaults to 0.5. */ 44 | public static Body createWall (World world, float xmin, float ymin, float xmax, float ymax, float angle) { 45 | return createWall(world, xmin, ymin, xmax, ymax, angle, 0f); 46 | } 47 | 48 | /** Creates a wall by constructing a rectangle whose corners are (xmin,ymin) and (xmax,ymax), and rotating the box 49 | * counterclockwise through the given angle, with specified restitution. */ 50 | public static Body createWall (World world, float xmin, float ymin, float xmax, float ymax, float angle, float restitution) { 51 | float cx = (xmin + xmax) / 2; 52 | float cy = (ymin + ymax) / 2; 53 | float hx = (xmax - xmin) / 2; 54 | float hy = (ymax - ymin) / 2; 55 | if (hx < 0) hx = -hx; 56 | if (hy < 0) hy = -hy; 57 | PolygonShape wallshape = new PolygonShape(); 58 | wallshape.setAsBox(hx, hy, new Vector2(0f, 0f), angle); 59 | 60 | FixtureDef fdef = new FixtureDef(); 61 | fdef.shape = wallshape; 62 | fdef.density = 1.0f; 63 | if (restitution > 0) fdef.restitution = restitution; 64 | 65 | BodyDef bd = new BodyDef(); 66 | bd.position.set(cx, cy); 67 | Body wall = world.createBody(bd); 68 | wall.createFixture(fdef); 69 | wall.setType(BodyDef.BodyType.StaticBody); 70 | return wall; 71 | } 72 | 73 | /** Creates a segment-like thin wall with 0.05 thickness going from (x1,y1) to (x2,y2) */ 74 | public static Body createThinWall (World world, float x1, float y1, float x2, float y2, float restitution) { 75 | // determine center point and rotation angle for createWall 76 | float cx = (x1 + x2) / 2; 77 | float cy = (y1 + y2) / 2; 78 | float angle = (float)Math.atan2(y2 - y1, x2 - x1); 79 | float mag = (float)Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); 80 | return createWall(world, cx - mag / 2, cy - 0.05f, cx + mag / 2, cy + 0.05f, angle, restitution); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/DropTargetGroupElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import com.badlogic.gdx.physics.box2d.Body; 11 | import com.badlogic.gdx.physics.box2d.World; 12 | import com.dozingcatsoftware.bouncy.Field; 13 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 14 | 15 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 16 | 17 | /** This FieldElement subclass represents a set of drop targets, which are segments that disappear when hit. When all targets are 18 | * hit, the Field delegate is notified, and if the reset parameter is set, the targets will reappear after a delay. 19 | * @author brian */ 20 | 21 | public class DropTargetGroupElement extends FieldElement { 22 | 23 | // store all bodies and positions, use Body's active flag to determine which targets have been hit 24 | List allBodies = new ArrayList(); 25 | Map bodyPositions = new HashMap(); 26 | 27 | @Override 28 | public void finishCreate (Map params, World world) { 29 | // individual targets are specified in "positions" list 30 | List positions = (List)params.get("positions"); 31 | for (List pos : positions) { 32 | float[] parray = new float[] {asFloat(pos.get(0)), asFloat(pos.get(1)), asFloat(pos.get(2)), asFloat(pos.get(3))}; 33 | float restitution = 0f; 34 | Body wallBody = Box2DFactory.createThinWall(world, parray[0], parray[1], parray[2], parray[3], restitution); 35 | allBodies.add(wallBody); 36 | bodyPositions.put(wallBody, parray); 37 | } 38 | } 39 | 40 | @Override 41 | public Collection getBodies () { 42 | return allBodies; 43 | } 44 | 45 | /** Returns true if all targets have been hit (and their corresponding bodies made inactive) */ 46 | public boolean allTargetsHit () { 47 | for (Body body : allBodies) { 48 | if (body.isActive()) return false; 49 | } 50 | return true; 51 | } 52 | 53 | @Override 54 | public void handleCollision (Body ball, Body bodyHit, final Field field) { 55 | bodyHit.setActive(false); 56 | // if all hit, notify delegate and check for reset parameter 57 | if (allTargetsHit()) { 58 | field.getDelegate().allDropTargetsInGroupHit(field, this); 59 | 60 | float restoreTime = asFloat(this.parameters.get("reset")); 61 | if (restoreTime > 0) { 62 | field.scheduleAction((long)(restoreTime * 1000), new Runnable() { 63 | public void run () { 64 | makeAllTargetsVisible(field); 65 | } 66 | }); 67 | } 68 | } 69 | } 70 | 71 | /** Makes all targets visible by calling Body.setActive(true) on each target body */ 72 | public void makeAllTargetsVisible (Field field) { 73 | for (Body body : allBodies) { 74 | body.setActive(true); 75 | } 76 | } 77 | 78 | @Override 79 | public void draw (IFieldRenderer renderer) { 80 | // draw line for each target 81 | int r = redColorComponent(0); 82 | int g = greenColorComponent(255); 83 | int b = blueColorComponent(0); 84 | 85 | int len = allBodies.size(); 86 | for (int i = 0; i < len; i++) { 87 | Body body = allBodies.get(i); 88 | if (body.isActive()) { 89 | float[] parray = bodyPositions.get(body); 90 | renderer.drawLine(parray[0], parray[1], parray[2], parray[3], r, g, b); 91 | } 92 | } 93 | 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/JSONStringer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | /* 5 | Copyright (c) 2006 JSON.org 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | The Software shall be used for Good, not Evil. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | */ 27 | 28 | import java.io.StringWriter; 29 | 30 | /** JSONStringer provides a quick and convenient way of producing JSON text. The texts produced strictly conform to JSON syntax 31 | * rules. No whitespace is added, so the results are ready for transmission or storage. Each instance of JSONStringer can produce 32 | * one JSON text. 33 | *

34 | * A JSONStringer instance provides a value method for appending values to the text, and a key method 35 | * for adding keys before values in objects. There are array and endArray methods that make and bound 36 | * array values, and object and endObject methods which make and bound object values. All of these 37 | * methods return the JSONWriter instance, permitting cascade style. For example, 38 | * 39 | *

40 |  * myString = new JSONStringer().object().key("JSON").value("Hello, World!").endObject().toString();
41 |  * 
42 | * 43 | * which produces the string 44 | * 45 | *
46 |  * {"JSON":"Hello, World!"}
47 |  * 
48 | *

49 | * The first method called must be array or object. There are no methods for adding commas or colons. 50 | * JSONStringer adds them for you. Objects and arrays can be nested up to 20 levels deep. 51 | *

52 | * This can sometimes be easier than using a JSONObject to build a string. 53 | * @author JSON.org 54 | * @version 2008-09-18 */ 55 | public class JSONStringer extends JSONWriter { 56 | /** Make a fresh JSONStringer. It can be used to build one JSON text. */ 57 | public JSONStringer () { 58 | super(new StringWriter()); 59 | } 60 | 61 | /** Return the JSON text. This method is used to obtain the product of the JSONStringer instance. It will return 62 | * null if there was a problem in the construction of the JSON text (such as the calls to array were 63 | * not properly balanced with calls to endArray). 64 | * @return The JSON text. */ 65 | public String toString () { 66 | return this.mode == 'd' ? this.writer.toString() : null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/Bouncy.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | import com.badlogic.gdx.ApplicationListener; 5 | import com.badlogic.gdx.Gdx; 6 | import com.badlogic.gdx.InputAdapter; 7 | import com.badlogic.gdx.graphics.GL20; 8 | import com.badlogic.gdx.graphics.OrthographicCamera; 9 | import com.badlogic.gdx.math.WindowedMean; 10 | import com.badlogic.gdx.utils.TimeUtils; 11 | 12 | import com.dozingcatsoftware.bouncy.elements.FieldElement; 13 | 14 | public class Bouncy extends InputAdapter implements ApplicationListener { 15 | OrthographicCamera cam; 16 | GLFieldRenderer renderer; 17 | Field field; 18 | int level = 1; 19 | WindowedMean physicsMean = new WindowedMean(10); 20 | WindowedMean renderMean = new WindowedMean(10); 21 | long startTime = TimeUtils.nanoTime(); 22 | 23 | @Override 24 | public void create () { 25 | cam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 26 | renderer = new GLFieldRenderer(); 27 | field = new Field(); 28 | field.resetForLevel(level); 29 | Gdx.input.setInputProcessor(this); 30 | } 31 | 32 | @Override 33 | public void resume () { 34 | 35 | } 36 | 37 | @Override 38 | public void render () { 39 | GL20 gl = Gdx.gl; 40 | 41 | long startPhysics = TimeUtils.nanoTime(); 42 | field.tick((long)(Gdx.graphics.getDeltaTime() * 3000), 4); 43 | physicsMean.addValue((TimeUtils.nanoTime() - startPhysics) / 1000000000.0f); 44 | 45 | gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 46 | cam.viewportWidth = field.getWidth(); 47 | cam.viewportHeight = field.getHeight(); 48 | cam.position.set(field.getWidth() / 2, field.getHeight() / 2, 0); 49 | cam.update(); 50 | renderer.setProjectionMatrix(cam.combined); 51 | 52 | long startRender = TimeUtils.nanoTime(); 53 | renderer.begin(); 54 | int len = field.getFieldElements().size(); 55 | for (int i = 0; i < len; i++) { 56 | FieldElement element = field.getFieldElements().get(i); 57 | element.draw(renderer); 58 | } 59 | renderer.end(); 60 | 61 | renderer.begin(); 62 | field.drawBalls(renderer); 63 | renderer.end(); 64 | renderMean.addValue((TimeUtils.nanoTime() - startRender) / 1000000000.0f); 65 | 66 | if (TimeUtils.nanoTime() - startTime > 1000000000) { 67 | Gdx.app.log("Bouncy", "fps: " + Gdx.graphics.getFramesPerSecond() + ", physics: " + physicsMean.getMean() * 1000 68 | + ", rendering: " + renderMean.getMean() * 1000); 69 | startTime = TimeUtils.nanoTime(); 70 | } 71 | } 72 | 73 | @Override 74 | public void resize (int width, int height) { 75 | 76 | } 77 | 78 | @Override 79 | public void pause () { 80 | 81 | } 82 | 83 | @Override 84 | public void dispose () { 85 | 86 | } 87 | 88 | @Override 89 | public boolean touchDown (int x, int y, int pointer, int button) { 90 | field.removeDeadBalls(); 91 | if (field.getBalls().size() != 0) field.setAllFlippersEngaged(true); 92 | return false; 93 | } 94 | 95 | @Override 96 | public boolean touchUp (int x, int y, int pointer, int button) { 97 | field.removeDeadBalls(); 98 | if (field.getBalls().size() == 0) field.launchBall(); 99 | field.setAllFlippersEngaged(false); 100 | return false; 101 | } 102 | 103 | @Override 104 | public boolean touchDragged (int x, int y, int pointer) { 105 | if (field.getBalls().size() != 0) field.setAllFlippersEngaged(true); 106 | return false; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/WallArcElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.badlogic.gdx.physics.box2d.Body; 10 | import com.badlogic.gdx.physics.box2d.World; 11 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 12 | 13 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 14 | 15 | /** This FieldElement subclass approximates a circular wall with a series of straight wall segments whose endpoints lie on a circle 16 | * or ellipse. These elements are defined in the layout JSON as follows: { "class": "WallArcElement", "center": [5.5, 10], // 17 | * center of circle or ellipse "xradius": 2.5, // radius in the horizontal direction "yradius": 2, // radius in the y direction 18 | * "minangle": 45, // starting angle in degrees, 0 is to the right of the center, 90 is up. "maxangle": 135, // ending angle in 19 | * degrees "segments": 10, // number of straight wall segments to use to approximate the arc. "color": [255,0,0] // optional RGB 20 | * values for the arc's color } 21 | * 22 | * For circular walls, the "radius" attribute can be used instead of xradius and yradius. 23 | * 24 | * @author brian */ 25 | 26 | public class WallArcElement extends FieldElement { 27 | 28 | public List wallBodies = new ArrayList(); 29 | List lineSegments = new ArrayList(); 30 | 31 | public void finishCreate (Map params, World world) { 32 | List centerPos = (List)params.get("center"); 33 | float cx = asFloat(centerPos.get(0)); 34 | float cy = asFloat(centerPos.get(1)); 35 | 36 | // can specify "radius" for circle, or "xradius" and "yradius" for ellipse 37 | float xradius, yradius; 38 | if (params.containsKey("radius")) { 39 | xradius = yradius = asFloat(params.get("radius")); 40 | } else { 41 | xradius = asFloat(params.get("xradius")); 42 | yradius = asFloat(params.get("yradius")); 43 | } 44 | 45 | Number segments = (Number)params.get("segments"); 46 | int numsegments = (segments != null) ? segments.intValue() : 5; 47 | float minangle = toRadians(asFloat(params.get("minangle"))); 48 | float maxangle = toRadians(asFloat(params.get("maxangle"))); 49 | float diff = maxangle - minangle; 50 | // create numsegments line segments to approximate circular arc 51 | for (int i = 0; i < numsegments; i++) { 52 | float angle1 = minangle + i * diff / numsegments; 53 | float angle2 = minangle + (i + 1) * diff / numsegments; 54 | float x1 = cx + xradius * (float)Math.cos(angle1); 55 | float y1 = cy + yradius * (float)Math.sin(angle1); 56 | float x2 = cx + xradius * (float)Math.cos(angle2); 57 | float y2 = cy + yradius * (float)Math.sin(angle2); 58 | 59 | Body wall = Box2DFactory.createThinWall(world, x1, y1, x2, y2, 0f); 60 | this.wallBodies.add(wall); 61 | lineSegments.add(new float[] {x1, y1, x2, y2}); 62 | } 63 | } 64 | 65 | @Override 66 | public Collection getBodies () { 67 | return wallBodies; 68 | } 69 | 70 | @Override 71 | public void draw (IFieldRenderer renderer) { 72 | int len = lineSegments.size(); 73 | for (int i = 0; i < len; i++) { 74 | float[] segment = lineSegments.get(i); 75 | renderer.drawLine(segment[0], segment[1], segment[2], segment[3], redColorComponent(DEFAULT_WALL_RED), 76 | greenColorComponent(DEFAULT_WALL_GREEN), blueColorComponent(DEFAULT_WALL_BLUE)); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/WallElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.badlogic.gdx.math.Vector2; 10 | import com.badlogic.gdx.physics.box2d.Body; 11 | import com.badlogic.gdx.physics.box2d.World; 12 | import com.dozingcatsoftware.bouncy.Field; 13 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 14 | 15 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 16 | 17 | /** FieldElement subclass that represents a straight wall. Its position is specified by the "position" parameter with 4 values, 18 | * which are [start x, start y, end x, end y]. There are several optional parameters to customize the wall's behavior: "kick": 19 | * impulse to apply when a ball hits the wall, used for kickers and ball savers. "kill": if true, the ball is lost when it hits 20 | * the wall. Used for invisible wall below the flippers. "retractWhenHit": if true, the wall is removed when hit by a ball. Used 21 | * for ball savers. "disabled": if true, the wall starts out retracted, and will only be shown when setRetracted(field, true) is 22 | * called. 23 | * 24 | * Walls can be removed from the field by calling setRetracted(field, true), and restored with setRetracted(field, false). 25 | * 26 | * @author brian */ 27 | 28 | public class WallElement extends FieldElement { 29 | 30 | Body wallBody; 31 | Collection bodySet; 32 | float x1, y1, x2, y2; 33 | float kick; 34 | 35 | boolean killBall; 36 | boolean retractWhenHit; 37 | 38 | @Override 39 | public void finishCreate (Map params, World world) { 40 | List pos = (List)params.get("position"); 41 | this.x1 = asFloat(pos.get(0)); 42 | this.y1 = asFloat(pos.get(1)); 43 | this.x2 = asFloat(pos.get(2)); 44 | this.y2 = asFloat(pos.get(3)); 45 | float restitution = asFloat(params.get("restitution")); 46 | 47 | wallBody = Box2DFactory.createThinWall(world, x1, y1, x2, y2, restitution); 48 | bodySet = Collections.singleton(wallBody); 49 | 50 | this.kick = asFloat(params.get("kick")); 51 | this.killBall = (Boolean.TRUE.equals(params.get("kill"))); 52 | this.retractWhenHit = (Boolean.TRUE.equals(params.get("retractWhenHit"))); 53 | 54 | boolean disabled = Boolean.TRUE.equals(params.get("disabled")); 55 | if (disabled) { 56 | setRetracted(true); 57 | } 58 | } 59 | 60 | public boolean isRetracted () { 61 | return !wallBody.isActive(); 62 | } 63 | 64 | public void setRetracted (boolean retracted) { 65 | if (retracted != this.isRetracted()) { 66 | wallBody.setActive(!retracted); 67 | } 68 | } 69 | 70 | @Override 71 | public Collection getBodies () { 72 | return bodySet; 73 | } 74 | 75 | @Override 76 | public boolean shouldCallTick () { 77 | // tick() only needs to be called if this wall provides a kick which makes it flash 78 | return (this.kick > 0.01f); 79 | } 80 | 81 | Vector2 impulseForBall (Body ball) { 82 | if (this.kick <= 0.01f) return null; 83 | // rotate wall direction 90 degrees for normal, choose direction toward ball 84 | float ix = this.y2 - this.y1; 85 | float iy = this.x1 - this.x2; 86 | float mag = (float)Math.sqrt(ix * ix + iy * iy); 87 | float scale = this.kick / mag; 88 | ix *= scale; 89 | iy *= scale; 90 | 91 | // dot product of (ball center - wall center) and impulse direction should be positive, if not flip impulse 92 | Vector2 balldiff = ball.getWorldCenter().cpy().sub(this.x1, this.y1); 93 | float dotprod = balldiff.x * ix + balldiff.y * iy; 94 | if (dotprod < 0) { 95 | ix = -ix; 96 | iy = -iy; 97 | } 98 | 99 | return new Vector2(ix, iy); 100 | } 101 | 102 | @Override 103 | public void handleCollision (Body ball, Body bodyHit, Field field) { 104 | if (retractWhenHit) { 105 | this.setRetracted(true); 106 | } 107 | 108 | if (killBall) { 109 | field.removeBall(ball); 110 | } else { 111 | Vector2 impulse = this.impulseForBall(ball); 112 | if (impulse != null) { 113 | ball.applyLinearImpulse(impulse, ball.getWorldCenter(), true); 114 | flashForFrames(3); 115 | } 116 | } 117 | } 118 | 119 | @Override 120 | public void draw (IFieldRenderer renderer) { 121 | if (isRetracted()) return; 122 | renderer.drawLine(x1, y1, x2, y2, redColorComponent(DEFAULT_WALL_RED), greenColorComponent(DEFAULT_WALL_GREEN), 123 | blueColorComponent(DEFAULT_WALL_BLUE)); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | android { 2 | buildToolsVersion project.androidToolsVersion 3 | compileSdkVersion project.androidSDKVersion.toInteger() 4 | sourceSets { 5 | main { 6 | manifest.srcFile 'AndroidManifest.xml' 7 | java.srcDirs = ['src'] 8 | aidl.srcDirs = ['src'] 9 | renderscript.srcDirs = ['src'] 10 | res.srcDirs = ['res'] 11 | assets.srcDirs = ['assets'] 12 | jniLibs.srcDirs = ['libs'] 13 | } 14 | } 15 | packagingOptions { 16 | exclude 'META-INF/robovm/ios/robovm.xml' 17 | } 18 | defaultConfig { 19 | applicationId "com.dozingcatsoftware.bouncy.android" 20 | minSdkVersion 14 21 | targetSdkVersion 30 22 | versionCode 1 23 | versionName "1.0" 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled true 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | } 32 | 33 | // called every time gradle gets executed, takes the native dependencies of 34 | // the natives configuration, and extracts them to the proper libs/ folders 35 | // so they get packed with the APK. 36 | task copyAndroidNatives { 37 | doFirst { 38 | file("libs/armeabi-v7a/").mkdirs() 39 | file("libs/arm64-v8a/").mkdirs() 40 | file("libs/x86_64/").mkdirs() 41 | file("libs/x86/").mkdirs() 42 | 43 | configurations.natives.files.each { jar -> 44 | def outputDir = null 45 | if (jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a") 46 | if (jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a") 47 | if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64") 48 | if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86") 49 | if(outputDir != null) { 50 | copy { 51 | from zipTree(jar) 52 | into outputDir 53 | include "*.so" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | tasks.whenTaskAdded { packageTask -> 61 | if (packageTask.name.contains("package")) { 62 | packageTask.dependsOn 'copyAndroidNatives' 63 | } 64 | } 65 | 66 | task run(type: Exec) { 67 | def path 68 | def localProperties = project.file("../local.properties") 69 | if (localProperties.exists()) { 70 | Properties properties = new Properties() 71 | localProperties.withInputStream { instr -> 72 | properties.load(instr) 73 | } 74 | def sdkDir = properties.getProperty('sdk.dir') 75 | if (sdkDir) { 76 | path = sdkDir 77 | } else { 78 | path = "$System.env.ANDROID_HOME" 79 | } 80 | } else { 81 | path = "$System.env.ANDROID_HOME" 82 | } 83 | 84 | def adb = path + "/platform-tools/adb" 85 | commandLine "$adb", 'shell', 'am', 'start', '-n', 'com.dozingcatsoftware.bouncy.android/com.dozingcatsoftware.bouncy.android.AndroidLauncher' 86 | } 87 | 88 | // sets up the Android Eclipse project, using the old Ant based build. 89 | eclipse { 90 | // need to specify Java source sets explicitely, SpringSource Gradle Eclipse plugin 91 | // ignores any nodes added in classpath.file.withXml 92 | sourceSets { 93 | main { 94 | java.srcDirs "src", 'gen' 95 | } 96 | } 97 | 98 | jdt { 99 | sourceCompatibility = 1.6 100 | targetCompatibility = 1.6 101 | } 102 | 103 | classpath { 104 | plusConfigurations += [ project.configurations.compile ] 105 | containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES' 106 | } 107 | 108 | project { 109 | name = appName + "-android" 110 | natures 'com.android.ide.eclipse.adt.AndroidNature' 111 | buildCommands.clear(); 112 | buildCommand "com.android.ide.eclipse.adt.ResourceManagerBuilder" 113 | buildCommand "com.android.ide.eclipse.adt.PreCompilerBuilder" 114 | buildCommand "org.eclipse.jdt.core.javabuilder" 115 | buildCommand "com.android.ide.eclipse.adt.ApkBuilder" 116 | } 117 | } 118 | 119 | // sets up the Android Idea project, using the old Ant based build. 120 | idea { 121 | module { 122 | sourceDirs += file("src"); 123 | scopes = [ COMPILE: [plus:[project.configurations.compile]]] 124 | 125 | iml { 126 | withXml { 127 | def node = it.asNode() 128 | def builder = NodeBuilder.newInstance(); 129 | builder.current = node; 130 | builder.component(name: "FacetManager") { 131 | facet(type: "android", name: "Android") { 132 | configuration { 133 | option(name: "UPDATE_PROPERTY_FILES", value:"true") 134 | } 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/FieldLayout.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | import java.io.BufferedReader; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Random; 13 | 14 | import com.badlogic.gdx.Gdx; 15 | import com.badlogic.gdx.physics.box2d.World; 16 | import com.dozingcatsoftware.bouncy.elements.FieldElement; 17 | import com.dozingcatsoftware.bouncy.elements.FlipperElement; 18 | import com.dozingcatsoftware.bouncy.util.JSONUtils; 19 | 20 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 21 | 22 | public class FieldLayout { 23 | 24 | static List _layoutArray; 25 | Random RAND = new Random(); 26 | 27 | static void readLayoutArray () { 28 | try { 29 | InputStream fin = Gdx.files.internal("data/field_layout.json").read(); 30 | BufferedReader br = new BufferedReader(new InputStreamReader(fin)); 31 | 32 | StringBuilder buffer = new StringBuilder(); 33 | String line; 34 | while ((line = br.readLine()) != null) { 35 | buffer.append(line); 36 | } 37 | fin.close(); 38 | _layoutArray = JSONUtils.listFromJSONString(buffer.toString()); 39 | } catch (Exception ex) { 40 | ex.printStackTrace(); 41 | } 42 | } 43 | 44 | public static FieldLayout layoutForLevel (int level, World world) { 45 | try { 46 | if (_layoutArray == null) readLayoutArray(); 47 | Map layoutMap = (Map)_layoutArray.get(level - 1); 48 | return new FieldLayout(layoutMap, world); 49 | } catch (Exception ex) { 50 | ex.printStackTrace(); 51 | return null; 52 | } 53 | } 54 | 55 | public static int numberOfLevels () { 56 | return _layoutArray.size(); 57 | } 58 | 59 | List fieldElements = new ArrayList(); 60 | List flippers; 61 | float width; 62 | float height; 63 | List ballColor; 64 | float targetTimeRatio; 65 | Map allParameters; 66 | 67 | static List DEFAULT_BALL_COLOR = Arrays.asList(255, 0, 0); 68 | 69 | static List listForKey (Map map, String key) { 70 | if (map.containsKey(key)) return (List)map.get(key); 71 | return Collections.EMPTY_LIST; 72 | } 73 | 74 | List addFieldElements (Map layoutMap, String key, Class defaultClass, World world) { 75 | List elements = new ArrayList(); 76 | for (Object obj : listForKey(layoutMap, key)) { 77 | // allow strings in JSON for comments 78 | if (!(obj instanceof Map)) continue; 79 | Map params = (Map)obj; 80 | elements.add(FieldElement.createFromParameters(params, world, defaultClass)); 81 | } 82 | fieldElements.addAll(elements); 83 | return elements; 84 | } 85 | 86 | public FieldLayout (Map layoutMap, World world) { 87 | this.width = asFloat(layoutMap.get("width"), 20.0f); 88 | this.height = asFloat(layoutMap.get("height"), 30.0f); 89 | this.targetTimeRatio = asFloat(layoutMap.get("targetTimeRatio")); 90 | this.ballColor = (layoutMap.containsKey("ballcolor")) ? (List)layoutMap.get("ballcolor") : DEFAULT_BALL_COLOR; 91 | this.allParameters = layoutMap; 92 | 93 | flippers = addFieldElements(layoutMap, "flippers", FlipperElement.class, world); 94 | 95 | addFieldElements(layoutMap, "elements", null, world); 96 | } 97 | 98 | public List getFieldElements () { 99 | return fieldElements; 100 | } 101 | 102 | public List getFlipperElements () { 103 | return flippers; 104 | } 105 | 106 | public float getBallRadius () { 107 | return asFloat(allParameters.get("ballradius"), 0.5f); 108 | } 109 | 110 | public List getBallColor () { 111 | return ballColor; 112 | } 113 | 114 | public int getNumberOfBalls () { 115 | return (allParameters.containsKey("numballs")) ? ((Number)allParameters.get("numballs")).intValue() : 3; 116 | } 117 | 118 | public List getLaunchPosition () { 119 | Map launchMap = (Map)allParameters.get("launch"); 120 | return (List)launchMap.get("position"); 121 | } 122 | 123 | // can apply random velocity increment if specified by "random_velocity" key 124 | public List getLaunchVelocity () { 125 | Map launchMap = (Map)allParameters.get("launch"); 126 | List velocity = (List)launchMap.get("velocity"); 127 | float vx = velocity.get(0).floatValue(); 128 | float vy = velocity.get(1).floatValue(); 129 | 130 | if (launchMap.containsKey("random_velocity")) { 131 | List delta = (List)launchMap.get("random_velocity"); 132 | if (delta.get(0).floatValue() > 0) vx += delta.get(0).floatValue() * RAND.nextFloat(); 133 | if (delta.get(1).floatValue() > 0) vy += delta.get(1).floatValue() * RAND.nextFloat(); 134 | } 135 | return Arrays.asList(vx, vy); 136 | } 137 | 138 | public float getWidth () { 139 | return width; 140 | } 141 | 142 | public float getHeight () { 143 | return height; 144 | } 145 | 146 | /** Returns the desired ratio between real world time and simulation time. The application should adjust the frame rate and/or 147 | * time interval passed to Field.tick() to keep the ratio as close to this value as possible. */ 148 | public float getTargetTimeRatio () { 149 | return targetTimeRatio; 150 | } 151 | 152 | /** Returns the magnitude of the gravity vector. */ 153 | public float getGravity () { 154 | return asFloat(allParameters.get("gravity"), 4.0f); 155 | } 156 | 157 | public String getDelegateClassName () { 158 | return (String)allParameters.get("delegate"); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/FlipperElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.badlogic.gdx.math.Vector2; 10 | import com.badlogic.gdx.physics.box2d.Body; 11 | import com.badlogic.gdx.physics.box2d.BodyDef; 12 | import com.badlogic.gdx.physics.box2d.World; 13 | import com.badlogic.gdx.physics.box2d.joints.RevoluteJoint; 14 | import com.badlogic.gdx.physics.box2d.joints.RevoluteJointDef; 15 | import com.dozingcatsoftware.bouncy.Field; 16 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 17 | 18 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 19 | 20 | /** FieldElement subclass for a flipper that is controlled by the player. A flipper consists of a Box2D RevoluteJoint where a thin 21 | * wall rotates around an invisible anchor. Flippers are defined in the layout JSON as follows: { "class": "FlipperElement", 22 | * "position": [5.5, 10], // x,y of fixed end of flipper which it rotates around "length": 2.5, // length of the flipper. Negative 23 | * if the flipper rotates around its right end. "minangle": -20, // minimum angle from the horizontal. Negative angles are below 24 | * horizontal. "maxangle": 20, // maximum angle from the horizontal. "upspeed": 7, // rate at which the flipper rotates up when 25 | * activated (in radians/sec?) "downspeed": 3 // rate at which the flipper rotates down when not activated (in radians/sec?) } 26 | * 27 | * @author brian */ 28 | 29 | public class FlipperElement extends FieldElement { 30 | 31 | Body flipperBody; 32 | Collection flipperBodySet; 33 | public Body anchorBody; 34 | public RevoluteJoint joint; 35 | RevoluteJointDef jointDef; 36 | 37 | float flipperLength; // negative if flipper rotates around its right end 38 | float upspeed, downspeed; 39 | float minangle, maxangle; 40 | float cx, cy; 41 | 42 | @Override 43 | public void finishCreate (Map params, World world) { 44 | List pos = (List)params.get("position"); 45 | 46 | this.cx = asFloat(pos.get(0)); 47 | this.cy = asFloat(pos.get(1)); 48 | this.flipperLength = asFloat(params.get("length")); 49 | this.minangle = toRadians(asFloat(params.get("minangle"))); 50 | this.maxangle = toRadians(asFloat(params.get("maxangle"))); 51 | this.upspeed = asFloat(params.get("upspeed")); 52 | this.downspeed = asFloat(params.get("downspeed")); 53 | 54 | this.anchorBody = Box2DFactory.createCircle(world, this.cx, this.cy, 0.05f, true); 55 | // joint angle is 0 when flipper is horizontal 56 | // flipper needs to be slightly extended past anchorBody to rotate correctly 57 | float ext = (this.flipperLength > 0) ? -0.05f : +0.05f; 58 | // width larger than 0.12 slows rotation? 59 | this.flipperBody = Box2DFactory.createWall(world, cx + ext, cy - 0.12f, cx + flipperLength, cy + 0.12f, 0f); 60 | flipperBody.setType(BodyDef.BodyType.DynamicBody); 61 | flipperBody.setBullet(true); 62 | flipperBody.getFixtureList().get(0).setDensity(5.0f); 63 | 64 | jointDef = new RevoluteJointDef(); 65 | jointDef.initialize(anchorBody, flipperBody, new Vector2(this.cx, this.cy)); 66 | jointDef.enableLimit = true; 67 | jointDef.enableMotor = true; 68 | // counterclockwise rotations are positive, so flip angles for flippers extending left 69 | jointDef.lowerAngle = (this.flipperLength > 0) ? this.minangle : -this.maxangle; 70 | jointDef.upperAngle = (this.flipperLength > 0) ? this.maxangle : -this.minangle; 71 | jointDef.maxMotorTorque = 1000f; 72 | 73 | this.joint = (RevoluteJoint)world.createJoint(jointDef); 74 | 75 | flipperBodySet = Collections.singleton(flipperBody); 76 | this.setEffectiveMotorSpeed(-this.downspeed); // force flipper to bottom when field is first created 77 | } 78 | 79 | /** Returns true if the flipper rotates around its right end, which requires negating some values. */ 80 | boolean isReversed () { 81 | return (flipperLength < 0); 82 | } 83 | 84 | /** Returns the motor speed of the Box2D joint, normalized to be positive when the flipper is moving up. */ 85 | float getEffectiveMotorSpeed () { 86 | float speed = joint.getMotorSpeed(); 87 | return (isReversed()) ? -speed : speed; 88 | } 89 | 90 | /** Sets the motor speed of the Box2D joint, positive values move the flipper up. */ 91 | void setEffectiveMotorSpeed (float speed) { 92 | if (isReversed()) speed = -speed; 93 | joint.setMotorSpeed(speed); 94 | } 95 | 96 | @Override 97 | public Collection getBodies () { 98 | return flipperBodySet; 99 | } 100 | 101 | @Override 102 | public boolean shouldCallTick () { 103 | return true; 104 | } 105 | 106 | @Override 107 | public void tick (Field field) { 108 | super.tick(field); 109 | 110 | // if angle is at maximum, reduce speed so that the ball won't fly off when it hits 111 | if (getEffectiveMotorSpeed() > 0.5f) { 112 | float topAngle = (isReversed()) ? jointDef.lowerAngle : jointDef.upperAngle; 113 | if (Math.abs(topAngle - joint.getJointAngle()) < 0.05) { 114 | setEffectiveMotorSpeed(0.5f); 115 | } 116 | } 117 | } 118 | 119 | public boolean isFlipperEngaged () { 120 | return getEffectiveMotorSpeed() > 0; 121 | } 122 | 123 | public void setFlipperEngaged (boolean active) { 124 | // only adjust speed if state is changing, so we don't accelerate flipper that's been slowed down in tick() 125 | if (active != this.isFlipperEngaged()) { 126 | float speed = (active) ? upspeed : -downspeed; 127 | setEffectiveMotorSpeed(speed); 128 | } 129 | } 130 | 131 | public float getFlipperLength () { 132 | return flipperLength; 133 | } 134 | 135 | public RevoluteJoint getJoint () { 136 | return joint; 137 | } 138 | 139 | public Body getAnchorBody () { 140 | return anchorBody; 141 | } 142 | 143 | @Override 144 | public void draw (IFieldRenderer renderer) { 145 | // draw single line segment from anchor point 146 | Vector2 position = anchorBody.getPosition(); 147 | float angle = joint.getJointAngle(); 148 | // HACK: angle can briefly get out of range, always draw between min and max 149 | if (angle < jointDef.lowerAngle) angle = jointDef.lowerAngle; 150 | if (angle > jointDef.upperAngle) angle = jointDef.upperAngle; 151 | float x1 = position.x; 152 | float y1 = position.y; 153 | float x2 = position.x + flipperLength * (float)Math.cos(angle); 154 | float y2 = position.y + flipperLength * (float)Math.sin(angle); 155 | 156 | renderer.drawLine(x1, y1, x2, y2, redColorComponent(0), greenColorComponent(255), blueColorComponent(0)); 157 | 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/FieldElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import com.badlogic.gdx.physics.box2d.Body; 9 | import com.badlogic.gdx.physics.box2d.World; 10 | import com.dozingcatsoftware.bouncy.Field; 11 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 12 | 13 | /** Abstract superclass of all elements in the pinball field, such as walls, bumpers, and flippers. 14 | * @author brian */ 15 | 16 | public abstract class FieldElement { 17 | 18 | Map parameters; 19 | World box2dWorld; 20 | String elementID; 21 | int[] color; // 3-element r,g,b values between 0 and 255 22 | 23 | int flashCounter = 0; // when >0, inverts colors (e.g. after being hit by the ball), decrements in tick() 24 | long score = 0; 25 | 26 | // default wall color shared by WallElement, WallArcElement, WallPathElement 27 | static int DEFAULT_WALL_RED = 64; 28 | static int DEFAULT_WALL_GREEN = 64; 29 | static int DEFAULT_WALL_BLUE = 160; 30 | 31 | /** Creates and returns a FieldElement object from the given map of parameters. The default class to instantiate is an argument 32 | * to this method, and can be overridden by the "class" property of the parameter map. Calls the no-argument constructor of the 33 | * default or custom class, and then calls initialize() passing the parameter map and World. */ 34 | public static FieldElement createFromParameters (Map params, World world, Class defaultClass) { 35 | try { 36 | FieldElement self = null; 37 | if (params.containsKey("class")) { 38 | // if package not specified, use this package 39 | String className = (String)params.get("class"); 40 | if (className.contains("BumperElement")) self = new BumperElement(); 41 | if (className.contains("DropTargetGroupElement")) self = new DropTargetGroupElement(); 42 | if (className.contains("FlipperElement")) self = new FlipperElement(); 43 | if (className.contains("RolloverGroupElement")) self = new RolloverGroupElement(); 44 | if (className.contains("SensorElement")) self = new SensorElement(); 45 | if (className.contains("WallArcElement")) self = new WallArcElement(); 46 | if (className.contains("WallElement")) self = new WallElement(); 47 | if (className.contains("WallPathElement")) self = new WallPathElement(); 48 | } else { 49 | self = new FlipperElement(); 50 | } 51 | self.initialize(params, world); 52 | return self; 53 | } catch (Exception ex) { 54 | throw new RuntimeException(ex); 55 | } 56 | } 57 | 58 | /** Extracts common values from the definition parameter map, and calls finishCreate to allow subclasses to further initialize 59 | * themselves. Subclasses should override finishCreate, and should not override this method. */ 60 | public void initialize (Map params, World world) { 61 | this.parameters = params; 62 | this.box2dWorld = world; 63 | this.elementID = (String)params.get("id"); 64 | 65 | List colorList = (List)params.get("color"); 66 | if (colorList != null) { 67 | this.color = new int[] {colorList.get(0), colorList.get(1), colorList.get(2)}; 68 | } 69 | 70 | if (params.containsKey("score")) { 71 | this.score = ((Number)params.get("score")).longValue(); 72 | } 73 | 74 | this.finishCreate(params, world); 75 | } 76 | 77 | /** Called after creation to determine if tick() needs to be called after every frame is simulated. Default returns false, 78 | * subclasses must override to return true in order for tick() to be called. This is an optimization to avoid needless method 79 | * calls in the game loop. */ 80 | public boolean shouldCallTick () { 81 | return false; 82 | } 83 | 84 | /** Called on every update from Field.tick. Default implementation decrements flash counter if active, subclasses can override 85 | * to perform additional processing, e.g. RolloverGroupElement checking for balls within radius of rollovers. Subclasses should 86 | * call super.tick(field). */ 87 | public void tick (Field field) { 88 | if (flashCounter > 0) flashCounter--; 89 | } 90 | 91 | /** Called when the player activates one or more flippers. The default implementation does nothing; subclasses can override. */ 92 | public void flipperActivated (Field field) { 93 | 94 | } 95 | 96 | /** Causes the colors returned by red/blue/greenColorComponent methods to be inverted for the given number of frames. This can 97 | * be used to flash an element when it is hit by a ball, see PegElement. */ 98 | public void flashForFrames (int frames) { 99 | flashCounter = frames; 100 | } 101 | 102 | /** Must be overridden by subclasses, which should perform any setup required after creation. */ 103 | public abstract void finishCreate (Map params, World world); 104 | 105 | /** Must be overridden by subclasses to return a collection of all Box2D bodies which make up this element. */ 106 | public abstract Collection getBodies (); 107 | 108 | /** Must be overridden by subclasses to draw the element, using IFieldRenderer methods. */ 109 | public abstract void draw (IFieldRenderer renderer); 110 | 111 | /** Called when a ball collides with a Body in this element. The default implementation does nothing (allowing objects to bounce 112 | * off each other normally), subclasses can override (e.g. to apply extra force) */ 113 | public void handleCollision (Body ball, Body bodyHit, Field field) { 114 | } 115 | 116 | /** Returns this element's ID as specified in the JSON definition, or null if the ID is not specified. */ 117 | public String getElementID () { 118 | return elementID; 119 | } 120 | 121 | /** Returns the parameter map from which this element was created. */ 122 | public Map getParameters () { 123 | return parameters; 124 | } 125 | 126 | /** Returns the "score" value for this element. The score is automatically added when the element is hit by a ball, and elements 127 | * may apply scores under other conditions, e.g. RolloverGroupElement adds the score when a ball comes within range of a 128 | * rollover. */ 129 | public long getScore () { 130 | return score; 131 | } 132 | 133 | // look in optional "color" parameter, use default value if not present. Invert if flashCounter>0 134 | protected int colorComponent (int index, int defvalue) { 135 | int value = defvalue; 136 | if (this.color != null) value = this.color[index]; 137 | return (flashCounter > 0) ? 255 - value : value; 138 | } 139 | 140 | /** Returns the red component of this element's base color, taken from the "color" parameter. If there is no color parameter, 141 | * the default argument is returned. */ 142 | protected int redColorComponent (int defvalue) { 143 | return colorComponent(0, defvalue); 144 | } 145 | 146 | /** Returns the green component of this element's base color, taken from the "color" parameter. If there is no color parameter, 147 | * the default argument is returned. */ 148 | protected int greenColorComponent (int defvalue) { 149 | return colorComponent(1, defvalue); 150 | } 151 | 152 | /** Returns the blue component of this element's base color, taken from the "color" parameter. If there is no color parameter, 153 | * the default argument is returned. */ 154 | protected int blueColorComponent (int defvalue) { 155 | return colorComponent(2, defvalue); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/elements/RolloverGroupElement.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.elements; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import com.badlogic.gdx.math.Vector2; 11 | import com.badlogic.gdx.physics.box2d.Body; 12 | import com.badlogic.gdx.physics.box2d.World; 13 | import com.dozingcatsoftware.bouncy.Field; 14 | import com.dozingcatsoftware.bouncy.IFieldRenderer; 15 | 16 | import static com.dozingcatsoftware.bouncy.util.MathUtils.*; 17 | 18 | /** This class represents a collection of rollover elements, such as the rollovers in the top lanes. They are activated (and 19 | * optionally deactivated) when a ball passes over them. Individual rollovers in the group are represented by instances of the 20 | * Rollover nested class, which specify center, radius, and color. Parameters at the collection level control whether the 21 | * rollovers should cycle when flippers are activated, and whether rollovers can toggle from on to off. 22 | * @author brian */ 23 | 24 | public class RolloverGroupElement extends FieldElement { 25 | 26 | static class Rollover { 27 | float cx, cy; 28 | float radius; 29 | float radiusSquared; // optimization when computing whether ball is in range 30 | List color; 31 | long score; 32 | float resetDelay; 33 | } 34 | 35 | boolean cycleOnFlipper; 36 | boolean canToggleOff; 37 | boolean ignoreBall; 38 | float defaultRadius; 39 | float defaultResetDelay; 40 | List rollovers = new ArrayList(); 41 | List activeRollovers = new ArrayList(); 42 | 43 | List rolloversHitOnPreviousTick = new ArrayList(); 44 | 45 | @Override 46 | public void finishCreate (Map params, World world) { 47 | this.canToggleOff = Boolean.TRUE.equals(params.get("toggleOff")); 48 | this.cycleOnFlipper = Boolean.TRUE.equals(params.get("cycleOnFlipper")); 49 | this.ignoreBall = Boolean.TRUE.equals(params.get("ignoreBall")); 50 | this.defaultRadius = asFloat(params.get("radius")); 51 | this.defaultResetDelay = asFloat(params.get("reset")); 52 | 53 | List rolloverMaps = (List)params.get("rollovers"); 54 | for (Map rmap : rolloverMaps) { 55 | Rollover rollover = new Rollover(); 56 | rollovers.add(rollover); 57 | 58 | List pos = (List)rmap.get("position"); 59 | rollover.cx = asFloat(pos.get(0)); 60 | rollover.cy = asFloat(pos.get(1)); 61 | // radius, color, score, and reset delay can be specified for each rollover, if not present use default from group 62 | rollover.radius = (rmap.containsKey("radius")) ? asFloat(rmap.get("radius")) : this.defaultRadius; 63 | rollover.color = (List)rmap.get("color"); 64 | rollover.score = (rmap.containsKey("score")) ? ((Number)rmap.get("score")).longValue() : this.score; 65 | rollover.resetDelay = (rmap.containsKey("reset")) ? asFloat(rmap.get("reset")) : this.defaultResetDelay; 66 | 67 | rollover.radiusSquared = rollover.radius * rollover.radius; 68 | } 69 | } 70 | 71 | @Override 72 | public Collection getBodies () { 73 | return Collections.EMPTY_SET; 74 | } 75 | 76 | List hitRollovers = new ArrayList(); 77 | 78 | /** Returns a set of all rollovers which have balls within their specified radius. */ 79 | protected List rolloversHitByBalls (List balls) { 80 | int len = rollovers.size(); 81 | hitRollovers.clear(); 82 | for (int i = 0; i < len; i++) { 83 | Rollover rollover = rollovers.get(i); 84 | boolean hit = false; 85 | int len2 = balls.size(); 86 | for (int j = 0; j < len2; j++) { 87 | Body ball = balls.get(j); 88 | Vector2 position = ball.getPosition(); 89 | float xdiff = position.x - rollover.cx; 90 | float ydiff = position.y - rollover.cy; 91 | float distanceSquared = xdiff * xdiff + ydiff * ydiff; 92 | if (distanceSquared <= rollover.radiusSquared) { 93 | hit = true; 94 | break; 95 | } 96 | } 97 | if (hit) { 98 | hitRollovers.add(rollover); 99 | } 100 | } 101 | return hitRollovers; 102 | } 103 | 104 | /** Returns true if all rollovers in the group are active. */ 105 | public boolean allRolloversActive () { 106 | return activeRollovers.size() == rollovers.size(); 107 | } 108 | 109 | /** Activates the first unactivated rollover in the group. Has no effect if all are active. */ 110 | public void activateFirstUnactivatedRollover () { 111 | int len = rollovers.size(); 112 | for (int i = 0; i < len; i++) { 113 | Rollover rollover = rollovers.get(i); 114 | if (!activeRollovers.contains(rollover)) { 115 | activeRollovers.add(rollover); 116 | break; 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | public boolean shouldCallTick () { 123 | return true; 124 | 125 | } 126 | 127 | @Override 128 | public void tick (Field field) { 129 | if (this.ignoreBall) return; 130 | 131 | boolean allActivePrevious = this.allRolloversActive(); 132 | List hitRollovers = rolloversHitByBalls(field.getBalls()); 133 | // only update rollovers that are hit on this tick and weren't on the previous tick 134 | int len = hitRollovers.size(); 135 | for (int i = 0; i < len; i++) { 136 | final Rollover rollover = hitRollovers.get(i); 137 | if (rolloversHitOnPreviousTick.contains(rollover)) continue; 138 | // Inactive rollover becomes active, active rollover becomes inactive if toggleOff setting is true. 139 | // Add score whenever the state changes. 140 | if (!activeRollovers.contains(rollover)) { 141 | activeRollovers.add(rollover); 142 | field.addScore(rollover.score); 143 | // set timer to clear rollover if reset parameter is present and >0 144 | if (rollover.resetDelay > 0) { 145 | field.scheduleAction((long)(rollover.resetDelay * 1000), new Runnable() { 146 | public void run () { 147 | activeRollovers.remove(rollover); 148 | } 149 | }); 150 | } 151 | } else if (this.canToggleOff) { 152 | activeRollovers.remove(rollover); 153 | field.addScore(rollover.score); 154 | } 155 | } 156 | 157 | rolloversHitOnPreviousTick.clear(); 158 | for (int i = 0; i < hitRollovers.size(); i++) { 159 | rolloversHitOnPreviousTick.add(hitRollovers.get(i)); 160 | } 161 | // notify delegate if all rollovers are now active and they weren't previously 162 | if (!allActivePrevious && allRolloversActive()) { 163 | field.getDelegate().allRolloversInGroupActivated(field, this); 164 | } 165 | } 166 | 167 | @Override 168 | public void flipperActivated (Field field) { 169 | if (this.cycleOnFlipper) { 170 | this.cycleRollovers(); 171 | } 172 | } 173 | 174 | List newActiveRollovers = new ArrayList(); 175 | 176 | /** Cycles the states of all rollover elements by "rotating" right. For example, if this group has three rollovers whose states 177 | * are (on, on, off), after calling this method the states will be (off, on, on). The state of the last rollover wraps around 178 | * to the first, so (off, off, on) -> (on, off, off). */ 179 | public void cycleRollovers () { 180 | newActiveRollovers.clear(); 181 | for (int i = 0; i < this.rollovers.size(); i++) { 182 | int prevIndex = (i == 0) ? this.rollovers.size() - 1 : i - 1; 183 | if (this.activeRollovers.contains(this.rollovers.get(prevIndex))) { 184 | newActiveRollovers.add(this.rollovers.get(i)); 185 | } 186 | } 187 | this.activeRollovers.clear(); 188 | this.activeRollovers.addAll(newActiveRollovers); 189 | } 190 | 191 | /** Sets all rollovers to be active or inactive according to the boolean argument. */ 192 | public void setAllRolloversActivated (boolean active) { 193 | activeRollovers.clear(); 194 | if (active) { 195 | activeRollovers.addAll(rollovers); 196 | } 197 | } 198 | 199 | @Override 200 | public void draw (IFieldRenderer renderer) { 201 | // default color defined at the group level 202 | int defaultRed = this.redColorComponent(0); 203 | int defaultGreen = this.greenColorComponent(255); 204 | int defaultBlue = this.blueColorComponent(0); 205 | 206 | // for each rollover, draw outlined circle for inactive or filled circle for active# 207 | int len = rollovers.size(); 208 | for (int i = 0; i < len; i++) { 209 | Rollover rollover = rollovers.get(i); 210 | // use custom rollover color if available 211 | int red = (rollover.color != null) ? rollover.color.get(0) : defaultRed; 212 | int green = (rollover.color != null) ? rollover.color.get(1) : defaultGreen; 213 | int blue = (rollover.color != null) ? rollover.color.get(2) : defaultBlue; 214 | 215 | if (activeRollovers.contains(rollover)) { 216 | renderer.fillCircle(rollover.cx, rollover.cy, rollover.radius, red, green, blue); 217 | } else { 218 | renderer.frameCircle(rollover.cx, rollover.cy, rollover.radius, red, green, blue); 219 | } 220 | } 221 | 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/JSONWriter.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | import java.io.IOException; 5 | import java.io.Writer; 6 | 7 | /* 8 | Copyright (c) 2006 JSON.org 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | The Software shall be used for Good, not Evil. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | */ 30 | 31 | /** JSONWriter provides a quick and convenient way of producing JSON text. The texts produced strictly conform to JSON syntax 32 | * rules. No whitespace is added, so the results are ready for transmission or storage. Each instance of JSONWriter can produce 33 | * one JSON text. 34 | *

35 | * A JSONWriter instance provides a value method for appending values to the text, and a key method for 36 | * adding keys before values in objects. There are array and endArray methods that make and bound array 37 | * values, and object and endObject methods which make and bound object values. All of these methods 38 | * return the JSONWriter instance, permitting a cascade style. For example, 39 | * 40 | *

 41 |  * new JSONWriter(myWriter).object().key("JSON").value("Hello, World!").endObject();
 42 |  * 
43 | * 44 | * which writes 45 | * 46 | *
 47 |  * {"JSON":"Hello, World!"}
 48 |  * 
49 | *

50 | * The first method called must be array or object. There are no methods for adding commas or colons. 51 | * JSONWriter adds them for you. Objects and arrays can be nested up to 20 levels deep. 52 | *

53 | * This can sometimes be easier than using a JSONObject to build a string. 54 | * @author JSON.org 55 | * @version 2010-12-24 */ 56 | public class JSONWriter { 57 | private static final int maxdepth = 20; 58 | 59 | /** The comma flag determines if a comma should be output before the next value. */ 60 | private boolean comma; 61 | 62 | /** The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k' (key), 'o' (object). */ 63 | protected char mode; 64 | 65 | /** The object/array stack. */ 66 | private JSONObject stack[]; 67 | 68 | /** The stack top index. A value of 0 indicates that the stack is empty. */ 69 | private int top; 70 | 71 | /** The writer that will receive the output. */ 72 | protected Writer writer; 73 | 74 | /** Make a fresh JSONWriter. It can be used to build one JSON text. */ 75 | public JSONWriter (Writer w) { 76 | this.comma = false; 77 | this.mode = 'i'; 78 | this.stack = new JSONObject[maxdepth]; 79 | this.top = 0; 80 | this.writer = w; 81 | } 82 | 83 | /** Append a value. 84 | * @param string A string value. 85 | * @return this 86 | * @throws JSONException If the value is out of sequence. */ 87 | private JSONWriter append (String string) throws JSONException { 88 | if (string == null) { 89 | throw new JSONException("Null pointer"); 90 | } 91 | if (this.mode == 'o' || this.mode == 'a') { 92 | try { 93 | if (this.comma && this.mode == 'a') { 94 | this.writer.write(','); 95 | } 96 | this.writer.write(string); 97 | } catch (IOException e) { 98 | throw new JSONException(e); 99 | } 100 | if (this.mode == 'o') { 101 | this.mode = 'k'; 102 | } 103 | this.comma = true; 104 | return this; 105 | } 106 | throw new JSONException("Value out of sequence."); 107 | } 108 | 109 | /** Begin appending a new array. All values until the balancing endArray will be appended to this array. The 110 | * endArray method must be called to mark the array's end. 111 | * @return this 112 | * @throws JSONException If the nesting is too deep, or if the object is started in the wrong place (for example as a key or 113 | * after the end of the outermost array or object). */ 114 | public JSONWriter array () throws JSONException { 115 | if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { 116 | this.push(null); 117 | this.append("["); 118 | this.comma = false; 119 | return this; 120 | } 121 | throw new JSONException("Misplaced array."); 122 | } 123 | 124 | /** End something. 125 | * @param mode Mode 126 | * @param c Closing character 127 | * @return this 128 | * @throws JSONException If unbalanced. */ 129 | private JSONWriter end (char mode, char c) throws JSONException { 130 | if (this.mode != mode) { 131 | throw new JSONException(mode == 'a' ? "Misplaced endArray." : "Misplaced endObject."); 132 | } 133 | this.pop(mode); 134 | try { 135 | this.writer.write(c); 136 | } catch (IOException e) { 137 | throw new JSONException(e); 138 | } 139 | this.comma = true; 140 | return this; 141 | } 142 | 143 | /** End an array. This method most be called to balance calls to array. 144 | * @return this 145 | * @throws JSONException If incorrectly nested. */ 146 | public JSONWriter endArray () throws JSONException { 147 | return this.end('a', ']'); 148 | } 149 | 150 | /** End an object. This method most be called to balance calls to object. 151 | * @return this 152 | * @throws JSONException If incorrectly nested. */ 153 | public JSONWriter endObject () throws JSONException { 154 | return this.end('k', '}'); 155 | } 156 | 157 | /** Append a key. The key will be associated with the next value. In an object, every value must be preceded by a key. 158 | * @param string A key string. 159 | * @return this 160 | * @throws JSONException If the key is out of place. For example, keys do not belong in arrays or if the key is null. */ 161 | public JSONWriter key (String string) throws JSONException { 162 | if (string == null) { 163 | throw new JSONException("Null key."); 164 | } 165 | if (this.mode == 'k') { 166 | try { 167 | stack[top - 1].putOnce(string, Boolean.TRUE); 168 | if (this.comma) { 169 | this.writer.write(','); 170 | } 171 | this.writer.write(JSONObject.quote(string)); 172 | this.writer.write(':'); 173 | this.comma = false; 174 | this.mode = 'o'; 175 | return this; 176 | } catch (IOException e) { 177 | throw new JSONException(e); 178 | } 179 | } 180 | throw new JSONException("Misplaced key."); 181 | } 182 | 183 | /** Begin appending a new object. All keys and values until the balancing endObject will be appended to this 184 | * object. The endObject method must be called to mark the object's end. 185 | * @return this 186 | * @throws JSONException If the nesting is too deep, or if the object is started in the wrong place (for example as a key or 187 | * after the end of the outermost array or object). */ 188 | public JSONWriter object () throws JSONException { 189 | if (this.mode == 'i') { 190 | this.mode = 'o'; 191 | } 192 | if (this.mode == 'o' || this.mode == 'a') { 193 | this.append("{"); 194 | this.push(new JSONObject()); 195 | this.comma = false; 196 | return this; 197 | } 198 | throw new JSONException("Misplaced object."); 199 | 200 | } 201 | 202 | /** Pop an array or object scope. 203 | * @param c The scope to close. 204 | * @throws JSONException If nesting is wrong. */ 205 | private void pop (char c) throws JSONException { 206 | if (this.top <= 0) { 207 | throw new JSONException("Nesting error."); 208 | } 209 | char m = this.stack[this.top - 1] == null ? 'a' : 'k'; 210 | if (m != c) { 211 | throw new JSONException("Nesting error."); 212 | } 213 | this.top -= 1; 214 | this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1] == null ? 'a' : 'k'; 215 | } 216 | 217 | /** Push an array or object scope. 218 | * @param c The scope to open. 219 | * @throws JSONException If nesting is too deep. */ 220 | @SuppressWarnings("javadoc") 221 | private void push (JSONObject jo) throws JSONException { 222 | if (this.top >= maxdepth) { 223 | throw new JSONException("Nesting too deep."); 224 | } 225 | this.stack[this.top] = jo; 226 | this.mode = jo == null ? 'a' : 'k'; 227 | this.top += 1; 228 | } 229 | 230 | /** Append either the value true or the value false. 231 | * @param b A boolean. 232 | * @return this 233 | * @throws JSONException */ 234 | public JSONWriter value (boolean b) throws JSONException { 235 | return this.append(b ? "true" : "false"); 236 | } 237 | 238 | /** Append a double value. 239 | * @param d A double. 240 | * @return this 241 | * @throws JSONException If the number is not finite. */ 242 | public JSONWriter value (double d) throws JSONException { 243 | return this.value(new Double(d)); 244 | } 245 | 246 | /** Append a long value. 247 | * @param l A long. 248 | * @return this 249 | * @throws JSONException */ 250 | public JSONWriter value (long l) throws JSONException { 251 | return this.append(Long.toString(l)); 252 | } 253 | 254 | /** Append an object value. 255 | * @param object The object to append. It can be null, or a Boolean, Number, String, JSONObject, or JSONArray, or an object 256 | * that implements JSONString. 257 | * @return this 258 | * @throws JSONException If the value is out of sequence. */ 259 | public JSONWriter value (Object object) throws JSONException { 260 | return this.append(JSONObject.valueToString(object)); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /android/assets/data/field_layout.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Default", 4 | "width": 20, 5 | "height": 30, 6 | 7 | "delegate": "Field1Delegate", 8 | "targetTimeRatio": 2.5, 9 | "gravity": 4.0, 10 | 11 | "numballs": 3, 12 | "ballradius": 0.5, 13 | "ballcolor": [192, 192, 224], 14 | "launch": {"position": [19.4, 3.0], "velocity": [0, 15.4], "random_velocity": [0, 0.6]}, 15 | 16 | "elements": [ 17 | "Strings in this section are treated as comments and ignored.", 18 | "All field elements except for flippers are defined in this section.", 19 | "Each element is a JSON object with a mandatory class property to indicate type", 20 | "and type=specific additional attributes. The element classes are defined", 21 | "in the com.dozingcatsoftware.bouncy.element package.", 22 | 23 | "launch rail", 24 | {"class": "WallElement", "position": [18.7, 0.1, 18.7, 23]}, 25 | {"class": "WallElement", "position": [19.9, 0.1, 19.9, 23]}, 26 | 27 | "main outside arc", 28 | { 29 | "class": "WallArcElement", 30 | "center": [10,23], 31 | "xradius": 9.9, 32 | "yradius": 7, 33 | "minangle": 0, 34 | "maxangle": 180, 35 | "segments": 30 36 | }, 37 | 38 | "right inside arc", 39 | { 40 | "class": "WallArcElement", 41 | "center": [10,23], 42 | "xradius": 8.6, 43 | "yradius": 5.6, 44 | "minangle": 0, 45 | "maxangle": 54, 46 | "segments": 12 47 | }, 48 | 49 | "left inside arc, upper", 50 | { 51 | "class": "WallArcElement", 52 | "center": [10,23], 53 | "xradius": 8.1, 54 | "yradius": 5.6, 55 | "minangle": 110, 56 | "maxangle": 180, 57 | "segments": 12 58 | }, 59 | 60 | "left outside arc, outer", 61 | {"class": "WallArcElement", "center": [6.246, 23], "xradius": 6.146, "yradius": 7.778, "minangle": 180, "maxangle": 225}, 62 | "left outside arc, inner", 63 | {"class": "WallArcElement", "center": [6.246, 23], "xradius": 4.346, "yradius": 5.978, "minangle": 180, "maxangle": 225}, 64 | 65 | {"class": "WallElement", "position": [0.1, 0.1, 18.7, 0.1], "color": [0,0,0], "kill": true}, 66 | {"class": "WallElement", "position": [18.7, 0.1, 19.9, 0.1]}, 67 | 68 | "left bottom", 69 | {"class": "WallElement", "position": [0.1, 4, 6, 1]}, 70 | "right bottom", 71 | {"class": "WallElement", "position": [18.7, 4, 12.7, 1]}, 72 | 73 | "left side", 74 | {"class": "WallElement", "position": [0.1, 4, 0.1, 10], "name": "left border"}, 75 | 76 | "above left outlane", 77 | { 78 | "class": "WallArcElement", 79 | "center": [3.1,10], 80 | "xradius": 3.0, 81 | "yradius": 3.0, 82 | "minangle": 120, 83 | "maxangle": 180, 84 | "segments": 8 85 | }, 86 | {"class": "WallElement", "position": [1.6, 12.6, 1.9, 17.5]}, 87 | 88 | "left outlane", 89 | {"class": "WallElement", "position": [1.45, 8.5, 1.45, 4.8]}, 90 | 91 | "right outlane", 92 | {"class": "WallElement", "position": [17.35, 8.5, 17.35, 4.8]}, 93 | "above right outlane", 94 | { 95 | "class": "WallArcElement", 96 | "center": [15.7,10], 97 | "xradius": 3.0, 98 | "yradius": 3.0, 99 | "minangle": 0, 100 | "maxangle": 60, 101 | "segments": 8 102 | }, 103 | 104 | "path along right edge", 105 | {"class": "WallPathElement", "positions": [[17.2, 12.6], [17, 15], [14.8, 21.4], [15.05, 24.5], [15.05, 27.53]]}, 106 | 107 | "barrier covering launch rail once the ball enters play, initially disabled", 108 | {"class": "WallElement", "id": "LaunchBarrier", "position": [15.05, 27.53, 15.75, 28.74], "disabled": true, "color": [128, 128, 128]}, 109 | "sensor to enable launch barrier", 110 | {"class": "SensorElement", "id": "LaunchBarrierSensor", "rect": [14.05, 27.53, 15.05, 29]}, 111 | 112 | "flipper lanes", 113 | {"class": "WallElement", "position": [1.45, 4.8, 6.25, 2.3]}, 114 | {"class": "WallElement", "position": [17.35, 4.8, 12.45, 2.3]}, 115 | 116 | "ball savers, disappear when hit and reappear when drop targets are cleared, controlled by Field1Delegate", 117 | {"class": "WallElement", "id": "BallSaver-left", "position": [0.15, 5.0, 1.35, 5.0], 118 | "kick": 6.0, "color": [0, 255, 0], "retractWhenHit": true}, 119 | 120 | {"class": "WallElement", "id": "BallSaver-right", "position": [17.45, 5.0, 18.65, 5.0], 121 | "kick": 6.0, "color": [0, 255, 0], "retractWhenHit": true}, 122 | 123 | "separators between top lanes", 124 | {"class": "WallElement", "position": [9.0, 26.5, 9.0, 27.8]}, 125 | {"class": "WallElement", "position": [10.5, 26.5, 10.5, 27.8]}, 126 | {"class": "WallElement", "position": [12.0, 26.5, 12.0, 27.8]}, 127 | {"class": "WallElement", "position": [13.5, 26.5, 13.5, 27.8]}, 128 | 129 | 130 | "right drop targets (computed with python script to place parallel to wall, 0.2 perpendicular distance, 1.0 width, 0.2 gap)", 131 | {"class": "DropTargetGroupElement", "id": "DropTargetRightSave", "color": [0, 255, 0], "score": 500, "reset": 2.0, 132 | "positions": [[16.778, 15.026, 16.440, 15.968], 133 | [16.372, 16.156, 16.034, 17.097], 134 | [15.966, 17.285, 15.628, 18.226], 135 | [15.560, 18.414, 15.222, 19.355]]}, 136 | 137 | "left drop targets: 0.2 perpendicular distance from wall, 1.0 width, 0.2 gap, start 0.7 from bottom of wall", 138 | {"class": "DropTargetGroupElement", "id": "DropTargetLeftSave", "color": [0, 255, 0], "score": 500, "reset": 2.0, 139 | "positions": [[1.807, 12.708, 1.868, 13.706], 140 | [1.88, 13.905, 1.941, 14.903], 141 | [1.954, 15.103, 2.015, 16.101], 142 | [2.027, 16.301, 2.088, 17.299]]}, 143 | 144 | "left kicker, 60 degree angle", 145 | { 146 | "class": "WallElement", 147 | "position": [3.2, 8.77, 5.7, 4.43], 148 | "kick": 3.5, 149 | "score": 100, 150 | "color": [0, 0, 255] 151 | }, 152 | "top/bottom of left kicker without kick, x goes .25 (=0.5*cos(pi/3)), y goes .43 (=0.5*sin(pi/3))", 153 | {"class": "WallElement", "position": [2.95, 9.2, 3.2, 8.77]}, 154 | {"class": "WallElement", "position": [5.95, 4, 5.7, 4.43]}, 155 | 156 | "around left kicker", 157 | {"class": "WallElement", "position": [2.95, 9.2, 2.95, 5.8]}, 158 | {"class": "WallElement", "position": [2.95, 5.8, 5.95, 4]}, 159 | 160 | "right kicker", 161 | { 162 | "class": "WallElement", 163 | "position": [15.6, 8.77, 13.1, 4.43], 164 | "kick": 3.5, 165 | "score": 100, 166 | "color": [0, 0, 255] 167 | }, 168 | "top/bottom of right kicker without kick", 169 | {"class": "WallElement", "position": [15.85, 9.2, 15.6, 8.77]}, 170 | {"class": "WallElement", "position": [12.85, 4, 13.1, 4.43]}, 171 | 172 | "around right kicker", 173 | {"class": "WallElement", "position": [15.85, 9.2, 15.85, 5.8]}, 174 | {"class": "WallElement", "position": [15.85, 5.8, 12.85, 4]}, 175 | 176 | "box above bumpers with drop targets", 177 | {"class": "WallElement", "position": [9.0, 27.9, 7.230, 28.262]}, 178 | {"class": "WallElement", "position": [9.0, 26.4, 5.95, 25.6]}, 179 | {"class": "WallElement", "position": [5.95, 25.6, 5.95, 27.85]}, 180 | 181 | "upper drop targets", 182 | {"class": "DropTargetGroupElement", "id": "DropTargetTop", "color": [0, 255, 255], "score": 500, "reset": 2.0, 183 | "positions": [[8.615, 26.092, 7.745, 25.864], 184 | [7.261, 25.737, 6.391, 25.509], 185 | [5.75, 26.1, 5.75, 27.1]]}, 186 | 187 | "bumpers below top lanes", 188 | {"class": "BumperElement", "radius": 0.85, "position": [9.5, 18], "kick": 3, "color": [0, 0, 224], "score": 500}, 189 | {"class": "BumperElement", "radius": 0.85, "position": [12.3, 21.2], "kick": 3, "color": [0, 0, 224], "score": 500}, 190 | {"class": "BumperElement", "radius": 0.85, "position": [6.5, 20.5], "kick": 3, "color": [0, 0, 224], "score": 500}, 191 | "purple bumper is extra points", 192 | {"class": "BumperElement", "radius": 0.70, "position": [4.5, 23.5], "kick": 2.5, "color": [192, 0, 192], "score": 1000}, 193 | 194 | "rollovers for ramp shot", 195 | {"class": "RolloverGroupElement", "id": "RampRollovers", "radius": 0.5, "score": 500, "reset": 5, 196 | "rollovers": [{"position": [1.7, 19.5], "color": [255, 0, 0], "score": 2000}, 197 | {"position": [1.0, 23.0], "color": [0, 255, 0], "score": 3000}, 198 | {"position": [3.1, 27.0], "color": [0, 0, 255], "score": 5000}, 199 | {"position": [6.3, 28.7], "color": [255, 255, 255], "score": 10000} 200 | ] 201 | }, 202 | 203 | "rollovers for flipper lanes", 204 | {"class": "RolloverGroupElement", "id": "FlipperRollovers", "color": [0, 224, 224], "radius": 0.4, "score": 500, "cycleOnFlipper": true, 205 | "rollovers": [{"position": [0.775, 7.0]}, 206 | {"position": [2.2, 7.0]}, 207 | {"position": [16.6, 7.0]}, 208 | {"position": [18.025, 7.0]} 209 | ] 210 | }, 211 | 212 | "rollovers for top lanes", 213 | {"class": "RolloverGroupElement", "id": "TopRollovers", "color": [0, 224, 224], "radius": 0.45, "score": 500, "cycleOnFlipper": true, 214 | "rollovers": [{"position": [9.75, 27.2]}, 215 | {"position": [11.25, 27.2]}, 216 | {"position": [12.75, 27.2]}, 217 | {"position": [14.25, 27.2]} 218 | ] 219 | }, 220 | 221 | "rollovers in middle for extra ball when all lit", 222 | {"class": "RolloverGroupElement", "id": "ExtraBallRollovers", "radius": 0.5, "ignoreBall": true, 223 | "rollovers": [{"position": [9.4, 13], "color": [224, 0, 0]}, 224 | {"position": [12.0, 11.5], "color": [224, 224, 0]}, 225 | {"position": [12.0, 8.5], "color": [0, 224, 0]}, 226 | {"position": [9.4, 7], "color": [0, 224, 224]}, 227 | {"position": [6.8, 8.5], "color": [0, 0, 224]}, 228 | {"position": [6.8, 11.5], "color": [224, 0, 224]}, 229 | {"position": [9.4, 10], "radius": 1.5, "color": [224, 224, 224]} 230 | ] 231 | } 232 | 233 | ], 234 | 235 | "flippers": [ 236 | { 237 | "position": [6.35, 2.2], 238 | "length": 2.5, 239 | "minangle": -20, 240 | "maxangle": 20, 241 | "upspeed": 7, 242 | "downspeed": 3 243 | }, 244 | { 245 | "position": [12.35, 2.2], 246 | "length": -2.5, 247 | "minangle": -20, 248 | "maxangle": 20, 249 | "upspeed": 7, 250 | "downspeed": 3 251 | } 252 | ] 253 | } 254 | ] -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/JSONTokener.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.Reader; 9 | import java.io.StringReader; 10 | 11 | /* 12 | Copyright (c) 2002 JSON.org 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | The Software shall be used for Good, not Evil. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | */ 34 | 35 | /** A JSONTokener takes a source string and extracts characters and tokens from it. It is used by the JSONObject and JSONArray 36 | * constructors to parse JSON source strings. 37 | * @author JSON.org 38 | * @version 2010-12-24 */ 39 | public class JSONTokener { 40 | 41 | private int character; 42 | private boolean eof; 43 | private int index; 44 | private int line; 45 | private char previous; 46 | private Reader reader; 47 | private boolean usePrevious; 48 | 49 | /** Construct a JSONTokener from a Reader. 50 | * 51 | * @param reader A reader. */ 52 | public JSONTokener (Reader reader) { 53 | this.reader = reader.markSupported() ? reader : new BufferedReader(reader); 54 | this.eof = false; 55 | this.usePrevious = false; 56 | this.previous = 0; 57 | this.index = 0; 58 | this.character = 1; 59 | this.line = 1; 60 | } 61 | 62 | /** Construct a JSONTokener from an InputStream. */ 63 | public JSONTokener (InputStream inputStream) throws JSONException { 64 | this(new InputStreamReader(inputStream)); 65 | } 66 | 67 | /** Construct a JSONTokener from a string. 68 | * 69 | * @param s A source string. */ 70 | public JSONTokener (String s) { 71 | this(new StringReader(s)); 72 | } 73 | 74 | /** Back up one character. This provides a sort of lookahead capability, so that you can test for a digit or letter before 75 | * attempting to parse the next number or identifier. */ 76 | public void back () throws JSONException { 77 | if (usePrevious || index <= 0) { 78 | throw new JSONException("Stepping back two steps is not supported"); 79 | } 80 | this.index -= 1; 81 | this.character -= 1; 82 | this.usePrevious = true; 83 | this.eof = false; 84 | } 85 | 86 | /** Get the hex value of a character (base16). 87 | * @param c A character between '0' and '9' or between 'A' and 'F' or between 'a' and 'f'. 88 | * @return An int between 0 and 15, or -1 if c was not a hex digit. */ 89 | public static int dehexchar (char c) { 90 | if (c >= '0' && c <= '9') { 91 | return c - '0'; 92 | } 93 | if (c >= 'A' && c <= 'F') { 94 | return c - ('A' - 10); 95 | } 96 | if (c >= 'a' && c <= 'f') { 97 | return c - ('a' - 10); 98 | } 99 | return -1; 100 | } 101 | 102 | public boolean end () { 103 | return eof && !usePrevious; 104 | } 105 | 106 | /** Determine if the source string still contains characters that next() can consume. 107 | * @return true if not yet at the end of the source. */ 108 | public boolean more () throws JSONException { 109 | next(); 110 | if (end()) { 111 | return false; 112 | } 113 | back(); 114 | return true; 115 | } 116 | 117 | /** Get the next character in the source string. 118 | * 119 | * @return The next character, or 0 if past the end of the source string. */ 120 | public char next () throws JSONException { 121 | int c; 122 | if (this.usePrevious) { 123 | this.usePrevious = false; 124 | c = this.previous; 125 | } else { 126 | try { 127 | c = this.reader.read(); 128 | } catch (IOException exception) { 129 | throw new JSONException(exception); 130 | } 131 | 132 | if (c <= 0) { // End of stream 133 | this.eof = true; 134 | c = 0; 135 | } 136 | } 137 | this.index += 1; 138 | if (this.previous == '\r') { 139 | this.line += 1; 140 | this.character = c == '\n' ? 0 : 1; 141 | } else if (c == '\n') { 142 | this.line += 1; 143 | this.character = 0; 144 | } else { 145 | this.character += 1; 146 | } 147 | this.previous = (char)c; 148 | return this.previous; 149 | } 150 | 151 | /** Consume the next character, and check that it matches a specified character. 152 | * @param c The character to match. 153 | * @return The character. 154 | * @throws JSONException if the character does not match. */ 155 | public char next (char c) throws JSONException { 156 | char n = next(); 157 | if (n != c) { 158 | throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); 159 | } 160 | return n; 161 | } 162 | 163 | /** Get the next n characters. 164 | * 165 | * @param n The number of characters to take. 166 | * @return A string of n characters. 167 | * @throws JSONException Substring bounds error if there are not n characters remaining in the source string. */ 168 | public String next (int n) throws JSONException { 169 | if (n == 0) { 170 | return ""; 171 | } 172 | 173 | char[] chars = new char[n]; 174 | int pos = 0; 175 | 176 | while (pos < n) { 177 | chars[pos] = next(); 178 | if (end()) { 179 | throw syntaxError("Substring bounds error"); 180 | } 181 | pos += 1; 182 | } 183 | return new String(chars); 184 | } 185 | 186 | /** Get the next char in the string, skipping whitespace. 187 | * @throws JSONException 188 | * @return A character, or 0 if there are no more characters. */ 189 | public char nextClean () throws JSONException { 190 | for (;;) { 191 | char c = next(); 192 | if (c == 0 || c > ' ') { 193 | return c; 194 | } 195 | } 196 | } 197 | 198 | /** Return the characters up to the next close quote character. Backslash processing is done. The formal JSON format does not 199 | * allow strings in single quotes, but an implementation is allowed to accept them. 200 | * @param quote The quoting character, either " (double quote) or ' 201 | *  (single quote). 202 | * @return A String. 203 | * @throws JSONException Unterminated string. */ 204 | public String nextString (char quote) throws JSONException { 205 | char c; 206 | StringBuffer sb = new StringBuffer(); 207 | for (;;) { 208 | c = next(); 209 | switch (c) { 210 | case 0: 211 | case '\n': 212 | case '\r': 213 | throw syntaxError("Unterminated string"); 214 | case '\\': 215 | c = next(); 216 | switch (c) { 217 | case 'b': 218 | sb.append('\b'); 219 | break; 220 | case 't': 221 | sb.append('\t'); 222 | break; 223 | case 'n': 224 | sb.append('\n'); 225 | break; 226 | case 'f': 227 | sb.append('\f'); 228 | break; 229 | case 'r': 230 | sb.append('\r'); 231 | break; 232 | case 'u': 233 | sb.append((char)Integer.parseInt(next(4), 16)); 234 | break; 235 | case '"': 236 | case '\'': 237 | case '\\': 238 | case '/': 239 | sb.append(c); 240 | break; 241 | default: 242 | throw syntaxError("Illegal escape."); 243 | } 244 | break; 245 | default: 246 | if (c == quote) { 247 | return sb.toString(); 248 | } 249 | sb.append(c); 250 | } 251 | } 252 | } 253 | 254 | /** Get the text up but not including the specified character or the end of line, whichever comes first. 255 | * @param delimiter A delimiter character. 256 | * @return A string. */ 257 | public String nextTo (char delimiter) throws JSONException { 258 | StringBuffer sb = new StringBuffer(); 259 | for (;;) { 260 | char c = next(); 261 | if (c == delimiter || c == 0 || c == '\n' || c == '\r') { 262 | if (c != 0) { 263 | back(); 264 | } 265 | return sb.toString().trim(); 266 | } 267 | sb.append(c); 268 | } 269 | } 270 | 271 | /** Get the text up but not including one of the specified delimiter characters or the end of line, whichever comes first. 272 | * @param delimiters A set of delimiter characters. 273 | * @return A string, trimmed. */ 274 | public String nextTo (String delimiters) throws JSONException { 275 | char c; 276 | StringBuffer sb = new StringBuffer(); 277 | for (;;) { 278 | c = next(); 279 | if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { 280 | if (c != 0) { 281 | back(); 282 | } 283 | return sb.toString().trim(); 284 | } 285 | sb.append(c); 286 | } 287 | } 288 | 289 | /** Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the 290 | * JSONObject.NULL object. 291 | * @throws JSONException If syntax error. 292 | * 293 | * @return An object. */ 294 | public Object nextValue () throws JSONException { 295 | char c = nextClean(); 296 | String string; 297 | 298 | switch (c) { 299 | case '"': 300 | case '\'': 301 | return nextString(c); 302 | case '{': 303 | back(); 304 | return new JSONObject(this); 305 | case '[': 306 | back(); 307 | return new JSONArray(this); 308 | } 309 | 310 | /* 311 | * Handle unquoted text. This could be the values true, false, or null, or it can be a number. An implementation (such as 312 | * this one) is allowed to also accept non-standard forms. 313 | * 314 | * Accumulate characters until we reach the end of the text or a formatting character. 315 | */ 316 | 317 | StringBuffer sb = new StringBuffer(); 318 | while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { 319 | sb.append(c); 320 | c = next(); 321 | } 322 | back(); 323 | 324 | string = sb.toString().trim(); 325 | if (string.equals("")) { 326 | throw syntaxError("Missing value"); 327 | } 328 | return JSONObject.stringToValue(string); 329 | } 330 | 331 | /** Skip characters until the next character is the requested character. If the requested character is not found, no characters 332 | * are skipped. 333 | * @param to A character to skip to. 334 | * @return The requested character, or zero if the requested character is not found. */ 335 | public char skipTo (char to) throws JSONException { 336 | char c; 337 | try { 338 | int startIndex = this.index; 339 | int startCharacter = this.character; 340 | int startLine = this.line; 341 | reader.mark(Integer.MAX_VALUE); 342 | do { 343 | c = next(); 344 | if (c == 0) { 345 | reader.reset(); 346 | this.index = startIndex; 347 | this.character = startCharacter; 348 | this.line = startLine; 349 | return c; 350 | } 351 | } while (c != to); 352 | } catch (IOException exc) { 353 | throw new JSONException(exc); 354 | } 355 | 356 | back(); 357 | return c; 358 | } 359 | 360 | /** Make a JSONException to signal a syntax error. 361 | * 362 | * @param message The error message. 363 | * @return A JSONException object, suitable for throwing */ 364 | public JSONException syntaxError (String message) { 365 | return new JSONException(message + toString()); 366 | } 367 | 368 | /** Make a printable string of this JSONTokener. 369 | * 370 | * @return " at {index} [character {character} line {line}]" */ 371 | public String toString () { 372 | return " at " + index + " [character " + this.character + " line " + this.line + "]"; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/Field.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy; 3 | 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.PriorityQueue; 9 | import java.util.Random; 10 | 11 | import com.badlogic.gdx.math.Vector2; 12 | import com.badlogic.gdx.physics.box2d.Body; 13 | import com.badlogic.gdx.physics.box2d.CircleShape; 14 | import com.badlogic.gdx.physics.box2d.Contact; 15 | import com.badlogic.gdx.physics.box2d.ContactImpulse; 16 | import com.badlogic.gdx.physics.box2d.ContactListener; 17 | import com.badlogic.gdx.physics.box2d.Fixture; 18 | import com.badlogic.gdx.physics.box2d.Manifold; 19 | import com.badlogic.gdx.physics.box2d.World; 20 | import com.dozingcatsoftware.bouncy.elements.Box2DFactory; 21 | import com.dozingcatsoftware.bouncy.elements.DropTargetGroupElement; 22 | import com.dozingcatsoftware.bouncy.elements.FieldElement; 23 | import com.dozingcatsoftware.bouncy.elements.FlipperElement; 24 | import com.dozingcatsoftware.bouncy.elements.RolloverGroupElement; 25 | import com.dozingcatsoftware.bouncy.elements.SensorElement; 26 | import com.dozingcatsoftware.bouncy.fields.Field1Delegate; 27 | 28 | public class Field implements ContactListener { 29 | 30 | FieldLayout layout; 31 | World world; 32 | 33 | List layoutBodies; 34 | List balls; 35 | List ballsAtTargets; 36 | 37 | // allow access to model objects from Box2d bodies 38 | Map bodyToFieldElement; 39 | Map fieldElements; 40 | Map> elementsByGroupID = new HashMap>(); 41 | FieldElement[] fieldElementsToTick; 42 | 43 | Random RAND = new Random(); 44 | 45 | long gameTime; 46 | PriorityQueue scheduledActions; 47 | 48 | Delegate delegate; 49 | 50 | GameState gameState = new GameState(); 51 | GameMessage gameMessage; 52 | 53 | // interface to allow custom behavior for various game events 54 | public static interface Delegate { 55 | public void gameStarted (Field field); 56 | 57 | public void ballLost (Field field); 58 | 59 | public void gameEnded (Field field); 60 | 61 | public void tick (Field field, long msecs); 62 | 63 | public void processCollision (Field field, FieldElement element, Body hitBody, Body ball); 64 | 65 | public void flipperActivated (Field field); 66 | 67 | public void allDropTargetsInGroupHit (Field field, DropTargetGroupElement targetGroup); 68 | 69 | public void allRolloversInGroupActivated (Field field, RolloverGroupElement rolloverGroup); 70 | 71 | public void ballInSensorRange (Field field, SensorElement sensor); 72 | } 73 | 74 | // helper class to represent actions scheduled in the future 75 | static class ScheduledAction implements Comparable { 76 | Long actionTime; 77 | Runnable action; 78 | 79 | @Override 80 | public int compareTo (ScheduledAction another) { 81 | // sort by action time so these objects can be added to a PriorityQueue in the right order 82 | return actionTime.compareTo(another.actionTime); 83 | } 84 | } 85 | 86 | /** Creates Box2D world, reads layout definitions for the given level (currently only one), and initializes the game to the 87 | * starting state. 88 | * @param level */ 89 | public void resetForLevel (int level) { 90 | Vector2 gravity = new Vector2(0.0f, -1.0f); 91 | boolean doSleep = true; 92 | world = new World(gravity, doSleep); 93 | world.setContactListener(this); 94 | 95 | layout = FieldLayout.layoutForLevel(level, world); 96 | world.setGravity(new Vector2(0.0f, -layout.getGravity())); 97 | balls = new ArrayList(); 98 | ballsAtTargets = new ArrayList(); 99 | 100 | scheduledActions = new PriorityQueue(); 101 | gameTime = 0; 102 | 103 | // map bodies and IDs to FieldElements, and get elements on whom tick() has to be called 104 | bodyToFieldElement = new HashMap(); 105 | fieldElements = new HashMap(); 106 | List tickElements = new ArrayList(); 107 | 108 | for (FieldElement element : layout.getFieldElements()) { 109 | if (element.getElementID() != null) { 110 | fieldElements.put(element.getElementID(), element); 111 | } 112 | for (Body body : element.getBodies()) { 113 | bodyToFieldElement.put(body, element); 114 | } 115 | if (element.shouldCallTick()) { 116 | tickElements.add(element); 117 | } 118 | } 119 | fieldElementsToTick = tickElements.toArray(new FieldElement[0]); 120 | 121 | delegate = new Field1Delegate(); 122 | // String delegateClass = layout.getDelegateClassName(); 123 | // if (delegateClass != null) { 124 | // if (delegateClass.indexOf('.') == -1) { 125 | // delegateClass = "com.dozingcatsoftware.bouncy.fields." + delegateClass; 126 | // } 127 | // try { 128 | // delegate = (Delegate)Class.forName(delegateClass).newInstance(); 129 | // } catch (Exception ex) { 130 | // throw new RuntimeException(ex); 131 | // } 132 | // } else { 133 | // // use no-op delegate if no class specified, so that field.getDelegate() is always non-null 134 | // delegate = new BaseFieldDelegate(); 135 | // } 136 | } 137 | 138 | public void startGame () { 139 | gameState.setTotalBalls(layout.getNumberOfBalls()); 140 | gameState.startNewGame(); 141 | getDelegate().gameStarted(this); 142 | } 143 | 144 | /** Returns the FieldElement with the given value for its "id" attribute, or null if there is no such element. */ 145 | public FieldElement getFieldElementByID (String elementID) { 146 | return fieldElements.get(elementID); 147 | } 148 | 149 | /** Called to advance the game's state by the specified number of milliseconds. iters is the number of times to call the Box2D 150 | * World.step method; more iterations produce better accuracy. After updating physics, processes element collisions, calls 151 | * tick() on every FieldElement, and performs scheduled actions. */ 152 | void tick (long msecs, int iters) { 153 | float dt = (msecs / 1000.0f) / iters; 154 | 155 | for (int i = 0; i < iters; i++) { 156 | clearBallContacts(); 157 | world.step(dt, 10, 10); 158 | processBallContacts(); 159 | } 160 | 161 | gameTime += msecs; 162 | processElementTicks(); 163 | processScheduledActions(); 164 | processGameMessages(); 165 | 166 | getDelegate().tick(this, msecs); 167 | } 168 | 169 | /** Calls the tick() method of every FieldElement in the layout. */ 170 | void processElementTicks () { 171 | int size = fieldElementsToTick.length; 172 | for (int i = 0; i < size; i++) { 173 | fieldElementsToTick[i].tick(this); 174 | } 175 | } 176 | 177 | /** Runs actions that were scheduled with scheduleAction and whose execution time has arrived. */ 178 | void processScheduledActions () { 179 | while (true) { 180 | ScheduledAction nextAction = scheduledActions.peek(); 181 | if (nextAction != null && gameTime >= nextAction.actionTime) { 182 | scheduledActions.poll(); 183 | nextAction.action.run(); 184 | } else { 185 | break; 186 | } 187 | } 188 | } 189 | 190 | /** Schedules an action to be run after the given interval in milliseconds has elapsed. Interval is in game time, not real time. */ 191 | public void scheduleAction (long interval, Runnable action) { 192 | ScheduledAction sa = new ScheduledAction(); 193 | sa.actionTime = gameTime + interval; 194 | sa.action = action; 195 | scheduledActions.add(sa); 196 | } 197 | 198 | /** Launches a new ball. The position and velocity of the ball are controlled by the "launch" key in the field layout JSON. */ 199 | public Body launchBall () { 200 | List position = layout.getLaunchPosition(); 201 | List velocity = layout.getLaunchVelocity(); 202 | float radius = layout.getBallRadius(); 203 | 204 | Body ball = Box2DFactory.createCircle(world, position.get(0).floatValue(), position.get(1).floatValue(), radius, false); 205 | ball.setBullet(true); 206 | ball.setLinearVelocity(new Vector2(velocity.get(0), velocity.get(1))); 207 | this.balls.add(ball); 208 | return ball; 209 | } 210 | 211 | /** Removes a ball from play. If this results in no balls remaining on the field, calls doBallLost. */ 212 | public void removeBall (Body ball) { 213 | world.destroyBody(ball); 214 | this.balls.remove(ball); 215 | if (this.balls.size() == 0) { 216 | this.doBallLost(); 217 | } 218 | } 219 | 220 | /** Called when a ball has ended. Ends the game if that was the last ball, otherwise updates GameState to the next ball. Shows a 221 | * game message to indicate the ball number or game over. */ 222 | public void doBallLost () { 223 | boolean hasExtraBall = (this.gameState.getExtraBalls() > 0); 224 | this.gameState.doNextBall(); 225 | // display message for next ball or game over 226 | String msg = null; 227 | if (hasExtraBall) 228 | msg = "Shoot Again"; 229 | else if (this.gameState.isGameInProgress()) msg = "Ball " + this.gameState.getBallNumber(); 230 | 231 | if (msg != null) { 232 | // game is still going, show message after delay 233 | final String msg2 = msg; // must be final for closure, yay Java 234 | this.scheduleAction(1500, new Runnable() { 235 | public void run () { 236 | showGameMessage(msg2, 1500); 237 | } 238 | }); 239 | } else { 240 | endGame(); 241 | } 242 | 243 | getDelegate().ballLost(this); 244 | } 245 | 246 | /** Returns true if there are active elements in motion. Returns false if there are no active elements, indicating that tick() 247 | * can be called with larger time steps, less frequently, or not at all. */ 248 | public boolean hasActiveElements () { 249 | // HACK: to allow flippers to drop properly at beginning of game, we need accurate simulation 250 | if (this.gameTime < 500) return true; 251 | // allow delegate to return true even if there are no balls? 252 | return this.getBalls().size() > 0; 253 | } 254 | 255 | ArrayList deadBalls = new ArrayList(); 256 | 257 | /** Removes balls that are not in play, currently defined as those having a y position of less than 1. */ 258 | public void removeDeadBalls () { 259 | int len = balls.size(); 260 | deadBalls.clear(); 261 | for (int i = 0; i < len; i++) { 262 | Body ball = this.balls.get(i); 263 | if (ball.getPosition().y < 1) { 264 | world.destroyBody(ball); 265 | deadBalls.add(ball); 266 | } 267 | } 268 | 269 | len = deadBalls.size(); 270 | for (int i = 0; i < len; i++) { 271 | this.balls.remove(deadBalls.get(i)); 272 | } 273 | } 274 | 275 | /** Called by FieldView to draw the balls currently in play. */ 276 | public void drawBalls (IFieldRenderer renderer) { 277 | List color = layout.getBallColor(); 278 | int len = balls.size(); 279 | for (int i = 0; i < len; i++) { 280 | Body ball = balls.get(i); 281 | CircleShape shape = (CircleShape)ball.getFixtureList().get(0).getShape(); 282 | renderer.fillCircle(ball.getPosition().x, ball.getPosition().y, shape.getRadius(), color.get(0), color.get(1), 283 | color.get(2)); 284 | } 285 | } 286 | 287 | /** Called to engage or disengage all flippers. If called with an argument of true, and all flippers were not previously 288 | * engaged, calls the flipperActivated methods of all field elements and the field's delegate. */ 289 | public void setAllFlippersEngaged (boolean engaged) { 290 | boolean allFlippersPreviouslyActive = true; 291 | for (FlipperElement flipper : this.getFlipperElements()) { 292 | if (allFlippersPreviouslyActive && !flipper.isFlipperEngaged()) allFlippersPreviouslyActive = false; 293 | flipper.setFlipperEngaged(engaged); 294 | } 295 | 296 | if (engaged && !allFlippersPreviouslyActive) { 297 | for (FieldElement element : this.getFieldElements()) { 298 | element.flipperActivated(this); 299 | } 300 | getDelegate().flipperActivated(this); 301 | } 302 | } 303 | 304 | /** Ends a game in progress by removing all balls in play, calling setGameInProgress(false) on the GameState, and setting a 305 | * "Game Over" message for display by the score view. */ 306 | public void endGame () { 307 | int len = balls.size(); 308 | for (int i = 0; i < len; i++) { 309 | world.destroyBody(balls.get(i)); 310 | } 311 | balls.clear(); 312 | this.getGameState().setGameInProgress(false); 313 | this.showGameMessage("Game Over", 2500); 314 | getDelegate().gameEnded(this); 315 | } 316 | 317 | /** Adjusts gravity in response to the device being tilted; not currently used. */ 318 | public void receivedOrientationValues (float azimuth, float pitch, float roll) { 319 | double angle = roll - Math.PI / 2; 320 | float gravity = layout.getGravity(); 321 | float gx = (float)(gravity * Math.cos(angle)); 322 | float gy = -Math.abs((float)(gravity * Math.sin(angle))); 323 | world.setGravity(new Vector2(gx, gy)); 324 | } 325 | 326 | // contact support 327 | Map> ballContacts = new HashMap(); 328 | 329 | void clearBallContacts () { 330 | ballContacts.clear(); 331 | } 332 | 333 | /** Called after Box2D world step method, to notify FieldElements that the ball collided with. */ 334 | void processBallContacts () { 335 | int len = balls.size(); 336 | for (int i = 0; i < len; i++) { 337 | Body ball = balls.get(i); 338 | if (ball.getUserData() == null) continue; 339 | 340 | List fixtures = (List)ball.getUserData(); 341 | int len2 = fixtures.size(); 342 | for (int j = 0; j < len2; j++) { 343 | Fixture f = fixtures.get(j); 344 | FieldElement element = bodyToFieldElement.get(f.getBody()); 345 | if (element != null) { 346 | element.handleCollision(ball, f.getBody(), this); 347 | if (delegate != null) { 348 | delegate.processCollision(this, element, f.getBody(), ball); 349 | } 350 | this.gameState.addScore(element.getScore()); 351 | } 352 | } 353 | fixtures.clear(); 354 | } 355 | } 356 | 357 | // ContactListener methods 358 | @Override 359 | public void beginContact (Contact contact) { 360 | // nothing here, contact is recorded in endContact() 361 | } 362 | 363 | @Override 364 | public void endContact (Contact contact) { 365 | // A ball can have multiple contacts (e.g. against two walls), so store list of contacted fixtures 366 | Body ball = null; 367 | Fixture fixture = null; 368 | if (balls.contains(contact.getFixtureA().getBody())) { 369 | ball = contact.getFixtureA().getBody(); 370 | fixture = contact.getFixtureB(); 371 | } 372 | if (balls.contains(contact.getFixtureB().getBody())) { 373 | ball = contact.getFixtureB().getBody(); 374 | fixture = contact.getFixtureA(); 375 | } 376 | if (ball != null) { 377 | List fixtures = (List)ball.getUserData(); 378 | if (fixtures == null) { 379 | ball.setUserData(fixtures = new ArrayList()); 380 | } 381 | fixtures.add(fixture); 382 | } 383 | } 384 | 385 | // end ContactListener methods 386 | 387 | /** Displays a message in the score view for the specified duration in milliseconds. Duration is in real world time, not 388 | * simulated game time. */ 389 | public void showGameMessage (String text, long duration) { 390 | gameMessage = new GameMessage(); 391 | gameMessage.text = text; 392 | gameMessage.duration = duration; 393 | gameMessage.creationTime = System.currentTimeMillis(); 394 | } 395 | 396 | // updates time remaining on current game message 397 | void processGameMessages () { 398 | if (gameMessage != null) { 399 | if (System.currentTimeMillis() - gameMessage.creationTime > gameMessage.duration) { 400 | gameMessage = null; 401 | } 402 | } 403 | } 404 | 405 | /** Adds the given value to the game score. The value is multiplied by the GameState's current multipler. */ 406 | public void addScore (long s) { 407 | gameState.addScore(s); 408 | } 409 | 410 | // accessors 411 | public float getWidth () { 412 | return layout.getWidth(); 413 | } 414 | 415 | public float getHeight () { 416 | return layout.getHeight(); 417 | } 418 | 419 | public List getLayoutBodies () { 420 | return layoutBodies; 421 | } 422 | 423 | public List getBalls () { 424 | return balls; 425 | } 426 | 427 | public List getFlipperElements () { 428 | return layout.getFlipperElements(); 429 | } 430 | 431 | public List getFieldElements () { 432 | return layout.getFieldElements(); 433 | } 434 | 435 | public GameMessage getGameMessage () { 436 | return gameMessage; 437 | } 438 | 439 | public GameState getGameState () { 440 | return gameState; 441 | } 442 | 443 | public long getGameTime () { 444 | return gameTime; 445 | } 446 | 447 | public float getTargetTimeRatio () { 448 | return layout.getTargetTimeRatio(); 449 | } 450 | 451 | public World getBox2DWorld () { 452 | return world; 453 | } 454 | 455 | public Delegate getDelegate () { 456 | return delegate; 457 | } 458 | 459 | @Override 460 | public void preSolve (Contact contact, Manifold oldManifold) { 461 | // TODO Auto-generated method stub 462 | 463 | } 464 | 465 | @Override 466 | public void postSolve (Contact contact, ContactImpulse impulse) { 467 | // TODO Auto-generated method stub 468 | 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /core/src/com/dozingcatsoftware/bouncy/util/JSONArray.java: -------------------------------------------------------------------------------- 1 | 2 | package com.dozingcatsoftware.bouncy.util; 3 | 4 | /* 5 | Copyright (c) 2002 JSON.org 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | The Software shall be used for Good, not Evil. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | */ 27 | 28 | import java.io.IOException; 29 | import java.io.Writer; 30 | import java.util.ArrayList; 31 | import java.util.Collection; 32 | import java.util.Iterator; 33 | import java.util.Map; 34 | 35 | /** A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in square brackets with commas 36 | * separating the values. The internal form is an object having get and opt methods for accessing the 37 | * values by index, and put methods for adding or replacing values. The values can be any of these types: 38 | * Boolean, JSONArray, JSONObject, Number, String, or the 39 | * JSONObject.NULL object. 40 | *

41 | * The constructor can convert a JSON text into a Java object. The toString method converts to JSON text. 42 | *

43 | * A get method returns a value if one can be found, and throws an exception if one cannot be found. An 44 | * opt method returns a default value instead of throwing an exception, and so is useful for obtaining optional 45 | * values. 46 | *

47 | * The generic get() and opt() methods return an object which you can cast or query for type. There are 48 | * also typed get and opt methods that do type checking and type coercion for you. 49 | *

50 | * The texts produced by the toString methods strictly conform to JSON syntax rules. The constructors are more 51 | * forgiving in the texts they will accept: 52 | *

    53 | *
  • An extra , (comma) may appear just before the closing bracket.
  • 54 | *
  • The null value will be inserted when there is , (comma) elision.
  • 55 | *
  • Strings may be quoted with ' (single quote).
  • 56 | *
  • Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not contain 57 | * leading or trailing spaces, and if they do not contain any of these characters: { } [ ] / \ : , = ; # and if they 58 | * do not look like numbers and if they are not the reserved words true, false, or null.
  • 59 | *
  • Values can be separated by ; (semicolon) as well as by , (comma).
  • 60 | *
  • Numbers may have the 0x- (hex) prefix.
  • 61 | *
62 | * 63 | * @author JSON.org 64 | * @version 2010-12-28 */ 65 | public class JSONArray { 66 | 67 | /** The arrayList where the JSONArray's properties are kept. */ 68 | private ArrayList myArrayList; 69 | 70 | /** Construct an empty JSONArray. */ 71 | public JSONArray () { 72 | this.myArrayList = new ArrayList(); 73 | } 74 | 75 | /** Construct a JSONArray from a JSONTokener. 76 | * @param x A JSONTokener 77 | * @throws JSONException If there is a syntax error. */ 78 | public JSONArray (JSONTokener x) throws JSONException { 79 | this(); 80 | if (x.nextClean() != '[') { 81 | throw x.syntaxError("A JSONArray text must start with '['"); 82 | } 83 | if (x.nextClean() != ']') { 84 | x.back(); 85 | for (;;) { 86 | if (x.nextClean() == ',') { 87 | x.back(); 88 | this.myArrayList.add(JSONObject.NULL); 89 | } else { 90 | x.back(); 91 | this.myArrayList.add(x.nextValue()); 92 | } 93 | switch (x.nextClean()) { 94 | case ';': 95 | case ',': 96 | if (x.nextClean() == ']') { 97 | return; 98 | } 99 | x.back(); 100 | break; 101 | case ']': 102 | return; 103 | default: 104 | throw x.syntaxError("Expected a ',' or ']'"); 105 | } 106 | } 107 | } 108 | } 109 | 110 | /** Construct a JSONArray from a source JSON text. 111 | * @param source A string that begins with [ (left bracket) and ends with ] 112 | *  (right bracket). 113 | * @throws JSONException If there is a syntax error. */ 114 | public JSONArray (String source) throws JSONException { 115 | this(new JSONTokener(source)); 116 | } 117 | 118 | /** Construct a JSONArray from a Collection. 119 | * @param collection A Collection. */ 120 | public JSONArray (Collection collection) { 121 | this.myArrayList = new ArrayList(); 122 | if (collection != null) { 123 | Iterator iter = collection.iterator(); 124 | while (iter.hasNext()) { 125 | this.myArrayList.add(JSONObject.wrap(iter.next())); 126 | } 127 | } 128 | } 129 | 130 | /** Construct a JSONArray from an array 131 | * @throws JSONException If not an array. */ 132 | public JSONArray (Object array) throws JSONException { 133 | this(); 134 | if (array.getClass().isArray()) { 135 | // int length = Array.getLength(array); 136 | // for (int i = 0; i < length; i += 1) { 137 | // this.put(JSONObject.wrap(Array.get(array, i))); 138 | // } 139 | } else { 140 | throw new JSONException("JSONArray initial value should be a string or collection or array."); 141 | } 142 | } 143 | 144 | /** Get the object value associated with an index. 145 | * @param index The index must be between 0 and length() - 1. 146 | * @return An object value. 147 | * @throws JSONException If there is no value for the index. */ 148 | public Object get (int index) throws JSONException { 149 | Object object = opt(index); 150 | if (object == null) { 151 | throw new JSONException("JSONArray[" + index + "] not found."); 152 | } 153 | return object; 154 | } 155 | 156 | /** Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean. 157 | * 158 | * @param index The index must be between 0 and length() - 1. 159 | * @return The truth. 160 | * @throws JSONException If there is no value for the index or if the value is not convertible to boolean. */ 161 | public boolean getBoolean (int index) throws JSONException { 162 | Object object = get(index); 163 | if (object.equals(Boolean.FALSE) || (object instanceof String && ((String)object).equalsIgnoreCase("false"))) { 164 | return false; 165 | } else if (object.equals(Boolean.TRUE) || (object instanceof String && ((String)object).equalsIgnoreCase("true"))) { 166 | return true; 167 | } 168 | throw new JSONException("JSONArray[" + index + "] is not a boolean."); 169 | } 170 | 171 | /** Get the double value associated with an index. 172 | * 173 | * @param index The index must be between 0 and length() - 1. 174 | * @return The value. 175 | * @throws JSONException If the key is not found or if the value cannot be converted to a number. */ 176 | public double getDouble (int index) throws JSONException { 177 | Object object = get(index); 178 | try { 179 | return object instanceof Number ? ((Number)object).doubleValue() : Double.parseDouble((String)object); 180 | } catch (Exception e) { 181 | throw new JSONException("JSONArray[" + index + "] is not a number."); 182 | } 183 | } 184 | 185 | /** Get the int value associated with an index. 186 | * 187 | * @param index The index must be between 0 and length() - 1. 188 | * @return The value. 189 | * @throws JSONException If the key is not found or if the value is not a number. */ 190 | public int getInt (int index) throws JSONException { 191 | Object object = get(index); 192 | try { 193 | return object instanceof Number ? ((Number)object).intValue() : Integer.parseInt((String)object); 194 | } catch (Exception e) { 195 | throw new JSONException("JSONArray[" + index + "] is not a number."); 196 | } 197 | } 198 | 199 | /** Get the JSONArray associated with an index. 200 | * @param index The index must be between 0 and length() - 1. 201 | * @return A JSONArray value. 202 | * @throws JSONException If there is no value for the index. or if the value is not a JSONArray */ 203 | public JSONArray getJSONArray (int index) throws JSONException { 204 | Object object = get(index); 205 | if (object instanceof JSONArray) { 206 | return (JSONArray)object; 207 | } 208 | throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); 209 | } 210 | 211 | /** Get the JSONObject associated with an index. 212 | * @param index subscript 213 | * @return A JSONObject value. 214 | * @throws JSONException If there is no value for the index or if the value is not a JSONObject */ 215 | public JSONObject getJSONObject (int index) throws JSONException { 216 | Object object = get(index); 217 | if (object instanceof JSONObject) { 218 | return (JSONObject)object; 219 | } 220 | throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); 221 | } 222 | 223 | /** Get the long value associated with an index. 224 | * 225 | * @param index The index must be between 0 and length() - 1. 226 | * @return The value. 227 | * @throws JSONException If the key is not found or if the value cannot be converted to a number. */ 228 | public long getLong (int index) throws JSONException { 229 | Object object = get(index); 230 | try { 231 | return object instanceof Number ? ((Number)object).longValue() : Long.parseLong((String)object); 232 | } catch (Exception e) { 233 | throw new JSONException("JSONArray[" + index + "] is not a number."); 234 | } 235 | } 236 | 237 | /** Get the string associated with an index. 238 | * @param index The index must be between 0 and length() - 1. 239 | * @return A string value. 240 | * @throws JSONException If there is no value for the index. */ 241 | public String getString (int index) throws JSONException { 242 | Object object = get(index); 243 | return object == JSONObject.NULL ? null : object.toString(); 244 | } 245 | 246 | /** Determine if the value is null. 247 | * @param index The index must be between 0 and length() - 1. 248 | * @return true if the value at the index is null, or if there is no value. */ 249 | public boolean isNull (int index) { 250 | return JSONObject.NULL.equals(opt(index)); 251 | } 252 | 253 | /** Make a string from the contents of this JSONArray. The separator string is inserted between each element. 254 | * Warning: This method assumes that the data structure is acyclical. 255 | * @param separator A string that will be inserted between the elements. 256 | * @return a string. 257 | * @throws JSONException If the array contains an invalid number. */ 258 | public String join (String separator) throws JSONException { 259 | int len = length(); 260 | StringBuffer sb = new StringBuffer(); 261 | 262 | for (int i = 0; i < len; i += 1) { 263 | if (i > 0) { 264 | sb.append(separator); 265 | } 266 | sb.append(JSONObject.valueToString(this.myArrayList.get(i))); 267 | } 268 | return sb.toString(); 269 | } 270 | 271 | /** Get the number of elements in the JSONArray, included nulls. 272 | * 273 | * @return The length (or size). */ 274 | public int length () { 275 | return this.myArrayList.size(); 276 | } 277 | 278 | /** Get the optional object value associated with an index. 279 | * @param index The index must be between 0 and length() - 1. 280 | * @return An object value, or null if there is no object at that index. */ 281 | public Object opt (int index) { 282 | return (index < 0 || index >= length()) ? null : this.myArrayList.get(index); 283 | } 284 | 285 | /** Get the optional boolean value associated with an index. It returns false if there is no value at that index, or if the 286 | * value is not Boolean.TRUE or the String "true". 287 | * 288 | * @param index The index must be between 0 and length() - 1. 289 | * @return The truth. */ 290 | public boolean optBoolean (int index) { 291 | return optBoolean(index, false); 292 | } 293 | 294 | /** Get the optional boolean value associated with an index. It returns the defaultValue if there is no value at that index or 295 | * if it is not a Boolean or the String "true" or "false" (case insensitive). 296 | * 297 | * @param index The index must be between 0 and length() - 1. 298 | * @param defaultValue A boolean default. 299 | * @return The truth. */ 300 | public boolean optBoolean (int index, boolean defaultValue) { 301 | try { 302 | return getBoolean(index); 303 | } catch (Exception e) { 304 | return defaultValue; 305 | } 306 | } 307 | 308 | /** Get the optional double value associated with an index. NaN is returned if there is no value for the index, or if the value 309 | * is not a number and cannot be converted to a number. 310 | * 311 | * @param index The index must be between 0 and length() - 1. 312 | * @return The value. */ 313 | public double optDouble (int index) { 314 | return optDouble(index, Double.NaN); 315 | } 316 | 317 | /** Get the optional double value associated with an index. The defaultValue is returned if there is no value for the index, or 318 | * if the value is not a number and cannot be converted to a number. 319 | * 320 | * @param index subscript 321 | * @param defaultValue The default value. 322 | * @return The value. */ 323 | public double optDouble (int index, double defaultValue) { 324 | try { 325 | return getDouble(index); 326 | } catch (Exception e) { 327 | return defaultValue; 328 | } 329 | } 330 | 331 | /** Get the optional int value associated with an index. Zero is returned if there is no value for the index, or if the value is 332 | * not a number and cannot be converted to a number. 333 | * 334 | * @param index The index must be between 0 and length() - 1. 335 | * @return The value. */ 336 | public int optInt (int index) { 337 | return optInt(index, 0); 338 | } 339 | 340 | /** Get the optional int value associated with an index. The defaultValue is returned if there is no value for the index, or if 341 | * the value is not a number and cannot be converted to a number. 342 | * @param index The index must be between 0 and length() - 1. 343 | * @param defaultValue The default value. 344 | * @return The value. */ 345 | public int optInt (int index, int defaultValue) { 346 | try { 347 | return getInt(index); 348 | } catch (Exception e) { 349 | return defaultValue; 350 | } 351 | } 352 | 353 | /** Get the optional JSONArray associated with an index. 354 | * @param index subscript 355 | * @return A JSONArray value, or null if the index has no value, or if the value is not a JSONArray. */ 356 | public JSONArray optJSONArray (int index) { 357 | Object o = opt(index); 358 | return o instanceof JSONArray ? (JSONArray)o : null; 359 | } 360 | 361 | /** Get the optional JSONObject associated with an index. Null is returned if the key is not found, or null if the index has no 362 | * value, or if the value is not a JSONObject. 363 | * 364 | * @param index The index must be between 0 and length() - 1. 365 | * @return A JSONObject value. */ 366 | public JSONObject optJSONObject (int index) { 367 | Object o = opt(index); 368 | return o instanceof JSONObject ? (JSONObject)o : null; 369 | } 370 | 371 | /** Get the optional long value associated with an index. Zero is returned if there is no value for the index, or if the value 372 | * is not a number and cannot be converted to a number. 373 | * 374 | * @param index The index must be between 0 and length() - 1. 375 | * @return The value. */ 376 | public long optLong (int index) { 377 | return optLong(index, 0); 378 | } 379 | 380 | /** Get the optional long value associated with an index. The defaultValue is returned if there is no value for the index, or if 381 | * the value is not a number and cannot be converted to a number. 382 | * @param index The index must be between 0 and length() - 1. 383 | * @param defaultValue The default value. 384 | * @return The value. */ 385 | public long optLong (int index, long defaultValue) { 386 | try { 387 | return getLong(index); 388 | } catch (Exception e) { 389 | return defaultValue; 390 | } 391 | } 392 | 393 | /** Get the optional string value associated with an index. It returns an empty string if there is no value at that index. If 394 | * the value is not a string and is not null, then it is coverted to a string. 395 | * 396 | * @param index The index must be between 0 and length() - 1. 397 | * @return A String value. */ 398 | public String optString (int index) { 399 | return optString(index, ""); 400 | } 401 | 402 | /** Get the optional string associated with an index. The defaultValue is returned if the key is not found. 403 | * 404 | * @param index The index must be between 0 and length() - 1. 405 | * @param defaultValue The default value. 406 | * @return A String value. */ 407 | public String optString (int index, String defaultValue) { 408 | Object object = opt(index); 409 | return object != null ? object.toString() : defaultValue; 410 | } 411 | 412 | /** Append a boolean value. This increases the array's length by one. 413 | * 414 | * @param value A boolean value. 415 | * @return this. */ 416 | public JSONArray put (boolean value) { 417 | put(value ? Boolean.TRUE : Boolean.FALSE); 418 | return this; 419 | } 420 | 421 | /** Put a value in the JSONArray, where the value will be a JSONArray which is produced from a Collection. 422 | * @param value A Collection value. 423 | * @return this. */ 424 | public JSONArray put (Collection value) { 425 | put(new JSONArray(value)); 426 | return this; 427 | } 428 | 429 | /** Append a double value. This increases the array's length by one. 430 | * 431 | * @param value A double value. 432 | * @throws JSONException if the value is not finite. 433 | * @return this. */ 434 | public JSONArray put (double value) throws JSONException { 435 | Double d = new Double(value); 436 | JSONObject.testValidity(d); 437 | put(d); 438 | return this; 439 | } 440 | 441 | /** Append an int value. This increases the array's length by one. 442 | * 443 | * @param value An int value. 444 | * @return this. */ 445 | public JSONArray put (int value) { 446 | put(new Integer(value)); 447 | return this; 448 | } 449 | 450 | /** Append an long value. This increases the array's length by one. 451 | * 452 | * @param value A long value. 453 | * @return this. */ 454 | public JSONArray put (long value) { 455 | put(new Long(value)); 456 | return this; 457 | } 458 | 459 | /** Put a value in the JSONArray, where the value will be a JSONObject which is produced from a Map. 460 | * @param value A Map value. 461 | * @return this. */ 462 | public JSONArray put (Map value) { 463 | put(new JSONObject(value)); 464 | return this; 465 | } 466 | 467 | /** Append an object value. This increases the array's length by one. 468 | * @param value An object value. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the 469 | * JSONObject.NULL object. 470 | * @return this. */ 471 | public JSONArray put (Object value) { 472 | this.myArrayList.add(value); 473 | return this; 474 | } 475 | 476 | /** Put or replace a boolean value in the JSONArray. If the index is greater than the length of the JSONArray, then null 477 | * elements will be added as necessary to pad it out. 478 | * @param index The subscript. 479 | * @param value A boolean value. 480 | * @return this. 481 | * @throws JSONException If the index is negative. */ 482 | public JSONArray put (int index, boolean value) throws JSONException { 483 | put(index, value ? Boolean.TRUE : Boolean.FALSE); 484 | return this; 485 | } 486 | 487 | /** Put a value in the JSONArray, where the value will be a JSONArray which is produced from a Collection. 488 | * @param index The subscript. 489 | * @param value A Collection value. 490 | * @return this. 491 | * @throws JSONException If the index is negative or if the value is not finite. */ 492 | public JSONArray put (int index, Collection value) throws JSONException { 493 | put(index, new JSONArray(value)); 494 | return this; 495 | } 496 | 497 | /** Put or replace a double value. If the index is greater than the length of the JSONArray, then null elements will be added as 498 | * necessary to pad it out. 499 | * @param index The subscript. 500 | * @param value A double value. 501 | * @return this. 502 | * @throws JSONException If the index is negative or if the value is not finite. */ 503 | public JSONArray put (int index, double value) throws JSONException { 504 | put(index, new Double(value)); 505 | return this; 506 | } 507 | 508 | /** Put or replace an int value. If the index is greater than the length of the JSONArray, then null elements will be added as 509 | * necessary to pad it out. 510 | * @param index The subscript. 511 | * @param value An int value. 512 | * @return this. 513 | * @throws JSONException If the index is negative. */ 514 | public JSONArray put (int index, int value) throws JSONException { 515 | put(index, new Integer(value)); 516 | return this; 517 | } 518 | 519 | /** Put or replace a long value. If the index is greater than the length of the JSONArray, then null elements will be added as 520 | * necessary to pad it out. 521 | * @param index The subscript. 522 | * @param value A long value. 523 | * @return this. 524 | * @throws JSONException If the index is negative. */ 525 | public JSONArray put (int index, long value) throws JSONException { 526 | put(index, new Long(value)); 527 | return this; 528 | } 529 | 530 | /** Put a value in the JSONArray, where the value will be a JSONObject which is produced from a Map. 531 | * @param index The subscript. 532 | * @param value The Map value. 533 | * @return this. 534 | * @throws JSONException If the index is negative or if the the value is an invalid number. */ 535 | public JSONArray put (int index, Map value) throws JSONException { 536 | put(index, new JSONObject(value)); 537 | return this; 538 | } 539 | 540 | /** Put or replace an object value in the JSONArray. If the index is greater than the length of the JSONArray, then null 541 | * elements will be added as necessary to pad it out. 542 | * @param index The subscript. 543 | * @param value The value to put into the array. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, Long, 544 | * or String, or the JSONObject.NULL object. 545 | * @return this. 546 | * @throws JSONException If the index is negative or if the the value is an invalid number. */ 547 | public JSONArray put (int index, Object value) throws JSONException { 548 | JSONObject.testValidity(value); 549 | if (index < 0) { 550 | throw new JSONException("JSONArray[" + index + "] not found."); 551 | } 552 | if (index < length()) { 553 | this.myArrayList.set(index, value); 554 | } else { 555 | while (index != length()) { 556 | put(JSONObject.NULL); 557 | } 558 | put(value); 559 | } 560 | return this; 561 | } 562 | 563 | /** Remove an index and close the hole. 564 | * @param index The index of the element to be removed. 565 | * @return The value that was associated with the index, or null if there was no value. */ 566 | public Object remove (int index) { 567 | Object o = opt(index); 568 | this.myArrayList.remove(index); 569 | return o; 570 | } 571 | 572 | /** Produce a JSONObject by combining a JSONArray of names with the values of this JSONArray. 573 | * @param names A JSONArray containing a list of key strings. These will be paired with the values. 574 | * @return A JSONObject, or null if there are no names or if this JSONArray has no values. 575 | * @throws JSONException If any of the names are null. */ 576 | public JSONObject toJSONObject (JSONArray names) throws JSONException { 577 | if (names == null || names.length() == 0 || length() == 0) { 578 | return null; 579 | } 580 | JSONObject jo = new JSONObject(); 581 | for (int i = 0; i < names.length(); i += 1) { 582 | jo.put(names.getString(i), this.opt(i)); 583 | } 584 | return jo; 585 | } 586 | 587 | /** Make a JSON text of this JSONArray. For compactness, no unnecessary whitespace is added. If it is not possible to produce a 588 | * syntactically correct JSON text then null will be returned instead. This could occur if the array contains an invalid 589 | * number. 590 | *

591 | * Warning: This method assumes that the data structure is acyclical. 592 | * 593 | * @return a printable, displayable, transmittable representation of the array. */ 594 | public String toString () { 595 | try { 596 | return '[' + join(",") + ']'; 597 | } catch (Exception e) { 598 | return null; 599 | } 600 | } 601 | 602 | /** Make a prettyprinted JSON text of this JSONArray. Warning: This method assumes that the data structure is acyclical. 603 | * @param indentFactor The number of spaces to add to each level of indentation. 604 | * @return a printable, displayable, transmittable representation of the object, beginning with [ 605 | *  (left bracket) and ending with ] (right bracket). 606 | * @throws JSONException */ 607 | public String toString (int indentFactor) throws JSONException { 608 | return toString(indentFactor, 0); 609 | } 610 | 611 | /** Make a prettyprinted JSON text of this JSONArray. Warning: This method assumes that the data structure is acyclical. 612 | * @param indentFactor The number of spaces to add to each level of indentation. 613 | * @param indent The indention of the top level. 614 | * @return a printable, displayable, transmittable representation of the array. 615 | * @throws JSONException */ 616 | String toString (int indentFactor, int indent) throws JSONException { 617 | int len = length(); 618 | if (len == 0) { 619 | return "[]"; 620 | } 621 | int i; 622 | StringBuffer sb = new StringBuffer("["); 623 | if (len == 1) { 624 | sb.append(JSONObject.valueToString(this.myArrayList.get(0), indentFactor, indent)); 625 | } else { 626 | int newindent = indent + indentFactor; 627 | sb.append('\n'); 628 | for (i = 0; i < len; i += 1) { 629 | if (i > 0) { 630 | sb.append(",\n"); 631 | } 632 | for (int j = 0; j < newindent; j += 1) { 633 | sb.append(' '); 634 | } 635 | sb.append(JSONObject.valueToString(this.myArrayList.get(i), indentFactor, newindent)); 636 | } 637 | sb.append('\n'); 638 | for (i = 0; i < indent; i += 1) { 639 | sb.append(' '); 640 | } 641 | } 642 | sb.append(']'); 643 | return sb.toString(); 644 | } 645 | 646 | /** Write the contents of the JSONArray as JSON text to a writer. For compactness, no whitespace is added. 647 | *

648 | * Warning: This method assumes that the data structure is acyclical. 649 | * 650 | * @return The writer. 651 | * @throws JSONException */ 652 | public Writer write (Writer writer) throws JSONException { 653 | try { 654 | boolean b = false; 655 | int len = length(); 656 | 657 | writer.write('['); 658 | 659 | for (int i = 0; i < len; i += 1) { 660 | if (b) { 661 | writer.write(','); 662 | } 663 | Object v = this.myArrayList.get(i); 664 | if (v instanceof JSONObject) { 665 | ((JSONObject)v).write(writer); 666 | } else if (v instanceof JSONArray) { 667 | ((JSONArray)v).write(writer); 668 | } else { 669 | writer.write(JSONObject.valueToString(v)); 670 | } 671 | b = true; 672 | } 673 | writer.write(']'); 674 | return writer; 675 | } catch (IOException e) { 676 | throw new JSONException(e); 677 | } 678 | } 679 | } 680 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | 651 | Also add information on how to contact you by electronic and paper mail. 652 | 653 | If the program does terminal interaction, make it output a short 654 | notice like this when it starts in an interactive mode: 655 | 656 | Copyright (C) 657 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 658 | This is free software, and you are welcome to redistribute it 659 | under certain conditions; type `show c' for details. 660 | 661 | The hypothetical commands `show w' and `show c' should show the appropriate 662 | parts of the General Public License. Of course, your program's commands 663 | might be different; for a GUI interface, you would use an "about box". 664 | 665 | You should also get your employer (if you work as a programmer) or school, 666 | if any, to sign a "copyright disclaimer" for the program, if necessary. 667 | For more information on this, and how to apply and follow the GNU GPL, see 668 | . 669 | 670 | The GNU General Public License does not permit incorporating your program 671 | into proprietary programs. If your program is a subroutine library, you 672 | may consider it more useful to permit linking proprietary applications with 673 | the library. If this is what you want to do, use the GNU Lesser General 674 | Public License instead of this License. But first, please read 675 | . 676 | --------------------------------------------------------------------------------