breakout.py

Created by schraf

Created on March 29, 2026

7.92 KB


from kandinsky import *
from ion import *
from time import *
import random
W, H = 320, 222
BLACK   = (0,   0,   0)
WHITE   = (255, 255, 255)
GRAY = (120, 120, 120)
DGRAY   = (50,  50,  50)
CYAN = (0,   220, 220)
YELLOW  = (255, 230,  0)
ORANGE  = (255, 140,  0)
RED  = (220,  30,  30)
GREEN   = (30,  200,  60)
BLUE = (30,  100, 220)
PURPLE  = (160,  40, 210)
PINK = (255,  80, 160)
LBLUE   = (80,  180, 255)
PAD_Y   = H - 18
PAD_H   = 6
PAD_W_DEF  = 52
PAD_W_WIDE = 80
BALL_S  = 6
BALL_SPD   = 4
BRICK_W = 28
BRICK_H = 10
BRICK_COLS = 10
BRICK_ROWS = 5
BRICK_PADX = 5
BRICK_PADY = 30
BONUS_W = 10
BONUS_H = 10
SCORE_ZONE = 20
ROW_COLORS = [RED, ORANGE, YELLOW, GREEN, CYAN]
ROW_PTS = [5,   4,   3,   2,  1]
def rect(x, y, w, h, c):
 fill_rect(int(x), int(y), int(w), int(h), c)
def draw_text_center(txt, y, color, bg=BLACK):
 x = (W - len(txt) * 10) // 2
 draw_string(txt, x, y, color, bg)
def make_bricks(level):
 bricks = []
 total_w = BRICK_COLS * BRICK_W + (BRICK_COLS - 1) * 2
 start_x = (W - total_w) // 2
 rows = BRICK_ROWS if level == 1 else BRICK_ROWS + 1
 for r in range(rows):
  ci = min(r, len(ROW_COLORS) - 1)
  for c in range(BRICK_COLS):
   bx = start_x + c * (BRICK_W + 2)
   by = BRICK_PADY + r * (BRICK_H + 3)
   bricks.append({
    'x': bx, 'y': by,
    'alive': True,
    'color': ROW_COLORS[ci],
    'pts': ROW_PTS[ci]
   })
 return bricks
def draw_score_bar(score, lives, level):
 rect(0, 0, W, SCORE_ZONE, DGRAY)
 draw_string("Niv:" + str(level), 4, 4, YELLOW, DGRAY)
 sc = "Score:" + str(score)
 draw_string(sc, (W - len(sc)*10)//2, 4, WHITE, DGRAY)
 draw_string("Vies:" + str(lives), W - 70, 4, CYAN, DGRAY)
def draw_bricks(bricks):
 for b in bricks:
  if b['alive']:
   rect(b['x'], b['y'], BRICK_W, BRICK_H, b['color'])
   rect(b['x']+1, b['y']+1, BRICK_W-2, 2, WHITE)
def draw_paddle(px, pw):
 rect(px, PAD_Y, pw, PAD_H, LBLUE)
 rect(px+2, PAD_Y+1, pw-4, 2, WHITE)
def draw_ball(bx, by):
 rect(bx, by, BALL_S, BALL_S, WHITE)
 rect(bx+1, by+1, 2, 2, GRAY)
def draw_balls(balls):
 for b in balls:
  draw_ball(b['x'], b['y'])
def draw_bonus(bonuses):
 for bo in bonuses:
  if bo['type'] == 'wide':
   c = GREEN;  t = "W"
  else:
   c = PINK;   t = "+"
  rect(bo['x'], bo['y'], BONUS_W, BONUS_H, c)
def erase_ball(bx, by):
 rect(bx, by, BALL_S, BALL_S, BLACK)
def erase_paddle(px, pw):
 rect(px, PAD_Y, pw, PAD_H, BLACK)
def erase_bonus(bo):
 rect(bo['x'], bo['y'], BONUS_W, BONUS_H, BLACK)
def screen_clear():
 rect(0, 0, W, H, BLACK)
def wait_key(k):
 while keydown(k): pass
 while not keydown(k): pass
def show_start(level):
 screen_clear()
 draw_text_center("=  BREAKOUT  =", 40, YELLOW)
 draw_text_center("Niveau " + str(level), 70, CYAN)
 draw_text_center("Gauche / Droite", 100, WHITE)
 draw_text_center("pour bouger", 116, WHITE)
 if level == 1:
  draw_text_center("OK pour demarrer", 150, GREEN)
 else:
  draw_text_center("Niveau 2 : plus vite !", 140, ORANGE)
  draw_text_center("OK pour continuer", 160, GREEN)
 wait_key(KEY_OK)
def show_game_over(score):
 screen_clear()
 draw_text_center("GAME  OVER", 70, RED)
 draw_text_center("Score final : " + str(score), 100, YELLOW)
 draw_text_center("OK pour rejouer", 140, WHITE)
 wait_key(KEY_OK)
def show_win(score):
 screen_clear()
 draw_text_center("BRAVO  !", 60, YELLOW)
 draw_text_center("Tous les niveaux", 90, GREEN)
 draw_text_center("sont termines !", 106, GREEN)
 draw_text_center("Score : " + str(score), 136, CYAN)
 draw_text_center("OK pour rejouer", 166, WHITE)
 wait_key(KEY_OK)
def clamp(v, lo, hi):
 return max(lo, min(hi, v))
def ball_brick_collide(ball, brick):
 bx, by = ball['x'], ball['y']
 rx, ry = brick['x'], brick['y']
 return (bx < rx + BRICK_W and bx + BALL_S > rx and
   by < ry + BRICK_H and by + BALL_S > ry)
def resolve_ball_brick(ball, brick):
 bx, by   = ball['x'], ball['y']
 pvx, pvy = ball['vx'], ball['vy']
 rx, ry   = brick['x'], brick['y']
 bcx = bx + BALL_S/2;  bcy = by + BALL_S/2
 rcx = rx + BRICK_W/2; rcy = ry + BRICK_H/2
 dx = bcx - rcx;  dy = bcy - rcy
 ox = (BALL_S + BRICK_W)/2 - abs(dx)
 oy = (BALL_S + BRICK_H)/2 - abs(dy)
 if ox < oy:
  ball['vx'] = -pvx
 else:
  ball['vy'] = -pvy
def update_ball(ball, pad_x, pad_w, bricks, bonuses, score):
 erase_ball(ball['x'], ball['y'])
 ball['x'] += ball['vx']
 ball['y'] += ball['vy']
 if ball['x'] <= 0:
  ball['x'] = 0;    ball['vx'] = abs(ball['vx'])
 if ball['x'] + BALL_S >= W:
  ball['x'] = W - BALL_S; ball['vx'] = -abs(ball['vx'])
 if ball['y'] <= SCORE_ZONE:
  ball['y'] = SCORE_ZONE; ball['vy'] = abs(ball['vy'])
 if (ball['vy'] > 0 and
  ball['y'] + BALL_S >= PAD_Y and
  ball['y'] + BALL_S <= PAD_Y + PAD_H + abs(ball['vy']) and
  ball['x'] + BALL_S > pad_x and
  ball['x'] < pad_x + pad_w):
  ball['vy'] = -abs(ball['vy'])
  rel = (ball['x'] + BALL_S/2 - (pad_x + pad_w/2)) / (pad_w/2)
  ball['vx'] = rel * BALL_SPD * 1.5
 for b in bricks:
  if b['alive'] and ball_brick_collide(ball, b):
   resolve_ball_brick(ball, b)
   b['alive'] = False
   score += b['pts']
   rect(b['x'], b['y'], BRICK_W, BRICK_H, BLACK)
   if int(random.randint(0, 3)) == 0:
    btype = 'wide' if int(random.randint(0, 1)) == 0 else 'multi'
    bonuses.append({
     'x': b['x'] + BRICK_W//2 - BONUS_W//2,
     'y': b['y'],
     'vy': 1.5,
     'type': btype
    })
   break
 return score
def play_level(level, score_in, lives_in):
 speed  = BALL_SPD if level == 1 else BALL_SPD + 1
 pad_w  = PAD_W_DEF
 pad_x  = (W - pad_w) // 2
 wide_timer = 0
 balls = [{
  'x': float(W//2 - BALL_S//2),
  'y': float(PAD_Y - BALL_S - 2),
  'vx': float(speed * 0.7),
  'vy': float(-speed)
 }]
 bricks  = make_bricks(level)
 bonuses = []
 score   = score_in
 lives   = lives_in
 screen_clear()
 draw_score_bar(score, lives, level)
 draw_bricks(bricks)
 draw_paddle(pad_x, pad_w)
 draw_balls(balls)
 while True:
  t0 = monotonic()
  old_px = pad_x
  if keydown(KEY_LEFT):
   pad_x = max(0, pad_x - 5)
  if keydown(KEY_RIGHT):
   pad_x = min(W - pad_w, pad_x + 5)
  if old_px != pad_x:
   erase_paddle(old_px, pad_w)
  if wide_timer > 0:
   wide_timer -= 1
   if wide_timer == 0:
    erase_paddle(pad_x, pad_w)
    pad_w = PAD_W_DEF
    pad_x = clamp(pad_x, 0, W - pad_w)
  dead_balls = []
  for i, ball in enumerate(balls):
   score = update_ball(ball, pad_x, pad_w, bricks, bonuses, score)
   if ball['y'] > H:
    dead_balls.append(i)
  for i in reversed(dead_balls):
   balls.pop(i)
  if len(balls) == 0:
   lives -= 1
   draw_score_bar(score, lives, level)
   if lives <= 0:
    return 'gameover', score, lives
   sleep(0.6)
   erase_paddle(pad_x, pad_w)
   pad_w  = PAD_W_DEF
   pad_x  = (W - pad_w) // 2
   wide_timer = 0
   balls = [{
    'x': float(W//2 - BALL_S//2),
    'y': float(PAD_Y - BALL_S - 2),
    'vx': float(speed * 0.7),
    'vy': float(-speed)
   }]
   draw_bricks(bricks)
  dead_bonus = []
  for i, bo in enumerate(bonuses):
   erase_bonus(bo)
   bo['y'] += bo['vy']
   if (bo['y'] + BONUS_H >= PAD_Y and
    bo['y'] <= PAD_Y + PAD_H and
    bo['x'] + BONUS_W > pad_x and
    bo['x'] < pad_x + pad_w):
    dead_bonus.append(i)
    if bo['type'] == 'wide':
     erase_paddle(pad_x, pad_w)
     pad_w = PAD_W_WIDE
     pad_x = clamp(pad_x, 0, W - pad_w)
     wide_timer = 300
    else:
     if len(balls) < 4 and len(balls) > 0:
      ref = balls[0]
      balls.append({
       'x': ref['x'], 'y': ref['y'],
       'vx': -ref['vx'], 'vy': ref['vy']
      })
   elif bo['y'] > H:
    dead_bonus.append(i)
  for i in reversed(dead_bonus):
   bonuses.pop(i)
  draw_bonus(bonuses)
  draw_paddle(pad_x, pad_w)
  draw_balls(balls)
  draw_score_bar(score, lives, level)
  if all(not b['alive'] for b in bricks):
   return 'win', score, lives
  elapsed = monotonic() - t0
  if elapsed < 0.033:
   sleep(0.033 - elapsed)
def main():
 while True:
  score = 0
  lives = 3
  show_start(1)
  screen_clear()
  result, score, lives = play_level(1, score, lives)
  if result == 'gameover':
   show_game_over(score)
   continue
  score += 100
  lives  = min(lives + 1, 5)
  show_start(2)
  screen_clear()
  result, score, lives = play_level(2, score, lives)
  if result == 'gameover':
   show_game_over(score)
  else:
   show_win(score)
main()

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.