Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

When I was a young teen I had a very boastful friend who claimed he could code up breakout (http://en.wikipedia.org/wiki/Breakout_(video_game)) in half an hour. Half an hour later he had done a great job but it didn't quite work. This challenge is to see in 2013 how short it is possible to code this classic game. For the purposes of the competition there need only be one level and no score keeping is needed. The game should be controlled from the keyboard (right and left is all that is needed) but the bricks need to be destroyed correctly and the ball has to bounce in the usual way.

You can choose any programming language you like as well as any free (in both sense) libraries. The code should run in linux.

To inspire you, here is a bare bones and completely ungolfed version of the game in python by Leonel Machava from http://codentronix.com/2011/04/14/game-programming-with-python-and-pygame-making-breakout/ .

"""
 bricka (a breakout clone)
 Developed by Leonel Machava <leonelmachava@gmail.com>

 http://codeNtronix.com
"""
import sys
import pygame

SCREEN_SIZE   = 640,480

# Object dimensions
BRICK_WIDTH   = 60
BRICK_HEIGHT  = 15
PADDLE_WIDTH  = 60
PADDLE_HEIGHT = 12
BALL_DIAMETER = 16
BALL_RADIUS   = BALL_DIAMETER / 2

MAX_PADDLE_X = SCREEN_SIZE[0] - PADDLE_WIDTH
MAX_BALL_X   = SCREEN_SIZE[0] - BALL_DIAMETER
MAX_BALL_Y   = SCREEN_SIZE[1] - BALL_DIAMETER

# Paddle Y coordinate
PADDLE_Y = SCREEN_SIZE[1] - PADDLE_HEIGHT - 10

# Color constants
BLACK = (0,0,0)
WHITE = (255,255,255)
BLUE  = (0,0,255)
BRICK_COLOR = (200,200,0)

# State constants
STATE_BALL_IN_PADDLE = 0
STATE_PLAYING = 1
STATE_WON = 2
STATE_GAME_OVER = 3

class Bricka:

    def __init__(self):
        pygame.init()

        self.screen = pygame.display.set_mode(SCREEN_SIZE)
        pygame.display.set_caption("bricka (a breakout clone by codeNtronix.com)")

        self.clock = pygame.time.Clock()

        if pygame.font:
            self.font = pygame.font.Font(None,30)
        else:
            self.font = None

        self.init_game()


    def init_game(self):
        self.lives = 3
        self.score = 0
        self.state = STATE_BALL_IN_PADDLE

        self.paddle   = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
        self.ball     = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)

        self.ball_vel = [5,-5]

        self.create_bricks()


    def create_bricks(self):
        y_ofs = 35
        self.bricks = []
        for i in range(7):
            x_ofs = 35
            for j in range(8):
                self.bricks.append(pygame.Rect(x_ofs,y_ofs,BRICK_WIDTH,BRICK_HEIGHT))
                x_ofs += BRICK_WIDTH + 10
            y_ofs += BRICK_HEIGHT + 5

    def draw_bricks(self):
        for brick in self.bricks:
            pygame.draw.rect(self.screen, BRICK_COLOR, brick)

    def check_input(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_LEFT]:
            self.paddle.left -= 5
            if self.paddle.left < 0:
                self.paddle.left = 0

        if keys[pygame.K_RIGHT]:
            self.paddle.left += 5
            if self.paddle.left > MAX_PADDLE_X:
                self.paddle.left = MAX_PADDLE_X

        if keys[pygame.K_SPACE] and self.state == STATE_BALL_IN_PADDLE:
            self.ball_vel = [5,-5]
            self.state = STATE_PLAYING
        elif keys[pygame.K_RETURN] and (self.state == STATE_GAME_OVER or self.state == STATE_WON):
            self.init_game()

    def move_ball(self):
        self.ball.left += self.ball_vel[0]
        self.ball.top  += self.ball_vel[1]

        if self.ball.left <= 0:
            self.ball.left = 0
            self.ball_vel[0] = -self.ball_vel[0]
        elif self.ball.left >= MAX_BALL_X:
            self.ball.left = MAX_BALL_X
            self.ball_vel[0] = -self.ball_vel[0]

        if self.ball.top < 0:
            self.ball.top = 0
            self.ball_vel[1] = -self.ball_vel[1]
        elif self.ball.top >= MAX_BALL_Y:            
            self.ball.top = MAX_BALL_Y
            self.ball_vel[1] = -self.ball_vel[1]

    def handle_collisions(self):
        for brick in self.bricks:
            if self.ball.colliderect(brick):
                self.score += 3
                self.ball_vel[1] = -self.ball_vel[1]
                self.bricks.remove(brick)
                break

        if len(self.bricks) == 0:
            self.state = STATE_WON

        if self.ball.colliderect(self.paddle):
            self.ball.top = PADDLE_Y - BALL_DIAMETER
            self.ball_vel[1] = -self.ball_vel[1]
        elif self.ball.top > self.paddle.top:
            self.lives -= 1
            if self.lives > 0:
                self.state = STATE_BALL_IN_PADDLE
            else:
                self.state = STATE_GAME_OVER

    def show_stats(self):
        if self.font:
            font_surface = self.font.render("SCORE: " + str(self.score) + " LIVES: " + str(self.lives), False, WHITE)
            self.screen.blit(font_surface, (205,5))

    def show_message(self,message):
        if self.font:
            size = self.font.size(message)
            font_surface = self.font.render(message,False, WHITE)
            x = (SCREEN_SIZE[0] - size[0]) / 2
            y = (SCREEN_SIZE[1] - size[1]) / 2
            self.screen.blit(font_surface, (x,y))


    def run(self):
        while 1:            
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit

            self.clock.tick(50)
            self.screen.fill(BLACK)
            self.check_input()

            if self.state == STATE_PLAYING:
                self.move_ball()
                self.handle_collisions()
            elif self.state == STATE_BALL_IN_PADDLE:
                self.ball.left = self.paddle.left + self.paddle.width / 2
                self.ball.top  = self.paddle.top - self.ball.height
                self.show_message("PRESS SPACE TO LAUNCH THE BALL")
            elif self.state == STATE_GAME_OVER:
                self.show_message("GAME OVER. PRESS ENTER TO PLAY AGAIN")
            elif self.state == STATE_WON:
                self.show_message("YOU WON! PRESS ENTER TO PLAY AGAIN")

            self.draw_bricks()

            # Draw paddle
            pygame.draw.rect(self.screen, BLUE, self.paddle)

            # Draw ball
            pygame.draw.circle(self.screen, WHITE, (self.ball.left + BALL_RADIUS, self.ball.top + BALL_RADIUS), BALL_RADIUS)

            self.show_stats()

            pygame.display.flip()

if __name__ == "__main__":
    Bricka().run()

I will accept the answer with the highest number of votes in exactly one week. Extra extra points if anyone can make a version in half an hour :)

Update The answer are great but... surely there are more people who want to show off their favourite language to play breakout! Come on python/perl/Haskell/crazy language experts :)

Winners And the winner is.. Ray. However, extra extra points go to... Schmiddty for completing the task in under 30 minutes! I would still love to see implementations in other languages however.

share|improve this question
    
What exactly needs to be implemented? Do all bricks break after being hit once? Indestructible bricks? Win/lose logic? Brick Colors? –  Shmiddty Feb 11 '13 at 20:22
    
@Shmiddty A brick breaks when it is hit once. No indestructible bricks needed nor win/lose logic. The bouncing rules have to be correct however. Colors are up to you but I hope you can include some. –  felipa Feb 11 '13 at 22:07
    
Could you clarify The bouncing rules have to be correct? In your python example implementation, the absolute value of the ball's velocity will always be [5, 5]. Is this acceptable? –  Shmiddty Feb 11 '13 at 23:58
    
@Shmiddty Constant speed is fine. I should say the example I gave is a little fast for me... :) –  felipa Feb 12 '13 at 8:52
    
@felipa I am glad to see my python code referenced here. Thank you. –  Leonel Machava Mar 25 '13 at 21:30
show 1 more comment

4 Answers 4

up vote 7 down vote accepted

Python 3 with Pygame and PIL / many hours

After playing other answers, that has simple level config, an idea came to me: The level can be initialized be an image.

So I make this one. To play it, you draw an image that contains many convex polygons, like this: image example

The black color will be parsed as walls and the lowest block will be the player. Save it as break-windows.png and then run with command:

python3 brcki.py break-windows.png

Another feature is that the collision detection is very precise. I use binary-search to do it. When two objects collide, time goes back until nothing collide so that the exact collide time and point will be detected. With this tecnique, the ball can go very fast while the bounce effect is still realistic.

Here is brcki.py. I know it's a bit too long to be a golf. But isn't that insteresting playing on your own image?

import pygame as pg
import itertools
import Image
from random import randint
from math import pi,sin,cos,atan2

norm = lambda c:c/abs(c)
times = lambda c,d:(c*d.conjugate()).real
cross = lambda c,d:(c*d.conjugate()).imag

class Poly:
    def __init__(self, ps, c, color=0):
        assert isinstance(c, complex)
        for p in ps:
            assert isinstance(p, complex)
        self.c = c
        self.ps = ps
        self.boundR = max(abs(p) for p in ps)
        self.ns = [norm(ps[i]-ps[i-1])*1j for i in range(len(ps))]
        self.color = color

class Ball(Poly):
    def __init__(self, r, c, v):
        n = int(1.5*r)
        d = 2*pi/n
        ps = [r * (cos(i*d)+sin(i*d)*1j) for i in range(n)]
        super().__init__(ps, c)
        self.v = v
        self.r = r
        self.color = ColorBall

class Player(Poly):
    def __init__(self, ps, c, color=0):
        super().__init__(ps, c, color)
        self.v = 0+0j

pg.display.init()
W, H = 600, 700
ColorBG = pg.Color(0xffffffff)
ColorBall = pg.Color(0x615ea6ff)
ColorBrick = pg.Color(0x555566ff)
FPS = 40
BallR, BallV = 15, 120+640j
PlayerV = 300

Bc, Bi, Bj = W/2+H*1j, 1+0j, -1j

def phy2scr(p):
    p = Bc + p.real * Bi + p.imag * Bj
    return round(p.real), round(p.imag)

def hittest(dt, b, plr, Ps)->'(dt, P) or None': 
    t0, t1, t2 = 0, dt, dt
    moveon(dt, b, plr)
    if not existhit(b, Ps): return None
    while t1 - t0 > 1e-2:
        t3 = (t0 + t1)/2
        moveon(t3 - t2, b, plr)
        if existhit(b, Ps): t1 = t3
        else: t0 = t3
        t2 = t3
    moveon(t1 - t2, b, plr)
    P = next(P for P in Ps if collide(b, P))
    moveon(t0 - t1, b, plr)
    assert not existhit(b, Ps)
    return (t1, P)

def existhit(b, Ps)->'bool':
    return any(collide(b, P) for P in Ps)

def inside(p, P)->'bool': 
    return all(times(p-q-P.c, n)>=0 for q,n in zip(P.ps,P.ns))

def collide(P1, P2)->'bool': 
    if abs(P1.c - P2.c) > P1.boundR + P2.boundR + 1:
        return False
    return any(inside(P1.c + p, P2) for p in P1.ps) \
            or any(inside(P2.c + p, P1) for p in P2.ps)

def moveon(dt, *ps): 
    for p in ps:
        p.c += p.v * dt

def hithandle(b, P):
    hp, n = hitwhere(b, P)
    b.v -= 2 * n * times(b.v, n)

def hitwhere(b, P)->'(hitpoint, norm)':
    c = P.c
    for p in P.ps:
        if abs(b.c - p - c) < b.r + 1e-1:
            return (p+c, norm(b.c - p - c))
    minD = 100
    for p, n in zip(P.ps, P.ns):
        d = abs(times(b.c - p - c, -n) - b.r)
        if d < minD:
            minD, minN = d, n
    n = minN
    return (b.c + n * b.r, -n)

def draw(sur, P):
    pg.draw.polygon(sur, P.color, [phy2scr(p + P.c) for p in P.ps])

def flood_fill(img, bgcolor):
    dat = img.load()
    w, h = img.size
    mark = set()
    blocks = []
    for x0 in range(w):
        for y0 in range(h):
            if (x0, y0) in mark: continue
            color = dat[x0, y0]
            if color == bgcolor: continue
            mark.add((x0, y0))
            stk = [(x0, y0)]
            block = []
            while stk:
                x, y = stk.pop()
                for p1 in ((x-1,y),(x,y-1),(x+1,y), (x,y+1)):
                    x1, y1 = p1
                    if x1 < 0 or x1 >= w or y1 < 0 or y1 >= h: continue
                    if dat[p1] == color and p1 not in mark:
                        mark.add(p1)
                        block.append(p1)
                        stk.append(p1)
            block1 = []
            vis = set(block)
            for x, y in block:
                neig = sum(1 for p1 in ((x-1,y),(x,y-1),(x+1,y), (x,y+1)) if p1 in vis)
                if neig < 4: block1.append((x, y))
            if len(block1) >= 4: blocks.append((dat[x0, y0], block1))
    return blocks

def place_ball(b, plr):
    c = plr.c
    hl, hr = 0+0j, 0+300j
    # assume:
    # when b.c = c + hl, the ball overlaps player
    # when b.c = c + hr, the ball do not overlaps player
    while abs(hr - hl) > 1:
        hm = (hl + hr) / 2
        b.c = c + hm
        if collide(b, plr): hl = hm
        else: hr = hm
    b.c = c + hr

def pixels2convex(pixels):
    """
    convert a list of pixels into a convex polygon using Gramham Scan.
    """
    c = pixels[0]
    for p in pixels:
        if c[1] > p[1]:
            c = p
    ts = [(atan2(p[1]-c[1], p[0]-c[0]), p) for p in pixels]
    ts.sort()
    stk = []
    for x, y in ts:
        while len(stk) >= 2:
            y2, y1 = complex(*stk[-1]), complex(*stk[-2])
            if cross(y1 - y2, complex(*y) - y1) > 0:
                break
            stk.pop()
        stk.append(y)
    if len(stk) < 3: return None
    stk.reverse()
    return stk

def img2data(path) -> "(ball, player, brckis, walls)":
    """
    Extract polygons from the image in path.
    The lowest(with largest y value in image) polygon will be
    the player. All polygon will be converted into convex.
    """
    ColorWall = (0, 0, 0)
    ColorBG = (255, 255, 255)
    print('Start parsing image...')
    img = Image.open(path)
    w, h = img.size

    blocks = flood_fill(img, ColorBG)
    brckis = []
    walls = []
    player = None
    def convert(x, y):
        return x * W / float(w) - W/2 + (H - y * H / float(h))*1j

    for color, block in blocks:
        conv = []
        conv = pixels2convex(block)
        if conv is None: continue
        conv = [convert(x, y) for x, y in conv]
        center = sum(conv) / len(conv)
        p = Poly([c-center for c in conv], center, color)
        if color == ColorWall:
            walls.append(p)
        else:
            brckis.append(p)
            if player is None or player.c.imag > center.imag:
                player = p
    ball = Ball(BallR, player.c, BallV)
    print('Parsed image:\n  {0} polygons,\n  {1} vertices.'.format(
        len(walls) + len(brckis),
        sum(len(P.ps) for P in itertools.chain(brckis, walls))))
    print('Ball: {0} vertices, radius={1}'.format(len(ball.ps), ball.r))
    brckis.remove(player)
    player = Player(player.ps, player.c, player.color)
    place_ball(ball, player)
    return ball, player, brckis, walls

def play(config): 
    scr = pg.display.set_mode((W, H), 0, 32)
    quit = False
    tm = pg.time.Clock()
    ball, player, brckis, walls = config
    polys = walls + brckis + [player]
    inputD = None
    while not quit:
        dt = 1. / FPS
        for e in pg.event.get():
            if e.type == pg.KEYDOWN:
                inputD = {pg.K_LEFT:-1, pg.K_RIGHT:1}.get(e.key, inputD)
            elif e.type == pg.KEYUP:
                inputD = 0
            elif e.type == pg.QUIT:
                quit = True
        if inputD is not None: 
            player.v = PlayerV * inputD + 0j

        while dt > 0:
            r = hittest(dt, ball, player, polys)
            if not r: break
            ddt, P = r
            dt -= ddt
            hithandle(ball, P)
            if P in brckis:
                polys.remove(P)
                brckis.remove(P)
        if ball.c.imag < 0: print('game over');quit = True
        if not brckis: print('you win');quit = True
        scr.fill(ColorBG)
        for p in itertools.chain(walls, brckis, [player, ball]):
            draw(scr, p)
        pg.display.flip()
        tm.tick(FPS)

if __name__ == '__main__':
    import sys
    play(img2data(sys.argv[1]))
share|improve this answer
add comment

Javascript/KineticJS

Here's a "working" version in 28 minutes, but it's not exactly playable (the collision logic is too slow). http://jsfiddle.net/ukva5/5/

(function (con, wid, hei, brk) {
    var stage = new Kinetic.Stage({
        container: con,
        width: wid,
        height: hei
    }),
        bricks = new Kinetic.Layer(),
        brks = brk.bricks,
        bX = wid / 2 - brk.width * brks[0].length / 2,
        bY = brk.height,
        mov = new Kinetic.Layer(),
        ball = new Kinetic.Circle({
            x: wid / 4,
            y: hei / 2,
            radius: 10,
            fill: 'black'
        }),
        paddle = new Kinetic.Rect({
            x:wid/2-30,
            y:hei*9/10,
            width:60,
            height:10,
            fill:'black'
        }),
        left = false,
        right = false;

    mov.add(ball);
    mov.add(paddle);
    stage.add(mov);

    paddle.velocity = 5;

    ball.angle = Math.PI/4;
    ball.velocity = 3;
    ball.bottom = function(){
        var x = ball.getX();
        var y = ball.getY();
        return {x:x, y:y+ball.getRadius()};
    };
    ball.top = function(){
        var x = ball.getX();
        var y = ball.getY();
        return {x:x, y:y-ball.getRadius()};
    };
    ball.left = function(){
        var x = ball.getX();
        var y = ball.getY();
        return {x:x-ball.getRadius(), y:y};
    };
    ball.right = function(){
        var x = ball.getX();
        var y = ball.getY();
        return {x:x+ball.getRadius(), y:y};
    };
    ball.update = function(){
        ball.setX(ball.getX() + Math.cos(ball.angle)*ball.velocity);
        ball.setY(ball.getY() + Math.sin(ball.angle)*ball.velocity);
    };
    paddle.update = function(){
        var x = paddle.getX();
        if (left) x-=paddle.velocity;
        if (right) x+= paddle.velocity;

        paddle.setX(x);
    };


    for (var i = 0; i < brks.length; i++) {
        for (var j = 0; j < brks[i].length; j++) {
            if (brks[i][j]) {
                bricks.add(new Kinetic.Rect({
                    x: bX + j * brk.width + .5,
                    y: bY + i * brk.height + .5,
                    width: brk.width,
                    height: brk.height,
                    stroke: 'black',
                    strokeWidth: 1,
                    fill: 'gold'
                }));
            }
        }
    }
    stage.add(bricks);

    $(window).keydown(function(e){
        switch(e.keyCode){
            case 37:
                left = true;
                break;
            case 39:
                right = true;
                break;
        }
    }).keyup(function(e){
        switch(e.keyCode){
            case 37:
                left = false;
                break;
            case 39:
                right = false;
                break;
        }
    });

    (function loop(){
        ball.update();
        paddle.update();

        if (paddle.intersects(ball.bottom())){
            ball.setY(paddle.getY()-ball.getRadius());
            ball.angle = -ball.angle;
        }
        if (ball.right().x > wid){
            ball.setX(wid - ball.getRadius());
            ball.angle = Math.PI - ball.angle;
        }
        if (ball.left().x < 0){
            ball.setX(ball.getRadius());
            ball.angle = Math.PI - ball.angle;
        }
        if (ball.top().y < 0){
            ball.setY(ball.getRadius());
            ball.angle = -ball.angle;
        }

        for(var i = bricks.children.length; i--;){
            var b = bricks.children[i];
            if (b.intersects(ball.top()) || b.intersects(ball.bottom())){
                ball.angle = -ball.angle;
                b.destroy();
            }
            else if (b.intersects(ball.left()) || b.intersects(ball.right())){
                ball.angle = Math.PI-ball.angle;
                b.destroy();
            }
        }

        stage.draw();

        webkitRequestAnimationFrame(loop);
    })()

})('b', 640, 480, {
    width: 80,
    height: 20,
    bricks: [
        [1, 1, 1, 1, 1, 1],
        [1, 1, 0, 0, 1, 1],
        [1, 1, 1, 1, 1, 1]
    ]
});

I'll work on making the game snappier now. :)

Note: Works only in webkit browsers right now. I'll add a shim so it will work in any HTML5 ready browser eventually.

Updates:

  1. http://jsfiddle.net/ukva5/6/ more playable
  2. http://jsfiddle.net/ukva5/7/ random bricks, faster ball
  3. http://jsfiddle.net/ukva5/8/ random brick health
  4. http://jsfiddle.net/ukva5/12/show/light/ stopping point for the day.
share|improve this answer
    
I feel sure this is a dumb question but... How would you run this in a browser without making an HTML page to put it in? –  felipa Feb 12 '13 at 18:55
    
@felipa You would need the HTML page. –  Shmiddty Feb 12 '13 at 20:16
    
28 minutes is amazing! I am not clear on the rules of golfing. Is javascript allowed without the associated HTML? –  felipa Feb 12 '13 at 21:50
    
@felipa 28 minutes was only possible because you said we could use libraries. :) I am using both jQuery and KineticJS, which simplify a great many things for me. –  Shmiddty Feb 12 '13 at 21:58
    
Also, I'm not sure if there is a definite answer on that. My gut says that HTML/JS answers would include any HTML inside of the body tag towards their character count, but nothing outside of it, or the body tag itself. IE, for my solution, I would simply include <div id='b'></div> or <div id='b'> if I were golfing it. –  Shmiddty Feb 12 '13 at 22:02
add comment

Processing, 400 characters

Didn't manage to do it in 30min, but here's a surf'd version. The ball is square and if you press any other key than left/right it jumps fully right, but it was shorter and the spec didn't say anything against it ;).

int x,y=999,u,v,p=300,q=580,i,l,t;long g;void setup(){size(720,600);}void draw(){background(0);if(keyPressed)p=min(max(p+8*(keyCode&2)-8,0),630);rect(p,q,90,20);for(i=0;i<64;i++)if((g>>i&1)<1){l=i%8*90;t=i/8*20+40;if(x+20>l&&x<l+90&&y+20>t&&y<t+20){v=-v;g|=1l<<i;}rect(l,t,90,20);}if(x+20>p&&x<p+90&&y+20>q)v=-abs(v);if(y<0)v=-v;if(x<0||x>700)u=-u;if(y>600){x=y=400;u=v=3;}x+=u;y+=v;rect(x,y,20,20);}

With proper names & some whitespace for clarity, that's:

int ballX, ballY=999, speedX, speedY, paddleX=300, paddleY=580;
long bricks;

void setup() {
  size(720, 600);
}

void draw() {
  background(0);
  if(keyPressed) paddleX = min(max(paddleX+8*(keyCode^37)-8,0),630);
  rect(paddleX, paddleY, 90, 20);
  for(int i=0; i<64; i++) {
    if((bricks>>i&1)<1) {
      int brickX=i%8*90, brickY=i/8*20+40;
      if(ballX+20>brickX && ballX<brickX+90 && ballY+20>brickY && ballY<brickY+20) {
        speedY = -speedY;
        bricks |= 1l<<i;
      }
      rect(brickX, brickY, 90, 20);
    }
  }
  if(ballX+20>paddleX && ballX<paddleX+90 && ballY+20>paddleY) speedY = -abs(speedY);
  if(ballY<0) speedY = -speedY;
  if(ballX<0 || ballX>700) speedX = -speedX;
  if(ballY>600) {
    ballX = ballY = 400;
    speedX = speedY = 3;
  }
  ballX += speedX; ballY += speedY;
  rect(ballX, ballY, 20, 20);
}
share|improve this answer
    
Thanks! That is very nice and short too. –  felipa Feb 17 '13 at 13:15
add comment

Elm, 1 hour 44 minutes

I tried to do it in half an hour, but overshot a little. To try to save time, I used circles for everything (paddle, ball, bricks). This avoided the many cases needed to implement circle/rectangle collision, but required some vector math to compute the resulting direction.

To run, go to http://elm-lang.org/edit/, paste the code into the editor, and click the "In Tab" button at the bottom.

import Keyboard

brickR = 30
ballR = 10
paddleY = 450
paddleR = 50

-- Initial (center) position of each brick
brickLayout = [(150,50), (250, 50), (350, 50), (450, 50),
            (100,100), (200,100), (300,100), (400,100), (500,100)]

brickForm = filled blue . circle brickR

-- Return True if the brick coordinate collides with the ball coordinate
brickVsBall brickPos ballPos =
    distance brickPos ballPos <= brickR + ballR

addVec (ax,ay) (bx,by) = (ax+bx, ay+by)
subVec (ax,ay) (bx,by) = (ax-bx, ay-by)
dot (ax,ay) (bx,by) = ax*bx + ay*by
magsq (x,y) = x*x + y*y
scaleVec s (x,y) = (x*s, y*s)
proj n v = scaleVec (dot v n / magsq n) n
flipV n v = v `subVec` scaleVec 2 (proj n v)
distance a b = sqrt $ magsq $ a `subVec` b

-- Only flip the velocity if headed "toward" the target, where n is a vector
-- pointing from the ball to the target.
bounceV n v =
    if v `dot` n > 0
       then v `subVec` scaleVec 2 (proj n v)
       else v

checkBricks ballPos ballV bricks =
    let (smashed, kept) = updateBricks ballPos [] [] bricks
     in (updateBallV ballPos ballV smashed, kept)

updateBricks ballPos smashed kept bricks = case bricks of
    [] -> (reverse smashed, reverse kept)
    (x:xs) ->
        if brickVsBall x ballPos
           then updateBricks ballPos (x:smashed) kept xs
           else updateBricks ballPos smashed (x:kept) xs

updateBallV ballPos ballV smashed = case smashed of
    []     -> ballV
    (x:xs) -> updateBallV ballPos (bounceV (x `subVec` ballPos) ballV) xs

checkPaddle paddleX ballPos ballV =
    let paddlePos = (paddleX,paddleY) in
    if distance ballPos paddlePos <= ballR + paddleR
       then bounceV (paddlePos `subVec` ballPos) ballV
       else ballV

checkWalls r (x,y) =
    let checkLeft  (vx,vy) = if x-r <= 0   && vx<0 then (0-vx,vy) else (vx,vy)
        checkRight (vx,vy) = if x+r >= 600 && vx>0 then (0-vx,vy) else (vx,vy)
        checkTop   (vx,vy) = if y-r <= 0   && vy<0 then (vx,0-vy) else (vx,vy)
     in checkLeft . checkRight . checkTop

-- type Scene = ([BrickPos], BallPos, BallV, PaddleX)
-- BallV is in pixels per frame

initScene = (brickLayout, (300,385), (0, 0-3), 300)

renderScene (bricks, ballPos, ballV, paddleX) =
    collage 600 500 $
    map brickForm bricks ++
    [ filled red $ circle ballR ballPos
    , filled black $ circle paddleR (paddleX,paddleY)
    ]

updateScene (bricks, ballPos, ballV, paddleX) arrows =
    let paddleX' = paddleX + arrows.x * 4
        (ballV1, bricks') = checkBricks ballPos ballV bricks
        ballV2 = checkPaddle paddleX' ballPos ballV1
        ballV' = checkWalls ballR ballPos ballV2
     in (bricks', ballPos `addVec` ballV', ballV', paddleX')

main =
    lift renderScene $
    foldp (\(t, arrows) scene -> updateScene scene arrows) initScene $
    lift2 (,) (every (second/60)) arrows
share|improve this answer
    
That is very cool. Two small problems. One is that I think the ball bouncing logic is slightly broken. If the ball comes from above a "brick" it seems to go straight through it rather than bounce off. Second, it's not golfed at all :) –  felipa Feb 10 '13 at 19:24
1  
Also, in case anyone else is struggling with circle rectangle collsion, here is a very nice and simple way to do it stackoverflow.com/questions/401847/… . Of course it could be fun to golf that too :) –  felipa Feb 10 '13 at 19:36
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.