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);
|
||||||
|
}
|