mkweb
PDP-8 Emulator written in Javascipt with HTML5 Canvas based frontend.
const KEYBOARD_BUFSIZE = 3; // # of chars
const PRINTER_BUFSIZE = 4096; // # of chars
const KEYBOARD_BUFFER = [];
const PRINTER_BUFFER = [];
class Teletype extends Phaser.Scene {
TelePrinter = null;
constructor () {
super('teletype');
}
preload () {
this.load.image('retrofont', 'assets/5x7.png');
}
create () {
let background = this.add.graphics();
background.fillStyle(0xffffff); // White background
background.fillRect(0,0,1200,700);
const stand = this.add.graphics();
// Top of PDP-8
stand.fillStyle(0x0);
stand.fillTriangle(0,700, 600,0, 1200,700)
// Stand
stand.fillGradientStyle(0xACA688, 0xcac29f, 0xACA688, 0xACA688); // light to dark beige top to bottom
stand.fillTriangle(105, 650, 600, 0, 1095, 650);
//Highlight
stand.fillStyle(0xe8e3cb);
stand.fillRoundedRect(100,648,1000,50,5);
// Base
stand.fillStyle(0xcac29f);
stand.fillRoundedRect(100,650,1000,50,5);
let backdrop = this.add.graphics();
backdrop.fillGradientStyle(0x827e6d, 0x827e6d, 0xcac29f, 0xcac29f); // Dark to light beige top to bottom
backdrop.fillRect(0,0,1200,600);
// White hightlights in corners
this.add.ellipse(65,50,150,25, 0xe8e3cb, 0.2).setAngle(35); // Top left
this.add.ellipse(1135,50,150,25, 0xe8e3cb, 0.2).setAngle(-35); // Top right
this.add.ellipse(65,550,150,25, 0xe8e3cb, 0.4).setAngle(-35); // Bottom left
this.add.ellipse(1135,550,150,25, 0xe8e3cb, 0.4).setAngle(35); // Bottom right
let screen = this.add.graphics();
screen.fillStyle(0x0); // Black
screen.fillRoundedRect(95,70,1010,460,20); // Base screen
screen.fillEllipse(96,300,20,429); // Left side bulge
screen.fillEllipse(1104,300,20,429); // Right side bulge
screen.fillEllipse(600,70,980,20); // Top Bulge
screen.fillEllipse(600,530,980,20); // Bottom Bulge
screen.fillStyle(0x6f7374); // Grey
screen.fillRoundedRect(100,75,1000,450,20); // Base screen
screen.fillEllipse(101,300,20,419); // Left side bulge
screen.fillEllipse(1099,300,20,419); // Right side bulge
screen.fillEllipse(600,75,970,20); // Top Bulge
screen.fillEllipse(600,525,970,20); // Bottom Bulge
let bezel = this.add.graphics();
// Rounded bezel outside
bezel.lineStyle(20,0xffffff); // 20 px width in white
bezel.strokeRect(10, 10, 1180, 580); // background
bezel.lineStyle(20, 0xcac29f); // 20px stroke width in light beige
bezel.strokeRoundedRect(10, 10, 1180, 580, {tl:32,tr:32, bl:1,br:1}); // Outer bezel radius
bezel.strokeRoundedRect(10, 10, 1180, 580, 32); // Inner bezel radius
bezel.lineStyle(5, 0xe8e3cb, 0.5); // 5px stroke width in highlight color 50% opacity
bezel.strokeRoundedRect(20, 20, 1160, 560, 22);
const fcfg = {
image: 'retrofont',
width: 6,
height: 8,
chars: Phaser.GameObjects.RetroFont.TEXT_SET1,
charsPerRow: 18,
offset: { x: 1, y: 1 },
spacing: { x: 1, y: 1 },
lineSpacing: 1
};
const retrofontConfig = Phaser.GameObjects.RetroFont.Parse(this, fcfg);
this.cache.bitmapFont.add('retrofont', retrofontConfig);
this.TelePrinter = this.add.bitmapText(120, 86, 'retrofont', '');
this.TelePrinter.setScale(2);
/// Keyboard Events ///
this.input.keyboard.on('keydown', event =>
{
if (event.key == ' ') {
// Prevent space from scrolling down screen (default behavior)
event.preventDefault();
}
if (KEYBOARD_BUFFER.length >= KEYBOARD_BUFSIZE) {
// Keyboard buffer is full
// We should beep or something
} else if (event.key.length == 1) {
let charCode = event.key.charCodeAt(0);
let keypress = false; // true if we recorded a keypress
if (event.ctrlKey) { // If ctrl key is down it eats all keystrokes.
if (event.key == ' ') {
// Ctrl + Space executes single instruction cycle
COMPUTER.cycle();
}
} else if (charCode >= 32 && charCode <= 126) { // Printable ascii characters
// Add character to keyboard buffer
KEYBOARD_BUFFER.push(charCode);
keypress = true;
}
} else if (event.key == "Enter") {
if (event.ctrlKey) {
// Ctrl + Enter starts the computer
COMPUTER.start();
} else {
// Enter emits a CR
KEYBOARD_BUFFER.push("\r".charCodeAt(0));
}
} else if (event.key == "Backspace") {
if (event.ctrlKey) {
// Ctrl + Backspace halts computer
COMPUTER.halt();
} else {
// Backspace emits a ASCII BS (8)
KEYBOARD_BUFFER.push("\b".charCodeAt(0));
}
} else if (event.key == "Escape") {
if (event.ctrlKey) {
// Ctrl + Esc resets computer
// Power cycle should halt if not, clear registers, and load reset vector
// Also clear teletype screen on reset
COMPUTER.off();
this.TelePrinter.text = '';
COMPUTER.on();
}
} else if (event.key == 'Delete') {
// Ctrl + Delete clears teletype screen
if (event.ctrlKey) {
this.TelePrinter.text = '';
}
}
});
/// Pointer Events ///
this.input.on('pointerdown', function () {
document.getElementById("keyboardtrigger").focus();
});
}
update () {
let bufLen = PRINTER_BUFFER.length;
if (bufLen > 0) {
// Display characters in printer buffer
for (var i = 0; i < bufLen; i++) {
// Get length of last line
let lineLength = this.TelePrinter.text.length - this.TelePrinter.text.lastIndexOf("\n") - 1;
// Next character to print
let nextChar = PRINTER_BUFFER.shift();
// Wrap lines @ 80 characters
if (lineLength >= 80 && nextChar != '\n') {
this.TelePrinter.text += '\n';
lineLength = 0;
}
// Scroll display @ 24 lines
let lines = this.TelePrinter.text.split("\n");
if (lines.length > 24) {
lines.shift();
this.TelePrinter.setText(lines);
}
// Add character to line
this.TelePrinter.text += nextChar;
// lineLength++;
}
}
}
}