8-bit style bouncing ball around a canvas

Inspired by this listing from the Commodore 64 User's Guide:

10 PRINT "{CLR/HOME}"
20 POKE 53280,7 : POKE 53281,13
30 X = 1 : Y = 1
40 DX = 1 : DY = 1
50 POKE 1024 + X + 40 * Y, 81
60 FOR T = 1 TO 10 : NEXT
70 POKE 1024 + X + 40 * Y, 32
80 X = X + DX
90 IF X <= 0 OR X >= 39 THEN DX = -DX
100 Y = Y + DY
110 IF Y <= 0 OR Y >= 24 THEN DY = -DY
120 GOTO 50


Make a similar program in your chosen language/platform to bounce a ball-alike object around your terminal, screen, canvas or other visual display area.

You don't have to mimic the C64's PETSCII graphics exactly, a simple O or o will do, nor do you have to use the GOTO command if it exists in your language still. As long as your ball starts at the top of your canvas and travels diagonally until it hits a canvas limit, and then bounces accordingly, as follows:

• Travelling downwards and right and hits the bottom of the screen area, bounces up and continues right;
• Travelling up and right and hits the right-most boundary, and bounces left and up;
• Travelling left and up and hits the top, bounces left and down;
• Travelling left and down and reaches the left-most boundary, bounces right and down;
• Hits any corner and reverses direction;

Then we're all good.

You don't have to move the ball 8-pixels at a time either, like is happening in the BASIC listing on the C64; you can move one character block or one pixel at a time, whichever you think is most appropriate.

To see this BASIC listing working, you can type it in with this online Commodore 64 emulator providing your browser supports Flash.

• JavaScript canvas. D'uh. – Matthew Roh Feb 17 '17 at 14:05
• I'm not sure what you call a screen nowadays. You used to have just the screen and border area viewed through your television set or VDU... and now you've got terminals, windows, canvases, stdout etc... it's all very confusing to me. – Shaun Bebbers Feb 17 '17 at 14:09
• It'd be better if we had a constant pixel size value. – Matthew Roh Feb 17 '17 at 14:11
• Can we assume the screen size of 1x1 and print o forever? – Matthew Roh Feb 18 '17 at 5:27
• possible duplicate of ASCII Ball in Box Animation – Titus Mar 2 '17 at 14:07

6502 machine code (C64), 9089 91 bytes

+2 bytes because it needs a load address (not PIC because of self modification)

00 C0 20 44 E5 CA D0 FD C6 FC D0 F9 A2 20 86 FC A9 D8 85 9E A9 03 85 9F A4 CA
18 A5 9E 69 28 85 9E 90 02 E6 9F 88 10 F2 A4 C9 8A 91 9E C9 20 D0 08 E6 C9 E6
CA A2 51 10 D7 A5 C9 F0 04 C9 27 D0 08 AD 2F C0 49 20 8D 2F C0 A5 CA F0 04 C9
18 D0 B4 AD 31 C0 49 20 8D 31 C0 D0 AA


Online demo

Usage: sys49152

I tried hard to reduce size (e.g. NOT using IRQs for timing but stupid empty loops instead), still impossible to reach the level of Titus' golfed C64 BASIC :o oh, well. But it looks less flickering ;)

Explanation: (vice disassembly)

00 C0       .WORD $C000 ; load address 20 44 E5 JSR$E544           ; clear screen
CA          DEX
D0 FD       BNE $C003 ; inner wait (256 decrements) C6 FC DEC$FC
D0 F9       BNE $C003 ; outer wait (32 decrements in zeropage) A2 20 LDX #$20            ; wait counter and screen code for "space"
86 FC       STX $FC ; store wait counter A9 D8 LDA #$D8            ; load screen base address ...
85 9E       STA $9E ; ... -40 (quasi row "-1") ... A9 03 LDA #$03            ; ... into vector at $9e/$9f
85 9F       STA $9F A4 CA LDY$CA             ; load current row in Y
18          CLC                 ; clear carry flag
A5 9E       LDA $9E ; add ... 69 28 ADC #$28            ; ... $28 (40 cols) to ... 85 9E STA$9E             ; ... vector
90 02       BCC $C023 E6 9F INC$9F             ; handle carry
88          DEY                 ; count rows down
10 F2       BPL $C018 A4 C9 LDY$C9             ; load current col in Y
8A          TXA                 ; copy screen code from X to A
91 9E       STA ($9E),Y ; store at position of screen C9 20 CMP #$20            ; screen code was "space"
D0 08       BNE $C037 ; if not, ball was drawn E6 C9 INC$C9             ; next column   | opcodes are modified
E6 CA       INC $CA ; next row | here for directions A2 51 LDX #$51            ; screen code for "ball"
10 D7       BPL $C00E ; and back to drawing code A5 C9 LDA$C9             ; load current column
F0 04       BEQ $C03F ; if zero, change X direction C9 27 CMP #$27            ; compare with last column (39)
D0 08       BNE $C047 ; if not equal, don't change X direction AD 2F C0 LDA$C02F           ; load opcode for X direction
49 20       EOR #$20 ; toggle between ZP INC and DEC 8D 2F C0 STA$C02F           ; store back
A5 CA       LDA $CA ; load current row F0 04 BEQ$C04F           ; if zero, change Y direction
C9 18       CMP #$18 ; compare with last row (24) D0 B4 BNE$C003           ; if not equal, don't change Y direction
AD 31 C0    LDA $C031 ; load opcode for Y direction 49 20 EOR #$20            ; toggle between ZP INC and DEC
8D 31 C0    STA $C031 ; store back D0 AA BNE$C003           ; -> main loop


Just for fun, here's a more professional variant using a sprite for the ball and flashing the border when hit in 385 bytes (containing the sprite data that's used in place):

00 C0 AD 15 D0 F0 30 A9 CC 85 FC A9 04 20 A2 C0 A9 97 8D 00 DD A9 15 8D 18 D0
A9 00 8D 15 D0 8D 1A D0 A2 81 8E 0D DC A2 31 8E 14 03 A2 EA 8E 15 03 58 A6 D6
4C F0 E9 A9 04 85 FC A9 CC 20 A2 C0 A2 31 86 01 A2 10 A9 D0 85 FC B1 FB C6 01
91 FB E6 01 C8 D0 F5 E6 FC CA D0 F0 A9 37 85 01 A9 94 8D 00 DD A9 35 8D 18 D0
8D 27 D0 A2 05 8E F8 CF A2 01 8E 15 D0 8E 1A D0 8E 12 D0 86 FD 86 FE A2 18 8E
00 D0 A2 1B 8E 11 D0 A2 32 8E 01 D0 A2 7F 8E 0D DC AE 0D DC AE 20 D0 86 FB A2
C1 8E 14 03 A2 C0 D0 8A 85 FE 8D 88 02 A9 00 85 FB 85 FD A2 04 A0 00 78 B1 FB
91 FD C8 D0 F9 E6 FC E6 FE CA D0 F2 60 A6 FB 8E 20 D0 CE 19 D0 A5 FD F0 20 AD
00 D0 18 69 04 8D 00 D0 90 03 EE 10 D0 C9 40 D0 2C AD 10 D0 29 01 F0 25 20 38
C1 C6 FD F0 1E AD 00 D0 38 E9 04 8D 00 D0 B0 03 CE 10 D0 C9 18 D0 0C AD 10 D0
29 01 D0 05 20 38 C1 E6 FD A5 FE F0 14 AD 01 D0 18 69 04 8D 01 D0 C9 E6 D0 19
20 38 C1 C6 FE F0 12 AD 01 D0 38 E9 04 8D 01 D0 C9 32 D0 05 20 38 C1 E6 FE 4C
31 EA A9 01 8D 20 D0 60 00 00 00 7E 00 03 FF C0 07 FF E0 1F FF F8 1F FF F8 3F
FF FC 7F FF FE 7F FF FE FF FF FF FF FF FF FF FF FF FF FF FF 7F FF FE 7F FF FE
3F FF FC 1F FF F8 1F FF F8 07 FF E0 03 FF C0 00 7E 00 00 00 00


Start and stop the bouncing ball with sys49152.

• This leaves the C64 BASIC running, that's done by moving the VIC-II address space up to $C000, which requires copying the screen contents and the character set (font). • It hooks into the system IRQ and to avoid flicker, changes the source of this IRQ to the VIC-II graphics chip, so updates are always done between frames. • Glitches: 1. RUN/STOP + RESTORE is broken, don't try. 2. With the VIC-II as IRQ source, the cursor blinks slightly slower and TI$ will lag behind as well.
3. when stopping while the border is flashed (very unlikely but possible), it stays white -- you have to restore it manually.
• It´s not totally independent, is it? I see two absolute LDAs and two STAs. Great work nonetheless! – Titus Sep 4 '17 at 21:54
• Damn you're right :o I forgot the self modification! I'll update as soon as I'm on the PC. – Felix Palmen Sep 5 '17 at 5:16
• @Titus fixed ... and just for fun, added a "better" variant :) – Felix Palmen Sep 5 '17 at 10:22
• Have you considered packing the sprite? (Hmm ... use the charset ROM?) And I´d prefer inc $d020 over jsr flash ;) hitshimselfwithalargetrout It´s marvellous! – Titus Sep 5 '17 at 22:26 • @Titus would save 2 bytes, yes. As for the load address, it's part of a valid .prg file and from my meta question here I take I have to include it ... could probably leave it out if the code was position independent. – Felix Palmen Sep 7 '17 at 16:15 Bash + Unix utilities, 125 117 bytes for((x=y=u=v=1;;x+=u,y+=v,u=(x<1||x>=tput cols-1)?-u:u,v=(y<1||y>=tput lines-1)?-v:v)){ tput cup$y $x sleep .1 }  Animation of sample run: • It hit the exact corner! :O – mbomb007 Feb 21 '17 at 16:12 CP-1610 assembly, 67 … 64 62 DECLEs = 78 bytes This code is intended to be run on an Intellivision. It's using one of its hardware sprites, known as a MOB (for Mobile Object). A CP-1610 opcode is encoded with a 10-bit value, known as a 'DECLE'. This program is 62 DECLEs long, starting at$4800 and ending at $483D. Hexadecimal dump + source  ROMW 10 ; use 10-bit ROM ORG$4800         ; start program at address $4800 FRAME EQU$17E          ; frame #

;; ------------------------------------------------ ;;
;;  main entry point                                ;;
;; ------------------------------------------------ ;;
main    PROC

4800 0001                   SDBD                ; load Interrupt Service Routine
4801 02B8 002B 0048         MVII  #isr,   R0    ; into R0

4804 0240 0100              MVO   R0,     $100 ; update ISR 4806 0040 SWAP R0 4807 0240 0101 MVO R0,$101

4809 02B9 0208              MVII  #$0208, R1 ; initialize R1 = X 480B 02BA 0108 MVII #$0108, R2    ; initialize R2 = Y
480D 02BB 0001              MVII  #1,     R3    ; initialize R3 = DX
480F 009C                   MOVR  R3,     R4    ; initialize R4 = DY

4810 0002                   EIS                 ; enable interrupts

;; ------------------------------------------------ ;;
;;  main loop                                       ;;
;; ------------------------------------------------ ;;
4811 0280 017E      @@loop  MVI   FRAME,  R0    ; R0 = current frame #

4813 0340 017E      @@spin  CMP   FRAME,  R0    ; wait for next frame
4815 0224 0003              BEQ   @@spin

4817 00D9                   ADDR  R3,     R1    ; X += DX

4818 0379 02A0              CMPI  #$2A0, R1 ; reached right border? 481A 0204 0003 BEQ @@updDx 481C 0379 0208 CMPI #$208,  R1    ; reached left border?

481F 0023           @@updDx NEGR  R3            ; DX = -DX

4820 00E2                   ADDR  R4,     R2    ; Y += DY

4821 037A 0160              CMPI  #$160, R2 ; reached bottom border? 4823 0204 0003 BEQ @@updDy 4825 037A 0108 CMPI #$108,  R2    ; reached top border?

4828 0024           @@updDy NEGR  R4            ; DY = -DY

4829 0220 0019              B     @@loop        ; loop forever

ENDP

;; ------------------------------------------------ ;;
;;  ISR                                             ;;
;; ------------------------------------------------ ;;
isr     PROC

482B 01DB                   CLRR  R3            ; clear a bunch of STIC registers
482C 02BC 0020              MVII  #$20, R4 482E 0263 @@clear MVO@ R3, R4 ; (including background color, 482F 037C 0032 CMPI #$32,   R4    ; border color, etc.)
4831 0226 0004              BLE   @@clear

4833 0259                   MVO@  R1,     R3    ; update X register of MOB #0
4834 0242 0008              MVO   R2,     $8 ; update Y register of MOB #0 4836 02BB 017E MVII #$017E, R3    ; update A register of MOB #0

Python 2, 176 168 Bytes

This assumes a terminal size of 80x24. Definitely not optimal but I'm new to golfing so yeah.

import time;x=y=d=e=1
while 1:
m=[[' 'for i in' '*80]for j in' '*24];x+=d;y+=e;m[y][x]='O';time.sleep(.1)
if x%79<1:d=-d
if y%23<1:e=-e
for r in m:print''.join(r)


Thanks to R. Kap for suggesting the x%79<1 instead of x<1or x>79 and ditto for y.

• You can save a few bytes by replacing x<1or x>78 with x%79<0 and y<1or y>22 with y%23<1. – R. Kap Feb 20 '17 at 3:26

Rebol/View, 284 266 bytes

rebol[]p: 3x9 d:[3 3]view layout[b: box blue 99x99 effect[draw[circle p 2]]rate :0.01 feel[engage: func[f a e][if a = 'time[case/all[p/x < 2[d/1: abs d/1]p/y < 2[d/2: abs d/2]p/x > 98[d/1: negate d/1]p/y > 98[d/2: negate d/2]]p/x: p/x + d/1 p/y: p/y + d/2 show b]]]]


Ungolfed:

rebol []

p: 3x9     ;; starting position
d: [3 3]   ;; direction

view layout [
b: box blue 99x99 effect [
draw [
circle p 2
]
]

rate :0.01 feel [
engage: func [f a e] [
if a = 'time [
case/all [
p/x < 2  [d/1: abs d/1]
p/y < 2  [d/2: abs d/2]
p/x > 98 [d/1: negate d/1]
p/y > 98 [d/2: negate d/2]
]
p/x: p/x + d/1
p/y: p/y + d/2
show b
]
]
]
]


C 294 bytes

#include<graphics.h> f(){int d=0;g,x,y,a=0,b=0;initgraph(&d,&g,NULL);x=30;y=30;while(1){x+=6;y+=7;if(y<60)b=0;if(x<60)a=0;if((y>getmaxy()-40)) b=!b;if((x>getmaxx()-40))a=!a;if(b){y-=18;x+=3;}if(a){x-=15;y+=2;}usleep(10000);setcolor(4);cleardevice();circle(x, y,30);floodfill(x,y,4);delay(45);}}


Ungolfed version:

#include<graphics.h>
void f()
{
int d=DETECT,g,x,y,r=30,a=0,b=0;
initgraph(&d,&g,NULL);
x=30;
y=30;

while(1)
{
x+=6;
y+=7;

if(y<60)
b=0;
if(x<60)
a=0;

if((y>getmaxy()-40))
b=!b;

if((x>getmaxx()-40))
a=!a;

if(b)
{
y-=18;
x+=3;
}

if(a)
{
x-=15;
y+=2;
}
usleep(10000);
setcolor(RED);
cleardevice();
circle(x,y,r);
floodfill(x,y,RED);
delay(45);

}

}


Explanation

• So in order to begin with this, I had to get graphics.h in my /usr/include directory. Therefore, i searched and this is what I found. It is a TurboC Graphics implementation using SDL for Linux. One could also use OpenGL. In windows, I guess it is already installed, not sure about MacOS.
• void initgraph(int *graphdriver, int *graphmode, char *pathtodriver); initialises the system and puts it in a graphics mode, in this case graphics driver is automatically detected. Please refer to this link for more details.
• x and y are coordinates that determines the ball's position.
• a and b are flags, a is set to zero when the x value drops below 60 and b is set to zero when y drops below 60.
• The flags are toggled when x and y exceeds boundary values of window, and the coordinates are accordingly adjusted.
• I put a usleep so that my CPU does not get stressed out.
• One should normally use a closegraph() call, in order to close the window. But it's missing here.

Must be compiled with the linker flag -lgraph

It runs smoother on real hardware. :)

• Are import statements necessary to run this program? – user41805 Feb 20 '17 at 18:44
• @KritixiLithos Yes sir; Updated! you need to include graphics.h. This answer askubuntu.com/questions/525051/… was helpful. – Abel Tom Feb 20 '17 at 18:48

MATL, 42 bytes

1thXH_XIXx8E70hZ"79HZ}&(DH4M\1>EqI*XIH+XH


This uses a 70×16 screen and character O. If you wait for a few bounces you'll see the ball hitting a corner.

Try at MATL Online!

Screen size can be easily modified in the code. The relevant part is 8E70, which pushes 8, doubles it, and pushes 70. For example, for a 80×25 screen replace by 5W80, which pushes 5, squares it, and pushes 80 (or replace by 25 80, but that requires one more byte).

Also, adding tD at the end of the code shows the current position in real time (vertical, then horizontal, 1 1 is upper left). As an example, for a 80×18 screen,

1thXH_XIXx9E80hZ"79HZ}&(DH4M\1>EqI*XIH+XHtD


Try it too!

Explanation

This uses an infinite loop. Position is kept in clipboard H as a 1×2 vector, and direction is kept in clipboard I as a 1×2 vector with entries 1 or -1.

Each iteration clears the screen, defines a matrix of spaces, writes an O at the relevant position, and displays it. Then the position and directio need to be updated.

Position is 1-based, and thus the edges of the screen are 1 and the maximum screen size. So if position modulo screen size gives 0 or 1 in either the first or second components, which means we have reached a vertical or horizontal edge respectively, that component of the direction vector is negated. After that, the new direction is added to the current position to obtain the new position.

Here's the ZX Spectrum listing.

  10 FOR n=0 to 7
20 READ a: POKE USR "a"+n, a
30 NEXT n
40 DATA 60,126,243,251,255,255,126,60
50 LET x=10:LET y=10:LET vx=1: LET vy=1
60 PRINT AT y,x;"\a"
70 IF x<1 OR x>30 THEN LET vx=-vx
80 IF y<1 OR x>20 THEN LET vy=-vy
90 LET x=x+vx: LET y=y+vy
100 PRINT AT y-vy,x-vx;" ": GO TO 60

• Nice first entry DrlB - could you please include a byte count. I assume this will work on any Speccy including 16K machines? – Shaun Bebbers Sep 4 '17 at 12:08
• Hi this is 201 bytes, you could omit the first 4 lines but then you get just a bouncing "a" character but it saves you 64 bytes. I will try to optimize. This nothing fancy at all and will work on any Spectrum model :) – DrIB Sep 8 '17 at 1:40
• Ok I've managed to cut it down to 185 by condensing the lines a bit without dropping the ball graphics. It's a bit less readable though but it's faster. – DrIB Sep 8 '17 at 5:00

C + curses, 190 bytes

#include<curses.h>


Explanation:

#include<curses.h>
w;h;x;y;d;
main(e)
{
initscr();
curs_set(0);
getmaxyx(stdscr,h,w);

// initialize distances to 1 (e is 1 when called without arguments)
// wait for 1/5 second, then write space character at current pos
{
// advance current pos and write ball character (0)

// check and change direction:
if(!y||y>h-2)d=-d;
if(!x||x>w-2)e=-e;

// trigger output to screen:
refresh();
}
}


Lua (LÖVE 2D), 130 bytes

x,y,a,b=0,0,1,1
function love.draw()a=(x<0 or x>800)and-a or a
b=(y<0 or y>600)and-b or b
x=x+a
y=y+b
love.graphics.points(x,y)end


Lua is not the best language when it comes to code golf, but here you go! A few points worth mentioning:

• The default canvas size is 800 x 600. It can be changed in the configuration file, but I didn't see any size restrictions, so I left it as is.

• love.draw() is LÖVE's drawing function and it has a predetermined name. Alternative LÖVE functions that could be used would be love.update(dt) and love.run() -- the first being longer, in bytes, and the latter being shorter, yes, but without a built-in infinite loop. Thus, draw() seems to be our best bet here.

• The above version uses love.graphics.points to draw the ball. Although shorter, I am not sure it is allowed. Here is a GIF of how it runs:

As you can see (or perhaps can't), there is a single pixel moving on the screen. While that saves up bytes, it's not the most satisfying of results.

So I've made an alternative 131 bytes solution:

x,y,a,b=0,0,1,1
function love.draw()a=(x<0 or x>795)and-a or a
b=(y<0 or y>595)and-b or b
x=x+a
y=y+b
love.graphics.print(0,x,y)end


This one uses love.graphics.print -- which prints text -- and a 0 as a ball, making it much more visible and appealing.

CHIP-8, 3634 28 bytes

FF29 'LDF I,vF //load digit sprite for the value of vF (should be 0)

4000 'SNE v0,0 //if x is 0...
6201 'LD v2,1 //set x velocity to 1
403C 'SNE v0,3C //if x is 3C...
62FF 'LD v2,FF //set x velocity to -1
4100 'SNE v1,0 //if y is 0...
6301 'LD v3,1 //set y velocity to 1
411B 'SNE v1,1B //if y is 1B...
63FF 'LD v3,FF //set y velocity to -1

D015 'DRW v0,v1,5 //draw sprite
D015 'DRW v0,v1,5 //draw sprite again to clear it.



No fancy tricks here...

Requires an interpreter that draws sprites correctly (only one sprite can be drawn per frame, which slows down the program enough for you to be able to see it).

Low quality video

ZX Spectrum BASIC - 179 bytes

Here it is just condensed a little bit. It's 179 bytes with the ball graphics included

  10 LET a=10: LET b=10: LET c=1: LET d=-1: FOR e=0 TO 7: READ f: POKE USR "a"+e, f: NEXT e
20 DATA 60, 126,243,251,255,255,126,60
30 PRINT AT b,a;"\a"
40 IF a<1 OR a>30 THEN LET c=-c
50 IF b<1 OR b>20 THEN LET d=-d
60 LET a=a+c: LET b=b+d: PRINT AT b-d, a-c;" ": GO TO 30

• Please look at the mark-up used for answers, also by using the character o or O you may be able to save some bytes in the symbolic listing; you are also able to edit previous answers when you have improved solutions, rather than re-answering the same question – Shaun Bebbers Sep 8 '17 at 8:48