remove prosperon

This commit is contained in:
2025-11-22 17:46:53 -06:00
parent 9881158e62
commit 34de9e6dc4
333 changed files with 3 additions and 9182 deletions

View File

@@ -1,121 +0,0 @@
var cam = {}
/*
presentation can be one of
letterbox
overscan
stretch
... or simply 'null' for no presentation
*/
var basecam = {
pos: [0,0], // where it is
ortho:true,
width: 100,
aspect_ratio: 16/9,
fov:50,
near_z:0,
far_z:1000,
anchor:[0.5,0.5],
rotation:[0,0,0,1],
presentation: "letterbox",
background: {r:1,g:1,b:1,a:0},
viewport: {x:0,y:0,width:1,height:1},
}
basecam.draw_rect = function(size)
{
var mode = this.presentation || "letterbox"
var vp = {
x:this.viewport.x,
y:1-this.viewport.y-this.viewport.height,
width:this.viewport.width,
height:this.viewport.height
}
var src_rect = {x:0,y:0,width:this.size.x,height:this.size.y}
var dst_rect = {x:vp.x*size.x,y:vp.y*size.y,width:vp.width*size.x,height:vp.height*size.y};
return mode_rect(src_rect,dst_rect,mode);
}
basecam.screen2camera = function(pos)
{
var draw_rect = this.draw_rect(prosperon.window.size);
var ret = [pos.x-draw_rect.x, pos.y - draw_rect.y];
ret.x /= draw_rect.width;
ret.y /= draw_rect.height;
ret.y = 1 - ret.y;
return ret;
}
basecam.screen2hud = function(pos)
{
var cam = this.screen2camera(pos);
cam.x *= this.size.x;
cam.y *= this.size.y;
return cam;
}
basecam.screen2world = function(pos)
{
var hud = this.screen2hud(pos);
hud.x += this.transform.pos.x - this.size.x/2;
hud.y += this.transform.pos.y - this.size.y/2;
return hud;
}
function mode_rect(src,dst,mode = "stretch")
{
var aspect_src = src.width/src.height;
var aspect_dst = dst.width/dst.height;
var out = {
x:dst.x,
y:dst.y,
width:dst.width,
height:dst.height
};
if (mode == "stretch") return out;
if (mode == "letterbox") {
if (aspect_src > aspect_dst) {
var scaled_h = out.width/aspect_src;
var off = (out.height - scaled_h) * 0.5;
out.y += off;
out.height = scaled_h;
} else {
var scaled_w =out.height * aspect_src;
var off = (out.width - scaled_w) * 0.5;
out.x += off;
out.width = scaled_w;
}
} else if (mode == "overscan"){
if (aspect_src > aspect_dst) {
var scaled_w = out.height * aspect_src;
var off = (out.width - scaled_w) * 0.5;
out.x += off;
out.width = scaled_w;
} else {
var scaled_h = out.width / aspect_src;
var off = (out.height - scaled_h) * 0.5;
out.y += off;
out.height = scaled_h;
}
}
return out;
}
cam.make = function()
{
var c = Object.create(basecam)
c.transform = new transform
c.transform.unit()
c.size = [640,360]
c.mode = 'keep'
c.viewport = {x:0,y:0,width:1,height:1}
c.fov = 45
c.type = 'ortho'
c.ortho = true
c.aspect = 16/9
return c
}
return cam

View File

@@ -1,410 +0,0 @@
// Layout code
// Contain is for how it will treat its children. If they should be laid out as a row, or column, or in a flex style, etc.
var layout = use('layout')
var geometry = use('geometry')
var draw = use('prosperon/draw2d')
var graphics = use('graphics')
var util = use('util')
var input = use('input')
var prosperon = use('prosperon')
var CHILDREN = Symbol('children')
var PARENT = Symbol('parent')
function normalizeSpacing(spacing) {
if (typeof spacing == 'number') {
return {l: spacing, r: spacing, t: spacing, b: spacing}
} else if (Array.isArray(spacing)) {
if (spacing.length == 2) {
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
} else if (spacing.length == 4) {
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
}
} else if (typeof spacing == 'object') {
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
} else {
return {l:0, r:0, t:0, b:0}
}
}
var lay_ctx = layout.make_context();
var clay_base = {
font: null,
background_image: null,
slice: 0,
font: 'smalle.16',
font_size: null,
color: {r:1,g:1,b:1,a:1},
spacing:0,
padding:0,
margin:0,
offset:{x:0, y:0},
size:null,
background_color: null,
clipped: false,
text_break: 'word',
text_align: 'left',
max_size: null, // {width: null, height: null}
};
var root_item;
var root_config;
var tree_root;
var clay = {}
clay.CHILDREN = CHILDREN
clay.PARENT = PARENT
var focused_textbox = null
clay.behave = layout.behave;
clay.contain = layout.contain;
clay.draw = function draw(fn, size = [prosperon.camera.width, prosperon.camera.height])
{
lay_ctx.reset();
var root = lay_ctx.item();
// Accept both array and object formats
if (Array.isArray(size)) {
size = {width: size[0], height: size[1]};
}
lay_ctx.set_size(root,size);
lay_ctx.set_contain(root,layout.contain.row);
root_item = root;
root_config = Object.assign({}, clay_base);
tree_root = {
id: root,
config: root_config,
};
tree_root[CHILDREN] = [];
tree_root[PARENT] = null;
fn()
lay_ctx.run();
// Adjust bounding boxes for padding - traverse tree instead of array
function adjust_bounding_boxes(node) {
node.content = lay_ctx.get_rect(node.id);
node.boundingbox = Object.assign({}, node.content);
var padding = normalizeSpacing(node.config.padding || 0);
node.boundingbox.x -= padding.l;
node.boundingbox.y -= padding.t;
node.boundingbox.width += padding.l + padding.r;
node.boundingbox.height += padding.t + padding.b;
node.marginbox = Object.assign({}, node.content);
var margin = normalizeSpacing(node.config.margin || 0);
node.marginbox.x -= margin.l;
node.marginbox.y -= margin.t;
node.marginbox.width += margin.l+margin.r;
node.marginbox.height += margin.t+margin.b;
// Apply max_size clamping post-layout
if (node.config.max_size) {
if (node.config.max_size.width != null) {
// Clamp the layout rect size
var rect = lay_ctx.get_rect(node.id);
rect.width = Math.min(rect.width, node.config.max_size.width);
// Also clamp bounding box
node.content.width = Math.min(node.content.width, node.config.max_size.width);
node.boundingbox.width = Math.min(node.boundingbox.width, node.config.max_size.width + padding.l + padding.r);
node.marginbox.width = Math.min(node.marginbox.width, node.config.max_size.width + padding.l + padding.r + margin.l + margin.r);
}
if (node.config.max_size.height != null) {
// Clamp the layout rect size
var rect = lay_ctx.get_rect(node.id);
rect.height = Math.min(rect.height, node.config.max_size.height);
// Also clamp bounding box
node.content.height = Math.min(node.content.height, node.config.max_size.height);
node.boundingbox.height = Math.min(node.boundingbox.height, node.config.max_size.height + padding.t + padding.b);
node.marginbox.height = Math.min(node.marginbox.height, node.config.max_size.height + padding.t + padding.b + margin.t + margin.b);
}
}
node.content.y *= -1;
node.content.y += size.height;
node.boundingbox.y *= -1;
node.boundingbox.y += size.height;
node.content.anchor_y = 1;
node.boundingbox.anchor_y = 1;
// Recursively adjust children
if (node[CHILDREN]) {
for (var child of node[CHILDREN]) {
adjust_bounding_boxes(child);
}
}
}
adjust_bounding_boxes(tree_root);
return tree_root;
}
function create_view_fn(base_config)
{
var base = Object.assign(Object.create(clay_base), base_config);
return function view(config = {}, fn) {
config.__proto__ = base;
var item = add_item(config);
var prev_item = root_item;
var prev_config = root_config;
var prev_tree_root = tree_root;
root_item = item;
root_config = config;
root_config._childIndex = 0; // Initialize child index
// Find the tree node for this item and set it as current tree_root
if (prev_tree_root[CHILDREN] && prev_tree_root[CHILDREN].length > 0) {
tree_root = prev_tree_root[CHILDREN][prev_tree_root[CHILDREN].length - 1];
} else {
// If no children yet, this shouldn't happen, but handle gracefully
tree_root = prev_tree_root;
}
fn?.();
root_item = prev_item;
root_config = prev_config;
tree_root = prev_tree_root;
}
}
clay.vstack = create_view_fn({
contain: layout.contain.column | layout.contain.start,
});
clay.hstack = create_view_fn({
contain: layout.contain.row | layout.contain.start,
});
clay.spacer = create_view_fn({
behave: layout.behave.hfill | layout.behave.vfill
});
clay.frame = create_view_fn({});
function image_size(img)
{
return [img.width * (img.rect?.width || 1), img.height * (img.rect?.height || 1)];
}
function add_item(config)
{
// Normalize the child's margin
var margin = normalizeSpacing(config.margin || 0);
var padding = normalizeSpacing(config.padding || 0);
var childGap = root_config.child_gap || 0;
// Adjust for child_gap
root_config._childIndex ??= 0
if (root_config._childIndex > 0) {
var parentContain = root_config.contain || 0;
var directionMask = layout.contain.row | layout.contain.column;
var direction = parentContain & directionMask;
var isVStack = direction == layout.contain.column;
var isHStack = direction == layout.contain.row;
if (isVStack) {
margin.t += childGap;
} else if (isHStack) {
margin.l += childGap;
}
}
var use_config = Object.create(config);
use_config.margin = {
t: margin.t+padding.t,
b: margin.b+padding.b,
r:margin.r+padding.r,
l:margin.l+padding.l
};
var item = lay_ctx.item();
lay_ctx.set_margins(item, use_config.margin);
use_config.size ??= {width:0, height:0}
// Convert array to object if needed
if (Array.isArray(use_config.size)) {
use_config.size = {width: use_config.size[0], height: use_config.size[1]};
}
// Apply max_size constraint - only clamp computed size, don't set explicit size pre-layout
if (use_config.max_size) {
// max_size should not force explicit sizing pre-layout - let children compute natural size,
// then clamp the container after layout. For now, just ensure we don't set size to max_size.
}
lay_ctx.set_size(item,use_config.size);
lay_ctx.set_contain(item,use_config.contain);
lay_ctx.set_behave(item,use_config.behave);
var tree_node = {
id: item,
config: use_config,
};
tree_node[CHILDREN] = [];
tree_node[PARENT] = tree_root;
tree_root[CHILDREN].push(tree_node);
lay_ctx.insert(root_item,item);
// Increment the parent's child index
root_config._childIndex++;
return item;
}
function rectify_configs(config_array)
{
if (config_array.length == 0)
config_array = [{}];
for (var i = config_array.length-1; i > 0; i--)
config_array[i].__proto__ = config_array[i-1];
config_array[0].__proto__ = clay_base;
var cleanobj = Object.create(config_array[config_array.length-1]);
return cleanobj;
}
clay.image = function image(path, ...configs)
{
var config = rectify_configs(configs);
var image = graphics.texture(path);
config.image = path; // Store the path string, not the texture object
config.size ??= {width: image.width, height: image.height};
add_item(config);
}
clay.text = function text(str, ...configs)
{
var config = rectify_configs(configs);
config.size ??= [0,0]
config.font = graphics.get_font(config.font)
config.text = str
var tsize = config.font.text_size(str, 0, config.size[0], config.text_break, config.text_align);
tsize.x = Math.ceil(tsize.x)
tsize.y = Math.ceil(tsize.y)
config.size = config.size.map((x,i) => Math.max(x, tsize[i]));
add_item(config);
}
/*
For a given size,
the layout engine should "see" size + margin
but its interior content should "see" size - padding
hence, the layout box should be size-padding, with margin of margin+padding
*/
var button_base = Object.assign(Object.create(clay_base), {
padding:0,
hovered:{
}
});
clay.button = function button(str, action, config = {})
{
config.__proto__ = button_base;
config.font = graphics.get_font(config.font)
config.size = config.font.text_size(str, 0, 0, config.text_break, config.text_align)
add_item(config);
config.text = str;
config.action = action;
}
var point = use('point')
clay.draw_commands = function draw_commands(tree_root, pos = {x:0,y:0})
{
function draw_node(node) {
var config = node.config
var boundingbox = geometry.rect_move(node.boundingbox,point.add(pos,config.offset))
var content = geometry.rect_move(node.content,point.add(pos, config.offset))
if (config.background_image)
if (config.slice)
draw.slice9(config.background_image, boundingbox, config.slice, config.background_color)
else
draw.image(config.background_image, boundingbox, 0, config.color)
else if (config.background_color)
draw.rectangle(boundingbox, null, {color:config.background_color})
if (config.text) {
var baseline_y = content.y + content.height - config.font.ascent
draw.text(config.text, {x: content.x, y: baseline_y}, config.font, config.color, content.width)
}
if (config.image)
draw.image(config.image, content, 0, config.color)
if (config.clipped) {
draw.scissor(content)
}
// Recursively draw children
if (node[CHILDREN]) {
for (var child of node[CHILDREN]) {
draw_node(child);
}
}
if (config.clipped)
draw.scissor(null)
}
draw_node(tree_root);
}
var dbg_colors = {};
clay.debug_colors = dbg_colors;
dbg_colors.content = [1,0,0,0.1];
dbg_colors.boundingbox = [0,1,0,0,0.1];
dbg_colors.margin = [0,0,1,0.1];
clay.draw_debug = function draw_debug(tree_root, pos = {x:0, y:0})
{
function draw_debug_node(node) {
var boundingbox = geometry.rect_move(node.boundingbox,pos);
var content = geometry.rect_move(node.content,pos);
draw.rectangle(content, null, {color:dbg_colors.content});
draw.rectangle(boundingbox, null, {color:dbg_colors.boundingbox});
// draw.rectangle(geometry.rect_move(node.marginbox,pos), dbg_colors.margin);
// Recursively draw debug for children
if (node[CHILDREN]) {
for (var child of node[CHILDREN]) {
draw_debug_node(child);
}
}
}
draw_debug_node(tree_root);
}
clay.print_tree = function print_tree(tree_root, indent = 0) {
var indent_str = ' '.repeat(indent)
var node_type = 'unknown'
if (tree_root.config.text) {
node_type = 'text'
} else if (tree_root.config.image) {
node_type = 'image'
} else if (tree_root.config.contain) {
if (tree_root.config.contain & layout.contain.column) {
node_type = 'vstack'
} else if (tree_root.config.contain & layout.contain.row) {
node_type = 'hstack'
} else {
node_type = 'container'
}
} else {
node_type = 'node'
}
log.console(`${indent_str}${node_type} (id: ${tree_root.id})`)
if (tree_root[CHILDREN] && tree_root[CHILDREN].length > 0) {
log.console(`${indent_str} children: ${tree_root[CHILDREN].length}`)
for (var child of tree_root[CHILDREN]) {
print_tree(child, indent + 1)
}
} else {
log.console(`${indent_str} (no children)`)
}
}
return clay

View File

@@ -1,110 +0,0 @@
// clay_input.cm - Input handling for clay UI
// Separates input concerns from layout/rendering
var geometry = use('geometry')
var point = use('point')
var clay = use('prosperon/clay')
var clay_input = {}
function rect_contains(node, pos) {
var bb = geometry.rect_move(node.boundingbox, node.config.offset || {x: 0, y: 0})
return geometry.rect_point_inside(bb, pos)
}
function pointer_enabled(node) {
var p = node.config.pointer_events
if (!p || p == 'auto') return true
if (p == 'none') return false
return true
}
function should_skip_children(node) {
var p = node.config.pointer_events
if (p == 'box-only') return true
return false
}
function should_skip_self(node) {
var p = node.config.pointer_events
if (p == 'box-none') return true
return false
}
function find_path(node, path, pos) {
if (!pointer_enabled(node)) return null
if (!rect_contains(node, pos)) return null
var next_path = path.concat(node)
if (node[clay.CHILDREN] && !should_skip_children(node)) {
// Children drawn later should be tested first; reverse if your render order differs
for (var i = node[clay.CHILDREN].length - 1; i >= 0; i--) {
var child = node[clay.CHILDREN][i]
var child_path = find_path(child, next_path, pos)
if (child_path) return child_path
}
}
if (should_skip_self(node)) return null
return next_path
}
clay_input.deepest = function deepest(tree_root, pos) {
var path = find_path(tree_root, [], pos) || []
var deepest = path.length ? path[path.length - 1] : null
return deepest
}
clay_input.bubble = function bubble(deepest, prop) {
var current = deepest
while (current) {
if (current.config && current.config[prop])
return current
current = current[clay.PARENT]
}
return null
}
clay_input.click = function click(tree_root, mousepos, button = 'left') {
var deepest = clay_input.deepest(tree_root, mousepos)
var action_target = clay_input.bubble(deepest, 'action')
if (action_target && action_target.config.action) action_target.config.action()
}
clay_input.get_actionable = function get_actionable(tree_root) {
var actionable = []
function walk(node) {
if (node.config.action) actionable.push(node)
if (node[clay.CHILDREN])
for (var child of node[clay.CHILDREN]) walk(child)
}
walk(tree_root)
return actionable
}
clay_input.filter = function filter(tree_root, predicate) {
var results = []
function rec(node) {
if (predicate(node)) results.push(node)
if (node[clay.CHILDREN])
for (var child of node[clay.CHILDREN]) rec(child)
}
rec(tree_root)
return results
}
clay_input.find_by_id = function find_by_id(tree_root, id) {
function rec(node) {
if (node.id == id) return node
if (node[clay.CHILDREN])
for (var child of node[clay.CHILDREN]) {
var f = rec(child)
if (f) return f
}
return null
}
return rec(tree_root)
}
return clay_input

View File

@@ -1,216 +0,0 @@
function tohex(n) {
var s = Math.floor(n).toString(16);
if (s.length == 1) s = "0" + s;
return s.toUpperCase();
};
var Color = {
white: [1, 1, 1],
black: [0, 0, 0],
blue: [0, 0, 1],
green: [0, 1, 0],
yellow: [1, 1, 0],
red: [1, 0, 0],
gray: [0.71, 0.71, 0.71],
cyan: [0, 1, 1],
purple: [0.635, 0.365, 0.89],
orange: [1, 0.565, 0.251],
magenta: [1, 0, 1],
};
Color.editor = {};
Color.editor.ur = Color.green;
Color.tohtml = function (v) {
var html = v.map(function (n) {
return tohex(n * 255);
});
return "#" + html.join("");
};
var esc = {};
esc.reset = "\x1b[0";
esc.color = function (v) {
var c = v.map(function (n) {
return Math.floor(n * 255);
});
var truecolor = "\x1b[38;2;" + c.join(";") + ";";
return truecolor;
};
esc.doc = "Functions and constants for ANSI escape sequences.";
Color.Arkanoid = {
orange: [1, 0.561, 0],
teal: [0, 1, 1],
green: [0, 1, 0],
red: [1, 0, 0],
blue: [0, 0.439, 1],
purple: [1, 0, 1],
yellow: [1, 1, 0],
silver: [0.616, 0.616, 0.616],
gold: [0.737, 0.682, 0],
};
Color.Arkanoid.Powerups = {
red: [0.682, 0, 0] /* laser */,
blue: [0, 0, 0.682] /* enlarge */,
green: [0, 0.682, 0] /* catch */,
orange: [0.878, 0.561, 0] /* slow */,
purple: [0.824, 0, 0.824] /* break */,
cyan: [0, 0.682, 1] /* disruption */,
gray: [0.561, 0.561, 0.561] /* 1up */,
};
Color.Gameboy = {
darkest: [0.898, 0.42, 0.102],
dark: [0.898, 0.741, 0.102],
light: [0.741, 0.898, 0.102],
lightest: [0.42, 0.898, 0.102],
};
Color.Apple = {
green: [0.369, 0.741, 0.243],
yellow: [1, 0.725, 0],
orange: [0.969, 0.51, 0],
red: [0.886, 0.22, 0.22],
purple: [0.592, 0.224, 0.6],
blue: [0, 0.612, 0.875],
};
Color.Debug = {
boundingbox: Color.white,
names: [0.329, 0.431, 1],
};
Color.Editor = {
grid: [0.388, 1, 0.502],
select: [1, 1, 0.216],
newgroup: [0.471, 1, 0.039],
};
/* Detects the format of all colors and munges them into a floating point format */
Color.normalize = function (c) {
var add_a = function (a) {
var n = this.slice();
n[3] = a;
return n;
};
for (var p of Object.keys(c)) {
if (typeof c[p] != "object") continue;
if (!Array.isArray(c[p])) {
Color.normalize(c[p]);
continue;
}
// Add alpha channel if not present
if (c[p].length == 3) {
c[p][3] = 1;
}
// Check if any values are > 1 (meaning they're in 0-255 format)
var needs_conversion = false;
for (var color of c[p]) {
if (color > 1) {
needs_conversion = true;
break;
}
}
// Convert from 0-255 to 0-1 if needed
if (needs_conversion) {
c[p] = c[p].map(function (x) {
return x / 255;
});
}
c[p].alpha = add_a;
}
};
Color.normalize(Color);
var ColorMap = {};
ColorMap.makemap = function (map) {
var newmap = Object.create(ColorMap);
Object.assign(newmap, map);
return newmap;
};
ColorMap.Jet = ColorMap.makemap({
0: [0, 0, 0.514],
0.125: [0, 0.235, 0.667],
0.375: [0.02, 1, 1],
0.625: [1, 1, 0],
0.875: [0.98, 0, 0],
1: [0.502, 0, 0],
});
ColorMap.BlueRed = ColorMap.makemap({
0: [0, 0, 1],
1: [1, 0, 0],
});
ColorMap.Inferno = ColorMap.makemap({
0: [0, 0, 0.016],
0.13: [0.122, 0.047, 0.282],
0.25: [0.333, 0.059, 0.427],
0.38: [0.533, 0.133, 0.416],
0.5: [0.729, 0.212, 0.333],
0.63: [0.89, 0.349, 0.2],
0.75: [0.976, 0.549, 0.039],
0.88: [0.976, 0.788, 0.196],
1: [0.988, 1, 0.643],
});
ColorMap.Bathymetry = ColorMap.makemap({
0: [0.157, 0.102, 0.173],
0.13: [0.233, 0.192, 0.353],
0.25: [0.251, 0.298, 0.545],
0.38: [0.247, 0.431, 0.592],
0.5: [0.282, 0.557, 0.62],
0.63: [0.333, 0.682, 0.639],
0.75: [0.471, 0.808, 0.639],
0.88: [0.733, 0.902, 0.675],
1: [0.992, 0.996, 0.8],
});
ColorMap.Viridis = ColorMap.makemap({
0: [0.267, 0.004, 0.329],
0.13: [0.278, 0.173, 0.478],
0.25: [0.231, 0.318, 0.545],
0.38: [0.173, 0.443, 0.557],
0.5: [0.129, 0.565, 0.553],
0.63: [0.153, 0.678, 0.506],
0.75: [0.361, 0.784, 0.388],
0.88: [0.667, 0.863, 0.196],
1: [0.992, 0.906, 0.145],
});
Color.normalize(ColorMap);
ColorMap.sample = function (t, map = this) {
if (t < 0) return map[0];
if (t > 1) return map[1];
var lastkey = 0;
for (var key of Object.keys(map).sort()) {
if (t < key) {
var b = map[key];
var a = map[lastkey];
var tt = (key - lastkey) * t;
return a.lerp(b, tt);
}
lastkey = key;
}
return map[1];
};
ColorMap.doc = {
sample: "Sample a given colormap at the given percentage (0 to 1).",
};
Color.maps = ColorMap
Color.utils = esc
return Color

View File

@@ -1,345 +0,0 @@
var input = use('input')
return {}
var downkeys = {};
function keyname(key)
{
var str = input.keyname(key);
return str.toLowerCase();
}
function modstr(mod = input.keymod()) {
var s = "";
if (mod.ctrl) s += "C-";
if (mod.alt) s += "M-";
if (mod.super) s += "S-";
return s;
}
prosperon.on('key_down', function key_down(e) {
downkeys[e.key] = true;
var emacs = modstr(e.mod) + keyname(e.key);
if (e.repeat) player[0].raw_input(emacs, "rep");
else player[0].raw_input(emacs, "pressed");
})
prosperon.on('quit', function() {
os.exit(0);
})
prosperon.on('key_up', function key_up(e) {
delete downkeys[e.key];
var emacs = modstr(e.mod) + keyname(e.key);
player[0].raw_input(emacs, "released");
})
prosperon.on('drop_file', function (path) {
player[0].raw_input("drop", "pressed", path);
})
var mousepos = [0, 0];
prosperon.on('text_input', function (e) {
player[0].raw_input("char", "pressed", e.text);
})
prosperon.on('mouse_motion', function (e)
{
mousepos = e.pos;
player[0].mouse_input("move", e.pos, e.d_pos);
})
prosperon.on('mouse_wheel', function mousescroll(e) {
player[0].mouse_input(modstr() + "scroll", e.scroll);
})
prosperon.on('mouse_button_down', function(e) {
player[0].mouse_input(modstr() + e.button, "pressed");
input.mouse.buttons[e.button] = true
})
prosperon.on('mouse_button_up', function(e) {
player[0].mouse_input(modstr() + e.button, "released");
input.mouse.buttons[e.button] = false
})
input.mouse = {};
input.mouse.screenpos = function mouse_screenpos() {
return mousepos.slice();
};
input.mouse.worldpos = function mouse_worldpos() {
return prosperon.camera.screen2world(mousepos);
};
input.mouse.viewpos = function mouse_viewpos()
{
var world = input.mouse.worldpos();
return mousepos.slice();
}
input.mouse.disabled = function mouse_disabled() {
input.mouse_mode(1);
};
input.mouse.normal = function mouse_normal() {
input.mouse_mode(0);
};
input.mouse.mode = function mouse_mode(m) {
if (input.mouse.custom[m]) input.cursor_img(input.mouse.custom[m]);
else input.mouse_cursor(m);
};
input.mouse.buttons = {
0:false,
1:false,
2:false
}
input.mouse.set_custom_cursor = function mouse_cursor(img, mode = input.mouse.cursor.default) {
if (!img) delete input.mouse.custom[mode];
else {
input.cursor_img(img);
input.mouse.custom[mode] = img;
}
};
input.mouse.doc = {};
input.mouse.doc.pos = "The screen position of the mouse.";
input.mouse.doc.worldpos = "The position in the game world of the mouse.";
input.mouse.disabled.doc = "Set the mouse to hidden. This locks it to the game and hides it, but still provides movement and click events.";
input.mouse.normal.doc = "Set the mouse to show again after hiding.";
input.keyboard = {};
input.keyboard.down = function (code) {
if (typeof code == "number") return downkeys[code];
if (typeof code == "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
return null;
};
input.print_pawn_kbm = function (pawn) {
if (!("inputs" in pawn)) return;
var str = "";
for (var key in pawn.inputs) {
if (!pawn.inputs[key].doc) continue;
str += `${key} | ${pawn.inputs[key].doc}\n`;
}
return str;
};
var joysticks = {};
joysticks["wasd"] = {
uy: "w",
dy: "s",
ux: "d",
dx: "a",
};
input.procdown = function procdown() {
for (var k in downkeys) player[0].raw_input(keyname(k), "down");
for (var i in joysticks) {
var joy = joysticks[i];
var x = joy.ux - joy.dx;
var y = joy.uy - joy.dy;
player[0].joy_input(i, joysticks[i]);
}
};
input.print_md_kbm = function print_md_kbm(pawn) {
if (!("inputs" in pawn)) return;
var str = "";
str += "|control|description|\n|---|---|\n";
for (var key in pawn.inputs) {
str += `|${key}|${pawn.inputs[key].doc}|`;
str += "\n";
}
return str;
};
input.has_bind = function (pawn, bind) {
return typeof pawn.inputs?.[bind] == "function";
};
input.action = {
add_new(name) {
var action = Object.create(input.action);
action.name = name;
action.inputs = [];
this.actions.push(action);
return action;
},
actions: [],
};
input.tabcomplete = function tabcomplete(val, list) {
if (!val) return val;
list = filter(x => x.startsWith(val))
if (list.length == 1) {
return list[0];
}
var ret = null;
var i = val.length;
while (!ret && list.length != 0) {
var char = list[0][i];
if (
!list.every(function (x) {
return x[i] == char;
})
)
ret = list[0].slice(0, i);
else {
i++;
list = list.filter(x => x.length-1 > i)
}
}
return ret ? ret : val;
};
/* May be a human player; may be an AI player */
/*
'block' on a pawn's input blocks any input from reaching below for the
*/
var Player = {
players: [],
input(fn, ...args) {
this.pawns.forEach(x => x[fn]?.(...args));
},
mouse_input(type, ...args) {
for (var pawn of [...this.pawns].reverse()) {
if (typeof pawn.inputs?.mouse?.[type] == "function") {
pawn.inputs.mouse[type].call(pawn, ...args);
pawn.inputs.post?.call(pawn);
if (!pawn.inputs.fallthru) return;
}
}
},
char_input(c) {
for (var pawn of [...this.pawns].reverse()) {
if (typeof pawn.inputs?.char == "function") {
pawn.inputs.char.call(pawn, c);
pawn.inputs.post?.call(pawn);
if (!pawn.inputs.fallthru) return;
}
}
},
joy_input(name, joystick) {
for (var pawn of [...this.pawns].reverse()) {
if (!pawn.inputs) return;
if (!pawn.inputs.joystick) return;
if (!pawn.inputs.joystick[name]) return;
var x = 0;
if (input.keyboard.down(joystick.ux)) x++;
if (input.keyboard.down(joystick.dx)) x--;
var y = 0;
if (input.keyboard.down(joystick.uy)) y++;
if (input.keyboard.down(joystick.dy)) y--;
pawn.inputs.joystick[name](x, y);
}
},
raw_input(cmd, state, ...args) {
for (var pawn of [...this.pawns].reverse()) {
var inputs = pawn.inputs;
if (!inputs[cmd]) {
if (inputs.block) return;
continue;
}
var fn = null;
switch (state) {
case "pressed":
fn = inputs[cmd];
break;
case "rep":
fn = inputs[cmd].rep ? inputs[cmd] : null;
break;
case "released":
fn = inputs[cmd].released;
break;
case "down":
if (typeof inputs[cmd].down == "function") fn = inputs[cmd].down;
else if (inputs[cmd].down) fn = inputs[cmd];
}
var consumed = false;
if (typeof fn == "function") {
fn.call(pawn, ...args);
consumed = true;
}
if (state == "released") inputs.release_post?.call(pawn);
if (inputs.block) return;
if (consumed) return;
}
},
obj_controlled(obj) {
for (var p in Player.players) {
if (p.pawns.has(obj)) return true;
}
return false;
},
print_pawns() {
[...this.pawns].reverse().forEach(x => log.console(x))
},
create() {
var n = Object.create(this);
n.pawns = new Set()
n.gamepads = [];
this.players.push(n);
this[this.players.length - 1] = n;
return n;
},
control(pawn) {
if (!pawn)
return
if (!pawn.inputs)
throw new Error("attempted to control a pawn without any input object.");
this.pawns.add(pawn);
},
uncontrol(pawn) {
this.pawns.delete(pawn)
},
};
input.do_uncontrol = function input_do_uncontrol(pawn) {
if (!pawn.inputs) return;
Player.players.forEach(function (p) {
p.pawns.delete(pawn)
});
};
//for (var i = 0; i < 4; i++)
Player.create();
Player.control.doc = "Control a provided object, if the object has an 'inputs' object.";
Player.uncontrol.doc = "Uncontrol a previously controlled object.";
Player.print_pawns.doc = "Print out a list of the current pawn control stack.";
Player.doc = {};
Player.doc.players = "A list of current players.";
var player = Player;
input.player = Player
return input

View File

@@ -1,35 +0,0 @@
// helpful render devices. width and height in pixels; diagonal in inches.
return {
pc: { width: 1920, height: 1080 },
macbook_m2: { width: 2560, height: 1664, diagonal: 13.6 },
ds_top: { width: 400, height: 240, diagonal: 3.53 },
ds_bottom: { width: 320, height: 240, diagonal: 3.02 },
playdate: { width: 400, height: 240, diagonal: 2.7 },
switch: { width: 1280, height: 720, diagonal: 6.2 },
switch_lite: { width: 1280, height: 720, diagonal: 5.5 },
switch_oled: { width: 1280, height: 720, diagonal: 7 },
dsi: { width: 256, height: 192, diagonal: 3.268 },
ds: { width: 256, height: 192, diagonal: 3 },
dsixl: { width: 256, height: 192, diagonal: 4.2 },
ipad_air_m2: { width: 2360, height: 1640, diagonal: 11.97 },
iphone_se: { width: 1334, height: 750, diagonal: 4.7 },
iphone_12_pro: { width: 2532, height: 1170, diagonal: 6.06 },
iphone_15: { width: 2556, height: 1179, diagonal: 6.1 },
gba: { width: 240, height: 160, diagonal: 2.9 },
gameboy: { width: 160, height: 144, diagonal: 2.48 },
gbc: { width: 160, height: 144, diagonal: 2.28 },
steamdeck: { width: 1280, height: 800, diagonal: 7 },
vita: { width: 960, height: 544, diagonal: 5 },
psp: { width: 480, height: 272, diagonal: 4.3 },
imac_m3: { width: 4480, height: 2520, diagonal: 23.5 },
macbook_pro_m3: { width: 3024, height: 1964, diagonal: 14.2 },
ps1: { width: 320, height: 240, diagonal: 5 },
ps2: { width: 640, height: 480 },
snes: { width: 256, height: 224 },
gamecube: { width: 640, height: 480 },
n64: { width: 320, height: 240 },
c64: { width: 320, height: 200 },
macintosh: { width: 512, height: 342 },
gamegear: { width: 160, height: 144, diagonal: 3.2 }
};

View File

@@ -1,251 +0,0 @@
var math = use('math')
var color = use('color')
var gamestate = use('gamestate')
var draw = {}
var current_list = []
// Clear current list
draw.clear = function() {
current_list = []
}
// Get commands from current list
draw.get_commands = function() {
return current_list
}
// Helper to add a command
function add_command(type, data) {
data.cmd = type
current_list.push(data)
}
// Default geometry definitions
var ellipse_def = {
start: 0,
end: 1,
mode: 'fill',
thickness: 1,
}
var line_def = {
thickness: 1,
cap:"butt",
}
var rect_def = {
thickness:1,
radius: 0
}
var image_info = {
tile_x: false,
tile_y: false,
flip_x: false,
flip_y: false,
mode: 'linear'
}
var circle_def = {
inner_radius:1, // percentage: 1 means filled circle
start:0,
end: 1,
}
// Drawing functions
draw.point = function(pos, size, opt = {}, material) {
add_command("draw_point", {
pos: pos,
size: size,
opt: opt,
material: material
})
}
draw.ellipse = function(pos, radii, defl, material) {
var opt = defl ? {...ellipse_def, ...defl} : ellipse_def
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
add_command("draw_ellipse", {
pos: pos,
radii: radii,
opt: opt,
material: material
})
}
draw.line = function(points, defl, material)
{
var opt = defl ? {...line_def, ...defl} : line_def
add_command("draw_line", {
points: points,
opt: opt,
material: material
})
}
draw.cross = function render_cross(pos, size, defl, material) {
var a = [pos.add([0, size]), pos.add([0, -size])]
var b = [pos.add([size, 0]), pos.add([-size, 0])]
draw.line(a, defl, material)
draw.line(b, defl, material)
}
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, defl, material) {
var dir = math.norm(end.sub(start))
var wing1 = [math.rotate(dir, wingangle).scale(wingspan).add(end), end]
var wing2 = [math.rotate(dir, -wingangle).scale(wingspan).add(end), end]
draw.line([start, end], defl, material)
draw.line(wing1, defl, material)
draw.line(wing2, defl, material)
}
draw.rectangle = function render_rectangle(rect, defl, material = {color:{r:1,g:1,b:1,a:1}}) {
var opt = defl ? {...rect_def, ...defl} : rect_def
add_command("draw_rect", {
rect,
opt,
material
})
}
var slice9_info = {
tile_top:true,
tile_bottom:true,
tile_left:true,
tile_right:true,
tile_center_x:true,
tile_center_right:true,
}
draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info, material) {
if (!image) throw Error('Need an image to render.')
add_command("draw_slice9", {
image,
rect,
slice,
info,
material
})
}
draw.image = function image(image, rect, scale = {x:1,y:1}, anchor, shear, info, material) {
if (!rect) throw Error('Need rectangle to render image.')
if (!image) throw Error('Need an image to render.')
if (!('x' in rect && 'y' in rect)) throw Error('Must provide X and Y for image.')
add_command("draw_image", {
image,
rect,
scale,
anchor,
shear,
info,
material
})
}
draw.circle = function render_circle(pos, radius, defl, material) {
draw.ellipse(pos, [radius,radius], defl, material)
}
// wrap is the width before wrapping
// config is any additional config to pass to the text renderer
var text_base_config = {
align: 'left', // left, right, center, justify
break: 'word', // word, character
}
draw.text = function text(text, pos, font = 'fonts/c64.8', color = {r:1,g:1,b:1,a:1}, wrap = 0, config = {}) {
config.align ??= text_base_config.align
config.break ??= text_base_config.break
add_command("draw_text", {
text,
pos,
font,
wrap,
material: {color},
config
})
}
draw.grid = function grid(rect, spacing, thickness = 1, offset = {x: 0, y: 0}, material) {
if (!rect || rect.x == null || rect.y == null ||
rect.width == null || rect.height == null) {
throw Error('Grid requires rect with x, y, width, height')
}
if (!spacing || typeof spacing.x == 'undefined' || typeof spacing.y == 'undefined') {
throw Error('Grid requires spacing with x and y')
}
var left = rect.x
var right = rect.x + rect.width
var top = rect.y
var bottom = rect.y + rect.height
// Apply offset and align to grid
var start_x = Math.floor((left - offset.x) / spacing.x) * spacing.x + offset.x
var end_x = Math.ceil((right - offset.x) / spacing.x) * spacing.x + offset.x
var start_y = Math.floor((top - offset.y) / spacing.y) * spacing.y + offset.y
var end_y = Math.ceil((bottom - offset.y) / spacing.y) * spacing.y + offset.y
// Draw vertical lines
for (var x = start_x; x <= end_x; x += spacing.x) {
if (x >= left && x <= right) {
var line_top = Math.max(top, start_y)
var line_bottom = Math.min(bottom, end_y)
draw.line([[x, line_top], [x, line_bottom]], {thickness: thickness}, material)
}
}
// Draw horizontal lines
for (var y = start_y; y <= end_y; y += spacing.y) {
if (y >= top && y <= bottom) {
var line_left = Math.max(left, start_x)
var line_right = Math.min(right, end_x)
draw.line([[line_left, y], [line_right, y]], {thickness: thickness}, material)
}
}
}
draw.scissor = function(rect)
{
var screen_rect = null
if (rect && gamestate.camera) {
var bottom_left = gamestate.camera.world_to_window(rect.x, rect.y)
var top_right = gamestate.camera.world_to_window(rect.x + rect.width, rect.y + rect.height)
var screen_left = bottom_left.x
var screen_top = bottom_left.y
var screen_right = top_right.x
var screen_bottom = top_right.y
screen_rect = {
x: Math.round(screen_left),
y: Math.round(screen_top),
width: Math.round(screen_right - screen_left),
height: Math.round(screen_bottom - screen_top)
}
// TODO: must be a better way than manually inverting here. Some camera specific function.
var sensor = gamestate.camera.sensor()
screen_rect.y = sensor.height - screen_rect.y - screen_rect.height
}
current_list.push({
cmd: "scissor",
rect: screen_rect
})
}
draw.add_command = function(cmd)
{
current_list.push(cmd)
}
return draw

View File

@@ -1,148 +0,0 @@
var Ease = {
linear(t) {
return t
},
in(t) {
return t * t
},
out(t) {
var d = 1 - t
return 1 - d * d
},
inout(t) {
var d = -2 * t + 2
return t < 0.5 ? 2 * t * t : 1 - (d * d) / 2
},
}
function make_easing_fns(num) {
var obj = {}
obj.in = function (t) {
return Math.pow(t, num)
}
obj.out = function (t) {
return 1 - Math.pow(1 - t, num)
}
var mult = Math.pow(2, num - 1)
obj.inout = function (t) {
return t < 0.5 ? mult * Math.pow(t, num) : 1 - Math.pow(-2 * t + 2, num) / 2
}
return obj
}
Ease.quad = make_easing_fns(2)
Ease.cubic = make_easing_fns(3)
Ease.quart = make_easing_fns(4)
Ease.quint = make_easing_fns(5)
Ease.expo = {
in(t) {
return t == 0 ? 0 : Math.pow(2, 10 * t - 10)
},
out(t) {
return t == 1 ? 1 : 1 - Math.pow(2, -10 * t)
},
inout(t) {
return t == 0
? 0
: t == 1
? 1
: t < 0.5
? Math.pow(2, 20 * t - 10) / 2
: (2 - Math.pow(2, -20 * t + 10)) / 2
},
}
Ease.bounce = {
in(t) {
return 1 - this.out(1 - t)
},
out(t) {
var n1 = 7.5625
var d1 = 2.75
if (t < 1 / d1) {
return n1 * t * t
} else if (t < 2 / d1) {
return n1 * (t -= 1.5 / d1) * t + 0.75
} else if (t < 2.5 / d1) {
return n1 * (t -= 2.25 / d1) * t + 0.9375
} else return n1 * (t -= 2.625 / d1) * t + 0.984375
},
inout(t) {
return t < 0.5 ? (1 - this.out(1 - 2 * t)) / 2 : (1 + this.out(2 * t - 1)) / 2
},
}
Ease.sine = {
in(t) {
return 1 - Math.cos((t * Math.PI) / 2)
},
out(t) {
return Math.sin((t * Math.PI) / 2)
},
inout(t) {
return -(Math.cos(Math.PI * t) - 1) / 2
},
}
Ease.elastic = {
in(t) {
return t == 0
? 0
: t == 1
? 1
: -Math.pow(2, 10 * t - 10) *
Math.sin((t * 10 - 10.75) * this.c4)
},
out(t) {
return t == 0
? 0
: t == 1
? 1
: Math.pow(2, -10 * t) *
Math.sin((t * 10 - 0.75) * this.c4) +
1
},
inout(t) {
return t == 0
? 0
: t == 1
? 1
: t < 0.5
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2 + 1
},
}
Ease.elastic.c4 = (2 * Math.PI) / 3
Ease.elastic.c5 = (2 * Math.PI) / 4.5
Ease.zoom = {
// Creates a smooth zoom that maintains constant perceptual speed
// ratio is the zoom factor (e.g., 10 for 10x zoom)
smooth(ratio) {
return function(t) {
if (t == 0) return 0
if (t == 1) return 1
if (Math.abs(ratio - 1) < 0.001) return t
// Position interpolation formula: (r^t - 1) / (r - 1)
return (Math.pow(ratio, t) - 1) / (ratio - 1)
}
},
// Exponential interpolation for zoom values
// Interpolates in logarithmic space for smooth visual zoom
exp(startZoom, endZoom) {
return function(t) {
if (t == 0) return startZoom
if (t == 1) return endZoom
// Scale := Exp(LinearInterpolate(Ln(Scale1), Ln(Scale2), t))
return Math.exp(Math.log(startZoom) + t * (Math.log(endZoom) - Math.log(startZoom)))
}
}
}
return Ease

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

View File

@@ -1,5 +0,0 @@
return {
title:"Bunnymark",
width:1200,
height:600,
}

View File

@@ -1,70 +0,0 @@
var draw = use('draw2d')
var render = use('render')
var graphics = use('graphics')
var sprite = use('sprite')
var geom = use('geometry')
var input = use('controller')
var config = use('config')
var color = use('color')
var bunnyTex = graphics.texture("bunny")
// We'll store our bunnies in an array of objects: { x, y, vx, vy }
var bunnies = []
// Start with some initial bunnies:
for (var i = 0; i < 100; i++) {
bunnies.push({
x: Math.random() * config.width,
y: Math.random() * config.height,
vx: (Math.random() * 300) - 150,
vy: (Math.random() * 300) - 150
})
}
var fpsSamples = []
var fpsAvg = 0
this.update = function(dt) {
// Compute FPS average over the last 60 frames:
var currentFPS = 1 / dt
fpsSamples.push(currentFPS)
if (fpsSamples.length > 60) fpsSamples.shift()
var sum = 0
for (var f of fpsSamples) sum += f
fpsAvg = sum / fpsSamples.length
// If left mouse is down, spawn some more bunnies:
var mouse = input.mousestate()
if (mouse.left)
for (var i = 0; i < 50; i++) {
bunnies.push({
x: mouse.x,
y: mouse.y,
vx: (Math.random() * 300) - 150,
vy: (Math.random() * 300) - 150
})
}
// Update bunny positions and bounce them inside the screen:
for (var i = 0; i < bunnies.length; i++) {
var b = bunnies[i]
b.x += b.vx * dt
b.y += b.vy * dt
// Bounce off left/right edges
if (b.x < 0) { b.x = 0; b.vx = -b.vx }
else if (b.x > config.width) { b.x = config.width; b.vx = -b.vx }
// Bounce off bottom/top edges
if (b.y < 0) { b.y = 0; b.vy = -b.vy }
else if (b.y > config.height) { b.y = config.height; b.vy = -b.vy }
}
}
this.hud = function() {
draw.images(bunnyTex, bunnies)
var msg = 'FPS: ' + fpsAvg.toFixed(2) + ' Bunnies: ' + bunnies.length
draw.text(msg, {x:0, y:0, width:config.width, height:40}, null, 0, color.white, 0)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

View File

@@ -1,395 +0,0 @@
/* main.js runs the demo with your prototype-based grid */
var json = use('json')
var draw2d = use('prosperon/draw2d')
var blob = use('blob')
/*──── import our pieces + systems ───────────────────────────────────*/
var Grid = use('grid'); // your new ctor
var MovementSystem = use('movement').MovementSystem;
var startingPos = use('pieces').startingPosition;
var rules = use('rules');
/*──── build board ───────────────────────────────────────────────────*/
var grid = new Grid(8, 8);
grid.width = 8; // (the ctor didn't store them)
grid.height = 8;
var mover = new MovementSystem(grid, rules);
startingPos(grid);
/*──── networking and game state ─────────────────────────────────────*/
var gameState = 'waiting'; // 'waiting', 'searching', 'server_waiting', 'connected'
var isServer = false;
var opponent = null;
var myColor = null; // 'white' or 'black'
var isMyTurn = false;
function updateTitle() {
var title = "Misty Chess - ";
switch(gameState) {
case 'waiting':
title += "Press S to start server or J to join";
break;
case 'searching':
title += "Searching for server...";
break;
case 'server_waiting':
title += "Waiting for player to join...";
break;
case 'connected':
if (myColor) {
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
} else {
title += mover.turn + " turn";
}
break;
}
log.console(title)
}
// Initialize title
updateTitle();
/*──── mouse → click-to-move ─────────────────────────────────────────*/
var selectPos = null;
var hoverPos = null;
var holdingPiece = false;
var opponentMousePos = null;
var opponentHoldingPiece = false;
var opponentSelectPos = null;
function handleMouseButtonDown(e) {
if (e.which != 0) return;
// Don't allow piece selection unless we have an opponent
if (gameState != 'connected' || !opponent) return;
var mx = e.mouse.x;
var my = e.mouse.y;
var c = [Math.floor(mx / 60), Math.floor(my / 60)];
if (!grid.inBounds(c)) return;
var cell = grid.at(c);
if (cell.length && cell[0].colour == mover.turn) {
selectPos = c;
holdingPiece = true;
// Send pickup notification to opponent
if (opponent) {
send(opponent, {
type: 'piece_pickup',
pos: c
});
}
} else {
selectPos = null;
}
}
function handleMouseButtonUp(e) {
if (e.which != 0 || !holdingPiece || !selectPos) return;
// Don't allow moves unless we have an opponent and it's our turn
if (gameState != 'connected' || !opponent || !isMyTurn) {
holdingPiece = false;
return;
}
var mx = e.mouse.x;
var my = e.mouse.y;
var c = [Math.floor(mx / 60), Math.floor(my / 60)];
if (!grid.inBounds(c)) {
holdingPiece = false;
return;
}
if (mover.tryMove(grid.at(selectPos)[0], c)) {
log.console("Made move from", selectPos, "to", c);
// Send move to opponent
log.console("Sending move to opponent:", opponent);
send(opponent, {
type: 'move',
from: selectPos,
to: c
});
isMyTurn = false; // It's now opponent's turn
log.console("Move sent, now opponent's turn");
selectPos = null;
updateTitle();
}
holdingPiece = false;
// Send piece drop notification to opponent
if (opponent) {
send(opponent, {
type: 'piece_drop'
});
}
}
function handleMouseMotion(e) {
var mx = e.pos.x;
var my = e.pos.y;
var c = [Math.floor(mx / 60), Math.floor(my / 60)];
if (!grid.inBounds(c)) {
hoverPos = null;
return;
}
hoverPos = c;
// Send mouse position to opponent in real-time
if (opponent && gameState == 'connected') {
send(opponent, {
type: 'mouse_move',
pos: c,
holding: holdingPiece,
selectPos: selectPos
});
}
}
function handleKeyDown(e) {
// S key - start server
if (e.scancode == 22 && gameState == 'waiting') { // S key
startServer();
}
// J key - join server
else if (e.scancode == 13 && gameState == 'waiting') { // J key
joinServer();
}
}
/*──── drawing helpers ───────────────────────────────────────────────*/
/* ── constants ─────────────────────────────────────────────────── */
var S = 60; // square size in px
var light = [0.93,0.93,0.93,1];
var dark = [0.25,0.25,0.25,1];
var allowedColor = [1.0, 0.84, 0.0, 1.0]; // Gold for allowed moves
var myMouseColor = [0.0, 1.0, 0.0, 1.0]; // Green for my mouse
var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse
/* ── draw one 8×8 chess board ──────────────────────────────────── */
function drawBoard() {
for (var y = 0; y < 8; ++y)
for (var x = 0; x < 8; ++x) {
var isMyHover = hoverPos && hoverPos[0] == x && hoverPos[1] == y;
var isOpponentHover = opponentMousePos && opponentMousePos[0] == x && opponentMousePos[1] == y;
var isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
var color = ((x+y)&1) ? dark : light;
if (isValidMove) {
color = allowedColor; // Gold for allowed moves
} else if (isMyHover && !isOpponentHover) {
color = myMouseColor; // Green for my mouse
} else if (isOpponentHover) {
color = opponentMouseColor; // Red for opponent mouse
}
draw2d.rectangle(
{ x: x*S, y: y*S, width: S, height: S },
{ thickness: 0 },
{ color: color }
);
}
}
function isValidMoveForTurn(from, to) {
if (!grid.inBounds(to)) return false;
var piece = grid.at(from)[0];
if (!piece) return false;
// Check if the destination has a piece of the same color
var destCell = grid.at(to);
if (destCell.length && destCell[0].colour == piece.colour) {
return false;
}
return rules.canMove(piece, from, to, grid);
}
/* ── draw every live piece ─────────────────────────────────────── */
function drawPieces() {
grid.each(function (piece) {
if (piece.captured) return;
// Skip drawing the piece being held (by me or opponent)
if (holdingPiece && selectPos &&
piece.coord[0] == selectPos[0] &&
piece.coord[1] == selectPos[1]) {
return;
}
// Skip drawing the piece being held by opponent
if (opponentHoldingPiece && opponentSelectPos &&
piece.coord[0] == opponentSelectPos[0] &&
piece.coord[1] == opponentSelectPos[1]) {
return;
}
var r = { x: piece.coord[0]*S, y: piece.coord[1]*S,
width:S, height:S };
draw2d.image(piece.sprite, r);
});
// Draw the held piece at the mouse position if we're holding one
if (holdingPiece && selectPos && hoverPos) {
var piece = grid.at(selectPos)[0];
if (piece) {
var r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
width:S, height:S };
draw2d.image(piece.sprite, r);
}
}
// Draw opponent's held piece if they're dragging one
if (opponentHoldingPiece && opponentSelectPos && opponentMousePos) {
var opponentPiece = grid.at(opponentSelectPos)[0];
if (opponentPiece) {
var r = { x: opponentMousePos[0]*S, y: opponentMousePos[1]*S,
width:S, height:S };
// Draw with slight transparency to show it's the opponent's piece
draw2d.image(opponentPiece.sprite, r);
}
}
}
function update(dt)
{
return {}
}
function draw()
{
draw2d.clear()
drawBoard()
drawPieces()
return draw2d.get_commands()
}
function startServer() {
gameState = 'server_waiting';
isServer = true;
myColor = 'white';
isMyTurn = true;
updateTitle();
$_.portal(e => {
log.console("Portal received contact message");
// Reply with this actor to establish connection
log.console (json.encode($_))
send(e, $_);
log.console("Portal replied with server actor");
}, 5678);
}
function joinServer() {
gameState = 'searching';
updateTitle();
function contact_fn(actor, reason) {
log.console("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
if (actor) {
opponent = actor;
log.console("Connection established with server, sending join request");
// Send a greet message with our actor object
send(opponent, {
type: 'greet',
client_actor: $_
});
} else {
log.console(`Failed to connect: ${json.encode(reason)}`);
gameState = 'waiting';
updateTitle();
}
}
$_.contact(contact_fn, {
address: "192.168.0.149",
port: 5678
});
}
$_.receiver(e => {
if (e.kind == 'update')
send(e, update(e.dt))
else if (e.kind == 'draw')
send(e, draw())
else if (e.type == 'game_start' || e.type == 'move' || e.type == 'greet')
log.console("Receiver got message:", e.type, e);
if (e.type == 'greet') {
log.console("Server received greet from client");
// Store the client's actor object for ongoing communication
opponent = e.client_actor;
log.console("Stored client actor:", json.encode(opponent));
gameState = 'connected';
updateTitle();
// Send game_start to the client
log.console("Sending game_start to client");
send(opponent, {
type: 'game_start',
your_color: 'black'
});
log.console("game_start message sent to client");
}
else if (e.type == 'game_start') {
log.console("Game starting, I am:", e.your_color);
myColor = e.your_color;
isMyTurn = (myColor == 'white');
gameState = 'connected';
updateTitle();
} else if (e.type == 'move') {
log.console("Received move from opponent:", e.from, "to", e.to);
// Apply opponent's move
var fromCell = grid.at(e.from);
if (fromCell.length) {
var piece = fromCell[0];
if (mover.tryMove(piece, e.to)) {
isMyTurn = true; // It's now our turn
updateTitle();
log.console("Applied opponent move, now my turn");
} else {
log.console("Failed to apply opponent move");
}
} else {
log.console("No piece found at from position");
}
} else if (e.type == 'mouse_move') {
// Update opponent's mouse position
opponentMousePos = e.pos;
opponentHoldingPiece = e.holding;
opponentSelectPos = e.selectPos;
} else if (e.type == 'piece_pickup') {
// Opponent picked up a piece
opponentSelectPos = e.pos;
opponentHoldingPiece = true;
} else if (e.type == 'piece_drop') {
// Opponent dropped their piece
opponentHoldingPiece = false;
opponentSelectPos = null;
} else if (e.type == 'mouse_button_down') {
handleMouseButtonDown(e)
} else if (e.type == 'mouse_button_up') {
handleMouseButtonUp(e)
} else if (e.type == 'mouse_motion') {
handleMouseMotion(e)
} else if (e.type == 'key_down') {
handleKeyDown(e)
}
})

View File

@@ -1,9 +0,0 @@
// Chess game configuration for Moth framework
return {
title: "Chess",
resolution: { width: 480, height: 480 },
internal_resolution: { width: 480, height: 480 },
fps: 60,
clearColor: [22/255, 120/255, 194/255, 1],
mode: 'stretch' // No letterboxing for chess
};

View File

@@ -1,69 +0,0 @@
function grid(w, h) {
this.width = w;
this.height = h;
// create a height×width array of empty lists
this.cells = new Array(h);
for (let y = 0; y < h; y++) {
this.cells[y] = new Array(w);
for (let x = 0; x < w; x++) {
this.cells[y][x] = []; // each cell holds its own list
}
}
}
grid.prototype = {
// return the array at (x,y)
cell(x, y) {
return this.cells[y][x];
},
// alias for cell
at(pos) {
return this.cell(pos.x, pos.y);
},
// add an entity into a cell
add(entity, pos) {
this.cell(pos.x, pos.y).push(entity);
entity.coord = pos.slice();
},
// remove an entity from a cell
remove(entity, pos) {
const c = this.cell(pos.x, pos.y);
const i = c.indexOf(entity);
if (i !== -1) c.splice(i, 1);
},
// bounds check
inBounds(pos) {
return (
pos.x >= 0 && pos.x < this.width &&
pos.y >= 0 && pos.y < this.height
);
},
// call fn(entity, coord) for every entity in every cell
each(fn) {
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const list = this.cells[y][x];
for (let entity of list) {
fn(entity, entity.coord);
}
}
}
},
// printable representation
toString() {
let out = `grid [${this.width}×${this.height}]\n`;
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
out += this.cells[y][x].length;
}
if (y !== this.height - 1) out += "\n";
}
return out;
}
};

View File

@@ -1,32 +0,0 @@
var MovementSystem = function(grid, rules) {
this.grid = grid;
this.rules = rules || {}; // expects { canMove: fn }
this.turn = 'white';
}
MovementSystem.prototype.tryMove = function (piece, to) {
if (piece.colour != this.turn) return false;
// normalise to into our hybrid coord
var dest = [to.x ?? t[0],
to.y ?? to[1]];
if (!this.grid.inBounds(dest)) return false;
if (!this.rules.canMove(piece, piece.coord, dest, this.grid)) return false;
var victims = this.grid.at(dest);
if (victims.length && victims[0].colour == piece.colour) return false;
if (victims.length) victims[0].captured = true;
this.grid.remove(piece, piece.coord);
this.grid.add (piece, dest);
// grid.add() re-creates coord; re-add .x/.y fields:
piece.coord.x = dest.x;
piece.coord.y = dest.y;
this.turn = (this.turn == 'white') ? 'black' : 'white';
return true;
};
return { MovementSystem: MovementSystem };

View File

@@ -1,29 +0,0 @@
/* pieces.js simple data holders + starting layout */
function Piece(kind, colour) {
this.kind = kind; // "pawn" etc.
this.colour = colour; // "white"/"black"
this.sprite = colour + '_' + kind; // for draw2d.image
this.captured = false;
this.coord = [0,0];
}
Piece.prototype.toString = function () {
return this.colour.charAt(0) + this.kind.charAt(0).toUpperCase();
};
function startingPosition(grid) {
var W = 'white', B = 'black', x;
// pawns
for (x = 0; x < 8; x++) {
grid.add(new Piece('pawn', W), [x, 6]);
grid.add(new Piece('pawn', B), [x, 1]);
}
// major pieces
var back = ['rook','knight','bishop','queen','king','bishop','knight','rook'];
for (x = 0; x < 8; x++) {
grid.add(new Piece(back[x], W), [x, 7]);
grid.add(new Piece(back[x], B), [x, 0]);
}
}
return { Piece, startingPosition };

Binary file not shown.

View File

@@ -1,45 +0,0 @@
/* helper robust coord access */
function cx(c) { return c.x ?? c[0] }
function cy(c) { return c.y ?? c[1] }
/* simple move-shape checks */
var deltas = {
pawn: function (pc, dx, dy, grid, to) {
var dir = (pc.colour == 'white') ? -1 : 1;
var base = (pc.colour == 'white') ? 6 : 1;
var one = (dy == dir && dx == 0 && grid.at(to).length == 0);
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir }).length == 0 &&
grid.at(to).length == 0);
var cap = (dy == dir && Math.abs(dx) == 1 && grid.at(to).length);
return one || two || cap;
},
rook : function (pc, dx, dy) { return (dx == 0 || dy == 0); },
bishop: function (pc, dx, dy) { return Math.abs(dx) == Math.abs(dy); },
queen : function (pc, dx, dy) { return (dx == 0 || dy == 0 || Math.abs(dx) == Math.abs(dy)); },
knight: function (pc, dx, dy) { return (Math.abs(dx) == 1 && Math.abs(dy) == 2) ||
(Math.abs(dx) == 2 && Math.abs(dy) == 1); },
king : function (pc, dx, dy) { return Math.max(Math.abs(dx), Math.abs(dy)) == 1; }
};
function clearLine(from, to, grid) {
var dx = Math.sign(cx(to) - cx(from));
var dy = Math.sign(cy(to) - cy(from));
var x = cx(from) + dx, y = cy(from) + dy;
while (x != cx(to) || y != cy(to)) {
if (grid.at({ x: x, y: y }).length) return false;
x += dx; y += dy;
}
return true;
}
function canMove(piece, from, to, grid) {
var dx = cx(to) - cx(from);
var dy = cy(to) - cy(from);
var f = deltas[piece.kind];
if (!f || !f(piece, dx, dy, grid, to)) return false;
if (piece.kind == 'knight') return true;
return clearLine(from, to, grid);
}
return { canMove };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

View File

@@ -1,5 +0,0 @@
return {
title: "Pong",
width: 858,
height: 525
}

View File

@@ -1,86 +0,0 @@
// main.js
var draw = use('draw2d')
var input = use('controller')
var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0]
var paddleW = 10, paddleH = 80
var p1 = {x: 30, y: config.height*0.5, speed: 300}
var p2 = {x: config.width-30, y: config.height*0.5, speed: 300}
var ball = {x: 0, y: 0, vx: 220, vy: 150, size: 10}
var score1 = 0, score2 = 0
function resetBall() {
ball.x = config.width*0.5
ball.y = config.height*0.5
// give it a random vertical bounce
ball.vy = (Math.random()<0.5 ? -1:1)*150
// keep horizontal speed to the same magnitude
ball.vx = ball.vx>0 ? 220 : -220
}
resetBall()
this.update = function(dt) {
// Move paddles: positive Y is up, so W/↑ means p.y += speed
if (input.keyboard.down('w')) p1.y += p1.speed*dt
if (input.keyboard.down('s')) p1.y -= p1.speed*dt
// Paddle 2 movement (ArrowUp = up, ArrowDown = down)
if (input.keyboard.down('i')) p2.y += p2.speed*dt
if (input.keyboard.down('k')) p2.y -= p2.speed*dt
// Clamp paddles to screen
if (p1.y < paddleH*0.5) p1.y = paddleH*0.5
if (p1.y > config.height - paddleH*0.5) p1.y = config.height - paddleH*0.5
if (p2.y < paddleH*0.5) p2.y = paddleH*0.5
if (p2.y > config.height - paddleH*0.5) p2.y = config.height - paddleH*0.5
// Move ball
ball.x += ball.vx*dt
ball.y += ball.vy*dt
// Bounce top/bottom
if (ball.y+ball.size*0.5>config.height || ball.y-ball.size*0.5<0) ball.vy = -ball.vy
// Check paddle collisions
// p1 bounding box
var left1 = p1.x - paddleW*0.5, right1 = p1.x + paddleW*0.5
var top1 = p1.y + paddleH*0.5, bottom1 = p1.y - paddleH*0.5
// p2 bounding box
var left2 = p2.x - paddleW*0.5, right2 = p2.x + paddleW*0.5
var top2 = p2.y + paddleH*0.5, bottom2 = p2.y - paddleH*0.5
// ball half-edges
var l = ball.x - ball.size*0.5, r = ball.x + ball.size*0.5
var b = ball.y - ball.size*0.5, t = ball.y + ball.size*0.5
// Collide with paddle 1?
if (r>left1 && l<right1 && t>bottom1 && b<top1)
ball.vx = Math.abs(ball.vx)
// Collide with paddle 2?
if (r>left2 && l<right2 && t>bottom2 && b<top2)
ball.vx = -Math.abs(ball.vx)
// Check left/right out-of-bounds
if (r<0) { score2++; resetBall() }
if (l>config.width) { score1++; resetBall() }
}
this.hud = function() {
// Clear screen black
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
// Draw paddles
draw.rectangle({x:p1.x - paddleW*0.5, y:p1.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
draw.rectangle({x:p2.x - paddleW*0.5, y:p2.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
// Draw ball
draw.rectangle({x:ball.x - ball.size*0.5, y:ball.y - ball.size*0.5, width:ball.size, height:ball.size}, color.white)
// Simple score display
var msg = score1 + " " + score2
draw.text(msg, {x:0, y:10, width:config.width, height:40}, null, 0, color.white, 0)
}

View File

@@ -1,5 +0,0 @@
return {
title: "Snake",
width: 600,
height: 600
}

View File

@@ -1,119 +0,0 @@
// main.js
var draw = use('draw2d')
var render = use('render')
var graphics = use('graphics')
var input = use('input')
var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0]
var cellSize = 20
var gridW = Math.floor(config.width / cellSize)
var gridH = Math.floor(config.height / cellSize)
var snake, direction, nextDirection, apple
var moveInterval = 0.1
var moveTimer = 0
var gameState = "playing"
function resetGame() {
var cx = Math.floor(gridW / 2)
var cy = Math.floor(gridH / 2)
snake = [
{x: cx, y: cy},
{x: cx-1, y: cy},
{x: cx-2, y: cy}
]
direction = {x:1, y:0}
nextDirection = {x:1, y:0}
spawnApple()
gameState = "playing"
moveTimer = 0
}
function spawnApple() {
apple = {x:Math.floor(Math.random()*gridW), y:Math.floor(Math.random()*gridH)}
// Re-spawn if apple lands on snake
for (var i=0; i<snake.length; i++)
if (snake[i].x == apple.x && snake[i].y == apple.y) { spawnApple(); return }
}
function wrap(pos) {
if (pos.x < 0) pos.x = gridW - 1
if (pos.x >= gridW) pos.x = 0
if (pos.y < 0) pos.y = gridH - 1
if (pos.y >= gridH) pos.y = 0
}
resetGame()
this.update = function(dt) {
if (gameState != "playing") return
moveTimer += dt
if (moveTimer < moveInterval) return
moveTimer -= moveInterval
// Update direction
direction = {x: nextDirection.x, y: nextDirection.y}
// New head
var head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y}
wrap(head)
// Check collision with body
for (var i=0; i<snake.length; i++) {
if (snake[i].x == head.x && snake[i].y == head.y) {
gameState = "gameover"
return
}
}
// Place head
snake.unshift(head)
// Eat apple?
if (head.x == apple.x && head.y == apple.y) spawnApple()
else snake.pop()
}
this.hud = function() {
// Optional clear screen
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
// Draw snake
for (var i=0; i<snake.length; i++) {
var s = snake[i]
draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, color.green)
}
// Draw apple
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, color.red)
if (gameState == "gameover") {
var msg = "GAME OVER! Press SPACE to restart."
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, null, 0, color.white)
}
}
// No immediate reversal
// "Up" means y=1, so going physically up on screen
this.inputs = {
up: function() {
if (direction.y != -1) nextDirection = {x:0,y:1}
},
down: function() {
if (direction.y != 1) nextDirection = {x:0,y:-1}
},
left: function() {
if (direction.x != 1) nextDirection = {x:-1,y:0}
},
right: function() {
if (direction.x != -1) nextDirection = {x:1,y:0}
},
space: function() {
if (gameState=="gameover") resetGame()
}
}
input.player[0].control(this)

View File

@@ -1,187 +0,0 @@
// Steam Integration Example
// This example shows how to use Steam achievements and stats
var steam = use("steam");
// Achievement names (these should match your Steam app configuration)
var ACHIEVEMENTS = {
FIRST_WIN: "ACH_FIRST_WIN",
PLAY_10_GAMES: "ACH_PLAY_10_GAMES",
HIGH_SCORE: "ACH_HIGH_SCORE_1000"
};
// Stat names
var STATS = {
GAMES_PLAYED: "stat_games_played",
TOTAL_SCORE: "stat_total_score",
PLAY_TIME: "stat_play_time"
};
var steam_available = false;
var stats_loaded = false;
// Initialize Steam
function init_steam() {
if (!steam) {
log.console("Steam module not available");
return false;
}
log.console("Initializing Steam...");
steam_available = steam.steam_init();
if (steam_available) {
log.console("Steam initialized successfully");
// Request current stats/achievements
if (steam.stats.stats_request()) {
log.console("Stats requested");
stats_loaded = true;
}
} else {
log.console("Failed to initialize Steam");
}
return steam_available;
}
// Update Steam (call this regularly, e.g., once per frame)
function update_steam() {
if (steam_available) {
steam.steam_run_callbacks();
}
}
// Unlock an achievement
function unlock_achievement(achievement_name) {
if (!steam_available || !stats_loaded) return false;
// Check if already unlocked
var unlocked = steam.achievement.achievement_get(achievement_name);
if (unlocked) {
log.console("Achievement already unlocked:", achievement_name);
return true;
}
// Unlock it
if (steam.achievement.achievement_set(achievement_name)) {
log.console("Achievement unlocked:", achievement_name);
// Store stats to make it permanent
steam.stats.stats_store();
return true;
}
return false;
}
// Update a stat
function update_stat(stat_name, value, is_float) {
if (!steam_available || !stats_loaded) return false;
var success;
if (is_float) {
success = steam.stats.stats_set_float(stat_name, value);
} else {
success = steam.stats.stats_set_int(stat_name, value);
}
if (success) {
log.console("Stat updated:", stat_name, "=", value);
steam.stats.stats_store();
}
return success;
}
// Get a stat value
function get_stat(stat_name, is_float) {
if (!steam_available || !stats_loaded) return 0;
if (is_float) {
return steam.stats.stats_get_float(stat_name) || 0;
} else {
return steam.stats.stats_get_int(stat_name) || 0;
}
}
// Example game logic
var games_played = 0;
var total_score = 0;
var current_score = 0;
function start_game() {
games_played = get_stat(STATS.GAMES_PLAYED, false);
total_score = get_stat(STATS.TOTAL_SCORE, false);
current_score = 0;
log.console("Starting game #" + (games_played + 1));
}
function end_game(score) {
current_score = score;
games_played++;
total_score += score;
// Update stats
update_stat(STATS.GAMES_PLAYED, games_played, false);
update_stat(STATS.TOTAL_SCORE, total_score, false);
// Check for achievements
if (games_played == 1) {
unlock_achievement(ACHIEVEMENTS.FIRST_WIN);
}
if (games_played >= 10) {
unlock_achievement(ACHIEVEMENTS.PLAY_10_GAMES);
}
if (score >= 1000) {
unlock_achievement(ACHIEVEMENTS.HIGH_SCORE);
}
}
// Cloud save example
function save_to_cloud(save_data) {
if (!steam_available) return false;
var json_data = JSON.stringify(save_data);
return steam.cloud.cloud_write("savegame.json", json_data);
}
function load_from_cloud() {
if (!steam_available) return null;
var data = steam.cloud.cloud_read("savegame.json");
if (data) {
// Convert ArrayBuffer to string
var decoder = new TextDecoder();
var json_str = decoder.decode(data);
return JSON.parse(json_str);
}
return null;
}
// Cleanup
function cleanup_steam() {
if (steam_available) {
steam.steam_shutdown();
log.console("Steam shut down");
}
}
// Export the API
module.exports = {
init: init_steam,
update: update_steam,
cleanup: cleanup_steam,
unlock_achievement: unlock_achievement,
update_stat: update_stat,
get_stat: get_stat,
start_game: start_game,
end_game: end_game,
save_to_cloud: save_to_cloud,
load_from_cloud: load_from_cloud,
is_available: function() { return steam_available; }
};

View File

@@ -1,5 +0,0 @@
return {
title: "Tetris",
width:160,
height:144
}

View File

@@ -1,271 +0,0 @@
var draw = use('draw2d')
var input = use('input')
var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0]
// Board constants
var COLS = 10, ROWS = 20
var TILE = 6 // each cell is 6x6
// Board storage (2D), each cell is either 0 or a [r,g,b,a] color
var board = []
// Gravity timing
var baseGravity = 0.8 // seconds between drops at level 0
var gravityTimer = 0
// Current piece & position
var piece = null
var pieceX = 0
var pieceY = 0
// Next piece
var nextPiece = null
// Score/lines/level
var score = 0
var linesCleared = 0
var level = 0
// Rotation lock to prevent spinning with W
var rotateHeld = false
var gameOver = false
// Horizontal movement gating
var hMoveTimer = 0
var hDelay = 0.2 // delay before repeated moves begin
var hRepeat = 0.05 // time between repeated moves
var prevLeft = false
var prevRight = false
// Tetrimino definitions
var SHAPES = {
I: { color:[0,1,1,1], blocks:[[0,0],[1,0],[2,0],[3,0]] },
O: { color:[1,1,0,1], blocks:[[0,0],[1,0],[0,1],[1,1]] },
T: { color:[1,0,1,1], blocks:[[0,0],[1,0],[2,0],[1,1]] },
S: { color:[0,1,0,1], blocks:[[1,0],[2,0],[0,1],[1,1]] },
Z: { color:[1,0,0,1], blocks:[[0,0],[1,0],[1,1],[2,1]] },
J: { color:[0,0,1,1], blocks:[[0,0],[0,1],[1,1],[2,1]] },
L: { color:[1,0.5,0,1], blocks:[[2,0],[0,1],[1,1],[2,1]] }
}
var shapeKeys = Object.keys(SHAPES)
// Initialize board with empty (0)
function initBoard() {
board = []
for (var r=0; r<ROWS; r++) {
var row = []
for (var c=0; c<COLS; c++) row.push(0)
board.push(row)
}
}
initBoard()
function randomShape() {
var key = shapeKeys[Math.floor(Math.random()*shapeKeys.length)]
// Make a copy of the shapes blocks
return {
type: key,
color: SHAPES[key].color,
blocks: SHAPES[key].blocks.map(b => [b[0], b[1]])
}
}
function spawnPiece() {
piece = nextPiece || randomShape()
nextPiece = randomShape()
pieceX = 3
pieceY = 0
// Collision on spawn => game over
if (collides(pieceX, pieceY, piece.blocks)) gameOver = true
}
function collides(px, py, blocks) {
for (var i=0; i<blocks.length; i++) {
var x = px + blocks[i][0]
var y = py + blocks[i][1]
if (x<0 || x>=COLS || y<0 || y>=ROWS) return true
if (y>=0 && board[y][x]) return true
}
return false
}
// Lock piece into board
function lockPiece() {
for (var i=0; i<piece.blocks.length; i++) {
var x = pieceX + piece.blocks[i][0]
var y = pieceY + piece.blocks[i][1]
if (y>=0) board[y][x] = piece.color
}
}
// Rotate 90° clockwise
function rotate(blocks) {
// (x,y) => (y,-x)
for (var i=0; i<blocks.length; i++) {
var x = blocks[i][0]
var y = blocks[i][1]
blocks[i][0] = y
blocks[i][1] = -x
}
}
function clearLines() {
var lines = 0
for (var r=ROWS-1; r>=0;) {
if (board[r].every(cell => cell)) {
lines++
// remove row
board.splice(r,1)
// add empty row on top
var newRow = []
for (var c=0; c<COLS; c++) newRow.push(0)
board.unshift(newRow)
} else {
r--
}
}
// Score
if (lines==1) score += 100
else if (lines==2) score += 300
else if (lines==3) score += 500
else if (lines==4) score += 800
linesCleared += lines
level = Math.floor(linesCleared/10)
}
function placePiece() {
lockPiece()
clearLines()
spawnPiece()
}
// Hard drop
function hardDrop() {
while(!collides(pieceX, pieceY+1, piece.blocks)) pieceY++
placePiece()
}
spawnPiece()
this.update = function(dt) {
if (gameOver) return
// ======= Horizontal Movement Gate =======
var leftPressed = input.keyboard.down('a')
var rightPressed = input.keyboard.down('d')
var horizontalMove = 0
// If user just pressed A, move once & start gating
if (leftPressed && !prevLeft) {
horizontalMove = -1
hMoveTimer = hDelay
}
// If user is holding A & the timer is up, move again, then reset timer to repeat
else if (leftPressed && hMoveTimer <= 0) {
horizontalMove = -1
hMoveTimer = hRepeat
}
// Same logic for D
if (rightPressed && !prevRight) {
horizontalMove = 1
hMoveTimer = hDelay
} else if (rightPressed && hMoveTimer <= 0) {
horizontalMove = 1
hMoveTimer = hRepeat
}
// Move horizontally if it doesn't collide
if (horizontalMove < 0 && !collides(pieceX-1, pieceY, piece.blocks)) pieceX--
else if (horizontalMove > 0 && !collides(pieceX+1, pieceY, piece.blocks)) pieceX++
// If neither A nor D is pressed, reset the timer so next press is immediate
if (!leftPressed && !rightPressed) {
hMoveTimer = 0
}
// Decrement horizontal timer
hMoveTimer -= dt
prevLeft = leftPressed
prevRight = rightPressed
// ======= End Horizontal Movement Gate =======
// Rotate with W (once per press, no spinning)
if (input.keyboard.down('w')) {
if (!rotateHeld) {
rotateHeld = true
var test = piece.blocks.map(b => [b[0], b[1]])
rotate(test)
if (!collides(pieceX, pieceY, test)) piece.blocks = test
}
} else {
rotateHeld = false
}
// Soft drop if S is held (accelerates gravity)
var fallSpeed = input.keyboard.down('s') ? 10 : 1
// Gravity
gravityTimer += dt * fallSpeed
var dropInterval = Math.max(0.1, baseGravity - level*0.05)
if (gravityTimer >= dropInterval) {
gravityTimer = 0
if (!collides(pieceX, pieceY+1, piece.blocks)) {
pieceY++
} else {
placePiece()
}
}
// Hard drop if space is held
if (input.keyboard.down('space')) {
// hardDrop()
}
}
this.hud = function() {
// Clear screen
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
// Draw board
for (var r=0; r<ROWS; r++) {
for (var c=0; c<COLS; c++) {
var cell = board[r][c]
if (!cell) continue
draw.rectangle({x:c*TILE, y:(ROWS-1-r)*TILE, width:TILE, height:TILE}, cell)
}
}
// Draw falling piece
if (!gameOver && piece) {
for (var i=0; i<piece.blocks.length; i++) {
var x = pieceX + piece.blocks[i][0]
var y = pieceY + piece.blocks[i][1]
draw.rectangle({x:x*TILE, y:(ROWS-1-y)*TILE, width:TILE, height:TILE}, piece.color)
}
}
// Next piece window
draw.text("Next", {x:70, y:5, width:50, height:10}, null, 0, color.white)
if (nextPiece) {
for (var i=0; i<nextPiece.blocks.length; i++) {
var nx = nextPiece.blocks[i][0]
var ny = nextPiece.blocks[i][1]
var dx = 12 + nx
var dy = 16 - ny
draw.rectangle({x:dx*TILE, y:(ROWS-1-dy)*TILE, width:TILE, height:TILE}, nextPiece.color)
}
}
// Score & Level
var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level
draw.text(info, {x:70, y:30, width:90, height:50}, null, 0, color.white)
if (gameOver) {
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, null, 0, color.red)
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,248 +0,0 @@
var geometry = this
geometry[cell.DOC] = `
A collection of geometry-related functions for circles, spheres, boxes, polygons,
and rectangle utilities. Some functionality is implemented in C and exposed here.
`
var math = use('math')
geometry.box = {}
geometry.box[cell.DOC] = `
An object for box-related operations. Overridden later by a function definition, so
its direct usage is overshadowed. Contains:
- points(ll, ur): Return an array of four 2D points for a box from ll (lower-left) to ur (upper-right).
`
geometry.box.points = function (ll, ur) {
return [ll, ll.add([ur.x - ll.x, 0]), ur, ll.add([0, ur.y - ll.y])]
}
geometry.box.points[cell.DOC] = `
:param ll: Lower-left coordinate as a 2D vector (x,y).
:param ur: Upper-right coordinate as a 2D vector (x,y).
:return: An array of four points forming the corners of the box in order [ll, lower-right, ur, upper-left].
Compute the four corners of a box given lower-left and upper-right corners.
`
geometry.sphere = {}
geometry.sphere[cell.DOC] = `
Sphere-related geometry functions:
- volume(r): Return the volume of a sphere with radius r.
- random(r, theta, phi): Return a random point on or inside a sphere.
`
geometry.circle = {}
geometry.circle[cell.DOC] = `
Circle-related geometry functions:
- area(r): Return the area of a circle with radius r.
- random(r, theta): Return a random 2D point on a circle; uses sphere.random internally and extracts x,z.
`
geometry.sphere.volume = function (r) {
return (Math.pi * r * r * r * 4) / 3
}
geometry.sphere.volume[cell.DOC] = `
:param r: The sphere radius.
:return: The volume of the sphere, calculated as (4/3) * pi * r^3.
`
geometry.sphere.random = function (r, theta = [0, 1], phi = [-0.5, 0.5]) {
if (typeof r == "number") r = [r, r]
if (typeof theta == "number") theta = [theta, theta]
if (typeof phi == "number") phi = [phi, phi]
var ra = Math.random_range(r[0], r[1])
var ta = Math.turn2rad(Math.random_range(theta[0], theta[1]))
var pa = Math.turn2rad(Math.random_range(phi[0], phi[1]))
return [ra * Math.sin(ta) * Math.cos(pa), ra * Math.sin(ta) * Math.sin(pa), ra * Math.cos(ta)]
}
geometry.sphere.random[cell.DOC] = `
:param r: A single number (radius) or a 2-element array [minRadius, maxRadius].
:param theta: A single number or 2-element array defining the range in turns for the theta angle, default [0,1].
:param phi: A single number or 2-element array defining the range in turns for the phi angle, default [-0.5,0.5].
:return: A 3D point (x,y,z) randomly placed within a sphere.
Generate a random point inside a sphere of variable radius, distributing angles in the specified ranges.
`
geometry.circle.area = function (r) {
return Math.pi * r * r
}
geometry.circle.area[cell.DOC] = `
:param r: Radius of the circle.
:return: The area, pi * r^2.
`
geometry.circle.random = function (r, theta) {
return geometry.sphere.random(r, theta).xz
}
geometry.circle.random[cell.DOC] = `
:param r: A radius or [minRadius, maxRadius].
:param theta: Angle range in turns (single number or [min,max]).
:return: A 2D point (x,z) in the circle, using the sphere random generator and ignoring y.
`
geometry.box = function (w, h) {
w /= 2
h /= 2
var points = [
[w, h],
[-w, h],
[-w, -h],
[w, -h],
]
return points
}
geometry.box[cell.DOC] = `
:param w: The width of the box.
:param h: The height of the box.
:return: An array of four 2D points representing the corners of a rectangle centered at [0,0].
Construct a box centered at the origin with the given width and height. This overrides the box object above.
`
geometry.ngon = function (radius, n) {
return geometry.arc(radius, 360, n)
}
geometry.ngon[cell.DOC] = `
:param radius: The radius of the n-gon from center to each vertex.
:param n: Number of sides/vertices.
:return: An array of 2D points forming a regular n-gon.
Generates a regular n-gon by calling geometry.arc with full 360 degrees.
`
geometry.arc = function (radius, angle, n, start = 0) {
start = Math.deg2rad(start)
if (angle >= 360) angle = 360
if (n <= 1) return []
var points = []
angle = Math.deg2rad(angle)
var arclen = angle / n
for (var i = 0; i < n; i++) points.push(math.rotate([radius, 0], start + arclen * i))
return points
}
geometry.arc[cell.DOC] = `
:param radius: The distance from center to the arc points.
:param angle: The total angle (in degrees) over which points are generated, capped at 360.
:param n: Number of segments (if <=1, empty array is returned).
:param start: Starting angle (in degrees), default 0.
:return: An array of 2D points along the arc.
Generate an arc (or partial circle) of n points, each angle spread equally over 'angle' degrees from 'start'.
`
geometry.circle.points = function (radius, n) {
if (n <= 1) return []
return geometry.arc(radius, 360, n)
}
geometry.circle.points[cell.DOC] = `
:param radius: The circle's radius.
:param n: Number of points around the circle.
:return: An array of 2D points equally spaced around a full 360-degree circle.
Shortcut for geometry.arc(radius, 360, n).
`
geometry.corners2points = function (ll, ur) {
return [ll, ll.add([ur.x, 0]), ur, ll.add([0, ur.y])]
}
geometry.corners2points[cell.DOC] = `
:param ll: Lower-left 2D coordinate.
:param ur: Upper-right 2D coordinate (relative offset in x,y).
:return: A four-point array of corners [ll, lower-right, upper-right, upper-left].
Similar to box.points, but calculates differently.
`
geometry.sortpointsccw = function (points) {
var cm = points2cm(points)
var cmpoints = points.map(function (x) { return x.sub(cm) })
var ccw = cmpoints.sort(function (a, b) {
var aatan = Math.atan2(a.y, a.x)
var batan = Math.atan2(b.y, b.x)
return aatan - batan
})
return ccw.map(function (x) { return x.add(cm) })
}
geometry.sortpointsccw[cell.DOC] = `
:param points: An array of 2D points.
:return: A new array of the same points, sorted counterclockwise around their centroid.
Sort an array of points in CCW order based on their angles from the centroid.
`
function points2cm(pts) {
var x = 0
var y = 0
var n = pts.length
pts.forEach(function (p) {
x += p[0]
y += p[1]
})
return [x / n, y / n]
}
geometry.points2cm = function(points) {
var x = 0
var y = 0
var n = points.length
points.forEach(function (p) {
x += p[0]
y += p[1]
})
return [x / n, y / n]
}
geometry.points2cm[cell.DOC] = `
:param points: An array of 2D points.
:return: The centroid (average x,y) of the given points.
`
geometry.rect_intersection[cell.DOC] = `
:param a: The first rectangle as {x, y, w, h}.
:param b: The second rectangle as {x, y, w, h}.
:return: A rectangle that is the intersection of the two. May have zero width/height if no overlap.
Return the intersection of two rectangles. The result may be empty if no intersection.
`
geometry.rect_intersects[cell.DOC] = `
:param a: Rectangle {x,y,w,h}.
:param b: Rectangle {x,y,w,h}.
:return: A boolean indicating whether the two rectangles overlap.
`
geometry.rect_expand[cell.DOC] = `
:param a: Rectangle {x,y,w,h}.
:param b: Rectangle {x,y,w,h}.
:return: A new rectangle that covers the bounds of both input rectangles.
Merge or combine two rectangles, returning their bounding rectangle.
`
geometry.rect_inside[cell.DOC] = `
:param inner: A rectangle to test.
:param outer: A rectangle that may contain 'inner'.
:return: True if 'inner' is completely inside 'outer', otherwise false.
`
geometry.rect_random[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}.
:return: A random point within the rectangle (uniform distribution).
`
geometry.cwh2rect[cell.DOC] = `
:param center: A 2D point [cx, cy].
:param wh: A 2D size [width, height].
:return: A rectangle {x, y, w, h} with x,y set to center and w,h set to the given size.
Helper: convert a center point and width/height vector to a rect object.
`
geometry.rect_point_inside[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}.
:param point: A 2D point [px, py].
:return: True if the point lies inside the rectangle, otherwise false.
`
geometry.rect_pos[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}.
:return: A 2D vector [x,y] giving the rectangle's position.
`
geometry.rect_move[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}.
:param offset: A 2D vector to add to the rectangle's position.
:return: A new rectangle with updated x,y offset.
`
return geometry

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Some files were not shown because too many files have changed in this diff Show More