diff --git a/BitCycle/bitcycle.css b/BitCycle/bitcycle.css index e212cac..af780f9 100644 --- a/BitCycle/bitcycle.css +++ b/BitCycle/bitcycle.css @@ -47,9 +47,12 @@ .green { color: #0c0; } +.hide { + display: none; +} h1 { text-align: center; - font-family: Consolas, 'Courier New' ,monospace; + font-family: Consolas, 'Courier New', monospace; } a { text-decoration: none; diff --git a/BitCycle/bitcycle.js b/BitCycle/bitcycle.js index 527252b..a4d68bf 100644 --- a/BitCycle/bitcycle.js +++ b/BitCycle/bitcycle.js @@ -10,9 +10,12 @@ const ZERO_BIT = 1; const ONE_BIT = 2; const BIT_SHAPE_RADIUS = 7; -const GRID_SQUARE_SIZE = 16; +const GRID_SQUARE_SIZE = 24; const GRID_FONT_SIZE = 14; +const DEFAULT_TICKS_PER_SECOND = 10; +const DEFAULT_FRAMES_PER_TICK = 1; + const BLUE = "#ABF"; const GREEN = "#6E6"; const TEAL = "#4A9"; @@ -177,14 +180,14 @@ function Source(x, y, inputString, ioFormat) { } else if (ioFormat === "signed") { inputString = inputString.split(",").map(intToSignedUnary).join("0"); } - this.queue = inputString.split(""); + this.queue = inputString.split("").map(value => new Bit(this.x, this.y, EAST, value)); this.open = (this.queue.length > 0); } Source.prototype.tick = function() { var outBit = null; if (this.open) { - outBit = new Bit(this.x, this.y, EAST, this.queue.shift()); + outBit = this.queue.shift(); if (this.queue.length === 0) { this.open = false; } @@ -255,10 +258,12 @@ Device.prototype.toString = function() { } // Define Program class -function Program(codeLines, inputLines, speed, ioFormat, expand) { +function Program(codeLines, inputLines, ticksPerSecond, ioFormat, expand) { var sinkNumber = 0; - this.setSpeed(speed); + const framesPerTick = DEFAULT_FRAMES_PER_TICK; // TODO: let user set this + this.setSpeed(ticksPerSecond, framesPerTick); + this.frame = 0; this.done = false; this.paused = true; @@ -344,18 +349,34 @@ function Program(codeLines, inputLines, speed, ioFormat, expand) { } } -Program.prototype.setSpeed = function(speed) { - this.speed = +speed || 10; +Program.prototype.setSpeed = function(ticksPerSecond, framesPerTick) { + this.ticksPerSecond = +ticksPerSecond || this.ticksPerSecond || DEFAULT_TICKS_PER_SECOND; + this.framesPerTick = +framesPerTick || this.framesPerTick || DEFAULT_FRAMES_PER_TICK; + this.speed = this.ticksPerSecond * this.framesPerTick; } Program.prototype.run = function() { this.paused = false; - this.tick(); + this.step(); if (!this.done) { this.timeout = window.setTimeout(this.run.bind(this), 1000 / this.speed); } } +Program.prototype.step = function() { + // Step one frame forward + this.frame++; + + if (this.frame === this.framesPerTick) { + // Move the program state forward one tick and display the current + // state of the playfield + this.tick(); + } else { + // Display the current state of the playfield + this.displayPlayfield(); + } +} + Program.prototype.tick = function() { if (this.done) { haltProgram(); @@ -539,7 +560,8 @@ Program.prototype.tick = function() { } // Display the current state of the playfield - displaySource(this.grid, this.activeBits); + this.frame = 0; + this.displayPlayfield(); } Program.prototype.reset = function() { @@ -578,21 +600,26 @@ Program.prototype.halt = function() { } } -function displaySource(grid, activeBits) { +Program.prototype.displayPlayfield = function() { clearCanvas(); - // Attach active bits to the devices at those coordinates - for (var b = 0; b < activeBits.length; b++) { - var bit = activeBits[b]; - grid[bit.y][bit.x].bitCode |= (bit.value ? ONE_BIT : ZERO_BIT); - } - // Display active bits and devices - for (var y = 0; y < grid.length; y++) { - var row = grid[y]; + // Display all zero bits on the playfield first + for (var b = 0; b < this.activeBits.length; b++) { + if (this.activeBits[b].value === 0) { + drawBitOffset(this.activeBits[b], this.frame / this.framesPerTick ); + } + } + // Then display all one bits on the playfield + for (var b = 0; b < this.activeBits.length; b++) { + if (this.activeBits[b].value === 1) { + drawBitOffset(this.activeBits[b], this.frame / this.framesPerTick ); + } + } + // Then display devices + for (var y = 0; y < this.grid.length; y++) { + var row = this.grid[y]; for (var x = 0; x < row.length; x++) { var device = row[x]; - drawBitsAt(device.bitCode, x, y); drawDeviceAt(device, x, y); - device.bitCode = 0; } } } @@ -603,17 +630,24 @@ function clearCanvas() { } } -function drawBitsAt(bitCode, x, y) { +function drawBitOffset(bit, offsetAmount) { + var x = bit.x + dx(bit.direction) * offsetAmount; + var y = bit.y + dy(bit.direction) * offsetAmount; + var bitCode = (bit.value ? ONE_BIT : ZERO_BIT); + drawBitsAt(bitCode, x, y); +} + +function drawBitsAt(bitCode, x, y, scale=1) { if (bitCode > 0) { var centerX = (x + 0.5) * GRID_SQUARE_SIZE; var centerY = (y + 0.5) * GRID_SQUARE_SIZE; if (bitCode === ZERO_BIT) { - drawCircle(centerX, centerY, BIT_SHAPE_RADIUS, BLUE); + drawCircle(centerX, centerY, BIT_SHAPE_RADIUS * scale, BLUE); } else if (bitCode === ONE_BIT) { - drawDiamond(centerX, centerY, BIT_SHAPE_RADIUS, GREEN); + drawDiamond(centerX, centerY, BIT_SHAPE_RADIUS * scale, GREEN); } else { // Both a zero and a one bit - drawCircle(centerX, centerY, BIT_SHAPE_RADIUS, BLUE); - drawDiamond(centerX, centerY, BIT_SHAPE_RADIUS, GREEN); + drawCircle(centerX, centerY, BIT_SHAPE_RADIUS * scale, BLUE); + drawDiamond(centerX, centerY, BIT_SHAPE_RADIUS * scale, GREEN); } } } @@ -640,6 +674,18 @@ function drawDeviceAt(device, x, y) { var textY = (y + 0.5) * GRID_SQUARE_SIZE + 0.25 * GRID_FONT_SIZE; context.fillStyle = BLACK; context.fillText(device.toString(), textX, textY); + + if (device instanceof Collector || device instanceof Source) { + for (let i = 0; i < Math.min(device.queue.length, 6); i++) { + let bit = device.queue[i]; + drawBitsAt( + (bit.value ? ONE_BIT : ZERO_BIT), + x - (1 / 7 + i / 7) + 0.5, + y + 1 / 7 - 0.5, + 0.25 + ); + } + } } function urlDecode(value) { @@ -777,6 +823,19 @@ function hideEditor() { executionControls.style.display = "block"; } +function toggleCheatSheet() { + var cheatSheet = document.getElementById('cheat-sheet'), + indicator = document.getElementById('cheat-sheet-indicator'); + + if (cheatSheet.classList.contains("hide")) { + cheatSheet.classList.remove("hide"); + indicator.innerText = "-"; + } else { + cheatSheet.classList.add("hide"); + indicator.innerText = "+"; + } +} + function loadProgram() { var sourceCode = document.getElementById('source'), ticksPerSecond = document.getElementById('ticks-per-second'), @@ -803,7 +862,7 @@ function loadProgram() { context.font = "bold " + GRID_FONT_SIZE + "px Courier New"; // Display the current state of the playfield - displaySource(program.grid, program.activeBits); + program.displayPlayfield(); runPause.style.display = "block"; step.style.display = "block"; @@ -853,7 +912,7 @@ function runPauseBtnClick() { if (program !== null && !program.done) { if (program.paused) { var ticksPerSecond = document.getElementById('ticks-per-second'); - program.setSpeed(ticksPerSecond.innerText); // TBD: is innerText the best way to do this? + program.setSpeed(ticksPerSecond.innerText); runPause.value = "Pause"; program.run(); } else { @@ -867,7 +926,7 @@ function stepBtnClick() { var runPause = document.getElementById('run-pause'); if (program !== null && !program.done) { program.pause(); - program.tick(); + program.step(); runPause.value = "Run"; } } diff --git a/BitCycle/index.html b/BitCycle/index.html index 832e226..13d098f 100644 --- a/BitCycle/index.html +++ b/BitCycle/index.html @@ -2,7 +2,8 @@
+
+