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
|
||||
tile.direction = 'north';
|
||||
tile.gate_type = 'counter';
|
||||
tile.counter_value = modifier - 0x1e;
|
||||
tile.memory = modifier - 0x1e;
|
||||
}
|
||||
else {
|
||||
tile.direction = ['north', 'east', 'south', 'west'][modifier & 0x03];
|
||||
|
||||
207
js/tileset.js
207
js/tileset.js
@ -401,58 +401,52 @@ export const CC2_TILESET_LAYOUT = {
|
||||
],
|
||||
|
||||
logic_gate: {
|
||||
// TODO currently, 'wired' can't coexist with visual state etc...
|
||||
// TODO *long sigh* of course, logic gates have parts with independent current too
|
||||
// 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': {
|
||||
north: [8, 21],
|
||||
east: [9, 21],
|
||||
south: [10, 21],
|
||||
west: [11, 21],
|
||||
special: 'logic-gate',
|
||||
logic_gate_tiles: {
|
||||
'latch-ccw': {
|
||||
north: [8, 21],
|
||||
east: [9, 21],
|
||||
south: [10, 21],
|
||||
west: [11, 21],
|
||||
},
|
||||
not: {
|
||||
north: [0, 25],
|
||||
east: [1, 25],
|
||||
south: [2, 25],
|
||||
west: [3, 25],
|
||||
},
|
||||
and: {
|
||||
north: [4, 25],
|
||||
east: [5, 25],
|
||||
south: [6, 25],
|
||||
west: [7, 25],
|
||||
},
|
||||
or: {
|
||||
north: [8, 25],
|
||||
east: [9, 25],
|
||||
south: [10, 25],
|
||||
west: [11, 25],
|
||||
},
|
||||
xor: {
|
||||
north: [12, 25],
|
||||
east: [13, 25],
|
||||
south: [14, 25],
|
||||
west: [15, 25],
|
||||
},
|
||||
'latch-cw': {
|
||||
north: [0, 26],
|
||||
east: [1, 26],
|
||||
south: [2, 26],
|
||||
west: [3, 26],
|
||||
},
|
||||
nand: {
|
||||
north: [4, 26],
|
||||
east: [5, 26],
|
||||
south: [6, 26],
|
||||
west: [7, 26],
|
||||
},
|
||||
counter: [14, 26],
|
||||
},
|
||||
not: {
|
||||
north: [0, 25],
|
||||
east: [1, 25],
|
||||
south: [2, 25],
|
||||
west: [3, 25],
|
||||
},
|
||||
and: {
|
||||
north: [4, 25],
|
||||
east: [5, 25],
|
||||
south: [6, 25],
|
||||
west: [7, 25],
|
||||
},
|
||||
or: {
|
||||
north: [8, 25],
|
||||
east: [9, 25],
|
||||
south: [10, 25],
|
||||
west: [11, 25],
|
||||
},
|
||||
xor: {
|
||||
north: [12, 25],
|
||||
east: [13, 25],
|
||||
south: [14, 25],
|
||||
west: [15, 25],
|
||||
},
|
||||
'latch-cw': {
|
||||
north: [0, 26],
|
||||
east: [1, 26],
|
||||
south: [2, 26],
|
||||
west: [3, 26],
|
||||
},
|
||||
nand: {
|
||||
north: [4, 26],
|
||||
east: [5, 26],
|
||||
south: [6, 26],
|
||||
west: [7, 26],
|
||||
},
|
||||
// FIXME need to draw the number as well
|
||||
counter: [14, 26],
|
||||
},
|
||||
|
||||
'#unpowered': [13, 26],
|
||||
@ -731,6 +725,94 @@ export class Tileset {
|
||||
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
|
||||
// without it you'll get defaults.
|
||||
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;
|
||||
if (drawspec.mask) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
_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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
134
js/tiletypes.js
134
js/tiletypes.js
@ -654,6 +654,9 @@ const TILE_TYPES = {
|
||||
on_gray_button(me, level) {
|
||||
level.transmute_tile(me, 'green_wall');
|
||||
},
|
||||
on_power(me, level) {
|
||||
me.type.on_gray_button(me, level);
|
||||
},
|
||||
},
|
||||
green_wall: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
@ -661,6 +664,9 @@ const TILE_TYPES = {
|
||||
on_gray_button(me, level) {
|
||||
level.transmute_tile(me, 'green_floor');
|
||||
},
|
||||
on_power(me, level) {
|
||||
me.type.on_gray_button(me, level);
|
||||
},
|
||||
},
|
||||
green_chip: {
|
||||
draw_layer: DRAW_LAYERS.item,
|
||||
@ -1116,42 +1122,114 @@ const TILE_TYPES = {
|
||||
logic_gate: {
|
||||
// gate_type: not, and, or, xor, nand, latch-cw, latch-ccw, counter, bogus
|
||||
_gate_types: {
|
||||
not: ['out', null, 'in1', null],
|
||||
and: ['out', 'in2', null, 'in1'],
|
||||
or: [],
|
||||
xor: [],
|
||||
nand: [],
|
||||
'latch-cw': [],
|
||||
'latch-ccw': [],
|
||||
not: ['out0', null, 'in0', null],
|
||||
and: ['out0', 'in0', null, 'in1'],
|
||||
or: ['out0', 'in0', null, 'in1'],
|
||||
xor: ['out0', 'in0', null, 'in1'],
|
||||
nand: ['out0', 'in0', null, 'in1'],
|
||||
// in0 is the trigger, in1 is the input
|
||||
'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,
|
||||
is_power_source: true,
|
||||
on_ready(me, level) {
|
||||
me.gate_def = me.type._gate_types[me.gate_type];
|
||||
if (me.gate_type === 'latch-cw' || me.gate_type === 'latch-ccw') {
|
||||
me.memory = false;
|
||||
}
|
||||
else if (me.gate_type === 'counter') {
|
||||
me.memory = me.memory ?? 0;
|
||||
me.incrementing = false;
|
||||
me.decrementing = false;
|
||||
me.underflowing = false;
|
||||
me.direction = 'north';
|
||||
}
|
||||
},
|
||||
get_emitting_edges(me, level) {
|
||||
if (me.gate_type === 'and') {
|
||||
let vars = {};
|
||||
let out_bit = 0;
|
||||
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) {
|
||||
vars[name] = (me.cell.powered_edges & dirinfo.bit) !== 0;
|
||||
}
|
||||
dir = dirinfo.right;
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (vars.in1 && vars.in2) {
|
||||
return out_bit;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
if (me.gate_type === 'not') {
|
||||
output0 = ! input0;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
else if (me.gate_type === 'and') {
|
||||
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 if (dec && ! inc) {
|
||||
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) {
|
||||
return me.gate_type;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user