|
require 'gosu' |
|
|
|
up = ->(position){ position[:y] -= 5; position } |
|
down = ->(position){ position[:y] += 5; position } |
|
|
|
player1 = { |
|
player: :player1, |
|
position: {x: 0, y: 384}, |
|
bindings: { |
|
Gosu::KbQ => up, |
|
Gosu::KbA => down |
|
} |
|
} |
|
player2 = { |
|
player: :player2, |
|
position: {x: 1014, y: 384}, |
|
bindings: { |
|
Gosu::KbUp => up, |
|
Gosu::KbDown => down |
|
} |
|
} |
|
|
|
class PawngWindow < Gosu::Window |
|
@@ball = nil |
|
attr_accessor :paddles, :ball, :score |
|
def initialize(players) |
|
super(1024, 768, false) |
|
self.score = Score.new(self) |
|
self.paddles = players.map { |player| Paddle.new(self, player) } |
|
self.caption = "Pawng" |
|
reset |
|
load_assets |
|
end |
|
|
|
def reset |
|
self.ball = Ball.new(self, {x: 512, y: 384}) |
|
end |
|
|
|
def update |
|
close if button_down?(Gosu::KbEscape) |
|
ball.update(self) |
|
|
|
paddles.each do |paddle| |
|
paddle.update(self) |
|
ball.collide(paddle.position) |
|
end |
|
|
|
if ball.offscreen? |
|
farthest = ball.farthest(paddles) |
|
score.point(farthest) |
|
reset |
|
end |
|
end |
|
|
|
def draw |
|
paddles.each(&:draw) |
|
ball.draw |
|
score.draw(self) |
|
end |
|
|
|
def load_assets |
|
@@ball = Gosu::Image.new(self, 'ball.png', false) |
|
end |
|
end |
|
|
|
class Ball |
|
LEFT = -1 |
|
RIGHT = 1 |
|
attr_accessor :position, :speed, :direction |
|
def initialize(context, position) |
|
@image = Gosu::Image.new(context, 'ball.png', false) |
|
@position = position |
|
@direction = rand(2) == 0 ? LEFT : RIGHT |
|
@speed = 5 |
|
@offscreen = false |
|
end |
|
|
|
def update(context) |
|
position[:x] += speed * direction |
|
@offscreen = position[:x] > context.width || position[:x] < 0 |
|
end |
|
|
|
def draw |
|
@image.draw(position[:x], position[:y], 1) |
|
end |
|
|
|
def collide(other) |
|
deltaX = (position[:x] - other[:x]).abs |
|
deltaY = (position[:y] - other[:y]) |
|
hittingPaddle = deltaY > 0 && deltaY < 100 |
|
change_direction! if deltaX < 5 && hittingPaddle |
|
end |
|
|
|
def change_direction! |
|
self.direction = self.direction * -1 |
|
end |
|
|
|
def offscreen? |
|
@offscreen |
|
end |
|
|
|
def farthest(paddles) |
|
measurements = paddles.map do |paddle| |
|
delta = (position[:x] - paddle.position[:x]).abs |
|
{player: paddle.player, delta: delta} |
|
end |
|
measurements.sort{ |m1, m2| m1[:delta] <=> m2[:delta] }.last[:player] |
|
end |
|
end |
|
|
|
class Paddle |
|
attr_accessor :bindings, :position, :player |
|
def initialize(context, options = {}) |
|
@player = options[:player] |
|
@position = options[:position] |
|
@bindings = options[:bindings] |
|
@image = Gosu::Image.new(context, 'paddle.png', false) |
|
end |
|
|
|
def update(context) |
|
bindings.each do |binding, handler| |
|
self.position = handler.call(self.position) if context.button_down?(binding) |
|
end |
|
end |
|
|
|
def draw |
|
@image.draw(position[:x], position[:y], 1) |
|
end |
|
end |
|
|
|
class Score |
|
def initialize(context) |
|
@font = Gosu::Font.new(context, Gosu::default_font_name, 40) |
|
reset |
|
end |
|
|
|
def score(player) |
|
@scores[player] || 0 |
|
end |
|
|
|
def point(player) |
|
@scores[player] += 1 if @scores[player] |
|
end |
|
|
|
def reset |
|
@scores = {player1: 0, player2: 0} |
|
end |
|
|
|
def draw(context) |
|
y = @font.height * 2 |
|
x = context.width / 2 |
|
p1Score = scoreFor(1, score(:player1)) |
|
p2Score = scoreFor(2, score(:player2)) |
|
{p1Score => -1, p2Score => 1}.each do |msg, offsetModifier| |
|
width = @font.text_width(msg) |
|
@font.draw(msg, x + offsetModifier*width, y, 0) |
|
end |
|
end |
|
|
|
private |
|
|
|
def scoreFor(num, score) |
|
"P#{num} - #{score}" |
|
end |
|
end |
|
|
|
window = PawngWindow.new([player1, player2]) |
|
window.show |