Skip to content

Instantly share code, notes, and snippets.

@scottwalters
Last active October 10, 2018 08:53
Show Gist options
  • Save scottwalters/ae628db55726670c54e8 to your computer and use it in GitHub Desktop.
Save scottwalters/ae628db55726670c54e8 to your computer and use it in GitHub Desktop.
Slides from YAPC::NA 2014 talk, Programming the Atari 2600
=effect instant
=background_image 3686176796_a6eb39a73e_o.jpg
# 1977
# How much RAM does this thing have? Guess.
==========
=effect instant
# About Me
# My brother and I owned an Intellivision and then I got an Atari 8 bit home computer.
# The 400/800/XL/XE line was my introduction to programming.
# their home computers were Atari's 3rd home hardware iteration and were pretty advanced.
=background_image 640px-Atari_800.jpg
==========
# They got modernized over a few generations. This is the most recent.
=background_image Atari-130XE.jpg
==========
# 70s programming books were trippy
=background_image howtoprogramyouratari6502.jpg
==========
=background_image atariforce1_01.jpg
==========
=background_image atariforth.jpg
============
# I also later had an Amiga, another Jay Miner design that added even more such as a blitter.
=background_image jay-miner-1.jpg
==================
# History
# atari's first product was Pong, though they stole the idea
=background_image pong2.jpg
==================
# pong was absolutely huge
# everyone was playing it
# there were a bunch of clones
# went straight from the arcade (inventing the concept of the arcade machine) to the home
# first time the TV was interactive
# implemented directly in digital logic; no CPU
=background_image pong.jpg
=================
=background_image atarixmas.jpg
=================
# Introduced Mario
# =background_image mario_bros_pal_5.gif
=background_image mqdefault.jpg
===================
# but not in Mario Bros... in Donkey Kokng
=background_image donkey_kong.jpg
===============
# Minimal and Clever
# it's 1975. you want to make a $200 home game system.
# $870 in todays dollars
# computers cost many thousands.
# how would you design a game system?
# it was also the first CD system
# same year
# US$1298[14] (with 4 kB of RAM)
# without disk or monitor
# crap graphics
# bad colors, bad animation
=background_image appleII.jpg
=====================
=system mplayer "Aztec - Apple Ii (Datamost 1982) (2011).mp4"
# TIA Programming
# the design of the Atari 2600 is intimitely tied to the design of the cathode ray tube
=background_image tv-cathode.gif
===========
# how do you make something cheap?
# replace hardware with software.
# 192 visible scanlines
# 76 machine cycles each
# about 1/3rd of visible scanlines isn't visible
# instructions take between 2 and 7 with most instructions that access memory taking 3 to 5
# CPU is occupied updating a bunch of hardware registers to change what appears on the screen from one line to the next
# 70 non-visible lines give 5320 cycles to do game logic in
=background_image frame.png
==========
=background_image dealameal.jpg
==========
=background_image mailboxes_DSC_0016.JPG
# $00-$7f is hardware registers. $80-$ff is RAM. all of those are addressable with one byte, and the 6502 has an addressing mode where the first 256 bytes are of RAM are addressed with one byte.
# the 6502 stack at $1ff downward maps there also.
# $280 up is PIO I/O ports.
# reads and writes to RAM addresses work exactly how you'd expect.
# reads and write to these "hardware register" addresses bridge the gap between hardware and software.
# one dimensional universe; everything else is an illusion
==============
# hardware registers look just like RAM
# except you can't read back values you stored there
=font FreeMono.ttf
=clear_background 1
sta $1b ; hardware register: change the pattern data
sta $80 ; RAM: writing data
===================
=background_image 2600_color_chart.jpg
====================
=background_image write1.jpg
===================
=background_image write2.png
# two players, two missiles, and one ball.
# things that don't say "strobe" or "reset" or "sync" in their name stay in effect until you change them.
# PF0, GRP0, COLUP0, and most other registers need to be updated every scanline *if* you want that property to change.
# things that turn bits on and off: 2 player registers, ball, 2 missiles, 20 bits of background pattern data (mirrored or repeated)
# what happends when you set the background data to repeat but change the registers right after it uses them the first time?
==========
=background_image write3.png
==========
=background_image write4.png
# you don't store a number in a register to set the positions of movable objects. oh, no.
==========
=background_image write5.png
==========
=background_image write6.png
==========
# there are documented usages, and then there are clever interactions and side effects
=background_image write7.png
==========
# How Did They Do this?
# what steps did they have to take?
# which registers did they update when?
=background_image Combat (1977) (Atari) (4K) [a].png
=effect instant
# 6502
# 6507 total cost was $12; Motorola/Intel wanted $150-200
# 6502 assembly
# only one general purpose register. eg, ADC only adds A to memory, not to X or Y.
# zero page
# also used in Bender, the Terminator robot, the Apple I and ][, C=64, Atari home computers, the NES, and a bunch of other things
# basically the cockroach of the microprocessor world
# one general purpose register
# other low end chips had 2 and high end chips had lots
# can't move directly from one memory location to another; have to do a load then a store.
# can't do math operations between two memory locations.
# can't do math operations between two registers.
# 8080, 8048, and basically everything else except the even more minimal Sinclair has a much nicer instruction set
=background_image bender6502images.jpeg
=============
=background_image terminator.jpg
============
# assembly programming
=background_image combat-illus-150dpi.png
=================
v--- start of line
label opc operands ; comment
================
# timings and address modes
ADC #$01 ; +2 Immediate
ADC $99 ; +3 Zero Page
ADC $99,X ; +4 Zero Page,X (or ,Y)
ADC $1234 ; +4 Absolute
ADC $1234,X ; +4* Absolute,X (or ,Y)
ADC ($AA,X) ; +6 (Indirect,X)
ADC ($CC),Y ; +5* (Indirect),Y
================
=background_image opcode.jpg
=================
=clear_background 1
=font_size 20
ASO
This opcode ASLs the contents of a memory location and then ORs the result
with the accumulator.
RLA
RLA ROLs the contents of a memory location and then ANDs the result with
the accumulator.
AXS
AXS ANDs the contents of the A and X registers (without changing the
contents of either register) and stores the result in memory.
LAX
This opcode loads both the accumulator and the X register with the contents
of a memory location.
DCM
This opcode DECs the contents of a memory location and then CMPs the result
with the A register.
INS
This opcode INCs the contents of a memory location and then SBCs the result
from the A register.
OAL
This opcode ORs the A register with #$EE, ANDs the result with an immediate
value, and then stores the result in both A and X.
==============
=image tia001.jpg
# 6502+TIA integration
# 1.17 mhz
# Exactly 1/3rd of the NTSC color clock
# Every 3 pixels draw on the screen, the CPU gets one clock cycle
# Small instructions take two clock cycles; six pixels go by
# WSYNC
# INTIM, TIM64T
==============
# My game
# ... inspired by Joust
=background_image retro:-joust_wallpapers_20408_1440x900.jpg
==========
# ... and JoustPong
=background_image joustpong.png
# ... which was inpsired by Pong
=========
# and Flappy Bird
=background_image flappo.png
# ... as ported to the Atari 2600
==========
# 39% perl
=background_image 39_percent_perl.png
# so, we have 128 bytes of RAM to play with.
# I had this bright idea of using most of it as a ghetto sort of frame buffer.
# that would allow time to do some 3D math before we have to start cranking on banging video registers.
# some games have a few 3D positioned objects
# 5 bits of z-buffer depth data, 3 bits of color data.
=effect instant
=font FreeMono.ttf
==========
# momentum... those nice arcing jumps and flaps
# every enemy I add takes 6 bytes of data and removes 6 lines of display
=background_image speed.png
==========
=clear_background 1
=font_size 8
# speed
momentum0
; control loops back here when we iterate over the movable bodies
lda playerzspeed,x
bmi momentum1
; positive case
clc
adc playerzlo,x
sta playerzlo,x
bvc momentum2 ; overflow means we need to carry from our signed playerzlo into the unsigned playerz. if no overflow so we didn't go above 127. go on to dealing with Y speed.
clc
lda playerz,x
adc #01
; bcs momentum2 ; branch if we carried; don't wrap playerz above 255 XXX actually, wouldn't this be the win condition for the level? XXX I might be dreaming, but it seems like wrapping around the edge of a level actually works
sta playerz,x
jmp momentum2
momentum1
; playerzspeed is negative
_absolute ; absolute value of playerzspeed
sta tmp1
lda playerzlo,x
sec
sbc tmp1
sta playerzlo,x
lda playerz,x
; beq momentum1a ; but don't go below 0; XXX testing; actually, we do want enemies flying towards us to be able to warp off the end of the level
sbc #0 ; if we barrowed above, this will effective decrement playerz by one
sta playerz,x
momentum1a
==========
=clear_background 1
=font_size 20
# speed
momentum0
; control loops back here when we iterate over the movable bodies
lda playerzspeed,x
bmi momentum1
; positive case
clc
adc playerzlo,x
sta playerzlo,x
bvc momentum2
clc
lda playerz,x
adc #01
; bcs momentum2
sta playerz,x
jmp momentum2
momentum1
; playerzspeed is negative
_absolute
sta tmp1
lda playerzlo,x
sec
sbc tmp1
sta playerzlo,x
lda playerz,x
; beq momentum1a
sbc #0
sta playerz,x
momentum1a
===============
# Rotation Function
Vx'=rot((x,0),a) = (x*cos(a) ,x*sin(a))
+ Vy'=rot((0,y),a) = ( +y*sin(a), -y*cos(a))
----------------------------------------------------------
V' =rot((x,y),a) = (x*cos(a)+y*sin(a),x*sin(a)-y*cos(a))
# We don't do that.
# Instead, view is fixed straight ahead.
==========
# Projection Function
New Y=k*y/(z+dist)
X=k*x/(z+dist)
# we don't use that.
=============
=background_image render_overview.png
=============
=clear_background 1
# _arctan
# "arctangent can find the angle given the ratio of two sides of a right triangle"
# there's a right triangle between us and the object we're rendering:
# playerz - objectz, playery - objecty
# the arctangent table is indexed by the ratio of the y and z deltas
# we use the most significant non-zero four bits of each delta
# table only has things in our field of view. ie,
# atan(z/y) normalized to between 0 and #(viewsize/2) instead of taking rads or degrees
# the perl code is inline in the 6502 code; asm uses the semicolon to indicate a comment. found that handy.
# XXX look at the JS raycaster page
; use Math::Trig;
; my $scanlines = 107;
; my $max = int($scanlines / 2); $max-- if(( $scalines & 0b01) == 0);
; my $field_of_view_in_angles = 90;
; my $multiplier = $scanlines / $field_of_view_in_angles;
; for my $y (0..15) {
; my @z = $y+1 .. $y+16;
; print "\t\t; z = @{[ join ', ', @z ]}\n";
; print "\t\tdc.b ";
; for my $z (@z) {
; if( $z == 0 ) { print '%0000000, '; next; }
; my $angle = int(rad2deg(atan($y/$z))*$multiplier);
; print $angle;
; print ", " if $z != $z[-1];
; }
; print "; y = $y\n";
; }
; print "\n";
=============
arctangent
; z = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; y = 0
; z = 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
dc.b 31, 21, 16, 13, 11, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4, 4; y = 1
; z = 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
dc.b 40, 31, 25, 21, 18, 16, 14, 13, 12, 11, 10, 9, 9, 8, 7, 7; y = 2
; z = 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
dc.b 43, 36, 31, 27, 24, 21, 19, 18, 16, 15, 14, 13, 12, 11, 11, 10; y = 3
; z = 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
dc.b 45, 40, 35, 31, 28, 25, 23, 21, 20, 18, 17, 16, 15, 14, 14, 13; y = 4
; z = 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21
dc.b 47, 42, 38, 34, 31, 29, 26, 25, 23, 21, 20, 19, 18, 17, 16, 15; y = 5
; z = 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
dc.b 48, 43, 40, 36, 34, 31, 29, 27, 25, 24, 23, 21, 20, 19, 18, 18; y = 6
; z = 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
dc.b 48, 45, 41, 38, 35, 33, 31, 29, 28, 26, 25, 24, 22, 21, 20, 20; y = 7
; z = 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24
dc.b 49, 45, 42, 40, 37, 35, 33, 31, 29, 28, 27, 25, 24, 23, 22, 21; y = 8
; z = 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
dc.b 49, 46, 43, 41, 38, 36, 34, 33, 31, 30, 28, 27, 26, 25, 24, 23; y = 9
; z = 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
dc.b 50, 47, 44, 42, 40, 38, 36, 34, 33, 31, 30, 29, 27, 26, 25, 25; y = 10
; z = 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27
dc.b 50, 47, 45, 43, 41, 39, 37, 35, 34, 32, 31, 30, 29, 28, 27, 26; y = 11
; z = 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
dc.b 50, 48, 45, 43, 41, 40, 38, 36, 35, 34, 32, 31, 30, 29, 28, 27; y = 12
; z = 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29
dc.b 50, 48, 46, 44, 42, 40, 39, 37, 36, 35, 33, 32, 31, 30, 29, 28; y = 13
; z = 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
dc.b 51, 48, 46, 45, 43, 41, 40, 38, 37, 35, 34, 33, 32, 31, 30, 29; y = 14
; z = 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
dc.b 51, 49, 47, 45, 43, 42, 40, 39, 38, 36, 35, 34, 33, 32, 31, 30; y = 15
===============
=font_size 12
.platlinedelta
lda deltay
beq .platlinedeltastuff2 ; stuff the case where deltay = 0
_absolute
sta tmp2 ; tmp2 has abs(deltay)
cmp deltaz ; is deltaz = deltay?
bne .platlinedeltago ; branch on the normal case where deltaz != deltay
.platlinedeltastuff0
; deltaz = deltay or deltaz = 0; stuff the return value to be the very top or very bottom scanline, depending
bit deltay
bpl .platlinedeltastuff1
; platform is above us
lda #[viewsize - 1] ; is this correct? XXX or are we off by one?
jmp .platarctan9
.platlinedeltastuff1
; platform is below us
lda #0
jmp .platarctan9
.platlinedeltastuff2
lda #[viewsize/2]
bne .platarctan9
.platlinedeltago
lda deltaz
beq .platlinedeltastuff0 ; stuff the case where deltaz = 0
sec
sbc tmp2
sta tmp1 ; tmp1 has deltaz - abs(deltay)
ora tmp2 ; combined bits so we only have to test one value to see if all bits are clear of the top nibble
.platlinedeltaagain
cmp #$0F
bmi .platlinedeltadone ; 0F is larger, had to barrow, so we know that no high nibble bits are set
lsr
lsr tmp1
lsr tmp2
jmp .platlinedeltaagain
.platlinedeltadone
lda tmp2 ; table loops over $z then over $y, so $z is down and $y is across
asl ; tmp1 is our deltaz, tmp2 our deltay
asl ; so we make tmp1 our high nibble so it goes down and tmp2 our low nibble so it goes across
asl
asl
ora tmp1
tay
bit deltay ; handle the separate cases of the platform above us and the platform below us
bpl .platarctan1 ; branch if deltay is positive; this means that the platform is lower than us
lda arctangent,y ; platform is above us; add the arctangent value to the middle of the screen
clc
adc #(viewsize/2) ; 'view' is upside down, so adding relative to the middle of it moves things towards the top of the screen
jmp .platarctan9
.platarctan1
lda #(viewsize/2) ; platform is below us; subtract the arctangent value from the middle of the screen
sec
sbc arctangent,y ; 'view' is upside down, so subtracting relative the middle of it moves things towards the bottom of the screen
; negative value indicates off screen angle; return the negative value to proprogate the error
.platarctan9
====================================
# hypot unit test
=font_size 16
use Test::More;
use Acme::6502;
use symbols;
my $symbols = symbols::symbols('newbies.lst');
my $cpu = Acme::6502->new();
$cpu->load_rom( 'newbies.bin', 0xf000 );
sub run_cpu {
$cpu->run(10000, sub {
my ($pc, $inst, $a, $x, $y, $s, $p) = @_;
if( $pc == $symbols->{'.plot9'} ) {
${ PadWalker::peek_my(1)->{'$ic'} } = 0;
}
});
}
my $view = $symbols->view;
my $viewsize = 0xff - $view;
====================================
=font_size 16
# hypot unit test
$cpu->write_8( $symbols->curplat, 0 ); # controls what color the line drawn will be
$cpu->write_8( $symbols->lastline, 0xff );
ok my $expected_width = $cpu->read_8( $symbols->perspectivetable + 10 ), 'there is an expected width';
ok my $expected_color = $cpu->read_8( $symbols->level0 + 0 + 3 ), 'there is an expected color';
is $cpu->read_8( $symbols->view + 50 ), 0, "is line 50 blank to start with?";
# Y gets the distance, which we use to figure out which size of line to draw
# X gets the scan line to draw at
$cpu->set_pc($symbols->{'.plotonscreen'});
$cpu->set_y( 10 );
$cpu->set_x( 50 );
run_cpu();
my $line = $cpu->read_8( $symbols->view + 50 );
is $line & 0b11100000, $expected_color, "line drawn with expected color";
is $line & 0b00011111, $expected_width, "line drawn with expected width";
====================
# _plathypot
=background_image hypot.jpg
===================
# _plathypot
=clear_background 1
=font_size 18
# add zdelta back into zdelta a fractional number of times depending on ydelta
lda deltay
_absolute
tay
lda distancemods,y
sta tmp2 ; top three bits indicate whether 1/4th of deltaz should be re-added to itself, then 1/8th, etc
lda deltaz
lsr
lsr
sta tmp1 ; tmp1 contains the fractional (1/4 at first, then 1/8th, then 1/16th) value of deltaz
lda deltaz ; fresh copy to add fractional parts to
clc
asl tmp2
bcc .plathypot1
clc
adc tmp1 ; 1/4
.plathypot1
lsr tmp1 ; half again
asl tmp2
bcc .plathypot2
clc
adc tmp1 ; 1/8th
.plathypot2
lsr tmp1 ; half again
asl tmp2
bcc .plathypot3
clc
adc tmp1 ; 1/16th
.plathypot3
========================
=font_size 16
=clear_background 1
$cpu->set_pc( $symbols->platrenderline );
$cpu->write_8( $symbols->deltaz, 2 );
$cpu->write_8( $symbols->deltay, 2 );
run_cpu( $symbols->platrenderline1, $symbols->platnext );
is $cpu->get_pc, $symbols->platrenderline1 + 2, "deltaz = 2, deltay = 2 slope";
====================
=font_size 16
# _plotonscreen
.plot_down1
lda view,x ; +4 9 get the line width of what's there already
and #%00011111 ; +2 11 mask off the color part and any other data
cmp perspectivetable,y ; +4 15 compare to the fatness of line we wanted to draw
bpl .plot_down2 ; +2 17 what we wanted to draw is smaller. skip it.
lda perspectivetable,y ; +4 21 perspectivetable translates distance to on-screen line width
ora tmp2 ; +3 24 add the platform color
sta view,x ; +4 28 draw to the framebuffer
.plot_down2
cpx lastline ; +3 31 we want to catch it at 1 diff
beq .plot8 ; +2 33 if lastline minus curline is exactly 1 away, we're done
.plot_down2a
dex ; +2 35 drawing downwards relative last plot
lda INTIM ; +2 2 worst case scenario, we read this as a '1' one cycle before 0
bne .plot_down1 ; +3 5
_vsync ; do the next vsync/vblank thing that needs to be done and then
; put more time on the timer, or else jump to start
bne .plot_down1 ; loop always
===========================
=font_size 14
open my $fh, '6502_formatted.txt' or die $!;
while( my $line = readline $fh ) {
chomp $line;
my @line = split m/ /, $line;
$cycles_per_opcode->[ hex($line[0]) ] = $line[1];
}
sub run_cpu {
my @stop_symbols = @_;
my $cycles = 0;
$cpu->run(100000, sub {
my ($pc, $inst, $a, $x, $y, $s, $p) = @_;
$cycles_per_opcode->[$inst] or die sprintf( "%2x (%d) has no cycle count", $inst, $inst) . "\n" . Dumper( $cycles_per_opcode );
$cycles += $cycles_per_opcode->[$inst];
if( grep $pc == $_, @stop_symbols ) {
${ PadWalker::peek_my(1)->{'$ic'} } = 0;
}
});
return $cycles;
}
==========================
# XXX counting cycles
# unit testing runtime
=font_size 16
$cpu->set_pc( $symbols->platlevelclear );
$cpu->write_8( $symbols->playerz, 0x00 );
$cpu->write_8( $symbols->playery, 0x20 );
$cycles = run_cpu( $symbols->vblanktimerendalmost );
diag "ran in $cycles cycles"; # 10744
ok $cycles < $available_cycles, "finishes in less than $available_cycles cycles";
================
=font_size 16
# unit testing timers/hardware registers
perldoc Acme::6502 says:
BUGS AND LIMITATIONS
Doesn't have support for hardware emulation hooks - so memory
mapped I/O is out of the question until someone fixes it.
=======================
# unit testing timers/hardware registers
=clear_background 1
=font_size 16
package Register::TIM64T {
# start a new timer
use base 'Tie::StdScalar';
sub TIESCALAR { my $class = shift; $_[0] ||= 0; return bless \$_[0] => $class; }
sub STORE {
$timer_cycles = $cycles;
${$_[0]} = $_[1];
}
};
tie $cpu->{mem}->[ $symbols->TIM64T ], 'Register::TIM64T';
package Register::INTIM {
# read the timer
use base 'Tie::StdScalar';
sub TIESCALAR { my $class = shift; $_[0] ||= 0; return bless \$_[0] => $class; }
sub FETCH {
my $ret = $cpu->{mem}->[ $symbols->TIM64T ] - int( ( $cycles - $timer_cycles ) / 64 );
$last_intim_value = $ret;
$ret;
}
};
tie $cpu->{mem}->[ $symbols->INTIM ], 'Register::INTIM';
=========================
# unit testing timers/hardware registers
=clear_background 1
=font_size 16
$cpu->write_8( $symbols->TIM64T, 76 );
diag "code is checking timer against this constant: " . $cpu->read_8( $symbols->platnextline0a+1 );
$cpu->set_pc( $symbols->platlevelclear );
$cpu->write_8( $symbols->playerz, 0x00 );
$cpu->write_8( $symbols->playery, 0x1F );
run_cpu( $symbols->vblanktimerendalmost, $symbols->startofframe );
diag "stopped at symbol " . $symbols->name_that_location( $cpu->get_pc );
ok ! $ran_out_of_time, "didn't run out of time";
==========================================
# display kernel
=clear_background 1
=font_size 16
; get COLUPF, COLUBK, and scanline values ready to roll
ldy scanline ; +3 36 (33 when execution arrives)
; get value for COLUPF ready in Y
lax view,y ; +4 40
ldy platformcolors,x; +4 44
; if the looked up color is $00, skip redrawing CULUPF, COLUBK, and the
; PF registers to instead update the player registers
beq enemy ; +2 46
; get the pf*lookup index ready in X
and #%00011111 ; +2 48
tax ; +2 50
; get value for COLUBK somewhat setup in A
lda scanline ; +3 53
adc skyline ; +3 56
nop ; +2 58 ... XXX 12 bonus cycles
nop ; +2 60
nop ; +2 62
nop ; +2 64
nop ; +2 66
nop ; +2 68
dec scanline ; +5 73
bpl platforms ; +3 76
; (counting the case where it's taken; this has to come out to exactly 76 cycles)
==========================================
# display kernel
=clear_background 1
=font_size 16
# drawing starts on cycle 22
platforms
sty COLUPF ; +3 3
tay ; +2 5
lda background,y ; +4 9
sta COLUBK ; +3 12
lda pf0lookup,x ; +4 16
sta PF0 ; +3 19 ... this needs to happen sometime on or before cycle 22
lda pf1lookup,x ; +4 23
sta PF1 ; +3 26 ... this needs to happen some time before cycle 28
lda pf2lookup,x ; +4 30
sta PF2 ; +3 33
==========================================
=background_image newbiesrender.jpg
===========================================
# notes on my stuff:
# no use of jsr/rts. don't want to use the two bytes of RAM for one level of nesting, and I'm using the S register to hold stuff.
==============
# Current Status of 2600 Development
# current status...
=background_image melody_board.jpg
# advanced topics: asserting the bus
# ARM processors in the cart
# open source style knowledge sharing
# active community on atariage.com
# new homebrews finished every year, and for sale
===========================
=font_size 18
level3
playfield:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X..............................X
...............................X
XXXXXXXXXXXXXXXXXX.............X
X..............................X
X..............................X
X......................XXXXXXXXX
X..............................X
X.....XXXXXXXXXXX..............X
X..............................X
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
end
ballx = 19 : bally = 19
return
===============================
=font_size 16
rem check for portal collision and overwrite the initial portal with the new one
if collision(missile0,missile1) && m0_shot{0} then missile1x = 255 : missile1y = 255 : m1_persistence{1}=0
if collision(missile0, missile1) && m1_shot{1} then missile0x = 255 : missile0y = 255 : m0_persistence{0}=0
if collision(missile0,ball) then missile0x = 255 : missile0y = 255 : m0_persistence{0}=0
if collision(missile1,ball) then missile1x = 255 : missile1y = 255 : m1_persistence{1}=0
rem check to see if a moving portal hit the playfield and if so then stop moving and make it active
rem also disregard portals that are designated inactive
if collision(missile1,playfield) && missile1x < 255 then m1_shot{1}=0 : m1_persistence{1}=1
if collision(missile0,playfield) && missile0x < 255 then m0_shot{0}=0 : m0_persistence{0}=1
rem see if the player is accessing a portal and change their position to the other one
if collision(player1,missile0) && m1_persistence{1} then gosub player_pos1
if collision(player1,missile1) && m0_persistence{0} then gosub player_pos0
=============
# Conclusion
=background_image portal.bas.png
# 14 year production run.
# fun to optimize and polish and rewrite and rewrite a small bit of code
# It's all about gameplay. What makes the game tick? What's the essential element?
# It doesn't take much RAM get gameplay right.
===================
END
=============
=============
# fodder
=background_image perlprogramming.png
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment