initial commit

This commit is contained in:
Steven Hugg 2016-12-15 20:21:51 -05:00
parent 0cc1de0cb2
commit e42ff3a56a
52 changed files with 36415 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*~
node_modules

9
.gitmodules vendored Normal file
View File

@ -0,0 +1,9 @@
[submodule "javatari.js"]
path = javatari.js
url = https://github.com/sehugg/javatari.js
[submodule "dasm"]
path = dasm
url = https://github.com/sehugg/dasm
[submodule "codemirror"]
path = codemirror
url = https://github.com/sehugg/codemirror

349
css/codemirror.css Normal file
View File

@ -0,0 +1,349 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
border: 1px solid #eee;
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
font-size: 0.8em;
height: 96vh;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 5px 0 5px;
min-width: 20px;
text-align: right;
color: #99cc99 !important;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: -20px;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
color: #cccccc;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

26681
dasm.js Normal file

File diff suppressed because one or more lines are too long

23
doc/notes.txt Normal file
View File

@ -0,0 +1,23 @@
TODO:
- download ROM file
- NaN in cycle count for macros
- debugging of scan line overflow
- confuse code/data in listing
- show memory locations hovering over lines
- don't play sound when debugging
- coalesce compile events
- don't check against ROM signatures
- better errors when ROM format wrong
- debugging inside of bank switching??? relocated segs?
- support 6502 test cases
- DASM: macro forward refs
- support narrow screens
- show other TIA internal values
- case sensisitvity looking for mismatch variables
- remove pulldown when no preset?
- some units test maybe
- can't step after reset (or when funky frame; TIA frame is out of sync)
- break on BRK/illegal opcode?

BIN
images/pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

BIN
images/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

BIN
images/resetandrun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

BIN
images/runtoline.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

BIN
images/share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

BIN
images/singlestep.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

BIN
images/timing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

176
include/macro.h Normal file
View File

@ -0,0 +1,176 @@
; MACRO.H
; Version 1.06, 3/SEPTEMBER/2004
VERSION_MACRO = 106
;
; THIS FILE IS EXPLICITLY SUPPORTED AS A DASM-PREFERRED COMPANION FILE
; PLEASE DO *NOT* REDISTRIBUTE MODIFIED VERSIONS OF THIS FILE!
;
; This file defines DASM macros useful for development for the Atari 2600.
; It is distributed as a companion machine-specific support package
; for the DASM compiler. Updates to this file, DASM, and associated tools are
; available at at http://www.atari2600.org/dasm
;
; Many thanks to the people who have contributed. If you take issue with the
; contents, or would like to add something, please write to me
; (atari2600@taswegian.com) with your contribution.
;
; Latest Revisions...
;
; 1.06 03/SEP/2004 - nice revision of VERTICAL_BLANK (Edwin Blink)
; 1.05 14/NOV/2003 - Added VERSION_MACRO equate (which will reflect 100x version #)
; This will allow conditional code to verify MACRO.H being
; used for code assembly.
; 1.04 13/NOV/2003 - SET_POINTER macro added (16-bit address load)
;
; 1.03 23/JUN/2003 - CLEAN_START macro added - clears TIA, RAM, registers
;
; 1.02 14/JUN/2003 - VERTICAL_SYNC macro added
; (standardised macro for vertical synch code)
; 1.01 22/MAR/2003 - SLEEP macro added.
; - NO_ILLEGAL_OPCODES switch implemented
; 1.0 22/MAR/2003 Initial release
; Note: These macros use illegal opcodes. To disable illegal opcode usage,
; define the symbol NO_ILLEGAL_OPCODES (-DNO_ILLEGAL_OPCODES=1 on command-line).
; If you do not allow illegal opcode usage, you must include this file
; *after* including VCS.H (as the non-illegal opcodes access hardware
; registers and require them to be defined first).
; Available macros...
; SLEEP n - sleep for n cycles
; VERTICAL_SYNC - correct 3 scanline vertical synch code
; CLEAN_START - set machine to known state on startup
; SET_POINTER - load a 16-bit absolute to a 16-bit variable
;-------------------------------------------------------------------------------
; SLEEP duration
; Original author: Thomas Jentzsch
; Inserts code which takes the specified number of cycles to execute. This is
; useful for code where precise timing is required.
; ILLEGAL-OPCODE VERSION DOES NOT AFFECT FLAGS OR REGISTERS.
; LEGAL OPCODE VERSION MAY AFFECT FLAGS
; Uses illegal opcode (DASM 2.20.01 onwards).
MAC SLEEP ;usage: SLEEP n (n>1)
.CYCLES SET {1}
IF .CYCLES < 2
ECHO "MACRO ERROR: 'SLEEP': Duration must be > 1"
ERR
ENDIF
IF .CYCLES & 1
IFNCONST NO_ILLEGAL_OPCODES
nop 0
ELSE
bit VSYNC
ENDIF
.CYCLES SET .CYCLES - 3
ENDIF
REPEAT .CYCLES / 2
nop
REPEND
ENDM
;-------------------------------------------------------------------------------
; VERTICAL_SYNC
; revised version by Edwin Blink -- saves bytes!
; Inserts the code required for a proper 3 scanline vertical sync sequence
; Note: Alters the accumulator
; OUT: A = 0
MAC VERTICAL_SYNC
lda #%1110 ; each '1' bits generate a VSYNC ON line (bits 1..3)
.VSLP1 sta WSYNC ; 1st '0' bit resets Vsync, 2nd '0' bit exit loop
sta VSYNC
lsr
bne .VSLP1 ; branch until VYSNC has been reset
ENDM
;-------------------------------------------------------------------------------
; CLEAN_START
; Original author: Andrew Davie
; Standardised start-up code, clears stack, all TIA registers and RAM to 0
; Sets stack pointer to $FF, and all registers to 0
; Sets decimal mode off, sets interrupt flag (kind of un-necessary)
; Use as very first section of code on boot (ie: at reset)
; Code written to minimise total ROM usage - uses weird 6502 knowledge :)
MAC CLEAN_START
sei
cld
ldx #0
txa
tay
.CLEAR_STACK dex
txs
pha
bne .CLEAR_STACK ; SP=$FF, X = A = Y = 0
ENDM
;-------------------------------------------------------
; SET_POINTER
; Original author: Manuel Rotschkar
;
; Sets a 2 byte RAM pointer to an absolute address.
;
; Usage: SET_POINTER pointer, address
; Example: SET_POINTER SpritePTR, SpriteData
;
; Note: Alters the accumulator, NZ flags
; IN 1: 2 byte RAM location reserved for pointer
; IN 2: absolute address
MAC SET_POINTER
.POINTER SET {1}
.ADDRESS SET {2}
LDA #<.ADDRESS ; Get Lowbyte of Address
STA .POINTER ; Store in pointer
LDA #>.ADDRESS ; Get Hibyte of Address
STA .POINTER+1 ; Store in pointer+1
ENDM
;-------------------------------------------------------
; BOUNDARY byte#
; Original author: Denis Debro (borrowed from Bob Smith / Thomas)
;
; Push data to a certain position inside a page and keep count of how
; many free bytes the programmer will have.
;
; eg: BOUNDARY 5 ; position at byte #5 in page
.FREE_BYTES SET 0
MAC BOUNDARY
REPEAT 256
IF <. % {1} = 0
MEXIT
ELSE
.FREE_BYTES SET .FREE_BYTES + 1
.byte $00
ENDIF
REPEND
ENDM
;-------------------------------------------------------
; SKIP_SCANLINES #lines
;
; Skip a given # of scanlines.
; Sets the X register to zero.
MAC SKIP_SCANLINES
.LINES SET {1}
ldx #.LINES
.vblank sta WSYNC
dex
bne .vblank
ENDM
; EOF

200
include/vcs.h Normal file
View File

@ -0,0 +1,200 @@
; VCS.H
; Version 1.05, 13/November/2003
VERSION_VCS = 105
; THIS IS A PRELIMINARY RELEASE OF *THE* "STANDARD" VCS.H
; THIS FILE IS EXPLICITLY SUPPORTED AS A DASM-PREFERRED COMPANION FILE
; PLEASE DO *NOT* REDISTRIBUTE THIS FILE!
;
; This file defines hardware registers and memory mapping for the
; Atari 2600. It is distributed as a companion machine-specific support package
; for the DASM compiler. Updates to this file, DASM, and associated tools are
; available at at http://www.atari2600.org/dasm
;
; Many thanks to the original author(s) of this file, and to everyone who has
; contributed to understanding the Atari 2600. If you take issue with the
; contents, or naming of registers, please write to me (atari2600@taswegian.com)
; with your views. Please contribute, if you think you can improve this
; file!
;
; Latest Revisions...
; 1.05 13/NOV/2003 - Correction to 1.04 - now functions as requested by MR.
; - Added VERSION_VCS equate (which will reflect 100x version #)
; This will allow conditional code to verify VCS.H being
; used for code assembly.
; 1.04 12/NOV/2003 Added TIA_BASE_WRITE_ADDRESS and TIA_BASE_READ_ADDRESS for
; convenient disassembly/reassembly compatibility for hardware
; mirrored reading/writing differences. This is more a
; readability issue, and binary compatibility with disassembled
; and reassembled sources. Per Manuel Rotschkar's suggestion.
; 1.03 12/MAY/2003 Added SEG segment at end of file to fix old-code compatibility
; which was broken by the use of segments in this file, as
; reported by Manuel Polik on [stella] 11/MAY/2003
; 1.02 22/MAR/2003 Added TIMINT($285)
; 1.01 Constant offset added to allow use for 3F-style bankswitching
; - define TIA_BASE_ADDRESS as $40 for Tigervision carts, otherwise
; it is safe to leave it undefined, and the base address will
; be set to 0. Thanks to Eckhard Stolberg for the suggestion.
; Note, may use -DLABEL=EXPRESSION to define TIA_BASE_ADDRESS
; - register definitions are now generated through assignment
; in uninitialised segments. This allows a changeable base
; address architecture.
; 1.0 22/MAR/2003 Initial release
;-------------------------------------------------------------------------------
; TIA_BASE_ADDRESS
; The TIA_BASE_ADDRESS defines the base address of access to TIA registers.
; Normally 0, the base address should (externally, before including this file)
; be set to $40 when creating 3F-bankswitched (and other?) cartridges.
; The reason is that this bankswitching scheme treats any access to locations
; < $40 as a bankswitch.
IFNCONST TIA_BASE_ADDRESS
TIA_BASE_ADDRESS = 0
ENDIF
; Note: The address may be defined on the command-line using the -D switch, eg:
; dasm.exe code.asm -DTIA_BASE_ADDRESS=$40 -f3 -v5 -ocode.bin
; *OR* by declaring the label before including this file, eg:
; TIA_BASE_ADDRESS = $40
; include "vcs.h"
; Alternate read/write address capability - allows for some disassembly compatibility
; usage ; to allow reassembly to binary perfect copies). This is essentially catering
; for the mirrored ROM hardware registers.
; Usage: As per above, define the TIA_BASE_READ_ADDRESS and/or TIA_BASE_WRITE_ADDRESS
; using the -D command-line switch, as required. If the addresses are not defined,
; they defaut to the TIA_BASE_ADDRESS.
IFNCONST TIA_BASE_READ_ADDRESS
TIA_BASE_READ_ADDRESS = TIA_BASE_ADDRESS
ENDIF
IFNCONST TIA_BASE_WRITE_ADDRESS
TIA_BASE_WRITE_ADDRESS = TIA_BASE_ADDRESS
ENDIF
;-------------------------------------------------------------------------------
SEG.U TIA_REGISTERS_WRITE
ORG TIA_BASE_WRITE_ADDRESS
; DO NOT CHANGE THE RELATIVE ORDERING OF REGISTERS!
VSYNC ds 1 ; $00 0000 00x0 Vertical Sync Set-Clear
VBLANK ds 1 ; $01 xx00 00x0 Vertical Blank Set-Clear
WSYNC ds 1 ; $02 ---- ---- Wait for Horizontal Blank
RSYNC ds 1 ; $03 ---- ---- Reset Horizontal Sync Counter
NUSIZ0 ds 1 ; $04 00xx 0xxx Number-Size player/missle 0
NUSIZ1 ds 1 ; $05 00xx 0xxx Number-Size player/missle 1
COLUP0 ds 1 ; $06 xxxx xxx0 Color-Luminance Player 0
COLUP1 ds 1 ; $07 xxxx xxx0 Color-Luminance Player 1
COLUPF ds 1 ; $08 xxxx xxx0 Color-Luminance Playfield
COLUBK ds 1 ; $09 xxxx xxx0 Color-Luminance Background
CTRLPF ds 1 ; $0A 00xx 0xxx Control Playfield, Ball, Collisions
REFP0 ds 1 ; $0B 0000 x000 Reflection Player 0
REFP1 ds 1 ; $0C 0000 x000 Reflection Player 1
PF0 ds 1 ; $0D xxxx 0000 Playfield Register Byte 0
PF1 ds 1 ; $0E xxxx xxxx Playfield Register Byte 1
PF2 ds 1 ; $0F xxxx xxxx Playfield Register Byte 2
RESP0 ds 1 ; $10 ---- ---- Reset Player 0
RESP1 ds 1 ; $11 ---- ---- Reset Player 1
RESM0 ds 1 ; $12 ---- ---- Reset Missle 0
RESM1 ds 1 ; $13 ---- ---- Reset Missle 1
RESBL ds 1 ; $14 ---- ---- Reset Ball
AUDC0 ds 1 ; $15 0000 xxxx Audio Control 0
AUDC1 ds 1 ; $16 0000 xxxx Audio Control 1
AUDF0 ds 1 ; $17 000x xxxx Audio Frequency 0
AUDF1 ds 1 ; $18 000x xxxx Audio Frequency 1
AUDV0 ds 1 ; $19 0000 xxxx Audio Volume 0
AUDV1 ds 1 ; $1A 0000 xxxx Audio Volume 1
GRP0 ds 1 ; $1B xxxx xxxx Graphics Register Player 0
GRP1 ds 1 ; $1C xxxx xxxx Graphics Register Player 1
ENAM0 ds 1 ; $1D 0000 00x0 Graphics Enable Missle 0
ENAM1 ds 1 ; $1E 0000 00x0 Graphics Enable Missle 1
ENABL ds 1 ; $1F 0000 00x0 Graphics Enable Ball
HMP0 ds 1 ; $20 xxxx 0000 Horizontal Motion Player 0
HMP1 ds 1 ; $21 xxxx 0000 Horizontal Motion Player 1
HMM0 ds 1 ; $22 xxxx 0000 Horizontal Motion Missle 0
HMM1 ds 1 ; $23 xxxx 0000 Horizontal Motion Missle 1
HMBL ds 1 ; $24 xxxx 0000 Horizontal Motion Ball
VDELP0 ds 1 ; $25 0000 000x Vertical Delay Player 0
VDELP1 ds 1 ; $26 0000 000x Vertical Delay Player 1
VDELBL ds 1 ; $27 0000 000x Vertical Delay Ball
RESMP0 ds 1 ; $28 0000 00x0 Reset Missle 0 to Player 0
RESMP1 ds 1 ; $29 0000 00x0 Reset Missle 1 to Player 1
HMOVE ds 1 ; $2A ---- ---- Apply Horizontal Motion
HMCLR ds 1 ; $2B ---- ---- Clear Horizontal Move Registers
CXCLR ds 1 ; $2C ---- ---- Clear Collision Latches
;-------------------------------------------------------------------------------
SEG.U TIA_REGISTERS_READ
ORG TIA_BASE_READ_ADDRESS
; bit 7 bit 6
CXM0P ds 1 ; $00 xx00 0000 Read Collision M0-P1 M0-P0
CXM1P ds 1 ; $01 xx00 0000 M1-P0 M1-P1
CXP0FB ds 1 ; $02 xx00 0000 P0-PF P0-BL
CXP1FB ds 1 ; $03 xx00 0000 P1-PF P1-BL
CXM0FB ds 1 ; $04 xx00 0000 M0-PF M0-BL
CXM1FB ds 1 ; $05 xx00 0000 M1-PF M1-BL
CXBLPF ds 1 ; $06 x000 0000 BL-PF -----
CXPPMM ds 1 ; $07 xx00 0000 P0-P1 M0-M1
INPT0 ds 1 ; $08 x000 0000 Read Pot Port 0
INPT1 ds 1 ; $09 x000 0000 Read Pot Port 1
INPT2 ds 1 ; $0A x000 0000 Read Pot Port 2
INPT3 ds 1 ; $0B x000 0000 Read Pot Port 3
INPT4 ds 1 ; $0C x000 0000 Read Input (Trigger) 0
INPT5 ds 1 ; $0D x000 0000 Read Input (Trigger) 1
;-------------------------------------------------------------------------------
SEG.U RIOT
ORG $280
; RIOT MEMORY MAP
SWCHA ds 1 ; $280 Port A data register for joysticks:
; Bits 4-7 for player 1. Bits 0-3 for player 2.
SWACNT ds 1 ; $281 Port A data direction register (DDR)
SWCHB ds 1 ; $282 Port B data (console switches)
SWBCNT ds 1 ; $283 Port B DDR
INTIM ds 1 ; $284 Timer output
TIMINT ds 1 ; $285
; Unused/undefined registers ($285-$294)
ds 1 ; $286
ds 1 ; $287
ds 1 ; $288
ds 1 ; $289
ds 1 ; $28A
ds 1 ; $28B
ds 1 ; $28C
ds 1 ; $28D
ds 1 ; $28E
ds 1 ; $28F
ds 1 ; $290
ds 1 ; $291
ds 1 ; $292
ds 1 ; $293
TIM1T ds 1 ; $294 set 1 clock interval
TIM8T ds 1 ; $295 set 8 clock interval
TIM64T ds 1 ; $296 set 64 clock interval
T1024T ds 1 ; $297 set 1024 clock interval
;-------------------------------------------------------------------------------
; The following required for back-compatibility with code which does not use
; segments.
SEG
; EOF

172
index.html Normal file
View File

@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>8bitworkshop IDE</title>
<style type="text/css" media="screen">
body {
overflow: hidden;
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
}
.dbg_info {
font-size: 0.8em;
}
#controls_top {
padding: 0.5em;
}
.gutter-offset {
width: 3em;
}
.gutter-bytes {
width: 6em;
}
.gutter-clock {
width: 0.5em;
}
.gutter-info {
width: 1em;
}
.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
/* Position the tooltip */
position: absolute;
z-index: 10;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
#notebook {
height: 100%;
width: 100%;
}
div.editor {
position:absolute;
left:0;
top:0;
bottom:0;
right:0;
width:50%;
height:100vh;
background-color:#999;
}
div.emulator {
position:absolute;
left:50%;
top:0;
width:50%;
height:100vh;
overflow-y: scroll;
background-color: #666;
}
div.mem_info {
position: absolute;
bottom: 10px;
background-color: #333;
color: #66ff66;
left: 20px;
white-space: pre;
padding: 20px;
z-index: 2;
}
span.hilite {
color: #ff66ff;
}
div.has-errors {
background-color: #ff6666 !important;
}
div.menu_div {
position: absolute;
width: 200px;
}
div.booklink {
position: fixed;
bottom: 0;
right: 0;
padding: 6px;
background-color: #ffffff;
}
a {
color:#333399;
font-weight: bold;
}
</style>
</head>
<body>
<div id="notebook">
<div id="editor" class="editor">
<div id="controls_top">
<!-- <a id="file_new" href="#" title="New File">+</a> -->
<!-- <a id="preset_prev" href="#">&lt;&lt;</a> -->
<button id="btn_share" title="Share File"><img src="images/share.png"></button>
<select id="preset_select" name="">
</select>
<button id="dbg_pause" type="button" title="Pause"><img src="images/pause.png"></button>
<button id="dbg_go" type="button" title="Run"><img src="images/play.png"></button>
<button id="dbg_toline" type="submit" title="Run To Line"><img src="images/runtoline.png"></button>
<button id="dbg_step" type="submit" title="Step"><img src="images/singlestep.png"></button>
<button id="dbg_reset" type="submit" title="Reset and Run To Line"><img src="images/resetandrun.png"></button>
<button id="dbg_timing" type="submit" title="See Timing"><img src="images/timing.png"></button>
<span class="dbg_info" id="dbg_info"></span>
</div>
</div>
<div style="margin-top: 30px auto 0; min-height: 594px;" class="emulator">
<div id="javatari-screen" style="margin: 0 auto; box-shadow: 2px 2px 10px rgb(60, 60, 60);"></div>
<div id="javatari-console-panel" style="margin: 0 auto; box-shadow: 2px 2px 10px rgb(60, 60, 60);"></div>
<div id="mem_info" class="mem_info" style="display:none">
</div>
</div>
</div>
<div class="booklink">
<!--
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0"
src="http://ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=pzp-20&marketplace=amazon&region=US&placement=B01N4DSRIZ&asins=B01N4DSRIZ&linkId=67b114b83ce0b13ceaf715ee86833626&show_border=true&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=7d6d6d">
</iframe>
-->
<img src="https://images-na.ssl-images-amazon.com/images/I/5153Bd8oWeL._AC_AC_SR98,95_.jpg" style="float:right"/>
Want to learn more?<br>
Get the new book<br>
<a target="_new" href="https://www.amazon.com/gp/product/B01N4DSRIZ/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&tag=pzp-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=B01N4DSRIZ&linkId=04d39e274c06e6c93b93d20a9a977111">
Making Games For The Atari 2600</a>
</div>
<script src="jquery/jquery-2.2.3.min.js"></script>
<script src="codemirror/lib/codemirror.js"></script>
<script src="codemirror/mode/6502/6502.js"></script>
<link rel="stylesheet" href="css/codemirror.css">
<link rel="stylesheet" href="codemirror/theme/mbo.css">
<script src="codemirror/addon/search/search.js"></script>
<script src="codemirror/addon/search/searchcursor.js"></script>
<script src="codemirror/addon/search/jumpToLine.js"></script>
<script src="codemirror/addon/dialog/dialog.js"></script>
<link rel="stylesheet" href="codemirror/addon/dialog/dialog.css">
<script src="javatari.js/release/javatari/javatari.js"></script>
<script src="src/jsdiff.js"></script>
<script src="src/ui.js"></script>
</body>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-54497476-9', 'auto');
ga('send', 'pageview');
</script>
</html>

1
javatari Symbolic link
View File

@ -0,0 +1 @@
javatari.js/release/javatari

4
jquery/jquery-2.2.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

10
package.json Normal file
View File

@ -0,0 +1,10 @@
{
"name": "RetroConsole",
"version": "0.0.1",
"author": "Steven Hugg",
"dependencies": {
},
"devDependencies": {
"browserify": "^13.0.0"
}
}

View File

@ -0,0 +1,247 @@
processor 6502
include "vcs.h"
include "macro.h"
org $f000
; Now, we'll finally put everything together and
; make a little person with a hat that can move
; back and forth and throw rocks. We'll use one player
; to generate a 8x16 sprite and one missile.
;
; We have two tables for the sprite, a bitmap table and
; a color table. We'll lookup from both of these tables on
; each scanline.
;
; Because VCS programming is all about thrift, we'll
; reuse the machine code for our program as the backdrop
; for the playfield, just to show we can fit it all on
; a scanline.
;
; Note: the Y coordinate goes bottom-top, not top-bottom
; as in the next section.
counter equ $81 ; increments each frame
yplyr equ $82 ; player y pos
yrock equ $83 ; rock y pos
animofs equ $84 ; sprite data offset, used for animation
; Initialize and set initial offsets of objects.
start CLEAN_START
lda #5
sta yplyr
lda #70
ldx #0
jsr SetHorizPos ; set player 0 horiz. pos
sta WSYNC
sta HMOVE
; Next frame loop
nextframe
VERTICAL_SYNC
; in case the rock is on screen
lda ColorFrame0 ; load 1st entry of color data
sta COLUP0 ; set sprite 0 color
; 37 lines of VBLANK
ldx #37
lvblank sta WSYNC
dex
bne lvblank
; set some colors
lda #$80
sta COLUBK ; set the background color
lda #1
sta CTRLPF ; playfield reflection
; Draw 192 scanlines with our sprite/missile kernel
ldx #192
lvscan
; Draw sprite?
txa
sec ; make sure carry is set (meaning no borrow for subtraction)
sbc yplyr ; get offset into sprite for this line
cmp #SpriteHeight ; sprite is 16 pixels high + padding on either side
bcc insprite
lda #SpriteHeight-1 ; draw the padding
insprite
tay
lda ColorFrame0,y ; load color data
pha ; push color data onto stack
clc ; clear carry flag
adc animofs ; add animation offset (not for color though)
lda Frame0,y ; load bitmap data
sta WSYNC ; wait for next scanline (as late as possible!)
sta GRP0 ; set sprite 0 pixels
pla ; pull bitmap data from stack
sta COLUP0 ; set sprite 0 color
lda start,x ; store some random garbage into the playfield
sta PF0
sta PF1
sta PF2
; Draw rock? Player 0 and missile 0 share a color register,
; so they will have the same colors on the same scanline
lda #%00000000
cpx yrock
bne norock
lda #%00000010 ; for ENAM0 the 2nd bit is enable
norock
sta ENAM0 ; enable missile 0
dex
bne lvscan ; repeat next scanline until finished
; End of scanline loop
; Clear all colors to black before overscan
stx COLUBK
stx COLUP0
stx COLUP1
stx COLUPF
; 30 lines of overscan
ldx #29
lvover sta WSYNC
dex
bne lvover
; Joystick movement
; For up and down, we INC or DEC the Y Position
lda #%00010000 ;Up?
bit SWCHA
bne SkipMoveUp
inc yplyr
SkipMoveUp
lda #%00100000 ;Down?
bit SWCHA
bne SkipMoveDown
dec yplyr
SkipMoveDown
; Note that the horizontal position is not contained in RAM,
; but inaccessibly inside the TIA's registers! Some games can
; get away with this if they use the collision registers.
ldx #0 ; assume speed is 0 if no movement
lda #%01000000 ;Left?
bit SWCHA
bne SkipMoveLeft
ldx #$10 ;a 1 in the left nibble means go left
SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne SkipMoveRight
ldx #$F0 ;a -1 in the left nibble means go right...
SkipMoveRight
stx HMP0 ;set the move for plyr 0
sta WSYNC
sta HMOVE
; Throw a rock when fire button pressed
lda #0
sta animofs
ldy #%00000000 ; use later to enable/disable missile lock on player
lda INPT4 ;read button input
bmi ButtonNotPressed ;skip if button not pressed
lda yplyr
sta yrock ; set rock vert pos to player's vert
lda #SpriteHeight
sta animofs ; while button pressed, use alternate player bitmap
ldy #%00000010 ; for RESMP0 the 2nd bit is the enable bit
ButtonNotPressed
sty RESMP0 ; reset missile 0 to player 0's horiz position
inc yrock ; rock moves no matter what (actually a boomerang)
; Goto next frame
jmp nextframe
; SetHorizPos - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; This routine does a WSYNC and HMOVE before executing,
; so whatever you do here will not take effect until you
; call the routine again or do your own WSYNC and HMOVE.
SetHorizPos
sta WSYNC ; start a new line
sta HMOVE ; apply the previous fine position(s)
sta HMCLR ; reset the old horizontal position(s)
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
rts ; return to caller
; Height of our sprite in lines
SpriteHeight equ 18
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF ; rock color if not sharing line with player sprite
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Epilogue
org $fffc
.word start
.word start

View File

@ -0,0 +1,98 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg.u Variables
org $80
Temp .byte
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Macro that implements Bank Switching trampoline
; X = bank number
; A = hi byte of destination PC
; Y = lo byte of destination PC
MAC BANK_SWITCH_TRAMPOLINE
pha ; push hi byte
tya ; Y -> A
pha ; push lo byte
lda $1FF8,x ; do the bank switch
rts ; return to target
ENDM
; Macro that performs bank switch
MAC BANK_SWITCH
.Bank SET {1}
.Addr SET {2}
lda #>(.Addr-1)
ldy #<(.Addr-1)
ldx #.Bank
jmp BankSwitch
ENDM
seg Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; BANK 0
org $1000
rorg $F000
;----The following code is the same on both banks----
Start
; Ensure that bank 0 is selected
lda #>(Reset_0-1)
ldy #<(Reset_0-1)
ldx #0
BankSwitch
BANK_SWITCH_TRAMPOLINE
;----End of bank-identical code----
Reset_0
CLEAN_START
lda #$30
sta COLUBK ; make the screen red
bit INPT4 ; test button
bmi Reset_0 ; button not pressed, repeat
; Switch to Bank 2 routine
lda #>(Main_1-1)
ldy #<(Main_1-1)
ldx #1
jmp BankSwitch
; Bank 0 epilogue
org $1FFA
rorg $FFFA
.word Start ; NMI
.word Start ; RESET
.word Start ; BRK
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; BANK 1
org $2000
rorg $F000
;----The following code is the same on both banks----
Start
; Ensure that bank 0 is selected
lda #>(Reset_0-1)
ldy #<(Reset_0-1)
ldx #0
BankSwitch
BANK_SWITCH_TRAMPOLINE
;----End of bank-identical code----
Main_1
inc Temp
lda Temp
sta COLUBK ; make rainbows
bit INPT4 ; test button
bpl Main_1 ; button is pressed, repeat
BANK_SWITCH 0,Reset_0
; Bank 1 epilogue
org $2FFA
rorg $FFFA
.word Start ; NMI
.word Start ; RESET
.word Start ; BRK

View File

@ -0,0 +1,125 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
Temp byte
LoopCount byte
THREE_COPIES equ %011
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
lda #64
sta LoopCount ; scanline counter
lda #$ff
sta COLUBK ; background color
lda #$22
sta COLUP0 ; show how players alternate
lda #$12
sta COLUP1 ; by having different colors
lda #THREE_COPIES
sta NUSIZ0
sta NUSIZ1 ; both players have 3 copies
sta WSYNC
SLEEP 20
sta RESP0 ; position 1st player
sta RESP1 ; ...and 2nd player
lda #$10
sta HMP1 ; 1 pixel to the left
sta WSYNC
sta HMOVE ; apply HMOVE
sta HMCLR
lda #1
sta VDELP0 ; we need the VDEL registers
sta VDELP1 ; so we can do our 4-store trick
TIMER_WAIT
TIMER_SETUP 192
SLEEP 40 ; start near end of scanline
BigLoop
ldy LoopCount ; counts backwards
lda Bitmap0,y ; load B0 (1st sprite byte)
sta GRP0 ; B0 -> [GRP0]
lda Bitmap1,y ; load B1 -> A
sta GRP1 ; B1 -> [GRP1], B0 -> GRP0
sta WSYNC ; sync to next scanline
lda Bitmap2,y ; load B2 -> A
sta GRP0 ; B2 -> [GRP0], B1 -> GRP1
lda Bitmap5,y ; load B5 -> A
sta Temp ; B5 -> temp
ldx Bitmap4,y ; load B4 -> X
lda Bitmap3,y ; load B3 -> A
ldy Temp ; load B5 -> Y
sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0
stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1
sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0
sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1
dec LoopCount ; go to next line
bpl BigLoop ; repeat until < 0
TIMER_WAIT
TIMER_SETUP 30
TIMER_WAIT
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Bitmap data, six columns
align $100 ; ensure we start on a page boundary
Bitmap0
hex 00
hex 00000000000000000000000000000000
hex 00000000000000000000000000000001
hex 01010205040402040404040404040404
hex 040404060203000107df7f1f0f000000
Bitmap1
hex 00
hex 0000073f1f0303000000000000010704
hex 0808101010101412120a0a1e75c38000
hex 0000076260e0e0e0c0c0e0e0c0c00000
hex 00000000408170feffffffffc7030000
Bitmap2
hex 00
hex 007ffffcf0e0c0404040404020bb7608
hex 000402020809094f494949fa07010000
hex 00000000006070f0f0f0c0e0e0e0e0e0
hex 400000000010a06010e0e0f1ffff7f00
Bitmap3
hex 00
hex 3effff07010000000000302159878184
hex 848efeffffff9f0f0e9c7c0402c12010
hex 08040207050000000002020104070f0f
hex 0f070703030707070f1f7efcf8f0c000
Bitmap4
hex 00
hex 00f0f0fffff0404040404080641e02c3
hex 4242c3e2e2f4fcfc787838787cfa3131
hex 6102021cf840c04042a022c14080c4c3
hex c0c0c0c0808090900010180c04243800
Bitmap5
hex 00
hex 000000fcfefc1c0c040402020202040c
hex f800e01804040402020101010111e192
hex dc700000000000000000000080808000
hex 00000000000000000000000000000000
; Epilogue
org $fffc
.word Start
.word Start

154
presets/examples/bitmap.a Normal file
View File

@ -0,0 +1,154 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
; Set playfield foreground and background
lda #$FF
sta COLUBK
lda #$F0
sta COLUPF
TIMER_WAIT
ldy #192
ScanLoop
; WSYNC and store playfield registers
sta WSYNC
lda PFBitmap0,y
sta PF0 ; store first playfield byte
lda PFBitmap1,y
sta PF1 ; store 2nd byte
lda PFBitmap2,y
sta PF2 ; store 3rd byte
; Here's the asymmetric part -- by this time the TIA clock
; is far enough that we can rewrite the same PF registers
; and display new data on the right side of the screen
nop
nop
nop ; pause to let playfield finish drawing
lda PFBitmap3,y
sta PF0 ; store 4th byte
lda PFBitmap4,y
sta PF1 ; store 5th byte
lda PFBitmap5,y
sta PF2 ; store 6th byte
dey
bne ScanLoop ; repeat until all scanlines drawn
; Reset playfield
SLEEP 14 ; give time to finish drawing scanline
lda #0
sta PF0
sta PF1
sta PF2 ; clear playfield
TIMER_SETUP 29
TIMER_WAIT
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BITMAP DATA - Ada Lovelace
PFBitmap0
hex 00
hex a070d0b0d0b0e050f0d0b0f050f060b0
hex f0d070b060f050f0a0f050f050f0d070
hex d0b0d0b0d0b050f050f0a0f0a0f060b0
hex e0b0e0d0f0d0b0f0a070d070d0b0e0b0
hex 60f0a0f050f0b0d0f0d0f0d0f0d0f0f0
hex e0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0
hex f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0
hex f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0
hex f0f0e0f0f0f0e0f0f0f0f0d0f0f0e0f0
hex d0f0b0f070b0f0d0f0d0b0d0f0d0b0d0
hex f0d0b0f0a0f0e0b0e050f0d0b070a0f0
hex b0b0e0b070d070f050f0b0f060f0b0f0
PFBitmap1
hex 00
hex fedf7fdf7fdfbeffde7ffe5fff5fffaf
hex 77b7dfafff5fef7faf77ef5ff7ae7fae
hex f75eef7aaff6dfb7de775ff65ff6affe
hex aeff5efe5efebe7efefefffefe7efefe
hex fefefefefefefefefdfefefefefefefe
hex fcfef6fefcfefefefef6fef6fef6eef7
hex eefeeffffffffeffbfffffdfffdeffdf
hex fefefffeff7fffffffbfffbfbfbfbe9f
hex bfbfffffffffffffffffffffffffffff
hex fffffffffbffbdf75ff55ff56ff55ff5
hex 5ff55ff55ff55ef75af7de75dff5de77
hex de75dff5dffbaefbaffb6edbbeef55ff
PFBitmap2
hex 00
hex 375f555f2f55975b4729430103000101
hex 00010000010000000000000001000108
hex 05040a0409040414242834041c040e06
hex 04020200010401040406140a1a0c0a05
hex 04010001000001000000000000000000
hex 00000000000000000000000081020104
hex 050e060d8e0d0f0f070605800009060e
hex 85060404040406070303030000000000
hex 00000000000000000000000100010001
hex 01010101010101030303030703030706
hex 070d0f0a1b1307050b9ff5f7f7e5cf8d
hex 1f153e7bf6fbfdd7fab7bdf7adffaaff
PFBitmap3
hex 00
hex f0f0f0f0e0f0e0e0d0a0d0a0d0c0d0c0
hex c0a08080408000808080808000000000
hex 00000000000080008000800080808000
hex 80808000808080008000800080000000
hex 00000000000000000000000000000000
hex 0000000080c0104040e0c0c0e0c0e0c0
hex 70e060a000804090706050200090a0c0
hex e0706030000000000000000000000000
hex 00000000000000000000000000000000
hex 00000000000000000000000000008080
hex 80400000808080c0e0f0f0f0f0f07030
hex 00000080f0f0f0f0f0f0f0f0f0f0e0f0
PFBitmap4
hex 00
hex fffffffffbfffbfffdfffffefffffeff
hex ffffffffffffffffffffffffffffff7f
hex ffffffffffffffffffffffffffffffff
hex ffffffffffffffffffffffffffffffff
hex ffffffffffffff7fff7fff7f7f7f7f7f
hex 7f7f7f7f7f3d7fbfbfffbfbf3fbf3d1f
hex 3f1f3f1f3f1f1f3f1f5f1f9f1f9f1f1f
hex 1f1f1f0f1f0f1f1f1f1f3f1f3f1f3f3f
hex 3f3f3f3e3e3f3f3f7e3c7c3c7d3c7c38
hex 7879797872787270f262e0e3c3cb9b39
hex 327a79fafafaf0e1e1e0c185cd0c9d3c
hex 3efeffffffffffffffffffffffffffff
PFBitmap5
hex 00
hex ffffffffffffffffffffffffffffffff
hex fffffeffffffffffffffffffffffffff
hex fffffffffbffffffffffffffffffffff
hex ffffffffffffffffffffffffffffffff
hex ffffffffffffffffffffff7fffffffff
hex ffffffffffffffffffff7fffffffffff
hex ffffffffffffffffffffffffffffffff
hex ffffffffffffffffffbf3f3fbf1fbf9f
hex bf9efb8ddf9098c1c0c8c1d0b080b080
hex 9080918ccac4c2c2c2e0c2c2d2808190
hex 80a0e0f0e0f0f1e2e1c1c0c0e2c1e0e1
hex e9f9fafafcf8fcfeffffffffffffffff
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,737 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
; We've got collisions working, but now we'd like some more
; interaction. We can make a little "breakout" style game
; where the ball knocks out rows of bricks. We'll need to
; draw several rows of bricks, any or all of which might be
; missing.
; We'll use a technique called "asychronous playfields".
; Remember that the playfield is either symmetric (20 pixels
; followed by the same 20 pixels reversed) or repeated (20 pixels
; repeated twice). But if we change the playfield registers
; *during* the scanline, we can make the second half a
; different bitmap than the first half.
; We're going to move away from the HMPx/HMOVE method of
; setting object position and use the SetHorizPos method, since
; we really need to know the X position of both player and ball
; at all times. The way the subroutine is written, this takes
; two scanlines per object. But we do it during the overscan
; period at the end of the frame, and we've got the cycles
; to spare.
; Also, we're going to keep score and have a rudimentary
; scoreboard, which makes this sort of an actual game!
; Fun fact: Messing with the HMOVE register causes a "comb"
; effect on the left 8 pixels of the screen, which can be seen
; at the bottom of the screen in the overscan region.
seg.u Variables
org $80
XPlyr byte ; player x pos
YPlyr byte ; player y pos
XBall byte ; ball x pos
YBall byte ; ball y pos
SpritePtr word ; sprite pointer
YSprOfs byte ; temp sprite offset
YBallVel byte ; ball Y velocity
XBallVel byte ; ball X velocity
XBallErr byte ; ball X fractional error
Captured byte ; ball capture flag
AVol0 byte ; shadow register for AVOL0
Score byte ; current BCD-encoded score
Temp byte ; temporary storage
Bricks ds 36 ; brick bitmap (6x6 bytes)
ScoreHeight equ 20 ; height of top scoreboard
BrickYStart equ 32 ; starting Y coordinate of bricks
BrickHeight equ 16 ; height of each brick in pixels
NBrickRows equ 6 ; number of lines of bricks
NBL equ NBrickRows ; abbreviation for number of brick rows
BytesPerRow equ 6 ; number of bytes for each row of bricks
BricksPerRow equ 40 ; number of bricks in each row
; (two bytes have only 4 active pixels)
; Color constants
BGCOLOR equ #$80
PLCOLOR equ #$6c
GNDCOLOR equ #$c0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Enable ball if it is on this scanline (in X register)
; Modifies A.
; Takes 13 cycles if ball is present, 12 if absent.
MAC DRAW_BALL
lda #%00000000
cpx YBall
bne .noball
lda #%00000010 ; for ENAM0 the 2nd bit is enable
.noball
sta ENABL ; enable ball
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
; Initialize and set initial offsets of objects.
Start CLEAN_START
; Set player 0 vertical position
lda #185-SpriteHeight
sta YPlyr ; player Y position, top to bottom
; Set player 0 horizontal position
lda #70
sta XPlyr
ldx #0
jsr SetHorizPos2
; Set ball horizontal position
lda #0
sta XBall
ldx #4
jsr SetHorizPos2
sta WSYNC
sta HMOVE
; Set ball initial velocity
lda #1
sta YBallVel
lda #129
sta YBall
lda #$40
sta XBallVel
; Set up initial bricks
ldx #0
lda #$ff
SetupBricks
;txa ; uncomment for a sparse brick pattern
sta Bricks,x
inx
cpx #BytesPerRow*NBrickRows
bne SetupBricks
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Next frame loop
NextFrame
VERTICAL_SYNC
; in case the ball is on screen
lda ColorFrame0 ; load 1st entry of color data
sta COLUP0 ; set sprite 0 color
; Set up playfield
lda #BGCOLOR ; set the background color
sta COLUBK
lda #PLCOLOR ; set the playfield color
sta COLUPF
lda #%00010101 ; playfield reflection and ball size/priority
sta CTRLPF
lda #0 ; blank out the playfield
sta PF0
sta PF1
sta PF2
; 37 lines of VBLANK
TIMER_SETUP 37
; Set up sprite pointer depending on button press
lda #<Frame0
sta SpritePtr
lda #>Frame0
sta SpritePtr+1 ; normal sprite bitmap
lda INPT4 ;read button input
bmi ButtonNotPressed2 ;skip if button not pressed
lda #<Frame1
sta SpritePtr
lda #>Frame1
sta SpritePtr+1 ; alternate sprite bitmap
ButtonNotPressed2
TIMER_WAIT
; Draw 192 scanlines.
ldx #0 ; X will contain the frame Y coordinate
; First, we'll draw the scoreboard.
; Put the playfield into score mode (bit 2) which gives
; two different colors for the left/right side of
; the playfield (given by COLUP0 and COLUP1).
lda #%00010010 ; score mode + 2 pixel ball
sta CTRLPF
lda #$48
sta COLUP0 ; set color for left
lda #$a8
sta COLUP1 ; set color for right
; We need to extract each digit of the BCD-coded score
; (there are two digits, each 4 bits) and find the appropriate
; entry in the DigitsBitmap bitmap table.
; We'll just draw one digit to keep it simple.
ScanLoop1a
clc ; clear carry flag
; Digits are 5 pixels high, so we need to multiply each
; digit by 5 to find our digit in the bitmap table.
lda Score ; grab the BCD score
and #$0F ; mask out the least significant digit
sta Temp
asl
asl
adc Temp
sta Temp ; tmp = score * 5
; Now we divide our current Y coordinate by 2
; to get the index into the digit bitmap.
txa
ror ; A = Ycoord / 2
adc Temp ; A += tmp
tay
lda DigitsBitmap,y ; A = DigitsBitmap[offset]
and #$0F ; mask out the rightmost digit
sta WSYNC
sta PF1 ; store digit to playfield 1 register
DRAW_BALL ; draw the ball on this line?
; (only for collision purposes)
inx
cpx #10 ; digits are 5 pixels high * 2 lines per pixel
bcc ScanLoop1a
; Clear the playfield
lda #0
sta PF1
; Turn playfield reflection off, since our brick field
; will be drawn asymetrically (and turn score mode off)
lda #%00010100 ; no reflection + ball priority + 2 pixel ball
sta CTRLPF
; Continue until the bricks start on line 32.
ScanLoop1b
sta WSYNC
DRAW_BALL ; draw the ball on this line?
; (only for collision purposes)
inx
cpx #BrickYStart
bne ScanLoop1b
; Next we'll draw the brick field, which is asymmetrical.
; We use two loops: the inner loop draws a row of bricks
; and the outer loop sets up for the next row.
; Timing is very important here! Note that we skip
; drawing the ball if it falls on a line after we start a
; new row. This will cause a little flicker but it is not
; very noticable.
SLEEP 44 ; make sure we start near the end of scanline
ldy #$ff ; start with row = -1
ScanLoop3b
iny ; go to next brick row
lda #BrickHeight ; for the outer loop, we count
sta Temp ; 'brickheight' scan lines for each row
cpy #NBrickRows ; done drawing all brick rows?
bcc ScanSkipSync ; no -- but don't have time to draw ball!
jmp DoneBrickDraw ; exit outer loop
ScanLoop3a
; These instructions are skipped on lines after the brick row changes.
; We need the extra cycles.
DRAW_BALL ; draw the ball on this line?
ScanSkipSync
sta WSYNC
< stx COLUPF ; change colors for bricks
; Load the first byte of bricks
; Bricks are stored in six contiguous arrays (row-major)
lda Bricks+NBL*0,y
sta PF0 ; store first playfield byte
; Store the next two bytes
lda Bricks+NBL*1,y
sta PF1
lda Bricks+NBL*2,y
sta PF2
; Here's the asymmetric part -- by this time the TIA clock
; is far enough that we can rewrite the same PF registers
; and display new data on the right side of the screen
inx ; good place for INX b/c of timing
nop ; yet more timing
lda Bricks+NBL*3,y
sta PF0
lda Bricks+NBL*4,y
sta PF1
lda Bricks+NBL*5,y
sta PF2
dec Temp
beq ScanLoop3b ; all lines in current brick row done?
bne ScanLoop3a ; branch always taken
; Clear playfield from bricks loop
DoneBrickDraw
SLEEP 6
lda #0
sta PF0
sta PF1
sta PF2
; Draw bottom half of screen with player sprite.
; Setup 'ysprofs' which is the calculated offset into
; sprite lookup tables (it can exceed bounds, we'll test)
; Since the sprite table is reversed, the starting offset is
; Yplyr - Ystart - SpriteHeight
lda YPlyr
sec
sbc #128-SpriteHeight
sta YSprOfs
ScanLoop4
; Is this scanline within sprite bounds?
dec YSprOfs
lda YSprOfs
cmp #SpriteHeight ; sprite is 16 pixels high + padding
bcc InSprite
lda #0 ; no sprite, draw the padding
InSprite
tay
lda ColorFrame0,y ; load color data
pha ; push color data onto stack
lda (SpritePtr),y ; load bitmap data
sta WSYNC ; wait for next scanline (as late as possible!)
sta GRP0 ; set sprite 0 pixels
pla ; pull bitmap data from stack
sta COLUP0 ; set sprite 0 color
DRAW_BALL ; draw the ball on this line?
inx
cpx #184
bne ScanLoop4 ; repeat next scanline until finished
; 8 more pixels for bottom border, and then we'll just leave it
; on for the overscan region.
ldy #$c8 ; set the playfield color
ScanLoop5
dey ; make a nice gradient
lda #$ff
sta WSYNC
sty COLUPF ; set the playfield color
sta PF0
sta PF1
sta PF2
lda #0
sta GRP0
inx
cpx #192
bne ScanLoop5
; Disable ball
lda #0
sta ENABL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 30 lines of overscan needed, but we have lots of logic to do.
; So we're going to use the PIA timer to let us know when
; almost 30 lines of overscan have passed.
; This handy macro does a WSYNC and then sets up the timer.
TIMER_SETUP 30
; Check for collisions
lda #%01000000
bit CXP0FB ; collision between player 0 and ball?
bne PlayerCollision
lda #%10000000
bit CXBLPF ; collision between playfield and ball?
bne PlayfieldCollision
beq NoCollision
; Now we bounce the ball depending on where it is
PlayerCollision
; Is the button pressed? if so, just capture the ball
lda INPT4 ;read button input
bmi ButtonNotPressed ;skip if button not pressed
inc Captured ; set capture flag
bne NoCollision
ButtonNotPressed
lda #0
sta Captured ; clear capture flag
; See if we bounce off of top half or bottom half of player
; (yplyr + height/2 - yball)
ldx #1
lda YPlyr
clc
adc #SpriteHeight/2
sec
sbc YBall
bmi StoreVel ; bottom half, bounce down
ldx #$ff ; top half, bounce up
bne StoreVel
PlayfieldCollision
; Which brick do we break?
; try the one nearest to us
lda YBall
ldx XBall
jsr BreakBrick
bmi CollisionNoBrick ; return -1 = no brick found
; Did we hit the top or the bottom of a brick?
; If top, bounce up, otherwise down
ldx #$ff ; ball velocity = up
cmp #BrickHeight/2 ; top half of brick?
bcc BounceBallUp ; yofs < brickheight/2
ldx #1 ; ball velocity = down
BounceBallUp
; Go to BCD mode and increment the score.
; This treats 'score' as two decimal digits,
; one in each nibble, for ADC and SBC operations.
sed
lda Score
clc
adc #1
sta Score
cld
jmp StoreVel
CollisionNoBrick
; If bouncing off top of playfield, bounce down
ldx #1
lda YBall
bpl StoreVel
; Otherwise bounce up
ldx #$ff
StoreVel
; Store final velocity
stx YBallVel
; Make a little sound
txa
adc #45
sta AUDF0 ; frequency
lda #6
sta AVol0 ; shadow register for volume
NoCollision
; Clear collision registers for next frame
sta CXCLR
; Ball captured? if so, no motion
lda Captured
bne DoneMovement
; Move ball vertically
lda YBall
clc
adc YBallVel
bne NoBallHitTop
ldx #1
stx YBallVel
NoBallHitTop
sta YBall
; Move ball horizontally
lda XBallVel ; signed X velocity
bmi BallMoveLeft ; < 0? move left
clc
adc XBallErr
sta XBallErr ; XBallErr += XBallVel
bcc DoneMovement ; no wrap around? done
inc XBall ; XBall += 1
lda XBall
cmp #160 ; moved off right side?
bcc DoneMovement ; no, done
lda #0
sta XBall ; wrap around to left
beq DoneMovement ; always taken
BallMoveLeft
clc
adc XBallErr
sta XBallErr
bcs DoneMovement
dec XBall ; decrement xball
lda XBall
cmp #160
bcc DoneMovement
lda #159
sta XBall
DoneMovement
; Joystick player movement
; For up and down, we INC or DEC the Y Position
lda #%00010000 ;Up?
bit SWCHA
bne SkipMoveUp ; bit set? skip move
ldx YPlyr
cpx #129
bcc SkipMoveUp
dex
stx YPlyr
lda Captured ; captured? move the ball too
beq SkipMoveUp
dec YPlyr
SkipMoveUp
lda #%00100000 ;Down?
bit SWCHA
bne SkipMoveDown ; bit set? skip move
ldx YPlyr
cpx #185-SpriteHeight
bcs SkipMoveDown
inx
stx YPlyr
lda Captured ; captured? move the ball too
beq SkipMoveDown
inc YBall
SkipMoveDown
; Note that the horizontal position is not contained in RAM,
; but inaccessibly inside the TIA's registers! Some games can
; get away with this if they use the collision registers.
ldx #0 ; assume speed is 0 if no movement
; We'll test the left/right flags using a special feature of
; the BIT instruction, which sets the N and V flags to the
; 7th and 6th bit of the target.
bit SWCHA
bvs SkipMoveLeft ; V flag set?
lda XPlyr
beq SkipMoveLeft ; don't allow move left of screen
dec XPlyr
lda Captured
beq SkipMoveLeft
dec XBall ; if captured, also move the ball
SkipMoveLeft
bit SWCHA
bmi SkipMoveRight ; N flag set?
lda XPlyr
cmp #150
bcs SkipMoveRight ; don't allow move right of screen
inc XPlyr
lda Captured
beq SkipMoveRight
inc XBall ; if captured, also move the ball
SkipMoveRight
; Set ball position using SetHorizPos
lda XBall
ldx #4
jsr SetHorizPos2
sta WSYNC
sta HMOVE
; Set player position using SetHorizPos
lda XPlyr
ldx #0
jsr SetHorizPos2
sta WSYNC
sta HMOVE
; Play audio from shadow register?
ldx AVol0
beq NoAudio
dex ; decrement volume every frame
stx AUDV0 ; store in volume hardware register
stx AVol0 ; store in shadow register
lda #3
sta AUDC0 ; shift counter mode 3 for weird bounce sound
NoAudio
; Wait until our timer expires and then WSYNC, so then we'll have
; passed 30 scanlines. This handy macro does this.
TIMER_WAIT
; Goto next frame
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Subroutine to try to break a brick at a given X-Y coordinate.
; X contains the X coordinate.
; A contains the Y coordinate.
; On return, A = -1 if no brick was present,
; otherwise A = Y offset (0-brickheight-1) of brick hit.
BreakBrick
ldy #$ff
sec
sbc #BrickYStart ; subtract top Y of brick field
; Divide by brick height
DivideRowLoop
iny
sbc #BrickHeight
bcs DivideRowLoop ; loop until < 0
cpy #NBrickRows
bcs NoBrickFound
; Now that we have the line, get byte and bit offset for brick
clc
adc #BrickHeight
pha ; save the remainder to return as result
txa
clc
adc #3 ; adjust because SetHorizPos is off by a few pixels
lsr
lsr ; divide X coordinate by 4
tax ; transfer brick column to X
tya ; load brick row # in A
clc
adc PFOfsTable,x ; add offset
tay
lda PFMaskTable,x
eor #$ff
and Bricks,y
cmp Bricks,y ; was there a change?
beq NoBrickFound2 ; no, so return -1 as result
sta Bricks,y
pla ; return remainder as result
rts
NoBrickFound2
pla ; pull the remainder, but ignore it
NoBrickFound
lda #$FF ; return -1 as result
rts
; SetHorizPos2 - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; This routine does a WSYNC both before and after, followed by
; a HMOVE and HMCLR. So it takes two scanlines to complete.
SetHorizPos2
sta WSYNC ; start a new line
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta HMP0,x ; set fine offset
sta RESP0,x ; fix coarse position
sta WSYNC
sta HMOVE ; apply the previous fine position(s)
sta HMCLR ; reset the old horizontal position(s)
rts ; return to caller
; Height of our sprite in lines
SpriteHeight equ 17
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF ; ball color if not sharing line with player sprite
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Bitmap pattern for digits
DigitsBitmap .byte $0E ; | XXX | $F5C5 Leading zero is not drawn
.byte $0A ; | X X | $F5C6 because it's never used.
.byte $0A ; | X X | $F5C7
.byte $0A ; | X X | $F5C8
.byte $0E ; | XXX | $F5C9
.byte $22 ; | X X | $F5CA
.byte $22 ; | X X | $F5CB
.byte $22 ; | X X | $F5CC
.byte $22 ; | X X | $F5CD
.byte $22 ; | X X | $F5CE
.byte $EE ; |XXX XXX | $F5CF
.byte $22 ; | X X | $F5D0
.byte $EE ; |XXX XXX | $F5D1
.byte $88 ; |X X | $F5D2
.byte $EE ; |XXX XXX | $F5D3
.byte $EE ; |XXX XXX | $F5D4
.byte $22 ; | X X | $F5D5
.byte $66 ; | XX XX | $F5D6
.byte $22 ; | X X | $F5D7
.byte $EE ; |XXX XXX | $F5D8
.byte $AA ; |X X X X | $F5D9
.byte $AA ; |X X X X | $F5DA
.byte $EE ; |XXX XXX | $F5DB
.byte $22 ; | X X | $F5DC
.byte $22 ; | X X | $F5DD
.byte $EE ; |XXX XXX | $F5DE
.byte $88 ; |X X | $F5DF
.byte $EE ; |XXX XXX | $F5E0
.byte $22 ; | X X | $F5E1
.byte $EE ; |XXX XXX | $F5E2
.byte $EE ; |XXX XXX | $F5E3
.byte $88 ; |X X | $F5E4
.byte $EE ; |XXX XXX | $F5E5
.byte $AA ; |X X X X | $F5E6
.byte $EE ; |XXX XXX | $F5E7
.byte $EE ; |XXX XXX | $F5E8
.byte $22 ; | X X | $F5E9
.byte $22 ; | X X | $F5EA
.byte $22 ; | X X | $F5EB
.byte $22 ; | X X | $F5EC
.byte $EE ; |XXX XXX | $F5ED
.byte $AA ; |X X X X | $F5EE
.byte $EE ; |XXX XXX | $F5EF
.byte $AA ; |X X X X | $F5F0
.byte $EE ; |XXX XXX | $F5F1
.byte $EE ; |XXX XXX | $F5F2
.byte $AA ; |X X X X | $F5F3
.byte $EE ; |XXX XXX | $F5F4
.byte $22 ; | X X | $F5F5
.byte $EE ; |XXX XXX | $F5F6
; Playfield bitmasks for all 40 brick columns
PFMaskTable
REPEAT 2
.byte #$10,#$20,#$40,#$80
.byte #$80,#$40,#$20,#$10,#$08,#$04,#$02,#$01
.byte #$01,#$02,#$04,#$08,#$10,#$20,#$40,#$80
REPEND
; Brick array byte offsets for all 40 brick columns
PFOfsTable
.byte NBL*0,NBL*0,NBL*0,NBL*0
.byte NBL*1,NBL*1,NBL*1,NBL*1, NBL*1,NBL*1,NBL*1,NBL*1
.byte NBL*2,NBL*2,NBL*2,NBL*2, NBL*2,NBL*2,NBL*2,NBL*2
.byte NBL*3,NBL*3,NBL*3,NBL*3
.byte NBL*4,NBL*4,NBL*4,NBL*4, NBL*4,NBL*4,NBL*4,NBL*4
.byte NBL*5,NBL*5,NBL*5,NBL*5, NBL*5,NBL*5,NBL*5,NBL*5
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,476 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
; In this example, we're going to tackle collision detection,
; which is one thing in the VCS that's easier than expected.
; The TIA has 15 different collision flags that can detect a
; collision between any of the 2 players, 2 missiles, ball,
; and playfield. You can check these flags at any time (at the
; end of the frame is pretty common). When you're done checking
; you clear them all at once by writing to CXCLR.
; For this example we'll use the ball object, and detect collisions
; between it and the playfield and the player. We only know
; the Y position of the ball and player (the X position is in
; the TIA chip) so we'll base our bounce decisions on the Y position
; of the ball (for playfield bounces) or the relative Y position of
; ball and player (for player bounces).
; Note: You can press the button to capture the ball.
; We're going to also include sound, which is generated by writing
; a volume register, a frequency register, and a mode register for
; one of two channels.
counter byte ; increments each frame
yplyr byte ; player y pos
yball byte ; ball y pos
animofs byte ; sprite data offset, used for animation
ysprofs byte ; temp sprite offset
yballvel byte ; ball Y velocity
xballvel byte ; ball X velocity
xballerr byte ; ball X fractional error
captured byte ; ball capture flag
avol0 byte ; shadow register for AVOL0
; Color constants
BGCOLOR equ $80
PLCOLOR equ $6c
GNDCOLOR equ $c0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Enable ball if it is on this scanline (in X register)
; Modifies A.
MAC DRAW_BALL
lda #%00000000
cpx yball
bne .noball
lda #%00000010 ; for ENAM0 the 2nd bit is enable
.noball
sta ENABL ; enable ball
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
; Initialize and set initial offsets of objects.
Start CLEAN_START
lda #185-SpriteHeight
sta yplyr ; player Y position, top to bottom
; Set player 0 horizontal position
lda #80
ldx #0
jsr SetHorizPos2
; Set ball horizontal position
lda #84
ldx #4
jsr SetHorizPos2
sta WSYNC
sta HMOVE
; Set ball initial velocity
lda #1
sta yballvel
lda #$30
sta xballvel
; Next frame loop
NextFrame
VERTICAL_SYNC
; in case the ball is on screen
lda ColorFrame0 ; load 1st entry of color data
sta COLUP0 ; set sprite 0 color
; Set up playfield
lda #BGCOLOR ; set the background color
sta COLUBK
lda #PLCOLOR ; set the playfield color
sta COLUPF
lda #%00010101 ; playfield reflection and ball size/priority
sta CTRLPF
lda #0 ; blank out the playfield
sta PF0
sta PF1
sta PF2
; 37 lines of VBLANK
ldx #37
Underscan
sta WSYNC
dex
bne Underscan
; Draw 192 scanlines
; First, 20 lines at top of screen
ldx #0
ScanLoop1
sta WSYNC
stx PF1
inx
cpx #20
bne ScanLoop1
; Top border of screen (8 lines)
ScanLoop2
; Fetch 1st part of bitmap data from table (we start on 20th line)
lda #TopBorder0-20,x
sta WSYNC
sta PF0
DRAW_BALL ; draw the ball on this line?
; (only for collision purposes)
; Fetch 2nd and 3rd parts of bitmap
lda #TopBorder1-20,x
sta PF1
lda #TopBorder2-20,x
sta PF2
inx
cpx #28
bne ScanLoop2
; Top half of screen (100 pixels)
lda #0
sta PF0
sta PF1
sta PF2
ScanLoop3
sta WSYNC
DRAW_BALL ; draw the ball on this line?
inx
cpx #128
bne ScanLoop3
; Bottom half of screen with sprite
; Setup 'ysprofs' which is the calculated offset into
; sprite lookup tables (it can exceed bounds, we'll test)
; Since the sprite table is reversed, the starting offset is
; Yplyr - Ystart - SpriteHeight
lda yplyr
sec
sbc #128-SpriteHeight
sta ysprofs
ScanLoop4
; Is this scanline within sprite bounds?
dec ysprofs
lda ysprofs
cmp #SpriteHeight ; sprite is 16 pixels high + padding
bcc InSprite
lda #0 ; no sprite, draw the padding
InSprite
tay
lda ColorFrame0,y ; load color data
pha ; push color data onto stack
tya
clc ; clear carry flag
adc animofs ; add animation offset (not for color though)
tay
lda Frame0,y ; load bitmap data
sta WSYNC ; wait for next scanline (as late as possible!)
sta GRP0 ; set sprite 0 pixels
pla ; pull bitmap data from stack
sta COLUP0 ; set sprite 0 color
DRAW_BALL ; draw the ball on this line?
inx
cpx #184
bne ScanLoop4 ; repeat next scanline until finished
; 8 more pixels for bottom border
lda #GNDCOLOR ; set the background color
sta COLUPF
ScanLoop5
lda #$ff
sta WSYNC
sta PF0
sta PF1
sta PF2
lda #0
sta GRP0
inx
cpx #192
bne ScanLoop5
; Disable ball
lda #0
sta ENABL
; 30 lines of overscan needed, but we have lots of logic to do.
; So we're going to use the PIA timer to let us know when
; almost 30 lines of overscan have passed.
; This handy macro does a WSYNC and then sets up the timer.
TIMER_SETUP 30
; Check for collisions
lda #%01000000
bit CXP0FB ; collision between player 0 and ball?
bne PlayerCollision
lda #%10000000
bit CXBLPF ; collision between playfield and ball?
bne PlayfieldCollision
beq NoCollision
; Now we bounce the ball depending on where it is
PlayerCollision
; Is the button pressed? if so, just capture the ball
lda INPT4 ;read button input
bmi ButtonNotPressed ;skip if button not pressed
inc captured ; set capture flag
lda #SpriteHeight
sta animofs ; use different bitmap when captured
bne NoCollision
ButtonNotPressed
lda #0
sta captured ; clear capture flag
sta animofs ; use regular bitmap
; See if we bounce off of top half or bottom half of player
; (yplyr + height/2 - yball)
ldx #1
lda yplyr
clc
adc #SpriteHeight/2
sec
sbc yball
bmi StoreVel ; bottom half, bounce down
ldx #$ff ; top half, bounce up
bne StoreVel
PlayfieldCollision
; If bouncing off top of playfield, bounce down
ldx #1
lda yball
bpl StoreVel
; Otherwise bounce up
ldx #$ff
StoreVel
; Store final velocity
stx yballvel
; Make a little sound
txa
adc #45
sta AUDF0 ; frequency
lda #6
sta avol0 ; shadow register for volume
NoCollision
; Clear collision registers for next frame
sta CXCLR
; Ball captured? if so, no motion
lda captured
bne ballCaptured
; Move ball vertically
lda yball
clc
adc yballvel
sta yball
; Move ball horizontally
; We use an fractional counter for the ball, and we have to
; set a different HMOVE value depending on if it's left or right
lda xballvel
bmi ballMoveLeft
clc
adc xballerr
sta xballerr
bcc ballCaptured
lda #$f0
sta HMBL
bne ballCaptured
ballMoveLeft
sec
sbc xballerr
sta xballerr
bcs ballCaptured
lda #$10
sta HMBL
ballCaptured
sta WSYNC
sta HMOVE ; apply the move(s)
sta HMCLR
; Joystick movement
; For up and down, we INC or DEC the Y Position
lda #%00010000 ;Up?
bit SWCHA
bne SkipMoveUp
ldx yplyr
cpx #129
bcc SkipMoveUp
dex
stx yplyr
lda captured ; captured? move the ball too
beq SkipMoveUp
dec yball
SkipMoveUp
lda #%00100000 ;Down?
bit SWCHA
bne SkipMoveDown
ldx yplyr
cpx #185-SpriteHeight
bcs SkipMoveDown
inx
stx yplyr
lda captured ; captured? move the ball too
beq SkipMoveDown
inc yball
SkipMoveDown
; Note that the horizontal position is not contained in RAM,
; but inaccessibly inside the TIA's registers! Some games can
; get away with this if they use the collision registers.
ldx #0 ; assume speed is 0 if no movement
lda #%01000000 ;Left?
bit SWCHA
bne SkipMoveLeft
ldx #$10 ;a 1 in the left nibble means go left
SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne SkipMoveRight
ldx #$F0 ;a -1 in the left nibble means go right...
SkipMoveRight
stx HMP0 ; set the move for player 0
lda captured ; captured? move the ball too
beq NoCaptureMove
stx HMBL ; set ball move register
NoCaptureMove
sta WSYNC
sta HMOVE ; apply the move(s)
; Play audio from shadow register
ldx avol0
beq NoAudio
dex ; decrement volume every frame
stx AUDV0 ; store in volume hardware register
stx avol0 ; store in shadow register
lda #3
sta AUDC0 ; shift counter mode 3 for weird bounce sound
NoAudio
; Wait until our timer expires and then WSYNC, so then we'll have
; passed 30 scanlines. This handy macro does this.
TIMER_WAIT
; Goto next frame
jmp NextFrame
; SetHorizPos2 - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; This routine does a WSYNC both before and after, followed by
; a HMOVE and HMCLR. So it takes two scanlines to complete.
SetHorizPos2
sta WSYNC ; start a new line
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
sta WSYNC
sta HMOVE ; apply the previous fine position(s)
sta HMCLR ; reset the old horizontal position(s)
rts ; return to caller
; Height of our sprite in lines
SpriteHeight equ 17
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF ; ball color if not sharing line with player sprite
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Playfield top border bitmap
TopBorder0
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11110000
.byte #%11000000
.byte #%10000000
.byte #%00000000
TopBorder1
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%11111000
.byte #%11100000
.byte #%11000000
.byte #%10000000
TopBorder2
.byte #%11111111
.byte #%11111111
.byte #%11111111
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%00000000
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,267 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
PFPtr word ; pointer to playfield data
PFIndex byte ; offset into playfield array
PFCount byte ; lines left in this playfield segment
Temp byte ; temporary
YPos byte ; Y position of player sprite
XPos byte ; X position of player sprite
SpritePtr word ; pointer to sprite bitmap table
ColorPtr word ; pointer to sprite color table
; Temporary slots used during kernel
Bit2p0 byte
Colp0 byte
YP0 byte
; Height of sprite in scanlines
SpriteHeight equ 9
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
; Set up initial pointers and player position
lda #<PlayfieldData
sta PFPtr
lda #>PlayfieldData
sta PFPtr+1
lda #<Frame0
sta SpritePtr
lda #>Frame0
sta SpritePtr+1
lda #<ColorFrame0
sta ColorPtr
lda #>ColorFrame0
sta ColorPtr+1
lda #242
sta YPos
lda #38
sta XPos
NextFrame
VERTICAL_SYNC
; Set up VBLANK timer
TIMER_SETUP 37
lda #$88
sta COLUBK ; bg color
lda #$5b
sta COLUPF ; fg color
lda #$68
sta COLUP0 ; player color
lda #1
sta CTRLPF ; symmetry
lda #0
sta PFIndex ; reset playfield offset
; Set temporary Y counter and set horizontal position
lda YPos
sta YP0 ; yp0 = temporary counter
lda XPos
ldx #0
jsr SetHorizPos
sta WSYNC
sta HMOVE ; gotta apply HMOVE
; Wait for end of VBLANK
TIMER_WAIT
sta WSYNC
lda #0
sta VBLANK
; Set up timer (in case of bugs where we don't hit exactly)
TIMER_SETUP 192
SLEEP 10 ; to make timing analysis work out
NewPFSegment
; Load a new playfield segment.
; Defined by length and then the 3 PF registers.
; Length = 0 means stop
ldy PFIndex ; load index into PF array
lda (PFPtr),y ; load length of next segment
beq NoMoreSegs ; == 0, we're done
sta PFCount ; save for later
; Preload the PF0/PF1/PF2 registers for after WSYNC
iny
lda (PFPtr),y ; load PF0
tax ; PF0 -> X
iny
lda (PFPtr),y ; load PF1
sta Temp ; PF1 -> Temp
iny
lda (PFPtr),y ; load PF2
iny
sty PFIndex
tay ; PF2 -> Y
; WSYNC, then store playfield registers
; and also the player 0 bitmap for line 2
sta WSYNC
stx PF0 ; X -> PF0
lda Temp
sta PF1 ; Temp -> PF1
lda Bit2p0 ; player bitmap
sta GRP0 ; Bit2p0 -> GRP0
sty PF2 ; Y -> PF2
; Load playfield length, we'll keep this in X for the loop
ldx PFCount
KernelLoop
; Does this scanline intersect our sprite?
lda #SpriteHeight ; height in 2xlines
isb YP0 ; INC yp0, then SBC yp0
bcs .DoDraw ; inside bounds?
lda #0 ; no, load the padding offset (0)
.DoDraw
; Load color value for both lines, store in temp var
pha ; save original offset
tay ; -> Y
lda (ColorPtr),y ; color for both lines
sta Colp0 ; -> colp0
; Load bitmap value for each line, store in temp var
pla
asl ; offset * 2
tay ; -> Y
lda (SpritePtr),y ; bitmap for first line
sta Bit2p0 ; -> bit2p0
iny
lda (SpritePtr),y ; bitmap for second line
; WSYNC and store values for first line
sta WSYNC
sta GRP0 ; Bit1p0 -> GRP0
lda Colp0
sta COLUP0 ; Colp0 -> COLUP0
dex
beq NewPFSegment ; end of this playfield segment?
; WSYNC and store values for second line
sta WSYNC
lda Bit2p0
sta GRP0 ; Bit2p0 -> GRP0
jmp KernelLoop
NoMoreSegs
; Change colors so we can see when our loop ends
lda #0
sta COLUBK
; Wait for timer to finish
TIMER_WAIT
; Set up overscan timer
TIMER_SETUP 30
lda #2
sta VBLANK
jsr MoveJoystick
TIMER_WAIT
jmp NextFrame
SetHorizPos
sta WSYNC ; start a new line
bit 0 ; waste 3 cycles
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
rts ; return to caller
; Read joystick movement and apply to object 0
MoveJoystick
; Move vertically
; (up and down are actually reversed since ypos starts at bottom)
ldx YPos
lda #%00100000 ;Up?
bit SWCHA
bne SkipMoveUp
cpx #175
bcc SkipMoveUp
dex
SkipMoveUp
lda #%00010000 ;Down?
bit SWCHA
bne SkipMoveDown
cpx #254
bcs SkipMoveDown
inx
SkipMoveDown
stx YPos
; Move horizontally
ldx XPos
lda #%01000000 ;Left?
bit SWCHA
bne SkipMoveLeft
cpx #1
bcc SkipMoveLeft
dex
SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne SkipMoveRight
cpx #153
bcs SkipMoveRight
inx
SkipMoveRight
stx XPos
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align $100; make sure data doesn't cross page boundary
PlayfieldData
.byte 4,#%00000000,#%11111110,#%00110000
.byte 8,#%11000000,#%00000001,#%01001000
.byte 15,#%00100000,#%01111110,#%10000100
.byte 20,#%00010000,#%10000000,#%00010000
.byte 20,#%00010000,#%01100011,#%10011000
.byte 15,#%00100000,#%00001100,#%01000100
.byte 8,#%11000000,#%00110000,#%00110010
.byte 4,#%00000000,#%11000000,#%00001100
.byte 0
; Bitmap data "standing" position
Frame0
.byte #0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$F4;
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,286 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
PFPtr word ; pointer to playfield data
PFIndex byte ; offset into playfield array
PFCount byte ; lines left in this playfield segment
YPos0 byte ; Y position of player 0 sprite
XPos0 byte ; X position of player 0 sprite
YPos1 byte ; Y position of player 1 sprite
XPos1 byte ; X position of player 1 sprite
; pointers to bitmap and color tables
SpritePtr0 word
ColorPtr0 word
SpritePtr1 word
ColorPtr1 word
; temporary values for kernel
YP0 byte ; Y counter for player 0
YP1 byte ; Y counter for player 1
Temp byte
Bitp0 byte
Bitp1 byte
Colp0 byte
Colp1 byte
tmpPF0 byte
tmpPF1 byte
tmpPF2 byte
SpriteHeight equ 16
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
Data0
lda #<Data0
sta PFPtr
lda #>Data0
sta PFPtr+1
lda #<Frame0
sta SpritePtr0
lda #>Frame0
sta SpritePtr0+1
lda #<ColorFrame0
sta ColorPtr0
lda #>ColorFrame0
sta ColorPtr0+1
lda #<Frame0
sta SpritePtr1
lda #>Frame0
sta SpritePtr1+1
lda #<ColorFrame0
sta ColorPtr1
lda #>ColorFrame0
sta ColorPtr1+1
lda #242
sta YPos0
lda #200
sta YPos1
lda #38
sta XPos0
sta XPos1
lda #1
sta VDELP0 ; updates to GRP0 will be delayed
NextFrame
VERTICAL_SYNC
; Set up VBLANK timer
TIMER_SETUP 37
lda #$88
sta COLUBK ; bg color
lda #$5b
sta COLUPF ; fg color
lda #$68
sta COLUP0 ; player color
lda #1
sta CTRLPF ; symmetry
lda #72
sta PFIndex ; reset playfield offset
; Set temporary Y counter and set horizontal position
lda YPos0
sta YP0 ; yp0 = temporary counter
lda YPos1
sta YP1 ; yp0 = temporary counter
lda XPos0
ldx #0
jsr SetHorizPos
lda XPos1
ldx #1
jsr SetHorizPos
sta WSYNC
sta HMOVE ; gotta apply HMOVE
; Wait for end of VBLANK
TIMER_WAIT
sta WSYNC
lda #0
sta VBLANK
SLEEP 31 ; so timing analysis works out
KernelLoop
; Phase 0: Fetch PF0 byte
jsr DrawSprites
ldy PFIndex ; no more playfield?
beq NoMoreLines ; exit loop
dey
lda (PFPtr),y ; load PF0
sty PFIndex
sta tmpPF0
; Phase 1: Fetch PF1 byte
jsr DrawSprites
ldy PFIndex
dey
lda (PFPtr),y ; load PF1
sty PFIndex
sta tmpPF1
; Phase 2: Fetch PF2 byte
jsr DrawSprites
ldy PFIndex
dey
lda (PFPtr),y ; load PF2
sty PFIndex
sta tmpPF2
; Phase 3: Write PF0/PF1/PF2 registers
jsr DrawSprites
lda tmpPF0
sta PF0
lda tmpPF1
sta PF1
lda tmpPF2
sta PF2
; Go to next scanline
jmp KernelLoop
NoMoreLines
; Set up overscan timer
TIMER_SETUP 30
lda #2
sta VBLANK
jsr MoveJoystick
TIMER_WAIT
jmp NextFrame
; DrawSprite subroutine called by kernel
DrawSprites
; Fetch sprite 0 values
lda #SpriteHeight ; height in 2xlines
sec
isb YP0 ; INC yp0, then SBC yp0
bcs DoDraw0 ; inside bounds?
lda #0 ; no, load the padding offset (0)
DoDraw0
tay ; -> Y
lda (ColorPtr0),y ; color for both lines
sta Colp0 ; -> colp0
lda (SpritePtr0),y ; bitmap for first line
sta GRP0 ; -> [GRP0] (delayed due to VDEL)
; Fetch sprite 1 values
lda #SpriteHeight ; height in 2xlines
sec
isb YP1 ; INC yp0, then SBC yp0
bcs DoDraw1 ; inside bounds?
lda #0 ; no, load the padding offset (0)
DoDraw1
tay ; -> Y
lda (ColorPtr1),y ; color for both lines
tax
lda (SpritePtr1),y ; bitmap for first line
tay
; WSYNC and store sprite values
lda Colp0
sta WSYNC
sty GRP1 ; GRP0 is also updated due to VDELP0 flag
stx COLUP1
sta COLUP0
; Return to caller
rts
SetHorizPos
sta WSYNC ; start a new line
bit 0 ; waste 3 cycles
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
rts ; return to caller
; Read joystick movement and apply to object 0
MoveJoystick
; Move vertically
; (up and down are actually reversed since ypos starts at bottom)
ldx YPos0
lda #%00100000 ;Up?
bit SWCHA
bne SkipMoveUp
cpx #175
bcc SkipMoveUp
dex
SkipMoveUp
lda #%00010000 ;Down?
bit SWCHA
bne SkipMoveDown
cpx #254
bcs SkipMoveDown
inx
SkipMoveDown
stx YPos0
; Move horizontally
ldx XPos0
lda #%01000000 ;Left?
bit SWCHA
bne SkipMoveLeft
cpx #1
bcc SkipMoveLeft
dex
SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne SkipMoveRight
cpx #153
bcs SkipMoveRight
inx
SkipMoveRight
stx XPos0
rts
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF;
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Epilogue
org $fffc
.word Start
.word Start

395
presets/examples/fullgame.a Normal file
View File

@ -0,0 +1,395 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg.u Variables
org $80
Temp byte
PlyrXPos byte ; player X position
PlyrYPos byte ; player Y position
TrailFrac byte ; 8-bit fractional trail position
TrailPos word ; current 16-bit position along trail
Speed byte ; velocity along trail
Random word
PF1Mask byte
PF2Mask byte
YP0 byte ; sprite 0 vertical counter
YP1 byte ; sprite 1 vertical counter
SpritePtr0 word ; sprite 0 pointer
SpritePtr1 word ; sprite 1 pointer
YStop byte ; temp. register
NextHeight byte ; next sprite height
ObsIndex byte ; current obstacle index (0-7)
; Y offset into first obstacle
TrailObsYOffset byte
; next 8 bytes of trail data, starting from top of screen
TrailData ds 8
; Data for all 8 obstacle sprites
ObsXpos ds 8
ObsColor ds 8
ObsPtrLo ds 8
ObsPtrHi ds 8
SpriteHeight equ 20 ; Height of all sprites
TrailDisplayHeight equ 160 ; Height of trail dispaly area
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
lda #1
sta Random
lda #70
sta PlyrXPos
lda #70
sta ObsXpos+0
lda #$7f
sta ObsColor+0
lda #<Frame0
sta ObsPtrLo+0
lda #>Frame0
sta ObsPtrHi+0
lda #40
sta ObsXpos+1
lda #$68
sta ObsColor+1
lda #<Frame0
sta ObsPtrLo+1
lda #>Frame0
sta ObsPtrHi+1
lda #60
sta ObsXpos+2
lda #$28
sta ObsColor+2
lda #<Frame0
sta ObsPtrLo+2
lda #>Frame0
sta ObsPtrHi+2
lda #$40
sta Speed
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
lda #150
sta YP0
lda #$ff
sta PF1Mask
lda #$0f
sta PF2Mask
lda #<Frame0
sta SpritePtr0
lda #>Frame0
sta SpritePtr0+1
lda #$ff
sta COLUP0
lda PlyrXPos
ldx #0
jsr SetHorizPos
sta WSYNC
sta HMOVE
TIMER_WAIT
TIMER_SETUP 192
jsr DrawTrail
TIMER_WAIT
TIMER_SETUP 30
jsr MoveAlongTrail
TIMER_WAIT
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Move player along the trail
MoveAlongTrail subroutine
lda TrailFrac
sec
adc Speed
sta TrailFrac
php
lda TrailPos
adc #0
sta TrailPos
lda TrailPos+1
adc #0
sta TrailPos+1
plp
lda TrailObsYOffset
adc #0
cmp #SpriteHeight+4
bcc .NoNextObstacle
; Move obstacles up one slot
ldx #6
.MoveLoop
lda ObsXpos,x
sta ObsXpos+1,x
lda ObsColor,x
sta ObsColor+1,x
lda ObsPtrLo,x
sta ObsPtrLo+1,x
lda ObsPtrHi,x
sta ObsPtrHi+1,x
dex
bpl .MoveLoop
; Generate new obstacle
jsr NextRandom
sta ObsColor
and #$5f
sta ObsXpos
; Reset TrailObsYOffset
lda #0
.NoNextObstacle
sta TrailObsYOffset
rts
; Put the trail edges into the PF1 and PF2 registers.
; X = trail position lo byte
MAC TRAIL_PLAYFIELD
lda PF1Mask
and $f000,x
sta PF1
lda PF2Mask
and $f100,x
sta PF2
dex
ENDM
; Get next bitmap byte of sprite 0
MAC DRAW_SPRITE0
sec ; ensure carry set for subtraction
lda #SpriteHeight
isb YP0 ; INC yp0, then SBC yp0
tay
bcs .DoDraw ; outside bounds?
lda #0 ; clear bitmap
.byte $2c ; (BIT aaaa, skips next 2 bytes)
.DoDraw
lda (SpritePtr0),y ; lookup sprite bitmap
sta GRP0 ; A -> GRP0
ENDM
; Draw the trail
DrawTrail subroutine
lda TrailObsYOffset
sta NextHeight
lda #$c4
sta COLUPF
lda #$F0
sta PF0
lda #%0000001
sta CTRLPF
; lda #1
; sta VDELP0
; Set up ObsIndex (index of next obstacle to draw)
ldy #0
sty ObsIndex
; Set up X register with trail position lo byte,
; which displays the scrolling "forest" mask.
; We decrement this value every scanline and when it
; hits "YStop" we are done drawing.
lda TrailPos
sta YStop
clc
adc #TrailDisplayHeight
tax
; Set position of sprite
.SetSpritePos
sta HMCLR ; clear fine offsets
DRAW_SPRITE0
; Set up more values for sprite 1
ldy ObsIndex
lda ObsPtrLo,y
sta SpritePtr1
lda ObsPtrHi,y
sta SpritePtr1+1
lda ObsColor,y
sta COLUP1
; Update the playfield masks
; lda PFMask1,x
; sta PFMask1
; lda PFMask2,x
; sta PFMask2
; Set next height
lda NextHeight
sta YP1
lda #SpriteHeight
sta NextHeight
; Make sure grass texture is aligned
dex
cpx YStop
beq .NoMoreObjects
dex
cpx YStop
beq .NoMoreObjects
dex
cpx YStop
beq .NoMoreObjects
; Redraw sprite
DRAW_SPRITE0
; Position object horizontally
sta WSYNC
ldy ObsIndex
lda ObsXpos,y
iny
sty ObsIndex
sec
.DivideLoop
sbc #15 ; subtract 15
bcs .DivideLoop ; branch until negative
eor #7
asl
asl
asl
asl
sta HMP1 ; set fine offset
sta RESP1 ; fix coarse position
.Loop
sta WSYNC
; Draw sprite 0, with skipdraw
DRAW_SPRITE0
; Draw the playfield
TRAIL_PLAYFIELD
; Should we exit?
cpx YStop
beq .NoMoreObjects
; Draw sprite 1, no skipdraw
ldy YP1
lda (SpritePtr1),y
dey
sty YP1 ; YP1--
sta GRP1 ; A -> GRP1
; repeat until sprite 1 finished drawing (YP1<0)
bpl .Loop
; If not out of scanlines, position next sprite and continue
jmp .SetSpritePos
; Clean up playfield
.NoMoreObjects
lda #0
sta PF0
sta PF1
sta PF2
sta GRP0
sta GRP1
rts
; Random number generation
NextRandom subroutine
lda Random
lsr
ror Random+1
bcc .NoEor
eor #$d4
.NoEor:
sta Random
eor Random+1
rts
PrevRandom subroutine
lda Random
asl
rol Random+1
bcc .NoEor
eor #$a9
.NoEor:
sta Random
eor Random+1
rts
SetHorizPos
sta WSYNC ; start a new line
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta HMP0,x ; set fine offset
sta RESP0,x ; fix coarse position
rts ; return to caller
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align $100
PFMask1
byte #%00000000
byte #%10000000
byte #%11000000
byte #%11100000
byte #%11110000
byte #%11111000
byte #%11111100
byte #%11111110
byte #%11111111
byte #%11111111
byte #%11111111
byte #%11111111
byte #%11111111
byte #%11111111
byte #%11111111
byte #%11111111
byte #%11111111
PFMask2
byte #%00000000
byte #%00000000
byte #%00000000
byte #%00000000
byte #%00000000
byte #%00000000
byte #%00000000
byte #%00000000
byte #%00000001
byte #%00000011
byte #%00000111
byte #%00001111
byte #%00011111
byte #%00111111
byte #%01111111
byte #%11111111
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
.byte 0,0,0,0,0,0,0
HMoveTable
hex 7060504030201000f0e0d0c0b0a09080
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Epilogue
org $fffc
.word Start
.word Start

47
presets/examples/hello.a Normal file
View File

@ -0,0 +1,47 @@
; Assembler should use basic 6502 instructions
processor 6502
; Include files for Atari 2600 constants and handy macro routines
include "vcs.h"
include "macro.h"
; Here we're going to introduce the 6502 (the CPU) and
; the TIA (the chip that generates the video signal).
; There's no frame buffer, so you have to program the TIA
; before (or during) each scanline.
; We're just going to initialize the system and put some
; color on the TV.
; 4K Atari 2600 ROMs usually start at address $F000
org $f000
; Typical initialization routine
; ('start' is a label because it's on the left margin)
Start sei ; disable interrupts
cld ; disable BCD math mode
ldx #$ff ; init stack pointer to $FF (grows upward)
txs ; ... transfer X register to S register (stack pointer)
; Another typical thing is to clear the zero page region ($00-$FF)
; This includes a bunch of TIA registers as well as RAM ($80-$FF)
lda #$00 ; set A register to zero ('#' denotes constant)
; X register is already at $ff from previous instruction, so let's loop...
Zero sta $00,X ; store A register at address ($0 + X)
dex ; decrement X by one
bne Zero ; branch until X is zero
; Set background color
lda #$30 ;load value into A ($30 is deep red on NTSC)
sta COLUBK ;put the value of A into the background color register
; Nothing else to do, so let's start over.
; There's no vertical sync logic, and the zero-page clearing routine
; will run again, so you'll see alternating black and red lines.
jmp Start
; Here we skip to address $FFFC and define a word with the
; address of where the CPU should start fetching instructions.
; This also fills out the ROM size to $1000 (4k) bytes
org $fffc
.word Start
.word Start

136
presets/examples/lines.a Normal file
View File

@ -0,0 +1,136 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
Temp .byte
X1 .byte ; start X coordinate of line
Y1 .byte ; start Y coordinate of line
Y2 .byte ; end Y coordinate of line
Slope .word ; 16-bit slope
XFrac .byte ; X fractional
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
lda #80
sta X1
lda #80
sta Y1
lda #120
sta Y2
lda #0
sta Slope
lda #0
sta Slope+1
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
; Set missile 0 X start position
lda X1 ; starting X
ldx #2 ; missile 0
jsr SetHorizPos
sta WSYNC
sta HMOVE
TIMER_WAIT
TIMER_SETUP 192
lda #$54
sta COLUP0
ldy #0
sty XFrac
ScanLoop
cpy Y1
bcc NoLine ; out of bounds (< Y1)?
cpy Y2
bcs NoLine ; out of bounds (> Y2)?
; Add slope to X fractional error
lda XFrac
clc
adc Slope ; this sets carry flag
sta XFrac
lda Slope+1
adc #7 ; 7 + carry flag
tax ; -> X
lda DotWidths,x ; lookup register for missile width
sta Temp ; -> Temp
lda HMoveTable,x ; lookup HMOVE register for X offset
sta WSYNC
sta HMOVE ; apply moves on previous scanline
ldx #2
stx ENAM0 ; enable missile
ldx Temp
stx NUSIZ0 ; set missile width
sta HMM0 ; set HMM0 for next scanline
NextScan
iny
cpy #192
bcc ScanLoop ; any more scanlines?
beq DoneLine ; branch always taken
NoLine
sta WSYNC
lda #0
sta ENAM0 ; hide missile
jmp NextScan
DoneLine
TIMER_SETUP 30
; Change slope of line
lda Slope
clc
adc #3
sta Slope
lda Slope+1
adc #0
cmp #2
bmi NoLineReset
lda #$fe
NoLineReset
sta Slope+1
TIMER_WAIT
jmp NextFrame
SetHorizPos subroutine
sta WSYNC ; start a new line
bit 0 ; waste 3 cycles
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
rts ; return to caller
; HMOVE table from -7 to +8
HMoveTable
hex 7060504030201000f0e0d0c0b0a09080
; Table for NUSIZ registers
DotWidths
hex 40403030201000000010203030404040
; Epilogue
org $fffc
.word Start
.word Start

227
presets/examples/missiles.a Normal file
View File

@ -0,0 +1,227 @@
processor 6502
include "vcs.h"
include "macro.h"
org $f000
; Besides the two 8x1 sprites ("players") the TIA has
; two "missiles" and one "ball", which are just variable-length
; dots or dashes. They have similar positioning and display
; requirements, so we're going to make a subroutine that can
; set the horizontal position of any of them.
; But we can also use the HMPx/HMOVE registers directly to move the
; objects by small offsets without using this routine every time.
counter equ $81
; Initialize and set initial offsets of objects.
start CLEAN_START
lda #10
ldx #0
jsr SetHorizPos ; set player 0 horiz. pos
inx
lda #130
jsr SetHorizPos ; set player 1 horiz. pos
inx
lda #40
jsr SetHorizPos ; set missile 0 horiz. pos
lda #$10
sta NUSIZ0 ; make missile 0 2x-wide
inx
lda #70
jsr SetHorizPos ; set missile 1 horiz. pos
lda #$20
sta NUSIZ1 ; make missile 1 4x-wide
inx
lda #100
jsr SetHorizPos ; set ball horiz. pos
lda #$30
sta CTRLPF ; set ball 8x-wide
sta WSYNC
sta HMOVE
; We've technically generated an invalid frame because
; these operations have generated superfluous WSYNCs.
; But it's just at the beginning of our program, so whatever.
; Next frame loop
nextframe
VERTICAL_SYNC
; 37 lines of VBLANK
ldx #37
lvblank sta WSYNC
dex
bne lvblank
; Draw 192 scanlines
; We're going to draw both players, both missiles, and the ball
; straight down the screen. We can draw various kinds of vertical
; lines this way.
ldx #192
stx COLUBK ; set the background color
lda #0 ; A changes every scanline
ldy #0 ; Y is sprite data index
lvscan
sta WSYNC ; wait for next scanline
lda NUMBERS,y
sta GRP0 ; set sprite 0 pixels
sta GRP1 ; set sprite 1 pixels
tya ; we'll use the Y position, only the 2nd bit matters
sta ENAM0 ; enable/disable missile 0
sta ENAM1 ; enable/disable missile 1
sta ENABL ; enable/disable ball
iny
cpy #60
bne wrap1 ; wrap Y at 60 to 0
ldy #0
wrap1
dex
bne lvscan ; repeat next scanline until finished
; Clear all colors to black before overscan
stx COLUBK
stx COLUP0
stx COLUP1
stx COLUPF
; 30 lines of overscan
ldx #25
lvover sta WSYNC
dex
bne lvover
; Move all the objects by a different offset using HMP/HMOVE registers
; We'll hard-code the offsets in a table for now
ldx #0
hmoveloop
lda MOVEMENT,x
sta HMCLR
sta HMP0,x
sta WSYNC
sta HMOVE
inx
cpx #5
bcc hmoveloop
; This loop also gave us 5 extra scanlines = 30 total
; Cycle the sprite colors for the next frame
inc counter
lda counter
sta COLUP0
clc
ror
sta COLUP1
clc
ror
sta COLUPF
jmp nextframe
; SetHorizPos - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; This routine does a WSYNC and HMOVE before executing,
; so whatever you do here will not take effect until you
; call the routine again or do your own WSYNC and HMOVE.
SetHorizPos
sta WSYNC ; start a new line
sta HMOVE ; apply the previous fine position(s)
sta HMCLR ; reset the old horizontal position(s)
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
rts ; return to caller
; Hard-coded values for movement registers
MOVEMENT
.byte $f0 ; +1 pixels
.byte $e0 ; +2 pixels
.byte $c0 ; +4 pixels
.byte $10 ; -1 pixels
.byte $20 ; -2 pixels
; Bitmap pattern for digits
NUMBERS .byte $0E ; | XXX | $F5C5 Leading zero is not drawn
.byte $0A ; | X X | $F5C6 because it's never used.
.byte $0A ; | X X | $F5C7
.byte $0A ; | X X | $F5C8
.byte $0E ; | XXX | $F5C9
.byte $00
.byte $22 ; | X X | $F5CA
.byte $22 ; | X X | $F5CB
.byte $22 ; | X X | $F5CC
.byte $22 ; | X X | $F5CD
.byte $22 ; | X X | $F5CE
.byte $00
.byte $EE ; |XXX XXX | $F5CF
.byte $22 ; | X X | $F5D0
.byte $EE ; |XXX XXX | $F5D1
.byte $88 ; |X X | $F5D2
.byte $EE ; |XXX XXX | $F5D3
.byte $00
.byte $EE ; |XXX XXX | $F5D4
.byte $22 ; | X X | $F5D5
.byte $66 ; | XX XX | $F5D6
.byte $22 ; | X X | $F5D7
.byte $EE ; |XXX XXX | $F5D8
.byte $00
.byte $AA ; |X X X X | $F5D9
.byte $AA ; |X X X X | $F5DA
.byte $EE ; |XXX XXX | $F5DB
.byte $22 ; | X X | $F5DC
.byte $22 ; | X X | $F5DD
.byte $00
.byte $EE ; |XXX XXX | $F5DE
.byte $88 ; |X X | $F5DF
.byte $EE ; |XXX XXX | $F5E0
.byte $22 ; | X X | $F5E1
.byte $EE ; |XXX XXX | $F5E2
.byte $00
.byte $EE ; |XXX XXX | $F5E3
.byte $88 ; |X X | $F5E4
.byte $EE ; |XXX XXX | $F5E5
.byte $AA ; |X X X X | $F5E6
.byte $EE ; |XXX XXX | $F5E7
.byte $00
.byte $EE ; |XXX XXX | $F5E8
.byte $22 ; | X X | $F5E9
.byte $22 ; | X X | $F5EA
.byte $22 ; | X X | $F5EB
.byte $22 ; | X X | $F5EC
.byte $00
.byte $EE ; |XXX XXX | $F5ED
.byte $AA ; |X X X X | $F5EE
.byte $EE ; |XXX XXX | $F5EF
.byte $AA ; |X X X X | $F5F0
.byte $EE ; |XXX XXX | $F5F1
.byte $00
.byte $EE ; |XXX XXX | $F5F2
.byte $AA ; |X X X X | $F5F3
.byte $EE ; |XXX XXX | $F5F4
.byte $22 ; | X X | $F5F5
.byte $EE ; |XXX XXX | $F5F6
.byte $00
; Epilogue
org $fffc
.word start
.word start

View File

@ -0,0 +1,273 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
; Given all the heavy lifting it requires to put a single
; sprite on the screen, we'd like to abstract away the process
; of putting multiple sprites on the screen so we can actually
; make a game. This is called the "kernel" because it is
; particularly timing-sensitive and the CPU will spend a lot
; of time in that loop.
; We'll start with drawing two overlapping sprites with full
; resolution (1 scanline per sprite line) and full color
; (1 color per scanline). To do this our kernel (DrawSprites)
; will have to update four TIA registers after each WSYNC,
; pulling from four lookup tables, and handling edge cases
; where sprites overlap partially in the Y-dimension.
; This will use up almost an entire scanline of CPU time.
; Right now we'll just hard-code the horizontal position
; of each sprite, limiting ourselves to two sprites.
seg.u Variables
org $80
Counter byte ; increments each frame
Scanline byte ; scanline to draw next
PData0 word ; pointer (lo/hi) to player 0 bitmap data
PData1 word ; pointer to player 1 bitmap data
PColr0 word ; pointer to player 0 color data
PColr1 word ; pointer to player 1 color data
SIndx0 byte ; index into player data (0 = inactive)
SIndx1 byte ; index into player data (0 = inactive)
SSize0 byte ; sprite size for each player
SSize1 byte ; sprite size for each player
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
; Initialize and set initial offsets of objects.
Start CLEAN_START
; Hard-code the horizontal player positions.
lda #70
ldx #0
jsr SetHorizPos ; set player 0 horiz. pos
lda #73
ldx #1
jsr SetHorizPos ; set player 1 horiz. pos
sta WSYNC
sta HMOVE
; Next frame loop
NextFrame
TIMER_SETUP 37
VERTICAL_SYNC
; Set some colors
lda #$80
sta COLUBK ; set the background color
; Setup the two sprites
lda #50
sta SIndx0
lda Counter ; changes every frame
sta SIndx1
lda #17
sta SSize0
sta SSize1
lda #<Frame0
sta PData0
lda #>Frame0
sta PData0+1
lda #<ColorFrame0
sta PColr0
lda #>ColorFrame0
sta PColr0+1
lda #<Frame1
sta PData1
lda #>Frame1
sta PData1+1
lda #<ColorFrame0
sta PColr1
lda #>ColorFrame0
sta PColr1+1
TIMER_WAIT
; Scanline loop
lda #0
sta Scanline
NextScanline
jsr DrawSprites
inc Scanline
; WSYNC and then clear sprite data
sta WSYNC
lda #0
stx GRP0
stx GRP1
; repeat until all scanlines drawn
lda Scanline
cmp #192
bcc NextScanline
; Clear all colors to black before overscan
stx COLUBK
stx COLUP0
stx COLUP1
stx COLUPF
; 30 lines of overscan
TIMER_SETUP 30
TIMER_WAIT
; Jump to next frame
inc Counter
jmp NextFrame
; Here is our sprite kernel.
; Inputs:
; ssize0, ssize1 (size in lines of each sprite)
; sindx0, sindx1 (# scanlines to bottom of sprites)
; pdata0, pdata1 (ptr to sprite bitmaps)
; pcolr0, pcolr0 (ptr to sprite color tables)
DrawSprites
; Find out the maximum # of lines to draw
; by taking the maximum of the two sprite heights
lda SIndx0
cmp SIndx1
bpl Cmp1 ; sindx0 < sindx1
lda SIndx1
Cmp1 tax ; X = # of lines left to draw
cmp #0 ; no lines?
beq NoSprites
; add total draw height to scanline count
clc
adc Scanline
sta Scanline
DrawNextScanline
; Fetch next pixels/colors for sprite 0
ldy SIndx0
cpy SSize0
bcs Inactive0 ; index >= size? (unsigned)
lda (PData0),y
; Do WSYNC and then quickly store pixels/colors for player 0
sta WSYNC
sta GRP0
lda (PColr0),y
sta COLUP0
DrawSprite1
; Fetch next pixels/colors for sprite 1
ldy SIndx0+1
cpy SSize0+1
bcs Inactive1 ; index >= size? (unsigned)
lda (PData1),y
sta GRP1
lda (PColr1),y
sta COLUP1
Inactive1
; Decrement the two sprite indices
dey
sty SIndx0+1
dec SIndx0
dex
bne DrawNextScanline
NoSprites
; Clear player data on exit
rts
Inactive0
; Alternate sprite 0 path when inactive
lda #0
sta WSYNC
sta GRP0
sta COLUP0
beq DrawSprite1 ; always taken due to lda #0
; SetHorizPos - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; This routine does a WSYNC and HMOVE before executing,
; so whatever you do here will not take effect until you
; call the routine again or do your own WSYNC and HMOVE.
SetHorizPos
sta WSYNC ; start a new line
sta HMOVE ; apply the previous fine position(s)
sta HMCLR ; reset the old horizontal position(s)
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
rts ; return to caller
; Height of our sprite in lines
SpriteHeight equ 18
; To avoid nasty timing issues,
; we'll start the bitmap data at a page boundary
org $f800
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF;
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,567 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
; For lots of games, we'd like to display more than two sprites.
; There are lots of different ways to tackle this on the VCS,
; but we're going to try for a generalized approach that lets
; use have N different sprites at any X-Y coordinate, each with
; its own bitmap and color table. This is tricky because we can
; only do so much on each scanline.
; Our approach is to separate the problem into three phases.
; In the sort phase, we sort all sprites by Y coordinate.
; We do one sort pass per frame, so it may take several frames
; for the sort to stabilize.
; In the positioning phase, we look at the sprites in Y-sorted
; order, looking several lines ahead to see if a sprite is
; coming up. We then allocate it to one of the two player
; objects in hardware and set its position using the SetHorizPos
; method. We can set one or both of the player objects this way.
; In the display phase, we display the objects which we previously
; assigned and positioned. First we figure out how many scanlines are
; required. If only one object is scheduled, we just use its height.
; If two objects are scheduled, we go until the bottommost line has
; been displayed. We then loop through, fetching pixels and colors
; for one or both objects (up to four lookup tables) and setting
; registers at the appropriate time. We don't have time to do much
; else, so we don't look for any new objects to schedule until
; we're done with this loop.
; This scheme can only display up to two objects on a given
; scanline, so if the system tries to schedule a third, it will
; be ignored. Also, the positioning routine takes a few scanlines
; to complete, so if the top of a sprite is too close to the
; bottom of another sprite, the latter may not be displayed.
;
; To mitigate this, we increment a priority counter when a
; sprite entry is missed. In the sort phase, we move those sprites
; ahead of lower priority sprites in the sort order. This makes
; overlapping sprites flicker instead of randomly disappear.
;
; Note that we sprinkle NEWLINE macros around the codebase. This
; macro increments the 'scanline' variable and then does a WSYNC
; to sync to the next line. These locations are carefully selected
; so that we don't run out of CPU time on any line.
; (If you open the Console on your browser, you'll see debug
; statements printed when this happens)
;
; There are still some timing issues to fix as you'll see when you
; move the adventure person around with the joystick. These might
; add additional lines to the display.
seg.u Variables
org $80
Scanline byte ; scanline to draw next
CurIndex byte ; current sprite # to try to schedule
PData0 word ; pointer (lo/hi) to player 0 bitmap data
PColr0 word ; pointer to player 0 color data
PData1 word ; pointer to player 1 bitmap data
PColr1 word ; pointer to player 1 color data
SIndx0 byte ; next y-position to draw player 0
; or during draw, index into sprite
; zero means not assigned
SIndx1 byte ; ... for player 1
SSize0 byte ; sprite size for player 0
SSize1 byte ; sprite size for player 1
NSprites equ 8 ; max # of sprites
XPos0 ds NSprites ; x coord for each sprite
YPos0 ds NSprites ; y coord for each sprite
Sorted0 ds NSprites ; sorted list of sprite indices
Priority0 ds NSprites ; sprite priority list, if missed
; Macro to go to the next line so we don't forget
; to increment 'scanline'.
MAC NEWLINE
inc Scanline
sta WSYNC
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
; Initialize and set initial X and Y offsets of objects.
Start
CLEAN_START
ldx #0
lda #10
ldy #40
InitLoop
sty XPos0,x
sta YPos0,x
clc
adc #20
iny
iny
iny
iny
inx
cpx #NSprites
bne InitLoop
; Initialize initial sort order
ldx #0
InitLoop2
txa
sta Sorted0,x
inx
cpx #NSprites
bne InitLoop2
; Next frame loop
NextFrame
; VSYNC and VBLANK periods
VERTICAL_SYNC
TIMER_SETUP 37
; Do joystick movement
jsr MoveJoystick
; Do one iteration of bubble sort on sprite indices
ldx #NSprites-2
SortLoop
jsr SwapSprites
dex
bpl SortLoop ; loop until <= 0
; Reset scanline counter and sprite objects
ldx #0
stx Scanline
stx CurIndex
stx SIndx0
stx SIndx1
stx SSize0
stx SSize1
TIMER_WAIT
; end of VBLANK
; Scanline loop
; Start with WSYNC so we are at a known state
lda #$60
NEWLINE
sta COLUBK ; set the background color
NextFindSprite
; Schedule a player to a sprite
jsr FindAnotherSprite
; See if time to draw
jsr DrawSpritesIfTime
; repeat until all scanlines drawn
lda Scanline
cmp #192
bcc NextFindSprite
; end of Scanline loop
NoMoreScanlines
; Clear all colors to black before overscan
ldx #0
stx COLUBK
stx COLUP0
stx COLUP1
stx COLUPF
; 30 lines of overscan
TIMER_SETUP 30
TIMER_WAIT
; Go to next frame
jmp NextFrame
; We were too late to display a sprite.
; Put it earlier in the sort order and try next frame.
; X = sort index
.MissedSprite subroutine
; Have we already looked at all the sprites?
cpx #NSprites
bcs .OutOfSprites
; Increment priority for this sort entry
inc Priority0,x
; Go to next sort index, until we get to the end
inx
stx CurIndex
.OutOfSprites
NEWLINE
rts
; Try to assign the next sprite in the sort order into
; one of the two player slots.
; Uses 1 line (if no sprite found) or 3 lines (if sprite found)
FindAnotherSprite ; subroutine entry point
ldx CurIndex
ldy Sorted0,x ; get sprite index # in Y-sorted order
lda YPos0,y ; get Y position of sprite
sec
sbc Scanline ; SpriteY - Scanline
; Don't schedule the sprite if it's too soon or its scanline
; has already passed -- mark it missed
bmi .MissedSprite ; passed it? (or > 127 lines away)
cmp #3
bcc .MissedSprite ; less than 3 scanlines away
.SpriteUpcoming
; A sprite is starting soon, now we need to schedule it
; to either one of the player objects
lda XPos0,y
; Is player 1 available?
ldx SIndx1
bne .Plyr1NotReady
; Due to timing issues, we have artifacts if player 1 is
; too close to the left edge of the screen. So we'd prefer to
; put those sprites in the player 0 slot.
cmp #34 ; X < 34
bcc .Plyr1NotReady
; First let's set its horizontal offset (requires 2 lines)
ldx #1
jsr SetHorizPos ; set horizontal position (does WSYNC)
; Assign the sprite's Y position to player 1
lda YPos0,y
sta SIndx1
; Get index into SpriteDataMap (index * 4)
lda MultBy4,y
tay
; Copy addresses of pixel/color maps to player 1
lda SpriteDataMap,y
sta PData1
lda SpriteDataMap+1,y
sta PData1+1
lda SpriteDataMap+2,y
sta PColr1
lda SpriteDataMap+3,y
sta PColr1+1
; Get the sprite height as the first byte of the color map
ldy #0
lda (PColr1),y
sta SSize1
jmp .SetupDone
.Plyr1NotReady
ldx SIndx0
bne .NoNearSprite ; both players in use
; Player 0 is available
; This is essentially the same as the player 1 routine
ldx #0
jsr SetHorizPos
lda YPos0,y
sta SIndx0
lda MultBy4,y
tay
lda SpriteDataMap,y
sta PData0
lda SpriteDataMap+1,y
sta PData0+1
lda SpriteDataMap+2,y
sta PColr0
lda SpriteDataMap+3,y
sta PColr0+1
ldy #0
lda (PColr0),y
sta SSize0
.SetupDone
inc CurIndex ; go to next sprite in sort order
.NoNearSprite
NEWLINE
sta HMOVE ; apply the previous fine position(s)
sta HMCLR ; reset the old horizontal position(s)
rts
; Draw sprites if they are starting in the next few scanlines.
DrawSpritesIfTime subroutine
; See if either sprite is almost time to draw (within 4 lines)
lda SIndx0
sec
sbc Scanline
cmp #4
bcc .DrawSprites ; (ypos-scanline) < 4?
lda SIndx1
sec
sbc Scanline
cmp #4
bcs .NoSprites ; (ypos-scanline) < 4?
.DrawSprites
NEWLINE
; Calculate # of lines to draw for each sprite
; Sprite Y - current scanline + sprite height
lda SIndx0
beq .Empty0 ; sprite 0 is inactive?
sec
sbc Scanline
clc
adc SSize0
sta SIndx0 ; SIndx0 += SSize0 - Scanline
.Empty0
lda SIndx1
beq .Empty1 ; sprite 1 is inactive?
sec
sbc Scanline
clc
adc SSize1
sta SIndx1 ; SIndx1 += SSize1 - Scanline
.Empty1
; Find out the maximum # of lines to draw
; by taking the maximum of the two sprite heights
cmp SIndx0
bpl .Cmp1 ; sindx0 < sindx1?
lda SIndx0
.Cmp1 tax ; X = # of lines left to draw
; Add total draw height to scanline count
; Saves time rather than 'inc scanline' each line
clc
adc Scanline
sta Scanline
.DrawNextScanline
; Make sure player 0 index is within bounds
ldy SIndx0
cpy SSize0
bcs .Inactive0 ; index >= size? (or index < 0)
; Lookup pixels for player 0
lda (PData0),y
; Do WSYNC and then quickly store pixels for player 0
sta WSYNC
sta GRP0
; Lookup/store colors for player 0
lda (PColr0),y
sta COLUP0
.DrawSprite1
; Make sure player 1 index is within bounds
ldy SIndx1
cpy SSize1
bcs .Inactive1 ; index >= size? (or index < 0)
; Lookup/store pixels and colors for player 1
; Note that we are already 30-40 pixels into the scanline
; by this point...
lda (PData1),y
sta GRP1
lda (PColr1),y
sta COLUP1
.Inactive1
; Decrement the two sprite indices
dey
sty SIndx1
dec SIndx0
; Repeat until we've drawn all the scanlines for this job
dex
bne .DrawNextScanline
; Free up both player objects by zeroing them out
stx SIndx0
stx SIndx1
stx SSize0
stx SSize1
; WSYNC and then clear sprite data
NEWLINE
ldx #0
stx GRP0
stx GRP1
rts
; No sprites were drawn; just exit
.NoSprites
NEWLINE
rts
.Inactive0
; Alternate player 0 path when it is inactive
sta WSYNC
lda #0
sta GRP0
sta COLUP0
beq .DrawSprite1 ; always taken due to lda #0
; Perform one sort iteration
; X register contains sort index (0 to NSprites-1)
SwapSprites subroutine
; First compare Priority[i] and Priority[i+1]
lda Priority0,x
cmp Priority0+1,x
bcs .CompareYPos
; If Priority[i] < Priority[i+1], do the swap
; anyway after resetting priorities
lda #0
sta Priority0,x
sta Priority0+1,x ; reset
ldy Sorted0+1,x
bcc .DoSwap ; swap due to priority
.CompareYPos
; Compare Y[i] and Y[i+1]
ldy Sorted0,x
lda YPos0,y
ldy Sorted0+1,x
cmp YPos0,y
bcc .NoSwap ; Y[i] < Y[i+1]? don't swap
.DoSwap
; Swap Sorted[i] and Sorted[i+1]
lda Sorted0,x ; A <- Sorted[i]
sty Sorted0,x ; Y -> Sorted[i]
sta Sorted0+1,x ; A -> Sorted[i+1]
.NoSwap
rts
; Read joystick movement and apply to object 0
MoveJoystick subroutine
; Move vertically
ldx YPos0
lda #%00010000 ;Up?
bit SWCHA
bne .SkipMoveUp
cpx #8
bcc .SkipMoveUp
dex
.SkipMoveUp
lda #%00100000 ;Down?
bit SWCHA
bne .SkipMoveDown
cpx #170
bcs .SkipMoveDown
inx
.SkipMoveDown
stx YPos0
; Move horizontally
ldx XPos0
lda #%01000000 ;Left?
bit SWCHA
bne .SkipMoveLeft
cpx #5
bcc .SkipMoveLeft
dex
.SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne .SkipMoveRight
cpx #140
bcs .SkipMoveRight
inx
.SkipMoveRight
stx XPos0
rts
; SetHorizPos - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; NOTE: This version of the routine does a NEWLINE after executing
; because when the beam is at the far right side of the screen
; there is little time to do so before wrapping to the next line.
; It does NOT do a HMOVE and HCLR.
SetHorizPos subroutine
; NEWLINE
sta WSYNC ; start a new line
inc Scanline
sec ; set carry flag
.DivideLoop
sbc #15 ; subtract 15
bcs .DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
NEWLINE ; another WSYNC
rts ; return to caller
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #17 ; height
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Enemy cat-head graphics data
EnemyFrame0
.byte #0
.byte #%00111100;$AE
.byte #%01000010;$AE
.byte #%11100111;$AE
.byte #%11111111;$AC
.byte #%10011001;$8E
.byte #%01111110;$8E
.byte #%11000011;$98
.byte #%10000001;$98
; Enemy cat-head color data
EnemyColorFrame0
.byte #9 ; height
.byte #$AE;
.byte #$AC;
.byte #$A8;
.byte #$AC;
.byte #$8E;
.byte #$8E;
.byte #$98;
.byte #$94;
; Mapping of sprite objects (0-7) to sprite data
SpriteDataMap
.word Frame0,ColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word Frame1,ColorFrame0
; Multiplication by 4 table
; faster than tya/asl/asl/tay
MultBy4
.byte #$00,#$04,#$08,#$0c
.byte #$10,#$14,#$18,#$1c
.byte #$20,#$24,#$28,#$2c
.byte #$30,#$34,#$38,#$3c
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,580 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
; For lots of games, we'd like to display more than two sprites.
; There are lots of different ways to tackle this on the VCS,
; but we're going to try for a generalized approach that lets
; use have N different sprites at any X-Y coordinate, each with
; its own bitmap and color table. This is tricky because we can
; only do so much on each scanline.
; Our approach is to separate the problem into three phases.
; In the sort phase, we sort all sprites by Y coordinate.
; We do one sort pass per frame, so it may take several frames
; for the sort to stabilize.
; In the positioning phase, we look at the sprites in Y-sorted
; order, looking several lines ahead to see if a sprite is
; coming up. We then allocate it to one of the two player
; objects in hardware and set its position using the SetHorizPos
; method. We can set one or both of the player objects this way.
; In the display phase, we display the objects which we previously
; assigned and positioned. First we figure out how many scanlines are
; required. If only one object is scheduled, we just use its height.
; If two objects are scheduled, we go until the bottommost line has
; been displayed. We then loop through, fetching pixels and colors
; for one or both objects (up to four lookup tables) and setting
; registers at the appropriate time. We don't have time to do much
; else, so we don't look for any new objects to schedule until
; we're done with this loop.
; This scheme can only display up to two objects on a given
; scanline, so if the system tries to schedule a third, it will
; be ignored. Also, the positioning routine takes a few scanlines
; to complete, so if the top of a sprite is too close to the
; bottom of another sprite, the latter may not be displayed.
;
; To mitigate this, we increment a priority counter when a
; sprite entry is missed. In the sort phase, we move those sprites
; ahead of lower priority sprites in the sort order. This makes
; overlapping sprites flicker instead of randomly disappear.
seg.u Variables
org $80
Scanline byte ; current scanline
CurIndex byte ; current sprite # to try to schedule
PData0 word ; pointer (lo/hi) to player 0 bitmap data
PData1 word ; pointer to player 1 bitmap data
PColr0 word ; pointer to player 0 color data
PColr1 word ; pointer to player 1 color data
SIndx0 byte ; next y-position to draw player 0
; or during draw, index into sprite
; zero means not assigned
SIndx1 byte ; ... for player 1
SSize0 byte ; sprite size for player 0
SSize1 byte ; sprite size for player 1
NSprites equ 8 ; max # of sprites
XPos0 ds NSprites ; x coord for each sprite
YPos0 ds NSprites ; y coord for each sprite
Sorted0 ds NSprites ; sorted list of sprite indices
Priority0 ds NSprites ; sprite priority list, if missed
MinYDist equ 7 ; min. Y distance to consider sprite
; Fetchs the approximate scanline (could be off by +/- 1)
; into A. Takes 11 or 14 cycles.
MAC GET_APPROX_SCANLINE
ldy INTIM
lda Timer2Scanline,y
bne .Ok
lda Timer2Scanline-1,y
.Ok
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
; Initialize and set initial X and Y offsets of objects.
Start
CLEAN_START
ldx #0
lda #10
ldy #40
InitLoop
sty XPos0,x
sta YPos0,x
clc
adc #19
iny
iny
iny
iny
iny
iny
inx
cpx #NSprites
bne InitLoop
; Initialize initial sort order
ldx #0
InitLoop2
txa
sta Sorted0,x
inx
cpx #NSprites
bne InitLoop2
; Next frame loop
NextFrame
; VSYNC and VBLANK periods
VERTICAL_SYNC
TIMER_SETUP 24
; Do joystick movement
jsr MoveJoystick
; Do one iteration of bubble sort on sprite indices
ldx #NSprites-2
SortLoop
jsr SwapSprites
dex
bpl SortLoop ; loop until <= 0
; Reset scanline counter and sprite objects
ldx #0
stx CurIndex
stx SIndx0
stx SIndx1
stx SSize0
stx SSize1
TIMER_WAIT
; end of VBLANK
; Scanline loop
TIMER_SETUP 216 ; timer <- #$ff
lda #$90
sta COLUBK
NextFindSprite
; Try to schedule sprites to both players
jsr FindAnotherSprite
jsr FindAnotherSprite
; Apply fine offsets
sta WSYNC ; start next scanline
sta HMOVE ; apply the previous fine position(s)
; See if time to draw
jsr DrawSprites
; Repeat until all scanlines drawn
sta HMCLR ; reset the old horizontal position(s)
lda INTIM
cmp #$14 ; scanline 198
bcs NextFindSprite
lda #201 ; + 9 lines, end exactly
jsr WaitForScanline
; end of Scanline loop
NoMoreScanlines
; Clear all colors to black before overscan
ldx #0
stx COLUBK
stx COLUP0
stx COLUP1
stx COLUPF
; 30-2 lines of overscan
TIMER_SETUP 28
TIMER_WAIT
; Go to next frame
jmp NextFrame
; We were too late to display a sprite.
; Put it earlier in the sort order and try next frame.
; X = sort index
.MissedSprite subroutine
; Have we already looked at all the sprites?
; Increment priority for this sort entry
inc Priority0,x
; Go to next sort index, until we get to the end
inx
stx CurIndex
.OutOfSprites
rts
; Try to assign the next sprite in the sort order into
; one of the two player slots.
; If sprite found, uses at least 3 scanlines for SetHorizPos.
FindAnotherSprite ; subroutine entry point
; Get the approximate scanline
GET_APPROX_SCANLINE
clc
adc #MinYDist
sta Scanline
; Calculate the distance to next sprite
ldx CurIndex
cpx #NSprites
bcs .OutOfSprites
ldy Sorted0,x ; get sprite index # in Y-sorted order
lda YPos0,y ; get Y position of sprite
cmp Scanline ; SpriteY - Scanline
; Don't schedule the sprite if it's too soon or its scanline
; has already passed -- mark it missed
bmi .MissedSprite ; passed it? (or > 127 lines away)
; A sprite is starting soon, now we need to schedule it
; to either one of the player objects
lda XPos0,y
; Is player 1 available?
ldx SIndx1
bne .Plyr1NotReady
; Due to timing issues, we have artifacts if player 1 is
; too close to the left edge of the screen. So we'd prefer to
; put those sprites in the player 0 slot.
cmp #34 ; X < 34
bcc .Plyr1NotReady
; First let's set its horizontal offset
ldx #1
jsr SetHorizPos ; set horizontal position (does WSYNC)
; Assign the sprite's Y position to player 1
lda YPos0,y
sta SIndx1
; Get index into SpriteDataMap (index * 4)
ldx MultBy4,y
; Copy addresses of pixel/color maps to player 1
lda SpriteDataMap,x
sta PData1
lda SpriteDataMap+1,x
sta PData1+1
lda SpriteDataMap+2,x
sta PColr1
lda SpriteDataMap+3,x
sta PColr1+1
; Get the sprite height as the first byte of the color map
ldy #0
lda (PColr1),y
sta SSize1
jmp .SetupDone
.Plyr1NotReady
ldx SIndx0
bne .NoNearSprite ; both players in use
; Player 0 is available
; This is essentially the same as the player 1 routine
ldx #0
jsr SetHorizPos
lda YPos0,y
sta SIndx0
ldx MultBy4,y
lda SpriteDataMap,x
sta PData0
lda SpriteDataMap+1,x
sta PData0+1
lda SpriteDataMap+2,x
sta PColr0
lda SpriteDataMap+3,x
sta PColr0+1
ldy #0
lda (PColr0),y
sta SSize0
.SetupDone
inc CurIndex ; go to next sprite in sort order
.NoNearSprite
rts
; Draw any scheduled sprites.
DrawSprites subroutine
; Wait for next precise scanline
lda #0 ; 0 = wait for next
jsr WaitForScanline
lda Timer2Scanline,y ; lookup scanline #
sta Scanline ; save it
; Calculate # of lines to draw for each sprite
; Sprite Y - current scanline + sprite height
lda SIndx0
beq .Empty0 ; sprite 0 is inactive?
sec
sbc Scanline
clc
adc SSize0
sta SIndx0 ; SIndx0 += SSize0 - Scanline
.Empty0
lda SIndx1
beq .Empty1 ; sprite 1 is inactive?
sec
sbc Scanline
clc
adc SSize1
sta SIndx1 ; SIndx1 += SSize1 - Scanline
.Empty1
; Find out the maximum # of lines to draw
; by taking the maximum of the two sprite heights
cmp SIndx0
bpl .Cmp1 ; sindx0 < sindx1?
lda SIndx0
.Cmp1
tax ; X = # of lines left to draw
beq .NoSprites ; X = 0? we're done
sta WSYNC ; next scanline
.DrawNextScanline
; Make sure player 0 index is within bounds
ldy SIndx0
cpy SSize0
bcs .Inactive0 ; index >= size? (or index < 0)
; Lookup pixels for player 0
lda (PData0),y
; Do WSYNC and then quickly store pixels for player 0
sta WSYNC
sta GRP0
; Lookup/store colors for player 0
lda (PColr0),y
sta COLUP0
.DrawSprite1
; Make sure player 1 index is within bounds
ldy SIndx1
cpy SSize1
bcs .Inactive1 ; index >= size? (or index < 0)
; Lookup/store pixels and colors for player 1
; Note that we are already 30-40 pixels into the scanline
; by this point...
lda (PData1),y
sta GRP1
lda (PColr1),y
sta COLUP1
.Inactive1
; Decrement the two sprite indices
dey
sty SIndx1
dec SIndx0
; Repeat until we've drawn all the scanlines for this job
dex
bne .DrawNextScanline
; Free up both player objects by zeroing them out
stx SIndx0
stx SIndx1
stx SSize0
stx SSize1
sta WSYNC
stx GRP0
stx GRP1
; No sprites were drawn; just exit
.NoSprites
rts
.Inactive0
; Alternate player 0 path when it is inactive
sta WSYNC
lda #0
sta GRP0
sta COLUP0
beq .DrawSprite1 ; always taken due to lda #0
; Perform one sort iteration
; X register contains sort index (0 to NSprites-1)
SwapSprites subroutine
; First compare Priority[i] and Priority[i+1]
lda Priority0,x
cmp Priority0+1,x
bcs .CompareYPos
; If Priority[i] < Priority[i+1], do the swap
; anyway after resetting priorities
lda #0
sta Priority0,x
sta Priority0+1,x ; reset
ldy Sorted0+1,x
bcc .DoSwap ; swap due to priority
.CompareYPos
; Compare Y[i] and Y[i+1]
ldy Sorted0,x
lda YPos0,y
ldy Sorted0+1,x
cmp YPos0,y
bcc .NoSwap ; Y[i] < Y[i+1]? don't swap
.DoSwap
; Swap Sorted[i] and Sorted[i+1]
lda Sorted0,x ; A <- Sorted[i]
sty Sorted0,x ; Y -> Sorted[i]
sta Sorted0+1,x ; A -> Sorted[i+1]
.NoSwap
rts
; Read joystick movement and apply to object 0
MoveJoystick subroutine
; Move vertically
ldx YPos0
lda #%00010000 ;Up?
bit SWCHA
bne .SkipMoveUp
cpx #8
bcc .SkipMoveUp
dex
.SkipMoveUp
lda #%00100000 ;Down?
bit SWCHA
bne .SkipMoveDown
cpx #170
bcs .SkipMoveDown
inx
.SkipMoveDown
stx YPos0
; Move horizontally
ldx XPos0
lda #%01000000 ;Left?
bit SWCHA
bne .SkipMoveLeft
cpx #5
bcc .SkipMoveLeft
dex
.SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne .SkipMoveRight
cpx #140
bcs .SkipMoveRight
inx
.SkipMoveRight
stx XPos0
rts
; SetHorizPos - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; NOTE: This version of the routine does a NEWLINE after executing
; because when the beam is at the far right side of the screen
; there is little time to do so before wrapping to the next line.
; It does NOT do a HMOVE and HCLR.
SetHorizPos subroutine
sta WSYNC ; start a new line
sec ; set carry flag
.DivideLoop
sbc #15 ; subtract 15
bcs .DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta HMP0,x ; set fine offset
sta RESP0,x ; fix coarse position
rts ; return to caller
; Pass: A = desired scanline
; Returns: Y = timer value - 1
align $10
WaitForScanline subroutine
ldy INTIM ; Fetch timer value
.Wait
cpy INTIM
beq .Wait ; Wait for it to change
sta WSYNC ; Sync with scan line
cmp Timer2Scanline,y ; lookup scanline
bcs WaitForScanline ; repeat until >=
rts
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #17 ; height
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Enemy cat-head graphics data
EnemyFrame0
.byte #0
.byte #%00111100;$AE
.byte #%01000010;$AE
.byte #%11100111;$AE
.byte #%11111111;$AC
.byte #%10011001;$8E
.byte #%01111110;$8E
.byte #%11000011;$98
.byte #%10000001;$98
; Enemy cat-head color data
EnemyColorFrame0
.byte #9 ; height
.byte #$AE;
.byte #$AC;
.byte #$A8;
.byte #$AC;
.byte #$8E;
.byte #$8E;
.byte #$98;
.byte #$94;
; Mapping of sprite objects (0-7) to sprite data
SpriteDataMap
.word Frame0,ColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word EnemyFrame0,EnemyColorFrame0
.word Frame1,ColorFrame0
; Multiplication by 4 table
; faster than tya/asl/asl/tay
MultBy4
.byte #$00,#$04,#$08,#$0c
.byte #$10,#$14,#$18,#$1c
.byte #$20,#$24,#$28,#$2c
.byte #$30,#$34,#$38,#$3c
; Timer -> Scanline table
align $100
Timer2Scanline
.byte 215, 0,214,213,212,211,210, 0,209,208,207,206,205,204, 0,203
.byte 202,201,200,199, 0,198,197,196,195,194, 0,193,192,191,190,189
.byte 188, 0,187,186,185,184,183, 0,182,181,180,179,178, 0,177,176
.byte 175,174,173,172, 0,171,170,169,168,167, 0,166,165,164,163,162
.byte 0,161,160,159,158,157,156, 0,155,154,153,152,151, 0,150,149
.byte 148,147,146, 0,145,144,143,142,141,140, 0,139,138,137,136,135
.byte 0,134,133,132,131,130, 0,129,128,127,126,125,124, 0,123,122
.byte 121,120,119, 0,118,117,116,115,114, 0,113,112,111,110,109,108
.byte 0,107,106,105,104,103, 0,102,101,100, 99, 98, 0, 97, 96, 95
.byte 94, 93, 92, 0, 91, 90, 89, 88, 87, 0, 86, 85, 84, 83, 82, 0
.byte 81, 80, 79, 78, 77, 76, 0, 75, 74, 73, 72, 71, 0, 70, 69, 68
.byte 67, 66, 0, 65, 64, 63, 62, 61, 60, 0, 59, 58, 57, 56, 55, 0
.byte 54, 53, 52, 51, 50, 0, 49, 48, 47, 46, 45, 44, 0, 43, 42, 41
.byte 40, 39, 0, 38, 37, 36, 35, 34, 0, 33, 32, 31, 30, 29, 28, 0
.byte 27, 26, 25, 24, 23, 0, 22, 21, 20, 19, 18, 0, 17, 16, 15, 14
.byte 13, 12, 0, 11, 10, 9, 8, 7, 0, 6, 5, 4, 3, 2, 0, 1
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,247 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
org $f000
; This program demonstrates a VCS music player based on tracks
; and patterns. A pattern is a list of variable-length notes,
; each of which is defined by a pitch and duration.
; There are two tracks, one for each audio channel.
; Each track consists of a list of patterns, each entry being
; a byte offset into the Patterns array.
; The patterns in the tracks are played in-order until one ends,
; and then both tracks are restarted. It's up to the composer
; to make sure the durations in each track line up properly.
; Patterns consist of NOTE or TONE commands. TONE sets the
; tone of the channel (the AUDCx register) and NOTE plays a note
; with a duration taken from a lookup table.
; TONE 0 ends a pattern.
; Both channels share the same logical array for tracks and patterns,
; so both tracks can take up to 255 bytes total, and all patterns
; can use up to 255 bytes total.
; The music player uses 8 bytes of RAM (not counting stack).
Trk0idx equ $e0 ; offset into tracks for channel 0
Trk1idx equ $e1 ; offset into tracks for channel 1
Pat0idx equ $e2 ; offset into patterns for channel 0
Pat1idx equ $e3 ; offset into patterns for channel 1
Chan0dur equ $e4 ; current note duration channel 0
Chan1dur equ $e5 ; current note duration channel 1
Chan0note equ $e6 ; current note pitch channel 0
Chan1note equ $e7 ; current note pitch channel 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Usage: NOTE pitch duration
; Plays a note in a pattern.
; pitch = 0-31
; duration = 1-7, uses DurFrames lookup table
MAC NOTE
.pitch SET {1}
.durat SET {2}
.byte (.pitch+(.durat<<5))
ENDM
; Usage: TONE tone
; Changes the tone in a pattern.
; tone = 1-15
MAC TONE
.tone SET {1}
.byte .tone
ENDM
; Usage: PATTERN address
; Plays a pattern in a track.
MAC PATTERN
.addr SET {1}
.byte (.addr-Patterns)
ENDM
; Usage: ENDTRACK
; Marks the end of a track.
MAC ENDTRACK
.byte 0
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Start
CLEAN_START
jsr ResetTrack
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
ldx #0
jsr MusicFrame
ldx #1
jsr MusicFrame
TIMER_WAIT
TIMER_SETUP 192
TIMER_WAIT
TIMER_SETUP 30
TIMER_WAIT
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ResetTrack
lda #0
sta Trk0idx
sta Pat0idx
sta Pat1idx
sta Chan0dur
sta Chan1dur
lda #Track1-Track0
sta Trk1idx
NextPattern
ldy Trk0idx,x
lda Track0,y
beq ResetTrack
sta Pat0idx,x
inc Trk0idx,x
MusicFrame
dec Chan0dur,x ; decrement note duration
bpl PlayNote ; only load if duration < 0
TryAgain
ldy Pat0idx,x ; load index into pattern table
lda Patterns,y ; load pattern code
beq NextPattern ; end of pattern?
inc Pat0idx,x ; increment pattern index for next time
pha ; save A for later
clc ; clear carry for ROL
rol
rol
rol
rol ; rotate A left by 4 (same as ROR by 5)
and #7 ; only take top 3 bits
beq NoteTone ; duration zero? tone instruction
tay ; Y = duration
lda DurFrames,y ; look up in duration table
sta Chan0dur,x ; save note duration
pla ; pop saved value into A
and #$1f ; extract first 5 bits
sta Chan0note,x ; store as note value
PlayNote
lda Chan0note,x ; get note pitch for channel
sta AUDF0,x ; store frequency register
lda Chan0dur,x ; get note duration remaining
clc
ror ; divide by 2
cmp #16
bcc NoHighVol
lda #15 ; make sure no greater than 15 (max)
NoHighVol
sta AUDV0,x ; store volume register
rts
; This routine is called for duration 0 (TONE) codes
NoteTone
pla
and #$f
beq NextPattern
sta AUDC0,x
jmp TryAgain
Patterns
TONE 0 ; byte 0 of patterns array is unused
Pattern00
TONE 3
NOTE 16,4
NOTE 2,4
NOTE 16,4
NOTE 30,4
NOTE 16,4
NOTE 4,4
NOTE 30,4
NOTE 16,4
TONE 0
Pattern10
TONE 6
NOTE 6,4
TONE 12
NOTE 16,4
NOTE 18,4
NOTE 19,4
NOTE 22,4
NOTE 23,4
NOTE 26,4
NOTE 23,4
NOTE 26,4
NOTE 23,4
NOTE 26,4
NOTE 23,4
NOTE 22,6
TONE 0
Pattern11
TONE 6
NOTE 6,6
NOTE 6,4
TONE 12
NOTE 16,4
NOTE 18,4
NOTE 19,4
NOTE 22,4
NOTE 23,4
NOTE 26,4
NOTE 23,4
NOTE 26,4
NOTE 26,4
NOTE 22,7
TONE 11
NOTE 0,7
NOTE 0,2
TONE 0
Pattern12
TONE 11
NOTE 0,5
TONE 12
NOTE 18,5
NOTE 18,3
NOTE 18,5
NOTE 16,6
NOTE 19,5
NOTE 22,3
TONE 6
NOTE 4,5
NOTE 4,4
TONE 12
NOTE 11,5
NOTE 11,3
NOTE 11,5
NOTE 10,6
NOTE 10,4
NOTE 17,3
NOTE 17,5
NOTE 16,5
TONE 0
Track0
PATTERN Pattern00
PATTERN Pattern00
PATTERN Pattern00
PATTERN Pattern00
PATTERN Pattern00
PATTERN Pattern00
PATTERN Pattern12
ENDTRACK
Track1
PATTERN Pattern10
PATTERN Pattern11
PATTERN Pattern10
PATTERN Pattern12
ENDTRACK
DurFrames
.byte 0,4,8,12,16,24,32,48
; Epilogue
org $fffc
.word Start
.word Start

111
presets/examples/piatable.a Normal file
View File

@ -0,0 +1,111 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg.u Variables
org $80
Temp .byte
Temp2 .byte
CSJump .word
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
lda #0
sta Temp
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
inc Temp
lda Temp
and #$1f
eor #$ff
clc
adc #<ClockslideEnd
sta CSJump
lda #>ClockslideEnd
sta CSJump+1
TIMER_WAIT
TIMER_SETUP 216
lda Temp
and #$7f
jsr WaitForScanline
sta COLUBK
lda #180
jsr WaitForScanline
lda #0
sta COLUBK
lda #192
jsr WaitForScanline
jmp TimerDone
jmp (CSJump)
REPEAT 36
.byte $c9
REPEND
.byte $c9,$c5
ClockslideEnd
nop
ScanLoop
ldy INTIM
beq TimerDone
lda Timer2Scanline,y
sta COLUBK
jmp ScanLoop
TimerDone
lda #0
sta COLUBK
TIMER_SETUP 30
TIMER_WAIT
jmp NextFrame
align $10
WaitForScanline subroutine
ldy INTIM ; Fetch timer value
.Wait
cpy INTIM
beq .Wait ; Wait for it to change
sta WSYNC ; Sync with scan line
cmp Timer2Scanline,y
bcs WaitForScanline
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align $100
Timer2Scanline
.byte 215, 0,214,213,212,211,210, 0,209,208,207,206,205,204, 0,203
.byte 202,201,200,199, 0,198,197,196,195,194, 0,193,192,191,190,189
.byte 188, 0,187,186,185,184,183, 0,182,181,180,179,178, 0,177,176
.byte 175,174,173,172, 0,171,170,169,168,167, 0,166,165,164,163,162
.byte 0,161,160,159,158,157,156, 0,155,154,153,152,151, 0,150,149
.byte 148,147,146, 0,145,144,143,142,141,140, 0,139,138,137,136,135
.byte 0,134,133,132,131,130, 0,129,128,127,126,125,124, 0,123,122
.byte 121,120,119, 0,118,117,116,115,114, 0,113,112,111,110,109,108
.byte 0,107,106,105,104,103, 0,102,101,100, 99, 98, 0, 97, 96, 95
.byte 94, 93, 92, 0, 91, 90, 89, 88, 87, 0, 86, 85, 84, 83, 82, 0
.byte 81, 80, 79, 78, 77, 76, 0, 75, 74, 73, 72, 71, 0, 70, 69, 68
.byte 67, 66, 0, 65, 64, 63, 62, 61, 60, 0, 59, 58, 57, 56, 55, 0
.byte 54, 53, 52, 51, 50, 0, 49, 48, 47, 46, 45, 44, 0, 43, 42, 41
.byte 40, 39, 0, 38, 37, 36, 35, 34, 0, 33, 32, 31, 30, 29, 28, 0
.byte 27, 26, 25, 24, 23, 0, 22, 21, 20, 19, 18, 0, 17, 16, 15, 14
.byte 13, 12, 0, 11, 10, 9, 8, 7, 0, 6, 5, 4, 3, 2, 0, 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,59 @@
processor 6502
include "vcs.h"
include "macro.h"
org $f000
; We're going to mess with the playfield registers, PF0, PF1 and PF2.
; Between them, they represent 20 bits of bitmap information
; which are replicated over 40 wide pixels for each scanline.
; By changing the registers before each scanline, we can draw bitmaps.
Counter equ $81
Start CLEAN_START
NextFrame
; This macro efficiently gives us 3 lines of VSYNC
VERTICAL_SYNC
; 37 lines of VBLANK
ldx #37
LVBlank sta WSYNC
dex
bne LVBlank
; Disable VBLANK
stx VBLANK
; Set foreground color
lda #$82
sta COLUPF
; Draw the 192 scanlines
ldx #192
lda #0 ; changes every scanline
;lda Counter ; uncomment to scroll!
ScanLoop
sta WSYNC ; wait for next scanline
sta PF0 ; set the PF1 playfield pattern register
sta PF1 ; set the PF1 playfield pattern register
sta PF2 ; set the PF2 playfield pattern register
stx COLUBK ; set the background color
adc #1 ; increment A
dex
bne ScanLoop
; Reenable VBLANK for bottom (and top of next frame)
lda #2
sta VBLANK
; 30 lines of overscan
ldx #30
LVOver sta WSYNC
dex
bne LVOver
; Go back and do another frame
inc Counter
jmp NextFrame
org $fffc
.word Start
.word Start

518
presets/examples/procgen1.a Normal file
View File

@ -0,0 +1,518 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
PFOfs .byte ; offset into PFData (0-20)
SpritePtr0 .word ; pointer to bitmap for sprite 0
SpritePtr1 .word ; pointer to bitmap for sprite 1
ColorPtr0 .word ; pointer to colors for sprite 0
ColorPtr1 .word ; pointer to colors for sprite 1
YPos0 .byte ; current Y position of sprite 0
YPos1 .byte ; current Y position of sprite 1
XPos0 .byte ; current X position of sprite 0
XPos1 .byte ; current X position of sprite 1
XPosPrev .byte ; previous X position of sprite 0
YPosPrev .byte ; previous X position of sprite 1
RoomType .byte ; current room definition byte
; these are modified line-by-line by the sprite kernel
Colp0 .byte ; temp. colors for player 0
YP0 .byte ; counts y-position for player 0
YP1 .byte ; counts y-position for player 1
tmpPF0 .byte ; temp. PF0
tmpPF1 .byte ; temp. PF1
tmpPF2 .byte ; temp. PF2
Temp .byte
PFData equ $c0 ; 21-byte array of playfield bytes
SpriteHeight equ 16 ; hard-coded height of sprites
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
Data0
lda #<Frame0
sta SpritePtr0
lda #>Frame0
sta SpritePtr0+1
lda #<ColorFrame0
sta ColorPtr0
lda #>ColorFrame0
sta ColorPtr0+1
lda #<Frame0
sta SpritePtr1
lda #>Frame0
sta SpritePtr1+1
lda #<ColorFrame1
sta ColorPtr1
lda #>ColorFrame1
sta ColorPtr1+1
lda #242
sta YPos0
lda #200
sta YPos1
lda #58
sta XPos0
sta XPos1
lda #1
sta VDELP0 ; updates to GRP0 will be delayed
lda #1
sta RoomType
jsr BuildRoom
NextFrame
VERTICAL_SYNC
; Set up VBLANK timer
; We'll do four fewer lines than usual, so we can
; load the playfield registers in the first four lines.
TIMER_SETUP 33
lda #$68
sta COLUP0 ; player color
lda #1
sta CTRLPF ; symmetry
lda #72
sta PFOfs ; reset playfield offset
; Set temporary Y counter and set horizontal position
lda YPos0
sta YP0 ; yp0 = temporary counter
lda YPos1
sta YP1 ; yp0 = temporary counter
lda XPos0
ldx #0
jsr SetHorizPos
lda XPos1
ldx #1
jsr SetHorizPos
sta WSYNC
sta HMOVE ; gotta apply HMOVE
sta CXCLR ; clear collisions
; Wait for end of VBLANK
TIMER_WAIT
sta WSYNC
KernelLoop
; Phase 0: Fetch PF0 byte
jsr DrawSprites
jsr FetchPlayfield
sta tmpPF0
; Phase 1: Fetch PF1 byte
jsr DrawSprites
jsr FetchPlayfield
sta tmpPF1
; Phase 2: Fetch PF2 byte
jsr DrawSprites
jsr FetchPlayfield
sta tmpPF2
; Phase 3: Write PF0/PF1/PF2 registers
jsr DrawSprites
lda tmpPF0
sta PF0
lda tmpPF1
sta PF1
lda tmpPF2
sta PF2
; Go to next scanline?
lda PFOfs
bne KernelLoop
NoMoreLines
; Set up overscan timer
; We'll take back those four lines we skipped earlier.
TIMER_SETUP 34
lda #0
sta PF0
sta PF1
sta PF2
; Did the player collide with the wall?
bit CXP0FB
bpl NoCollision
; Yes, load previous position
lda YPosPrev
sta YPos0
lda XPosPrev
sta XPos0
jmp NoMoveJoy
NoCollision
; No collision, update previous position and move player
lda YPos0
sta YPosPrev
lda XPos0
sta XPosPrev
jsr MoveJoystick
NoMoveJoy
TIMER_WAIT
jmp NextFrame
; DrawSprite subroutine called by kernel
DrawSprites subroutine
; Fetch sprite 0 values
lda #SpriteHeight ; height in 2xlines
sec
isb YP0 ; INC yp0, then SBC yp0
bcs DoDraw0 ; inside bounds?
lda #0 ; no, load the padding offset (0)
DoDraw0
tay ; -> Y
lda (ColorPtr0),y ; color for both lines
sta Colp0 ; -> colp0
lda (SpritePtr0),y ; bitmap for first line
sta GRP0 ; -> [GRP0] (delayed due to VDEL)
; Fetch sprite 1 values
lda #SpriteHeight ; height in 2xlines
sec
isb YP1 ; INC yp0, then SBC yp0
bcs DoDraw1 ; inside bounds?
lda #0 ; no, load the padding offset (0)
DoDraw1
tay ; -> Y
lda (ColorPtr1),y ; color for both lines
tax
lda (SpritePtr1),y ; bitmap for first line
tay
; WSYNC and store sprite values
lda Colp0
sta WSYNC
sty GRP1 ; GRP0 is also updated due to VDELP0 flag
stx COLUP1
sta COLUP0
; Return to caller
rts
; Fetch the next playfield byte.
FetchPlayfield subroutine
dec PFOfs
ldx PFOfs
ldy PFOffsets,x ; get index into PFData array
lda PFData,y ; load playfield byte
rts
SetHorizPos subroutine
sta WSYNC ; start a new line
bit 0 ; waste 3 cycles
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta RESP0,x ; fix coarse position
sta HMP0,x ; set fine offset
rts ; return to caller
; Read joystick movement and apply to object 0
MoveJoystick subroutine
; Move vertically
ldx YPos0
lda #%00100000 ;Down?
bit SWCHA
bne SkipMoveDown
dex
cpx #175
bcs SkipMoveDown
; If we move off the top of the screen,
; go to the next room in the sequence
ldy #1
jsr MoveNextRoom
jsr BuildRoom
ldx #254
bne SkipMoveUp
SkipMoveDown
lda #%00010000 ;Up?
bit SWCHA
bne SkipMoveUp
inx
cpx #254
bcc SkipMoveUp
; If we move off the top of the screen,
; go to the previous room in the sequence
ldy #1
jsr MovePrevRoom
jsr BuildRoom
ldx #174
bne SkipMoveUp
SkipMoveUp
stx YPos0
; Move horizontally
ldx XPos0
lda #%01000000 ;Left?
bit SWCHA
bne SkipMoveLeft
dex
cpx #1
bcs SkipMoveLeft
; If we move off the left of the screen,
; go back 7 rooms
ldy #7
jsr MovePrevRoom
jsr BuildRoom
ldx #152
SkipMoveLeft
lda #%10000000 ;Right?
bit SWCHA
bne SkipMoveRight
inx
cpx #153
bcc SkipMoveRight
; If we move off the right of the screen,
; go forward 7 rooms
ldy #7
jsr MoveNextRoom
jsr BuildRoom
ldx #1
SkipMoveRight
stx XPos0
rts
; Build a room based on the RoomType byte.
; Bits 0-1 = top walls
; Bits 2-3 = middle walls
BuildRoom subroutine
; First fill in the top section.
lda RoomType
and #3
jsr MulBy3ToX
lda PFRoomTop0+0,x
sta PFData+0
lda PFRoomTop0+1,x
sta PFData+1
lda PFRoomTop0+2,x
sta PFData+2
lda PFRoomTop1+0,x
sta PFData+3
lda PFRoomTop1+1,x
sta PFData+4
lda PFRoomTop1+2,x
sta PFData+5
; Now the middle section.
lda RoomType
ror
ror
and #3
jsr MulBy3ToX
lda PFRoomMid0+0,x
sta PFData+6
lda PFRoomMid0+1,x
sta PFData+7
lda PFRoomMid0+2,x
sta PFData+8
lda PFRoomMid1+0,x
sta PFData+9
lda PFRoomMid1+1,x
sta PFData+10
lda PFRoomMid1+2,x
sta PFData+11
lda PFRoomMid2+0,x
sta PFData+12
lda PFRoomMid2+1,x
sta PFData+13
lda PFRoomMid2+2,x
sta PFData+14
; And finally, the bottom.
lda RoomType
jsr NextRandom
pha
and #3
jsr MulBy3ToX
lda PFRoomTop1+0,x
sta PFData+15
lda PFRoomTop1+1,x
sta PFData+16
lda PFRoomTop1+2,x
sta PFData+17
lda PFRoomTop0+0,x
sta PFData+18
lda PFRoomTop0+1,x
sta PFData+19
lda PFRoomTop0+2,x
sta PFData+20
; Set the room colors and position the Green Man
lda RoomType
and #$f0
sta COLUBK
and #$7f
sta XPos1
pla ; next random value, stored
ora #$08
sta COLUPF ; fg color
ora #$80
sta YPos1
rts
; Helper function that computes X <- A*3
MulBy3ToX
sta Temp
asl
clc
adc Temp
tax
rts
; Get next random value
NextRandom subroutine
lsr
bcc .NoEor
eor #$d4
.NoEor:
rts
; Get previous random value
PrevRandom subroutine
asl
bcc .NoEor
eor #$a9
.NoEor:
rts
; Move to next room(s)
; Y = number of iterations
MoveNextRoom subroutine
lda RoomType
jsr NextRandom
dey
sta RoomType
bne MoveNextRoom
rts
; Move to previous room(s)
; Y = number of iterations
MovePrevRoom subroutine
lda RoomType
jsr PrevRandom
dey
sta RoomType
bne MovePrevRoom
rts
; Table used to get offsets to playfield bytes in memory
; 24 3-byte entries, one for each 8-pixel section
; in reverse order
PFOffsets
.byte 20,19,18
.byte 20,19,18
.byte 17,16,15
.byte 17,16,15
.byte 17,16,15
.byte 17,16,15
.byte 17,16,15
.byte 17,16,15
.byte 14,13,12
.byte 11,10,9
.byte 11,10,9
.byte 11,10,9
.byte 11,10,9
.byte 11,10,9
.byte 11,10,9
.byte 11,10,9
.byte 8,7,6
.byte 5,4,3
.byte 5,4,3
.byte 5,4,3
.byte 5,4,3
.byte 5,4,3
.byte 5,4,3
.byte 2,1,0
; Playfield components for rooms
PFRoomTop0
.byte #%11110000,#%11111111,#%00000111
.byte #%00110000,#%00001111,#%11111111
.byte #%00110000,#%00001111,#%11111111
.byte #%00110000,#%00000000,#%10000000
PFRoomTop1
.byte #%00110000,#%00000000,#%00000000
.byte #%00110000,#%00000000,#%00000000
.byte #%00110000,#%00000000,#%10000000
.byte #%00110000,#%00001111,#%11111111
PFRoomMid0
.byte #%00110000,#%00000000,#%00000000
.byte #%11110000,#%11111111,#%00000000
.byte #%11110000,#%11111111,#%00000000
.byte #%11110000,#%11111111,#%00000000
PFRoomMid1
.byte #%00000000,#%00000001,#%00000000
.byte #%00000000,#%00000001,#%00000000
.byte #%00000000,#%00000000,#%00000000
.byte #%00000000,#%00000000,#%00000000
PFRoomMid2
.byte #%11110000,#%11111111,#%00000000
.byte #%00110000,#%00000000,#%00000000
.byte #%00110000,#%00000000,#%00000000
.byte #%00110000,#%00000000,#%00000000
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
; Color data for each line of sprite
ColorFrame0
.byte #$FF;
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
; Color data for each line of sprite
ColorFrame1
.byte #$5F;
.byte #$56;
.byte #$36;
.byte #$36;
.byte #$36;
.byte #$32;
.byte #$32;
.byte #$32;
.byte #$32;
.byte #$c6;
.byte #$c6;
.byte #$c6;
.byte #$c8;
.byte #$c8;
.byte #$c8;
.byte #$02;
.byte #$02;
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,397 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
org $f000
CurRow equ $8f
NumRows equ 5
EnemyRows0 equ $80
EnemyYpos0 equ $85
EnemyXofs0 equ $8a
Temp equ $90
MissileY0 equ $91
MissileY1 equ $92
MissileX0 equ $93
BaseX equ $94
ZPRoutine equ $a0
ZPWrites equ ZPRoutine+(KernelStores-KernelStart)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Start
CLEAN_START
; Copy Grid Kernel routine into RAM so we can modify it.
ldx #KernelEnd-KernelStart-1
CopyRoutineLoop
lda KernelStart,x
sta ZPRoutine,x
dex
bpl CopyRoutineLoop
; Set up interlocuters
lda #$ff
sta EnemyRows0+4
sta EnemyRows0+3
sta EnemyRows0+2
sta EnemyRows0+1
sta EnemyRows0+0
lda #190
sta EnemyYpos0+4
lda #170
sta EnemyYpos0+3
lda #150
sta EnemyYpos0+2
lda #130
sta EnemyYpos0+1
lda #110
sta EnemyYpos0+0
lda #40
sta MissileY0
lda #50
sta MissileY1
lda #65
sta BaseX
lda #$df
sta COLUPF ; use the ball for the player's missile
; Try out the Grid Kernel (not neccessary except for the IDE to get timing)
ldy #0 ; just one line
jsr KernelStart
NextFrame
VERTICAL_SYNC
; VBLANK
TIMER_SETUP 37
jsr ReadControls
jsr MoveMissiles
TIMER_WAIT
; Main frame
TIMER_SETUP 192
jsr DrawEnemyFormation
jsr DrawBelowFormation
jsr DrawPlayerBase
lda #$10
sta COLUBK ; set ground color, just in time!
lda #0
sta ENABL
sta ENAM1 ; turn off missile and ball
; Overscan
TIMER_SETUP 30
TIMER_WAIT
lda #0
sta COLUBK ; clear ground color
; Jump to next frame
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Draw all lines of the enemy formation.
DrawEnemyFormation
lda #4
sta CurRow
; We're going to use the timer to wait for the
; scanline to approach the next row of enemies.
; We'll draw any missiles while we're waiting.
WaitForRow
jsr DrawMissiles
ldx CurRow
lda EnemyYpos0,x
cmp INTIM
bcc WaitForRow
; We've reached the right scanline, so we'll
; set up the NUSIZ registers and clear collisions.
lda #1 ; two copies, close for NUSIZ
sta NUSIZ0
sta NUSIZ1
sta CXCLR ; clear collisions
; WSYNC and draw the row of sprites.
sta WSYNC
jsr DrawFormation
; See if any of the player objects collided with ball.
lda CXP0FB
ora CXP1FB
and #$c0
beq NoCollision
; We've got a collision, now we need to see where it is.
; Our grid spacing is 12 pixels, so we need to divide by 12.
; We'll have to account for the X offset of the sprites, too.
lda MissileX0
ldx #$ff
sec
sbc #13
DivideBy12
inx
sbc #12
bcs DivideBy12
txa
; Now we lookup the bitmask to use, and erase the
; bit that corresponds to the enemy.
tax
lda PowersOf2,x
eor #$ff
ldx CurRow
and EnemyRows0,x
sta EnemyRows0,x
; Now we destroy the missile too.
lda #0
sta MissileY0
NoCollision
dec CurRow
bpl WaitForRow
lda #0 ; turn off two-copy mode
sta NUSIZ0
sta NUSIZ1
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
; Draw a line of the formation.
; First we'll modify the routine in RAM,
; then we'll transfer control to it.
DrawFormation
ldx CurRow
lda EnemyRows0,x
ldx #1 ; start at KernelStores+1
ShiftLoop
ldy #RESP0
ror
bcs NoClearEnemy0
ldy #$30 ; no-op
NoClearEnemy0
sty ZPWrites,x
inx
inx
ldy #RESP1
ror
bcs NoClearEnemy1
ldy #$30 ; no-op
NoClearEnemy1
sty ZPWrites,x
inx
inx
cpx #16 ; 8*2 bytes
bcc ShiftLoop
; Now we jump to our newly-modified kernel in RAM
; to draw the row of sprites.
ldy EnemyColorFrame0 ; get height -> Y
jsr ZPRoutine ; draw sprites
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The Grid Kernel, which we'll copy into RAM at the
; start of the program. We'll use the "STA RESPn,x"
; instruction since it takes up 4 cycles, which gives
; us a horizontal spacing of 12 pixels.
KernelStart
KernelLoop
lda EnemyFrame0,y ; load bitmap
sta WSYNC
ldx EnemyColorFrame0,y ; load color
sta GRP0
sta GRP1
stx COLUP0
stx COLUP1
ldx #0 ; so we can do the STA RESPn,x variant
KernelStores
; These STAs are meant to be modified
sta RESP0,x
sta RESP1,x
sta RESP0,x
sta RESP1,x
sta RESP0,x
sta RESP1,x
sta RESP0,x
sta RESP1,x
; End of modifiable section
dey ; also acts as 2-cycle delay
stx.w GRP0 ; clear player 0 bitmap (4-cycle version)
sta RESP0 ; reset player 0 position
stx GRP1 ; clear player 1 bitmap
sta RESP1 ; reset player 1 position
bpl KernelLoop ; repeat until Y < 0
rts
KernelEnd
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Draw the empty space in between interlocuters
; and the base, which consists of missiles.
DrawBelowFormation
lda #$FF ; set missile colors
sta COLUP0
sta COLUP1
jsr DrawMissiles
lda INTIM
cmp #17 ; are we close to the bottom?
bcs DrawBelowFormation
sta WSYNC ; exit in a known state
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Read the timer to see if we should draw either
; or both missiles.
; We do this in constant time by using the carry flag of
; the CMP operation.
DrawMissiles
lda INTIM ; load timer value
pha
sec
sbc MissileY0
cmp #8 ; within 8 lines of missile?
lda #3 ; bit 1 now set
adc #0 ; if carry set, bit 1 cleared
sta ENABL ; enable/disable ball
pla
sec
sbc MissileY1
cmp #8 ; within 8 lines of missile?
lda #3 ; bit 1 now set
adc #0 ; if carry set, bit 1 cleared
sta ENAM1 ; enable/disable missile
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Draw the player's base
DrawPlayerBase
lda BaseX
ldx #0
jsr SetHorizPos ; first set the horizontal position
ldy ColorFrame0 ; get sprite height
DrawBaseLoop
lda Frame0,y
ldx ColorFrame0,y
sta WSYNC
sta GRP0
stx COLUP0
dey
bpl DrawBaseLoop ; repeat until Y < 0
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Move missiles
; Missile 0 moves up, missile 1 moves down.
MoveMissiles
lda MissileY0
beq NoMoveMiss0
inc MissileY0
NoMoveMiss0
lda MissileY1
beq NoMoveMiss1
dec MissileY1
NoMoveMiss1
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Move player and shoot missiles with joystick/button
ReadControls
ldx BaseX
bit SWCHA ; read joystick
bvs NoMoveLeft ; bit 7 set?
lda #%0
sta REFP0 ; reset sprite flip
dex
bne NoMoveRight
NoMoveLeft
bmi NoMoveRight ; bit 6 set?
lda #%1000
sta REFP0 ; set sprite flip
inx
bmi NoStoreX
NoMoveRight
stx BaseX
NoStoreX
; Shoot a missile when fire button pressed
bit INPT4 ; read button
bmi NoFireButton ; bit 7 set?
lda #10
sta MissileY0 ; reset missile
lda BaseX
clc
adc #6
sta MissileX0
ldx #4
jsr SetHorizPos ; set ball X pos to player's X
NoFireButton
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Set horizontal position of object
SetHorizPos
sta WSYNC ; start a new line
sec ; set carry flag
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl
asl
asl
asl
sta HMP0,x ; set fine offset
sta RESP0,x ; fix coarse position
sta WSYNC
sta HMOVE
sta HMCLR
rts ; return to caller
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Make sure the bitmap tables used by the Grid Kernel
; do not cross page boundaries, because if so the CPU will
; add an extra cycle and mess up our timing!
org $ff00
; Enemy cat-head graphics data
EnemyFrame0
.byte #0
.byte #%00111100;$AE
.byte #%01000010;$AE
.byte #%11100111;$AE
.byte #%11111111;$AC
.byte #%10011001;$8E
.byte #%01111110;$8E
.byte #%11000011;$98
.byte #%10000001;$98
EnemyColorFrame0
.byte #8 ; height
.byte #$AE;
.byte #$AC;
.byte #$A8;
.byte #$AC;
.byte #$8E;
.byte #$8E;
.byte #$98;
.byte #$94;
; Player graphics data, such bitmap
Frame0
.byte #0
.byte #%11111111;$0E
.byte #%11111111;$0E
.byte #%11101111;$0E
.byte #%10010111;$0E
.byte #%10011111;$FE
.byte #%11111111;$FE
.byte #%11010111;$FE
.byte #%01111110;$FA
.byte #%00111110;$FA
.byte #%01000001;$FA
ColorFrame0
.byte #10 ; height
.byte #$0E;
.byte #$0E;
.byte #$0E;
.byte #$0E;
.byte #$FE;
.byte #$FE;
.byte #$FE;
.byte #$FA;
.byte #$FA;
.byte #$FA;
PowersOf2
.byte #$1,#$2,#$4,#$8,#$10,#$20,#$40,#$80
; Epilogue
org $fffc
.word Start
.word Start

548
presets/examples/road.a Normal file
View File

@ -0,0 +1,548 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
TrackFrac .byte ; fractional position along track
Speed .byte ; speed of car
TimeOfDay .word ; 16-bit time of day counter
; Variables for preprocessing step
XPos .word ; 16-bit X position
XVel .word ; 16-bit X velocity
TPos .word ; 16-bit track position
TrackLookahead .byte ; current fractional track increment
; Variables for track generation
Random .byte ; random counter
GenTarget .byte ; target of current curve
GenDelta .byte ; curve increment
GenCur .byte ; current curve value
ZOfs .byte ; counter to draw striped center line
Weather .byte ; bitmask for weather
NumRoadSegments equ 28
; Preprocessing result: X positions for all track segments
RoadX0 REPEAT NumRoadSegments
.byte
REPEND
; Generated track curve data
TrackLen equ 5
TrackData REPEAT TrackLen
.byte
REPEND
InitialSpeed equ 10 ; starting speed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
lda #1
sta Random
lda #InitialSpeed
sta Speed
lda #0
sta TimeOfDay+1
lda #0
sta Weather
NextFrame
VERTICAL_SYNC
; Set up some values for road curve computation,
; since we have some scanline left over.
lda #0
sta XVel
sta XVel+1
sta XPos
lda #70 ; approx. center of screen
sta XPos+1
lda TrackFrac
sta TPos
lda #0
sta TPos+1
lda #10 ; initial lookahead
sta TrackLookahead
; VSYNC+36+198+24 = 4+258 = 262 lines
TIMER_SETUP 36
; Initialize array with X road positions
jsr PreprocessCurve
TIMER_WAIT
; Now draw the main frame
TIMER_SETUP 198
jsr DrawSky
jsr SetupRoadComponents
jsr DrawRoad ; draw the road
TIMER_WAIT
TIMER_SETUP 24
; Advance position on track
; TrackFrac += Speed
lda TrackFrac
clc
adc Speed
sta TrackFrac
bcc .NoGenTrack ; addition overflowed?
jsr GenTrack ; yes, generate new track segment
.NoGenTrack
; TimeOfDay += 1
inc TimeOfDay
bne .NoTODInc
inc TimeOfDay+1
lda TimeOfDay+1
; See if it's nighttime yet, and if the stars come out
clc
adc #8
and #$3f
cmp #$35
ror
sta Weather
.NoTODInc
TIMER_WAIT
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Compute road curve from bottom of screen to horizon.
PreprocessCurve subroutine
ldx #NumRoadSegments-1
.CurveLoop
; Modify X position
; XPos += XVel (16 bit add)
lda XPos
clc
adc XVel
sta XPos
lda XPos+1
adc XVel+1
sta XPos+1
sta RoadX0,x ; store in RoadX0 array
; Modify X velocity (slope)
; XVel += TrackData[TPos]
ldy TPos+1
lda TrackData,y
clc ; clear carry for ADC
bmi .CurveLeft ; track slope negative?
adc XVel
sta XVel
lda XVel+1
adc #0 ; carry +1
jmp .NoCurveLeft
.CurveLeft
adc XVel
sta XVel
lda XVel+1
sbc #0 ; carry -1
nop ; make the branch timings are the same
.NoCurveLeft
sta XVel+1
; Advance TPos (TrackData index)
; TPos += TrackLookahead
lda TPos
clc
adc TrackLookahead
sta TPos
lda TPos+1
adc #0
sta TPos+1
; Go to next segment
inc TrackLookahead ; see further along track
dex
bpl .CurveLoop
rts
; Set road component X positions and enable registers
SetupRoadComponents subroutine
lda RoadX0
sta HMCLR ; clear HMOVE registers
sec
sta WSYNC
.DivideLoop
sbc #15 ; subtract 15
bcs .DivideLoop ; branch while carry still set
adc #15
tay
lda HMoveTable,y ; lookup HMOVE value
sta HMM0 ; set missile 0 fine pos
sta HMBL ; set ball fine pos
sta HMM1 ; set missile 1 fine pos
sta RESM0 ; set missile 0 position
sta RESBL ; set ball position
sta RESM1 ; set missile 1 position
sta WSYNC
sta HMOVE
; Make road components converge at horizon
; This will require an additional HMOVE
lda #$90 ; right 7 pixels
ldy #$70 ; left 7 pixels
ldx #$00 ; no movement
sta HMM0
sty HMM1
stx HMBL
sta WSYNC
sta HMOVE
rts
; Draw the sky, adding clouds and time-of-day colors.
DrawDaytime subroutine
lda TimeOfDay+1 ; offset into sunset color table
and #$3f
tay
lda #16 ; initial height of sky segment
.SkyLoop2
tax ; height -> X
pha ; push original height
.SkyLoop
lda SunsetColors,y ; get sunset color
sta WSYNC ; start scanline
sta COLUBK ; set background color
lda SunsetColors+2,y ; get cloud color
sta COLUPF ; set foreground color
lda CloudPFData0,x ; load clouds -> playfield
sta PF0
lda CloudPFData1,x
sta PF1
lda CloudPFData2,x
sta PF2
dex
bne .SkyLoop ; repeat until sky segment done
iny ; next sky color
tya
and #$3f ; keep sky color in range 0-63
tay ; sky color -> Y
pla ; restore original segment height
sec
sbc #2 ; segment height - 2
cmp #2 ; done with segments?
bcs .SkyLoop2 ; no, repeat
; Draw mountains
; First, load mountain color
lda TimeOfDay+1
lsr
lsr ; divide time-of-day by 4
and #$f ; keep in range 0-15
tax ; -> Y
lda MountainColors,x ; load mountain color
sta COLUPF ; set foreground
lda GroundColors,x ; load ground color
pha ; save it for later
ldx #0
stx PF0
stx PF1 ; to avoid artifacts, we have to
stx PF2 ; clear previous clouds
.MtnLoop
lda SunsetColors,y ; get sunset color
sta WSYNC ; start scanline
sta COLUBK ; set background color
lda MtnPFData0,x ; load mountains -> playfield
sta PF0
lda MtnPFData1,x
sta PF1
lda MtnPFData2,x
sta PF2
iny ; next sky color
tya
and #$3f ; keep sky color in range 0-63
tay ; sky color -> Y
inx
cpx #7 ; only 7 scanlines for the mountains
bne .MtnLoop
; Setup colors and enable road components
pla ; restore ground color
sta COLUBK ; set background
lda #0
sta PF0
sta PF1
sta PF2
rts
DrawSky
bit Weather
bmi DrawNight
jmp DrawDaytime
; Draw the night sky, with stars.
DrawNight subroutine
lda #6
sta ENABL
sta COLUPF
ldy #0
.MoreStars
sta RESBL ; strobe the ball to display a star
adc Start,y ; "randomize" the A register
bmi .Delay1
.Delay1
ror
bcc .Delay2
.Delay2
ror
bcs .Delay3
.Delay3
ror
bcs .Delay4
.Delay4
iny
ldx INTIM
cpx #$89 ; timer says we're done?
bcs .MoreStars ; nope, make more stars
lda #0
sta ENABL ; disable ball
rts
DrawRoad subroutine
lda #2
sta ENAM0
sta ENAM1 ; enable missiles
sta COLUPF
sta COLUP0
sta COLUP1 ; set their colors too
; Draw road
lda TrackFrac
asl
sta WSYNC ; WSYNC so scanline starts at same place each time
asl ; TrackFrac * 4
sta ZOfs ; -> counter for animated stripe
ldx #0 ; 0 is farthest segment
.RoadLoop
lda RoadColors,x ; color of sides and center line
sta COLUP0
sta COLUP1
sta COLUPF
lda RoadX0+1,x ; get next X coordinate
sec
sbc RoadX0,x ; subtract this X coordinate
clc
adc #7 ; add 7
tay ; -> Y
lda HMoveTable-3,y ; left side biased left
sta HMM0
lda HMoveTable,y ; center line
sta HMBL
lda HMoveTable+3,y ; right side biased right
sta HMM1
sta WSYNC
sta HMOVE
sta WSYNC
; Make dashed road stripe by using a counter
; initialized to the fractional track position,
; then subracting the PIA timer as an approximation
; to Z value.
lda ZOfs
sec
sbc INTIM
sta ZOfs ; ZOfs -= timer
rol
rol
rol ; shift left by 3
sta ENABL ; enable ball (bit 2)
sta WSYNC
lda RoadWidths,x ; lookup register for missile size
sta NUSIZ0 ; store missile 0 size
sta NUSIZ1 ; store missile 1 size
sta WSYNC
inx
cpx #NumRoadSegments-1
bne .RoadLoop ; repeat until all segments done
; Clean up road objects
lda #0
sta ENAM0
sta ENAM1
sta ENABL
sta COLUBK
sta NUSIZ0
sta NUSIZ1
rts
; Get next random number
NextRandom subroutine
lda Random
lsr
bcc .NoEor
eor #$d4
.NoEor:
sta Random
rts
; Generate next track byte
GenTrack subroutine
; Shift the existing track data one byte up
; (a[i] = a[i+1])
ldx #0
.ShiftTrackLoop
lda TrackData+1,x
sta TrackData,x
inx
cpx #TrackLen-1
bne .ShiftTrackLoop
; Modify our current track value and
; see if it intersects the target value
lda GenCur
clc
adc GenDelta
cmp GenTarget
beq .ChangeTarget ; target == cur?
bit GenTarget ; we need the sign flag
bmi .TargetNeg ; target<0?
bcs .ChangeTarget ; target>=0 && cur>=target?
bcc .NoChangeTarget ; branch always taken
.TargetNeg
bcs .NoChangeTarget ; target<0 && cur<target?
; Generate a new target value and increment value,
; and make sure the increment value is positive if
; the target is above the current value, and negative
; otherwise
.ChangeTarget
jsr NextRandom ; get a random value
and #$3f ; range 0..63
sec
sbc #$1f ; range -31..32
sta GenTarget ; -> target
cmp GenCur
bmi .TargetBelow ; current > target?
jsr NextRandom ; get a random value
and #$f ; mask to 0..15
jmp .TargetAbove
.TargetBelow
jsr NextRandom
ora #$f0 ; mask to -16..0
.TargetAbove
ora #1 ; to avoid 0 values
sta GenDelta ; -> delta
lda GenCur
.NoChangeTarget
; Store the value in GenCur, and also
; at the end of the TrackData array
sta GenCur
sta TrackData+TrackLen-1
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; HMOVE table from -7 to +8
HMoveTable
hex 7060504030201000f0e0d0c0b0a09080
; Sunset table
SunsetColors
hex 00000000a07060504446383efc0c9f9f
hex aeaeaeaeaeaeaeaeaeaeaeaeaeaeaeae
hex 9f9f9f0cfc3e384644506070a0000000
hex 00000000000000000000000000000000
hex 0000 ; for overflow
MountainColors
hex 00021012f4f6e6264604020000000000
GroundColors
hex 00c0c2c4c6c8c8e6e4e2e00000000000
RoadColors
hex 020202
hex 040404
hex 060606
hex 08080808
hex 0a0a0a0a0a
hex 0c0c0c0c0c0c
hex 0e0e0e0e0e0e0e0e0e
RoadWidths
hex 000000000000
hex 10101010
hex 1010101010
hex 202020202020
hex 202020202020202020
; Cloud data
CloudPFData0
.byte #%10000000
.byte #%11100000
.byte #%10000000
.byte #%00000000
.byte #%00000000
.byte #%10000000
.byte #%11100000
.byte #%00000000
CloudPFData1
.byte #%11000000
.byte #%11110000
.byte #%11100001
.byte #%00000011
.byte #%00000111
.byte #%11000000
.byte #%00000000
.byte #%00011000
CloudPFData2
.byte #%00000000
.byte #%00001110
.byte #%00011111
.byte #%00000111
.byte #%01100000
.byte #%11000000
.byte #%00000110
.byte #%00000000
; Mountain data
MtnPFData0
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%10000000
.byte #%11100000
MtnPFData1
.byte #%00000000
.byte #%00010000
.byte #%00110000
.byte #%01111000
.byte #%11111100
.byte #%11111110
.byte #%11111110
MtnPFData2
.byte #%00000000
.byte #%00000000
.byte #%00000000
.byte #%00000100
.byte #%00101110
.byte #%01111111
.byte #%11111111
CarSprite
.byte 0
.byte #%10000001;--
.byte #%10111101;--
.byte #%11111111;--
.byte #%10000001;--
.byte #%10111101;--
.byte #%01011010;--
.byte #%01011010;--
.byte #%01011010;--
.byte #%00100100;--
.byte #%11111111;--
.byte #%10111101;--
.byte #%00111100;--
; Epilogue
org $fffc
.word Start
.word Start

170
presets/examples/score6.a Normal file
View File

@ -0,0 +1,170 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
Temp .byte
LoopCount .byte ; counts scanline when drawing
; Pointers to bitmap for each digit
Digit0 .word
Digit1 .word
Digit2 .word
Digit3 .word
Digit4 .word
Digit5 .word
BCDScore hex 000000
THREE_COPIES equ %011 ; for NUSIZ registers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
lda #$18
sta COLUP0
lda #$28
sta COLUP1
lda #THREE_COPIES
sta NUSIZ0
sta NUSIZ1
; set horizontal position of player objects
sta WSYNC
SLEEP 26
sta RESP0
sta RESP1
lda #$10
sta HMP1
sta WSYNC
sta HMOVE
sta HMCLR
lda #1
sta VDELP0
sta VDELP1
TIMER_WAIT
TIMER_SETUP 192
jsr GetDigitPtrs ; get pointers
jsr DrawDigits ; draw digits
TIMER_WAIT
TIMER_SETUP 30
lda #1
ldx #0
ldy #0
jsr AddScore
TIMER_WAIT
jmp NextFrame
; Adds value to 6-BCD-digit score.
; A = 1st BCD digit
; X = 2nd BCD digit
; Y = 3rd BCD digit
AddScore subroutine
sed ; enter BCD mode
clc ; clear carry
sta Temp
lda BCDScore
adc Temp
sta BCDScore
stx Temp
lda BCDScore+1
adc Temp
sta BCDScore+1
sty Temp
lda BCDScore+2
adc Temp
sta BCDScore+2
cld ; exit BCD mode
rts
GetDigitPtrs subroutine
ldx #0 ; leftmost bitmap
ldy #2 ; start from most-sigificant BCD value
.Loop
lda BCDScore,y ; get BCD value
and #$f0 ; isolate high nibble (* 16)
lsr ; shift right 1 bit (* 8)
sta Digit0,x ; store pointer lo byte
lda #>FontTable
sta Digit0+1,x ; store pointer hi byte
inx
inx ; next bitmap pointer
lda BCDScore,y ; get BCD value (again)
and #$f ; isolate low nibble
asl
asl
asl ; * 8
sta Digit0,x ; store pointer lo byte
lda #>FontTable
sta Digit0+1,x ; store pointer hi byte
inx
inx ; next bitmap pointer
dey ; next BCD value
bpl .Loop ; repeat until < 0
rts
; Display the resulting 48x8 bitmap
; using the Digit0-5 pointers.
DrawDigits subroutine
sta WSYNC
SLEEP 40 ; start near end of scanline
lda #7
sta LoopCount
BigLoop
ldy LoopCount ; counts backwards
lda (Digit0),y ; load B0 (1st sprite byte)
sta GRP0 ; B0 -> [GRP0]
lda (Digit1),y ; load B1 -> A
sta GRP1 ; B1 -> [GRP1], B0 -> GRP0
sta WSYNC ; sync to next scanline
lda (Digit2),y ; load B2 -> A
sta GRP0 ; B2 -> [GRP0], B1 -> GRP1
lda (Digit5),y ; load B5 -> A
sta Temp ; B5 -> temp
lda (Digit4),y ; load B4
tax ; -> X
lda (Digit3),y ; load B3 -> A
ldy Temp ; load B5 -> Y
sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0
stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1
sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0
sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1
dec LoopCount ; go to next line
bpl BigLoop ; repeat until < 0
lda #0 ; clear the sprite registers
sta GRP0
sta GRP1
sta GRP0
sta GRP1
rts
; Font table for digits 0-9 (8x8 pixels)
align $100 ; make sure data doesn't cross page boundary
FontTable
hex 003c6666766e663c007e181818381818
hex 007e60300c06663c003c66061c06663c
hex 0006067f661e0e06003c6606067c607e
hex 003c66667c60663c00181818180c667e
hex 003c66663c66663c003c66063e66663c
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,198 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
Score0 byte ; BCD score of player 0
Score1 byte ; BCD score of player 1
FontBuf ds 10 ; 2x5 array of playfield bytes
Temp byte
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
lda #$09
sta Score0
lda #$42
sta Score1
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
lda Score0
ldx #0
jsr GetBCDBitmap
lda Score1
ldx #5
jsr GetBCDBitmap
TIMER_WAIT
TIMER_SETUP 192
; Draw 192 scanlines.
; First, we'll draw the scoreboard.
; Put the playfield into score mode (bit 2) which gives
; two different colors for the left/right side of
; the playfield (given by COLUP0 and COLUP1).
lda #%00010010 ; score mode + 2 pixel ball
sta CTRLPF
lda #$48
sta COLUP0 ; set color for left
lda #$a8
sta COLUP1 ; set color for right
; Now we draw all four digits.
ldy #0 ; Y will contain the frame Y coordinate
ScanLoop1a
sta WSYNC
tya
lsr ; divide Y by two for double-height lines
tax ; -> X
lda FontBuf+0,x
sta PF1 ; set left score bitmap
SLEEP 28
lda FontBuf+5,x
sta PF1 ; set right score bitmap
iny
cpy #10
bcc ScanLoop1a
; Clear the playfield
lda #0
sta WSYNC
sta PF1
; Turn playfield reflection off, since our brick field
; will be drawn asymetrically (and turn score mode off)
lda #%00010100 ; no reflection + ball priority + 2 pixel ball
sta CTRLPF
TIMER_WAIT
TIMER_SETUP 30
TIMER_WAIT
jmp NextFrame
; Fetches bitmap data for two digits of a
; BCD-encoded number, storing it in addresses
; FontBuf+x to FontBuf+4+x.
GetBCDBitmap subroutine
; First fetch the bytes for the 1st digit
pha ; save original BCD number
and #$0F ; mask out the least significant digit
sta Temp
asl
asl
adc Temp ; multiply by 5
tay ; -> Y
lda #5
sta Temp ; count down from 5
.loop1
lda DigitsBitmap,y
and #$0F ; mask out leftmost digit
sta FontBuf,x ; store leftmost digit
iny
inx
dec Temp
bne .loop1
; Now do the 2nd digit
pla ; restore original BCD number
lsr
lsr
lsr
lsr ; shift right by 4 (in BCD, divide by 10)
sta Temp
asl
asl
adc Temp ; multiply by 5
tay ; -> Y
dex
dex
dex
dex
dex ; subtract 5 from X (reset to original)
lda #5
sta Temp ; count down from 5
.loop2
lda DigitsBitmap,y
and #$F0 ; mask out leftmost digit
ora FontBuf,x ; combine left and right digits
sta FontBuf,x ; store combined digits
iny
inx
dec Temp
bne .loop2
rts
org $FF00
; Bitmap pattern for digits
DigitsBitmap
.byte $0E ; | XXX |
.byte $0A ; | X X |
.byte $0A ; | X X |
.byte $0A ; | X X |
.byte $0E ; | XXX |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $EE ; |XXX XXX |
.byte $22 ; | X X |
.byte $EE ; |XXX XXX |
.byte $88 ; |X X |
.byte $EE ; |XXX XXX |
.byte $EE ; |XXX XXX |
.byte $22 ; | X X |
.byte $66 ; | XX XX |
.byte $22 ; | X X |
.byte $EE ; |XXX XXX |
.byte $AA ; |X X X X |
.byte $AA ; |X X X X |
.byte $EE ; |XXX XXX |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $EE ; |XXX XXX |
.byte $88 ; |X X |
.byte $EE ; |XXX XXX |
.byte $22 ; | X X |
.byte $EE ; |XXX XXX |
.byte $EE ; |XXX XXX |
.byte $88 ; |X X |
.byte $EE ; |XXX XXX |
.byte $AA ; |X X X X |
.byte $EE ; |XXX XXX |
.byte $EE ; |XXX XXX |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $22 ; | X X |
.byte $EE ; |XXX XXX |
.byte $AA ; |X X X X |
.byte $EE ; |XXX XXX |
.byte $AA ; |X X X X |
.byte $EE ; |XXX XXX |
.byte $EE ; |XXX XXX |
.byte $AA ; |X X X X |
.byte $EE ; |XXX XXX |
.byte $22 ; | X X |
.byte $EE ; |XXX XXX |
; Epilogue
org $fffc
.word Start
.word Start

77
presets/examples/sprite.a Normal file
View File

@ -0,0 +1,77 @@
processor 6502
include "vcs.h"
include "macro.h"
org $f000
; Sprites are a tricky beast on the 2600.
; You only have two of them.
; They are 8 bits wide and 1 bit high.
; There's no way to position the X or Y coordinate directly.
; To position X, you have to wait until the TIA clock hits a
; given X position and then write to a register to lock it in.
; To position Y, you simply wait until the desired scanline and
; set the bits of your sprite to a non-zero value.
; Having fun yet? :)
Counter equ $81
Start CLEAN_START
NextFrame
; This macro efficiently gives us 3 lines of VSYNC
VERTICAL_SYNC
; 37 lines of VBLANK
ldx #37
LVBlank sta WSYNC
dex
bne LVBlank
; For the last VBLANK line, we'll lock in our sprites' X position.
; The first one will be 20 TIA clocks from the left, plus whatever the
; DEX and BNE instructions in the previous loop took.
; There are 68 TIA clocks *before* the scanline starts, and there are
; 3 TIA clocks per CPU cycle, so counting clocks (or trial-and-error)
; is neccessary to get exact positioning.
SLEEP 20 ; this macro hard-codes a 20 cycle delay (60 TIA clocks)
sta RESP0 ; set position of sprite #1
SLEEP 35 ; wait 35 more cycles (105 TIA clocks)
sta RESP1 ; set position of sprite #2
; Draw the 192 scanlines
; We'll draw both sprites all the way down the screen
ldx #192
lda #0 ; changes every scanline
ldy Counter ; changes every frame
ScanLoop
sta WSYNC ; wait for next scanline
sta COLUBK ; set the background color
sta GRP0 ; set sprite 0 pixels
sta GRP1 ; set sprite 1 pixels
adc #1 ; increment A to cycle through colors and bitmaps
dex
bne ScanLoop
; Clear the background color and sprites before overscan
; (We don't need to use VBLANK neccessarily...)
stx COLUBK
stx GRP0
stx GRP1
; 30 lines of overscan
ldx #30
LVOver sta WSYNC
dex
bne LVOver
; Cycle the sprite colors for the next frame
inc Counter
lda Counter
sta COLUP0
sta COLUP1
jmp NextFrame
org $fffc
.word Start
.word Start

100
presets/examples/timing1.a Normal file
View File

@ -0,0 +1,100 @@
processor 6502
include "vcs.h"
include "macro.h"
org $f000
; We're going to try to animate sprites horizontally.
; Remember, we have to pause the CPU until the exact moment the
; scanline hits the desired horizontal position of the sprite.
; Since we can't hard-code the SLEEP macro we'll have to do it
; dynamically somehow. But since the TIA beam is racing so much
; faster than the CPU clock, we'll have to be clever.
counter equ $81
start CLEAN_START
nextframe
VERTICAL_SYNC
; 37 lines of VBLANK
ldx #35
lvblank sta WSYNC
dex
bne lvblank
; This will be the 36th VBLANK. We'll use up some of this scanline's
; time to set up for the next line, where we'll set the sprite position.
; For now the position will be in CPU clocks, not TIA clocks.
; We're going to use 'counter' as the horiz. position, load into A.
lda counter
ror ; divide frame counter by 4 to slow animation
ror
and #$3f ; now in range (0-63)
sta WSYNC ; wait for next line
; This is the 37th VBLANK where we'll set the sprite position.
; We've got our desired horizontal position in A.
; First we divide it by 4. We'll need it later in our delay loop.
; But we'll use the remainder bits to add cycles.
; The first bit will add either 0 or 1 CPU cycles.
lsr ; shift right, bit 0 goes into carry flag
bcs delay1 ; branch to next insn if carry set - adds +1 cycle
delay1
; The second bit will add either 0 or 2 CPU cycles.
lsr
bcc delay2 ; branch if carry clear - subtract -1 cycle
bcs delay2 ; guaranteed to succeed - adds +3 cycles
delay2
; So now we've used the remainder of our divide-by-4 operations to
; add between 0 and 3 CPU cycles (0-9 TIA clocks).
; The next loop takes 5 CPU cycles per iteration (15 TIA clocks).
tax ; transfer A to X
delayx dex ; decrement X
bpl delayx ; branch while X is non-negative
sta RESP0 ; set position of sprite #1
sta WSYNC ; end of 37th line
; The TIA clocks 3 pixels for every 1 CPU clock.
; So our final sprite position is C + (X%4)*3 + (X/4)*15
; (C is the fixed # of instruction clocks)
; We've lost a bit of resolution in our positioning, since we
; divided the horizontal position by 4 but our tightest loop takes
; 5 cycles per iteration. We'll achieve finer control later using
; some additional TIA registers.
; Now draw the 192 scanlines, drawing the sprite.
; We've already set its horizontal position for the entire frame.
ldx #192
lda #0 ; changes every scanline
ldy counter ; changes every frame
lvscan
sta WSYNC ; wait for next scanline
sta COLUBK ; set the background color
sta GRP0 ; set sprite 0 pixels
adc #1 ; increment A to cycle through colors and bitmaps
dex
bne lvscan
; Clear the background color and sprites before overscan
stx COLUBK
stx GRP0
; 30 lines of overscan
ldx #30
lvover sta WSYNC
dex
bne lvover
; Cycle the sprite colors for the next frame
inc counter
lda counter
sta COLUP0
jmp nextframe
org $fffc
.word start
.word start

177
presets/examples/timing2.a Normal file
View File

@ -0,0 +1,177 @@
processor 6502
include "vcs.h"
include "macro.h"
org $f000
; We're going to use a more clever way to position sprites
; ("players") which relies on additional TIA features.
; Because the CPU timing is 3 times as coarse as the TIA's,
; we can only access 1 out of 3 possible positions using
; CPU delays alone.
; Additional TIA registers let us nudge the final position
; by discrete TIA clocks and thus target all 160 positions.
counter equ $81
start CLEAN_START
nextframe
VERTICAL_SYNC
; 37 lines of VBLANK
ldx #35
lvblank sta WSYNC
dex
bne lvblank
; Instead of representing the horizontal position in CPU clocks,
; we're going to use TIA clocks.
lda counter ; load the counter as horizontal position
and #$7f ; force range to (0-127)
; We're going to divide the horizontal position by 15.
; The easy way on the 6502 is to subtract in a loop.
; Note that this also conveniently adds 5 CPU cycles
; (15 TIA clocks) per iteration.
sta WSYNC ; 36th line
sta HMCLR ; reset the old horizontal position
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
; A now contains (the remainder - 15).
; We'll convert that into a fine adjustment, which has
; the range -8 to +7.
eor #7
asl ; HMOVE only uses the top 4 bits, so shift by 4
asl
asl
asl
; The fine offset goes into HMP0
sta HMP0
; Now let's fix the coarse position of the player, which as you
; remember is solely based on timing. If you rearrange any of the
; previous instructions, position 0 won't be exactly on the left side.
sta RESP0
; Finally we'll do a WSYNC followed by HMOVE to apply the fine offset.
sta WSYNC ; 37th line
sta HMOVE ; apply offset
; We'll see this method again, and it can be made into a subroutine
; that works on multiple objects.
; Now draw the 192 scanlines, drawing the sprite.
; We've already set its horizontal position for the entire frame,
; but we'll try to draw something real this time, some digits
; lifted from another game.
ldx #192
lda #0 ; changes every scanline
ldy #0 ; sprite data index
lvscan
sta WSYNC ; wait for next scanline
sty COLUBK ; set the background color
lda NUMBERS,y
sta GRP0 ; set sprite 0 pixels
iny
cpy #60
bcc wrap1
ldy #0
wrap1
dex
bne lvscan
; Clear the background color and sprites before overscan
stx COLUBK
stx GRP0
; 30 lines of overscan
ldx #30
lvover sta WSYNC
dex
bne lvover
; Cycle the sprite colors for the next frame
inc counter
lda counter
sta COLUP0
jmp nextframe
; Bitmap pattern for digits
NUMBERS .byte $0E ; | XXX | $F5C5 Leading zero is not drawn
.byte $0A ; | X X | $F5C6 because it's never used.
.byte $0A ; | X X | $F5C7
.byte $0A ; | X X | $F5C8
.byte $0E ; | XXX | $F5C9
.byte $00
.byte $22 ; | X X | $F5CA
.byte $22 ; | X X | $F5CB
.byte $22 ; | X X | $F5CC
.byte $22 ; | X X | $F5CD
.byte $22 ; | X X | $F5CE
.byte $00
.byte $EE ; |XXX XXX | $F5CF
.byte $22 ; | X X | $F5D0
.byte $EE ; |XXX XXX | $F5D1
.byte $88 ; |X X | $F5D2
.byte $EE ; |XXX XXX | $F5D3
.byte $00
.byte $EE ; |XXX XXX | $F5D4
.byte $22 ; | X X | $F5D5
.byte $66 ; | XX XX | $F5D6
.byte $22 ; | X X | $F5D7
.byte $EE ; |XXX XXX | $F5D8
.byte $00
.byte $AA ; |X X X X | $F5D9
.byte $AA ; |X X X X | $F5DA
.byte $EE ; |XXX XXX | $F5DB
.byte $22 ; | X X | $F5DC
.byte $22 ; | X X | $F5DD
.byte $00
.byte $EE ; |XXX XXX | $F5DE
.byte $88 ; |X X | $F5DF
.byte $EE ; |XXX XXX | $F5E0
.byte $22 ; | X X | $F5E1
.byte $EE ; |XXX XXX | $F5E2
.byte $00
.byte $EE ; |XXX XXX | $F5E3
.byte $88 ; |X X | $F5E4
.byte $EE ; |XXX XXX | $F5E5
.byte $AA ; |X X X X | $F5E6
.byte $EE ; |XXX XXX | $F5E7
.byte $00
.byte $EE ; |XXX XXX | $F5E8
.byte $22 ; | X X | $F5E9
.byte $22 ; | X X | $F5EA
.byte $22 ; | X X | $F5EB
.byte $22 ; | X X | $F5EC
.byte $00
.byte $EE ; |XXX XXX | $F5ED
.byte $AA ; |X X X X | $F5EE
.byte $EE ; |XXX XXX | $F5EF
.byte $AA ; |X X X X | $F5F0
.byte $EE ; |XXX XXX | $F5F1
.byte $00
.byte $EE ; |XXX XXX | $F5F2
.byte $AA ; |X X X X | $F5F3
.byte $EE ; |XXX XXX | $F5F4
.byte $22 ; | X X | $F5F5
.byte $EE ; |XXX XXX | $F5F6
.byte $00
; Epilogue
org $fffc
.word start
.word start
; QUESTION: What if you don't set the fine offset?
; QUESTION: What if you don't set the coarse offset?

View File

@ -0,0 +1,205 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
Temp byte
WriteOfs byte ; offset into dest. array FontBuf
WriteShift byte ; which nibble to write into
LoopCount byte ; counts scanline when drawing
StrPtr word ; pointer to text string
StrLen byte ; counts chars when building string
FontBuf ds 30 ; 30-byte buffer for generated bitmap
THREE_COPIES equ %011 ; for NUSIZ registers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
lda #4
sta LoopCount
lda #$80
sta COLUBK
lda #$18
sta COLUP0
lda #$28
sta COLUP1
lda #THREE_COPIES
sta NUSIZ0
sta NUSIZ1
sta WSYNC
SLEEP 20
sta RESP0
sta RESP1
lda #$10
sta HMP1
sta WSYNC
sta HMOVE
sta HMCLR
lda #1
sta VDELP0
sta VDELP1
TIMER_WAIT
TIMER_SETUP 192
; Build the 48x5 bitmap in memory
lda #<String0
sta StrPtr
lda #>String0
sta StrPtr+1
jsr BuildLine
; Display the resulting 48x5 bitmap
sta WSYNC
SLEEP 40 ; start near end of scanline
BigLoop
ldy LoopCount ; counts backwards
lda FontBuf+0,y ; load B0 (1st sprite byte)
sta GRP0 ; B0 -> [GRP0]
lda FontBuf+5,y ; load B1 -> A
sta GRP1 ; B1 -> [GRP1], B0 -> GRP0
sta WSYNC ; sync to next scanline
lda FontBuf+10,y ; load B2 -> A
sta GRP0 ; B2 -> [GRP0], B1 -> GRP1
lda FontBuf+25,y ; load B5 -> A
sta Temp ; B5 -> temp
ldx FontBuf+20,y ; load B4 -> X
lda FontBuf+15,y ; load B3 -> A
ldy Temp ; load B5 -> Y
sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0
stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1
sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0
sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1
dec LoopCount ; go to next line
bpl BigLoop ; repeat until < 0
lda #0
sta GRP0
sta GRP1
sta GRP0
sta GRP1
TIMER_WAIT
TIMER_SETUP 30
TIMER_WAIT
jmp NextFrame
; Create the 48x5 bitmap of a line of text, using
; 8 characters pointed to by StrPtr.
; The bitmap is stored in a 30-byte array starting at FontBuf.
BuildLine subroutine
lda #0
sta WriteOfs ; offset into dest. array FontBuf
sta WriteShift ; which nibble to write to (0=hi, $FF=lo)
lda #11
sta StrLen ; start at 11th character, go in reverse
.CharLoop
ldy StrLen
lda (StrPtr),y ; load next character
sec
sbc #32 ; subtract 32 (1st char is Space)
; Get offset into FontTable.
; We use the Carry flag to track which nibble we
; read from. It alternates with every line.
sta Temp
asl
asl
adc Temp ; multiply by 5
ror ; divide by 2 and set carry flag
tay ; font table byte offset -> Y
; and bit offset -> Carry flag
; Write the character to FontBuf
lda #5
sta Temp ; write 5 lines
ldx WriteOfs ; starting offset into FontBuf
; We use the earlier carry bit from the division by 2
; to track which nibble (4-bit half) we're in.
.Loop
lda FontTable,y
bcc .CClear ; carry clear, so low nibble
lsr
lsr
lsr
lsr ; shift high nibble into low nibble
iny ; go to next font table byte
clc ; clear carry bit
hex 04 ; NOP aa (skips next instruction)
.CClear
sec ; set carry bit
.CSet
and #$0f ; isolate low nibble
; Now we have to write the nibble we just loaded.
; Depending on the value of WriteShift, we may store this
; directly in memory or we may combine it with a previously
; stored value.
bit WriteShift
bpl .Shift ; WriteShift clear, so shift output
ora FontBuf,x ; combine with previously stored nibble
jmp .NoShift
.Shift
php ; save flags (we only care about Carry flag)
asl
asl
asl
asl ; shift low nibble to high nibble
plp ; restore flags
.NoShift
sta FontBuf,x ; store result
inx ; go to next output line
dec Temp
bne .Loop ; repeat until all lines done
.SkipChar
; Our next write target will be the next nibble (4 bits).
; If we've already done the high nibble, skip ahead 5 bytes.
lda WriteShift
eor #$FF
sta WriteShift
bne .NoIncOfs
lda WriteOfs
clc
adc #5
sta WriteOfs
.NoIncOfs
; Repeat until we run out of characters.
dec StrLen
bpl .CharLoop
rts
align $100 ; make sure data doesn't cross page boundary
; Packed font table. Each character consists of 5 nibbles
; (4 bits each) packed into bytes. So each character is
; actually 2.5 bytes long.
FontTable:
hex 00004040445500505757626313244153
hex 67460400424442224225052027002400
hex 70000004004024115655232226471266
hex 21611157656174574743247157576771
hex 75040440020221247170002421242071
hex 43575275255656364434565576747444
hex 47377534555775227252115165554744
hex 54755775772555254456365725655766
hex 21342222375555225555775555522522
hex 55471277447421047011712500700000
String0:
dc "UOY EREHT IH"
; Epilogue
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,223 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
Temp .byte
WriteOfs .byte ; offset into dest. array FontBuf
LoopCount .byte ; counts scanline when drawing
StrPtr .word ; pointer to text string
StrLen .byte ; counts chars when building string
TempSP .byte ; temp. storage for stack pointer
FontBuf ; 30 bytes for text bitmap
REPEAT 30
.byte
REPEND
LoChar equ #41 ; lowest character value
HiChar equ #90 ; highest character value
THREE_COPIES equ %011 ; for NUSIZ registers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
NextFrame
VERTICAL_SYNC
TIMER_SETUP 37
lda #$18
sta COLUP0
lda #$28
sta COLUP1
lda #THREE_COPIES
sta NUSIZ0
sta NUSIZ1
sta WSYNC
SLEEP 20
sta RESP0
sta RESP1
lda #$10
sta HMP1
sta WSYNC
sta HMOVE
sta HMCLR
lda #1
sta VDELP0
sta VDELP1
TIMER_WAIT
TIMER_SETUP 192
lda #<String0
sta StrPtr
lda #>String0
sta StrPtr+1
jsr BuildText ; build the 48x5 bitmap
jsr DrawText ; draw the bitmap
lda #<String1
sta StrPtr
lda #>String1
sta StrPtr+1
jsr BuildText ; build the 48x5 bitmap
jsr DrawText ; draw the bitmap
TIMER_WAIT
TIMER_SETUP 30
TIMER_WAIT
jmp NextFrame
; Create the 48x5 bitmap of a line of text, using
; 12 characters pointed to by StrPtr.
; The bitmap is stored in a 30-byte array starting at FontBuf.
BuildText subroutine
lda #$80
sta COLUBK
; First, save the original stack pointer.
tsx
stx TempSP
; Initialize WriteOfs (dest offset)
; and StrLen (source offset)
lda #FontBuf+4 ; +4 because PHA goes in decreasing order
sta WriteOfs ; offset into dest. array FontBuf
ldy #0
sty StrLen ; start at first character
.CharLoop
; Get next pair of characters.
; Get first character
lda (StrPtr),y ; load next character
sec
sbc #LoChar ; subtract 32 (1st char is Space)
sta Temp
asl
asl
adc Temp ; multiply by 5
tax ; first character offset -> X
; Get second character
iny
lda (StrPtr),y ; load next character
sec
sbc #LoChar ; subtract 32 (1st char is Space)
sta Temp
asl
asl
adc Temp ; multiply by 5
iny
sty StrLen ; StrLen += 2
tay ; second character offset -> Y
; Setup stack pointer so that successive PHA copy bytes
; to decreasing addresses, starting at WriteOfs.
txa
ldx WriteOfs
txs
tax
; Write the character to FontBuf, copying all 5 bytes.
; Because we are using PHA, we have essentially three
; registers to work with.
lda FontTableLo+4,y
ora FontTableHi+4,x
pha
lda FontTableLo+3,y
ora FontTableHi+3,x
pha
lda FontTableLo+2,y
ora FontTableHi+2,x
pha
lda FontTableLo+1,y
ora FontTableHi+1,x
pha
lda FontTableLo+0,y
ora FontTableHi+0,x
pha
; Go to next WriteOfs (skip 5 bytess)
lda WriteOfs
clc
adc #5
sta WriteOfs
.NoIncOfs
; Repeat until we run out of characters.
ldy StrLen
cpy #12
bne .CharLoop
; Restore stack pointer.
ldx TempSP
txs
rts
; Display the resulting 48x5 bitmap from FontBuf
DrawText subroutine
sta WSYNC
SLEEP 40 ; start near end of scanline
lda #4
sta LoopCount
BigLoop
ldy LoopCount ; counts backwards
lda FontBuf+0,y ; load B0 (1st sprite byte)
sta GRP0 ; B0 -> [GRP0]
lda FontBuf+5,y ; load B1 -> A
sta GRP1 ; B1 -> [GRP1], B0 -> GRP0
sta WSYNC ; sync to next scanline
lda FontBuf+10,y ; load B2 -> A
sta GRP0 ; B2 -> [GRP0], B1 -> GRP1
lda FontBuf+25,y ; load B5 -> A
sta Temp ; B5 -> temp
ldx FontBuf+20,y ; load B4 -> X
lda FontBuf+15,y ; load B3 -> A
ldy Temp ; load B5 -> Y
sta GRP1 ; B3 -> [GRP1]; B2 -> GRP0
stx GRP0 ; B4 -> [GRP0]; B3 -> GRP1
sty GRP1 ; B5 -> [GRP1]; B4 -> GRP0
sta GRP0 ; ?? -> [GRP0]; B5 -> GRP1
dec LoopCount ; go to next line
bpl BigLoop ; repeat until < 0
lda #0 ; clear the sprite registers
sta GRP0
sta GRP1
sta GRP0
sta GRP1
rts
; Unpacked font tables. Each character consists of 5 nibbles
; packed into 5 bytes. There's a table with the bitmap data in
; the low nibble, and one with the bitmap data in the high nibble.
align $100 ; make sure data doesn't cross page boundary
FontTableLo
hex 0402020204050205000002070200000402000000070000000004000000000404
hex 0201010605050503020202060207040201060601020106010107050506010604
hex 0707050704030404020107070507050706010705070400040000040200020001
hex 0204020107000700000402010204020002010703040705020505070502060506
hex 0506030404040306050505060704070407040407040703050704030505070505
hex 0702020207020501010105050605050704040404050507070505070707050205
hex 0505020404060506030705050205060705060601020403020202020703050505
hex 0502020505050507070505050502050502020205050704020107000000000000
align $100 ; make sure data doesn't cross page boundary
FontTableHi
hex 4020202040502050000020702000004020000000700000000040000000004040
hex 2010106050505030202020602070402010606010201060101070505060106040
hex 7070507040304040201070705070507060107050704000400000402000200010
hex 2040201070007000004020102040200020107030407050205050705020605060
hex 5060304040403060505050607040704070404070407030507040305050705050
hex 7020202070205010101050506050507040404040505070705050707070502050
hex 5050204040605060307050502050607050606010204030202020207030505050
hex 5020205050505070705050505020505020202050507040201070000000000000
String0 dc "HELLO[WORLD?"
String1 dc "TEXT[IS[NICE"
; Epilogue
org $fffc
.word Start
.word Start

79
presets/examples/vsync.a Normal file
View File

@ -0,0 +1,79 @@
processor 6502
include "vcs.h"
include "macro.h"
org $f000
; Now we're going to drive the TV signal properly.
; Assuming NTSC standards, we need the following:
; - 3 scanlines of VSYNC
; - 37 blank lines
; - 192 visible scanlines
; - 30 blank lines
; We'll use the VSYNC register to generate the VSYNC signal,
; and the VBLANK register to force a blank screen above
; and below the visible frame (it'll look letterboxed on
; the emulator, but not on a real TV)
; Let's define a variable to hold the starting color
; at memory address $81
BGColor equ $81
; The CLEAN_START macro zeroes RAM and registers
Start CLEAN_START
NextFrame
; Enable VBLANK (disable output)
lda #2
sta VBLANK
; At the beginning of the frame we set the VSYNC bit...
lda #2
sta VSYNC
; And hold it on for 3 scanlines...
sta WSYNC
sta WSYNC
sta WSYNC
; Now we turn VSYNC off.
lda #0
sta VSYNC
; Now we need 37 lines of VBLANK...
ldx #37
LVBlank sta WSYNC ; accessing WSYNC stops the CPU until next scanline
dex ; decrement X
bne LVBlank ; loop until X == 0
; Re-enable output (disable VBLANK)
lda #0
sta VBLANK
; 192 scanlines are visible
; We'll draw some rainbows
ldx #192
lda BGColor ; load the background color out of RAM
ScanLoop
adc #1 ; add 1 to the current background color in A
sta COLUBK ; set the background color
sta WSYNC ; WSYNC doesn't care what value is stored
dex
bne ScanLoop
; Enable VBLANK again
lda #2
sta VBLANK
; 30 lines of overscan to complete the frame
ldx #30
LVOver sta WSYNC
dex
bne LVOver
; The next frame will start with current color value - 1
; to get a downwards scrolling effect
dec BGColor
; Go back and do another frame
jmp NextFrame
org $fffc
.word Start
.word Start

View File

@ -0,0 +1,117 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
seg.u Variables
org $80
Cycle0Lo byte
Cycle1Lo byte
Cycle2Lo byte
Cycle3Lo byte
Cycle0Hi byte
Cycle1Hi byte
Cycle2Hi byte
Cycle3Hi byte
Delta0Lo byte
Delta1Lo byte
Delta2Lo byte
Delta3Lo byte
Delta0Hi byte
Delta1Hi byte
Delta2Hi byte
Delta3Hi byte
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg Code
org $f000
Start
CLEAN_START
lda #0
sta Delta0Hi
lda #137
sta Delta0Lo
lda #0
sta Delta1Hi
lda #172
sta Delta1Lo
lda #0
sta Delta2Hi
lda #217
sta Delta2Lo
lda #1
sta Delta2Hi
lda #273-256
sta Delta2Lo
NextFrame
sta WSYNC
jsr NextSamples
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Macro that mixes two software voices
; and sets the volume for one TIA audio channel.
; Usage: NEXTSAMPLEPAIR {audiochannel} {voice1} {voice2}
MAC NEXTSAMPLEPAIR
; Get first channel phase, put in X
lda Cycle0Lo+{2}
clc
adc Delta0Lo+{2}
sta Cycle0Lo+{2}
lda Cycle0Hi+{2}
adc Delta0Hi+{2}
and #$1F
sta Cycle0Hi+{2}
tax
; Get second channel phase, put in Y
lda Cycle0Lo+{3}
clc
adc Delta0Lo+{3}
sta Cycle0Lo+{3}
lda Cycle0Hi+{3}
adc Delta0Hi+{3}
and #$1F
sta Cycle0Hi+{3}
tay
; Lookup wavetable entry and sum
lda Wavetable,y
clc
adc Wavetable,x
; Divide by 2 and store to volume register
lsr
sta AUDV0+{1}
ENDM
NextSamples
NEXTSAMPLEPAIR 0,0,1 ; mix voices 0 and 1
NEXTSAMPLEPAIR 1,2,3 ; mix voices 2 and 3
rts
; 32-byte wavetable
Wavetable
hex 00010203 04050607 08090a0b 0c0d0e0f
hex 0f0e0d0c 0b0a0908 07060504 03020100
; Table of note periods
align $100
NoteDeltas
word 9, 9, 10, 10, 11, 11, 12, 13, 14, 14, 15, 16
word 17, 18, 19, 20, 22, 23, 24, 26, 27, 29, 30, 32
word 34, 36, 38, 41, 43, 46, 48, 51, 54, 57, 61, 65
word 68, 72, 77, 81, 86, 91, 97, 102, 108, 115, 122, 129
word 137, 145, 153, 163, 172, 182, 193, 205, 217, 230, 244, 258
word 273, 290, 307, 325, 344, 365, 387, 410, 434, 460, 487, 516
word 547, 579, 614, 650, 689, 730, 773, 819, 868, 920, 974, 1032
word 1093, 1159, 1227, 1300, 1378, 1460, 1546, 1638, 1736, 1839, 1948, 2064
word 2187, 2317, 2455, 2601, 2755, 2919, 3093, 3277, 3472, 3678, 3897, 4128
word 4374, 4634, 4910, 5202, 5511, 5839, 6186, 6554, 6943, 7356, 7793, 8257
word 8748, 9268, 9819, 10403, 11022, 11677, 12371, 13107
; Epilogue
org $fffc
.word Start
.word Start

45
presets/skeleton.a Normal file
View File

@ -0,0 +1,45 @@
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Variables segment
seg.u Variables
org $80
Temp .byte
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Code segment
seg Code
org $f000
Start
CLEAN_START
NextFrame
lsr SWCHB ; test Game Reset switch
bcc Start ; reset?
; 3 lines of VSYNC
VERTICAL_SYNC
; 37 lines of underscan
TIMER_SETUP 37
TIMER_WAIT
; 192 lines of frame
TIMER_SETUP 192
TIMER_WAIT
; 30 lines of overscan
TIMER_SETUP 30
TIMER_WAIT
; go to next frame
jmp NextFrame
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Epilogue
org $fffc
.word Start ; reset vector
.word Start ; BRK vector

30
share.php Normal file
View File

@ -0,0 +1,30 @@
<?php
$OUTDIR = $_SERVER['DOCUMENT_ROOT'] . '/.storage';
$filename = $_POST['filename'];
$text = $_POST['text'];
if (!$filename || !$text) {
http_response_code(400);
die;
}
$rec = array(
'filename' => $filename,
'text' => $text,
);
$json = json_encode($rec);
$hash = md5($json);
$outfn = "$OUTDIR/$hash";
$result = file_put_contents($outfn, $json);
if (!$result) {
http_response_code(500);
die;
}
header('Content-Type: text/json');
echo json_encode(array('key' => $hash));
?>

755
src/ui.js Normal file
View File

@ -0,0 +1,755 @@
var worker = new Worker("./src/worker/workermain.js");
var current_output = null;
var current_preset_idx = -1; // TODO: use URL
var current_preset_id = null;
var offset2line = null;
var line2offset = null;
var trace_pending_at_pc;
var PRESETS = [
{id:'examples/hello', chapter:4, name:'Hello 6502 and TIA'},
{id:'examples/vsync', chapter:5, name:'VSYNC and Color Bars', title:'Color Bars'},
{id:'examples/playfield', chapter:6, name:'The Playfield'},
{id:'examples/sprite', chapter:7, name:'Sprite Basics'},
{id:'examples/timing2', chapter:9, name:'Sprite Positioning', title:'Sprite Position'},
{id:'examples/missiles', chapter:10, name:'Two Missiles and a Ball', title:'Missles and Ball'},
{id:'examples/complexscene', chapter:15, name:'Playfield + Sprite I', title:'PF+Sprite 1'},
{id:'examples/complexscene2', chapter:16, name:'Playfield + Sprite II', title:'PF+Sprite 2'},
{id:'examples/scoreboard', chapter:18, name:'Scoreboard'},
{id:'examples/collisions', chapter:19, name:'Collisions'},
{id:'examples/bitmap', chapter:20, name:'Fullscreen Bitmap'},
{id:'examples/brickgame', chapter:21, name:'Brick Game'},
// {id:'examples/multisprite1', chapter:8, name:'Sprite Kernel'},
{id:'examples/bigsprite', chapter:22, name:'48-Pixel Sprite'},
{id:'examples/tinyfonts2', chapter:23, name:'Tiny Fonts'},
{id:'examples/score6', chapter:24, name:'6-Digit Score'},
{id:'examples/retrigger', chapter:26, name:'Formation Flying'},
// {id:'examples/tinyfonts', chapter:23, name:'Tiny Fonts, Slow'},
{id:'examples/multisprite3', chapter:28, name:'Multple Sprites'},
{id:'examples/procgen1', chapter:30, name:'Procedural Generation'},
{id:'examples/lines', chapter:31, name:'Drawing Lines'},
// {id:'examples/piatable', name:'Timer Table'},
{id:'examples/musicplayer', chapter:32, name:'Music Player'},
{id:'examples/road', chapter:33, name:'Pseudo 3D Road'},
{id:'examples/bankswitching', chapter:35, name:'Bankswitching'},
{id:'examples/wavetable', chapter:36, name:'Wavetable Sound'},
// {id:'examples/fullgame', name:'Thru Hike: The Game', title:'Thru Hike'},
];
Javatari.SHOW_ERRORS = false;
Javatari.CARTRIDGE_CHANGE_DISABLED = true;
Javatari.DEBUG_SCANLINE_OVERFLOW = false; // TODO: make a switch
Javatari.AUDIO_BUFFER_SIZE = 256;
var CODE = 'code1';
var editor = CodeMirror(document.getElementById('editor'), {
mode: '6502',
theme: 'mbo',
lineNumbers: true,
tabSize: 8,
gutters: ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"],
});
//editor.setSize("100%", "95%"); // TODO
editor.on('changes', function(ed, changeobj) {
text = editor.getValue() || "";
setCode(text);
});
function getCurrentPresetTitle() {
if (current_preset_idx < 0)
return "ROM";
else
return PRESETS[current_preset_idx].title || PRESETS[current_preset_idx].name || "ROM";
}
function setLastPreset(id) {
localStorage.setItem("__lastid", id);
}
function updatePreset(current_preset_id, text) {
if (text.trim().length) {
localStorage.setItem(current_preset_id, text);
}
}
function loadCode(text) {
editor.setValue(text);
current_output = null;
setCode(text);
}
function loadFile(fileid, filename, index) {
current_preset_id = fileid;
current_preset_idx = index;
var text = (localStorage.getItem(fileid) || "");
if (text) {
loadCode(text);
setLastPreset(fileid);
} else if (!text && index >= 0) {
filename += ".a";
console.log("Loading preset", fileid, filename, index, PRESETS[index]);
if (text.length == 0) {
console.log("Fetching", filename);
$.get( filename, function( text ) {
console.log("GET",text.length,'bytes');
loadCode(text);
setLastPreset(fileid);
}, 'text');
}
} else {
$.get( "presets/skeleton.a", function( text ) {
loadCode(text);
setLastPreset(fileid);
updatePreset(fileid, text);
}, 'text');
}
}
function loadPreset(preset_id) {
// TODO
var index = parseInt(preset_id+"");
for (var i=0; i<PRESETS.length; i++)
if (PRESETS[i].id == preset_id)
index = i;
index = (index + PRESETS.length) % PRESETS.length;
if (index >= 0) {
// load the preset
loadFile(preset_id, "presets/" + PRESETS[index].id, index);
} else {
// no preset found? load local
loadFile(preset_id, "local/" + preset_id, -1);
}
}
function gotoPresetAt(index) {
var index = (index + PRESETS.length) % PRESETS.length;
qs['file'] = PRESETS[index].id;
window.location = "?" + $.param(qs);
}
function gotoPresetNamed(id) {
if (id.startsWith("_")) {
var result = eval(id+"()");
console.log(id, result);
if (!result) {
updateSelector();
}
} else {
qs['file'] = id;
window.location = "?" + $.param(qs);
}
}
function _createNewFile(e) {
var filename = prompt("Create New File", "newfile.a");
if (filename && filename.length) {
if (filename.indexOf(".") < 0) {
filename += ".a";
}
qs['file'] = "local/" + filename;
window.location = "?" + $.param(qs);
return true;
} else {
return false;
}
}
function _shareFile(e) {
if (current_output == null) {
alert("Cannot share until errors are fixed.");
return false;
}
var text = editor.getValue();
console.log("POST",text.length,'bytes');
$.post({
url: 'share.php',
data: {
'platform':'vcs', /// TODO
'filename':current_preset_id.split('/').pop(),
'text':text,
},
error: function(e) {
console.log(e);
alert("Error sharing file.");
},
success: function(result) {
if (confirm("Share link to file?")) {
console.log(result);
var sharekey = result['key'];
var url = "http://8bitworkshop.com/?sharekey=" + sharekey;
alert("Copy this link to your clipboard:\n\n" + url);
}
}
});
return false;
}
function _resetPreset(e) {
if (current_preset_idx >= 0
&& confirm("Reset '" + PRESETS[current_preset_idx].name + "' to default?")) {
qs['reset'] = '1';
window.location = "?" + $.param(qs);
return true;
} else {
return false;
}
}
function populateExamples(sel) {
sel.append($("<option />").text("--------- Examples ---------"));
for (var i=0; i<PRESETS.length; i++) {
var preset = PRESETS[i];
var name = preset.chapter + ". " + preset.name;
sel.append($("<option />").val(preset.id).text(name).attr('selected',preset.id==current_preset_id));
}
sel.append($("<option />").val("_resetPreset").text("<< Reset to Default >>"));
}
function populateLocalFiles(sel) {
sel.append($("<option />").text("------- Local Files -------"));
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.startsWith("local/")) {
var name = key.substring(6);
sel.append($("<option />").val("local/"+name).text(name).attr('selected',key==current_preset_id));
}
}
sel.append($("<option />").val("_createNewFile").text("<< Create New File >>"));
//sel.append($("<option />").val("_shareFile").text("<< Share File >>"));
}
function populateSharedFiles(sel) {
sel.append($("<option />").text("--------- Shared ---------"));
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.startsWith("shared/")) {
var name = key.substring(6);
sel.append($("<option />").val("shared/"+name).text(name).attr('selected',key==current_preset_id));
}
}
}
function updateSelector() {
var sel = $("#preset_select").empty();
populateLocalFiles(sel);
populateSharedFiles(sel);
populateExamples(sel);
// set click handlers
sel.off('change').change(function(e) {
gotoPresetNamed($(this).val());
});
$("#preset_prev").off('click').click(function() {
gotoPresetAt(current_preset_idx - 1);
});
$("#preset_next").off('click').click(function() {
gotoPresetAt(current_preset_idx + 1);
});
}
function setCode(text) {
worker.postMessage({code:text});
}
function arrayCompare(a,b) {
if (a == null && b == null) return true;
if (a == null) return false;
if (b == null) return false;
if (a.length != b.length) return false;
for (var i=0; i<a.length; i++)
if (a[i] != b[i])
return false;
return true;
}
worker.onmessage = function(e) {
//console.log(e.data);
// errors?
var gutters = $("#editor"); //.find(".CodeMirror-linenumber");
if (e.data.listing.errors.length > 0) {
gutters.addClass("has-errors");
editor.clearGutter("gutter-info");
for (info of e.data.listing.errors) {
var div = document.createElement("div");
div.setAttribute("class", "tooltip tooltiperror");
div.style.color = '#ff3333'; // TODO
div.appendChild(document.createTextNode("\u24cd"));
var tooltip = document.createElement("span");
tooltip.setAttribute("class", "tooltiptext");
tooltip.appendChild(document.createTextNode(info.msg));
div.appendChild(tooltip);
editor.setGutterMarker(info.line-1, "gutter-info", div);
}
current_output = null;
} else {
gutters.removeClass("has-errors");
updatePreset(current_preset_id, text);
// load ROM
var rom = e.data.output.slice(2);
var rom_changed = rom && !arrayCompare(rom, current_output);
if (rom_changed) {
try {
resume();
//console.log("Loading ROM length", rom.length);
Javatari.loadROM(getCurrentPresetTitle(), rom);
current_output = rom;
} catch (e) {
console.log(e); // TODO
current_output = null;
}
}
if (rom_changed || trace_pending_at_pc) {
// update editor annotations
editor.clearGutter("gutter-info");
editor.clearGutter("gutter-bytes");
editor.clearGutter("gutter-offset");
editor.clearGutter("gutter-clock");
offset2line = {};
line2offset = {};
for (info of e.data.listing.lines) {
if (info.offset) {
var textel = document.createTextNode(info.offset.toString(16));
editor.setGutterMarker(info.line-1, "gutter-offset", textel);
offset2line[info.offset] = info.line;
line2offset[info.line] = info.offset;
}
if (info.insns) {
var insnstr = info.insns.length > 8 ? ("...") : info.insns;
var textel = document.createTextNode(insnstr);
editor.setGutterMarker(info.line-1, "gutter-bytes", textel);
if (info.iscode) {
var opcode = parseInt(info.insns.split()[0], 16);
var meta = Javatari.getOpcodeMetadata(opcode, info.offset);
var clockstr = meta.minCycles+"";
var textel = document.createTextNode(clockstr);
editor.setGutterMarker(info.line-1, "gutter-clock", textel);
}
}
}
}
if (trace_pending_at_pc) {
showLoopTimingForPC(trace_pending_at_pc);
}
}
trace_pending_at_pc = null;
}
function findLineForOffset(PC) {
if (offset2line) {
for (var i=0; i<256; i++) {
var line = offset2line[PC];
if (line) {
return line;
}
PC--;
}
}
return 0;
}
function setCurrentLine(line) {
editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
}
function hex(v, nd) {
try {
if (!nd) nd = 2;
var s = v.toString(16).toUpperCase();
while (s.length < nd)
s = "0" + s;
return s;
} catch (e) {
return v+"";
}
}
function decodeFlags(c, flags) {
var s = "";
s += c.N ? " N" : " -";
s += c.V ? " V" : " -";
s += c.D ? " D" : " -";
s += c.Z ? " Z" : " -";
s += c.C ? " C" : " -";
// s += c.I ? " I" : " -";
return s;
}
function cpuStateToLongString(c) {
return "PC " + hex(c.PC,4) + " " + decodeFlags(c) + " " + getTIAPosString() + "\n"
+ " A " + hex(c.A) + " " + (c.R ? "" : "BUSY") + "\n"
+ " X " + hex(c.X) + "\n"
+ " Y " + hex(c.Y) + " " + "SP " + hex(c.SP) + "\n";
}
function getTIAPosString() {
var clkfs = Javatari.room.console.getClocksFromFrameStart() - 1;
var row = Math.floor(clkfs/76);
var col = clkfs - row*76;
return "V" + (row-39) + " H" + (col*3-68);
}
var lastDebugInfo;
function highlightDifferences(s1, s2) {
var split1 = s1.split(/(\S+\s+)/).filter(function(n) {return n});
var split2 = s2.split(/(\S+\s+)/).filter(function(n) {return n});
var i = 0;
var j = 0;
var result = "";
while (i < split1.length && j < split2.length) {
var w1 = split1[i];
var w2 = split2[j];
if (w2 && w2.indexOf("\n") >= 0) {
while (i < s1.length && split1[i].indexOf("\n") < 0)
i++;
}
if (w1 != w2) {
w2 = '<span class="hilite">' + w2 + '</span>';
}
result += w2;
i++;
j++;
}
while (j < split2.length) {
result += split2[j++];
}
return result;
}
function showMemory(state) {
var s = "";
if (state) {
s = cpuStateToLongString(state.c);
s += "\n";
var ram = jt.Util.byteStringToUInt8Array(atob(state.r.b));
for (var ofs=0; ofs<0x80; ofs+=0x10) {
s += '$' + hex(ofs+0x80) + ':';
for (var i=0; i<0x10; i++) {
if (i == 8) s += " ";
s += " " + hex(ram[ofs+i]);
}
s += "\n";
}
var hs = lastDebugInfo ? highlightDifferences(lastDebugInfo, s) : s;
$("#mem_info").show().html(hs);
lastDebugInfo = s;
} else {
$("#mem_info").hide();
lastDebugInfo = null;
}
}
function setupBreakpoint() {
// TODO
Javatari.room.console.onBreakpointHit = function(state) {
var PC = state.c.PC;
var line = findLineForOffset(PC);
if (line) {
console.log("BREAKPOINT", hex(PC), line);
setCurrentLine(line);
}
showMemory(state);
}
}
function pause() {
clearBreakpoint();
if (Javatari.room.console.isRunning()) {
Javatari.room.console.pause();
}
}
function resume() {
clearBreakpoint();
if (! Javatari.room.console.isRunning()) {
Javatari.room.console.go();
editor.setSelection(editor.getCursor());
}
}
function singleStep() {
setupBreakpoint();
Javatari.room.console.debugSingleStepCPUClock();
}
function getCurrentLine() {
return editor.getCursor().line+1;
}
function runToCursor() {
setupBreakpoint();
var line = getCurrentLine();
var pc = line2offset[line];
if (pc) {
console.log("Run to", line, pc.toString(16));
Javatari.room.console.debugToPC(pc);
}
}
function clearBreakpoint() {
Javatari.room.console.disableDebug();
Javatari.room.console.onBreakpointHit = null;
$("#dbg_info").empty();
showMemory();
}
function getClockCountsAtPC(pc) {
// TODO
var opcode = current_output[pc - 0xf000];
var meta = Javatari.getOpcodeMetadata(opcode, pc);
return meta; // minCycles, maxCycles
}
var pc2minclocks = {};
var pc2maxclocks = {};
var jsrresult = {};
var MAX_CLOCKS = 76*2;
function byte2signed(b) {
b &= 0xff;
return (b < 0x80) ? b : -(256-b);
}
// [taken, not taken]
var BRANCH_CONSTRAINTS = [
[{N:0},{N:1}],
[{N:1},{N:0}],
[{V:0},{V:1}],
[{V:1},{V:0}],
[{C:0},{C:1}],
[{C:1},{C:0}],
[{Z:0},{Z:1}],
[{Z:1},{Z:0}]
];
function constraintEquals(a,b) {
if (a == null || b == null)
return null;
for (var n in a) {
if (b[n] !== 'undefined')
return a[n] == b[n];
}
for (var n in b) {
if (a[n] !== 'undefined')
return a[n] == b[n];
}
return null;
}
function _traceInstructions(pc, minclocks, maxclocks, subaddr, constraints) {
//console.log("trace", hex(pc), minclocks, maxclocks);
if (!minclocks) minclocks = 0;
if (!maxclocks) maxclocks = 0;
if (!constraints) constraints = {};
var modified = true;
var abort = false;
for (var i=0; i<1000 && modified && !abort; i++) {
modified = false;
var meta = getClockCountsAtPC(pc);
var lob = current_output[pc - 0xf000 + 1];
var hib = current_output[pc - 0xf000 + 2];
var addr = lob + (hib << 8);
var pc0 = pc;
if (!pc2minclocks[pc0] || minclocks < pc2minclocks[pc0]) {
pc2minclocks[pc0] = minclocks;
modified = true;
}
if (!pc2maxclocks[pc0] || maxclocks > pc2maxclocks[pc0]) {
pc2maxclocks[pc0] = maxclocks;
modified = true;
}
//console.log(hex(pc),minclocks,maxclocks,meta);
if (!meta.insnlength) {
console.log("Illegal instruction!", hex(pc), hex(meta.opcode), meta);
break;
}
pc += meta.insnlength;
var oldconstraints = constraints;
constraints = null;
// TODO: if jump to zero-page, maybe assume RTS?
switch (meta.opcode) {
/*
case 0xb9: // TODO: hack for zero page,y
if (addr < 0x100)
meta.maxCycles -= 1;
break;
*/
case 0x85:
if (lob == 0x2) { // STA WSYNC
minclocks = maxclocks = 0;
meta.minCycles = meta.maxCycles = 0;
}
break;
case 0x20: // JSR
_traceInstructions(addr, minclocks, maxclocks, addr, constraints);
var result = jsrresult[addr];
if (result) {
minclocks = result.minclocks;
maxclocks = result.maxclocks;
} else {
console.log("No JSR result!", hex(pc), hex(addr));
return;
}
break;
case 0x4c: // JMP
pc = addr; // TODO: make sure in ROM space
break;
case 0x60: // RTS
if (subaddr) {
// TODO: combine with previous result
var result = jsrresult[subaddr];
if (!result) {
result = {minclocks:minclocks, maxclocks:maxclocks};
} else {
result = {
minclocks:Math.min(minclocks,result.minclocks),
maxclocks:Math.max(maxclocks,result.maxclocks)
}
}
jsrresult[subaddr] = result;
console.log("RTS", hex(pc), hex(subaddr), jsrresult[subaddr]);
}
return;
case 0x10: case 0x30: // branch
case 0x50: case 0x70:
case 0x90: case 0xB0:
case 0xD0: case 0xF0:
var newpc = pc + byte2signed(lob);
var crosspage = (pc>>8) != (newpc>>8);
if (!crosspage) meta.maxCycles--;
// TODO: other instructions might modify flags too
var cons = BRANCH_CONSTRAINTS[Math.floor((meta.opcode-0x10)/0x20)];
var cons0 = constraintEquals(oldconstraints, cons[0]);
var cons1 = constraintEquals(oldconstraints, cons[1]);
if (cons0 !== false) {
_traceInstructions(newpc, minclocks+meta.maxCycles, maxclocks+meta.maxCycles, subaddr, cons[0]);
}
if (cons1 === false) {
console.log("abort", hex(pc), oldconstraints, cons[1]);
abort = true;
}
constraints = cons[1]; // not taken
meta.maxCycles = meta.minCycles; // branch not taken, no extra clock(s)
break;
case 0x6c:
console.log("Instruction not supported!", hex(pc), hex(meta.opcode), meta); // TODO
return;
}
// TODO: wraparound?
minclocks = Math.min(MAX_CLOCKS, minclocks + meta.minCycles);
maxclocks = Math.min(MAX_CLOCKS, maxclocks + meta.maxCycles);
}
}
function showLoopTimingForPC(pc) {
pc2minclocks = {};
pc2maxclocks = {};
jsrresult = {};
// recurse through all traces
_traceInstructions(pc | 0xf000, MAX_CLOCKS, MAX_CLOCKS);
// show the lines
for (var line in line2offset) {
var pc = line2offset[line];
var minclocks = pc2minclocks[pc];
var maxclocks = pc2maxclocks[pc];
if (minclocks>=0 && maxclocks>=0) {
var s;
if (maxclocks == minclocks)
s = minclocks + "";
else
s = minclocks + "-" + maxclocks;
if (maxclocks == MAX_CLOCKS)
s += "+";
var textel = document.createTextNode(s);
editor.setGutterMarker(line-1, "gutter-bytes", textel);
}
}
}
function traceTiming() {
trace_pending_at_pc = 0xf000;
setCode(editor.getValue());
}
/*
function showLoopTimingForCurrentLine() {
var line = getCurrentLine();
var pc = line2offset[line];
if (pc) {
showLoopTimingForPC(pc);
}
}
*/
function reset() {
Javatari.room.console.powerOff();
Javatari.room.console.resetDebug();
Javatari.room.console.powerOn();
}
function resetAndDebug() {
reset();
runToCursor();
}
function setupDebugControls(){
$("#dbg_reset").click(resetAndDebug);
$("#dbg_pause").click(pause);
$("#dbg_go").click(resume);
$("#dbg_step").click(singleStep);
$("#dbg_toline").click(runToCursor);
$("#dbg_timing").click(traceTiming);
$("#btn_share").click(_shareFile);
}
///////////////////////////////////////////////////
var qs = (function (a) {
if (!a || a == "")
return {};
var b = {};
for (var i = 0; i < a.length; ++i) {
var p = a[i].split('=', 2);
if (p.length == 1)
b[p[0]] = "";
else
b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
return b;
})(window.location.search.substr(1).split('&'));
setupDebugControls();
try {
// is this a share URL?
if (qs['sharekey']) {
var sharekey = qs['sharekey'];
console.log("Loading shared file ", sharekey);
$.getJSON( ".storage/" + sharekey, function( result ) {
console.log(result);
var newid = 'shared/' + result['filename'];
updatePreset(newid, result['text']);
qs['file'] = newid;
delete qs['sharekey'];
window.location = "?" + $.param(qs);
}, 'text');
} else {
// add default platform?
if (!qs['platform']) {
qs['platform'] = 'vcs';
}
// reset file?
if (qs['file'] && qs['reset']) {
localStorage.removeItem(qs['file']);
localStorage.removeItem('local/' + qs['file']);
qs['reset'] = '';
window.location = "?" + $.param(qs);
} else if (qs['file']) {
// load file
loadPreset(qs['file']);
updateSelector();
} else {
// try to load last file
var lastid = localStorage.getItem("__lastid");
gotoPresetNamed(lastid || PRESETS[1].id);
}
}
} catch (e) {
alert(e+""); // TODO?
}

117
src/worker/workermain.js Normal file
View File

@ -0,0 +1,117 @@
// set up require.js for worker
importScripts("../../dasm.js");
// shim out window and document objects
// https://github.com/mbostock/d3/issues/1053
var noop = function() { return new Function(); };
var window = noop();
window.CSSStyleDeclaration = noop();
window.CSSStyleDeclaration.setProperty = noop();
window.Element = noop();
window.Element.setAttribute = noop();
window.Element.setAttributeNS = noop();
window.navigator = noop();
var document = noop();
document.documentElement = noop();
document.documentElement.style = noop();
MAIN_FILENAME = "main.a";
PREAMBLE = "\tprocessor 6502\n";
PREAMBLE_LINES = 1;
function parseListing(code, unresolved) {
var errorMatch = /main.a [(](\d+)[)]: error: (.+)/;
// 4 08ee a9 00 start lda #01workermain.js:23:5
var lineMatch = /\s*(\d+)\s+(\S+)\s+([0-9a-f]+)\s+([0-9a-f][0-9a-f ]+)\s+(.+)/;
var equMatch = /\bequ\b/;
var errors = [];
var lines = [];
var lastline = 0;
for (var line of code.split(/\r?\n/)) {
var linem = lineMatch.exec(line);
if (linem && linem[1]) {
var linenum = parseInt(linem[1]) - PREAMBLE_LINES;
var filename = linem[2];
var offset = parseInt(linem[3], 16);
var insns = linem[4];
var restline = linem[5];
// inside of main file?
if (filename == MAIN_FILENAME) {
if (insns && !restline.match(equMatch)) {
lines.push({
line:linenum,
offset:offset,
insns:insns,
iscode:restline[0] != '.'
});
}
lastline = linenum;
} else {
// inside of macro or include file
if (linenum == -PREAMBLE_LINES) { // start of macro?
lines.push({
line:lastline+1,
offset:offset,
insns:null
});
}
}
// TODO: check filename too
// TODO: better symbol test (word boundaries)
for (key in unresolved) {
var pos = restline ? restline.indexOf(key) : line.indexOf(key);
if (pos >= 0) {
errors.push({
line:linenum,
msg:"Unresolved symbol '" + key + "'"
});
}
}
}
var errm = errorMatch.exec(line);
if (errm) {
errors.push({
line:parseInt(errm[1]),
msg:errm[2]
})
}
}
return {lines:lines, errors:errors};
}
function assemble(code) {
var re_usl = /(\w+)\s+0000\s+[?][?][?][?]/;
var unresolved = {};
function print_fn(s) {
var matches = re_usl.exec(s);
if (matches) {
unresolved[matches[1]] = 0;
}
}
var Module = DASM({
noInitialRun:true,
print:print_fn
});
var FS = Module['FS'];
FS.writeFile(MAIN_FILENAME, PREAMBLE + code);
Module.callMain([MAIN_FILENAME, "-v3", "-la.lst"]);
var aout = FS.readFile("a.out");
//console.log(aout);
var alst = FS.readFile("a.lst", {'encoding':'utf8'});
//console.log(alst);
var listing = parseListing(alst, unresolved);
return {exitstatus:Module.EXITSTATUS, output:aout, listing:listing};
}
//assemblepgm("\tprocessor 6502\n\torg $800\n\tlda #0");
onmessage = function(e) {
var code = e.data.code;
var result = assemble(code);
//console.log("RESULT", result);
postMessage(result);
}