Implement the remaining logic gates and /most/ of their rendering!
This commit is contained in:
parent
ac6e33bb6c
commit
3a454d77f5
@ -428,7 +428,7 @@ const TILE_ENCODING = {
|
|||||||
// Counter, which can't be rotated
|
// Counter, which can't be rotated
|
||||||
tile.direction = 'north';
|
tile.direction = 'north';
|
||||||
tile.gate_type = 'counter';
|
tile.gate_type = 'counter';
|
||||||
tile.counter_value = modifier - 0x1e;
|
tile.memory = modifier - 0x1e;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tile.direction = ['north', 'east', 'south', 'west'][modifier & 0x03];
|
tile.direction = ['north', 'east', 'south', 'west'][modifier & 0x03];
|
||||||
|
|||||||
123
js/tileset.js
123
js/tileset.js
@ -401,14 +401,8 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
logic_gate: {
|
logic_gate: {
|
||||||
// TODO currently, 'wired' can't coexist with visual state etc...
|
special: 'logic-gate',
|
||||||
// TODO *long sigh* of course, logic gates have parts with independent current too
|
logic_gate_tiles: {
|
||||||
// for a north-facing gate with two inputs, we have:
|
|
||||||
// - north output: just a wire, doesn't include center. -r 0 w -r
|
|
||||||
// - west output: bottom left quadrant, except for the middle wire part, but including the
|
|
||||||
// horizontal wire. 0 -r -r +r
|
|
||||||
// - east output: same but includes middle wire. -r -r +r +r
|
|
||||||
// TODO check if they're flipped for latches facing the other way
|
|
||||||
'latch-ccw': {
|
'latch-ccw': {
|
||||||
north: [8, 21],
|
north: [8, 21],
|
||||||
east: [9, 21],
|
east: [9, 21],
|
||||||
@ -451,9 +445,9 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
south: [6, 26],
|
south: [6, 26],
|
||||||
west: [7, 26],
|
west: [7, 26],
|
||||||
},
|
},
|
||||||
// FIXME need to draw the number as well
|
|
||||||
counter: [14, 26],
|
counter: [14, 26],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
'#unpowered': [13, 26],
|
'#unpowered': [13, 26],
|
||||||
'#powered': [15, 26],
|
'#powered': [15, 26],
|
||||||
@ -731,6 +725,94 @@ export class Tileset {
|
|||||||
this.draw_type(tile.type.name, tile, tic, blit);
|
this.draw_type(tile.type.name, tile, tic, blit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw a "standard" drawspec, which is either:
|
||||||
|
// - a single tile: [x, y]
|
||||||
|
// - an animation: [[x0, y0], [x1, y1], ...]
|
||||||
|
// - a directional tile: { north: T, east: T, ... } where T is either of the above
|
||||||
|
_draw_standard(drawspec, tile, tic, blit, mask = []) {
|
||||||
|
// If we have an object, it must be a table of directions
|
||||||
|
let coords = drawspec;
|
||||||
|
if (!(coords instanceof Array)) {
|
||||||
|
coords = coords[(tile && tile.direction) ?? 'south'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal with animation
|
||||||
|
if (coords[0] instanceof Array) {
|
||||||
|
if (tic !== null) {
|
||||||
|
if (tile && tile.animation_speed) {
|
||||||
|
// This tile reports its own animation timing (in tics), so trust that, and just
|
||||||
|
// use the current tic's fraction.
|
||||||
|
// That said: adjusting animation speed complicates this slightly. Consider the
|
||||||
|
// player's walk animation, which takes 4 tics to complete, during which time we
|
||||||
|
// cycle through 8 frames. Playing that at half speed means only half the
|
||||||
|
// animation actually plays, but if the player continues walking, then on the
|
||||||
|
// NEXT four tics, we should play the other half. To make this work, use the
|
||||||
|
// tic as a global timer as well: if the animation started on tics 0-4, play the
|
||||||
|
// first half; if it started on tics 5-8, play the second half. They could get
|
||||||
|
// out of sync if the player hesitates, but no one will notice that, and this
|
||||||
|
// approach minimizes storing extra state.
|
||||||
|
let i = (tile.animation_progress + tic % 1) / tile.animation_speed;
|
||||||
|
// But do NOT do this for explosions or splashes, which have a fixed duration
|
||||||
|
// and only play once
|
||||||
|
if (this.animation_slowdown > 1 && ! tile.type.ttl) {
|
||||||
|
// i ranges from [0, 1), but a slowdown of N means we'll only play the first
|
||||||
|
// 1/N of it before the game ends (or loops) the animation.
|
||||||
|
// So increase by [0..N-1] to get it in some other range, then divide by N
|
||||||
|
// to scale back down to [0, 1)
|
||||||
|
i += Math.floor(tic / tile.animation_speed % this.animation_slowdown);
|
||||||
|
i /= this.animation_slowdown;
|
||||||
|
}
|
||||||
|
coords = coords[Math.floor(i * coords.length)];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// This tile animates on a global timer, one cycle every quarter of a second
|
||||||
|
coords = coords[Math.floor(tic / this.animation_slowdown % 5 / 5 * coords.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
coords = coords[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blit(coords[0], coords[1], ...mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
_draw_logic_gate(drawspec, tile, tic, blit) {
|
||||||
|
// Layer 1: wiring state
|
||||||
|
// Always draw the unpowered wire base
|
||||||
|
let unpowered_coords = this.layout['#unpowered'];
|
||||||
|
let powered_coords = this.layout['#powered'];
|
||||||
|
blit(...unpowered_coords);
|
||||||
|
if (tile && tile.cell) {
|
||||||
|
// What goes on top varies a bit...
|
||||||
|
// FIXME implement for NOT and counter!
|
||||||
|
let r = this.layout['#wire-width'] / 2;
|
||||||
|
if (tile.cell.powered_edges & DIRECTIONS[tile.direction].bit) {
|
||||||
|
// Output (on top)
|
||||||
|
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0, 0.5 + r, 0.5);
|
||||||
|
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
|
||||||
|
}
|
||||||
|
if (tile.cell.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].right].bit) {
|
||||||
|
// Right input, which includes the middle
|
||||||
|
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0.5 - r, 1, 1);
|
||||||
|
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
|
||||||
|
}
|
||||||
|
if (tile.cell.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].left].bit) {
|
||||||
|
// Left input, which does not include the middle
|
||||||
|
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0, 0.5 - r, 0.5 - r, 1);
|
||||||
|
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 2: the tile itself
|
||||||
|
this._draw_standard(drawspec.logic_gate_tiles[tile.gate_type], tile, tic, blit);
|
||||||
|
|
||||||
|
// Layer 3: counter number
|
||||||
|
if (tile.gate_type === 'counter') {
|
||||||
|
blit(0, 3, tile.memory * 0.75, 0, 0.75, 1, 0.125, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draws a tile type, given by name. Passing in a tile is optional, but
|
// Draws a tile type, given by name. Passing in a tile is optional, but
|
||||||
// without it you'll get defaults.
|
// without it you'll get defaults.
|
||||||
draw_type(name, tile, tic, blit) {
|
draw_type(name, tile, tic, blit) {
|
||||||
@ -755,6 +837,14 @@ export class Tileset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO shift everything to use this style, this is ridiculous
|
||||||
|
if (drawspec.special) {
|
||||||
|
if (drawspec.special === 'logic-gate') {
|
||||||
|
this._draw_logic_gate(drawspec, tile, tic, blit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let coords = drawspec;
|
let coords = drawspec;
|
||||||
if (drawspec.mask) {
|
if (drawspec.mask) {
|
||||||
// Some tiles (OK, just the thin walls) don't actually draw a full
|
// Some tiles (OK, just the thin walls) don't actually draw a full
|
||||||
@ -991,4 +1081,19 @@ export class Tileset {
|
|||||||
blit(sx, sy, 0, 0, 0.5, 0.5, offset, offset);
|
blit(sx, sy, 0, 0, 0.5, 0.5, offset, offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_rotate(direction, x0, y0, x1, y1) {
|
||||||
|
if (direction === 'east') {
|
||||||
|
return [1 - y1, x0, 1 - y0, x1];
|
||||||
|
}
|
||||||
|
else if (direction === 'south') {
|
||||||
|
return [1 - x1, 1 - y1, 1 - x0, 1 - y0];
|
||||||
|
}
|
||||||
|
else if (direction === 'west') {
|
||||||
|
return [y0, 1 - x1, y1, 1 - x0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [x0, y0, x1, y1];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
126
js/tiletypes.js
126
js/tiletypes.js
@ -654,6 +654,9 @@ const TILE_TYPES = {
|
|||||||
on_gray_button(me, level) {
|
on_gray_button(me, level) {
|
||||||
level.transmute_tile(me, 'green_wall');
|
level.transmute_tile(me, 'green_wall');
|
||||||
},
|
},
|
||||||
|
on_power(me, level) {
|
||||||
|
me.type.on_gray_button(me, level);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
green_wall: {
|
green_wall: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
@ -661,6 +664,9 @@ const TILE_TYPES = {
|
|||||||
on_gray_button(me, level) {
|
on_gray_button(me, level) {
|
||||||
level.transmute_tile(me, 'green_floor');
|
level.transmute_tile(me, 'green_floor');
|
||||||
},
|
},
|
||||||
|
on_power(me, level) {
|
||||||
|
me.type.on_gray_button(me, level);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
green_chip: {
|
green_chip: {
|
||||||
draw_layer: DRAW_LAYERS.item,
|
draw_layer: DRAW_LAYERS.item,
|
||||||
@ -1116,42 +1122,114 @@ const TILE_TYPES = {
|
|||||||
logic_gate: {
|
logic_gate: {
|
||||||
// gate_type: not, and, or, xor, nand, latch-cw, latch-ccw, counter, bogus
|
// gate_type: not, and, or, xor, nand, latch-cw, latch-ccw, counter, bogus
|
||||||
_gate_types: {
|
_gate_types: {
|
||||||
not: ['out', null, 'in1', null],
|
not: ['out0', null, 'in0', null],
|
||||||
and: ['out', 'in2', null, 'in1'],
|
and: ['out0', 'in0', null, 'in1'],
|
||||||
or: [],
|
or: ['out0', 'in0', null, 'in1'],
|
||||||
xor: [],
|
xor: ['out0', 'in0', null, 'in1'],
|
||||||
nand: [],
|
nand: ['out0', 'in0', null, 'in1'],
|
||||||
'latch-cw': [],
|
// in0 is the trigger, in1 is the input
|
||||||
'latch-ccw': [],
|
'latch-cw': ['out0', 'in0', null, 'in1'],
|
||||||
|
// in0 is the input, in1 is the trigger
|
||||||
|
'latch-ccw': ['out0', 'in0', null, 'in1'],
|
||||||
|
// inputs: inc, dec; outputs: overflow, underflow
|
||||||
|
counter: ['out1', 'in0', 'in1', 'out0'],
|
||||||
},
|
},
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
is_power_source: true,
|
is_power_source: true,
|
||||||
get_emitting_edges(me, level) {
|
on_ready(me, level) {
|
||||||
if (me.gate_type === 'and') {
|
me.gate_def = me.type._gate_types[me.gate_type];
|
||||||
let vars = {};
|
if (me.gate_type === 'latch-cw' || me.gate_type === 'latch-ccw') {
|
||||||
let out_bit = 0;
|
me.memory = false;
|
||||||
let dir = me.direction;
|
|
||||||
for (let name of me.type._gate_types[me.gate_type]) {
|
|
||||||
let dirinfo = DIRECTIONS[dir];
|
|
||||||
if (name === 'out') {
|
|
||||||
out_bit |= dirinfo.bit;
|
|
||||||
}
|
}
|
||||||
else if (name) {
|
else if (me.gate_type === 'counter') {
|
||||||
vars[name] = (me.cell.powered_edges & dirinfo.bit) !== 0;
|
me.memory = me.memory ?? 0;
|
||||||
|
me.incrementing = false;
|
||||||
|
me.decrementing = false;
|
||||||
|
me.underflowing = false;
|
||||||
|
me.direction = 'north';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get_emitting_edges(me, level) {
|
||||||
|
// Collect which of our edges are powered, in clockwise order starting from our
|
||||||
|
// direction, matching _gate_types
|
||||||
|
let input0 = false, input1 = false;
|
||||||
|
let output0 = false, output1 = false;
|
||||||
|
let outbit0 = 0, outbit1 = 0;
|
||||||
|
let dir = me.direction;
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
let cxn = me.gate_def[i];
|
||||||
|
let dirinfo = DIRECTIONS[dir];
|
||||||
|
if (cxn === 'in0') {
|
||||||
|
input0 = (me.cell.powered_edges & dirinfo.bit) !== 0;
|
||||||
|
}
|
||||||
|
else if (cxn === 'in1') {
|
||||||
|
input1 = (me.cell.powered_edges & dirinfo.bit) !== 0;
|
||||||
|
}
|
||||||
|
else if (cxn === 'out0') {
|
||||||
|
outbit0 = dirinfo.bit;
|
||||||
|
}
|
||||||
|
else if (cxn === 'out1') {
|
||||||
|
outbit1 = dirinfo.bit;
|
||||||
}
|
}
|
||||||
dir = dirinfo.right;
|
dir = dirinfo.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vars.in1 && vars.in2) {
|
if (me.gate_type === 'not') {
|
||||||
return out_bit;
|
output0 = ! input0;
|
||||||
}
|
}
|
||||||
else {
|
else if (me.gate_type === 'and') {
|
||||||
return 0;
|
output0 = input0 && input1;
|
||||||
|
}
|
||||||
|
else if (me.gate_type === 'or') {
|
||||||
|
output0 = input0 || input1;
|
||||||
|
}
|
||||||
|
else if (me.gate_type === 'xor') {
|
||||||
|
output0 = input0 !== input1;
|
||||||
|
}
|
||||||
|
else if (me.gate_type === 'nand') {
|
||||||
|
output0 = ! (input0 && input1);
|
||||||
|
}
|
||||||
|
else if (me.gate_type === 'latch-cw') {
|
||||||
|
if (input0) {
|
||||||
|
level._set_tile_prop(me, 'memory', input1);
|
||||||
|
}
|
||||||
|
output0 = me.memory;
|
||||||
|
}
|
||||||
|
else if (me.gate_type === 'latch-ccw') {
|
||||||
|
if (input1) {
|
||||||
|
level._set_tile_prop(me, 'memory', input0);
|
||||||
|
}
|
||||||
|
output0 = me.memory;
|
||||||
|
}
|
||||||
|
else if (me.gate_type === 'counter') {
|
||||||
|
let inc = input0 && ! me.incrementing;
|
||||||
|
let dec = input1 && ! me.decrementing;
|
||||||
|
let mem = me.memory;
|
||||||
|
if (inc || dec) {
|
||||||
|
level._set_tile_prop(me, 'underflowing', false);
|
||||||
|
}
|
||||||
|
if (inc && ! dec) {
|
||||||
|
mem++;
|
||||||
|
if (mem > 9) {
|
||||||
|
mem = 0;
|
||||||
|
output0 = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (dec && ! inc) {
|
||||||
return 0;
|
mem--;
|
||||||
|
if (mem < 0) {
|
||||||
|
mem = 9;
|
||||||
|
// Underflow is persistent until the next pulse
|
||||||
|
level._set_tile_prop(me, 'underflowing', true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
output1 = me.underflowing;
|
||||||
|
level._set_tile_prop(me, 'memory', mem);
|
||||||
|
level._set_tile_prop(me, 'incrementing', input0);
|
||||||
|
level._set_tile_prop(me, 'decrementing', input1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (output0 ? outbit0 : 0) | (output1 ? outbit1 : 0);
|
||||||
},
|
},
|
||||||
visual_state(me) {
|
visual_state(me) {
|
||||||
return me.gate_type;
|
return me.gate_type;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user