Code viewer for World: Pyscript Snake

// # Clone by Brendan McGrath of:
// # "Hello Pyscript" by Brendan McGrath
// # https://ancientbrain.com/world.php?world=5833407984
// # Please leave this clone trail here.



// # Clone by Brendan McGrath of:
// # "Python demo (customisable)" by Starter user
// # https://ancientbrain.com/world.php?world=4219533706
// # Please leave this clone trail here.


// The rule for how this API handles Python 'print' output: 
// If we have an element with id="ab-python-console" then 'print' appends to it.
// If such an element does not exist, 'print' writes to the console.

// choose one of:
// <script type="py"> 
// <script type="mpy"> 



document.write(`
  <style>
    #fancy-output {
      font-family: "Fira Mono", "Consolas", monospace;
      color: #2b90d9;
      background: #222;
      padding: 2em;
      border-radius: 8px;
      font-size: 1.2em;
      margin: 5em auto;
      width: 350px;
      box-shadow: 0 2px 8px #0006;

      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
    }

    #snake-score {
      margin: 0.5em 0;
    }

    #snake-container {
      border: 2px solid #444;
      border-radius: 6px;
      background: #111;
    }
  </style>

  <div id="fancy-output">
    <h3>Snake Game</h3>
    <p class="help">
      Use arrow keys to control the snake. Press Space to restart after game over.
    </p>
    <div id="snake-score">Score: 0</div>
    <div id="snake-container">
      <canvas id="snake-canvas" width="280" height="280"></canvas>
    </div>
  </div>

  <script type="py">
    from js import document, window, console
    import random
    from pyodide.ffi import create_proxy

    # Snake constants
    SNAKE_CELL_SIZE = 20
    SNAKE_WIDTH = 14
    SNAKE_HEIGHT = 14

    snake_canvas_element = document.querySelector("#snake-canvas")
    snake_ctx = snake_canvas_element.getContext("2d")
    snake_score_element = document.querySelector("#snake-score")

    snake_body = [(7,7),(6,7),(5,7)]
    snake_direction = (1,0)
    snake_food = (10,10)
    snake_score = 0
    snake_game_over = False
    snake_last_time = 0
    snake_speed = 200

    def draw_snake_cell(x,y,color):
        snake_ctx.fillStyle = color
        snake_ctx.fillRect(x*SNAKE_CELL_SIZE, y*SNAKE_CELL_SIZE, SNAKE_CELL_SIZE-2, SNAKE_CELL_SIZE-2)

    def draw_snake_game():
        snake_ctx.clearRect(0,0,snake_canvas_element.width,snake_canvas_element.height)
        draw_snake_cell(snake_food[0], snake_food[1], "#e57373")
        for i,(x,y) in enumerate(snake_body):
            color = "#ffd54f" if i==0 else "#81c784"
            draw_snake_cell(x,y,color)

    def place_snake_food():
        while True:
            fx=random.randint(0,SNAKE_WIDTH-1)
            fy=random.randint(0,SNAKE_HEIGHT-1)
            if (fx,fy) not in snake_body:
                return (fx,fy)

    def update_snake_score():
        snake_score_element.innerHTML = f"Score: {snake_score}"

    def update_snake_game():
        global snake_body,snake_food,snake_score,snake_game_over
        if snake_game_over:
            return
        head_x,head_y = snake_body[0]
        dx,dy = snake_direction
        new_head = ((head_x+dx)%SNAKE_WIDTH,(head_y+dy)%SNAKE_HEIGHT)
        if new_head in snake_body:
            snake_game_over=True
            snake_ctx.fillStyle="#fff"
            snake_ctx.font="20px monospace"
            snake_ctx.fillText("Game Over!",80,130)
            snake_ctx.font="16px monospace"
            snake_ctx.fillText("Press Space to restart",60,160)
            return
        snake_body.insert(0,new_head)
        if new_head==snake_food:
            snake_score+=1
            update_snake_score()
            snake_food=place_snake_food()
        else:
            snake_body.pop()
        draw_snake_game()

    def restart_snake_game():
        global snake_body,snake_direction,snake_food,snake_score,snake_game_over,snake_last_time
        snake_body=[(7,7),(6,7),(5,7)]
        snake_direction=(1,0)
        snake_food=place_snake_food()
        snake_score=0
        snake_game_over=False
        snake_last_time=0
        update_snake_score()
        draw_snake_game()
        window.requestAnimationFrame(snake_loop_proxy)

    def snake_game_loop(current_time):
        global snake_last_time
        if current_time - snake_last_time >= snake_speed:
            update_snake_game()
            snake_last_time=current_time
        if not snake_game_over:
            window.requestAnimationFrame(snake_loop_proxy)

    def snake_keydown_handler(event):
        global snake_direction
        key=event.key
        if snake_game_over and key==" ":
            restart_snake_game()
            return
        if snake_game_over:
            return
        dx,dy=snake_direction
        if key=="ArrowUp" and dy!=1:
            snake_direction=(0,-1)
        elif key=="ArrowDown" and dy!=-1:
            snake_direction=(0,1)
        elif key=="ArrowLeft" and dx!=1:
            snake_direction=(-1,0)
        elif key=="ArrowRight" and dx!=-1:
            snake_direction=(1,0)

    snake_loop_proxy=create_proxy(snake_game_loop)
    snake_keydown_proxy=create_proxy(snake_keydown_handler)
    window.addEventListener("keydown",snake_keydown_proxy)
    update_snake_score()
    draw_snake_game()
    window.requestAnimationFrame(snake_loop_proxy)
    console.log("Snake game started")
  </script>
`);