initial commit
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*~
|
||||
node_modules
|
9
.gitmodules
vendored
Normal 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
@ -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; }
|
23
doc/notes.txt
Normal 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
After Width: | Height: | Size: 95 B |
BIN
images/play.png
Normal file
After Width: | Height: | Size: 123 B |
BIN
images/resetandrun.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
images/runtoline.png
Normal file
After Width: | Height: | Size: 128 B |
BIN
images/share.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
images/singlestep.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
images/timing.png
Normal file
After Width: | Height: | Size: 165 B |
176
include/macro.h
Normal 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
@ -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
@ -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="#"><<</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®ion=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>
|
4
jquery/jquery-2.2.3.min.js
vendored
Normal file
10
package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "RetroConsole",
|
||||
"version": "0.0.1",
|
||||
"author": "Steven Hugg",
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^13.0.0"
|
||||
}
|
||||
}
|
247
presets/examples/adventure.a
Normal 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
|
98
presets/examples/bankswitching.a
Normal 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
|
125
presets/examples/bigsprite.a
Normal 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
@ -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
|
737
presets/examples/brickgame.a
Normal 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
|
476
presets/examples/collisions.a
Normal 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
|
267
presets/examples/complexscene.a
Normal 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
|
286
presets/examples/complexscene2.a
Normal 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
@ -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
@ -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
@ -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
@ -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
|
273
presets/examples/multisprite1.a
Normal 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
|
567
presets/examples/multisprite2.a
Normal 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
|
580
presets/examples/multisprite3.a
Normal 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
|
247
presets/examples/musicplayer.a
Normal 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
@ -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
|
59
presets/examples/playfield.a
Normal 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
@ -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
|
397
presets/examples/retrigger.a
Normal 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
@ -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
@ -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
|
198
presets/examples/scoreboard.a
Normal 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
@ -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
@ -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
@ -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?
|
205
presets/examples/tinyfonts.a
Normal 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
|
223
presets/examples/tinyfonts2.a
Normal 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
@ -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
|
117
presets/examples/wavetable.a
Normal 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
@ -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
@ -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
@ -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
@ -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);
|
||||
}
|