Skip to content

Instantly share code, notes, and snippets.

@icebreaker
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save icebreaker/156be660281ab5dcb5b4 to your computer and use it in GitHub Desktop.
Save icebreaker/156be660281ab5dcb5b4 to your computer and use it in GitHub Desktop.
Quick and dirty Pong clone written in Ruby.
#!/usr/bin/env ruby
#
# Pong clone written in Ruby.
#
# 2014, Mihail Szabolcs
# Released into the Public Domain.
#
# Keys:
#
# Escape - quit
# Up Arrow - move up
# Down Arrow - move down
#
# That's all folks :)
#
#
require 'curses'
module Pong
FPS = 33 # 30 fps = 1000.0 / 30.0 =~ 33 ms
BLOCK = 254 # block character
ESC = 27 # escape ascii code
class Shape
attr_reader :w, :h
def initialize
@shape = yield.map { |l| l.split('') }
@w = @shape.map(&:length).max
@h = @shape.count
end
def draw(x, y)
@shape.each_with_index do |line, j|
line.each_with_index do |col, i|
if col != ' '
Curses.setpos(y + j, x + i)
Curses.addch(BLOCK)
end
end
end
end
end
class Logo
def initialize
@logo = Shape.new do
[ "0000000 0000000 00 0 0000000",
"0 0 0 0 0 0 0 0 ",
"0 0000 0 0 0 0 0 0 0000 ",
"0 0 0 0 0 0 0 0 0",
"0000 0000000 0 00 0000000" ]
end
@x = ((Curses.cols - @logo.w) / 2.0).ceil
@y = ((Curses.lines - @logo.h) / 2.0).ceil
end
def draw
@logo.draw(@x, @y)
end
end
class Digits
attr_reader :x, :y
def initialize
@digits = []
@digits << Shape.new do
[ "0000000",
"0 0",
"0 0",
"0 0",
"0000000"]
end
@digits << Shape.new do
[ " 00 ",
" 000 ",
" 0000 ",
" 00 ",
"0000000"]
end
@digits << Shape.new do
[ "0000000",
" 00 ",
" 00 ",
" 00 ",
"0000000"]
end
@digits << Shape.new do
[ "0000000",
" 0",
" 00000",
" 0",
"0000000"]
end
@digits << Shape.new do
[ "0 0",
"0 0",
"0000000",
" 0",
" 0"]
end
@digits << Shape.new do
[ "0000000",
"0 ",
"0000000",
" 0",
"0000000"]
end
@digits << Shape.new do
[ "0000000",
"0 ",
"0000000",
"0 0",
"0000000"]
end
@digits << Shape.new do
[ "0000000",
" 00",
" 00 ",
" 00 ",
" 00 "]
end
@digits << Shape.new do
[ "0000000",
"0 0",
"0000000",
"0 0",
"0000000"]
end
@digits << Shape.new do
[ "0000000",
"0 0",
"0000000",
" 0",
"0000000"]
end
@w = @digits.first.w
@h = @digits.first.h
end
def draw(x, y, n)
@digits[n].draw(x, y)
end
end
class Score
def initialize(x, y)
@x = x
@y = y
@digits = Digits.new
@score = 0
end
def draw
@digits.draw(@x, @y, @score)
end
def inc
@score += 1
self
end
def won?
@score > 9
end
end
class Separator
def initialize
@x = ((Curses.cols - 1) / 2).ceil
@y = 0
end
def draw
(0..Curses.lines-1).each do |i|
if i % 2 == 1
Curses.setpos(@y + i, @x)
Curses.addch(BLOCK)
end
end
end
end
class Ball
attr_reader :x, :y
def initialize
reset!
end
def reset!
@x = ((Curses.cols - 1) / 2).ceil
@y = ((Curses.lines - 1) / 2).ceil
@dx = rand(10) < 5 ? 1 : -1
@dy = rand(10) < 5 ? 1 : -1
@c = @x.ceil
end
def draw
Curses.setpos(@y, @x)
Curses.addch(BLOCK)
end
def coming_to_right?
@x > @c and @dx == 1
end
def is_out_left?
@x == 0
end
def is_out_right?
@x == Curses.cols - 1
end
def update
# if @x == 0 || @x == Curses.cols - 1
# @dx = -@dx
# end
if @y == 0 || @y == Curses.lines - 1
@dy = -@dy
end
if (x = @x - 1) == 0 || (x = @x + 1) == Curses.cols - 1
Curses.setpos(@y, x)
if Curses.inch == BLOCK
@dx = -@dx
Curses.setpos(@y - 1, x)
if Curses.inch != BLOCK
@dy = -1
else
Curses.setpos(@y + 1, x)
if Curses.inch != BLOCK
@dy = 1
end
end
end
Curses.setpos(@y, @x)
end
@x += @dx
@y += @dy
end
end
class Player
attr_reader :score
def initialize
@n = 4
@h = (@n / 2).ceil
@x ||= 0
@y = ((Curses.lines - @n) / 2).ceil
@score = Score.new((Curses.cols / 2.0).ceil + (@x == 0 ? -(@n * 3) : @n), 1)
end
def cy
@y + @h
end
def draw
(0..@n-1).each do |i|
Curses.setpos(@y + i, @x)
Curses.addch(BLOCK)
end
@score.draw
end
def up(n)
@y -= n if @y > 0
end
def down(n)
@y += n if @y + @n < Curses.lines
end
end
class Computer < Player
def initialize
@x = Curses.cols - 1
super
end
def can_move?
rand(66).to_i < 60
end
end
class Main
def initialize
srand(Time.now.to_f)
Curses.init_screen()
Curses.noecho()
Curses.nonl()
Curses.cbreak()
Curses.curs_set(0)
Curses.stdscr.keypad(true)
end
def run
Curses.stdscr.nodelay = false
Curses.clear()
Logo.new.draw
Curses.refresh()
case Curses.getch
when ESC
return
end
Curses.stdscr.nodelay = true
ball = Ball.new
separator = Separator.new
player = Player.new
computer = Computer.new
loop do
t = Time.now.to_f
Curses.clear()
separator.draw
ball.draw
player.draw
computer.draw
Curses.refresh()
case Curses.getch
when Curses::Key::UP
player.up(1)
when Curses::Key::DOWN
player.down(1)
when ESC
break
end
ball.update
if ball.coming_to_right? and computer.can_move?
if ball.y < computer.cy
computer.up(1)
elsif ball.y > computer.cy
computer.down(1)
end
end
if ball.is_out_left?
if computer.score.inc.won?
break
else
ball.reset!
end
elsif ball.is_out_right?
if player.score.inc.won?
break
else
ball.reset!
end
end
dt = (Time.now.to_f - t) * 1000.0
sleep((FPS - dt) * 0.001) if dt < FPS
end
run # :D
end
end
end
Pong::Main.new.run if $0 == __FILE__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment