Skip to content

Instantly share code, notes, and snippets.

@cadwallion
Created December 17, 2011 14:35
Show Gist options
  • Save cadwallion/1490347 to your computer and use it in GitHub Desktop.
Save cadwallion/1490347 to your computer and use it in GitHub Desktop.
Peter Cooper's jRuby Pong, Refactored
require 'java'
require 'lwjgl.jar'
require 'slick.jar'
java_import org.newdawn.slick.Image
require 'pong/image_context'
require 'pong/collidable_attributes'
module Pong
class Ball
include Pong::ImageContext
include CollidableAttributes
attr_accessor :x, :y, :velocity, :angle
def initialize
@image = Image.new('ball.png')
reset
end
def reset
@x = 200
@y = 200
@angle = 45
@velocity = 0.3
end
def move(delta)
@x += @velocity * delta * Math.cos(@angle * Math::PI / 180)
@y -= @velocity * delta * Math.sin(@angle * Math::PI / 180)
end
end
end
module Pong
# Used for collision detection of the ball to the container and the ball to the paddle.
# This module assumes the existence of dimension attributes (x, y, width, height)
#
# @TODO: Extract this into GameUtils
module CollidableAttributes
def is_colliding_with? entity
x >= entity.x && x <= (entity.x + entity.width) && y.round >= (entity.y - height)
end
def colliding_with_container? container
%w{left right top bottom}.each do |direction|
return true if container_collision(container, direction)
end
return false
end
def container_collision(entity, edge)
case edge
when 'left'
return x < 0
when 'right'
return x > entity.width - width
when 'top'
return y < 0
when 'bottom'
return y > entity.height
else
return false
end
end
end
end
module Pong
# Images in Slick2D work differently than...well, any other game library I've worked with. Instead of
# explicitly binding images to the window context, it implicitly binds them by only allowing a single
# container to be instantiated.
#
# ImageContext handles delegation of coordinates and sizing of an object to the image context. In Pong,
# we use this for the Pong and Paddle
module ImageContext
attr_accessor :image
def width
@image.width
end
def height
@image.height
end
# Assumes `x` and `y` methods in the included method
def draw
@image.draw(x, y)
end
end
end
$:.push File.expand_path('../lib', __FILE__)
require 'java'
require 'lwjgl.jar'
require 'slick.jar'
require 'pong'
# Screwing with the Java library integration
java_import org.newdawn.slick.AppGameContainer
# Slick2D isolates the game class from the window context that renders it
# This is different from Gosu that mixes it within the Gosu::Window class
app = AppGameContainer.new(Pong::Game.new)
app.set_display_mode(640, 480, false)
app.start
require 'java'
require 'lwjgl.jar'
require 'slick.jar'
java_import org.newdawn.slick.Image
require 'pong/image_context'
require 'pong/collidable_attributes'
module Pong
class Paddle
include Pong::ImageContext
include CollidableAttributes
attr_accessor :x, :y, :velocity
def initialize
@image = Image.new('paddle.png')
@y = 400
reset
end
def reset
@x = 200
@velocity = 0.3
end
def move(delta)
@x += velocity * delta
end
end
end
require 'java'
require 'lwjgl.jar'
require 'slick.jar'
require 'pong/ball'
require 'pong/paddle'
java_import org.newdawn.slick.BasicGame
java_import org.newdawn.slick.GameContainer
java_import org.newdawn.slick.Graphics
java_import org.newdawn.slick.Image
java_import org.newdawn.slick.Input
java_import org.newdawn.slick.SlickException
module Pong
# We subclass BasicGame so we can let Slick2D handle the rendering and main game loop
# This means we need to define `render`, `init`, and `update`.
class Game < BasicGame
def initialize
super('RubyPong')
end
# This method is called continuously. Rule of thumb: Always redraw the screen from blank. You do not
# because you do not know if `update` will have been called.
def render(container, graphics)
@bg.draw(0, 0)
@ball.draw
@paddle.draw
graphics.draw_string('RubyPong (ESC to exit)', 8, container.height - 30)
end
# Establishes the connection between container and game state. Creates our game objects. We have `init`
# AND `initialize` because Java sucks at naming...
#
# @param [AppContainer] - The AppContainer state
def init(container)
@bg = Image.new('bg.png')
@ball = Pong::Ball.new
@paddle = Pong::Paddle.new
end
# This is your game logic method. This will be called continuously in the main game loop and should be
# the entry point for input handling and game state change. Do NOT put rendering logic in the update method
# due to the nature of update vs render running potentially simultaneously
#
# @param [AppContainer] - The AppContainer game state
# @param [Float] - The time since last update call
def update(container, delta)
handle_input(container, delta)
@ball.move(delta)
collision_detection(container)
end
# Handles input to move the paddle left/right and game exit.
# Controls: [LEFT/A] for left, [RIGHT/D] for right, [ESC] to exit
#
# Input handler. Every key is mapped to a constant in the Input class. This method is not an override, but
# it's a good idea to isolate your input handler from your game logic as it will be the second most bloated
# point for game logic
#
# @param [AppContainer] - The AppContainer game state
# @param [Float] - Time since last update call
def handle_input(container, delta)
input = container.get_input
container.exit if input.is_key_down(Input::KEY_ESCAPE)
@paddle.move(-delta) if (input.is_key_down(Input::KEY_LEFT) || input.is_key_down(Input::KEY_A)) && !@paddle.container_collision(container, 'left')
@paddle.move(delta) if (input.is_key_down(Input::KEY_RIGHT) || input.is_key_down(Input::KEY_D)) && !@paddle.container_collision(container, 'right')
end
# Collision detection for the ball staying within the window container
#
# @param [container] - the AppContainer object state
def collision_detection(container)
if @ball.colliding_with_container? container
@ball.angle = (@ball.angle + 90) % 360
end
if @ball.container_collision(container, 'bottom')
@paddle.reset
@ball.reset
end
if @ball.is_colliding_with? @paddle
@ball.angle = (@ball.angle + 90) % 360
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment