001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.demos.sandbox; 031 032import java.awt.event.KeyEvent; 033import java.awt.event.KeyListener; 034import java.text.AttributedCharacterIterator.Attribute; 035import java.text.AttributedString; 036import java.util.HashMap; 037import java.util.Random; 038 039import javax.swing.JFrame; 040import javax.swing.SwingUtilities; 041 042import org.openimaj.image.MBFImage; 043import org.openimaj.image.colour.ColourSpace; 044import org.openimaj.image.colour.RGBColour; 045import org.openimaj.image.renderer.MBFImageRenderer; 046import org.openimaj.image.typography.FontStyle; 047import org.openimaj.image.typography.hershey.HersheyFont; 048import org.openimaj.math.geometry.line.Line2d; 049import org.openimaj.math.geometry.point.Point2dImpl; 050import org.openimaj.math.geometry.shape.Circle; 051import org.openimaj.math.geometry.shape.Rectangle; 052import org.openimaj.math.geometry.transforms.TransformUtilities; 053import org.openimaj.video.Video; 054import org.openimaj.video.VideoDisplay; 055 056import Jama.Matrix; 057 058/** 059 * A rather simple implementation of Pong 060 * 061 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 062 * 063 */ 064public class Pong extends Video<MBFImage> { 065 private static final float PADDLE_RADIUS = 0.07f; 066 private static final float BALL_SIZE = 0.02f; 067 private static final float PADDLE_VELOCITY = 0.01f; 068 069 private static final float BORDER_TOP = 0.1f; 070 private static final float BORDER_BOTTOM = 0.05f; 071 072 private static final float SPEED_MULTIPLIER = 1.2f; 073 private static final float INITIAL_VELOCITY = 5.0f; 074 075 private final Rectangle borderTop; 076 private final Rectangle borderBottom; 077 078 protected Circle paddleLeft; 079 protected Circle paddleRight; 080 private Point2dImpl paddleLeftPoint; 081 private Point2dImpl paddleRightPoint; 082 private Circle ball; 083 private Point2dImpl ballCentre; 084 085 private final MBFImage lastFrame; 086 private final MBFImage frame; 087 private final MBFImageRenderer renderer; 088 089 private Point2dImpl ballVelocity; 090 091 private int scoreLeft = 0; 092 private int scoreRight = 0; 093 private final int frame_height; 094 private final int frame_width; 095 private final HashMap<Attribute, Object> redText; 096 private final HashMap<Attribute, Object> blueText; 097 private final HashMap<Attribute, Object> allFont; 098 099 public Pong(final int width, final int height) { 100 101 this.redText = new HashMap<Attribute, Object>(); 102 this.redText.put(FontStyle.COLOUR, RGBColour.RED); 103 104 this.blueText = new HashMap<Attribute, Object>(); 105 this.blueText.put(FontStyle.COLOUR, RGBColour.BLUE); 106 107 this.allFont = new HashMap<Attribute, Object>(); 108 this.allFont.put(FontStyle.FONT, HersheyFont.ROMAN_SIMPLEX); 109 this.allFont.put(FontStyle.FONT_SIZE, 40); 110 111 this.frame_height = height; 112 this.frame_width = width; 113 this.lastFrame = new MBFImage(width, height, ColourSpace.RGB); 114 this.frame = new MBFImage(width, height, ColourSpace.RGB); 115 this.renderer = this.frame.createRenderer(); 116 117 this.borderTop = new Rectangle(0, 0, width, height * Pong.BORDER_TOP); 118 this.borderBottom = new Rectangle(0, height * (1 - Pong.BORDER_BOTTOM), width, height * Pong.BORDER_BOTTOM); 119 120 this.initMatch(); 121 122 this.getNextFrame(); 123 } 124 125 private void initMatch() { 126 this.scoreLeft = 0; 127 this.scoreRight = 0; 128 129 this.initGame(); 130 } 131 132 private void initGame() { 133 this.lastFrame.fill(RGBColour.BLACK); 134 this.frame.fill(RGBColour.BLACK); 135 final Random r = new Random(); 136 this.paddleLeftPoint = new Point2dImpl(0, 0.5f * this.frame_height); 137 this.paddleLeft = new Circle(this.paddleLeftPoint, Pong.PADDLE_RADIUS * this.frame_height); 138 this.paddleRightPoint = new Point2dImpl(this.frame_width, 0.5f * this.frame_height); 139 this.paddleRight = new Circle(this.paddleRightPoint, Pong.PADDLE_RADIUS * this.frame_height); 140 141 this.ballCentre = new Point2dImpl(0.5f * this.frame_width, 0.5f * this.frame_height); 142 this.ball = new Circle(this.ballCentre, Pong.BALL_SIZE * this.frame_width); 143 144 this.ballVelocity = new Point2dImpl(); 145 146 // float vx = (float) (Math.random() - 0.5); 147 // float vy = (float) (Math.random() - 0.5); 148 // 149 // if (0.5 * vy > vx) { 150 // float tmp = vx; 151 // vx = vy; 152 // vy = tmp; 153 // } 154 // 155 // float mult = (float) (INITIAL_VELOCITY / Math.sqrt(vx*vx + vy*vy)); 156 // vx*=mult; 157 // vy*=mult; 158 float vx = r.nextBoolean() ? 1f : -1f; 159 float vy = r.nextFloat() * 2 + -1f; 160 final float mult = (float) (Pong.INITIAL_VELOCITY / Math.sqrt(vx * vx + vy * vy)); 161 vx *= mult; 162 vy *= mult; 163 this.ballVelocity = new Point2dImpl(vx, vy); 164 } 165 166 @Override 167 public MBFImage getNextFrame() { 168 // draw scene 169 this.updateBall(); 170 171 this.frame.fill(RGBColour.BLACK); 172 this.renderer.drawShapeFilled(this.borderTop, RGBColour.GRAY); 173 this.renderer.drawShapeFilled(this.borderBottom, RGBColour.GRAY); 174 175 this.renderer.drawShapeFilled(this.paddleLeft, RGBColour.RED); 176 this.renderer.drawShapeFilled(this.paddleRight, RGBColour.BLUE); 177 178 this.renderer.drawShapeFilled(this.ball, RGBColour.WHITE); 179 // if(contactPoint!=null) renderer.drawPoint(contactPoint, 1.0f, 10); 180 final int scorLeftLength = new String("" + this.scoreLeft).length(); 181 final int scorRightLength = new String("" + this.scoreRight).length(); 182 final int sepLength = new String(" : ").length(); 183 final String allString = this.scoreLeft + " : " + this.scoreRight; 184 final AttributedString str = new AttributedString(allString); 185 str.addAttributes(this.allFont, 0, allString.length()); 186 str.addAttributes(this.redText, 0, scorLeftLength); 187 str.addAttributes(this.blueText, scorLeftLength + sepLength, scorLeftLength + sepLength + scorRightLength); 188 this.renderer.drawText(str, this.frame_width / 2, 40); 189 190 this.lastFrame.drawImage(this.frame, 0, 0); 191 192 this.currentFrame++; 193 194 return this.frame; 195 } 196 197 private void updateBall() { 198 float newX = this.ballCentre.x + this.ballVelocity.x; 199 float newY = this.ballCentre.y + this.ballVelocity.y; 200 201 if (newX < 0) { 202 this.initGame(); 203 this.scoreRight++; 204 return; 205 } 206 207 if (newX > this.frame_width) { 208 this.initGame(); 209 this.scoreLeft++; 210 return; 211 } 212 213 if (newY < this.frame_height * (Pong.BORDER_TOP + Pong.BALL_SIZE)) { 214 newY = 2 * this.frame_height * (Pong.BORDER_TOP + Pong.BALL_SIZE) - newY; 215 this.ballVelocity.y = -this.ballVelocity.y; 216 } 217 218 if (newY > this.frame_height * (1 - Pong.BORDER_BOTTOM - Pong.BALL_SIZE)) { 219 newY = 2 * this.frame_height * (1 - Pong.BORDER_BOTTOM - Pong.BALL_SIZE) - newY; 220 this.ballVelocity.y = -this.ballVelocity.y; 221 } 222 223 final double dLeftPaddle = Line2d.distance(this.paddleLeftPoint, new Point2dImpl(newX, newY)); 224 final double dRightPaddle = Line2d.distance(this.paddleRightPoint, new Point2dImpl(newX, newY)); 225 226 if (dLeftPaddle <= this.paddleLeft.getRadius() + this.ball.getRadius()) { 227 228 // contactPoint = contactPoint(paddleLeft,this.ball); 229 final Point2dImpl contactDelta = this.normContactDelta(this.paddleLeft, this.ball); 230 231 newX = this.paddleLeftPoint.x + contactDelta.x * ((Pong.PADDLE_RADIUS + Pong.BALL_SIZE) * this.frame_height); 232 newY = this.paddleLeftPoint.y + contactDelta.y * ((Pong.PADDLE_RADIUS + Pong.BALL_SIZE) * this.frame_height); 233 234 final double rotation = -Math.atan2(contactDelta.y, contactDelta.x); 235 final Matrix trans = TransformUtilities.rotationMatrix(rotation); 236 final Point2dImpl newVel = this.ballVelocity.transform(trans); 237 newVel.x *= -1; 238 newVel.transform(trans.inverse()); 239 this.ballVelocity = newVel; 240 241 // newX += ballVelocity.x; 242 243 this.ballVelocity.x *= Pong.SPEED_MULTIPLIER; 244 this.ballVelocity.y *= Pong.SPEED_MULTIPLIER; 245 } 246 247 if (dRightPaddle <= this.paddleRight.getRadius() + this.ball.getRadius()) { 248 // contactPoint = contactPoint(paddleRight,this.ball); 249 final Point2dImpl contactDelta = this.normContactDelta(this.paddleRight, this.ball); 250 251 newX = this.paddleRightPoint.x + contactDelta.x * ((Pong.PADDLE_RADIUS + Pong.BALL_SIZE) * this.frame_height); 252 newY = this.paddleRightPoint.y + contactDelta.y * ((Pong.PADDLE_RADIUS + Pong.BALL_SIZE) * this.frame_height); 253 final double rotation = Math.atan2(contactDelta.y, -contactDelta.x); 254 255 final Matrix trans = TransformUtilities.rotationMatrix(rotation); 256 final Point2dImpl newVel = this.ballVelocity.transform(trans); 257 newVel.x *= -1; 258 newVel.transform(trans.inverse()); 259 this.ballVelocity = newVel; 260 261 // newX += ballVelocity.x; 262 263 this.ballVelocity.x *= Pong.SPEED_MULTIPLIER; 264 this.ballVelocity.y *= Pong.SPEED_MULTIPLIER; 265 } 266 267 this.ballCentre.x = newX; 268 this.ballCentre.y = newY; 269 } 270 271 // private Point2dImpl contactPoint(final Circle paddle, final Circle ball) 272 // { 273 // final float px = paddle.getX(); 274 // final float py = paddle.getY(); 275 // final Point2dImpl contactDelta = this.normContactDelta(paddle,ball); 276 // return new Point2dImpl(px+contactDelta.x,py+contactDelta.y); 277 // } 278 279 private Point2dImpl normContactDelta(final Circle paddle, final Circle ball) { 280 final float px = paddle.getX(); 281 final float py = paddle.getY(); 282 final float dx = ball.getX() - px; 283 final float dy = ball.getY() - py; 284 final float plusX = dx * (paddle.getRadius() / (ball.getRadius() + paddle.getRadius())); 285 final float plusY = dy * (paddle.getRadius() / (ball.getRadius() + paddle.getRadius())); 286 287 final float prop = (float) (1f / Math.sqrt(plusX * plusX + plusY * plusY)); 288 return new Point2dImpl(plusX * prop, plusY * prop); 289 } 290 291 @Override 292 public MBFImage getCurrentFrame() { 293 return this.lastFrame; 294 } 295 296 @Override 297 public int getWidth() { 298 return this.frame_width; 299 } 300 301 @Override 302 public int getHeight() { 303 return this.frame_height; 304 } 305 306 @Override 307 public long getTimeStamp() { 308 return (long) (this.currentFrame * 1000d / this.getFPS()); 309 } 310 311 @Override 312 public double getFPS() { 313 return 30; 314 } 315 316 @Override 317 public boolean hasNextFrame() { 318 return true; 319 } 320 321 @Override 322 public long countFrames() { 323 return -1; 324 } 325 326 @Override 327 public void reset() { 328 this.initMatch(); 329 this.getNextFrame(); 330 } 331 332 public void leftPaddle(final float paddleY) { 333 this.paddleLeftPoint.y = paddleY; 334 335 if (this.paddleLeftPoint.y > this.frame_height * (1 - Pong.BORDER_BOTTOM - Pong.PADDLE_RADIUS)) 336 this.paddleLeftPoint.y = this.frame_height * (1 - Pong.BORDER_BOTTOM - Pong.PADDLE_RADIUS); 337 if (this.paddleLeftPoint.y < (Pong.BORDER_TOP + Pong.PADDLE_RADIUS) * this.frame_height) 338 this.paddleLeftPoint.y = (Pong.BORDER_TOP + Pong.PADDLE_RADIUS) * this.frame_height; 339 340 } 341 342 protected void rightPaddle(final float paddleY) { 343 this.paddleRightPoint.y = paddleY; 344 345 if (this.paddleRightPoint.y < (Pong.BORDER_TOP + Pong.PADDLE_RADIUS) * this.frame_height) 346 this.paddleRightPoint.y = (Pong.BORDER_TOP + Pong.PADDLE_RADIUS) * this.frame_height; 347 if (this.paddleRightPoint.y > this.frame_height * (1 - Pong.BORDER_BOTTOM - Pong.PADDLE_RADIUS)) 348 this.paddleRightPoint.y = this.frame_height * (1 - Pong.BORDER_BOTTOM - Pong.PADDLE_RADIUS); 349 } 350 351 public void leftPaddleUp() { 352 this.leftPaddle(this.paddleLeftPoint.y - Pong.PADDLE_VELOCITY * this.frame_height); 353 } 354 355 public void leftPaddleDown() { 356 this.leftPaddle(this.paddleLeftPoint.y + Pong.PADDLE_VELOCITY * this.frame_height); 357 } 358 359 protected void rightPaddleUp() { 360 this.rightPaddle(this.paddleRightPoint.y - Pong.PADDLE_VELOCITY * this.frame_height); 361 } 362 363 protected void rightPaddleDown() { 364 this.rightPaddle(this.paddleRightPoint.y + Pong.PADDLE_VELOCITY * this.frame_height); 365 } 366 367 public static void main(final String[] args) { 368 final Pong p = new Pong(640, 480); 369 final VideoDisplay<MBFImage> display = VideoDisplay.createVideoDisplay(p); 370 ((JFrame) SwingUtilities.getRoot(display.getScreen())).setTitle("OpenIMAJ Pong!"); 371 372 SwingUtilities.getRoot(display.getScreen()).addKeyListener(new KeyListener() { 373 374 @Override 375 public void keyTyped(final KeyEvent e) { 376 } 377 378 @Override 379 public void keyPressed(final KeyEvent e) { 380 switch (e.getKeyChar()) { 381 case 'a': 382 p.leftPaddleUp(); 383 break; 384 case 'z': 385 p.leftPaddleDown(); 386 break; 387 case '\'': 388 p.rightPaddleUp(); 389 break; 390 case '/': 391 p.rightPaddleDown(); 392 break; 393 case 'r': 394 p.reset(); 395 break; 396 } 397 } 398 399 @Override 400 public void keyReleased(final KeyEvent e) { 401 } 402 403 }); 404 } 405 406 public float leftPaddleY() { 407 return this.paddleLeftPoint.y; 408 } 409 410 public float rightPaddleY() { 411 return this.paddleRightPoint.y; 412 } 413}