├── .gitattributes ├── .gitignore ├── .htaccess ├── Jakefile ├── LICENSE ├── README.md ├── benchmarks ├── canvas.html ├── function_call.html ├── memset.html ├── memset.ods ├── probe.js ├── prop.html ├── proto.html ├── scope.html ├── switch.html └── var.html ├── build ├── cycloa.js └── cycloa_script.js ├── cl.sh ├── css └── style.css ├── img └── .gitignore ├── index.html ├── js ├── cycloa │ ├── cycloa.js │ ├── default_fairy.js │ ├── err.js │ ├── trace.js │ └── util.js ├── fairy.js ├── interface_script.js ├── interface_vm.js └── lib │ └── jquery-1.8.0.js ├── rakefile ├── roms ├── alter_ego │ ├── Alter_Ego.nes │ ├── label.png │ ├── manual.pdf │ └── notes.txt ├── croom │ ├── README.html │ └── croom.nes ├── lj65 │ ├── README.txt │ └── lj65.nes └── thwaite-0.03 │ ├── CHANGES.txt │ ├── README.html │ ├── docs │ ├── de.css │ ├── manual_ingame.png │ ├── manual_legend.png │ ├── manual_practice.png │ ├── manual_title.png │ ├── tas_words.txt │ └── tech_notes.html │ ├── gpl-3.0.txt │ ├── makefile │ ├── nes.ini │ ├── obj │ └── nes │ │ └── index.txt │ ├── src │ ├── bcd.s │ ├── bg.s │ ├── cutscene.pkb │ ├── cutscene.s │ ├── cutscripts.s │ ├── explosion.s │ ├── kinematics.s │ ├── levels.s │ ├── main.s │ ├── math.s │ ├── missiles.s │ ├── mouse.s │ ├── music.s │ ├── musicseq.h │ ├── musicseq.s │ ├── nes.h │ ├── pads.s │ ├── paldetect.s │ ├── practice.s │ ├── practice.txt │ ├── ram.h │ ├── random.s │ ├── scurry.s │ ├── smoke.s │ ├── sound.s │ ├── tips.s │ ├── title.pkb │ ├── title.s │ └── unpkb.s │ ├── thwaite.nes │ ├── tilesets │ ├── cuthouses.png │ └── maingfx.png │ ├── todo.txt │ ├── tools │ ├── 8name.py │ ├── mktables.py │ └── pilbmp2nes.py │ └── zip.in ├── script.html ├── script └── sample.js └── src ├── audio.rb ├── cpu.rb ├── gen.rb ├── nes.rb ├── opcode_info.rb ├── pad.rb ├── script.erb.js ├── video.rb ├── vm.erb.js ├── vm_audio_digital_create.erb.js ├── vm_audio_digital_init.erb.js ├── vm_audio_digital_method.erb.js ├── vm_audio_init.erb.js ├── vm_audio_method.erb.js ├── vm_audio_noize_create.erb.js ├── vm_audio_noize_init.erb.js ├── vm_audio_noize_method.erb.js ├── vm_audio_noize_runh.erb.js ├── vm_audio_noize_runq.erb.js ├── vm_audio_rectangle_create.erb.js ├── vm_audio_rectangle_init.erb.js ├── vm_audio_rectangle_method.erb.js ├── vm_audio_rectangle_runh.erb.js ├── vm_audio_rectangle_runq.erb.js ├── vm_audio_run.erb.js ├── vm_audio_triangle_create.erb.js ├── vm_audio_triangle_init.erb.js ├── vm_audio_triangle_method.erb.js ├── vm_audio_triangle_runh.erb.js ├── vm_audio_triangle_runq.erb.js ├── vm_cpu_init.erb.js ├── vm_cpu_method.erb.js ├── vm_cpu_run.erb.js ├── vm_mapper_init.erb.js ├── vm_mapper_method.erb.js ├── vm_pad_init.erb.js ├── vm_video_init.erb.js ├── vm_video_method.erb.js └── vm_video_run.erb.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Numerous always-ignore extensions 2 | *.diff 3 | *.err 4 | *.orig 5 | *.log 6 | *.rej 7 | *.swo 8 | *.swp 9 | *.vi 10 | *~ 11 | *.sass-cache 12 | 13 | # OS or Editor folders 14 | .DS_Store 15 | Thumbs.db 16 | .cache 17 | .project 18 | .settings 19 | .tmproj 20 | *.esproj 21 | nbproject 22 | *.sublime-project 23 | *.sublime-workspace 24 | 25 | # Dreamweaver added files 26 | _notes 27 | dwsync.xml 28 | 29 | # Komodo 30 | *.komodoproject 31 | .komodotools 32 | 33 | # Folders to ignore 34 | .hg 35 | .svn 36 | .CVS 37 | intermediate 38 | publish 39 | .idea 40 | 41 | # build script local files 42 | # build/* 43 | js/cycloa/vm.js 44 | js/cycloa/script.js 45 | 46 | #NES 47 | *.bak 48 | *.deb 49 | -------------------------------------------------------------------------------- /Jakefile: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | Encoding.default_external = "utf-8" 3 | jake_helper :version do 4 | return (`git rev-list HEAD | head -1 | cut -c-20`).strip 5 | end 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CycloaJS - a JavaScript NES emulator 2 | 3 | CycloaJS is a NES emulator implemented in JavaScript. 4 | 5 | CycloaJSはJavaScriptで実装されたファミコンエミュレータです 6 | 7 | # Playable Demo 8 | 9 | You can play games with this emulator at: 10 | 11 | 実際に遊べるデモはこちらです: 12 | 13 | - https://app.7io.org/CycloaJS/ 14 | 15 | # C++ Version 16 | 17 | Original C++ version is available at: 18 | 19 | 元になったC++のバージョンがこちらにあります: 20 | 21 | - https://github.com/ledyba/Cycloa 22 | 23 | # License 24 | 25 | CycloaJS is licensed under AGPL v3 or later. 26 | 27 | -------------------------------------------------------------------------------- /benchmarks/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 43 | benchmark 44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /benchmarks/function_call.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 46 | benchmark 47 | 48 | 49 | 50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /benchmarks/memset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 54 | benchmark 55 | 56 | 57 | 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /benchmarks/memset.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/benchmarks/memset.ods -------------------------------------------------------------------------------- /benchmarks/probe.js: -------------------------------------------------------------------------------- 1 | var cycloa; 2 | if(!cycloa) cycloa = {}; 3 | if(!cycloa.probe) cycloa.probe = {}; 4 | 5 | function log(msg){ 6 | console.log(msg); 7 | document.getElementById('console').innerHTML = 8 | document.getElementById('console').innerHTML + "
" + msg; 9 | } 10 | 11 | function clear(){ 12 | document.getElementById('console').innerHTML = undefined; 13 | } 14 | 15 | cycloa.probe.measure = function(func){ 16 | var start = new Date(); 17 | func(); 18 | var elapsed = (new Date())-start; 19 | return elapsed; 20 | }; 21 | -------------------------------------------------------------------------------- /benchmarks/prop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 29 | benchmark 30 | 31 | 32 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /benchmarks/proto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 38 | benchmark 39 | 40 | 41 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /benchmarks/scope.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 80 | benchmark 81 | 82 | 83 | 84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /benchmarks/switch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 44 | benchmark 45 | 46 | 47 | 48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /benchmarks/var.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 26 | benchmark 27 | 28 | 29 | 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /cl.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | DIRNAME=$(cd $(dirname $0);pwd) 4 | find $DIRNAME/js $DIRNAME/src -type f | xargs wc -l 5 | echo GIT logs: `git --git-dir=${DIRNAME}/.git log --pretty=oneline | wc -l` 6 | -------------------------------------------------------------------------------- /img/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/img/.gitignore -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CycloaJS: JavaScript NES Emulator 6 | 7 | 8 | 9 | 10 | 11 | 22 | 23 | 24 |
25 |

JavaScript NES Emulator: CycloaJS

26 |
27 |
28 | 30 |
31 |
32 |

Please drag & drop your .NES game or select a homebrew game below.

33 |

.NESファイルをドラッグ・アンド・ドロップするか、以下から無料なゲームを選択すると遊ぶことができます。

34 |
35 | 36 | 43 |
44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |

Software Requirements / 動作要件

59 |

Chrme/Firefox, Safari for iPad and Safari for Windows(v5). Sounds are available only for Chrome and Firefox (Web Audio API and Audio Data API).

60 |

Chrome/Firefox、iPad版Safari/Windows版Safari5での動作を確認しています。ChromeとFirefoxのみサウンドも出力されます(Web Audio API and Audio Data API)。

61 |
62 |
63 |

Control / 操作方法

64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
Player 1Button
AZ
BX
StartEnter
SelectSpace
93 |

Currently player2 is not supported. I have tried many times to support Gamepad API, but the API does not work at all... :(

94 |

現在プレイヤ2は対応していません。ゲームパッドAPIにも何度も対応しようとしたのですが、ゲームパッドの状態が拾えません…。

95 |
96 |
97 |

About CycloaJS

98 |

This emulator is a port of Cycloa, a NES emulator written in C++, to JavaScript. The aim of this development is survey of JavaScript performance. In PC browsers, this emulator runs faster than jsnes with funciton inline expansin, but source code has been very messy. In iPad (1st gen), both emulators runs at more of the same speed.

99 |

このエミュレータは、C++で書かれたCycloaいうエミュレータのJavaScriptへの移植です。開発の主目的はJavaScriptのパフォーマンスの調査です。その結果、関数呼び出しのインライン化などを行うことでPC上では十分なパフォーマンスを出せることがわかったものの、ソースコードの可読性が非常に下がってしまうことが分かりました。また、PCでは既存のjsnesよりも数倍と十分な高速化ができましたが、iPad上ではFPSに有意な差が出ませんでした。

100 |
101 |
102 |

Sourcecode

103 |

All sourecodes are licensed under AGPL v3 or later, and available on Github.

104 |

ソースコードのライセンスはAGPL v3かそれ以降のバージョンです。ソースコードはGithubで得ることができます。 105 |

106 |
107 |

About sample games

108 |
109 |

Alter Ego

110 |

Alter Ego is a puzzle action game, originally developed by Denis Grachev in ZX Spectrum. This NES version is ported by Shiru and Kulor in 2011. Warning! This game is "free of charge", but not licensed under GPL or any "opensource" license.

111 |

You can control a hero and his "alter ego". The hero and his alter ego move in mirrored fashion each other. You can swap them with B button in limited times. To complete stage, you have to collect all items in the satage.

112 |

Alter Egoは、Denis GrachevさんがZXスペクトラム向けに2011年に開発したアクションパズルゲームで、それを同年にShiruさんとKulorさんがファミコン向けに移植したものを今回許可を頂いて転載させて頂いています。「無料」のゲームですが、GPLなどの自由なライセンスではありません。

113 |

画面上には操作する主人公と、そのアルター・エゴ(分身)が居ます。分身はステージに応じて、左右または上下方向に対して対称に動きます。Bボタンを押すことで分身と主人公を制限回数付きで入れ替えることができ、これを使って通常行けない場所に行くことができます。

114 |

主人公と分身を上手く入れ替えて、ステージ上のアイテムをすべて集めてください!敵キャラに触れるか、ステージ上から落下してしまうとアウトです。

115 |
116 |
117 |

thwaite

118 |

thwaite is a shooting-game developed by Damian Yerrick. You have to break out the ICBM and save your town! Is is licensed under GPL v3 or later. Here is a "README" document.

119 |

thwaiteは、Damian Yerrickさんによって開発されたシューティングゲームです。 空から降り注ぐICBMを撃ち落とし、あなたの街を守りましょう。 ライセンスはGPL v3かそれ以降です。 READMEファイルはこちらです。

120 |
121 |
122 |

Concentration Room

123 |

Contentration Room is a card-matching game developed by Damian Yerrick. Is is licensed under GPL v3 or later. Here is a "README" document.

124 |

Contentration Roomは、Damian Yerrickさんによって開発された神経衰弱ゲームです。 ライセンスはGPL v3かそれ以降です。 READMEファイルはこちらです。

125 |
126 |
127 |

LJ65

128 |

LJ65 is a tetris(R)-like action puzzle game developed by Damian Yerrick. Is is licensed under GPL v2or later. Here is a "README" document.

129 |

LJ65は、Damian Yerrickさんによって開発された、テトリス(R)に似たアクションパズルゲームです。 ライセンスはGPL v2かそれ以降です。 READMEファイルはこちらです。

130 |
131 |
132 |
133 | 136 |
137 | 138 | 139 | -------------------------------------------------------------------------------- /js/cycloa/cycloa.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * cycloa名前空間本体 5 | * @namespace 6 | * @type {Object} 7 | */ 8 | var cycloa = {}; 9 | /** 10 | * エラークラスの名前空間 11 | * @type {Object} 12 | * @namespace 13 | */ 14 | cycloa.err = {}; 15 | /** 16 | * ユーティリティの名前空間 17 | * @type {Object} 18 | */ 19 | cycloa.util = {}; 20 | cycloa.debug = false; 21 | 22 | cycloa.NesPalette = new Uint32Array([ 23 | 0x787878, 0x2000B0, 0x2800B8, 0x6010A0, 0x982078, 0xB01030, 0xA03000, 0x784000, 24 | 0x485800, 0x386800, 0x386C00, 0x306040, 0x305080, 0x000000, 0x000000, 0x000000, 25 | 0xB0B0B0, 0x4060F8, 0x4040FF, 0x9040F0, 0xD840C0, 0xD84060, 0xE05000, 0xC07000, 26 | 0x888800, 0x50A000, 0x48A810, 0x48A068, 0x4090C0, 0x000000, 0x000000, 0x000000, 27 | 0xFFFFFF, 0x60A0FF, 0x5080FF, 0xA070FF, 0xF060FF, 0xFF60B0, 0xFF7830, 0xFFA000, 28 | 0xE8D020, 0x98E800, 0x70F040, 0x70E090, 0x60D0E0, 0x787878, 0x000000, 0x000000, 29 | 0xFFFFFF, 0x90D0FF, 0xA0B8FF, 0xC0B0FF, 0xE0B0FF, 0xFFB8E8, 0xFFC8B8, 0xFFD8A0, 30 | 0xFFF090, 0xC8F080, 0xA0F0A0, 0xA0FFC8, 0xA0FFF0, 0xA0A0A0, 0x000000, 0x000000 31 | ]); 32 | 33 | 34 | -------------------------------------------------------------------------------- /js/cycloa/default_fairy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * @constructor 5 | * */ 6 | cycloa.AbstractAudioFairy = function(){ 7 | this.enabled = false;//supported or not. 8 | this.data = undefined;//audio data buffer to fill 9 | this.dataLength = 0;//length of the buffer 10 | this.dataIndex = undefined;// the index of the buffer 11 | }; 12 | 13 | //called when all data buffer has been filled. 14 | cycloa.AbstractAudioFairy.prototype.onDataFilled = function(){ 15 | }; 16 | 17 | /** 18 | * @constructor 19 | * */ 20 | cycloa.AbstractVideoFairy = function(){ 21 | this.dispatchRendering = function(/* Uint8Array */ nesBuffer, /* Uint8 */ paletteMask){}; //called on vsync 22 | }; 23 | 24 | /** 25 | * @constructor 26 | * */ 27 | cycloa.AbstractPadFairy = function(){ 28 | }; 29 | 30 | cycloa.AbstractPadFairy.prototype.A=0; 31 | cycloa.AbstractPadFairy.prototype.B=1; 32 | cycloa.AbstractPadFairy.prototype.SELECT=2; 33 | cycloa.AbstractPadFairy.prototype.START=3; 34 | cycloa.AbstractPadFairy.prototype.UP=4; 35 | cycloa.AbstractPadFairy.prototype.DOWN=5; 36 | cycloa.AbstractPadFairy.prototype.LEFT=6; 37 | cycloa.AbstractPadFairy.prototype.RIGHT=7; 38 | cycloa.AbstractPadFairy.prototype.TOTAL=8; 39 | cycloa.AbstractPadFairy.prototype.MASK_A=1; 40 | cycloa.AbstractPadFairy.prototype.MASK_B=2; 41 | cycloa.AbstractPadFairy.prototype.MASK_SELECT=4; 42 | cycloa.AbstractPadFairy.prototype.MASK_START=8; 43 | cycloa.AbstractPadFairy.prototype.MASK_UP=16; 44 | cycloa.AbstractPadFairy.prototype.MASK_DOWN=32; 45 | cycloa.AbstractPadFairy.prototype.MASK_LEFT=64; 46 | cycloa.AbstractPadFairy.prototype.MASK_RIGHT=128; 47 | cycloa.AbstractPadFairy.prototype.MASK_ALL=255; 48 | cycloa.AbstractPadFairy.prototype.state = 0; //button state 49 | 50 | -------------------------------------------------------------------------------- /js/cycloa/err.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * 例外のベースクラスです 4 | * @param {string} name 例外クラス名 5 | * @param {string} message メッセージ 6 | * @const 7 | * @constructor 8 | */ 9 | cycloa.err.Exception = function (name, message) { 10 | /** 11 | * 例外のメッセージのインスタンス 12 | * @type {string} 13 | * @const 14 | * @private 15 | */ 16 | /** 17 | * @const 18 | * @type {string} 19 | */ 20 | this.name = name; 21 | this.message = "["+name.toString()+"] "+message; 22 | }; 23 | cycloa.err.Exception.prototype.toString = function(){ 24 | return this.message; 25 | }; 26 | /** 27 | * エミュレータのコアで発生した例外です 28 | * @param {string} message 29 | * @constructor 30 | * @extends cycloa.err.Exception 31 | */ 32 | cycloa.err.CoreException = function (message) { 33 | cycloa.err.Exception.call(this, "CoreException", message); 34 | }; 35 | cycloa.err.CoreException.prototype = { 36 | __proto__ : cycloa.err.Exception.prototype 37 | }; 38 | /** 39 | * 実装するべきメソッドを実装してない例外です 40 | * @param {string} message 41 | * @constructor 42 | * @extends cycloa.err.Exception 43 | */ 44 | cycloa.err.NotImplementedException = function (message) { 45 | cycloa.err.Exception.call(this, "NotImplementedException", message); 46 | }; 47 | cycloa.err.NotImplementedException.prototype = { 48 | __proto__: cycloa.err.Exception.prototype 49 | }; 50 | /** 51 | * サポートしてない事を示す例外です 52 | * @param {string} message 53 | * @constructor 54 | */ 55 | cycloa.err.NotSupportedException = function ( message ) { 56 | cycloa.err.Exception.call(this, "NotSupportedException", message); 57 | }; 58 | cycloa.err.NotSupportedException.prototype = { 59 | __proto__: cycloa.err.Exception.prototype 60 | }; 61 | 62 | 63 | -------------------------------------------------------------------------------- /js/cycloa/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} num 3 | * @param {number} [len = 8] 4 | * @return {string} 5 | */ 6 | cycloa.util.formatHex = function(num, len){ 7 | len = len || 8; 8 | return ("0000" + num.toString(16).toUpperCase()).slice(-(len>>2)); 9 | }; 10 | -------------------------------------------------------------------------------- /js/fairy.js: -------------------------------------------------------------------------------- 1 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 2 | window.requestAnimFrame = (function () { 3 | return window.requestAnimationFrame || 4 | window.webkitRequestAnimationFrame || 5 | window.mozRequestAnimationFrame || 6 | window.oRequestAnimationFrame || 7 | window.msRequestAnimationFrame || 8 | function (callback) { 9 | window.setTimeout(callback, 1000 / 60); 10 | }; 11 | })(); 12 | 13 | /** 14 | * @constructor 15 | * */ 16 | function VideoFairy() { 17 | this.screen_ = document.getElementById('nes_screen'); 18 | this.zoomed_ = false; 19 | this.ctx_ = this.screen_.getContext('2d'); 20 | this.image_ = this.ctx_.createImageData(256, 240); 21 | this.palette_ = cycloa.NesPalette; 22 | this.prevBuffer_ = new Uint8Array(256*240); 23 | for (var i = 0; i < 256 * 240; ++i) { 24 | this.image_.data[(i << 2) + 3] = 0xff; 25 | } 26 | } 27 | VideoFairy.prototype.__proto__ = cycloa.AbstractVideoFairy.prototype; 28 | VideoFairy.prototype.dispatchRendering = function (/* const uint8_t*/ nesBuffer, /* const uint8_t */ paletteMask) { 29 | var dat = this.image_.data; 30 | var palette = this.palette_; 31 | var prevBuffer = this.prevBuffer_; 32 | var pixel; 33 | for (var i = 0; i < 61440 /* = 256*240 */; ++i) { 34 | //TODO: 最適化 35 | pixel = nesBuffer[i] & paletteMask; 36 | if(pixel != prevBuffer[i]){ 37 | var idx = i << 2, color = palette[pixel]; 38 | dat[idx ] = (color >> 16) & 0xff; 39 | dat[idx + 1] = (color >> 8) & 0xff; 40 | dat[idx + 2] = color & 0xff; 41 | prevBuffer[i] = pixel; 42 | } 43 | } 44 | this.ctx_.putImageData(this.image_, 0, 0); 45 | }; 46 | VideoFairy.prototype.recycle = function(){ 47 | this.ctx_.fillStyle="#000000"; 48 | this.ctx_.fillRect(0, 0, 256, 240); 49 | var prevBuffer = this.prevBuffer_; 50 | for(var i=0;i < 240*256; ++i){ 51 | prevBuffer[i] = 0xff; 52 | } 53 | }; 54 | VideoFairy.prototype.zoom = function(){ 55 | if(this.zoomed_){ 56 | $("#nes_screen").animate({width: 256, height: 240}); 57 | }else{ 58 | $("#nes_screen").animate({width: 512, height: 480}); 59 | } 60 | this.zoomed_ = !this.zoomed_; 61 | }; 62 | /** 63 | * @constructor 64 | * */ 65 | function AudioFairy() { 66 | this.SAMPLE_RATE_ = 22050; 67 | this.dataLength = (this.SAMPLE_RATE_ / 4) | 0; 68 | this.enabled = false; 69 | var audioContext = window.AudioContext; 70 | if (audioContext) { 71 | this.enabled = true; 72 | this.context_ = new audioContext(); 73 | this.dataIndex = 0; 74 | this.initBuffer = function () { 75 | this.buffer_ = this.context_.createBuffer(1, this.dataLength, this.SAMPLE_RATE_); 76 | this.data = this.buffer_.getChannelData(0); 77 | }; 78 | this.onDataFilled = function () { 79 | var src = this.context_.createBufferSource(); 80 | src.loop = false; 81 | src.connect(this.context_.destination); 82 | src.buffer = this.buffer_; 83 | src.start(0); 84 | this.initBuffer(); 85 | this.dataIndex = 0; 86 | }; 87 | this.initBuffer(); 88 | }else{ 89 | log.info("Audio is not available"); 90 | } 91 | } 92 | 93 | AudioFairy.prototype.__proto__ = cycloa.AbstractAudioFairy.prototype; 94 | AudioFairy.prototype.recycle = function(){ 95 | this.dataIndex = 0; 96 | }; 97 | /** 98 | * @constructor 99 | * */ 100 | function PadFairy($dom) { 101 | this.state = 0; 102 | var self = this; 103 | $dom.bind("keydown", function(e){ 104 | switch (e.keyCode) { 105 | case 38: 106 | self.state |= self.MASK_UP; 107 | e.preventDefault(); 108 | break; 109 | case 40: 110 | self.state |= self.MASK_DOWN; 111 | e.preventDefault(); 112 | break; 113 | case 37: 114 | self.state |= self.MASK_LEFT; 115 | e.preventDefault(); 116 | break; 117 | case 39: 118 | self.state |= self.MASK_RIGHT; 119 | e.preventDefault(); 120 | break; 121 | case 90: 122 | self.state |= self.MASK_A; 123 | e.preventDefault(); 124 | break; 125 | case 88: 126 | self.state |= self.MASK_B; 127 | e.preventDefault(); 128 | break; 129 | case 32: 130 | self.state |= self.MASK_SELECT; 131 | e.preventDefault(); 132 | break; 133 | case 13: 134 | self.state |= self.MASK_START; 135 | e.preventDefault(); 136 | break; 137 | } 138 | }); 139 | $dom.bind("keyup", function(e){ 140 | switch (e.keyCode) { 141 | case 38: 142 | self.state &= ~self.MASK_UP; 143 | e.preventDefault(); 144 | break; 145 | case 40: 146 | self.state &= ~self.MASK_DOWN; 147 | e.preventDefault(); 148 | break; 149 | case 37: 150 | self.state &= ~self.MASK_LEFT; 151 | e.preventDefault(); 152 | break; 153 | case 39: 154 | self.state &= ~self.MASK_RIGHT; 155 | e.preventDefault(); 156 | break; 157 | case 90: 158 | self.state &= ~self.MASK_A; 159 | e.preventDefault(); 160 | break; 161 | case 88: 162 | self.state &= ~self.MASK_B; 163 | e.preventDefault(); 164 | break; 165 | case 32: 166 | self.state &= ~self.MASK_SELECT; 167 | e.preventDefault(); 168 | break; 169 | case 13: 170 | self.state &= ~self.MASK_START; 171 | e.preventDefault(); 172 | break; 173 | } 174 | }); 175 | } 176 | PadFairy.prototype.__proto__ = cycloa.AbstractPadFairy.prototype; 177 | /** 178 | * @constructor 179 | * */ 180 | PadFairy.prototype.recycle = function(){ 181 | this.state = 0; 182 | }; 183 | -------------------------------------------------------------------------------- /js/interface_script.js: -------------------------------------------------------------------------------- 1 | cycloa.calc_fps_mode = true; 2 | 3 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 4 | window.requestAnimFrame = (function () { 5 | if(cycloa.calc_fps_mode){ 6 | return function(callback){ 7 | window.setTimeout(callback, 0); 8 | }; 9 | } 10 | return window.requestAnimationFrame || 11 | window.webkitRequestAnimationFrame || 12 | window.mozRequestAnimationFrame || 13 | window.oRequestAnimationFrame || 14 | window.msRequestAnimationFrame || 15 | function (callback) { 16 | window.setTimeout(callback, 1000 / 60); 17 | }; 18 | })(); 19 | 20 | /** 21 | * @constructor 22 | * */ 23 | function NesController(){ 24 | this.videoFairy_ = new VideoFairy(); 25 | this.audioFairy_ = new AudioFairy(); 26 | this.padFairy_ = new PadFairy($("#nes_screen")); 27 | this.machine_ = new cycloa.ScriptMachine(this.videoFairy_, this.audioFairy_, this.padFairy_); 28 | this.running_ = false; 29 | this.loaded_ = false; 30 | this.total_frame_ = 0; 31 | } 32 | NesController.prototype.load = function(dat){ 33 | this.machine_.load(dat); 34 | if(!this.loaded_){ 35 | this.machine_.onHardReset(); 36 | }else{ 37 | this.machine_.onReset(); 38 | } 39 | this.loaded_ = true; 40 | if(!this.running_){ 41 | this.start(); 42 | } 43 | return true; 44 | }; 45 | NesController.prototype.start = function(){ 46 | if(this.running_){ 47 | $("#state").text("VM already running! Please stop the machine before loading another script."); 48 | return false; 49 | } 50 | if(!this.loaded_){ 51 | $("#state").text("VM has not loaded any games. Please load a script."); 52 | return false; 53 | } 54 | this.running_ = true; 55 | var self = this; 56 | var cnt = 0; 57 | var state = $("#state"); 58 | var loop = function () { 59 | if(self.running_) window.requestAnimFrame(loop); 60 | self.machine_.run(); 61 | cnt++; 62 | if (cnt >= 30) { 63 | var now = new Date(); 64 | self.total_frame_ += cnt; 65 | var str = "fps: " + (cnt * 1000 / (now - beg)).toFixed(2); 66 | if(cycloa.calc_fps_mode){ 67 | str += " / avg: "+(self.total_frame_ * 1000/(now-startTime)).toFixed(2); 68 | } 69 | state.text(str); 70 | beg = now; 71 | cnt = 0; 72 | } 73 | }; 74 | var beg = new Date(); 75 | var startTime = beg 76 | loop(); 77 | return true; 78 | }; 79 | NesController.prototype.stop = function(){ 80 | if(!this.loaded_){ 81 | $("#state").text("VM has not loaded any games. Please load a game."); 82 | return false; 83 | } 84 | this.running_ = false; 85 | return true; 86 | }; 87 | NesController.prototype.hardReset = function(){ 88 | if(!this.loaded_){ 89 | $("#state").text("VM has not loaded any games. Please load a game."); 90 | return false; 91 | } 92 | this.machine_.onHardReset(); 93 | return true; 94 | }; 95 | NesController.prototype.reset = function(){ 96 | if(!this.loaded_){ 97 | $("#state").text("VM has not loaded any games. Please load a game."); 98 | return false; 99 | } 100 | this.machine_.onReset(); 101 | return true; 102 | }; 103 | NesController.prototype.zoom = function(){ 104 | this.videoFairy_.zoom(); 105 | }; 106 | 107 | var nesController; 108 | 109 | (function(){ 110 | $(document).ready(function(){ 111 | $("#nes_hardreset").bind("click", function(){nesController.hardReset();}); 112 | $("#nes_reset").bind("click", function(){nesController.reset();}); 113 | $("#nes_stop").bind("click", function(){ 114 | if(nesController.stop()){ 115 | $("#nes_start").removeClass("disable"); 116 | $("#nes_stop").addClass("disable"); 117 | } 118 | }); 119 | var editor; 120 | $.ajax({ 121 | url: 'script/sample.js', 122 | dataType: 'text', 123 | success: function(data, dataType) { 124 | $("#editor").text(data); 125 | editor = ace.edit("editor"); 126 | editor.setTheme("ace/theme/twilight"); 127 | editor.getSession().setMode("ace/mode/javascript"); 128 | }, 129 | error: function(XMLHttpRequest, textStatus, errorThrown) { 130 | $("#state").text("Oops. failed to load sample script. code:"+textStatus); 131 | } 132 | }); 133 | 134 | $("#nes_start").bind("click", function(){ 135 | if(nesController.load(editor.getValue())) { 136 | $("#nes_stop").removeClass("disable"); 137 | $("#nes_start").addClass("disable"); 138 | } 139 | }); 140 | 141 | $("#screen_zoom").bind("click", function(){ 142 | nesController.zoom(); 143 | }); 144 | 145 | $("#nes_stop").addClass("disable"); 146 | $("#nes_start").removeClass("disable"); 147 | 148 | nesController = new NesController(); 149 | $("#state").text("Initialized."); 150 | }); 151 | }()); 152 | -------------------------------------------------------------------------------- /js/interface_vm.js: -------------------------------------------------------------------------------- 1 | cycloa.calc_fps_mode = false; 2 | 3 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 4 | window.requestAnimFrame = (function () { 5 | if(cycloa.calc_fps_mode){ 6 | return function(callback){ 7 | window.setTimeout(callback, 0); 8 | }; 9 | } 10 | return window.requestAnimationFrame || 11 | window.webkitRequestAnimationFrame || 12 | window.mozRequestAnimationFrame || 13 | window.oRequestAnimationFrame || 14 | window.msRequestAnimationFrame || 15 | function (callback) { 16 | window.setTimeout(callback, 1000 / 60); 17 | }; 18 | })(); 19 | 20 | /** 21 | * @constructor 22 | * */ 23 | function NesController(){ 24 | this.videoFairy_ = new VideoFairy(); 25 | this.audioFairy_ = new AudioFairy(); 26 | this.padFairy_ = new PadFairy($(document)); 27 | this.machine_ = new cycloa.VirtualMachine(this.videoFairy_, this.audioFairy_, this.padFairy_); 28 | this.running_ = false; 29 | this.loaded_ = false; 30 | this.total_frame_ = 0; 31 | } 32 | NesController.prototype.load = function(dat){ 33 | this.machine_.load(dat); 34 | if(!this.loaded_){ 35 | this.machine_.onHardReset(); 36 | }else{ 37 | this.machine_.onReset(); 38 | } 39 | this.loaded_ = true; 40 | if(!this.running_){ 41 | this.start(); 42 | } 43 | return true; 44 | }; 45 | NesController.prototype.start = function(){ 46 | if(this.running_){ 47 | $("#state").text("VM already running! Please stop the machine before loading another game."); 48 | return false; 49 | } 50 | if(!this.loaded_){ 51 | $("#state").text("VM has not loaded any games. Please load a game."); 52 | return false; 53 | } 54 | this.running_ = true; 55 | var self = this; 56 | var cnt = 0; 57 | var state = $("#state"); 58 | var loop = function () { 59 | if(self.running_) window.requestAnimFrame(loop); 60 | self.machine_.run(); 61 | cnt++; 62 | if (cnt >= 30) { 63 | var now = new Date(); 64 | self.total_frame_ += cnt; 65 | var str = "fps: " + (cnt * 1000 / (now - beg)).toFixed(2); 66 | if(cycloa.calc_fps_mode){ 67 | str += " / avg: "+(self.total_frame_ * 1000/(now-startTime)).toFixed(2); 68 | } 69 | state.text(str); 70 | beg = now; 71 | cnt = 0; 72 | } 73 | }; 74 | var beg = new Date(); 75 | var startTime = beg 76 | loop(); 77 | return true; 78 | }; 79 | NesController.prototype.stop = function(){ 80 | if(!this.loaded_){ 81 | $("#state").text("VM has not loaded any games. Please load a game."); 82 | return false; 83 | } 84 | this.running_ = false; 85 | return true; 86 | }; 87 | NesController.prototype.hardReset = function(){ 88 | if(!this.loaded_){ 89 | $("#state").text("VM has not loaded any games. Please load a game."); 90 | return false; 91 | } 92 | this.machine_.onHardReset(); 93 | return true; 94 | }; 95 | NesController.prototype.reset = function(){ 96 | if(!this.loaded_){ 97 | $("#state").text("VM has not loaded any games. Please load a game."); 98 | return false; 99 | } 100 | this.machine_.onReset(); 101 | return true; 102 | }; 103 | NesController.prototype.zoom = function(){ 104 | this.videoFairy_.zoom(); 105 | }; 106 | 107 | var nesController; 108 | 109 | (function(){ 110 | $(document).ready(function(){ 111 | jQuery.event.props.push('dataTransfer'); 112 | $("html").bind("drop", function(e){ 113 | e.stopPropagation(); 114 | e.preventDefault(); 115 | var file = e.dataTransfer.files[0]; 116 | 117 | $("#state").text("Now loading..."); 118 | var reader = new FileReader(); 119 | reader.onload = function (dat) { 120 | nesController.load(dat.target.result); 121 | $("#state").text("done."); 122 | }; 123 | reader.readAsArrayBuffer(file); 124 | }); 125 | $("html").bind("dragenter dragover", false); 126 | 127 | $("#rom_sel").bind("change", function(e){ 128 | var val = e.currentTarget.value; 129 | if(val){ 130 | $("#state").text("Now loading..."); 131 | var xhr = jQuery.ajaxSettings.xhr(); 132 | xhr.open('GET', val, true); 133 | xhr.responseType = 'arraybuffer'; 134 | xhr.onreadystatechange = function() { 135 | if (this.readyState === this.DONE) { 136 | if(this.status === 200){ 137 | nesController.load(this.response); 138 | }else{ 139 | $("#state").text("oops. Failed to load game... Status: "+this.status); 140 | } 141 | } 142 | }; 143 | xhr.send(); 144 | } 145 | }); 146 | 147 | $("#nes_hardreset").bind("click", function(){nesController.hardReset();}); 148 | $("#nes_reset").bind("click", function(){nesController.reset();}); 149 | $("#nes_stop").bind("click", function(){ 150 | if(nesController.stop()){ 151 | $("#nes_start").removeClass("disable"); 152 | $("#nes_stop").addClass("disable"); 153 | } 154 | }); 155 | $("#nes_start").bind("click", function(){ 156 | if(nesController.start()){ 157 | $("#nes_stop").removeClass("disable"); 158 | $("#nes_start").addClass("disable"); 159 | } 160 | }); 161 | 162 | $("#screen_zoom").bind("click", function(){ 163 | nesController.zoom(); 164 | }); 165 | 166 | $("#rom_sel")[0].selectedIndex = 0; 167 | $("#nes_stop").removeClass("disable"); 168 | $("#nes_start").addClass("disable"); 169 | 170 | nesController = new NesController(); 171 | $("#state").text("Initialized"); 172 | }); 173 | }()); 174 | -------------------------------------------------------------------------------- /rakefile: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | generated = ["build/cycloa.js", "build/cycloa_script.js"]; 4 | 5 | task :default => generated; 6 | 7 | def concat(files, to) 8 | src = files.reduce(""){|memo, t| 9 | puts "reading #{t} for #{to}" 10 | memo += open(t, "r").read 11 | } 12 | open(to,"w") { |f| 13 | f.print src 14 | } 15 | end 16 | 17 | file "build/cycloa.js" => "js/cycloa/vm.js" do |t| 18 | concat(["js/cycloa/cycloa.js", 19 | "js/cycloa/err.js", 20 | "js/cycloa/util.js", 21 | "js/cycloa/trace.js", 22 | "js/cycloa/default_fairy.js", 23 | "js/cycloa/vm.js", 24 | "js/fairy.js", 25 | "js/interface_vm.js"], t.name); 26 | end 27 | 28 | file "build/cycloa_script.js" => "js/cycloa/script.js" do |t| 29 | concat(["js/cycloa/cycloa.js", 30 | "js/cycloa/err.js", 31 | "js/cycloa/util.js", 32 | "js/cycloa/trace.js", 33 | "js/cycloa/default_fairy.js", 34 | "js/cycloa/script.js", 35 | "js/fairy.js", 36 | "js/interface_script.js"], t.name); 37 | end 38 | 39 | file "js/cycloa/vm.js" => FileList["src/vm*.erb.js", "src/*.rb"] do |t| 40 | puts "generating emulation core" 41 | sh "erb src/vm.erb.js > #{t.name}" 42 | end 43 | 44 | file "js/cycloa/script.js" => FileList["src/vm*.erb.js", "src/*.rb", "src/script.erb.js"] do |t| 45 | puts "generating script machine core" 46 | sh "erb src/script.erb.js > #{t.name}" 47 | end 48 | 49 | task :clean do 50 | rm "js/cycloa/vm.js" 51 | rm "js/cycloa/script.js" 52 | rm generated 53 | end 54 | 55 | 56 | -------------------------------------------------------------------------------- /roms/alter_ego/Alter_Ego.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/alter_ego/Alter_Ego.nes -------------------------------------------------------------------------------- /roms/alter_ego/label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/alter_ego/label.png -------------------------------------------------------------------------------- /roms/alter_ego/manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/alter_ego/manual.pdf -------------------------------------------------------------------------------- /roms/alter_ego/notes.txt: -------------------------------------------------------------------------------- 1 | Development notes 2 | 3 | After completing Lawn Master, I had plan to try use C compiler to make a simple NES game. From my previous experience with programming in C for micros (Genesis and ZX Spectrum), and from things that thefox did, I knew it could be very worthy in terms of development speed. The plan was to check it, and, in case of success, prove that C is an actual option to develop NES games, not just a theoretical possibility. 4 | 5 | I wanted to make a project very fast, so I decided to not do an original game this time, because design takes most of the time, and just make a port. I've seen two new ZX Spectrum games by Denis Grachev, Join and Alter ego, at WoS when they were released, and liked the combination of simplicity, playability, and sort of retro appeal in them. I made a low-level library in 6502 assembly to use in the project first. When main features of the library were implemented, I have sent a mail to Denis, asking if he would allow to port Alter Ego. It took some time, from June 8 to 17, to get the answer. Denis gave his permission, but I already was busy with other project, and only has been able to start on the port June 25. 6 | 7 | Development process took about 10 days, the game was fully completed, but without music, at July 5. This includes finishing the library, writing all the game code from scratch, reverse-engineering levels format, and beating up both the original game and port few times to test everything. The most difficult part that was not expected by me initially was redesign of all the levels from scratch. Initially I thought I can just convert them, edit a bit, and draw new graphics, but in order to be able to use more colors and make better graphics I had to completely redo all the levels to the NES attribute grid, only keeping overall design of the original levels. In other words, none of the original data get into the port, and there were some changes to make it more playable as well, so it is actually more like a remake than a port. Levels and graphics redesign took most of the time. I also made 5 graphics sets instead of 3 sets from the original. 8 | 9 | Code part was relatively easy both in assembly (low-level libary) and C (game), except for few WTF bugs that took some time to figure out. There are about 1000 lines of assembly code for library, 1000 lines of FamiTone code (has been adapted easiliy), and ~1500 lines of C code. Even total number of lines, 3500, is significally less than amount of assembly code in my previous NES games that were written in assembly, had ~5000 lines each (including FamiTone too), and were much simpler gameplay and game logic wise. 10 | 11 | As the game was a bit short on RAM, I've put FamiTone vars along with palette buffer into the stack page. Despite being written in C, the game uses ~20 bytes of the stack at most. 12 | 13 | Other part of speeding up the development process was 'outsourcing' of the music. I knew it is a risky decision, because any other person involved into a project actually increases overall time, not decreases it, but I just tired from making everything by myself all the time. It did increased time very considerably - although I've negotiated about the music with kulor even before starting any actual work on the game, by different reasons including personal busyness and some misunderstanding, he only started 15th, ten days after the game development itself was completed. This amount of music revealed a lot of bugs and problems in FamiTone, not all of which were fixed, and data of one of tracks was fixed by hand due to lack of time. Music was finished 22th, just in time for DiHalt demoparty. Initially I planned to just release game, but because the party date was now close, and there was a multiplatform game compo, I decided to release the game there to get more publicity. 14 | 15 | My conclucion regarding C usage on NES is that it is worthy indeed. It speeds up and simplifies development process a lot because it greatly reduces amount of code to be written and debugged, and the code is much more readable. However, to use C you just have to know the system and 6502 very well, because debugging is much more difficult - in case of the problems when C code does not work as expected, you need to figure out what to do by examining of the generated assembly code. So it is not easy way to program for NES, it actually requires more knowledge than programming in assembly. Execution speed is, of course, lower, but this wasn't an issue for this project, the size of the generated code was more important actually - it is much larger than it could be if programmed in assembly by hand. 16 | 17 | Please note that the game is released as freeware, not Public Domain. There are three authors involved. I personally grant you rights to do whatever you want with things I created (code, sound effects, graphics), but rights to other components (game concept, characters, title, music) are reserved to authors of these components. I.e., if you want to port it somewhere else, you need to ask Denis Grachev (and Kulor, if you need music) for permission. 18 | 19 | 20 | Software used 21 | 22 | CC65 - C compiler and assembler 23 | Notepad++ - for all the code and text works 24 | FamiTracker - to make all the music and sound effects 25 | UnrealSpeccy - playing and reversing the original version 26 | Borland Turbo Explorer - to make a level editor, but it was only used to view levels 27 | FCEUX, VirtuaNES (profiler mod) - to test everything, some others for compatibility tests 28 | NES Screen Tool - to design all the graphics, screens, and levels 29 | Inkscape, Blender, GIMP, CutePDF Writer - to make manual and label -------------------------------------------------------------------------------- /roms/croom/README.html: -------------------------------------------------------------------------------- 1 | 2 | Concentration Room 3 | 4 | 5 |
6 |

Concentration Room

7 | 17 | 18 |
19 | 20 |

Overview

21 | 22 |

23 | An accident at the biochemical lab has released a neurotoxin, 24 | and you've been quarantined after exposure. Maintain your 25 | sanity by playing a card-matching game. 26 |

27 | The table is littered with 10, 20, 36, 52, or 72 face-down cards. 28 | Flip two cards, and if they show the same emblem, you keep them. 29 | If they don't, flip them back. 30 |

31 | 32 |

System Requirements

33 |

34 | Concentration Room is designed for your Nintendo Entertainment System. This version is an NROM-128 (16 KiB PRG, 8 KiB CHR), and it has been tested on a PowerPak. It also works in PC-based emulators such as Nestopia and FCE Ultra. 35 |

36 | 37 |

Modes

38 |
39 |
1 Player Story
40 | Play solitaire to start to work the toxin out of your system. Then defeat other contaminated technicians and children one on one. 41 |
1 Player Solitaire
42 | Select a difficulty level, then try to clear the table without having to turn back more than 99 non-matching pairs. 43 |
2 Players
44 | Two players take turns turning over cards. They can pass one controller back and forth or use one controller each. If a pair doesn't match, the other player presses the A and B Buttons and takes a turn. The first player to take half the pairs wins. 45 |
Vs. CPU
46 | Like 2 Players, except the second player is controlled by the NES. 47 |
48 | 49 |

FAQ (Fully Anticipated Questions)

50 |
51 |
How long have you been working on this?
52 | This is actually my third try. The logo and the earliest background sketch date back to 2000. It got held up because I lacked artistic skill on the 16x16 pixel canvas. The second try in 2007 finalized the appearance of the game, and I did some work on the "emblem designer" that will show up in a future release. In late November 2009, I discovered Dian Shi Mali, a gambling simulator for the Famicom (Asian version of the NES) that also uses 16x16 pixel emblems. After a few hours of pushing Start to rich, I was inspired to create a set of 36 emblems. By then, I was ready to code most of the game in spare time during December 2009. 53 |
Why are you still making games that don't scroll? You're better than that, as I saw in the President video.
54 | I saw it as something simple that I could finish fairly quickly in order to push falling block games off the front page of my web site. 55 |
GameTek already made two other Concentration games on the NES. Why did you make this one?
56 | The controls in I Can Remember nor Classic Concentration are clunky. Neither of them features a full 72-card deck. And of course, they're not free software. 57 |
In vs. modes, why end the game at half the cards matched instead of one more than half?
58 | Pairs early in a game require more skill to clear, and the last pair requires absolutely no skill. For example, a 20-card game tied at 4-4 will always end up 6-4. And at 5-3, the player in the lead likely got more early matches. So if we award no points for the last pair, the first player to reach half always wins. 59 |
What's that font?
60 | The font in the game's logo is called Wasted Collection. The font in Multiboot Menu was based on it. The monospace font for menu text originally appeared in the "Who's Cuter" demo and is based on Apple Chicago by Susan Kare. (Another fun font is on this page.) 61 |
Are you a Nazi?
62 | No, and that's why this game is called Concentration Room, not Concentration Camp. 63 |
64 |

Legal

65 |

66 | Copyright © 2010 Damian Yerrick <croom@pineight.com> 67 |

68 | Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. 69 |

70 | The accompanying program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License, version 3 or later. As a special exception, you may copy and distribute exact copies of the program, as published by Damian Yerrick, in iNES or UNIF executable form without source code. 71 |

72 | This product is not sponsored or endorsed by Nintendo, Ravensburger, Hasbro, Mattel, Quaker Oats, NBC Universal, GameTek, or Apple. 73 |

74 |
75 | 76 | -------------------------------------------------------------------------------- /roms/croom/croom.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/croom/croom.nes -------------------------------------------------------------------------------- /roms/lj65/lj65.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/lj65/lj65.nes -------------------------------------------------------------------------------- /roms/thwaite-0.03/CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.03 (2011-12-08) 2 | * Fixed crosshair braking failure 3 | * Can play game with a Super NES Mouse in either controller port 4 | * Switched to non-DPCM-safe controller reading code because the mouse 5 | needs it 6 | * A button icon blinks after cut scenes dialogue finishes drawing 7 | * Practice mode to start on any level 8 | * Draws much of 3x3-tile explosions with sprite flipping to save CHR 9 | * CHR rearranged to allow for more distinct tiles in cut scenes 10 | * Music for 05:00 11 | * Two ! alert sounds don't play at the same time 12 | * Some later levels are harder 13 | * I am within 256 bytes of filling PRG ROM 14 | 15 | 0.02 (2011-08-26) 16 | * Includes source code, partly under GPLv3 and partly under an 17 | all-permissive license 18 | * Added HTML5 manual 19 | * Added music for 04:00 and daytime 20 | * Dual channel drums for stronger kick and snare 21 | * Villagers warp to houses after each hour, making it clearer that an 22 | hour has passed 23 | 24 | 0.01 (2011-06-01) 25 | * initial release for nesdevcompo 26 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Thwaite 4 | 5 | 6 | 7 | 10 | 19 | 20 |

thwaite

21 |

22 | By Damian Yerrick 23 |

24 | Missiles are falling from the sky. Shoot them down with your fireworks. 25 |

26 | 35 | 36 |

Requirements

37 |

38 | Thwaite has been tested and runs fine on a Nintendo Entertainment System using a PowerPak and on the FCEUX, Mednafen, Nestopia, and Nintendulator emulators. It attempts to adapt the music and game speed when played on an NTSC or PAL NES. If you can solder, and you want to put it on a permanent NES or Famicom cartridge, you can use a ReproPak board or any NROM-128 game's board (see list from NesCartDB). 39 |

40 | To compile Thwaite from source code, you'll need to install these: 41 |

47 | 48 |

Starting the game

49 |
Title screen
50 |

51 | Insert the Game Pak into the NES Control Deck, push it down, and turn on the NES. (Or load thwaite.nes into an NES emulator.) Press Start to skip the legal notice and development status screen. Press Up or Down to select between a single-player game and a 2-player cooperative game, and press Start. 52 |

53 | Thwaite supports a Super NES Mouse connected to port 1 or 2 through an adapter. In a 1-player game, plug the mouse into either port and click "1 Player". In a 2-player game, replace one or both controllers with a mouse and click "2 Players". When a mouse is detected, an icon to change the sensitivity is shown; click this to change the sensitivity (slow, medium, fast). 54 |

55 | 56 |

Objective

57 |

58 | A hippie guitarist who has visited your small town for years has gone rogue and launched ICBMs toward your town. Break out the fireworks that you had been saving for Independence Day, turn them into makeshift anti-ballistic missiles (ABMs), and shoot down the incoming missiles. Each of two silos starts with 15 ABMs. Place the aiming cursor in front of the incoming missile and fire an ABM to destroy it. Defend the town for seven nights (Sunday through Saturday) and you win! 59 |

60 |
Screen shows cursor, missiles, balloon, MIRV, explosions, ammo, score, and time.Game play screen. Click to show or hide names of the game objects.
61 |

62 | You have to lead your shot a bit because it takes a split second for the ABM to arrive. But the explosion of an ABM is wide enough to destroy several missiles, so aim where the incoming missiles' paths are about to cross. Balloons and multiple independent reentry vehicle (MIRV) missiles will release a payload of multiple missiles if not shot down promptly. 63 |

64 | At the end of each round, the silos are refilled with ABMs: 15 each or 20 if only one silo remains. A destroyed silo will get rebuilt at the end of a night or once you've lasted one round with no destroyed houses. And if a house has been destroyed, one house will be rebuilt the next day. Later in the week, when more houses are gone, you need not worry about missiles aimed at wreckage. 65 |

66 | The game is over once both silos or all ten houses have been destroyed, or once you have survived all seven nights. 67 |

68 | 69 |

Controls

70 |
71 |
Control Pad
Move the aiming cursor
72 |
B Button or left mouse button
Fire an ABM from Milo's missile silo (left)
73 |
A Button or right mouse button
Fire an ABM from Staisy's missile silo (right)
74 |
Start Button (Player 1)
Pause or resume the game
75 |
Select Button (Player 1)
End the game (practice mode only)
76 |

77 | A 2-player game requires two controllers. In a 2-player game, both B and A Buttons of each controller fire from the same silo. Player 1 controls Milo's cursor, and player 2 controls Staisy's cursor. 78 |

79 | Because a Control Pad is less precise than the mouse or trackball that other point-and-shoot games use, Thwaite uses acceleration and braking for the cursor. To move short distances, make short taps; to move farther, hold a direction longer. Let go to stop the cursor. 80 |

81 | 82 |

Scoring

83 |

84 | At the end of each round, your team gets 100 points for each house and 10 points for each remaining ABM in your silos. There is no bonus for shooting down missiles, but destroying two or three missiles with one ABM can make more ammo available. 85 |

86 | Can you reach the end of the game with 300,000 points? 87 |

88 | 89 |

Practice

90 |
Practice menu
91 |

92 | At the title screen, choose "Practice" to begin a readiness drill. This allows practicing any of the 35 waves with buildings on the left side, the right side, or both sides. Select a day, hour, and side by pointing at it and pressing A or the mouse button, then choose Play to begin. After you select a day and hour, you can see the types of ammo and how fast the missiles will come. You will return to the practice menu if you complete the wave, fail, or press the Select button while paused. 93 |

94 | 95 |

Credits

96 | 97 | 98 |

Legal

99 |

100 | Copyright © 2011 Damian Yerrick. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. 101 |

102 | The accompanying game program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 103 |

104 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 105 |

106 | You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. 107 |

108 | Thwaite makes explosions and smoke appear translucent by flickering them. A very small percentage of people have seizures after looking at flickering patterns in television and video games. If you have had convulsions or loss of awareness of surroundings, see a health care professional before playing any video game. 109 |

110 | Intense video games can be tiring and cause repetitive strain injury. Take a five-minute break at least every half hour or so, and don't play when you need sleep. 111 |

112 | 113 | 114 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/docs/de.css: -------------------------------------------------------------------------------- 1 | /* stylesheet for Thwaite docs 2 | 3 | Copyright 2011 Damian Yerrick 4 | Copying and distribution of this file, with or without modification, 5 | are permitted in any medium without royalty provided the copyright 6 | notice and this notice are preserved. This file is offered as-is, 7 | without any warranty. 8 | 9 | */ 10 | 11 | /* load body text fonts */ 12 | @import url('http://fonts.googleapis.com/css?family=Droid+Sans:400,700|Droid+Sans+Mono'); 13 | 14 | html { background: #000 } 15 | body { background: #FFF; color: #000; margin: 0.5em auto; padding: 1em 2em; max-width: 40em; font-family: 'Droid Sans', sans-serif; } 16 | html>body code, html>body tt, html>body pre, html>body kbd { font-family: "Droid Sans Mono", monospace; font-weight: normal; font-size: 90% } 17 | h1,h2,h3,h4 { font-family: FinkHeavy, Rockwell, Lubalin Graph; font-weight: normal; margin: 1em 0 0.4em 0 } 18 | h2 { clear: right } 19 | p, pre, ul, ol, dl { margin: 0.7em 0 } 20 | pre.tip { display: table; margin: 0.7em auto; padding: 0.5em 1em; letter-spacing: 0.1em; background: #000; color: #FFF } 21 | ul.toc { border: 1px solid #AAA; padding: 1em } 22 | ul.toc li { list-style-type: none; padding: 0; margin: 0 1em 0 0; display: inline } 23 | ul.toc li:last-child { margin-right: 0 } 24 | ul.toc a { white-space: nowrap } 25 | 26 | .floatscreen { border: 1px solid black; width: 276px; float: right; margin: 0 0 1em 1em; padding: 0 8px 0.5em 8px } 27 | .floatscreen img { margin: 0 -8px 0.5em -8px; width: 292px; height: 224px; display: block } 28 | 29 | .mainscreen { border: 1px solid black; margin: 0 auto; width: 568px; padding: 0 8px 0.5em 8px; position: relative } 30 | .mainscreen img.back { margin: 0 -8px 0.5em -8px; width: 584px; height: 448px; display: block } 31 | .mainscreen img.front { position: absolute; top: 0; left: 0; width: 584px; height: 448px; display: block; z-index: 1 } 32 | 33 | table.datatable { border: 1px solid #AAA; border-collapse: collapse; border-spacing: 0 } 34 | table.datatable th { background-color: #EEE; padding: 0.1em 0.3em } 35 | table.datatable td { border: 1px solid #AAA; padding: 0.1em 0.3em } 36 | td.numalign { text-align: right } 37 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/docs/manual_ingame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/docs/manual_ingame.png -------------------------------------------------------------------------------- /roms/thwaite-0.03/docs/manual_legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/docs/manual_legend.png -------------------------------------------------------------------------------- /roms/thwaite-0.03/docs/manual_practice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/docs/manual_practice.png -------------------------------------------------------------------------------- /roms/thwaite-0.03/docs/manual_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/docs/manual_title.png -------------------------------------------------------------------------------- /roms/thwaite-0.03/docs/tas_words.txt: -------------------------------------------------------------------------------- 1 | Anastasia 2 | Anastasia's 3 | Dakotas 4 | Itasca 5 | Itasca's 6 | Latasha 7 | Latasha's 8 | Mistassini 9 | Mistassini's 10 | Natasha 11 | Natasha's 12 | Pocahontas 13 | Pocahontas's 14 | Qantas 15 | Qantas's 16 | Unitas 17 | Unitas's 18 | aftertaste 19 | aftertaste's 20 | aftertastes 21 | aortas 22 | apostasies 23 | apostasy 24 | apostasy's 25 | betas 26 | birettas 27 | cantatas 28 | catastrophe 29 | catastrophe's 30 | catastrophes 31 | catastrophic 32 | catastrophically 33 | celestas 34 | deltas 35 | demitasse 36 | demitasse's 37 | demitasses 38 | distaste 39 | distaste's 40 | distasteful 41 | distastefully 42 | distastes 43 | ecstasies 44 | ecstasy 45 | ecstasy's 46 | erratas 47 | fantasied 48 | fantasies 49 | fantasize 50 | fantasized 51 | fantasizes 52 | fantasizing 53 | fantastic 54 | fantastically 55 | fantasy 56 | fantasy's 57 | fantasying 58 | fiestas 59 | foretaste 60 | foretaste's 61 | foretasted 62 | foretastes 63 | foretasting 64 | inamoratas 65 | iotas 66 | juntas 67 | margaritas 68 | metastases 69 | metastasis 70 | metastasis's 71 | metastasize 72 | metastasized 73 | metastasizes 74 | metastasizing 75 | multitasking 76 | operettas 77 | pastas 78 | pesetas 79 | phantasied 80 | phantasies 81 | phantasm 82 | phantasm's 83 | phantasmagoria 84 | phantasmagoria's 85 | phantasmagorias 86 | phantasms 87 | phantasy 88 | phantasy's 89 | phantasying 90 | placentas 91 | potash 92 | potash's 93 | potassium 94 | potassium's 95 | quotas 96 | regattas 97 | siestas 98 | sonatas 99 | stash 100 | stashed 101 | stashes 102 | stashing 103 | succotash 104 | succotash's 105 | task 106 | task's 107 | tasked 108 | tasking 109 | taskmaster 110 | taskmaster's 111 | taskmasters 112 | tasks 113 | tassel 114 | tassel's 115 | tasseled 116 | tasseling 117 | tasselled 118 | tasselling 119 | tassels 120 | taste 121 | taste's 122 | tasted 123 | tasteful 124 | tastefully 125 | tasteless 126 | tastelessly 127 | tastelessness 128 | tastelessness's 129 | taster 130 | taster's 131 | tasters 132 | tastes 133 | tastier 134 | tastiest 135 | tastiness 136 | tastiness's 137 | tasting 138 | tasty 139 | vendettas 140 | vistas 141 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # 3 | # Makefile for Thwaite 4 | # Copyright 2011 Damian Yerrick 5 | # 6 | # Copying and distribution of this file, with or without 7 | # modification, are permitted in any medium without royalty 8 | # provided the copyright notice and this notice are preserved. 9 | # This file is offered as-is, without any warranty. 10 | # 11 | version = 0.03 12 | objlist = main random levels smoke bg missiles explosion scurry \ 13 | title practice cutscene cutscripts tips \ 14 | math bcd unpkb pads mouse kinematics \ 15 | paldetect sound music musicseq ntscPeriods 16 | 17 | CC65 = /usr/local/bin 18 | AS65 = ca65 19 | LD65 = ld65 20 | #EMU := "/C/Program Files/nintendulator/Nintendulator.exe" 21 | EMU := mednafen -nes.pal 0 -nes.input.port1 gamepad -nes.input.port2 gamepad 22 | CC = gcc 23 | ifdef COMSPEC 24 | DOTEXE=.exe 25 | else 26 | DOTEXE= 27 | endif 28 | CFLAGS = -std=gnu99 -Wall -DNDEBUG -O 29 | CFLAGS65 = 30 | objdir = obj/nes 31 | srcdir = src 32 | imgdir = tilesets 33 | 34 | # -f while debugging code; -r while adding shuffle markup; 35 | # neither once a module has stabilized 36 | shufflemode = -r 37 | 38 | objlistntsc = $(foreach o,$(objlist),$(objdir)/$(o).o) 39 | 40 | .PHONY: run dist zip 41 | 42 | run: thwaite.nes 43 | $(EMU) $< 44 | 45 | # Actually this depends on every single file in zip.in, but currently 46 | # we use changes to thwaite.nes, makefile, and README as a heuristic 47 | # for when something was changed. Limitation: it won't see changes 48 | # to docs or tools. 49 | dist: zip 50 | zip: thwaite-$(version).zip 51 | thwaite-$(version).zip: zip.in thwaite.nes README.html $(objdir)/index.txt 52 | zip -9 -u $@ -@ < $< 53 | 54 | # Some unzip tools won't create empty folders, so put a file there. 55 | $(objdir)/index.txt: makefile CHANGES.txt 56 | echo Files produced by build tools go here, but caulk goes where? > $@ 57 | 58 | $(objdir)/%.o: $(srcdir)/%.s $(srcdir)/nes.h $(srcdir)/ram.h 59 | $(AS65) $(CFLAGS65) $< -o $@ 60 | 61 | $(objdir)/%.o: $(objdir)/%.s 62 | $(AS65) $(CFLAGS65) $< -o $@ 63 | 64 | # incbins 65 | 66 | $(objdir)/title.o: todo.txt src/title.pkb 67 | $(objdir)/cutscene.o: src/cutscene.pkb 68 | $(objdir)/practice.o: src/practice.txt 69 | 70 | $(objdir)/ntscPeriods.s: tools/mktables.py 71 | $< period $@ 72 | 73 | map.txt thwaite.prg: nes.ini $(objlistntsc) 74 | $(LD65) -C $^ -m map.txt -o thwaite.prg 75 | 76 | $(objdir)/%.chr: $(imgdir)/%.png 77 | tools/pilbmp2nes.py $< $@ 78 | 79 | %.nes: %.prg %.chr 80 | cat $^ > $@ 81 | 82 | thwaite.chr: $(objdir)/maingfx.chr $(objdir)/cuthouses.chr 83 | cat $^ > $@ 84 | 85 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/nes.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Linker script for Concentration Room (lite version) 3 | # Copyright 2010 Damian Yerrick 4 | # 5 | # Copying and distribution of this file, with or without 6 | # modification, are permitted in any medium without royalty 7 | # provided the copyright notice and this notice are preserved. 8 | # This file is offered as-is, without any warranty. 9 | # 10 | MEMORY { 11 | ZP: start = $10, size = $f0, type = rw; 12 | # use first $10 zeropage locations as locals 13 | HEADER: start = 0, size = $0010, type = ro, file = %O, fill=yes, fillval=$00; 14 | RAM: start = $0300, size = $0500, type = rw; 15 | ROM7: start = $C000, size = $4000, type = ro, file = %O, fill=yes, fillval=$FF; 16 | } 17 | 18 | SEGMENTS { 19 | INESHDR: load = HEADER, type = ro, align = $10; 20 | ZEROPAGE: load = ZP, type = zp; 21 | BSS: load = RAM, type = bss, define = yes, align = $100; 22 | CODE: load = ROM7, type = ro, align = $100; 23 | RODATA: load = ROM7, type = ro, align = $100; 24 | VECTORS: load = ROM7, type = ro, start = $FFFA; 25 | } 26 | 27 | FILES { 28 | %O: format = bin; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/obj/nes/index.txt: -------------------------------------------------------------------------------- 1 | Files produced by build tools go here, but caulk goes where? 2 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/bcd.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; Binary to decimal conversion for 8-bit numbers 3 | ; Copyright 2010 Damian Yerrick 4 | ; 5 | ; Copying and distribution of this file, with or without 6 | ; modification, are permitted in any medium without royalty provided 7 | ; the copyright notice and this notice are preserved in all source 8 | ; code copies. This file is offered as-is, without any warranty. 9 | ; 10 | .export bcd8bit 11 | 12 | .macro bcd8bit_iter value 13 | .local skip 14 | cmp value 15 | bcc skip 16 | sbc value 17 | skip: 18 | rol highDigits 19 | .endmacro 20 | 21 | ;; 22 | ; Converts a decimal number to two or three BCD digits 23 | ; in no more than 84 cycles. 24 | ; @param a the number to change 25 | ; @return a: low digit; 0: upper digits as nibbles 26 | .proc bcd8bit 27 | highDigits = 0 28 | pha 29 | lda #0 30 | sta 0 31 | pla 32 | 33 | ; Each iteration takes 11 if subtraction occurs or 10 if not. 34 | ; But if 80 is subtracted, 40 and 20 aren't, and if 200 is 35 | ; subtracted, 80 is not, and at least one of 40 and 20 is not. 36 | ; So this part takes up to 6*11-2 cycles. 37 | bcd8bit_iter #200 38 | bcd8bit_iter #100 39 | bcd8bit_iter #80 40 | bcd8bit_iter #40 41 | bcd8bit_iter #20 42 | bcd8bit_iter #10 43 | rts 44 | .endproc 45 | 46 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/cutscene.pkb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/src/cutscene.pkb -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/cutscene.s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/src/cutscene.s -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/cutscripts.s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/src/cutscripts.s -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/explosion.s: -------------------------------------------------------------------------------- 1 | ; explosions.s 2 | ; Explosion drawing and explosion-missile collision code for Thwaite 3 | 4 | ;;; Copyright (C) 2011 Damian Yerrick 5 | ; 6 | ; This program is free software; you can redistribute it and/or 7 | ; modify it under the terms of the GNU General Public License 8 | ; as published by the Free Software Foundation; either version 3 9 | ; of the License, or (at your option) any later version. 10 | ; 11 | ; This program is distributed in the hope that it will be useful, 12 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ; GNU General Public License for more details. 15 | ; 16 | ; You should have received a copy of the GNU General Public License 17 | ; along with this program; if not, write to 18 | ; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | ; Boston, MA 02111-1307, USA. 20 | ; 21 | ; Visit http://www.pineight.com/ for more information. 22 | 23 | .include "src/nes.h" 24 | .include "src/ram.h" 25 | 26 | ; 27 | ; Because of NES PPU limitations, only half the explosions are 28 | ; processed on each frame: even ones in even frames and odd ones in 29 | ; odd frames. This adds (predictable) flicker. 30 | ; 31 | .segment "BSS" 32 | explodeX: .res NUM_EXPLOSIONS 33 | explodeY: .res NUM_EXPLOSIONS 34 | explodeTime: .res NUM_EXPLOSIONS 35 | 36 | ; If zero, tiles for a 3x3 tile explosion are in x, x+$01, and x+$02. 37 | ; If nonzero, they're in x, x^$01, and x, with the right column 38 | ; horizontally flipped. 39 | EXPLOSION_XMIRROR = 1 40 | 41 | ; If zero, tile rows for a 3x3 tile explosion are at x, x+$10, and 42 | ; x+$20, with x an element in explodeFrameStartTile. If nonzero, 43 | ; they're in x, x^$10, and x, with the bottom row vertically flipped. 44 | EXPLOSION_YMIRROR = 1 45 | 46 | .segment "CODE" 47 | 48 | ;; 49 | ; Sets all explosions inactive. 50 | ; Does not modify memory. 51 | .proc clearExplosions 52 | lda #$FF 53 | ldx #NUM_EXPLOSIONS-1 54 | loop: 55 | sta explodeTime,x 56 | dex 57 | bpl loop 58 | rts 59 | .endproc 60 | 61 | ;; 62 | ; Makes an explosion at (x, y). 63 | ; LOCAL-8 compliant 64 | .proc makeExplosion 65 | xpos = 0 66 | ypos = 1 67 | stx xpos 68 | sty ypos 69 | 70 | ; Look for the best slot for this explosion. 71 | ; The "best" slot is the one whose explosion has been on screen 72 | ; the longest. 73 | ; y holds the index of the best slot so far 74 | ; x holds the index of the slot currently being considered 75 | ldx #NUM_EXPLOSIONS - 2 76 | ldy #NUM_EXPLOSIONS - 1 77 | 78 | findFreeSlot: 79 | ; if the time in x is less than the time in y, y is a better slot 80 | lda explodeTime,x 81 | cmp explodeTime,y 82 | bcc notBetterSlot 83 | ; otherwise x is a better slot, so set y to x 84 | txa 85 | tay 86 | notBetterSlot: 87 | dex 88 | bpl findFreeSlot 89 | lda tvSystem 90 | beq isNTSC 91 | lda #explodeTimeToFramePAL-explodeTimeToFrame 92 | isNTSC: 93 | sta explodeTime,y 94 | lda xpos 95 | sta explodeX,y 96 | lda ypos 97 | sta explodeY,y 98 | 99 | lda #SFX_BOOM_NOISE 100 | jsr start_sound 101 | lda #SFX_BOOM_SQUARE 102 | jmp start_sound 103 | .endproc 104 | 105 | 106 | ;; 107 | ; Draws explosion number X and checks for collision with missiles. 108 | ; Does not modify X. 109 | ; Not LOCAL-8 compliant, but it is called only by updateAllExplosions 110 | ; which should handle the saving and restoring. 111 | .proc updateExplosionX 112 | ; these are used during collision 113 | exploRadius = 12 114 | exploX = 13 115 | exploY = 14 116 | exploNumber = 15 117 | 118 | ; these are used only during drawing 119 | rowNum = 0 120 | curTile = 1 121 | attr2 = 2 122 | exploXHi = 3 123 | startTile = 4 124 | tileSize = exploRadius 125 | 126 | stx exploNumber 127 | ; Make sure this explosion is active (time < 128) 128 | lda explodeTime,x 129 | bmi isDone 130 | tay 131 | lda explodeTimeToFrame,y 132 | bpl runExplosionFrame 133 | sta explodeTime,x 134 | isDone: 135 | jmp bail 136 | runExplosionFrame: 137 | tay 138 | lda explodeFrameStartTile,y 139 | sta startTile 140 | lda explodeFrameTileSize,y 141 | sta tileSize 142 | asl a 143 | asl a 144 | eor #$FF 145 | pha 146 | sec 147 | adc explodeX,x 148 | sta exploX 149 | lda #$FF 150 | adc #$00 151 | sta exploXHi 152 | pla 153 | clc 154 | adc explodeY,x 155 | sta exploY 156 | ldx oamIndex 157 | lda #0 158 | sta rowNum 159 | lda #$01 160 | sta attr2 161 | rowloop: 162 | .if ::EXPLOSION_XMIRROR 163 | lda attr2 164 | and #<~$40 165 | sta attr2 166 | .endif 167 | lda startTile 168 | sta curTile 169 | ldy #0 ; y = x position within sprite 170 | tileloop: 171 | lda curTile 172 | sta OAM+1,x 173 | .if ::EXPLOSION_XMIRROR 174 | eor #%0001 175 | sta curTile 176 | .else 177 | inc curTile 178 | .endif 179 | tya 180 | asl a 181 | asl a 182 | asl a 183 | adc exploX 184 | sta OAM+3,x 185 | lda #0 186 | adc exploXHi 187 | bne skipOneTile 188 | lda attr2 189 | sta OAM+2,x 190 | lda rowNum 191 | asl a 192 | asl a 193 | asl a 194 | adc exploY 195 | cmp #224 196 | bcs skipOneTile 197 | sta OAM,x 198 | inx 199 | inx 200 | inx 201 | inx 202 | skipOneTile: 203 | iny 204 | .if ::EXPLOSION_XMIRROR 205 | cpy #2 206 | bcc nosetxflip 207 | lda attr2 208 | ora #$40 209 | sta attr2 210 | nosetxflip: 211 | .endif 212 | cpy tileSize 213 | bcc tileloop 214 | 215 | lda startTile 216 | .if ::EXPLOSION_YMIRROR 217 | eor #$10 218 | .else 219 | adc #$0F ; row width: 10; minus 1 because carry is set 220 | .endif 221 | sta startTile 222 | inc rowNum 223 | lda rowNum 224 | .if ::EXPLOSION_YMIRROR 225 | cmp #2 226 | bcc nosetyflip 227 | lda attr2 228 | ora #$80 229 | sta attr2 230 | lda rowNum 231 | nosetyflip: 232 | .endif 233 | cmp tileSize 234 | bcc rowloop 235 | 236 | ; now we're done drawing, so let's load the variables 237 | ; used for collision 238 | distSquared = rowNum 239 | stx oamIndex 240 | ldx exploNumber 241 | cpx #NUM_EXPLOSIONS 242 | bcc notOverflow 243 | lda #$0F 244 | sta debugHex1 245 | stx debugHex2 246 | jmp collision_continue 247 | notOverflow: 248 | lda explodeX,x 249 | sta exploX 250 | lda explodeY,x 251 | sta exploY 252 | ldy explodeTime,x 253 | lda explodeTimeToFrame,y 254 | tay 255 | lda explodeFrameRadiusSquared,y 256 | sta exploRadius 257 | 258 | ldx #4 259 | collision_loop: 260 | lda missileYHi,x 261 | beq collision_continue 262 | lda exploY 263 | beq collision_continue 264 | sec 265 | sbc missileYHi,x 266 | bcs yNotNeg 267 | eor #$FF 268 | adc #1 269 | bne yMadePos 270 | yNotNeg: 271 | sec 272 | ldy missileType,x 273 | sbc missileCollisionHt,y 274 | bcs yMadePos 275 | lda #0 276 | yMadePos: 277 | cmp #12 278 | bcs collision_continue 279 | tay 280 | lda xSquared,y 281 | sta distSquared 282 | 283 | lda missileXHi,x 284 | beq collision_continue 285 | sec 286 | sbc exploX 287 | bcs xNotNeg 288 | eor #$FF 289 | adc #1 290 | xNotNeg: 291 | cmp #12 292 | bcs collision_continue 293 | tay 294 | lda xSquared,y 295 | adc distSquared 296 | bcs collision_continue 297 | cmp exploRadius 298 | bcs collision_continue 299 | 300 | jmp found_explosion 301 | collision_continue: 302 | inx 303 | cpx #NUM_MISSILES 304 | bcc collision_loop 305 | 306 | ldx exploNumber 307 | ; now we're done; go to next frame 308 | inc explodeTime,x 309 | 310 | bail: 311 | ; epilog: callee-saved variables 312 | rts 313 | 314 | ; This part of the code is put down here so that the branch back to 315 | ; collision loop will be within +/- 128 bytes. 316 | found_explosion: 317 | txa 318 | pha 319 | ldy missileType,x 320 | lda missileCollisionHt,y 321 | lsr a 322 | adc missileYHi,x 323 | tay 324 | lda #0 325 | sta missileYHi,x 326 | lda missileXHi,x 327 | tax 328 | jsr makeExplosion 329 | pla 330 | tax 331 | 332 | ; if a building is threatened, take the building out of threat 333 | ; (a double-threatened building will get re-threatened next time) 334 | ldy missileTarget,x 335 | cpy #12 336 | bcs badTarget 337 | lda housesStanding,y 338 | cmp #2 339 | bne badTarget 340 | lda #1 341 | sta housesStanding,y 342 | .if ::SHOW_THREATENED 343 | lda #BG_DIRTY_HOUSES 344 | ora bgDirty 345 | sta bgDirty 346 | .endif 347 | badTarget: 348 | jmp collision_continue 349 | .endproc 350 | 351 | ;; 352 | ; Update even or odd explosions: draw them and check for collisions. 353 | ; 354 | .proc updateAllExplosions 355 | 356 | ; Save three callee saved regs 357 | lda 15 358 | pha 359 | lda 14 360 | pha 361 | lda 13 362 | pha 363 | lda 12 364 | pha 365 | 366 | ; update even explosions in even frames and odd explosions 367 | ; in odd frames, so that only half the explosions take up 368 | ; space in OAM 369 | lda nmis 370 | ora #$FE 371 | clc 372 | adc #NUM_EXPLOSIONS 373 | tax 374 | exploLoop: 375 | jsr updateExplosionX 376 | dex 377 | dex 378 | bpl exploLoop 379 | 380 | pla 381 | sta 12 382 | pla 383 | sta 13 384 | pla 385 | sta 14 386 | pla 387 | sta 15 388 | rts 389 | rts 390 | .endproc 391 | 392 | .segment "RODATA" 393 | explodeTimeToFrame: 394 | .byt 0,0,0,0,1,1,1,1,2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,5,5,5,5, $FF 395 | explodeTimeToFramePAL: 396 | .byt 0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,3,4,4,4,4,5,5,5, $FF 397 | explodeFrameStartTile: 398 | .byt $20,$22,$24,$26,$28,$2A 399 | explodeFrameTileSize: 400 | .byt 1, 2, 3, 3, 3, 3 401 | 402 | ; if xSquared[abs(dx)] + ySquared[abs(dy)] < explodeFrameRadiusSquared[frame] 403 | ; then there is a hit 404 | explodeFrameRadiusSquared: 405 | .byt 24, 64, 100, 144, 144, 144 406 | xSquared: 407 | .byt 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225 408 | ; in order of MISSILE_TYPE_* at the top of missiles.s 409 | missileCollisionHt: 410 | .byt 0, 0, 10 411 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/kinematics.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; acceleration/brake mechanics 3 | ; Copyright 2011 Damian Yerrick 4 | ; 5 | ; Copying and distribution of this file, with or without 6 | ; modification, are permitted in any medium without royalty provided 7 | ; the copyright notice and this notice are preserved in any source 8 | ; code copies. This file is offered as-is, without any warranty. 9 | ; 10 | .include "src/ram.h" 11 | 12 | ;; 13 | ; Applies acceleration, braking, and speed limit. 14 | ; XY untouched. 15 | .proc accelBrakeLimit 16 | lsr abl_keys 17 | bcc notAccelRight 18 | 19 | ; if traveling to left, brake instead 20 | lda abl_vel+1 21 | bmi notAccelRight 22 | 23 | ; Case 1: nonnegative velocity, accelerating positive 24 | clc 25 | lda abl_accelRate 26 | adc abl_vel 27 | sta abl_vel 28 | lda #0 29 | adc abl_vel+1 30 | sta abl_vel+1 31 | 32 | ; clamp maximum velocity 33 | lda abl_vel 34 | cmp abl_maxVel 35 | lda abl_vel+1 36 | sbc abl_maxVel+1 37 | bcc notOverPosLimit 38 | lda abl_maxVel 39 | sta abl_vel 40 | lda abl_maxVel+1 41 | sta abl_vel+1 42 | notOverPosLimit: 43 | rts 44 | notAccelRight: 45 | 46 | lsr abl_keys 47 | bcc notAccelLeft 48 | ; if traveling to right, brake instead 49 | lda abl_vel+1 50 | bmi isAccelLeft 51 | ora abl_vel 52 | bne notAccelLeft 53 | isAccelLeft: 54 | 55 | ; Case 2: nonpositive velocity, accelerating negative 56 | ;sec ; already guaranteed set from bcc statement above 57 | lda abl_accelRate 58 | eor #$FF 59 | adc abl_vel 60 | sta abl_vel 61 | lda #$FF 62 | adc abl_vel+1 63 | sta abl_vel+1 64 | 65 | ; clamp maximum velocity 66 | clc 67 | lda abl_maxVel 68 | adc abl_vel 69 | lda abl_maxVel+1 70 | adc abl_vel+1 71 | bcs notUnderNegLimit 72 | sec 73 | lda #0 74 | sbc abl_maxVel 75 | sta abl_vel 76 | lda #0 77 | sbc abl_maxVel+1 78 | sta abl_vel+1 79 | notUnderNegLimit: 80 | rts 81 | notAccelLeft: 82 | 83 | lda abl_vel+1 84 | bmi brakeNegVel 85 | 86 | ; Case 3: Velocity > 0 and brake 87 | sec 88 | lda abl_vel 89 | sbc abl_brakeRate 90 | sta abl_vel 91 | lda abl_vel+1 92 | sbc #0 93 | bcs notZeroVelocity 94 | zeroVelocity: 95 | lda #0 96 | sta abl_vel 97 | notZeroVelocity: 98 | sta abl_vel+1 99 | rts 100 | 101 | brakeNegVel: 102 | ; Case 4: Velocity < 0 and brake 103 | clc 104 | lda abl_vel 105 | adc abl_brakeRate 106 | sta abl_vel 107 | lda abl_vel+1 108 | adc #0 109 | bcs zeroVelocity 110 | sta abl_vel+1 111 | rts 112 | .endproc 113 | 114 | 115 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/levels.s: -------------------------------------------------------------------------------- 1 | ; levels.s 2 | ; Level loading code and level definitions for Thwaite 3 | 4 | ;;; Copyright (C) 2011 Damian Yerrick 5 | ; 6 | ; This program is free software; you can redistribute it and/or 7 | ; modify it under the terms of the GNU General Public License 8 | ; as published by the Free Software Foundation; either version 3 9 | ; of the License, or (at your option) any later version. 10 | ; 11 | ; This program is distributed in the hope that it will be useful, 12 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ; GNU General Public License for more details. 15 | ; 16 | ; You should have received a copy of the GNU General Public License 17 | ; along with this program; if not, write to 18 | ; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | ; Boston, MA 02111-1307, USA. 20 | ; 21 | ; Visit http://www.pineight.com/ for more information. 22 | 23 | .include "src/ram.h" 24 | 25 | .import levelTips 26 | 27 | .segment "RODATA" 28 | ; Seven parameters vary per round: the speed of the missiles, the 29 | ; number of missiles before the round ends, the time between salvos 30 | ; (groups of missiles spawned together), and the four kinds of salvos 31 | ; that appear in the round. There is an unused flags byte, which was 32 | ; originally going to store salvo types until I decided on the 33 | ; current system. 34 | ; 35 | ; Salvo types 1-5 are just that many missiles. 36 | ; 8 is a balloon (costs 2 missiles) 37 | ; 9 is a MIRV (costs 2 missiles) 38 | 39 | ; Number of days in levels.s::levelTable and tips.s::levelTips 40 | NUM_MADE_DAYS = 7 41 | 42 | ; Debug build to test balance starting at a specific day 43 | ; (0: sun; 7: sat) 44 | ; Always set to 0 for release builds. 45 | SKIP_TO_DAY = 0 46 | 47 | levelTable: 48 | ; +----------------------- missile speed (unit: px/85 frames) 49 | ; | +------------------- number of missiles 50 | ; | | +--------------- time between salvos (unit: 100 ms) 51 | ; | | | +----------- flags (unused) 52 | ; | | | | +- salvo types (1, 2, 3, 4, 5, 8, 9) 53 | ; | | | | | 54 | ;SUN spd num tim unused salvo types 55 | ; The first level is almost painfully slow. It's for nooblets 56 | ; and grandmas. 57 | .byt 16, 10, 50,%00000000, 1, 1, 2, 2 58 | .byt 17, 15, 45,%00000000, 2, 2, 3, 3 59 | .byt 18, 20, 40,%00000000, 2, 3, 4, 8 60 | .byt 19, 25, 37,%00000000, 3, 4, 4, 9 61 | .byt 20, 30, 35,%00000000, 3, 4, 5, 9 62 | 63 | ;MON spd num tim types 64 | .byt 20, 20, 30,%00000000, 8, 3, 4, 8 65 | .byt 21, 25, 25,%00000000, 9, 3, 4, 8 66 | .byt 22, 30, 20,%00000000, 9, 4, 4, 8 67 | ; Occasionally, we surprise the player with levels where the 68 | ; progression in difficulty is irregular, in order to teach the 69 | ; player to adapt to different play styles. 70 | ; Mon 04 has unusually fast missiles, but not as many of them. 71 | .byt 64, 15, 30,%00000000, 1, 1, 2, 2 72 | .byt 24, 30, 25,%00000000, 4, 4, 9, 9 73 | 74 | ;TUE 75 | ; Tue 01 is a preview of Balloon Fever. 76 | .byt 25, 30, 20,%00000000, 4, 8, 8, 8 77 | .byt 26, 30, 25,%00000000, 3, 3, 4, 8 78 | .byt 28, 30, 25,%00000000, 3, 4, 4, 8 79 | .byt 29, 32, 25,%00000000, 3, 4, 5, 8 80 | .byt 30, 32, 25,%00000000, 9, 4, 5, 8 81 | 82 | ;WED 83 | ; Wed 01 is the first of the "Balloon Fever" levels. All missiles 84 | ; are either balloons or MIRVs, and the number of missiles is 85 | ; cranked up to reflect that each balloon or MIRV uses up two 86 | ; missiles. If the aim is off or slow, watch split then boom. 87 | ; Fortunately, the player always has both silos at the beginning 88 | ; of a day, but it's easy to lose them if the player runs out of 89 | ; ammo. And it's really easy to run out of ammo when cleaning up 90 | ; after a couple splits. So for this reason don't crank up the 91 | ; ammo more than 40. 92 | .byt 44, 34, 20,%00000000, 8, 8, 8, 9 93 | .byt 32, 30, 24,%00000000, 9, 3, 4, 9 94 | ; Wed 03 is another fast level 95 | .byt 64, 20, 30,%00000000, 1, 1, 2, 2 96 | .byt 33, 28, 23,%00000000, 8, 3, 5, 8 97 | .byt 34, 35, 22,%00000000, 9, 4, 5, 8 98 | 99 | ;THU 100 | .byt 52, 36, 18,%00000000, 8, 8, 8, 9 101 | .byt 34, 25, 22,%00000000, 9, 4, 5, 9 102 | .byt 35, 25, 21,%00000000, 8, 4, 5, 8 103 | .byt 36, 30, 21,%00000000, 9, 4, 5, 9 104 | .byt 37, 35, 20,%00000000, 9, 4, 5, 8 ; yup, a wakeup call level 105 | ;FRI 106 | .byt 58, 38, 16,%00000000, 8, 8, 8, 9 107 | .byt 35, 30, 20,%00000000, 9, 4, 5, 9 108 | .byt 36, 30, 20,%00000000, 8, 4, 5, 8 109 | .byt 72, 20, 30,%00000000, 1, 2, 2, 9 110 | .byt 38, 35, 20,%00000000, 9, 4, 5, 8 111 | ;SAT 112 | .byt 64, 40, 15,%00000000, 8, 8, 8, 9 113 | .byt 37, 30, 20,%00000000, 9, 4, 5, 9 114 | .byt 38, 30, 20,%00000000, 8, 4, 5, 8 115 | .byt 39, 30, 20,%00000000, 9, 4, 5, 9 116 | .byt 40, 35, 20,%00000000, 9, 4, 5, 8 117 | 118 | 119 | .segment "BSS" 120 | ; Do not shuffle these; they must remain in the same order as in 121 | ; levelTable or level loading will fail. 122 | levelMissileSpeed: .res 1 123 | enemyMissilesLeft: .res 1 124 | levelReleasePeriod: .res 1 125 | levelMissileFlags: .res 1 126 | levelSalvoSizes: .res 4 127 | 128 | .segment "CODE" 129 | 130 | ;; 131 | ; Loads the data for a level 132 | ; @param A level number (0-63) 133 | .proc loadLevel 134 | tax 135 | lda isPractice 136 | bne noLoadTip 137 | lda levelTips,x 138 | sta curTip 139 | cpx #1 140 | bne notTwoPlayerReplace 141 | lda numPlayers 142 | cmp #2 143 | bne notTwoPlayerReplace 144 | lda #11 ; two player tip 145 | sta curTip 146 | notTwoPlayerReplace: 147 | lda #BG_DIRTY_TIP 148 | ora bgDirty 149 | sta bgDirty 150 | lda #50 151 | sta tipTimeLeft 152 | noLoadTip: 153 | txa 154 | asl a 155 | asl a 156 | asl a 157 | sta 0 158 | lda #0 159 | rol a 160 | sta 1 161 | lda #levelTable 165 | adc 1 166 | sta 1 167 | ldy #7 168 | copyloop: 169 | lda (0),y 170 | sta levelMissileSpeed,y 171 | dey 172 | bpl copyloop 173 | 174 | ; Make missiles 18.75% faster on PAL (should be 20% but 175 | ; who'll notice?) 176 | ldx tvSystem 177 | beq noPALCorrection 178 | lsr a 179 | adc levelMissileSpeed 180 | lsr a 181 | lsr a 182 | lsr a 183 | adc levelMissileSpeed 184 | sta levelMissileSpeed 185 | noPALCorrection: 186 | 187 | rts 188 | .endproc 189 | 190 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/math.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; math.s 3 | ; Arithmetic and trigonometry routines for Thwaite 4 | ; 5 | ; Copyright (c) 2011 Damian Yerrick 6 | ; 7 | ; Copying and distribution of this file, with or without 8 | ; modification, are permitted in any medium without royalty provided 9 | ; the copyright notice and this notice are preserved in all source 10 | ; code copies. This file is offered as-is, without any warranty. 11 | ; 12 | 13 | ; 14 | ; The NES CPU has no FPU, nor does it have a multiplier or divider 15 | ; for integer math. So we have to implement these in software. 16 | ; Here are subroutines to compute 8x8=16-bit product, a fractional 17 | ; quotient in 0.8 fixed point, 2-argument arctangent, and rectangular 18 | ; to polar coordinate conversion. Also included are lookup tables of 19 | ; sine and cosine for angles expressed in units of 1/32 of a turn 20 | ; from due right, where cos(0) = cos(32) = sin(8) = 1.0. 21 | ; 22 | ; Further information: 23 | ; http://en.wikipedia.org/wiki/Fixed-point_arithmetic 24 | ; http://en.wikipedia.org/wiki/Binary_multiplier 25 | ; http://en.wikipedia.org/wiki/Boxing_the_compass 26 | ; http://en.wikipedia.org/wiki/Binary_scaling#Binary_angles 27 | ; 28 | 29 | .include "src/ram.h" 30 | .segment "CODE" 31 | 32 | ;; 33 | ; Multiplies two 8-bit factors to produce a 16-bit product 34 | ; in about 153 cycles. 35 | ; @param A one factor 36 | ; @param Y another factor 37 | ; @return high 8 bits in A; low 8 bits in $0000 38 | ; Y and $0001 are trashed; X is untouched 39 | .proc mul8 40 | factor2 = 1 41 | prodlo = 0 42 | 43 | ; Factor 1 is stored in the lower bits of prodlo; the low byte of 44 | ; the product is stored in the upper bits. 45 | lsr a ; prime the carry bit for the loop 46 | sta prodlo 47 | sty factor2 48 | lda #0 49 | ldy #8 50 | loop: 51 | ; At the start of the loop, one bit of prodlo has already been 52 | ; shifted out into the carry. 53 | bcc noadd 54 | clc 55 | adc factor2 56 | noadd: 57 | ror a 58 | ror prodlo ; pull another bit out for the next iteration 59 | dey ; inc/dec don't modify carry; only shifts and adds do 60 | bne loop 61 | rts 62 | .endproc 63 | 64 | ;; 65 | ; Computes 256*a/y. Useful for finding slopes. 66 | ; 0 and 1 are trashed. 67 | .proc getSlope1 68 | quotient = 0 69 | divisor = 1 70 | 71 | sty divisor 72 | ldy #1 ; when this gets ROL'd eight times, the loop ends 73 | sty quotient 74 | loop: 75 | asl a 76 | bcs alreadyGreater 77 | cmp divisor 78 | bcc nosub 79 | alreadyGreater: 80 | sbc divisor 81 | sec ; without this, results using alreadyGreater are wrong 82 | ; thx to http://6502org.wikidot.com/software-math-intdiv 83 | ; for helping solve this 84 | nosub: 85 | rol quotient 86 | bcc loop 87 | lda quotient 88 | rts 89 | .endproc 90 | 91 | ;; 92 | ; Find the angle of a vector pointing from (x1, y1) to (x2, y2), 93 | ; all coordinates unsigned. 94 | ; This is also called arctan2 or (on TI calculators) "R>Ptheta". 95 | ; @param 0 x1 96 | ; @param 1 y1 97 | ; @param 2 x2 98 | ; @param 3 y2 99 | ; @return A: angle (0-31); 100 | ; 0: slope reflected into first octant; 101 | ; 1: angle reflected into first octant; 102 | ; 2, 3: point reflected into first octant 103 | ; Trashes Y and nothing else. 104 | .proc getAngle 105 | x1 = 0 106 | y1 = 1 107 | x2 = 2 108 | y2 = 3 109 | flags = 4 110 | 111 | lda y2 112 | cmp y1 113 | bne notHorizontal 114 | lda x2 115 | cmp x1 116 | lda #0 117 | sta 1 118 | bcs :+ 119 | lda #16 120 | : 121 | rts 122 | 123 | notHorizontal: 124 | ; make sure x2 > x1 125 | lda x2 126 | cmp x1 127 | bne notVertical 128 | lda y2 129 | cmp y1 130 | lda #$FF 131 | sta 3 132 | lda #0 ; store first-octant angle 133 | sta 1 134 | lda #24 135 | bcc :+ 136 | lda #8 137 | : 138 | rts 139 | notVertical: 140 | 141 | ; At this point, we have already eliminated the special cases of a 142 | ; perfectly horizontal or vertical ray. 143 | ; So now compute the sign and abs of (y2 - y1) 144 | sec 145 | lda y2 146 | sbc y1 147 | bcs noVerticalFlip 148 | eor #$FF 149 | adc #1 150 | noVerticalFlip: 151 | sta y1 152 | 153 | lda #0 154 | rol a 155 | sta flags ; flag 2: SKIP y flip (angle = 16 - angle) 156 | 157 | ; Compute the sign and abs of (x2 - x1) 158 | sec 159 | lda x2 160 | sbc x1 161 | bcs noHorizontalFlip 162 | eor #$FF 163 | adc #1 164 | noHorizontalFlip: 165 | sta x1 166 | rol flags ; flag 1: SKIP x flip (angle = 8 - angle) 167 | 168 | ; if x1 > y1 then swap x1 and y1 169 | lda y1 170 | cmp x1 171 | bcc noDiagonalFlip 172 | ldy x1 173 | sty y1 174 | sta x1 175 | noDiagonalFlip: 176 | rol flags ; flag 0: PERFORM diagonal flip (angle = 4 - angle) 177 | 178 | lda y1 179 | sta y2 180 | ldy x1 181 | sty x2 182 | jsr getSlope1 183 | sta x1 184 | 185 | ldy #4 186 | tansearch: 187 | cmp tantable-1,y 188 | bcs foundTan 189 | dey 190 | bne tansearch 191 | foundTan: 192 | tya 193 | sta y1 194 | 195 | lsr flags 196 | bcc noUndoDiagonal 197 | eor #$FF ; reverse-subtract 8 198 | adc #8 199 | noUndoDiagonal: 200 | 201 | lsr flags 202 | bcs noUndoHorizontal 203 | eor #$FF ; reverse-subtract 16 204 | adc #17 ; plus one because we came in with clc 205 | noUndoHorizontal: 206 | 207 | lsr flags 208 | bcs noUndoVertical 209 | eor #$FF ; reverse-subtract 32 210 | adc #33 ; plus one because we came in with clc 211 | noUndoVertical: 212 | 213 | rts 214 | .endproc 215 | 216 | ;; 217 | ; Finds the approximate euclidean distance from the silo to the crosshair. 218 | ; @param A x1 silo X (usually 64 or 192) 219 | ; @param 2 x2 target X 220 | ; @param 3 y2 target Y 221 | ; @return A: pixel distance (high bit in carry); 3: actual angle 222 | ; Y and 0-5 are trashed 223 | .proc measureFromSilo 224 | ; first special-case pointing straight up 225 | sta 0 226 | cmp 2 227 | bne notVertical 228 | lda #SILO_Y 229 | sbc 3 230 | ldy #24 231 | sty 3 232 | rts 233 | notVertical: 234 | lda #SILO_Y 235 | sta 1 236 | jsr getAngle 237 | 238 | ; getAngle flips the coordinates into the first octant and returns 239 | ; a vector v and the angle theta within pi/16 radians of v. 240 | ; From theta we look up u, a unit vector nearly parallel to v, 241 | ; and then compute length = u dot v. 242 | ; But treat the cases where u is axis-aligned separately for speed 243 | ; and because 256*cos(90deg) = 256, which overflows 8 bits. 244 | ldy 1 245 | bne notAngle0 246 | sta 3 247 | lda 2 248 | rts 249 | notAngle0: 250 | pha 251 | lda sine256Q1,y 252 | sta 5 253 | lda cosine256Q1,y 254 | sta 4 255 | ldy 2 256 | jsr mul8 257 | sta 4 258 | ldy 3 259 | pla 260 | sta 3 261 | lda 5 262 | jsr mul8 263 | clc 264 | adc 4 265 | rts 266 | .endproc 267 | 268 | 269 | .segment "RODATA" 270 | 271 | ; Tangents of angles between the ordinary angles, used by getAngle. 272 | ; you can make trig tables even in windows calculator 273 | ; (90/16*1)t*256= 25 274 | tantable: 275 | .byt 25, 78, 137, 210 276 | 277 | ; Accurate sin/cos table used by measureFromSilo. 278 | ; These are indexed by angle in quadrant 1, and scaled by 256. 279 | ; (90*7/8)s*256= 280 | sine256Q1: 281 | .byt 0, 50, 98, 142, 181, 213, 237, 251 282 | cosine256Q1: 283 | .byt 0, 251, 237, 213, 181, 142, 98, 50 284 | 285 | ; Less precise sin/cos table used by e.g. missile smoke generation. 286 | ; These are indexed by angle through the whole circle 287 | ; and scaled by 64. 288 | ; (90*7/8)s*64= 289 | missileSine: 290 | .byt 0, 12, 24, 36, 45, 53, 59, 63 291 | missileCosine: 292 | .byt 64, 63, 59, 53, 45, 36, 24, 12 293 | .byt 0,-12,-24,-36,-45,-53,-59,-63 294 | .byt -64,-63,-59,-53,-45,-36,-24,-12 295 | .byt 0, 12, 24, 36, 45, 53, 59, 63 296 | 297 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/mouse.s: -------------------------------------------------------------------------------- 1 | .export read_mouse, mouse_change_sensitivity 2 | .exportzp cur_mbuttons, new_mbuttons 3 | .segment "ZEROPAGE" 4 | cur_mbuttons: .res 2 5 | new_mbuttons: .res 2 6 | 7 | .segment "CODE" 8 | ;; 9 | ; @param X player number 10 | .proc read_mouse 11 | lda #1 12 | sta 1 13 | sta 2 14 | sta 3 15 | : 16 | lda $4016,x 17 | lsr a 18 | rol 1 19 | bcc :- 20 | lda cur_mbuttons,x 21 | eor #$FF 22 | and 1 23 | sta new_mbuttons,x 24 | lda 1 25 | sta cur_mbuttons,x 26 | : 27 | lda $4016,x 28 | lsr a 29 | rol 2 30 | bcc :- 31 | : 32 | lda $4016,x 33 | lsr a 34 | rol 3 35 | bcc :- 36 | rts 37 | .endproc 38 | 39 | .proc mouse_change_sensitivity 40 | lda #1 41 | sta $4016 42 | lda $4016,x 43 | lda #0 44 | sta $4016 45 | rts 46 | .endproc 47 | 48 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/music.s: -------------------------------------------------------------------------------- 1 | ; music.s 2 | ; part of sound engine for LJ65, Concentration Room, and Thwaite 3 | 4 | ;;; Copyright (C) 2009-2011 Damian Yerrick 5 | ; 6 | ; This program is free software; you can redistribute it and/or 7 | ; modify it under the terms of the GNU General Public License 8 | ; as published by the Free Software Foundation; either version 3 9 | ; of the License, or (at your option) any later version. 10 | ; 11 | ; This program is distributed in the hope that it will be useful, 12 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ; GNU General Public License for more details. 15 | ; 16 | ; You should have received a copy of the GNU General Public License 17 | ; along with this program; if not, write to 18 | ; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | ; Boston, MA 02111-1307, USA. 20 | ; 21 | ; Visit http://www.pineight.com/ for more information. 22 | 23 | .importzp psg_sfx_state 24 | .import soundBSS 25 | .import start_sound 26 | .export music_playing 27 | .export init_music, stop_music, update_music, update_music_ch 28 | .include "src/musicseq.h" 29 | 30 | .ifndef SOUND_NTSC_ONLY 31 | SOUND_NTSC_ONLY = 0 32 | .endif 33 | .if (!SOUND_NTSC_ONLY) 34 | .importzp tvSystem 35 | .endif 36 | 37 | musicPatternPos = psg_sfx_state + 2 38 | conductorPos = psg_sfx_state + 16 39 | noteEnvVol = soundBSS + 0 40 | notePitch = soundBSS + 1 41 | noteRowsLeft = soundBSS + 2 42 | ; 3 is in sound.s 43 | musicPattern = soundBSS + 16 44 | patternTranspose = soundBSS + 17 45 | noteInstrument = soundBSS + 18 46 | ; 19 is in sound.s 47 | tempoCounterLo = soundBSS + 48 48 | tempoCounterHi = soundBSS + 49 49 | music_tempoLo = soundBSS + 50 50 | music_tempoHi = soundBSS + 51 51 | conductorSegno = soundBSS + 52 52 | 53 | conductorWaitRows = soundBSS + 62 54 | music_playing = soundBSS + 63 55 | 56 | FRAMES_PER_MINUTE_PAL = 3000 57 | FRAMES_PER_MINUTE_NTSC = 3606 58 | 59 | 60 | .segment "RODATA" 61 | 62 | fpmLo: 63 | .byt FRAMES_PER_MINUTE_NTSC, >FRAMES_PER_MINUTE_PAL 66 | 67 | silentPattern: 68 | .byt 26*8+7, 255 69 | 70 | durations: 71 | .byt 1, 2, 3, 4, 6, 8, 12, 16 72 | 73 | .segment "CODE" 74 | .proc init_music 75 | asl a 76 | tax 77 | lda songTable,x 78 | sta conductorPos 79 | sta conductorSegno 80 | lda songTable+1,x 81 | sta conductorPos+1 82 | sta conductorSegno+1 83 | ldx #12 84 | stx music_playing 85 | channelLoop: 86 | lda #$FF 87 | sta musicPattern,x 88 | lda #silentPattern 91 | sta musicPatternPos+1,x 92 | lda #0 93 | sta patternTranspose,x 94 | sta noteInstrument,x 95 | sta noteEnvVol,x 96 | sta noteRowsLeft,x 97 | dex 98 | dex 99 | dex 100 | dex 101 | bpl channelLoop 102 | lda #0 103 | sta conductorWaitRows 104 | lda #$FF 105 | sta tempoCounterLo 106 | sta tempoCounterHi 107 | lda #<300 108 | sta music_tempoLo 109 | lda #>300 110 | sta music_tempoHi 111 | rts 112 | .endproc 113 | 114 | .proc stop_music 115 | lda #0 116 | sta music_playing 117 | rts 118 | .endproc 119 | 120 | .proc update_music 121 | lda music_playing 122 | beq music_not_playing 123 | lda music_tempoLo 124 | clc 125 | adc tempoCounterLo 126 | sta tempoCounterLo 127 | lda music_tempoHi 128 | adc tempoCounterHi 129 | sta tempoCounterHi 130 | bcs new_tick 131 | music_not_playing: 132 | rts 133 | new_tick: 134 | 135 | .if ::SOUND_NTSC_ONLY 136 | ldy #0 137 | .else 138 | ldy tvSystem 139 | beq is_ntsc_1 140 | ldy #1 141 | is_ntsc_1: 142 | .endif 143 | 144 | ; Subtract tempo 145 | lda tempoCounterLo 146 | sbc fpmLo,y 147 | sta tempoCounterLo 148 | lda tempoCounterHi 149 | sbc fpmHi,y 150 | sta tempoCounterHi 151 | 152 | ;jmp skipConductor 153 | 154 | lda conductorWaitRows 155 | beq doConductor 156 | dec conductorWaitRows 157 | jmp skipConductor 158 | 159 | doConductor: 160 | 161 | ldy #0 162 | lda (conductorPos),y 163 | inc conductorPos 164 | bne :+ 165 | inc conductorPos+1 166 | : 167 | sta 0 168 | cmp #CON_SETTEMPO 169 | bcc @notTempoChange 170 | and #%00000011 171 | sta music_tempoHi 172 | 173 | lda (conductorPos),y 174 | inc conductorPos 175 | bne :+ 176 | inc conductorPos+1 177 | : 178 | sta music_tempoLo 179 | jmp doConductor 180 | @notTempoChange: 181 | cmp #CON_WAITROWS 182 | bcc conductorPlayPattern 183 | beq conductorDoWaitRows 184 | 185 | cmp #CON_FINE 186 | bne @notFine 187 | lda #0 188 | sta music_playing 189 | sta music_tempoHi 190 | sta music_tempoLo 191 | rts 192 | @notFine: 193 | 194 | cmp #CON_SEGNO 195 | bne @notSegno 196 | lda conductorPos 197 | sta conductorSegno 198 | lda conductorPos+1 199 | sta conductorSegno+1 200 | jmp doConductor 201 | @notSegno: 202 | 203 | cmp #CON_DALSEGNO 204 | bne @notDalSegno 205 | lda conductorSegno 206 | sta conductorPos 207 | lda conductorSegno+1 208 | sta conductorPos+1 209 | jmp doConductor 210 | @notDalSegno: 211 | 212 | jmp skipConductor 213 | 214 | conductorPlayPattern: 215 | and #$03 216 | asl a 217 | asl a 218 | tax 219 | lda #0 220 | sta noteRowsLeft,x 221 | lda (conductorPos),y 222 | sta musicPattern,x 223 | iny 224 | lda (conductorPos),y 225 | sta patternTranspose,x 226 | iny 227 | lda (conductorPos),y 228 | sta noteInstrument,x 229 | tya 230 | sec 231 | adc conductorPos 232 | sta conductorPos 233 | bcc :+ 234 | inc conductorPos+1 235 | : 236 | jsr startPattern 237 | jmp doConductor 238 | 239 | ; this should be last so it can fall into skipConductor 240 | conductorDoWaitRows: 241 | 242 | lda (conductorPos),y 243 | inc conductorPos 244 | bne :+ 245 | inc conductorPos+1 246 | : 247 | sta conductorWaitRows 248 | 249 | skipConductor: 250 | 251 | ldx #12 252 | channelLoop: 253 | lda noteRowsLeft,x 254 | bne skipNote 255 | anotherPatternByte: 256 | lda (musicPatternPos,x) 257 | cmp #255 258 | bne notStartPatternOver 259 | jsr startPattern 260 | lda (musicPatternPos,x) 261 | notStartPatternOver: 262 | 263 | inc musicPatternPos,x 264 | bne patternNotNewPage 265 | inc musicPatternPos+1,x 266 | patternNotNewPage: 267 | 268 | cmp #$D8 269 | bcc notInstChange 270 | lda (musicPatternPos,x) 271 | sta noteInstrument,x 272 | nextPatternByte: 273 | inc musicPatternPos,x 274 | bne anotherPatternByte 275 | inc musicPatternPos+1,x 276 | jmp anotherPatternByte 277 | notInstChange: 278 | 279 | ; set the note's duration 280 | pha 281 | and #$07 282 | tay 283 | lda durations,y 284 | sta noteRowsLeft,x 285 | pla 286 | lsr a 287 | lsr a 288 | lsr a 289 | cmp #25 290 | bcc isTransposedNote 291 | beq notKeyOff 292 | lda #0 293 | sta noteEnvVol,x 294 | notKeyOff: 295 | jmp skipNote 296 | 297 | isTransposedNote: 298 | cpx #12 299 | beq isDrumNote 300 | adc patternTranspose,x 301 | sta notePitch,x 302 | lda noteInstrument,x 303 | asl a 304 | asl a 305 | tay 306 | lda instrumentTable,y 307 | asl a 308 | asl a 309 | asl a 310 | asl a 311 | ora #$0C 312 | sta noteEnvVol,x 313 | 314 | skipNote: 315 | dec noteRowsLeft,x 316 | dex 317 | dex 318 | dex 319 | dex 320 | bpl channelLoop 321 | 322 | rts 323 | 324 | isDrumNote: 325 | stx 5 326 | asl a 327 | pha 328 | tax 329 | lda drumSFX,x 330 | jsr start_sound 331 | pla 332 | tax 333 | lda drumSFX+1,x 334 | bmi noSecondDrum 335 | jsr start_sound 336 | noSecondDrum: 337 | ldx 5 338 | jmp skipNote 339 | 340 | startPattern: 341 | lda musicPattern,x 342 | asl a 343 | bcc @notSilentPattern 344 | lda #silentPattern 347 | sta musicPatternPos+1,x 348 | rts 349 | @notSilentPattern: 350 | tay 351 | lda musicPatternTable,y 352 | sta musicPatternPos,x 353 | lda musicPatternTable+1,y 354 | sta musicPatternPos+1,x 355 | rts 356 | .endproc 357 | 358 | .proc update_music_ch 359 | ch_number = 0 360 | out_volume = 2 361 | out_pitch = 3 362 | 363 | lda music_playing 364 | beq silenced 365 | lda noteEnvVol,x 366 | lsr a 367 | lsr a 368 | lsr a 369 | lsr a 370 | bne notSilenced 371 | silenced: 372 | lda #0 373 | sta 2 374 | rts 375 | notSilenced: 376 | sta 2 377 | lda noteInstrument,x 378 | asl a 379 | asl a 380 | tay 381 | lda 2 382 | eor instrumentTable,y 383 | and #$0F 384 | eor instrumentTable,y 385 | sta 2 386 | lda noteEnvVol,x 387 | sec 388 | sbc instrumentTable+1,y 389 | bcc silenced 390 | sta noteEnvVol,x 391 | lda notePitch,x 392 | sta 3 393 | 394 | ; bit 7 of attribute 2: cut note when half a row remains 395 | lda instrumentTable+2,y 396 | bpl notCutNote 397 | lda noteRowsLeft,x 398 | bne notCutNote 399 | 400 | clc 401 | lda tempoCounterLo 402 | adc #<(FRAMES_PER_MINUTE_NTSC/2) 403 | lda tempoCounterHi 404 | adc #>(FRAMES_PER_MINUTE_NTSC/2) 405 | bcc notCutNote 406 | lda #0 407 | sta noteEnvVol,x 408 | 409 | notCutNote: 410 | rts 411 | .endproc 412 | 413 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/musicseq.h: -------------------------------------------------------------------------------- 1 | ; Copyright 2010 Damian Yerrick 2 | ; 3 | ; Copying and distribution of this file, with or without 4 | ; modification, are permitted in any medium without royalty provided 5 | ; the copyright notice and this notice are preserved in all source 6 | ; code copies. This file is offered as-is, without any warranty. 7 | 8 | .global drumSFX, psg_sound_table 9 | .global musicPatternTable, instrumentTable, songTable 10 | 11 | N_C = 0*8 12 | N_CS = 1*8 13 | N_D = 2*8 14 | N_DS = 3*8 15 | N_E = 4*8 16 | N_F = 5*8 17 | N_FS = 6*8 18 | N_G = 7*8 19 | N_GS = 8*8 20 | N_A = 9*8 21 | N_AS = 10*8 22 | N_B = 11*8 23 | N_DB = N_CS 24 | N_EB = N_DS 25 | N_GB = N_FS 26 | N_AB = N_GS 27 | N_BB = N_AS 28 | N_CH = N_C + 12*8 29 | N_CSH = N_CS + 12*8 30 | N_DBH = N_DB + 12*8 31 | N_DH = N_D + 12*8 32 | N_DSH = N_DS + 12*8 33 | N_EBH = N_EB + 12*8 34 | N_EH = N_E + 12*8 35 | N_FH = N_F + 12*8 36 | N_FSH = N_FS + 12*8 37 | N_GBH = N_GB + 12*8 38 | N_GH = N_G + 12*8 39 | N_GSH = N_GS + 12*8 40 | N_ABH = N_AB + 12*8 41 | N_AH = N_A + 12*8 42 | N_ASH = N_AS + 12*8 43 | N_BBH = N_BB + 12*8 44 | N_BH = N_B + 12*8 45 | N_CHH = N_CH + 12*8 46 | N_TIE = 25*8 47 | REST = 26*8 48 | INSTRUMENT = $D8 49 | 50 | ; The default duration is one row (a sixteenth note in the tracker). 51 | ; OR the pitch with one of these constants. 52 | D_8 = 1 53 | D_D8 = 2 54 | D_4 = 3 55 | D_D4 = 4 56 | D_2 = 5 57 | D_D2 = 6 58 | D_1 = 7 59 | 60 | CON_PLAYPAT = $00 ; next: pattern, transpose (if not drums), instrument (if not drums) 61 | CON_LOOPPAT = $10 ; as CON_PLAYPAT 62 | CON_WAITROWS = $20 ; next: number of rows to wait minus 1 63 | CON_FINE = $21 ; stop music now 64 | CON_SEGNO = $22 ; set loop point 65 | CON_DALSEGNO = $23 ; jump to loop point. if no point was set, jump to start of song. 66 | CON_SETTEMPO = $30 ; low bits: bits 8-9 of tempo in rows/min; next: bits 0-7 of tempo 67 | 68 | ; Conductor macros 69 | .macro playPatSq1 patid, transpose, instrument 70 | .byt CON_PLAYPAT|0, patid, transpose, instrument 71 | .endmacro 72 | .macro playPatSq2 patid, transpose, instrument 73 | .byt CON_PLAYPAT|1, patid, transpose, instrument 74 | .endmacro 75 | .macro playPatTri patid, transpose, instrument 76 | .byt CON_PLAYPAT|2, patid, transpose, instrument 77 | .endmacro 78 | .macro playPatNoise patid, transpose, instrument 79 | .byt CON_PLAYPAT|3, patid, transpose, instrument 80 | .endmacro 81 | .macro stopPatSq1 82 | .byt CON_PLAYPAT|0, 255, 0, 0 83 | .endmacro 84 | .macro stopPatSq2 85 | .byt CON_PLAYPAT|1, 255, 0, 0 86 | .endmacro 87 | .macro stopPatTri 88 | .byt CON_PLAYPAT|2, 255, 0, 0 89 | .endmacro 90 | .macro stopPatNoise 91 | .byt CON_PLAYPAT|3, 255, 0, 0 92 | .endmacro 93 | .macro waitRows n 94 | .byt CON_WAITROWS, n-1 95 | .endmacro 96 | .macro fine 97 | .byt CON_FINE 98 | .endmacro 99 | .macro segno 100 | .byt CON_SEGNO 101 | .endmacro 102 | .macro dalSegno 103 | .byt CON_DALSEGNO 104 | .endmacro 105 | .macro setTempo rowsPerMin 106 | .byt CON_SETTEMPO|>rowsPerMin, practice_txt 29 | sta 1 30 | jsr display_textfile 31 | loop: 32 | ldx #0 33 | jsr moveCrosshairPlayerX 34 | jsr drawCrosshairPlayerX 35 | jsr drawPracticeMenuSprites 36 | jsr clearRestOfOAM 37 | jsr buildBGUpdate 38 | jsr update_sound 39 | 40 | lda nmis 41 | : 42 | cmp nmis 43 | beq :- 44 | ; it's vblank! 45 | jsr blitBGUpdate 46 | lda #0 47 | sta PPUSCROLL 48 | sta PPUSCROLL 49 | sta OAMADDR 50 | lda #>OAM 51 | sta OAM_DMA 52 | lda #VBLANK_NMI|BG_0000|OBJ_1000 53 | sta PPUCTRL 54 | lda #BG_ON|OBJ_ON 55 | sta PPUMASK 56 | 57 | jsr read_pads 58 | jsr mouse_to_vel 59 | lda new_keys 60 | and #KEY_A|KEY_B 61 | beq noClick 62 | jsr practiceDoClick 63 | 64 | noClick: 65 | 66 | lda gameState 67 | cmp #STATE_NEW_LEVEL 68 | bne noReloadLevel 69 | jsr practiceSetSide 70 | jsr doStateNewLevel 71 | lda #BG_DIRTY_STATUS|BG_DIRTY_HOUSES|BG_DIRTY_PRACTICE_METER 72 | ora bgDirty 73 | sta bgDirty 74 | noReloadLevel: 75 | 76 | lda new_keys 77 | and #KEY_START 78 | beq notStart 79 | lda #1 80 | sta isPractice 81 | notStart: 82 | 83 | lda isPractice 84 | cmp #2 85 | bcc done 86 | jmp loop 87 | done: 88 | 89 | ; clear the tip so that doStateLevelReward won't 90 | ; kick us out prematurely 91 | lda #0 92 | sta curTip 93 | lda #STATE_NEW_LEVEL 94 | sta gameState 95 | rts 96 | .endproc 97 | 98 | .proc practiceDoClick 99 | lda crosshairYHi 100 | cmp #$24 101 | bcc noClick1 102 | cmp #$34 103 | bcs noDayClick 104 | 105 | ; handle day click 106 | lda crosshairXHi 107 | sec 108 | sbc #60 109 | lsr a 110 | lsr a 111 | lsr a 112 | lsr a 113 | cmp #7 114 | bcs noClick1 115 | sta practiceDay 116 | sta gameDay 117 | lda #STATE_NEW_LEVEL 118 | sta gameState 119 | noClick1: 120 | rts 121 | noDayClick: 122 | 123 | cmp #$44 124 | bcs noHourClick 125 | 126 | ; handle hour click 127 | lda crosshairXHi 128 | sec 129 | sbc #60 130 | lsr a 131 | lsr a 132 | lsr a 133 | lsr a 134 | cmp #5 135 | bcs noClick1 136 | sta practiceHour 137 | sta gameHour 138 | lda #STATE_NEW_LEVEL 139 | sta gameState 140 | rts 141 | noHourClick: 142 | 143 | cmp #$5C 144 | bcc noClick1 145 | cmp #$7C 146 | bcs noSideClick 147 | sbc #$5C 148 | and #$10 149 | beq :+ 150 | lda #1 151 | : 152 | sta 0 153 | lda crosshairXHi 154 | sec 155 | sbc #$38 156 | bmi noSideClick 157 | cmp #$30 158 | lda 0 159 | rol a 160 | sta practiceSide 161 | lda #STATE_NEW_LEVEL 162 | sta gameState 163 | rts 164 | 165 | noSideClick: 166 | cmp #$8C 167 | bcs noCancelPlayClick 168 | lda crosshairXHi 169 | cmp #176 170 | bcs noCancelPlayClick 171 | cmp #92 172 | lda #0 173 | rol a 174 | sta isPractice 175 | 176 | noCancelPlayClick: 177 | rts 178 | 179 | .endproc 180 | 181 | .proc practiceSetSide 182 | firstHouseOK = 0 183 | lastHouseOK = 1 184 | 185 | ; side 0: through 6; otherwise through 12 186 | lda #12 187 | ldy practiceSide ; keep side in y 188 | bne :+ 189 | lda #6 190 | : 191 | sta lastHouseOK 192 | 193 | ; side 1: start at 6; otherwise start at 0 194 | lda #0 195 | cpy #1 196 | bne :+ 197 | lda #6 198 | : 199 | sta firstHouseOK 200 | 201 | ; side 3: 2 players; otherwise 1 player 202 | lda #1 203 | cpy #3 204 | bne :+ 205 | lda #2 206 | : 207 | sta numPlayers 208 | 209 | ldy #11 210 | sethouseloop: 211 | lda #0 212 | cpy firstHouseOK 213 | bcc houseNotOK 214 | cpy lastHouseOK 215 | bcs houseNotOK 216 | lda #1 217 | houseNotOK: 218 | sta housesStanding,y 219 | dey 220 | bpl sethouseloop 221 | rts 222 | 223 | .endproc 224 | 225 | 226 | .proc drawPracticeMenuSprites 227 | ldx oamIndex 228 | 229 | lda #39 230 | sta OAM,x 231 | lda #55 232 | sta OAM+4,x 233 | lda practiceSide 234 | and #$02 235 | asl a 236 | asl a 237 | asl a 238 | adc #95 239 | sta OAM+8,x 240 | 241 | ; set x coords 242 | lda gameDay 243 | asl a 244 | asl a 245 | asl a 246 | asl a 247 | adc #60 248 | sta OAM+3,x 249 | lda gameHour 250 | asl a 251 | asl a 252 | asl a 253 | asl a 254 | adc #60 255 | sta OAM+7,x 256 | lda practiceSide 257 | and #$01 258 | beq :+ 259 | lda #48 260 | : 261 | clc 262 | adc #60 263 | sta OAM+11,x 264 | 265 | lda #SELECTED_ARROW_TILE 266 | sta OAM+1,x 267 | sta OAM+5,x 268 | sta OAM+9,x 269 | lda #%00100001 ; behind; color set 1 270 | sta OAM+2,x 271 | sta OAM+6,x 272 | sta OAM+10,x 273 | 274 | txa 275 | clc 276 | adc #12 277 | tax 278 | 279 | ; At this point, draw the possible salvos 280 | 281 | salvoID = 0 282 | salvoY = 1 283 | salvoX = 2 284 | 285 | lda #0 286 | sta salvoID 287 | lda #39 288 | sta salvoY 289 | salvoLoop: 290 | lda #192 291 | sta salvoX 292 | ldy salvoID 293 | lda levelSalvoSizes,y 294 | cmp #8 295 | beq isBalloon 296 | bcs isMIRV 297 | 298 | ; Regular missiles; draw each 299 | tay 300 | salvoElLoop: 301 | lda #$0A 302 | mirvFinish: 303 | sta OAM+1,x 304 | lda salvoY 305 | sta OAM+0,x 306 | lda #$01 307 | sta OAM+2,x 308 | lda salvoX 309 | sta OAM+3,x 310 | clc 311 | adc #6 312 | sta salvoX 313 | inx 314 | inx 315 | inx 316 | inx 317 | dey 318 | bne salvoElLoop 319 | lda #12 320 | bne salvoAddToY 321 | 322 | isMIRV: 323 | ldy #1 324 | lda #$1A 325 | bne mirvFinish 326 | 327 | isBalloon: 328 | lda salvoY 329 | sta OAM+0,x 330 | clc 331 | adc #8 332 | sta OAM+4,x 333 | lda #$06 334 | sta OAM+1,x 335 | lda #$16 336 | sta OAM+5,x 337 | lda #$01 338 | sta OAM+2,x 339 | lsr a 340 | sta OAM+6,x 341 | lda salvoX 342 | sta OAM+3,x 343 | sta OAM+7,x 344 | txa 345 | clc 346 | adc #8 347 | tax 348 | lda #20 349 | 350 | salvoAddToY: 351 | clc 352 | adc salvoY 353 | sta salvoY 354 | inc salvoID 355 | lda salvoID 356 | cmp #4 357 | bcc salvoLoop 358 | stx oamIndex 359 | rts 360 | .endproc 361 | 362 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/practice.txt: -------------------------------------------------------------------------------- 1 | thwaite: practice 2 | 3 | Day: S M T W R F S 4 | 5 | Hour: 1 2 3 4 5 6 | 7 | Ammo Speed Delay 8 | 9 | 10 | Side: Left Right 11 | 12 | Both 2 Players 13 | 14 | (Cancel) (Play) 15 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/ram.h: -------------------------------------------------------------------------------- 1 | ; Copyright 2011 Damian Yerrick 2 | ; 3 | ; Copying and distribution of this file, with or without 4 | ; modification, are permitted in any medium without royalty provided 5 | ; the copyright notice and this notice are preserved in all source 6 | ; code copies. This file is offered as-is, without any warranty. 7 | 8 | xferBuf = $0100 9 | OAM = $0200 10 | 11 | ; Turn this on to replace the upper left tile of a threatened 12 | ; building with a ! 13 | SHOW_THREATENED = 0 14 | 15 | ; Turn this on to annoy Nova 16 | CHECK_ILLEGAL_MISSILES = 1 17 | ILLEGAL_MISSILES_TIP = 13 18 | 19 | ; main fields 20 | STATE_INACTIVE = 0 21 | STATE_NEW_LEVEL = 1 22 | STATE_ACTIVE = 2 23 | STATE_LEVEL_REWARD = 3 24 | STATE_REBUILDING_SILO = 4 25 | STATE_CUTSCENE = 5 26 | STATE_REBUILDING_HOUSE = 6 27 | STATE_GAMEOVER = 7 28 | .globalzp nmis, oamIndex, numPlayers, tvSystem, mouseEnabled 29 | .globalzp debugHex1, debugHex2 30 | .globalzp gameState 31 | ; main methods 32 | .global clearRestOfOAM, doStateNewLevel 33 | 34 | ; pads fields 35 | .globalzp cur_keys, new_keys, das_timer, das_keys 36 | ; pads methods 37 | .global read_pads, autorepeat 38 | 39 | ; mouse fields 40 | .globalzp cur_mbuttons, new_mbuttons 41 | ; mouse methods 42 | .global read_mouse, mouse_change_sensitivity 43 | 44 | ; title constants 45 | SELECTED_ARROW_TILE = $02 46 | ; title methods 47 | .global titleScreen, display_textfile 48 | 49 | ; unpkb methods 50 | .global PKB_unpackblk 51 | 52 | ; random fields 53 | .globalzp rand0, rand1, rand2, rand3 54 | ; random methods 55 | .global random, initRandomTarget, chooseRandomTarget 56 | .global findRandomDestroyedHouse 57 | 58 | ; bg constants 59 | NUM_BUILDINGS = 12 60 | BG_DIRTY_HOUSES = $01 61 | BG_DIRTY_STATUS = $02 62 | BG_DIRTY_TIP = $04 63 | BG_DIRTY_PRACTICE_METER = $08 64 | BUILDING_DESTROYED = 0 65 | BUILDING_OK = 1 66 | BUILDING_THREATENED = 2 67 | ; bg fields 68 | .global housesStanding, houseX, score1s, score100s 69 | .global gameDay, gameHour, gameMinute, gameSecond, gameTenthSecond, gameSubTenth 70 | .global bgDirty, curTip, tipTimeLeft 71 | .global buildingsDestroyedThisLevel, firstDestroyedHouse 72 | .global main_palette 73 | ; bg methods 74 | .global setupGameBG, blitBGUpdate, buildBGUpdate, buildLevelRewardBar 75 | .global buildHouseRebuiltBar 76 | .global newGame, addScore, incGameClock, countHousesLeft 77 | .global puthex 78 | 79 | ; missiles constants 80 | ; missiles 0 and 2 are player 1's 81 | ; missiles 1 and 3 are player 2's 82 | ; missiles 4 through NUM_MISSILES - 1 are the enemy missiles 83 | NUM_MISSILES = 20 84 | MISSILE_SPAWN_Y = 8 85 | BALLOON_SPAWN_Y = 40 ; in range 0-63 plus this 86 | SILO_Y = 192 ; player missiles launch from here 87 | BUILDING_HIT_Y = 200 88 | ; missiles fields 89 | .global crosshairXLo, crosshairXHi, crosshairDXLo, crosshairDXHi 90 | .global crosshairYLo, crosshairYHi, crosshairDYLo, crosshairDYHi 91 | .global missileXLo, missileXHi, missileYLo, missileYHi 92 | .global missileDYHi, missileDYLo 93 | .global missileType, missileTarget, missileTime, missileAngle 94 | ; and about 400 bytes of free space to use when missiles 95 | ; are not active 96 | .global missilesOverlay 97 | 98 | ; missiles methods 99 | .global clearAllMissiles, updateMissiles 100 | .global moveCrosshairPlayerX, drawCrosshairPlayerX, mouse_to_vel 101 | .global makeMissile, makeBalloon, firePlayerMissile 102 | .global siloMissilesLeft 103 | 104 | ; explosion constants 105 | NUM_EXPLOSIONS = 8 106 | ; explosion methods 107 | .global clearExplosions, makeExplosion, updateAllExplosions 108 | 109 | ; smoke methods 110 | .global makeSmoke, clearAllSmoke, updateSmoke 111 | 112 | ; math fields 113 | .global missileSine, missileCosine 114 | ; math methods 115 | .global getAngle, getSlope1, mul8, measureFromSilo 116 | 117 | ; bcd methods 118 | .global bcd8bit 119 | 120 | ; levels constants 121 | .globalzp NUM_MADE_DAYS, SKIP_TO_DAY 122 | ; levels fields 123 | .global levelMissileSpeed, enemyMissilesLeft, levelReleasePeriod 124 | .global levelMissileTypes, levelSalvoSizes 125 | ; levels methods 126 | .global loadLevel 127 | 128 | ; scurry constants 129 | NUM_VILLAGERS = NUM_BUILDINGS 130 | ; scurry methods 131 | .global initVillagers, updateVillagers 132 | .global testMissileThreats 133 | .global villagersGoHome, warpVillagersToTargets 134 | 135 | ; paldetect constants 136 | TV_SYSTEM_NTSC = 0 137 | TV_SYSTEM_PAL = 1 138 | TV_SYSTEM_DENDY = 2 139 | TV_SYSTEM_OTHER = 3 140 | ; paldetect methods 141 | .global getTVSystem 142 | 143 | ; sound constants 144 | MUSIC_CLEARED_LEVEL = 5 145 | MUSIC_1600 = 6 146 | 147 | SFX_SPLIT = 0 148 | SFX_ALERT_A = 4 149 | SFX_ALERT_B = 5 150 | SFX_LAUNCH = 6 151 | SFX_BOOM_SQUARE = 7 152 | SFX_BOOM_NOISE = 8 153 | ; sound methods 154 | .global init_sound, start_sound, update_sound, init_music, stop_music 155 | 156 | ; cutscene fields 157 | .global character_name_offset, character_name0 158 | .global actor_paranoid 159 | ; cutscene methods 160 | .global load_cutscene, cut_choose_villagers 161 | 162 | ; kinematics args 163 | abl_vel = 0 164 | abl_maxVel = 2 165 | abl_brakeRate = 4 166 | abl_accelRate = 5 167 | abl_keys = 6 168 | ; kinematics methods 169 | .global accelBrakeLimit 170 | 171 | ; practice fields 172 | .globalzp isPractice, practiceSide 173 | ; practice methods 174 | .global practice_menu 175 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/random.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; Target selection for Thwaite 3 | ; Copyright (C) 2011 Damian Yerrick 4 | ; 5 | ; This program is free software: you can redistribute it and/or modify 6 | ; it under the terms of the GNU General Public License as published by 7 | ; the Free Software Foundation, either version 3 of the License, or 8 | ; (at your option) any later version. 9 | ; 10 | ; This program is distributed in the hope that it will be useful, 11 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ; GNU General Public License for more details. 14 | ; 15 | ; You should have received a copy of the GNU General Public License 16 | ; along with this program. If not, see . 17 | ; 18 | .include "src/ram.h" 19 | ;.include "src/nes.h" ; for debugging high level shit 20 | .segment "ZEROPAGE" 21 | rand0: .res 1 22 | rand1: .res 1 23 | rand2: .res 1 24 | rand3: .res 1 25 | lastTarget: .res 1 26 | lruTargets: .res NUM_BUILDINGS 27 | 28 | .segment "CODE" 29 | ;; random 30 | ; Uses the crc32 polynomial to generate Y 31 | ; pseudorandom bits as the low_order bits of rand3. 32 | ; Average 48 cycles per bit. 33 | ; 34 | .proc random 35 | asl rand3 36 | rol rand2 37 | rol rand1 38 | rol rand0 39 | bcc @no_xor 40 | lda rand0 41 | eor #$04 42 | sta rand0 43 | lda rand1 44 | eor #$c1 45 | sta rand1 46 | lda rand2 47 | eor #$1d 48 | sta rand2 49 | lda rand3 50 | eor #$b7 51 | sta rand3 52 | @no_xor: 53 | dey 54 | bne random 55 | rts 56 | .endproc 57 | 58 | .proc chooseRandomTarget 59 | ldy #3 60 | jsr random 61 | lda #$07 62 | and rand3 63 | clc 64 | adc #NUM_BUILDINGS - 8 65 | tay 66 | lda lruTargets,y 67 | pha 68 | loop: 69 | lda lruTargets-1,y 70 | sta lruTargets,y 71 | dey 72 | bne loop 73 | pla 74 | sta lruTargets 75 | rts 76 | .endproc 77 | 78 | .proc initRandomTarget 79 | ldy #NUM_BUILDINGS - 1 80 | : 81 | lda initLRUData,y 82 | sta lruTargets,y 83 | dey 84 | bpl :- 85 | rts 86 | .endproc 87 | 88 | .proc findRandomDestroyedHouse 89 | ldx #NUM_BUILDINGS - 2 90 | stx 0 91 | ldy #3 92 | jsr random 93 | lda #$07 94 | and rand3 95 | clc 96 | adc #2 97 | tax 98 | searchloop: 99 | lda initLRUData,x 100 | tay 101 | lda housesStanding,y 102 | beq found 103 | inx 104 | cpx #NUM_BUILDINGS 105 | bcc :+ 106 | ldx #2 107 | : 108 | dec 0 109 | bne searchloop 110 | ldy #$FF 111 | found: 112 | rts 113 | .endproc 114 | 115 | .segment "RODATA" 116 | ; Initial LRU data for chooseRandomTarget. Putting 2, 9 at the 117 | ; front helps new players by not immediately blowing up the silos. 118 | initLRUData: 119 | .byt 2, 9, 11, 0, 1, 10, 8, 3, 4, 7, 6, 5 120 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/smoke.s: -------------------------------------------------------------------------------- 1 | ; smoke.s 2 | ; Dissipating smoke drawing functions for Thwaite 3 | 4 | ;;; Copyright (C) 2011 Damian Yerrick 5 | ; 6 | ; This program is free software; you can redistribute it and/or 7 | ; modify it under the terms of the GNU General Public License 8 | ; as published by the Free Software Foundation; either version 3 9 | ; of the License, or (at your option) any later version. 10 | ; 11 | ; This program is distributed in the hope that it will be useful, 12 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ; GNU General Public License for more details. 15 | ; 16 | ; You should have received a copy of the GNU General Public License 17 | ; along with this program; if not, write to 18 | ; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | ; Boston, MA 02111-1307, USA. 20 | ; 21 | ; Visit http://www.pineight.com/ for more information. 22 | 23 | .include "src/nes.h" 24 | .include "src/ram.h" 25 | 26 | NUM_SMOKE = 48 27 | FIRST_SMOKE_TILE = $10 28 | SMOKE_PALETTE = $23 29 | .segment "BSS" 30 | smokeX: .res NUM_SMOKE 31 | smokeY: .res NUM_SMOKE 32 | smokeSpeed: .res NUM_SMOKE 33 | smokeTime: .res NUM_SMOKE 34 | 35 | .segment "CODE" 36 | ;; 37 | ; Makes a smoke particle at (x, y) with dissipation speed a. 38 | .proc makeSmoke 39 | xpos = 0 40 | ypos = 1 41 | dissipSpeed = 2 42 | sta dissipSpeed 43 | txa 44 | clc 45 | adc #252 46 | bcc cancelSmoke 47 | sta xpos 48 | tya 49 | adc #250 50 | sta ypos 51 | bcc cancelSmoke 52 | 53 | ; Look for the best slot for this explosion. 54 | ; The "best" slot is the one whose explosion has been on screen 55 | ; the longest. 56 | ; y holds the index of the best slot so far 57 | ; x holds the index of the slot currently being considered 58 | ldx #NUM_SMOKE - 2 59 | ldy #NUM_SMOKE - 1 60 | 61 | findFreeSlot: 62 | ; if the time in x is less than the time in y, y is a better slot 63 | lda smokeTime,x 64 | cmp smokeTime,y 65 | bcc notBetterSlot 66 | ; otherwise x is a better slot, so set y to x 67 | txa 68 | tay 69 | notBetterSlot: 70 | dex 71 | bpl findFreeSlot 72 | lda dissipSpeed 73 | sta smokeSpeed,y 74 | lda #0 75 | sta smokeTime,y 76 | lda xpos 77 | sta smokeX,y 78 | lda ypos 79 | sta smokeY,y 80 | cancelSmoke: 81 | rts 82 | .endproc 83 | 84 | .proc updateSmoke 85 | lda nmis 86 | and #$01 87 | clc 88 | adc #NUM_SMOKE - 2 89 | tax 90 | ldy oamIndex 91 | smokeloop: 92 | ; set tile number if the particle is still active 93 | lda smokeTime,x 94 | cmp #5*32 95 | bcs smokecontinue 96 | lsr a 97 | lsr a 98 | lsr a 99 | lsr a 100 | lsr a 101 | clc 102 | adc #FIRST_SMOKE_TILE 103 | sta OAM+1,y 104 | 105 | ; set (pretransformed) coordinates 106 | lda smokeY,x 107 | sta OAM,y 108 | lda smokeX,x 109 | sta OAM+3,y 110 | 111 | ; put bits 2-1 of vblank count into smoke's flip bits 112 | lda nmis 113 | ror a 114 | ror a 115 | ror a 116 | ror a 117 | and #$C0 118 | ora #SMOKE_PALETTE 119 | sta OAM+2,y 120 | lda smokeTime,x 121 | clc 122 | adc smokeSpeed,x 123 | sta smokeTime,x 124 | iny 125 | iny 126 | iny 127 | iny 128 | beq bail 129 | smokecontinue: 130 | dex 131 | dex 132 | bpl smokeloop 133 | bail: 134 | sty oamIndex 135 | rts 136 | .endproc 137 | 138 | .proc clearAllSmoke 139 | ldy #NUM_SMOKE - 1 140 | lda #$FF 141 | loop: 142 | sta smokeTime,y 143 | dey 144 | bpl loop 145 | rts 146 | .endproc 147 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/sound.s: -------------------------------------------------------------------------------- 1 | ; sound.s 2 | ; part of sound engine for LJ65, Concentration Room, and Thwaite 3 | 4 | ;;; Copyright (C) 2009-2011 Damian Yerrick 5 | ; 6 | ; This program is free software; you can redistribute it and/or 7 | ; modify it under the terms of the GNU General Public License 8 | ; as published by the Free Software Foundation; either version 3 9 | ; of the License, or (at your option) any later version. 10 | ; 11 | ; This program is distributed in the hope that it will be useful, 12 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ; GNU General Public License for more details. 15 | ; 16 | ; You should have received a copy of the GNU General Public License 17 | ; along with this program; if not, write to 18 | ; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 19 | ; Boston, MA 02111-1307, USA. 20 | ; 21 | ; Visit http://www.pineight.com/ for more information. 22 | 23 | .import periodTableLo, periodTableHi 24 | .importzp psg_sfx_state ; a 32 byte buffer in zp? 25 | .import update_music, update_music_ch, music_playing, psg_sound_table 26 | .export init_sound, start_sound, update_sound, soundBSS 27 | 28 | SNDCHN = $4015 29 | 30 | ; Ordinarily, the effect engine will move a pulse sound effect from 31 | ; $4000 to $4004 if $4004 is idle and $4000 is not, or if $4004 has 32 | ; less sfx data left to play than $4000. Turn this off to force all 33 | ; pulse sfx to be played on $4000. 34 | SQUARE_POOLING = 1 35 | 36 | ; As of 2011-03-10, a sound effect interrupts a musical instrument on 37 | ; the same channel only if the volume of the sfx is greater than that 38 | ; of the instrument. Turn this off to force sound fx to interrupt 39 | ; the music whenever sfx data remains on that channel, even if the 40 | ; music is louder. 41 | KEEP_MUSIC_IF_LOUDER = 1 42 | 43 | .segment "BSS" 44 | soundBSS: .res 64 45 | 46 | psg_sfx_datalo = psg_sfx_state + 0 47 | psg_sfx_datahi = psg_sfx_state + 1 48 | psg_sfx_lastfreqhi = psg_sfx_state + 18 49 | psg_sfx_remainlen = psg_sfx_state + 19 50 | psg_sfx_rate = soundBSS + 3 51 | psg_sfx_ratecd = soundBSS + 19 52 | 53 | .ifndef SOUND_NTSC_ONLY 54 | SOUND_NTSC_ONLY = 0 55 | .endif 56 | .if (!SOUND_NTSC_ONLY) 57 | .importzp tvSystem 58 | .endif 59 | 60 | .segment "CODE" 61 | 62 | ;; 63 | ; Initializes all sound channels. 64 | ; Use it at the start of a program or as a "panic button" before 65 | ; entering a long stretch of code where you don't update_sound. 66 | ; 67 | .proc init_sound 68 | lda #$0F 69 | sta SNDCHN 70 | lda #$30 71 | sta $4000 72 | sta $4004 73 | sta $400C 74 | sta psg_sfx_lastfreqhi+0 75 | sta psg_sfx_lastfreqhi+8 76 | sta psg_sfx_lastfreqhi+4 77 | lda #$80 78 | sta $4008 79 | lda #8 80 | sta $4001 81 | sta $4005 82 | lda #0 83 | sta $4003 84 | sta $4007 85 | sta $400F 86 | sta psg_sfx_remainlen+0 87 | sta psg_sfx_remainlen+4 88 | sta psg_sfx_remainlen+8 89 | sta psg_sfx_remainlen+12 90 | sta music_playing 91 | lda #64 92 | sta $4011 93 | rts 94 | .endproc 95 | 96 | ;; 97 | ; Starts a sound effect. 98 | ; (Trashes $0000-$0004 and X.) 99 | ; 100 | ; @param A sound effect number (0-63) 101 | ; 102 | .proc start_sound 103 | snddatalo = 0 104 | snddatahi = 1 105 | sndchno = 2 106 | sndlen = 3 107 | sndrate = 4 108 | 109 | asl a 110 | asl a 111 | tax 112 | lda psg_sound_table,x 113 | sta snddatalo 114 | lda psg_sound_table+1,x 115 | sta snddatahi 116 | lda psg_sound_table+2,x 117 | and #$0C 118 | sta sndchno 119 | lda psg_sound_table+2,x 120 | lsr a 121 | lsr a 122 | lsr a 123 | lsr a 124 | sta sndrate 125 | 126 | lda psg_sound_table+3,x 127 | sta sndlen 128 | 129 | ; split up square wave sounds between $4000 and $4004 130 | .if ::SQUARE_POOLING 131 | lda sndchno 132 | bne not_ch0to4 ; if not ch 0, don't try moving it 133 | lda psg_sfx_remainlen+4 134 | cmp psg_sfx_remainlen 135 | bcs not_ch0to4 136 | lda #4 137 | sta sndchno 138 | not_ch0to4: 139 | .endif 140 | 141 | ldx sndchno 142 | lda sndlen 143 | cmp psg_sfx_remainlen,x 144 | bcs ch_not_full 145 | rts 146 | ch_not_full: 147 | 148 | lda snddatalo 149 | sta psg_sfx_datalo,x 150 | lda snddatahi 151 | sta psg_sfx_datahi,x 152 | lda sndlen 153 | sta psg_sfx_remainlen,x 154 | lda sndrate 155 | sta psg_sfx_rate,x 156 | lda #0 157 | sta psg_sfx_ratecd,x 158 | rts 159 | .endproc 160 | 161 | 162 | ;; 163 | ; Updates sound effect channels. 164 | ; 165 | .proc update_sound 166 | jsr update_music 167 | ldx #12 168 | loop: 169 | jsr update_music_ch 170 | jsr update_one_ch 171 | dex 172 | dex 173 | dex 174 | dex 175 | bpl loop 176 | rts 177 | .endproc 178 | 179 | .proc update_one_ch 180 | 181 | ; At this point, the music engine should have left duty and volume 182 | ; in 2 and pitch in 3. 183 | lda psg_sfx_remainlen,x 184 | bne ch_not_done 185 | lda 2 186 | bne update_channel_hw 187 | 188 | ; Turn off the channel and force a reinit of the length counter. 189 | cpx #8 190 | beq not_triangle_kill 191 | lda #$30 192 | not_triangle_kill: 193 | sta $4000,x 194 | lda #$FF 195 | sta psg_sfx_lastfreqhi,x 196 | rts 197 | ch_not_done: 198 | 199 | ; playback rate divider 200 | dec psg_sfx_ratecd,x 201 | bpl rate_divider_cancel 202 | lda psg_sfx_rate,x 203 | sta psg_sfx_ratecd,x 204 | 205 | ; fetch the instruction 206 | lda psg_sfx_datalo+1,x 207 | sta 1 208 | lda psg_sfx_datalo,x 209 | sta 0 210 | clc 211 | adc #2 212 | sta psg_sfx_datalo,x 213 | bcc :+ 214 | inc psg_sfx_datahi,x 215 | : 216 | ldy #0 217 | .if ::KEEP_MUSIC_IF_LOUDER 218 | lda 2 219 | and #$0F 220 | sta 4 221 | lda (0),y 222 | and #$0F 223 | 224 | ; At this point: A = sfx volume; 4 = musc volume 225 | cmp 4 226 | bcc music_was_louder 227 | .endif 228 | lda (0),y 229 | sta 2 230 | iny 231 | lda (0),y 232 | sta 3 233 | music_was_louder: 234 | dec psg_sfx_remainlen,x 235 | 236 | update_channel_hw: 237 | lda 2 238 | ora #$30 239 | cpx #12 240 | bne notnoise 241 | sta $400C 242 | lda 3 243 | sta $400E 244 | rate_divider_cancel: 245 | rts 246 | 247 | notnoise: 248 | sta $4000,x 249 | ldy 3 250 | .ifndef ::SOUND_NTSC_ONLY 251 | lda tvSystem 252 | beq :+ 253 | iny 254 | : 255 | .endif 256 | lda periodTableLo,y 257 | sta $4002,x 258 | lda periodTableHi,y 259 | cmp psg_sfx_lastfreqhi,x 260 | beq no_change_to_hi_period 261 | sta psg_sfx_lastfreqhi,x 262 | sta $4003,x 263 | no_change_to_hi_period: 264 | 265 | rts 266 | .endproc 267 | 268 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/tips.s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/src/tips.s -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/title.pkb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/src/title.pkb -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/title.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; title.s 3 | ; title screen code for Thwaite 4 | 5 | ;;; Copyright (C) 2011 Damian Yerrick 6 | ; 7 | ; This program is free software; you can redistribute it and/or 8 | ; modify it under the terms of the GNU General Public License 9 | ; as published by the Free Software Foundation; either version 3 10 | ; of the License, or (at your option) any later version. 11 | ; 12 | ; This program is distributed in the hope that it will be useful, 13 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ; GNU General Public License for more details. 16 | ; 17 | ; You should have received a copy of the GNU General Public License 18 | ; along with this program; if not, write to 19 | ; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 20 | ; Boston, MA 02111-1307, USA. 21 | ; 22 | ; Visit http://www.pineight.com/ for more information. 23 | 24 | .include "src/nes.h" 25 | .include "src/ram.h" 26 | 27 | B_TO_RESET = 0 28 | 29 | .segment "CODE" 30 | ;; 31 | ; Displays the text file pointed to at (0) 32 | ; starting at (2, 3) on nametable $2000. 33 | .proc display_textfile 34 | src = 0 35 | dstLo = 2 36 | dstHi = 3 37 | lda #$20 38 | sta dstHi 39 | lda #$62 40 | sta dstLo 41 | txt_rowloop: 42 | ldy dstHi 43 | sty PPUADDR 44 | ldy dstLo 45 | sty PPUADDR 46 | ldy #0 47 | txt_charloop: 48 | lda (src),y 49 | beq txt_done 50 | cmp #$0A 51 | beq is_newline 52 | sta PPUDATA 53 | iny 54 | bne txt_charloop 55 | is_newline: 56 | 57 | sec 58 | tya 59 | adc src+0 60 | sta src+0 61 | lda src+1 62 | adc #0 63 | sta src+1 64 | lda dstLo 65 | adc #32 66 | sta dstLo 67 | lda dstHi 68 | adc #0 69 | sta dstHi 70 | cmp #$23 71 | bcc txt_rowloop 72 | lda dstLo 73 | cmp #$C0 74 | bcc txt_rowloop 75 | 76 | txt_done: 77 | rts 78 | .endproc 79 | 80 | .proc display_todo 81 | lda #VBLANK_NMI 82 | ldx #$00 83 | ldy #$3F 84 | sta PPUCTRL 85 | stx PPUMASK 86 | sty PPUADDR 87 | stx PPUADDR 88 | copypal: 89 | lda title_palette,x 90 | sta PPUDATA 91 | inx 92 | cpx #32 93 | bcc copypal 94 | 95 | ; clear nt 96 | ldy #$20 97 | sty PPUADDR 98 | ldy #$00 99 | sty PPUADDR 100 | tya 101 | : 102 | sta PPUDATA 103 | sta PPUDATA 104 | sta PPUDATA 105 | sta PPUDATA 106 | dey 107 | bne :- 108 | 109 | lda #>todo_txt 110 | sta 1 111 | lda #title_pkb 180 | sta 1 181 | ldx #$20 182 | stx PPUADDR 183 | ldy #$00 184 | sty PPUADDR 185 | jsr PKB_unpackblk 186 | 187 | lda #$F0 188 | ldx #$04 189 | : 190 | sta OAM,x 191 | inx 192 | bne :- 193 | 194 | loop: 195 | jsr title_draw_sprites 196 | lda nmis 197 | : 198 | cmp nmis 199 | beq :- 200 | bit PPUSTATUS 201 | 202 | ldy #0 203 | lda #VBLANK_NMI|BG_0000|OBJ_1000 204 | ldx #>OAM 205 | sty OAMADDR 206 | sty PPUSCROLL 207 | sty PPUSCROLL 208 | sta PPUCTRL 209 | stx OAM_DMA 210 | 211 | ldx #BG_ON|OBJ_ON 212 | stx PPUMASK 213 | 214 | jsr update_sound 215 | jsr read_pads 216 | jsr title_detect_mice 217 | 218 | s0wait0: 219 | bit $2002 220 | bvs s0wait0 221 | s0wait1: 222 | bit $2002 223 | bmi s0waitfail 224 | bvc s0wait1 225 | lda #VBLANK_NMI|BG_1000|OBJ_1000 226 | sta PPUCTRL 227 | s0waitfail: 228 | 229 | jsr title_move_numPlayers 230 | 231 | .if ::B_TO_RESET 232 | lda new_keys 233 | and #KEY_B 234 | beq notB 235 | jmp ($FFFC) 236 | notB: 237 | .endif 238 | 239 | ; Let the buttons of the first mouse 240 | ldx #0 241 | lda mouseEnabled,x 242 | bne handleClick 243 | inx 244 | lda mouseEnabled,x 245 | bne handleClick 246 | doneClick: 247 | lda new_keys+0 248 | and #(KEY_START | KEY_A) 249 | beq loop 250 | done: 251 | 252 | ; mix the current time into the rng 253 | lda nmis 254 | eor rand3 255 | clc 256 | adc rand1 257 | sta rand1 258 | ldy #8 259 | jmp random ; and off we go, done with the title screen 260 | 261 | ; Appendix: How to handle mouse clicks 262 | handleClick: 263 | lda new_mbuttons,x 264 | and #MOUSE_L 265 | beq doneClick 266 | lda crosshairYHi+0 267 | sec 268 | sbc #128 269 | lsr a 270 | lsr a 271 | lsr a 272 | lsr a 273 | 274 | ; Row 0: Change speed 275 | ; Row 1: 1 player; 2: 2 player; 3: Practice 276 | beq changeSpd 277 | cmp #4 278 | bcs doneClick 279 | sta numPlayers 280 | jmp done 281 | changeSpd: 282 | ldx #0 283 | lda crosshairXHi+0 284 | bpl :+ 285 | inx 286 | : 287 | jsr mouse_change_sensitivity 288 | jmp doneClick 289 | .endproc 290 | 291 | ;; 292 | ; Checks for the signature of a Super NES Mouse, which is $x1 293 | ; on the second read report. 294 | ; Stores 0 for no mouse or 1-3 for mouse sensitivity 1/4, 1/2, 1 295 | .proc title_detect_mice 296 | lda #0 297 | sta 4 298 | sta 5 299 | ldx #1 300 | loop: 301 | jsr read_mouse 302 | lda 1 303 | and #$0F 304 | cmp #1 305 | bne notMouse 306 | lda 2 307 | sta 4 308 | lda 3 309 | sta 5 310 | lda 1 311 | and #$30 312 | lsr a 313 | lsr a 314 | lsr a 315 | lsr a 316 | clc 317 | adc #1 318 | bne isMouse 319 | notMouse: 320 | lda #0 321 | isMouse: 322 | sta mouseEnabled,x 323 | dex 324 | bpl loop 325 | 326 | ; If a mouse is connected, 4 will have Y motion and 5 will have 327 | ; X motion 328 | 329 | lda 4 330 | bpl mouseNotDown 331 | eor #$7F 332 | clc 333 | adc #$01 334 | mouseNotDown: 335 | clc 336 | adc crosshairYHi+0 337 | cmp #128 338 | bcs noClipTop 339 | lda #128 340 | noClipTop: 341 | cmp #191 342 | bcc noClipBottom 343 | lda #191 344 | noClipBottom: 345 | sta crosshairYHi+0 346 | 347 | lda 5 348 | bpl mouseNotLeft 349 | eor #$7F 350 | clc 351 | adc #$01 352 | mouseNotLeft: 353 | clc 354 | adc crosshairXHi+0 355 | cmp #48 356 | bcs noClipLeft 357 | lda #48 358 | noClipLeft: 359 | cmp #207 360 | bcc noClipRight 361 | lda #207 362 | noClipRight: 363 | sta crosshairXHi+0 364 | rts 365 | .endproc 366 | 367 | .proc title_move_numPlayers 368 | ldx numPlayers 369 | lda new_keys 370 | and #KEY_SELECT 371 | beq notSelect 372 | inx 373 | cpx #4 374 | bcc notSelect 375 | ldx #1 376 | notSelect: 377 | 378 | lda new_keys 379 | and #KEY_DOWN 380 | beq notDown 381 | inx 382 | cpx #4 383 | bcc notDown 384 | dex 385 | notDown: 386 | 387 | lda new_keys 388 | and #KEY_UP 389 | beq notUp 390 | dex 391 | bne notUp 392 | inx 393 | notUp: 394 | 395 | stx numPlayers 396 | rts 397 | .endproc 398 | 399 | .proc title_draw_sprites 400 | ldx oamIndex 401 | 402 | lda mouseEnabled 403 | ora mouseEnabled+1 404 | beq noMouseCrosshair 405 | lda crosshairYHi+0 406 | sec 407 | sbc #5 408 | sta OAM,x 409 | lda crosshairXHi+0 410 | sec 411 | sbc #4 412 | sta OAM+3,x 413 | lda #4 414 | sta OAM+1,x 415 | lda #0 416 | sta OAM+2,x 417 | 418 | inx 419 | inx 420 | inx 421 | inx 422 | noMouseCrosshair: 423 | 424 | ; Draw arrow for number of players 425 | lda numPlayers 426 | asl a 427 | asl a 428 | asl a 429 | asl a 430 | pha 431 | lda nmis 432 | lsr a 433 | pla 434 | adc #130 435 | sta OAM+0,x 436 | lda #SELECTED_ARROW_TILE 437 | sta OAM+1,x 438 | lda #1 439 | sta OAM+2,x 440 | lda #76 441 | sta OAM+3,x 442 | txa 443 | clc 444 | adc #4 445 | tax 446 | 447 | ldy #1 448 | miceloop: 449 | lda mouseEnabled,y 450 | beq not_mouse 451 | jsr draw_mouse_player_y 452 | 453 | not_mouse: 454 | dey 455 | bpl miceloop 456 | 457 | stx oamIndex 458 | jsr clearRestOfOAM 459 | 460 | ; draw sprite 0, used to switch in CHR for "1 Player" text 461 | ; (which is written in FH, 15px autohinted) 462 | lda #102 463 | sta OAM+0 464 | lda #$01 465 | sta OAM+1 466 | lda #$23 ; black sprite on black bg, behind 467 | sta OAM+2 468 | lda #172 469 | sta OAM+3 470 | rts 471 | 472 | draw_mouse_player_y: 473 | ; Y coordinate 474 | lda #127 475 | sta OAM+0,x 476 | sta OAM+4,x 477 | sta OAM+8,x 478 | sta OAM+12,x 479 | lda #135 480 | sta OAM+16,x 481 | sta OAM+20,x 482 | sta OAM+24,x 483 | sta OAM+28,x 484 | 485 | ; Tile numbers 486 | tya 487 | ora #$60 488 | sta OAM+1,x 489 | ora #$10 490 | sta OAM+17,x 491 | lda mouseEnabled,y 492 | clc 493 | adc #$56 494 | sta OAM+13,X 495 | sta OAM+29,x 496 | lda #$68 497 | sta OAM+5,x 498 | lda #$69 499 | sta OAM+9,x 500 | lda #$78 501 | sta OAM+21,x 502 | lda #$79 503 | sta OAM+25,x 504 | 505 | ; Colors 506 | lda #$00 507 | sta OAM+2,x 508 | sta OAM+14,x 509 | sta OAM+18,x 510 | sta OAM+30,x 511 | lda #$02 512 | sta OAM+6,x 513 | sta OAM+10,x 514 | sta OAM+22,x 515 | sta OAM+26,x 516 | 517 | ; X coordinate 518 | lda mouse_icon_x,y 519 | sta OAM+3,x 520 | sta OAM+19,x 521 | adc #8 522 | sta OAM+7,x 523 | sta OAM+23,x 524 | adc #8 525 | sta OAM+11,x 526 | sta OAM+27,x 527 | ; and the speed lines 528 | adc #6 529 | sta OAM+15,x 530 | adc #1 531 | sta OAM+31,x 532 | 533 | txa 534 | clc 535 | adc #32 536 | tax 537 | rts 538 | .endproc 539 | 540 | .segment "RODATA" 541 | ; backdrop: black 542 | ; bg0: dark gray, light gray, white 543 | ; bg1: brown, red, white 544 | ; bg2: brown, green, white 545 | ; bg3: brown, blue, white 546 | ; obj0: blue, light blue, ? (player 1 crosshair and presents) 547 | ; obj1: red, orange, pale yellow (player 2 crosshair, missiles, balloons, and explosions) 548 | ; obj2: red, green, peach (villagers) 549 | ; obj3: gray, orange, peach (villagers, smoke) 550 | 551 | title_pkb: 552 | .incbin "src/title.pkb" 553 | todo_txt: 554 | .incbin "todo.txt" 555 | .byt 0 556 | title_palette: 557 | .byt $0F,$00,$10,$20,$0F,$17,$16,$20,$0F,$17,$2A,$20,$0F,$17,$12,$20 558 | ; grayscale arrow mouse sprite 0 559 | .byt $0F,$00,$10,$30, $0F,$16,$27,$38, $0F,$00,$10,$13, $0F,$0F,$0F,$0F 560 | mouse_icon_x: 561 | .byt 92, 140 562 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/src/unpkb.s: -------------------------------------------------------------------------------- 1 | ; 2 | ; unpkb.s - RLE unpacking 3 | ; 4 | ; Copyright (c) 2008 Damian Yerrick 5 | ; 6 | ; Copying and distribution of this file, with or without 7 | ; modification, are permitted in any medium without royalty provided 8 | ; the copyright notice and this notice are preserved in all source 9 | ; code copies. This file is offered as-is, without any warranty. 10 | ; 11 | 12 | ;;; Version history 13 | ; 14 | ; 2008 Dec: Optimized to better use the Y register, on a 15 | ; suggestion by blargg 16 | ; 2003 Oct: Ported for use with ca65 assembler 17 | ; 2000: Initial release, for x816 assembler 18 | 19 | 20 | .export PKB_unpackblk, PKB_unpack, PKB_source, PKB_len 21 | 22 | 23 | ;;; Configuration 24 | ; 25 | ; Set PKB_outport to whatever data port you want PackBits to use. 26 | ; (Slightly more modification is necessary for memory-to-memory 27 | ; unpacking.) 28 | ; 29 | PKB_outport = $2007 ;NES PPU data register 30 | 31 | ; 32 | ; Set PKB_source to the address in direct page (i.e. zero page) 33 | ; where the pointer to packed data is stored. 34 | ; 35 | PKB_source = $00 36 | PKB_len = $02 37 | 38 | .segment "CODE" 39 | 40 | ; 41 | ; PKB_unpackblk 42 | ; Unpack PackBits() encoded data from memory at (PKB_source) 43 | ; to a character device such as the NES PPU data register. 44 | ; 45 | ; This entry point assumes a 16-bit length word in network 46 | ; byte order before the data. 47 | PKB_unpackblk: 48 | 49 | ldy #0 50 | lda (PKB_source),y 51 | inc PKB_source 52 | bne :+ 53 | inc PKB_source+1 54 | : 55 | sta PKB_len+1 56 | lda (PKB_source),y 57 | inc PKB_source 58 | bne :+ 59 | inc PKB_source+1 60 | : 61 | sta PKB_len 62 | 63 | ; This entry point assumes a 16-bit length word in host byte order 64 | ; at PKB_len. 65 | PKB_unpack: 66 | lda PKB_len 67 | beq :+ 68 | inc PKB_len+1 ;trick to allow easier 16-bit decrement 69 | : 70 | ; optimization suggested by blargg is to hold the low byte 71 | ; in Y instead of using constant Y=0; saves 3 cycles per 72 | ; compressed byte 73 | ldy PKB_source 74 | lda #0 75 | sta PKB_source 76 | 77 | @PKB_loop: 78 | lda (PKB_source),y 79 | bmi @PKB_run 80 | 81 | iny ; got a literal string 82 | bne :+ 83 | inc PKB_source+1 84 | : 85 | tax 86 | inx 87 | @PKB_strloop: 88 | lda (PKB_source),y 89 | iny 90 | bne :+ 91 | inc PKB_source+1 92 | : 93 | sta PKB_outport 94 | dec PKB_len 95 | bne :+ 96 | dec PKB_len+1 97 | beq @PKB_rts 98 | : 99 | dex 100 | bne @PKB_strloop 101 | beq @PKB_loop 102 | 103 | @PKB_rts: 104 | sty PKB_source ; restore the address after the compressed data 105 | rts 106 | 107 | @PKB_run: 108 | iny ; got a run 109 | bne :+ 110 | inc PKB_source+1 111 | : 112 | tax 113 | dex 114 | lda (PKB_source),y ; get the run contents 115 | iny 116 | bne @PKB_runloop 117 | inc PKB_source+1 118 | @PKB_runloop: 119 | sta PKB_outport 120 | dec PKB_len 121 | bne :+ 122 | dec PKB_len+1 123 | beq @PKB_rts 124 | : 125 | inx 126 | bne @PKB_runloop 127 | beq @PKB_loop 128 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/thwaite.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/thwaite.nes -------------------------------------------------------------------------------- /roms/thwaite-0.03/tilesets/cuthouses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/tilesets/cuthouses.png -------------------------------------------------------------------------------- /roms/thwaite-0.03/tilesets/maingfx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/tilesets/maingfx.png -------------------------------------------------------------------------------- /roms/thwaite-0.03/todo.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/roms/thwaite-0.03/todo.txt -------------------------------------------------------------------------------- /roms/thwaite-0.03/tools/mktables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Lookup table generator for Concentration Room 4 | # Copyright 2010 Damian Yerrick 5 | # 6 | # Copying and distribution of this file, with or without 7 | # modification, are permitted in any medium without royalty 8 | # provided the copyright notice and this notice are preserved. 9 | # This file is offered as-is, without any warranty. 10 | # 11 | from __future__ import with_statement, division 12 | import sys 13 | 14 | ntscOctaveBase = 39375000.0/(22 * 16 * 55) 15 | palOctaveBase = 266017125.0/(10 * 16 * 16 * 55) 16 | maxNote = 80 17 | 18 | def makePeriodTable(filename, pal=False): 19 | semitone = 2.0**(1./12) 20 | octaveBase = palOctaveBase if pal else ntscOctaveBase 21 | relFreqs = [(1 << (i // 12)) * semitone**(i % 12) 22 | for i in xrange(maxNote)] 23 | periods = [int(round(octaveBase / freq)) - 1 for freq in relFreqs] 24 | systemName = "PAL" if pal else "NTSC" 25 | with open(filename, 'wt') as outfp: 26 | outfp.write("""; %s period table generated by mktables.py 27 | .export periodTableLo, periodTableHi 28 | .segment "RODATA" 29 | periodTableLo:\n""" 30 | % systemName) 31 | for i in range(0, maxNote, 12): 32 | outfp.write(' .byt ' 33 | + ','.join('$%02x' % (i % 256) 34 | for i in periods[i:i + 12]) 35 | + '\n') 36 | outfp.write('periodTableHi:\n') 37 | for i in range(0, maxNote, 12): 38 | outfp.write(' .byt ' 39 | + ','.join('$%02x' % (i >> 8) 40 | for i in periods[i:i + 12]) 41 | + '\n') 42 | 43 | def makePALPeriodTable(filename): 44 | return makePeriodTable(filename, pal=True) 45 | 46 | tableNames = { 47 | 'period': makePeriodTable, 48 | 'palperiod': makePALPeriodTable 49 | } 50 | 51 | def main(argv): 52 | if len(argv) >= 2 and argv[1] in ('/?', '-?', '-h', '--help'): 53 | print "usage: %s TABLENAME FILENAME" % argv[0] 54 | print "known tables:", ' '.join(sorted(tableNames)) 55 | elif len(argv) < 3: 56 | print "mktables: too few arguments; try %s --help" % argv[0] 57 | elif argv[1] in tableNames: 58 | tableNames[argv[1]](argv[2]) 59 | else: 60 | print "mktables: no such table %s; try %s --help" % (argv[1], argv[0]) 61 | 62 | if __name__=='__main__': 63 | main(sys.argv) 64 | 65 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/tools/pilbmp2nes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Bitmap to NES CHR converter using Python Imaging Library 4 | # Copyright 2010 Damian Yerrick 5 | # 6 | # Copying and distribution of this file, with or without 7 | # modification, are permitted in any medium without royalty 8 | # provided the copyright notice and this notice are preserved. 9 | # This file is offered as-is, without any warranty. 10 | # 11 | from __future__ import with_statement 12 | from PIL import Image 13 | from time import sleep 14 | 15 | def formatTilePlanar(tile, nPlanes): 16 | import array 17 | if (tile.size != (8, 8)): 18 | return None 19 | pixels = iter(tile.getdata()) 20 | outplanes = [array.array('B') 21 | for i in range(nPlanes)] 22 | for y in range(8): 23 | slivers = [0 for i in range(nPlanes)] 24 | for x in range(8): 25 | px = pixels.next() 26 | for i in range(nPlanes): 27 | slivers[i] = slivers[i] << 1 28 | if px & 0x01: 29 | slivers[i] = slivers[i] | 1 30 | px >>= 1 31 | for i in range(nPlanes): 32 | outplanes[i].append(slivers[i]) 33 | out = "".join(plane.tostring() for plane in outplanes) 34 | return out 35 | 36 | def parse_argv(argv): 37 | from optparse import OptionParser 38 | parser = OptionParser(usage="usage: %prog [options] [-i] INFILE [-o] OUTFILE") 39 | parser.add_option("-i", "--image", dest="infilename", 40 | help="read image from INFILE", metavar="INFILE") 41 | parser.add_option("-o", "--output", dest="outfilename", 42 | help="write CHR data to OUTFILE", metavar="OUTFILE") 43 | parser.add_option("-W", "--tile-width", dest="tileWidth", 44 | help="set width of metatiles", metavar="HEIGHT", 45 | type="int", default=8) 46 | parser.add_option("-H", "--tile-height", dest="tileHeight", 47 | help="set height of metatiles", metavar="HEIGHT", 48 | type="int", default=8) 49 | (options, args) = parser.parse_args(argv[1:]) 50 | 51 | tileWidth = int(options.tileWidth) 52 | if tileWidth <= 0: 53 | raise ValueError("tile width '%d' must be positive" % tileWidth) 54 | 55 | tileHeight = int(options.tileHeight) 56 | if tileHeight <= 0: 57 | raise ValueError("tile height '%d' must be positive" % tileHeight) 58 | 59 | # Fill unfilled roles with positional arguments 60 | argsreader = iter(args) 61 | try: 62 | infilename = options.infilename 63 | if infilename is None: 64 | infilename = argsreader.next() 65 | outfilename = options.infilename 66 | if outfilename is None: 67 | outfilename = argsreader.next() 68 | except StopIteration: 69 | raise ValueError("not enough filenames") 70 | try: 71 | argsreader.next() 72 | raise ValueError("too many filenames") 73 | except StopIteration: 74 | pass 75 | 76 | return (infilename, outfilename, tileWidth, tileHeight) 77 | 78 | argvTestingMode = True 79 | 80 | def main(argv=None): 81 | if argv is None: 82 | import sys 83 | import os 84 | argv = sys.argv 85 | print argv 86 | if (argvTestingMode and len(argv) < 2 87 | and sys.stdin.isatty() and sys.stdout.isatty()): 88 | argv.extend(raw_input('args:').split()) 89 | try: 90 | (infilename, outfilename, tileWidth, tileHeight) = parse_argv(argv) 91 | except StandardError, e: 92 | import sys 93 | sys.stderr.write("%s: %s\n" % (argv[0], str(e))) 94 | sys.exit(1) 95 | 96 | print "loading", infilename 97 | im = Image.open(infilename) 98 | im.load() 99 | (w, h) = im.size 100 | 101 | out = [] 102 | for mt_y in range(0, h, tileHeight): 103 | for mt_x in range(0, w, tileWidth): 104 | metatile = im.crop((mt_x, mt_y, 105 | mt_x + tileWidth, mt_y + tileHeight)) 106 | for tile_y in range(0, tileHeight, 8): 107 | for tile_x in range(0, tileWidth, 8): 108 | tile = metatile.crop((tile_x, tile_y, 109 | tile_x + 8, tile_y + 8)) 110 | data = formatTilePlanar(tile, 2) 111 | out.append(data) 112 | with open(outfilename, 'wb') as outfp: 113 | outfp.writelines(out) 114 | 115 | if __name__=='__main__': 116 | main() 117 | -------------------------------------------------------------------------------- /roms/thwaite-0.03/zip.in: -------------------------------------------------------------------------------- 1 | CHANGES.txt 2 | gpl-3.0.txt 3 | makefile 4 | nes.ini 5 | README.html 6 | thwaite.nes 7 | todo.txt 8 | zip.in 9 | docs/tas_words.txt 10 | docs/tech_notes.html 11 | docs/de.css 12 | docs/manual_ingame.png 13 | docs/manual_legend.png 14 | docs/manual_practice.png 15 | docs/manual_title.png 16 | obj/nes/index.txt 17 | src/bcd.s 18 | src/bg.s 19 | src/cutscene.pkb 20 | src/cutscene.s 21 | src/cutscripts.s 22 | src/explosion.s 23 | src/kinematics.s 24 | src/levels.s 25 | src/main.s 26 | src/math.s 27 | src/missiles.s 28 | src/mouse.s 29 | src/music.s 30 | src/musicseq.h 31 | src/musicseq.s 32 | src/nes.h 33 | src/pads.s 34 | src/paldetect.s 35 | src/practice.s 36 | src/practice.txt 37 | src/ram.h 38 | src/random.s 39 | src/scurry.s 40 | src/smoke.s 41 | src/sound.s 42 | src/tips.s 43 | src/title.pkb 44 | src/title.s 45 | src/unpkb.s 46 | tilesets/maingfx.png 47 | tilesets/cuthouses.png 48 | tools/8name.py 49 | tools/mktables.py 50 | tools/pilbmp2nes.py 51 | -------------------------------------------------------------------------------- /script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CycloaJS: JavaScript NES Emulator 6 | 7 | 8 | 19 | 20 | 21 |
22 |

JavaScript NES Programming!

23 |
24 |
25 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 |
41 |
42 |

43 | 			
44 |
45 |
46 |

Software Requirements / 動作要件

47 |

Chrme/Firefox, Safari for iPad and Safari for Windows(v5). Sounds are available only for Chrome and Firefox (Web Audio API and Audio Data API).

48 |

Chrome/Firefox、iPad版Safari/Windows版Safari5での動作を確認しています。ChromeとFirefoxのみサウンドも出力されます(Web Audio API and Audio Data API)。

49 |
50 |
51 |

Control / 操作方法

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Player 1Button
AZ
BX
StartEnter
SelectSpace
81 |

Currently player2 is not supported. I have tried many times to support Gamepad API, but the API does not work at all... :(

82 |

現在プレイヤ2は対応していません。ゲームパッドAPIにも何度も対応しようとしたのですが、ゲームパッドの状態が拾えません…。

83 |
84 |
85 | 86 |
87 |
88 | 91 |
92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /script/sample.js: -------------------------------------------------------------------------------- 1 | var t = 0; //time 2 | for(var i=0;i<240;++i){ 3 | this.registerHandler(i, function(scanline, nes){ 4 | var off = Math.sin((scanline+(t/100))/240.0)*32; 5 | off = (off+32)|0; 6 | nes.write(0x2005, off); 7 | nes.write(0x2005, 0); 8 | }); 9 | } 10 | this.registerHandler('onReset', function(scanline, nes){ 11 | var fillBuffer = function(){ 12 | for(var x=0;x<240*4;++x){ 13 | nes.write(0x2007, 0); 14 | } 15 | }; 16 | var initPattern = function(){ 17 | nes.write(0x2006, 0x20); 18 | nes.write(0x2006, 0x00); 19 | fillBuffer(); 20 | nes.write(0x2006, 0x28); 21 | nes.write(0x2006, 0x00); 22 | fillBuffer(); 23 | 24 | }; 25 | var pasteString = function(str){ 26 | for(var j=0;j<30; ++j){ 27 | nes.write(0x2006, 0x20); 28 | nes.write(0x2006, 0x0a+j*32); 29 | for(var i=0;i Encoding::UTF_8), nil, "%>", "_#{rand(0xffffffff).to_s(16)}_").result(b) 12 | str 13 | end 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/nes.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | module NES 4 | PRG_ROM_PAGE_SIZE = 16 * 1024; 5 | CHR_ROM_PAGE_SIZE = 8 * 1024; 6 | TRAINER_SIZE = 512; 7 | # 8 | FOUR_SCREEN = 0; 9 | VERTICAL = 1; 10 | HORIZONTAL = 2; 11 | SINGLE0 = 3; 12 | SINGLE1 = 4; 13 | 14 | # 15 | PRG_ROM_BLOCK_SIZE=1024; 16 | PRG_ROM_BLOCK_SIZE_SHIFT=10; 17 | CHR_ROM_BLOCK_SIZE=512; 18 | CHR_ROM_BLOCK_SIZE_SHIFT=9; 19 | end 20 | 21 | -------------------------------------------------------------------------------- /src/pad.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.dirname(__FILE__)+"/gen.rb"; 3 | 4 | module Pad 5 | A=0; 6 | B=1; 7 | SELECT=2; 8 | START=3; 9 | UP=4; 10 | DOWN=5; 11 | LEFT=6; 12 | RIGHT=7; 13 | TOTAL=8; 14 | MASK_A=1; 15 | MASK_B=2; 16 | MASK_SELECT=4; 17 | MASK_START=8; 18 | MASK_UP=16; 19 | MASK_DOWN=32; 20 | MASK_LEFT=64; 21 | MASK_RIGHT=128; 22 | MASK_ALL=255; 23 | end 24 | 25 | -------------------------------------------------------------------------------- /src/video.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require 'erb' 3 | require File.expand_path( File.dirname(__FILE__)+"/gen.rb" ); 4 | 5 | module Video 6 | ClockFactor = 3; 7 | 8 | ScreenWidth = 256; 9 | ScreenWidthShift = 8; 10 | ScreenHeight = 240; 11 | EmptyBit = 0x00; 12 | BackSpriteBit = 0x40; 13 | BackgroundBit = 0x80; 14 | FrontSpriteBit = 0xc0; 15 | SpriteLayerBit = 0x40; 16 | LayerBitMask = 0xc0; 17 | ClockPerScanline = 341; 18 | ScanlinePerScreen = 262; 19 | DefaultSpriteCnt = 8; 20 | 21 | PaletteSize = 9*4; 22 | VramSize = 0x800; 23 | SpRamSize = 0x100; 24 | def self.RunInit() 25 | "#{UseVideoAccess()}" 26 | end 27 | def self.Init() 28 | "" 29 | end 30 | def self.UseVideoAccess() 31 | "var __video__palette = this.__video__palette; var __video__vramMirroring = this.__video__vramMirroring; var __video__pattern = this.__video__pattern; var __video__screenBuffer8 = this.__video__screenBuffer8;var __video__screenBuffer32 = this.__video__screenBuffer32;" 32 | end 33 | def self.Palette(i,j) 34 | "__video__palette[#{i<<2+j}]"; 35 | end 36 | def self.ReadVramExternal(addr) 37 | "(#{addr} < 0x2000 ? __video__pattern[(#{addr} >> 9) & 0xf][#{addr} & 0x1ff] : __video__vramMirroring[(#{addr} >> 10) & 0x3][#{addr} & 0x3ff])" 38 | end 39 | def self.ReadPalette(addr) 40 | "((#{addr} & 0x3 === 0) ? __video__palette[32 | ((addr >> 2) & 3)] : __video__palette[#{addr} & 31])" 41 | end 42 | def self.ReadVram(addr, with_this = false) 43 | "(((#{addr} & 0x3f00) !== 0x3f00) ? #{ReadVramExternal(addr)} : #{ReadPalette(addr)} )" 44 | end 45 | end 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/vm.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | %require File.expand_path File.dirname(__FILE__)+"/gen.rb"; 3 | %MachineName="cycloa.VirtualMachine"; 4 | 5 | /** 6 | * ファミコンエミュレータ本体を表すクラスです。 7 | * @constructor 8 | */ 9 | <%= MachineName %> = function(videoFairy, audioFairy, pad1Fairy, pad2Fairy) { 10 | this.tracer = new cycloa.Tracer(this); 11 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_cpu_init.erb.js" %> 12 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_video_init.erb.js" %> 13 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_init.erb.js" %> 14 | <%= render (File.expand_path (File.dirname(__FILE__)+"/vm_audio_rectangle_init.erb.js")), :isFirstChannel=>false %> 15 | <%= render (File.expand_path (File.dirname(__FILE__)+"/vm_audio_rectangle_init.erb.js")), :isFirstChannel=>false %> 16 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_triangle_init.erb.js" %> 17 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_noize_init.erb.js" %> 18 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_digital_init.erb.js" %> 19 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_pad_init.erb.js" %> 20 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_mapper_init.erb.js" %> 21 | this.__vm__reservedClockDelta = 0; 22 | /** @type {boolean} */ 23 | this.NMI = false; 24 | /** @type {boolean} */ 25 | this.IRQ = false; 26 | }; 27 | 28 | /** 29 | * VMを1フレーム分実行する 30 | */ 31 | <%= MachineName %>.prototype.run = function () { 32 | <%= CPU::RunInit() %> 33 | <%= Video::RunInit() %> 34 | <%= Audio::RunInit() %> 35 | var __vm__run = true; 36 | var __vm__clockDelta; 37 | var __vm__reservedClockDelta = this.__vm__reservedClockDelta; 38 | this.__vm__reservedClockDelta = 0; 39 | while(__vm__run) { 40 | __vm__clockDelta = __vm__reservedClockDelta; __vm__reservedClockDelta = 0; 41 | //console.log(this.tracer.decode()); 42 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_cpu_run.erb.js" %> 43 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_video_run.erb.js" %> 44 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_run.erb.js" %> 45 | } 46 | this.__vm__reservedClockDelta += __vm__reservedClockDelta; 47 | return __vm__run; 48 | }; 49 | 50 | /** 51 | * 関数実行時に 52 | * @function 53 | */ 54 | <%= MachineName %>.prototype.onHardReset = function () { 55 | this.NMI = false; 56 | this.IRQ = 0; 57 | this.onHardResetCPU(); 58 | this.__video__onHardReset(); 59 | this.__audio__onHardReset(); 60 | this.__rectangle0__onHardReset(); 61 | this.__rectangle1__onHardReset(); 62 | this.__triangle__onHardReset(); 63 | this.__noize__onHardReset(); 64 | this.__digital__onHardReset(); 65 | }; 66 | <%= MachineName %>.prototype.onReset = function () { 67 | this.NMI = false; 68 | this.IRQ = 0; 69 | this.onResetCPU(); 70 | this.__video__onReset(); 71 | this.__audio__onReset(); 72 | this.__rectangle0__onReset(); 73 | this.__rectangle1__onReset(); 74 | this.__triangle__onReset(); 75 | this.__noize__onReset(); 76 | this.__digital__onReset(); 77 | }; 78 | <%= MachineName %>.prototype.onVBlank = function(){ 79 | }; 80 | <%= MachineName %>.prototype.onIRQ = function(){ 81 | }; 82 | <%= MachineName %>.prototype.read = function(addr) { 83 | var __val__; 84 | var __cpu__rom = this.__cpu__rom; var __cpu__ram = this.__cpu__ram; 85 | <%= CPU::MemRead("addr", "__val__") %>; 86 | return __val__; 87 | }; 88 | 89 | <%= MachineName %>.prototype.write = function(addr, val) { 90 | var __cpu__rom = this.__cpu__rom; var __cpu__ram = this.__cpu__ram; 91 | <%= CPU::MemWrite("addr", "val") %>; 92 | }; 93 | 94 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_cpu_method.erb.js" %> 95 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_video_method.erb.js" %> 96 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_method.erb.js" %> 97 | <%= render (File.expand_path (File.dirname(__FILE__)+"/vm_audio_rectangle_method.erb.js")), :isFirstChannel=>false %> 98 | <%= render (File.expand_path (File.dirname(__FILE__)+"/vm_audio_rectangle_method.erb.js")), :isFirstChannel=>true %> 99 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_triangle_method.erb.js" %> 100 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_noize_method.erb.js" %> 101 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_audio_digital_method.erb.js" %> 102 | <%= render File.expand_path File.dirname(__FILE__)+"/vm_mapper_method.erb.js" %> 103 | 104 | -------------------------------------------------------------------------------- /src/vm_audio_digital_create.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | if(this.__digital__sampleLength != 0){ 3 | /*unsigned int*/ var __digital__nowCounter = this.__digital__freqCounter + __audio__delta; 4 | /*const uint16_t*/var __digital__divFreq = this.__digital__frequency + 1; 5 | while(__digital__nowCounter >= __digital__divFreq){ 6 | __digital__nowCounter -= divFreq; 7 | if(this.__digital__sampleBufferLeft == 0){ 8 | this.__digital__sampleLength--; 9 | var __val__; 10 | var __digital__addr = this.__digital__sampleAddr; 11 | <%= CPU::MemRead("__digital__addr", "__digitl__val__") %> 12 | this.__digital__sampleBuffer = __digitl__val__; 13 | 14 | if(this.__digital__sampleAddr >= 0xffff){ 15 | this.__digital__sampleAddr = 0x8000; 16 | }else{ 17 | this.__digital__sampleAddr++; 18 | } 19 | this.__digital__sampleBufferLeft = 7; 20 | <%= CPU::ConsumeReservedClock(4) %> 21 | if(this.__digital__sampleLength == 0){ 22 | if(this.__digital__loopEnabled){ 23 | this.__digital__sampleLength = this.__digital__sampleLengthBuffer; 24 | }else if(this.__digital__irqEnabled){ 25 | <%= CPU::ReserveIRQ(CPU::IRQ::DMC) %> 26 | }else{ 27 | break; 28 | } 29 | } 30 | } 31 | this.__digital__sampleBuffer = this.__digital__sampleBuffer >> 1; 32 | if((this.__digital__sampleBuffer & 1) == 1){ 33 | if(this.__digital__deltaCounter < 126){ 34 | this.__digital__deltaCounter+=2; 35 | } 36 | }else{ 37 | if(this.__digital__deltaCounter > 1){ 38 | this.__digital__deltaCounter-=2; 39 | } 40 | } 41 | this.__digital__sampleBufferLeft--; 42 | } 43 | this.__digital__freqCounter = __digitl__nowCounter; 44 | __audio__sound += this.__digital__deltaCounter; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/vm_audio_digital_init.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | this.__digital__FrequencyTable = [ 4 | 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 //NTSC 5 | //398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 6 | ]; -------------------------------------------------------------------------------- /src/vm_audio_digital_method.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | <%= MachineName %>.prototype.__digital__isIRQEnabled = function() 4 | { 5 | return this.__digital__irqEnabled; 6 | } 7 | <%= MachineName %>.prototype.__digital__onHardReset = function() { 8 | this.__digital__irqEnabled = false; 9 | <%= CPU::ReleaseIRQ(CPU::IRQ::DMC) %> 10 | this.__digital__loopEnabled = false; 11 | this.__digital__frequency = 0; 12 | this.__digital__deltaCounter = 0; 13 | this.__digital__sampleAddr = 0xc000; 14 | this.__digital__sampleLength = 0; 15 | this.__digital__sampleLengthBuffer = 0; 16 | this.__digital__sampleBuffer = 0; 17 | this.__digital__sampleBufferLeft = 0; 18 | 19 | this.__digital__freqCounter = 0; 20 | }; 21 | <%= MachineName %>.prototype.__digital__onReset = function() { 22 | this.__digital__onHardReset(); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /src/vm_audio_init.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | this.__audio__audioFairy = audioFairy; 3 | 4 | this.__audio__LengthCounterConst = <%= MachineName %>.LengthCounterConst; 5 | -------------------------------------------------------------------------------- /src/vm_audio_method.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | <%= MachineName %>.prototype.__audio__onHardReset = function() { 4 | this.__audio__clockCnt = 0; 5 | this.__audio__leftClock = 0; 6 | 7 | this.__audio__frameIRQenabled = true; 8 | <%= CPU::ReleaseIRQ(CPU::IRQ::FRAMECNT) %> 9 | 10 | this.__audio__isNTSCmode = true; 11 | this.__audio__frameIRQCnt = 0; 12 | this.__audio__frameCnt = 0; 13 | }; 14 | <%= MachineName %>.prototype.__audio__onReset = function() { 15 | }; 16 | 17 | <%= MachineName %>.LengthCounterConst = [ 18 | 0x0A,0xFE,0x14,0x02,0x28,0x04,0x50,0x06, 19 | 0xA0,0x08,0x3C,0x0A,0x0E,0x0C,0x1A,0x0E, 20 | 0x0C,0x10,0x18,0x12,0x30,0x14,0x60,0x16, 21 | 0xC0,0x18,0x48,0x1A,0x10,0x1C,0x20,0x1E 22 | ]; 23 | -------------------------------------------------------------------------------- /src/vm_audio_noize_create.erb.js: -------------------------------------------------------------------------------- 1 | if(this.__noize__lengthCounter != 0){ 2 | /* unsigned int */var __noize__nowCounter = this.__noize__freqCounter + __audio__delta; 3 | /* const uint16_t */var __noize__divFreq = this.__noize__frequency + 1; 4 | /* const uint8_t */var __noize__shiftAmount = this.__noize__modeFlag ? 6 : 1; 5 | //FIXME: frequencyが小さい時に此のモデルが破綻する 6 | var __noize__shiftReg = this.__noize__shiftRegister; 7 | while(__noize__nowCounter >= __noize__divFreq){ 8 | __noize__nowCounter -= __noize__divFreq; 9 | __noize__shiftReg =(__noize__shiftReg >> 1) | (((__noize__shiftReg ^ (__noize__shiftReg >> __noize__shiftAmount)) & 1) << 14); 10 | } 11 | 12 | if(((__noize__shiftReg & 1) == 1)){ 13 | __audio__sound += this.__noize__decayEnabled ? -this.__noize__decayVolume : -this.__noize__volumeOrDecayRate; 14 | }else{ 15 | __audio__sound += this.__noize__decayEnabled ? this.__noize__decayVolume : this.__noize__volumeOrDecayRate; 16 | } 17 | 18 | this.__noize__freqCounter = __noize__nowCounter; 19 | this.__noize__shiftRegister = __noize__shiftReg; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/vm_audio_noize_init.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | this.__noize__FrequencyTable = [ 4 | 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 //NTSC 5 | //4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 //PAL 6 | ]; -------------------------------------------------------------------------------- /src/vm_audio_noize_method.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | <%= MachineName %>.prototype.__noize__onHardReset = function() { 4 | //rand 5 | this.__noize__shiftRegister = 1<<14; 6 | this.__noize__modeFlag = false; 7 | 8 | //decay 9 | this.__noize__volumeOrDecayRate = false; 10 | this.__noize__decayReloaded = false; 11 | this.__noize__decayEnabled = false; 12 | 13 | this.__noize__decayCounter = 0; 14 | this.__noize__decayVolume = 0; 15 | // 16 | this.__noize__loopEnabled = false; 17 | this.__noize__frequency = 0; 18 | // 19 | this.__noize__lengthCounter = 0; 20 | // 21 | this.__noize__freqCounter = 0; 22 | }; 23 | <%= MachineName %>.prototype.__noize__onReset = function() { 24 | this.__noize__onHardReset(); 25 | }; 26 | -------------------------------------------------------------------------------- /src/vm_audio_noize_runh.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | if(this.__noize__lengthCounter != 0 && !this.__noize__loopEnabled){ 4 | this.__noize__lengthCounter--; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/vm_audio_noize_runq.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | if(this.__noize__decayCounter == 0){ 4 | this.__noize__decayCounter = this.__noize__volumeOrDecayRate; 5 | if(this.__noize__decayVolume == 0){ 6 | if(this.__noize__loopEnabled){ 7 | this.__noize__decayVolume = 0xf; 8 | } 9 | }else{ 10 | this.__noize__decayVolume--; 11 | } 12 | }else{ 13 | this.__noize__decayCounter--; 14 | } 15 | if(this.__noize__decayReloaded){ 16 | this.__noize__decayReloaded = false; 17 | this.__noize__decayVolume = 0xf; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/vm_audio_rectangle_create.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | % isFirstChannel = args[:isFirstChannel] 3 | % prefix=isFirstChannel ? "__rectangle0__" : "__rectangle1__" 4 | 5 | if(this.<%= prefix %>lengthCounter != 0 && this.<%= prefix %>frequency >= 0x8 && this.<%= prefix %>frequency <= 0x7ff){ 6 | /* 7 | * @type {number} unsigned int 8 | * @const 9 | */ 10 | var <%= prefix %>nowCounter = this.<%= prefix %>freqCounter + __audio__delta; 11 | this.<%= prefix %>freqCounter = <%= prefix %>nowCounter % (this.<%= prefix %>frequency + 1); 12 | this.<%= prefix %>dutyCounter = (this.<%= prefix %>dutyCounter + (<%= prefix %>nowCounter / (this.<%= prefix %>frequency + 1))) & 15; 13 | if(this.<%= prefix %>dutyCounter < this.<%= prefix %>dutyRatio){ 14 | __audio__sound += this.<%= prefix %>decayEnabled ? this.<%= prefix %>decayVolume : this.<%= prefix %>volumeOrDecayRate; 15 | }else{ 16 | __audio__sound += this.<%= prefix %>decayEnabled ? -this.<%= prefix %>decayVolume : -this.<%= prefix %>volumeOrDecayRate; 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/vm_audio_rectangle_init.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | % isFirstChannel = args[:isFirstChannel] 3 | % prefix=isFirstChannel ? "__rectangle0__" : "__rectangle1__" 4 | 5 | // <%= prefix %> do nothing 6 | -------------------------------------------------------------------------------- /src/vm_audio_rectangle_method.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | % isFirstChannel = args[:isFirstChannel] 3 | % prefix=isFirstChannel ? "__rectangle0__" : "__rectangle1__" 4 | 5 | <%= MachineName %>.prototype.<%= prefix %>onHardReset = function() { 6 | this.<%= prefix %>volumeOrDecayRate = 0; 7 | this.<%= prefix %>decayReloaded = false; 8 | this.<%= prefix %>decayEnabled = false; 9 | this.<%= prefix %>decayVolume = 0; 10 | this.<%= prefix %>dutyRatio = 0; 11 | this.<%= prefix %>freqCounter = 0; 12 | this.<%= prefix %>dutyCounter = 0; 13 | this.<%= prefix %>decayCounter = 0; 14 | this.<%= prefix %>sweepEnabled = 0; 15 | this.<%= prefix %>sweepShiftAmount = 0; 16 | this.<%= prefix %>sweepIncreased = false; 17 | this.<%= prefix %>sweepUpdateRatio = 0; 18 | this.<%= prefix %>sweepCounter = 0; 19 | this.<%= prefix %>frequency = 0; 20 | this.<%= prefix %>loopEnabled = false; 21 | this.<%= prefix %>lengthCounter = 0; 22 | }; 23 | <%= MachineName %>.prototype.<%= prefix %>onReset = function(){ 24 | this.<%= prefix %>onHardReset(); 25 | }; 26 | -------------------------------------------------------------------------------- /src/vm_audio_rectangle_runh.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | % isFirstChannel = args[:isFirstChannel] 3 | % prefix=isFirstChannel ? "__rectangle0__" : "__rectangle1__" 4 | 5 | if(this.<%= prefix %>lengthCounter != 0 && !this.<%= prefix %>loopEnabled){ 6 | this.<%= prefix %>lengthCounter--; 7 | } 8 | if(this.<%= prefix %>sweepEnabled){ 9 | if(this.<%= prefix %>sweepCounter == 0){ 10 | this.<%= prefix %>sweepCounter = this.<%= prefix %>sweepUpdateRatio; 11 | if(this.<%= prefix %>lengthCounter != 0 && this.<%= prefix %>sweepShiftAmount != 0){ 12 | /** 13 | * @type {number} uint16_t 14 | */ 15 | var <%= prefix %>shift = (this.<%= prefix %>frequency >> this.<%= prefix %>sweepShiftAmount); 16 | if(this.<%= prefix %>sweepIncreased){ 17 | this.<%= prefix %>frequency += <%= prefix %>shift; 18 | }else{ 19 | this.<%= prefix %>frequency -= <%= prefix %>shift; 20 | % if isFirstChannel 21 | this.<%= prefix %>frequency--; 22 | % end 23 | } 24 | } 25 | }else{ 26 | this.<%= prefix %>sweepCounter--; 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/vm_audio_rectangle_runq.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | % isFirstChannel = args[:isFirstChannel] 3 | % prefix=isFirstChannel ? "__rectangle0__" : "__rectangle1__" 4 | 5 | if(this.<%= prefix %>decayCounter === 0){ 6 | this.<%= prefix %>decayCounter = this.<%= prefix %>volumeOrDecayRate; 7 | if(this.<%= prefix %>decayVolume === 0){ 8 | if(this.<%= prefix %>loopEnabled){ 9 | this.<%= prefix %>decayVolume = 0xf; 10 | } 11 | }else{ 12 | this.<%= prefix %>decayVolume--; 13 | } 14 | }else{ 15 | this.<%= prefix %>decayCounter--; 16 | } 17 | if(this.<%= prefix %>decayReloaded){ 18 | this.<%= prefix %>decayReloaded = false; 19 | this.<%= prefix %>decayVolume = 0xf; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/vm_audio_run.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | this.__audio__frameCnt += (__vm__clockDelta * <%= Audio::FRAME_IRQ_RATE %>); 3 | while(this.__audio__frameCnt >= <%= Audio::AUDIO_CLOCK %>){ 4 | this.__audio__frameCnt -= <%= Audio::AUDIO_CLOCK %>; 5 | if(this.__audio__isNTSCmode){ 6 | this.__audio__frameIRQCnt ++; 7 | switch(this.__audio__frameIRQCnt){ 8 | case 1: 9 | // 10 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 11 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 12 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 13 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 14 | break; 15 | case 2: 16 | // 17 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 18 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 19 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 20 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 21 | // 22 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>false %> 23 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>true %> 24 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runh.erb.js")), :isFirstChannel=>true %> 25 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runh.erb.js")), :isFirstChannel=>true %> 26 | break; 27 | case 3: 28 | // 29 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 30 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 31 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 32 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 33 | break; 34 | case 4: 35 | // 36 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 37 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 38 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 39 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 40 | // 41 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>false %> 42 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>true %> 43 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runh.erb.js")), :isFirstChannel=>true %> 44 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runh.erb.js")), :isFirstChannel=>true %> 45 | if(this.__audio__frameIRQenabled){ 46 | <%= CPU::ReserveIRQ(CPU::IRQ::FRAMECNT) %> 47 | } 48 | this.__audio__frameIRQCnt = 0; 49 | break; 50 | default: 51 | throw new cycloa.err.CoreException("FIXME Audio::run interrupt NTSC"); 52 | } 53 | }else{ 54 | this.__audio__frameIRQCnt ++; 55 | switch(this.__audio__frameIRQCnt){ 56 | case 1: 57 | // 58 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 59 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 60 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 61 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 62 | break; 63 | case 2: 64 | // 65 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 66 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 67 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 68 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 69 | // 70 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>false %> 71 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>true %> 72 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runh.erb.js")), :isFirstChannel=>true %> 73 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runh.erb.js")), :isFirstChannel=>true %> 74 | break; 75 | case 3: 76 | // 77 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 78 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 79 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 80 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 81 | break; 82 | case 4: 83 | break; 84 | case 5: 85 | // 86 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>false %> 87 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runq.erb.js")), :isFirstChannel=>true %> 88 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runq.erb.js")), :isFirstChannel=>true %> 89 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runq.erb.js")), :isFirstChannel=>true %> 90 | // 91 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>false %> 92 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_runh.erb.js")), :isFirstChannel=>true %> 93 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_runh.erb.js")), :isFirstChannel=>true %> 94 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_runh.erb.js")), :isFirstChannel=>true %> 95 | this.__audio__frameIRQCnt = 0; 96 | break; 97 | default: 98 | throw new cycloa.err.CoreException("FIXME Audio::run interrupt PAL"); 99 | } 100 | } 101 | } 102 | this.__audio__clockCnt += (__vm__clockDelta * <%= Audio::SAMPLE_RATE %>); 103 | while(this.__audio__clockCnt >= <%= Audio::AUDIO_CLOCK %>){ 104 | /*unsigned int*/var __audio__processClock = <%= Audio::AUDIO_CLOCK %> + this.__audio__leftClock; 105 | /*unsigned int*/var __audio__delta = (__audio__processClock / <%= Audio::SAMPLE_RATE %>) | 0; 106 | this.__audio__leftClock = __audio__processClock % <%= Audio::SAMPLE_RATE %>; 107 | this.__audio__clockCnt-= <%= Audio::AUDIO_CLOCK %>; 108 | /*int16_t*/ var __audio__sound = 0; 109 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_create.erb.js")), :isFirstChannel=>false %> 110 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_rectangle_create.erb.js")), :isFirstChannel=>true %> 111 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_triangle_create.erb.js")), :isFirstChannel=>true %> 112 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_noize_create.erb.js")), :isFirstChannel=>true %> 113 | <%= render (File.expand_path (File.dirname(__FILE__)+"/src/vm_audio_digital_create.erb.js")), :isFirstChannel=>true %> 114 | if(__audio__enabled){ 115 | __audio__data[__audio__audioFairy.dataIndex++] = __audio__sound / 100; 116 | if(__audio__audioFairy.dataIndex >= __audio__data__length){ 117 | __audio__audioFairy.onDataFilled(); 118 | __audio__data = __audio__audioFairy.data; 119 | } 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /src/vm_audio_triangle_create.erb.js: -------------------------------------------------------------------------------- 1 | if(this.__triangle__lengthCounter != 0 && this.__triangle__linearCounter != 0){ 2 | //freqが1や0だと、ここでもモデルが破綻する。FF1のOPで発生。 3 | /* unsigned int */ var __triangle__nowCounter = this.__triangle__freqCounter + __audio__delta; 4 | var __triangle__freq = this.__triangle__frequency + 1; 5 | this.__triangle__freqCounter = __triangle__nowCounter % __triangle__freq; 6 | __audio__sound += this.__triangle__waveForm[this.__triangle__streamCounter = (this.__triangle__streamCounter + (__triangle__nowCounter / __triangle__freq)) & 31]; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/vm_audio_triangle_init.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | this.__triangle__waveForm = [ 4 | 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF, 5 | 0xF,0xE,0xD,0xC,0xB,0xA,0x9,0x8,0x7,0x6,0x5,0x4,0x3,0x2,0x1,0x0 6 | ]; 7 | 8 | -------------------------------------------------------------------------------- /src/vm_audio_triangle_method.erb.js: -------------------------------------------------------------------------------- 1 | <%= MachineName %>.prototype.__triangle__onHardReset = function(){ 2 | this.__triangle__haltFlag = false; 3 | this.__triangle__enableLinearCounter = false; 4 | this.__triangle__frequency = 0; 5 | this.__triangle__linearCounterBuffer = 0; 6 | this.__triangle__linearCounter = 0; 7 | this.__triangle__lengthCounter = 0; 8 | this.__triangle__freqCounter = 0; 9 | this.__triangle__streamCounter = 0; 10 | } 11 | <%= MachineName %>.prototype.__triangle__onReset = function() 12 | { 13 | this.__triangle__onHardReset(); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/vm_audio_triangle_runh.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | if(this.__triangle__lengthCounter != 0 && !this.__triangle__enableLinearCounter){ 4 | this.__triangle__lengthCounter--; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/vm_audio_triangle_runq.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | if(this.__triangle__haltFlag){ 4 | this.__triangle__linearCounter = this.__triangle__linearCounterBuffer; 5 | }else if(this.__triangle__linearCounter != 0){ 6 | this.__triangle__linearCounter--; 7 | } 8 | if(!this.__triangle__enableLinearCounter){ 9 | this.__triangle__haltFlag = false; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/vm_cpu_init.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | /** @type {number} */ 4 | this.A = 0; 5 | /** @type {number} */ 6 | this.X = 0; 7 | /** @type {number} */ 8 | this.Y = 0; 9 | /** @type {number} */ 10 | this.PC = 0; 11 | /** @type {number} */ 12 | this.SP = 0; 13 | /** @type {number} */ 14 | this.P = 0; 15 | /** 16 | * @const 17 | * @type {Uint8Array} 18 | */ 19 | this.__cpu__ram = new Uint8Array(new ArrayBuffer(0x800)); 20 | this.__cpu__rom = new Array(32); 21 | 22 | this.__cpu__ZNFlagCache = <%= MachineName %>.ZNFlagCache; 23 | this.__cpu__TransTable = <%= MachineName %>.TransTable; 24 | 25 | -------------------------------------------------------------------------------- /src/vm_cpu_method.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | <%= MachineName %>.prototype.onHardResetCPU = function(){ 4 | //from http://wiki.nesdev.com/w/index.php/CPU_power_up_state 5 | this.P = 0x24; 6 | this.A = 0x0; 7 | this.X = 0x0; 8 | this.Y = 0x0; 9 | this.SP = 0xfd; 10 | <%= CPU::MemWrite("0x4017", "0x00") %> 11 | <%= CPU::MemWrite("0x4015", "0x00") %> 12 | //this.PC = (this.read(0xFFFC) | (this.read(0xFFFD) << 8)); 13 | this.PC = (this.__cpu__rom[31][0x3FC]| (this.__cpu__rom[31][0x3FD] << 8)); 14 | 15 | }; 16 | 17 | <%= MachineName %>.prototype.onResetCPU = function () { 18 | //from http://wiki.nesdev.com/w/index.php/CPU_power_up_state 19 | //from http://crystal.freespace.jp/pgate1/nes/nes_cpu.htm 20 | this.__vm__reservedClockDelta += <%= CPU::ResetClock %>; 21 | this.SP -= 0x03; 22 | this.P |= <%= Opcode::Flag[:I] %>; 23 | <%= CPU::MemWrite("0x4015", "0x00") %> 24 | //this.PC = (read(0xFFFC) | (read(0xFFFD) << 8)); 25 | this.PC = (this.__cpu__rom[31][0x3FC]| (this.__cpu__rom[31][0x3FD] << 8)); 26 | }; 27 | 28 | <%= MachineName %>.ZNFlagCache = new Uint8Array([ 29 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 38 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 39 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 40 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 41 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 42 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 43 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 44 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 45 | ]); 46 | 47 | <%= MachineName %>.TransTable = new Uint32Array(<%= CPU::Middle::TransTable %>); 48 | 49 | -------------------------------------------------------------------------------- /src/vm_cpu_run.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | this.P |= 32; //必ずセットしてあるらしい。プログラム側から無理にいじっちゃった時用 4 | 5 | <%= CPU::Init() %> 6 | <%= Video::Init() %> 7 | 8 | if(this.NMI){ 9 | //from http://crystal.freespace.jp/pgate1/nes/nes_cpu.htm 10 | //from http://nesdev.parodius.com/6502_cpu.txt 11 | <%= CPU::ConsumeClock '7' %>; 12 | this.P &= <%= ((~Opcode::Flag[:B]) & 0xff).to_s %>; 13 | <%= CPU::Push "this.PC >> 8" %> 14 | <%= CPU::Push "this.PC" %> 15 | <%= CPU::Push "this.P" %>; 16 | this.P |= <%= Opcode::Flag[:I] %>; 17 | //this.PC = (this.read(0xFFFA) | (this.read(0xFFFB) << 8)); 18 | this.PC = (this.__cpu__rom[31][0x3FA]| (this.__cpu__rom[31][0x3FB] << 8)); 19 | this.NMI = false; 20 | }else if(this.IRQ){ 21 | this.onIRQ(); 22 | //from http://crystal.freespace.jp/pgate1/nes/nes_cpu.htm 23 | //from http://nesdev.parodius.com/6502_cpu.txt 24 | if((this.P & <%= Opcode::Flag[:I] %>) !== <%= Opcode::Flag[:I] %>){ 25 | <%= CPU::ConsumeClock '7' %>; 26 | this.P &= <%= ((~Opcode::Flag[:B]) & 0xff).to_s %>; 27 | <%= CPU::Push "this.PC >> 8" %> 28 | <%= CPU::Push "this.PC" %> 29 | <%= CPU::Push "this.P" %> 30 | this.P |= <%= Opcode::Flag[:I] %>; 31 | //this.PC = (this.read(0xFFFE) | (this.read(0xFFFF) << 8)); 32 | this.PC = (this.__cpu__rom[31][0x3FE] | (this.__cpu__rom[31][0x3FF] << 8)); 33 | } 34 | } 35 | 36 | if(this.needStatusRewrite){ 37 | this.P = this.newStatus; 38 | this.needStatusRewrite = false; 39 | } 40 | 41 | <%= CPU::AddrMode::Init() %> 42 | 43 | var __cpu__opbyte; 44 | <%= CPU::MemRead("__cpu__pc", "__cpu__opbyte") %> 45 | /** 46 | * @const 47 | * @type {number} 48 | */ 49 | var __cpu__inst = __cpu__TransTable[__cpu__opbyte]; 50 | // http://www.llx.com/~nparker/a2/opcodes.html 51 | switch( __cpu__inst & <%= CPU::Middle::AddrModeMask %> ){ 52 | % CPU::Middle::AddrMode.each do |addr, code| 53 | case <%= code %>: { /* <%= addr %> */ 54 | <%= CPU::AddrMode::method(addr).call %> 55 | break; 56 | } 57 | % end 58 | default: { throw new cycloa.err.CoreException("Invalid opcode."); } 59 | } 60 | switch( (__cpu__inst & <%= CPU::Middle::InstModeMask %>) >> 4 ){ 61 | % CPU::Middle::InstMode.each do |opsym, code| 62 | case <%= code>>4 %>: { /* <%= opsym %> */ 63 | <%= CPU::Inst::method(opsym).call %> 64 | break;} 65 | % end 66 | } 67 | <%= CPU::ConsumeClock "__cpu__inst >> #{CPU::Middle::ClockShift}" %> 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/vm_mapper_init.erb.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ledyba/CycloaJS/b8a49917b459ca3fc76e397275c2bb7c547a8184/src/vm_mapper_init.erb.js -------------------------------------------------------------------------------- /src/vm_mapper_method.erb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * マッパーごとの初期化関数 3 | */ 4 | <%= MachineName %>.Mapper = []; 5 | <%= MachineName %>.Mapper[0] = function(self){ 6 | self.__mapper__writeMapperCPU = function(/* uint8_t */ addr){ 7 | /*do nothing!*/ 8 | }; 9 | var idx = 0; 10 | for(var i=0; i<32; ++i){ 11 | self.__cpu__rom[i] = self.__mapper__prgRom.subarray(idx, idx+=<%= NES::PRG_ROM_BLOCK_SIZE %>); 12 | if(idx >= self.__mapper__prgRom.length){ 13 | idx = 0; 14 | } 15 | } 16 | var cidx = 0; 17 | for(var i=0;i<0x10; ++i){ 18 | self.__video__pattern[i] = self.__mapper__chrRom.subarray(cidx, cidx += <%= NES::CHR_ROM_BLOCK_SIZE %>); 19 | } 20 | }; 21 | 22 | /** 23 | * __cpu__romを解析してマッパーの初期化などを行う 24 | * @param {ArrayBuffer} __cpu__rom 25 | */ 26 | <%= MachineName %>.prototype.load = function(rom){ 27 | this.__mapper__parseROM(rom); 28 | // マッパー関数のインジェクション 29 | var mapperInit = <%= MachineName %>.Mapper[this.__mapper__mapperNo]; 30 | if(!mapperInit){ 31 | throw new cycloa.err.NotSupportedException("Not supported mapper: "+this.__mapper__mapperNo); 32 | } 33 | mapperInit(this); 34 | this.__video__changeMirrorType(this.__mapper__mirrorType); 35 | }; 36 | 37 | /** 38 | * __cpu__romをパースしてセットする 39 | * @param {ArrayBuffer} data 40 | */ 41 | <%= MachineName %>.prototype.__mapper__parseROM = function(data){ 42 | var data8 = new Uint8Array(data); 43 | /* check NES data8 */ 44 | if(!(data8[0] === 0x4e && data8[1]===0x45 && data8[2]===0x53 && data8[3] == 0x1a)){ 45 | throw new cycloa.err.CoreException("[FIXME] Invalid header!!"); 46 | } 47 | this.__mapper__prgSize = <%= NES::PRG_ROM_PAGE_SIZE %> * data8[4]; 48 | this.__mapper__chrSize = <%= NES::CHR_ROM_PAGE_SIZE %> * data8[5]; 49 | this.__mapper__prgPageCnt = data8[4]; 50 | this.__mapper__chrPageCnt = data8[5]; 51 | this.__mapper__mapperNo = ((data8[6] & 0xf0)>>4) | (data8[7] & 0xf0); 52 | this.__mapper__trainerFlag = (data8[6] & 0x4) === 0x4; 53 | this.__mapper__sramFlag = (data8[6] & 0x2) === 0x2; 54 | if((data8[6] & 0x8) == 0x8){ 55 | this.__mapper__mirrorType = <%= NES::FOUR_SCREEN %>; 56 | }else{ 57 | this.__mapper__mirrorType = (data8[6] & 0x1) == 0x1 ? <%= NES::VERTICAL %> : <%= NES::HORIZONTAL %>; 58 | } 59 | /** 60 | * @type {number} uint32_t 61 | */ 62 | var fptr = 0x10; 63 | if(this.__mapper__trainerFlag){ 64 | if(fptr + <%= NES::TRAINER_SIZE %> > data.byteLength) throw new cycloa.err.CoreException("[FIXME] Invalid file size; too short!"); 65 | this.__mapper__trainer = new Uint8Array(data, fptr, <%= NES::TRAINER_SIZE %>); 66 | fptr += <%= NES::TRAINER_SIZE %>; 67 | } 68 | /* read PRG __cpu__rom */ 69 | if(fptr + this.__mapper__prgSize > data.byteLength) throw new cycloa.err.CoreException("[FIXME] Invalid file size; too short!"); 70 | this.__mapper__prgRom = new Uint8Array(data, fptr, this.__mapper__prgSize); 71 | fptr += this.__mapper__prgSize; 72 | 73 | if(fptr + this.__mapper__chrSize > data.byteLength) throw new cycloa.err.CoreException("[FIXME] Invalid file size; too short!"); 74 | else if(fptr + this.__mapper__chrSize < data.byteLength) throw cycloa.err.CoreException("[FIXME] Invalid file size; too long!"); 75 | 76 | this.__mapper__chrRom = new Uint8Array(data, fptr, this.__mapper__chrSize); 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /src/vm_pad_init.erb.js: -------------------------------------------------------------------------------- 1 | this.__pad__pad1Fairy = pad1Fairy || new cycloa.AbstractPadFairy(); 2 | this.__pad__pad2Fairy = pad2Fairy || new cycloa.AbstractPadFairy(); 3 | 4 | this.__pad__pad1Idx = 0; 5 | this.__pad__pad2Idx = 0; 6 | -------------------------------------------------------------------------------- /src/vm_video_init.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | this.__video__videoFairy = videoFairy; 4 | 5 | this.__video__isEven = false; 6 | this.__video__nowY = 0; 7 | this.__video__nowX = 0; 8 | this.__video__spriteHitCnt = 0; 9 | this.__video__executeNMIonVBlank = false; 10 | this.__video__spriteHeight = 8; 11 | this.__video__patternTableAddressBackground = 0; 12 | this.__video__patternTableAddress8x8Sprites = 0; 13 | this.__video__vramIncrementSize = 1; 14 | this.__video__colorEmphasis = 0; 15 | this.__video__spriteVisibility = false; 16 | this.__video__backgroundVisibility = false; 17 | this.__video__spriteClipping = false; 18 | this.__video__backgroundClipping = false; 19 | this.__video__paletteMask = 0; 20 | this.__video__nowOnVBnank = false; 21 | this.__video__sprite0Hit = false; 22 | this.__video__lostSprites = false; 23 | this.__video__vramBuffer = 0; 24 | this.__video__spriteAddr = 0; 25 | this.__video__vramAddrRegister = 0x0; 26 | this.__video__vramAddrReloadRegister = 0; 27 | this.__video__horizontalScrollBits = 0; 28 | this.__video__scrollRegisterWritten = false; 29 | this.__video__vramAddrRegisterWritten = false; 30 | this.__video__screenBuffer = new ArrayBuffer(<%= Video::ScreenWidth %> * <%= Video::ScreenHeight %>); 31 | this.__video__screenBuffer8 = new Uint8Array(this.__video__screenBuffer); 32 | this.__video__screenBuffer32 = new Uint32Array(this.__video__screenBuffer); 33 | this.__video__spRam = new Uint8Array(256); 34 | this.__video__palette = new Uint8Array(9*4); 35 | this.__video__spriteTable = new Array(<%= Video::DefaultSpriteCnt %>); 36 | for(var i=0; i< <%= Video::DefaultSpriteCnt %>; ++i){ 37 | this.__video__spriteTable[i] = {}; 38 | } 39 | 40 | this.__video__pattern = new Array(0x10); 41 | 42 | this.__video__vramMirroring = new Array(4); 43 | this.__video__internalVram = new Array(4); 44 | for(var i=0;i<4;++i){ 45 | this.__video__internalVram[i] = new Uint8Array(0x400); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/vm_video_run.erb.js: -------------------------------------------------------------------------------- 1 | %# -*- encoding: utf-8 -*- 2 | 3 | this.__video__nowX += __vm__clockDelta * <%= Video::ClockFactor %>; 4 | while(this.__video__nowX >= 341){ 5 | this.__video__nowX -= 341; 6 | /** 7 | * @const 8 | * @type {number} 9 | */ 10 | var __video__nowY = (++this.__video__nowY); 11 | if(__video__nowY <= 240){ 12 | /** 13 | * @const 14 | * @type {Uint8Array} 15 | */ 16 | this.__video__spriteEval(); 17 | if(this.__video__backgroundVisibility || this.__video__spriteVisibility) { 18 | // from http://nocash.emubase.de/everynes.htm#pictureprocessingunitppu 19 | this.__video__vramAddrRegister = (this.__video__vramAddrRegister & 0x7BE0) | (this.__video__vramAddrReloadRegister & 0x041F); 20 | this.__video__buildBgLine(); 21 | this.__video__buildSpriteLine(); 22 | var __video__vramAddrRegister = this.__video__vramAddrRegister + (1 << 12); 23 | __video__vramAddrRegister += (__video__vramAddrRegister & 0x8000) >> 10; 24 | __video__vramAddrRegister &= 0x7fff; 25 | if((__video__vramAddrRegister & 0x03e0) === 0x3c0){ 26 | __video__vramAddrRegister &= 0xFC1F; 27 | __video__vramAddrRegister ^= 0x800; 28 | } 29 | this.__video__vramAddrRegister = __video__vramAddrRegister; 30 | } 31 | }else if(__video__nowY === 241){ 32 | //241: The PPU just idles during this scanline. Despite this, this scanline still occurs before the VBlank flag is set. 33 | this.__video__videoFairy.dispatchRendering(__video__screenBuffer8, this.__video__paletteMask); 34 | __vm__run = false; 35 | this.__video__nowOnVBnank = true; 36 | this.__video__spriteAddr = 0;//and typically contains 00h at the begin of the VBlank periods 37 | }else if(__video__nowY === 242){ 38 | // NESDEV: These occur during VBlank. The VBlank flag of the PPU is pulled low during scanline 241, so the VBlank NMI occurs here. 39 | // EVERYNES: http://nocash.emubase.de/everynes.htm#ppudimensionstimings 40 | // とあるものの…BeNesの実装だともっと後に発生すると記述されてる。詳しくは以下。 41 | // なお、$2002のレジスタがHIGHになった後にVBLANKを起こさないと「ソロモンの鍵」にてゲームが始まらない。 42 | // (NMI割り込みがレジスタを読み込みフラグをリセットしてしまう上、NMI割り込みが非常に長く、クリアしなくてもすでにVBLANKが終わった後に返ってくる) 43 | //nowOnVBlankフラグの立ち上がり後、数クロックでNMIが発生。 44 | this.NMI = this.__video__executeNMIonVBlank; /* reserve NMI if emabled */ 45 | this.onVBlank(); 46 | }else if(__video__nowY <= 261){ 47 | //nowVBlank. 48 | }else if(__video__nowY === 262){ 49 | this.__video__nowOnVBnank = false; 50 | this.__video__sprite0Hit = false; 51 | this.__video__nowY = 0; 52 | if(!this.__video__isEven){ 53 | this.__video__nowX++; 54 | } 55 | this.__video__isEven = !this.__video__isEven; 56 | // the reload value is automatically loaded into the Pointer at the end of the vblank period (vertical reload bits) 57 | // from http://nocash.emubase.de/everynes.htm#pictureprocessingunitppu 58 | if(this.__video__backgroundVisibility || this.__video__spriteVisibility){ 59 | this.__video__vramAddrRegister = (this.__video__vramAddrRegister & 0x041F) | (this.__video__vramAddrReloadRegister & 0x7BE0); 60 | } 61 | }else{ 62 | throw new cycloa.err.CoreException("Invalid scanline: "+this.__video__nowY); 63 | } 64 | } 65 | 66 | --------------------------------------------------------------------------------