Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new game for examples showcase #5

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ as change the background color of the application.

## Triangle Game
A simple "Triangle Game" that allows you to move a Roguelike '@' around the window (and off of it).

## Brick Game
A simple brick break / breakout game.
46 changes: 46 additions & 0 deletions brick/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<a href="https://codeclimate.com/github/mosinski/brick/maintainability"><img src="https://api.codeclimate.com/v1/badges/607fbf60ec4906c3bacb/maintainability" /></a>

# About

Classic brick game written in Ruby with the awesome [ruby2d](http://www.ruby2d.com) framework.

![ruby_brick](https://i.ibb.co/VCDbKqX/ezgif-com-video-to-gif.gif)

> Note: the low frame rate here is due to the GIF recording not the game itself.

# Install

Make sure you have installed:

* [simple2d](https://github.com/simple2d/simple2d):

```
brew tap simple2d/tap
brew install simple2d
```

* [ruby2d](https://github.com/ruby2d/ruby2d):

```
gem install ruby2d
```

Then clone the source code to your local:

```
git clone https://github.com/mosinski/brick.git
```

# Play

```
cd brick
ruby brick.rb
```

Paddle control:

* a: left
* d: right

Original code made by Tiago Guedesn (tiagopog) for Pong game (thanks!)
Binary file added brick/assets/PressStart2P.ttf
Binary file not shown.
121 changes: 121 additions & 0 deletions brick/brick.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require 'ruby2d'

require './lib/ball'
require './lib/paddle'
require './lib/match'

##
# Window & FPS
##

set title: 'Brick',
background: 'black',
with: 640,
height: 480,
resizable: false

fps_display = Text.new(get(:fps).to_i, x: 315, y: 463, size: 12)

##
# Pad
##

pad = Paddle.new(
x: get(:width) / 2 - 60 / 2,
y: get(:height) - 15,
width: 60,
height: 10,
speed: 7,
constraints: { x: { min: 0, max: get(:width) - 50 } }
)

##
# Bricks
##

bricks = []

4.times do |row|
10.times do |column|
bricks << Paddle.new(
x: get(:width) / 10 * column,
y: 15 * row,
width: 60,
height: 10,
speed: 0,
constraints: {}
)
end
end

##
# Ball
##

ball = Ball.new(
x: pad.x + (pad.width / 2) - 5,
y: 450,
size: 10,
speed: 5
)

##
# Pause
##

pause_display = Text.new(
'PRESS SPACE',
x: get(:width) / 2 - 110,
y: get(:height) / 2 - 20,
font: 'assets/PressStart2P.ttf',
color: 'gray',
size: 20,
opacity: 1
)

##
# Main
##

match = Match.new

# Paddle movement
on :key_held do |event|
pad.move(event, left: 'a', right: 'd')

if match.paused?
ball.x = pad.x + (pad.width / 2) - (ball.width / 2)
end
end

# Game pause
on :key_down do |event|
if event.key == 'space'
match.paused = false
pause_display.opacity = -1
end
end

update do
fps_display.text = get(:fps).to_i

if match.paused?
next
else
ball.move(window: get(:window), pad: pad, bricks: bricks)

if ball.scored?
ball.scored.each do |brick|
index = bricks.index(brick)
bricks[index].width = 0
bricks.delete(brick)
end
elsif ball.failed?
match.paused = !match.paused
pause_display.opacity = 1
match.restart!(get(:window), ball, pad)
end
end
end

show
112 changes: 112 additions & 0 deletions brick/lib/ball.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Deals with the logic of the brick's ball.
# @author Miłosz Osiński <milosz@icoded.com>
class Ball < Square
DEFAULTS = {
speed: 3,
direction: { x: 1, y: 1 }
}.freeze

attr_accessor :speed, :direction, :scored, :scored_at, :failed_at

# @api public
# @param speed [Integer, Float, nil] ball's speed
# @param direction [Hash, nil] ball's x and y axis directions
# @return [Ball]
def initialize(speed: nil, direction: nil, **args)
super(args)
@speed = speed || DEFAULTS[:speed]
@direction = direction || DEFAULTS[:direction]
end

# @api public
# @param window [Window] the game's window
# @param pad [Pad] the game's pad
# @param bricks [Array] the game's bricks
# @return [Hash] ball's current position
def move(window:, pad:, bricks:)
self.scored_at = nil

if edge_collision?(:x, window)
self.direction[:x] *= -1
elsif edge_collision?(:y, window)
self.failed_at = Time.now if check_edge_collision(:y, window) == :bottom
self.direction[:y] *= -1
elsif pad_collision?(pad)
self.direction[:y] *= -1
elsif brick_collision?(bricks).any?
self.scored_at = Time.now
self.scored = brick_collision?(bricks)
self.direction[:y] *= -1
end

self.x += direction[:x] * speed
self.y += direction[:y] * speed

{ x: x, y: y }
end

# @api public
# @return [Boolean] did it score a new point?
def scored?
!scored_at.nil?
end

# @api public
# @return [Boolean] did it fail?
def failed?
!failed_at.nil?
end

# @api public
# @param window [Window] the game's window
# @param pad [Pad] the game's pad
# @return [Integer, Float]
def reset_position!(window, pad)
self.x = pad.x + pad.width / 2 - 5
self.y = 450
end

private

# @api private
# @param axis [Symbol] which axis to check the collision
# @param window [Window] the game's window
# @return [Boolean]
def edge_collision?(axis, window)
!check_edge_collision(axis, window).nil?
end

# @api private
# @param axis [Symbol] which axis to check the collision
# @param window [Window] the game's window
# @return [Symbol] edge side where ball has collided
def check_edge_collision(axis, window)
if axis == :x && x + width >= window.get(:width)
:right
elsif axis == :x && x <= 0
:left
elsif axis == :y && y + height >= window.get(:height)
:bottom
elsif axis == :y && y <= 0
:top
end
end

# @api private
# @param pad [Pad] the game's pad
# @return [Boolean] has the ball collided with the pad?
def pad_collision?(pad)
y <= pad.y + pad.height && y + height >= pad.y &&
[x + width, pad.x + pad.width].min - [x, pad.x].max > 0
end

# @api private
# @param bricks [Hash] the game's bricks
# @return [Boolean] has the ball collided with any of the bricks?
def brick_collision?(bricks)
bricks.select do |brick|
y <= brick.y + brick.height && y + height >= brick.y &&
[x + width, brick.x + brick.width].min - [x, brick.x].max > 0
end
end
end
40 changes: 40 additions & 0 deletions brick/lib/match.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Deals with the logic of the brick's match.
# @author Miłosz Osiński <milosz@icoded.com>
class Match
attr_accessor :paused, :score, :reseted_at

# @api public
# @return [Match]
def initialize
@score = { left: 0, right: 0 }
@paused = true
end

# @api public
# @return [Boolaen]
alias paused? paused

# @api public
# @return [Boolaen]
def wait_to_start?
!reseted_at.nil?
end

# @api public
# @return [nil]
def check_wait!(current_frame)
self.reseted_at = nil if current_frame - reseted_at >= 60
end

# @api public
# @param window [Window] the game's window
# @param ball [Ball] the game's ball
# @param pad [Pad] the game's pad
# @return [Ingeter]
def restart!(window, ball, pad)
ball.scored_at = nil
ball.failed_at = nil
ball.reset_position!(window, pad)
self.reseted_at = window.get(:frames)
end
end
49 changes: 49 additions & 0 deletions brick/lib/paddle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Deals with the logic of the brick's paddle.
# @author Miłosz Osiński <milosz@icoded.com>
class Paddle < Rectangle
DEFAULTS = { speed: 3 }.freeze

attr_accessor :speed, :constraints

# @api public
# @param speed [Integer, Float, nil] pad's current speed
# @param constraints [Hash] pad's constraints (e.g. max "y")
# @return [Pad]
def initialize(speed: nil, constraints: {}, **args)
super(args)
@speed = speed || DEFAULTS[:speed]
@constraints = constraints
end

# @api public
# @param event [Ruby2D::Window::KeyEvent] event captured from keyboard
# @param speed [Integer, Float, nil] pad's speed
# @return [Integer, Float] pad's current y position
def move(event, left:, right:)
if move_left?(event, left)
self.x -= speed
elsif move_right?(event, right)
self.x += speed
end
end

private

# @api private
# @param event [Ruby2D::Window::KeyEvent] event captured from keyboard
# @param up [String] expected keyboard key for the up movement
# @return [Boolean] is it allowed to move up?
def move_left?(event, left)
min_x = constraints.dig(:x, :min)
event.key == left && (!min_x || x - speed >= min_x)
end

# @api private
# @param event [Ruby2D::Window::KeyEvent] event captured from keyboard
# @param down [String] expected keyboard key for the down movement
# @return [Boolean] is it allowed to move down?
def move_right?(event, right)
max_x = constraints.dig(:x, :max)
event.key == right && (!max_x || x + speed + height <= max_x)
end
end